Error when a form with a select field is submitted - spring

In my current spring project, my forms are implement with a structure like this:
<form class="form" id="Pagina" role="form" method="POST" action="/loja/Pagina/cadastra" enctype="multipart/form-data">
...
</form>
and it's processed in server by this methos:
controller
#RequestMapping(value="cadastra", method=RequestMethod.POST)
#ResponseBody
#ResponseStatus(HttpStatus.CREATED)
public E cadastra(#ModelAttribute("object") E object, BindingResult result, #RequestParam(value="file", required=false) MultipartFile file, #RequestParam(value="icone", required=false) MultipartFile icone, #RequestParam(value="screenshot", required=false) MultipartFile screenshot[]) throws Exception {
E ret = serv.cadastra(object, file, icone, screenshot);
if (ret != null)
return ret;
else
throw new Exception();
}
service
#PreAuthorize("hasPermission(#user, 'cadastra_'+#this.this.name)")
#Transactional
public E cadastra(E e, MultipartFile file, MultipartFile icone, MultipartFile[] screenshot) {
return dao.persist(e);
}
My problem it's when the form have a field like this:
<label>pagina</label>
<select name="pagina.id" class="form-control select" data-lista="/loja/Pagina/listagem.json">
...
</select>
<label>produto</label>
<select name="produto.id" class="form-control select" data-lista="/loja/Produto/listagem.json">
...
</select>
which maps a atribute like this in the entiy class:
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name="pagina_mae", nullable = true)
#Order(value=5)
#Select(name="pagina", ordem = 5)
#Sidebar
private Pagina pagina;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name="produto_mae", nullable = true)
#Order(value=6)
#Select(name="produto", ordem = 6)
#Sidebar
private Produto produto;
Where the options inside looks like this:
<option value="">.</option>
<option value="...">...</option>
If I submit the form with the blank option selected, I get this error:
object references an unsaved transient instance - save the transient instance before flushing: com.spring.loja.model.pagina.persistence.model.Pagina; nested exception is org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.spring.loja.model.pagina.persistence.model.Pagina
but if, for instance, insert a record manually in the database (in my case, using pgAdmin3), and select this item in the select, the form is submitted without errors.
Anyone can tell me how I fix that, to allow me submit the form with or without selected data from the <select>.
UPDATE
code for the class Pagina:
#Entity
#Table(name="pagina")
#MainForm(grupo = 2, icone = "file")
public class Pagina extends ModelEntity {
#Id
#Column(name = "id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
#Column(name = "nome", unique = true)
#Order(value=1)
#Input(type="hidden", name="nome", ordem = 1)
private String nome;
#Column(name = "titulo", nullable = false)
#Order(value=2)
#Input(name="titulo", ordem = 2)
private String titulo;
#Column(name = "descricao", length=65535)
#Order(value=4)
#Textarea(name="descricao", ordem = 4)
private String descricao;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name="pagina_mae", nullable = true)
#Order(value=5)
#Select(name="pagina", ordem = 5)
#Sidebar
private Pagina pagina;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name="produto_mae", nullable = true)
#Order(value=6)
#Select(name="produto", ordem = 6)
#Sidebar
private Produto produto;
}
UPDATE 2
PaginaEditor.java
#Component
public class PaginaEditor extends PropertyEditorSupport {
#Inject
private PaginaService paginaService;
#Override
public void setAsText(String text) {
if (!text.isEmpty()) {
Pagina pagina = paginaService.getObject(text);
setValue(pagina);
}
}
}
method added to my controller:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Pagina.class, new PaginaEditor());
}

Selects are tricky ones in Spring MVC.
I think your problem is that when your main entity is getting to the data layer to be persisted, the relationship is not there.
Try to debug and check if affirmation above is true.
There are two ways to get this sorted.
Let's assume a Company / Contact relationship in a contacts system.
A company has many contacts and a contact has one company.
Company snippet.
// package declaration imports and all
#Entity
#Table(name = "company")
public class Company {
private String name;
#OneToMany(mappedBy = "company")
private List<Contact> contacts = new ArrayList<Contact>();
// getter and setters and any extra stuff you fancy putting here
}
Contact snippet
// package declaration imports and all
#Entity
#Table(name = "contact")
public class Contact {
private String name;
#ManyToOne
private Company company;
// getter and setters and any extra stuff you fancy putting here
}
And a jsp snippet with the select.
We assume there is a "contact" object and a list of customers in the model.
<form:form modelAttribute="contact">
<form:input path="name" />
<form:select path="customer" items="${customers}" itemValue="id" itemLabel="name" />
</form:form>
With this code in place, you can use a PropertyEditor like this.
#Component
public class CustomerEditor extends PropertyEditorSupport {
#Inject
private CustomerService customerService;
#Override
public void setAsText(String text) {
if (StringUtils.isNotBlank(text)) {
Customer customer = this.customerService.findById(Integer
.valueOf(text));
this.setValue(customer);
}
}
}
and register in your Spring Context like this.
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Customer.class, this.customerEditor);
}
What happens here is that, whenever Spring finds an Object of type Customer, it will use the PropertyEditor to convert the (in this case) Id to the object and by the type the contact (in this case) gets to the data layer (Hibernate), the proper Customer entity will be there waiting as happy as Larry.
That is automagic way to do it.
Another way to do it is to create a form/DTO or whatever you would like to call and add the fields including a customerId field (in this case) and convert your self before you save your entity.
I hope I understood your problem right because it took me quite a few minutes to write this... :)

Related

Spring MVC and Hibernate

I'm using hibernate to store a Student object which is having an embedded type Address object. I'm using a form to get inputs name, city, state. City & State getting null while submitting the form how to capture these values in Model Object. ${student.homeAddress.city} - This value is NULL
Issue Resolved!
public class Student {
#Id
#Column(name = "student_id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "students_seq")
#SequenceGenerator(sequenceName = "students_seq", name = "students_seq", allocationSize = 1)
private int studentId;
#Column(name = "student_name")
private String studentName;
#Embedded
private Address homeAddress;
}
#Controller
#RequestMapping("/students")
public class StudentController {
#Autowired
StudentService studentService;
#RequestMapping("/registrationForm")
public String showFormForStudentRegistration(Model model) {
Student studentObj = new Student();
Address addressObj = new Address();
studentObj.setHomeAddress(addressObj);
model.addAttribute("student", studentObj);
return "student-form";
}
#RequestMapping("/register")
public String registerStudent(#ModelAttribute("student") Student student) {
studentService.createStudent(student);
return "student-success";
}
}
**${student.address.state} -> This is how needs to be accessed after submitting it. when submitting form it should be like this <form:input path="address.state" **
Data successfully inserted.

Thymeleaf Spring Boot: Binding form to object,some fields are empty?

So I am binding a form to an object to send to a spring controller,the "chosenAdvertisementsIds" being set with a hidden tag of ${advertisement.id} ends up with blank strings,the correct quantity of strings but empty strings.The tag for advertisement.description prints correctly on the page so the content is in the model correctly.
public class EditScheduleForm {
private String[] chosenAdvertisementsIds;
private String[] chosenAdvertisementsTime;//parralel arrays
private String dates;
//getters setters
}
My html,
<form th:action="#{|/web/editSchedule/${schedule.id}|}"
th:object="${EditScheduleForm}" method="post">
<h1>Advertisement Items</h1>
<div th:each="advertisement : ${chosenAdvertisements}">
<p th:text="${advertisement.description}"></p>
<input type="hidden" th:value="${advertisement.id}" th:field="*{chosenAdvertisementsIds}"/>
<p>Type below what times you want this advertisement to play at(type it like this 10:15/11:15/14:15 )</p>
<input name="text" th:field="*{chosenAdvertisementsTime}" type="text"/>
My controller method
#RequestMapping(value="/web/editSchedule/{scheduleId}",method = RequestMethod.POST)
public String editScheduleFormPost(Model model,
#ModelAttribute EditScheduleForm editScheduleForm,
#PathVariable Long scheduleId,
RedirectAttributes redirectAttributes) {
Advertisement
#Entity
public class Advertisement {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "usergroup_id")
private UserGroup userGroup;
private String description;
#Basic
#Temporal(TemporalType.DATE)
private java.util.Date dateAdded;
//getters setters
}
How I populate the model that the html page is on
#RequestMapping(value="/web/editschedule/{scheduleId}",method = RequestMethod.GET)
public String editOneSchedule(#PathVariable Long scheduleId,
Model model) {
// Get the schedult given by Id
Schedule schedule = scheduleService.findOne(scheduleId);
model.addAttribute("schedule",schedule);
ArrayList<Music> chosenMusics = new ArrayList<>();
for(int i=0;i<schedule.getMusicScheduleItems().size();i++){
chosenMusics.add(schedule.getMusicScheduleItems().get(i).getMusic());
}
model.addAttribute("chosenMusics",chosenMusics);
ArrayList<Advertisement> chosenAdvertisements = new ArrayList<>();
for(int i=0;i<schedule.getAdvertisementScheduleItems().size();i++){
chosenAdvertisements.add(schedule.getAdvertisementScheduleItems().get(i).getAdvertisement());
}
model.addAttribute("chosenAdvertisements",chosenAdvertisements);
model.addAttribute("EditScheduleForm", new EditScheduleForm());
return "editschedule";
}

Spring - How to use #RequestParam parameter of a Controller in a Custom Validator class?

I've got a problem about validation in Spring MVC with Hibernate.
I want a validator that valid user input, but the validation must be done out of the controller, so, in a separate validation class.
The situation: this is the head of my controller in which I want to do the validation. I need that id to retrieve a list of Booking of a specific car.
#PostMapping(value = "/rent")
public ModelAndView vehicleRent(#ModelAttribute("newBooking") Booking booking, BindingResult bindingResult, #RequestParam("id") long id) {
But if i want to separate the logic out of this controller creating a custom validator, i have this result:
public class BookingValidator implements Validator {
#Autowired
VehicleBO vehicleBo;
#Override
public boolean supports(Class<?> type) {
return Booking.class.isAssignableFrom(type);
}
#Override
public void validate(Object o, Errors errors) {
Booking booking = (Booking) o;
//other code
rejectIfBookingExists(booking, 0, errors, "validation.booking.startdate.exists");
}
}
public boolean rejectIfBookingExists(Booking booking, long id, Errors errors, String key){
boolean exists = false;
List<Booking> vehicleBookings = vehicleBo.getVehicleBookings(id);
if (booking != null || booking.getStartDate() != null || booking.getFinishDate() != null) {
for (Booking b : vehicleBookings) {
if (booking.getStartDate().before((b.getFinishDate())) || booking.getStartDate().equals(b.getFinishDate())) {
errors.rejectValue("startDate", key);
exists = true;
break;
}
}
}
return exists;
}
}
In this way I cannot retrieve the list because i don't have the required id, could you explain me how to do that? Or,there are other ways to solve this problem?
Thanks!
EDIT:
This is the Booking class, as you can see it has a Vehicle object mapped inside
#Entity
public class Booking implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
#JoinTable(name="user_booking", joinColumns={#JoinColumn(name ="booking_id", referencedColumnName ="id")},
inverseJoinColumns={#JoinColumn(name ="user_id", referencedColumnName ="id")})
private User user;
#ManyToOne
#JoinColumn(name = "vehicle_id")
private Vehicle vehicle;
#DateTimeFormat(pattern = "dd/MM/yyyy")
private Date startDate;
#DateTimeFormat(pattern = "dd/MM/yyyy")
private Date finishDate;
public Booking() {
//getter and setter and other code
}
Any ideas?
Why don't you simply map the vehicle id as booking.vehicle.id in your form? Provided Vehicle has a no-arg constructor (which it probably does, being an entity), the Booking should come back in the POST request handler with an instantiated Vehicle, along with its id property set. You should then be able to access booking.vehicle.id from wihtin the validator.
You can use an input[type=hidden] for the booking.vehicle.id field. In your GET request for the view with the form, simply inject the vehicle id as a #PathVariable and copy it to your model, so that you could reference the value inside the form.

Spring, modelmap, getting attributes for displaying Lists in JSTL <c:foreach>

I, a newbie in progrmming, develop spring MVC application with spring mvc frameworks. I want to list the data into table by using JSTL with 2 mapped entities.
<c:forEach items="${empList}" var="emp">
<tr class="odd gradeX">
<td>${emp.icode}</td>
<td>${emp.employeeName}</td>
//problem here
//I want to get employee's phone num
<td>${emp.ph_number}</td>
</tr>
</c:forEach>
This is the example of my Entity,
1) Employee Entity
#Entity
public class Employee implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long employeeId;
private String employeeName;
#OneToMany(mappedBy="employee" ,cascade={CascadeType.ALL})
private List<Phone>phones;
//method getter setter }
2)Phone Entity
#Entity
public class Phone implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long phoneId;
private String ph_number;
#ManyToOne
private Employee employee;
//method getter setter}
3)Enployee Controller
#Controller
public class EmployeeController {
#EJB(mappedName = "ejb:/EJB//EmployeeServiceBean!com.mfu.ejb.EmployeeService")
EmployeeService empServ;
#RequestMapping("/listEmp")
public ModelAndView listEmployee(HttpServletRequest request) {
ModelAndView mv = new ModelAndView("ListEmp.jsp");
List<Employee> empList;
try {
empList = empServ.getAllEmployee();
mv.addObject("empList", empList);
} catch (Exception e) {
e.printStackTrace();
}
return mv;
}
}
Thankyou and sorry for my bad English. Hope it make sence.

Using a drop-down list in a model driven action class in Struts2

I'm populating an <s:select> from database. The action class is model-driven.
#Namespace("/admin_side")
#ResultPath("/WEB-INF/content")
#ParentPackage(value="struts-default")
public final class TestAction extends ActionSupport implements Serializable, ValidationAware, Preparable, ModelDriven<Transporter>
{
#Autowired
private final transient SharableService sharableService=null;
private static final long serialVersionUID = 1L;
private Transporter transporter; //Getter and setter
private Long transporterId; //Getter and setter.
private List<Transporter> transporters; //Getter only.
#Action(value = "Test",
results = {
#Result(name=ActionSupport.SUCCESS, location="Test.jsp"),
#Result(name = ActionSupport.INPUT, location = "Test.jsp")},
interceptorRefs={#InterceptorRef(value="defaultStack", params={"validation.validateAnnotatedMethodOnly", "true", "validation.excludeMethods", "load"})})
public String load() throws Exception
{
return ActionSupport.SUCCESS;
}
#Validations(
requiredFields={#RequiredFieldValidator(fieldName="transporterId", type= ValidatorType.FIELD, key = "transporter.required")})
#Action(value = "testInsert",
results = {
#Result(name=ActionSupport.SUCCESS, location="Test.jsp", params={"namespace", "/admin_side", "actionName", "Test"}),
#Result(name = ActionSupport.INPUT, location = "Test.jsp")},
interceptorRefs={#InterceptorRef(value="defaultStack", params={"validation.validateAnnotatedMethodOnly", "true"})})
public String insert() {
System.out.println("Selected item in the drop box : "+transporterId);
return ActionSupport.SUCCESS;
}
#Override
public void prepare() throws Exception {
transporters=sharableService.getTransporterList();
}
#Override
public Transporter getModel() {
return transporter;
}
}
and the following is <s:select> :
<s:select id="transporterId"
name="transporterId"
list="transporters"
value="transporterId"
listKey="transporterId"
listValue="transporterName"
headerKey="" headerValue="Select"
listTitle="transporterName"/>
This works perfectly.
I need this <s:select> in another action class which implements ModelDriven<ZoneTable>.
The table structure is simple, transporter->zone_table->country->state->city. There exists a one-to-many relationship between these tables.
How can we have a model driven action class implementing ModelDrven<ZoneTable> in which Transporter can be mapped to <s:select>, something like?
#Namespace("/admin_side")
#ResultPath("/WEB-INF/content")
#ParentPackage(value="struts-default")
public final class ZoneAction extends ActionSupport implements Serializable, ValidationAware, Preparable, ModelDriven<ZoneTable>
{
#Autowired
private final transient ZoneService zoneService=null;
#Autowired
private final transient SharableService sharableService=null;
private ZoneTable entity=new ZoneTable(); //Getter and setter.
private Long transporterId; //Getter and setter.
private List<Transporter> transporters; //Getter only.
#Override
public ZoneTable getModel() {
return entity;
}
#Override
public void prepare() throws Exception {
transporters=sharableService.getTransporterList();
}
}
Doing like this doesn't work. It doesn't set the value of transporterId upon submission, since the action class is implementing ModelDriven<ZoneTable> and not ModelDriven<Transporter> like the first case.
Is this possible using the model driven approach?
EDIT:
ZoneTable.java
public class ZoneTable implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "zone_id", nullable = false)
private Long zoneId;
#Column(name = "zone_name", length = 45)
private String zoneName;
#JoinColumn(name = "transporter_id", referencedColumnName = "transporter_id")
#ManyToOne(fetch = FetchType.LAZY)
private Transporter transporterId;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "zoneTable", fetch = FetchType.LAZY)
private Set<ZoneCharge> zoneChargeSet;
#OneToMany(mappedBy = "zoneId", fetch = FetchType.LAZY)
private Set<Country> countrySet;
//Getters and setters + constructors.
}
Zone.jsp
<s:form namespace="/admin_side" action="Zone" validate="true" id="dataForm" name="dataForm" cssClass="search_form general_form">
<s:label key="label.zone.name" for="zone"/>
<s:textfield id="zoneName" name="zoneName" cssClass="validate[required, maxSize[45], minSize[2]] text-input text"/>
<s:fielderror fieldName="zoneName"/>
<s:label key="label.transporter.name" for="transporterId"/>
<s:select id="transporterId" name="transporterId" list="transporters" value="transporterId" listKey="transporterId" listValue="transporterName" headerKey="" headerValue="Select" listTitle="transporterName"/>
<s:fielderror fieldName="transporterId"/>
<s:text name="label.submit"/>
<s:submit id="btnSubmit" name="btnSubmit" value="Submit" action="AddZone"/>
</s:form>
Since this post has already a lot of code, I'm not posting the action class ZoneAction.java here. In case, it is needed, it is available here.
You need a converter to convert transporterId to Transporter Object. It goes like this:
package com.converter;
public class TransporterConverter extends StrutsTypeConverter {
#Override
public Object convertFromString(Map map, String[] strings, Class type) {
String value = strings[0]; // The value of transporterId submitted from the jsp
if (value != null && value.length() > 0) {
try {
Long longVal = Long.valueOf(value);
//Integer intVal = Integer.valueOf(value);
if (type == Transporter.class) {
Transporter data = find_transporter_from_the_back_by_transporter_id_using_longVal;
return data;
}
} catch (Exception ex) {}
}
return null;
}
#Override
public String convertToString(Map map, Object o) {
if ((o instanceof Transporter)) {
Transporter data = (Transporter) o;
//return the id of the Transporter Object
}
return null;
}
}
The next thing to do is to map this class in a file called xwork-conversion.properties. This file must reside in your classpath i.e. in classes directory. Enter the following entries in xwork-conversion.properties
package_of_transporter_class.Transporter=com.converter.TransporterConverter
I have not tested it, but I think it should work.
If you need more information on how type converters work, follow this url.

Resources