Inner object id save in Spring 3 - java

I want to save Village object through hibernate where already persisted District id need to be saved. I populated district object in dropdown. I coded similar work in spring 2, but in spring 3 it doesn't work.
here, if I log village.getDistrict() in POST, id is set perfectly as I set to dropdown but other value of district object is null.
#SessionAttributes({"village"})
#Controller
public class VillageController{
#Autowired(required=true)
private AddressService addressService;
#RequestMapping(value="/cp/village.html", method = RequestMethod.GET)
public String setForm(ModelMap model) {
Village village = new Village();
village.setDistrict(new District());
model.addAttribute("village", village);
return "/cp/village";
}
#ModelAttribute("districtList")
public List<District> populateDistrictList() {
return addressService.getDistrictList();
}
#RequestMapping(value="/cp/village.html", method = RequestMethod.POST)
public String getForm(#ModelAttribute("village") Village village,
BindingResult result,
SessionStatus status) {
log.debug("============================="+village.getDistrict());
addressService.saveVillage(village);
status.setComplete();
return "redirect:/cp/village.html123";
}
}
In JSP:
<form:form commandName ="village" action="village.html" >
<div>
<label><fmt:message key="location.district"/></label>
<form:select path="district.id">
<form:options items="${districtList}" itemValue="id" itemLabel="districtName"/>
</form:select>
</div>
<div>
<label><fmt:message key="location.village"/></label>
<form:input path = "villageName" />
</div>
<div>
<label><fmt:message key="prompt.remarks"/></label>
<form:textarea path = "remarks" rows="2" cols="50"/>
</div>
<div class = "button-area">
<input type = "submit" value="Save" class="submit-button" />
<input type = "button" value="Cancel" onclick="window.location='commonComplaintList.html'" class="submit-button" />
</div>
<br/>
</form:form>

You do not set any value in the District object, so it is empty.
If you expected that the district has the values of objects from List<District> populateDistrictList(), then I have to say: it does not work out of the box.
One way to do it, would be implementing a Converter, that converts a string (the id) to the District object, by loading it from the database.

I think you need to implement a Converter for the District entity as well. Since the Converter for your Village entity class is likely not called at all during the POST (at least not for new Villages, since they lack an id). Also, even if it was called the District would still need a Converter in order to fetch the correct District from Hibernate. Otherwise, Spring will just give you a fresh instance of the District with the Id set to the value in the select.
However, if you are only going to save the Village I am unsure whether you would actually need to fetch the whole District object from Hibernate. I think (not 100% sure on this since it was a while since I used Hibernate and Spring) that Hibernate will associate the village you create in your POST with the correct district even though you only have a District object with an Id. You might need to specify some annotation stuff about cascading for it to not overwrite the District objects other properties.
EDIT:
Looked it up a bit and I think you should annotate the district property of the Village class with something like:
#Column(name = "district", insertable = false)
That way Hibernate will know that no new districts should be added when storing a new Village.
Again not sure about this last part, but worth a quick try :)

Related

How to pass Object to ModelAttribute in controller - Spring

Is it possible to pass an object (car) to my controller by using a select tag? When i try to use the following code, the car parameter is not recognised and it results is:
400-Bad Request
A car consists of 2 Strings (Brand, Model)
A spot consists of 1 car and 2 Strings (town, streetName)
My jsp page:
<form:form method="post" modelAttribute="spot" action="${post_url}">
<form:select path="car">
<form:option value="-" label="--Select car"/>
<form:options items="${cars}"/>
</form:select>
<form:input type="text" path="town"/>
<form:input type="text" path="streetName"/>
<button>Save</button>
</form:form>
My controller:
#RequestMapping(value="/addSpot", method = RequestMethod.POST)
public String save(#ModelAttribute("spot") Spot spot){
service.addSpotToService(spot);
return "redirect:/spots.htm";
}
you can creta a component to convert the Long id of Car to object car
#Component
public class CarEditor extends PropertyEditorSupport {
private #Autowired CarService carService;
// Converts a Long to a Car
#Override
public void setAsText(Long id) {
Car c = this.carService.findById(id);
this.setValue(c);
}
}
in your controller add this
private #Autowired CarEditor carEditor;
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Car.class, this.carEditor);
}
and then pass the id of car in the select
<form:select path="car">
<form:option value="-" label="--Select car"/>
<form:options items="${cars}" itemValue="id" itemLabel="model"/>
</form:select>
have a look at the spring documentation http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/view.html and specifically at the section The options tag
The items attribute is typically populated with a collection or array
of item objects. itemValue and itemLabel simply refer to bean
properties of those item objects, if specified; otherwise, the item
objects themselves will be stringified. Alternatively, you may specify
a Map of items, in which case the map keys are interpreted as option
values and the map values correspond to option labels. If itemValue
and/or itemLabel happen to be specified as well, the item value
property will apply to the map key and the item label property will
apply to the map value.
Let me know if this worked for you

Input in array Thymeleaf

I need choose values from one array and assign it to other array. Using Spring Thymeleaf. No idea how retrieve these choosed values.
My classes:
#Entity
public class Collaborator {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Size (min=3, max=32)
private String name;
#NotNull
#ManyToOne (cascade = CascadeType.ALL)
private Role role;
public Collaborator() {}...
#Entity
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Size(min = 3, max = 99)
private String name;
public Role() {}....
My controllers:
#RequestMapping("/project_collaborators/{projectId}")
public String projectCollaborators(#PathVariable Long projectId, Model model) {
Project project = mProjectService.findById(projectId);
List<Collaborator> allCollaborators = mCollaboratorService.findAll();
List<Collaborator> assignments = new ArrayList<>();
if (project.getRolesNeeded()!=null) {
for (int i=0;i<project.getRolesNeeded().size();i++) {
assignments.add(new Collaborator("Unassigned", project.getRolesNeeded().get(i)));
assignments.get(i).setId((long) 0);
}
}
model.addAttribute("assignments", assignments);
model.addAttribute("allCollaborators", allCollaborators);
model.addAttribute("project", project);
return "project_collaborators";
}
#RequestMapping(value = "/project_collaborators/{projectId}", method = RequestMethod.POST)
public String projectCollaboratorsPost(#ModelAttribute Project project, #PathVariable Long projectId, Model model) {
Project p = mProjectService.findById(projectId);
//mProjectService.save(project);
return "redirect:/project_detail/{projectId}";
}
And template:
<form th:action="#{'/project_collaborators/' + ${project.id}}" method="post" th:object="${project}">
<label th:text="'Edit Collaborators: ' + ${project.name}">Edit Collaborators: Website Project</label>
<ul class="checkbox-list">
<li th:each="a : ${assignments}">
<span th:text="${a.role.name}" class="primary">Developer</span>
<div class="custom-select">
<span class="dropdown-arrow"></span>
<select th:field="${a.id}">
<option th:each="collaborator : ${allCollaborators}" th:value="${collaborator.id}" th:text="${collaborator.name}">Michael Pemulis</option>
</select>
</div>
</li>
</ul>
<div class="actions">
<input type="submit" value="Save" class="button"/>
Cancel
</div>
</form>
As you can see I want to let user choose for each role (roleNeeded) any collaborator from (allCollaborators) and keep that assigns in List (assignments).
And I get error message:
ava.lang.IllegalStateException: Neither BindingResult nor plain target
object for bean name 'a' available as request attribute
So question is: how to solve it, assign values from one array to another in template and retrieve that values in my controller.
The cause of the exception
The IllegalStateException you are getting is because th:field="${a.id}" in your select element must be related to the form th:object="${project}" element; the th:field attribute must refer to an actual field in the project instance (also you need to write th:field="*{fieldName}"). That should fix the exception you are getting, but will not solve your entire problem as the second part of it is related to how to make the values get into your controller, which I will explain next.
Sending the values to your controller
To get the values into your controller you will need to make a few changes. As I don't really know the code of your Project class, I will change a few things so you will be able to figure it out how to adapt this simple example to your particular case.
First, I understand you want to make a relation like the following in your form:
Role1 => CollaboratorA
Role2 => CollaboratorB
Your controller needs to receive a list and in order to receive this information we need two classes:
The class which will be storing the individual element data, mapping a role id with the collaborator id:
public class RoleCollaborator {
private Long roleId;
private Long collaboratorId;
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public Long getCollaboratorId() {
return collaboratorId;
}
public void setCollaboratorId(Long collaboratorId) {
this.collaboratorId = collaboratorId;
}
}
A wrapper class to store a list of individual mappings:
public class RolesCollaborators {
private List<RoleCollaborator> rolesCollaborators;
public List<RoleCollaborator> getRolesCollaborators() {
return rolesCollaborators;
}
public void setRolesCollaborators(List<RoleCollaborator> rolesCollaborators) {
this.rolesCollaborators = rolesCollaborators;
}
}
The next thing to do is change your controllers where you have two methods, one that handles GET requests and another one which handles the POST requests and so, receives your form data.
In the GET one:
public String projectCollaborators(#PathVariable Long projectId, Model model) {
(... your code ...)
model.addAttribute("project", project);
// Add the next line to add the "rolesCollaborators" instance
model.addAttribute("rolesCollaborators", new RolesCollaborators());
return "project_collaborators";
}
As you can see, we added a line that will be used by the thymeleaf template. Right now is a wrapper of an empty list of roles and collaborators, but you can add values if you need to edit existing mappings instead of adding new ones.
In the POST one:
// We changed the #ModelAttribute from Project to RolesCollaborators
public String projectCollaboratorsPost(#ModelAttribute RolesCollaborators rolesCollaborators, #PathVariable Long projectId, Model model) {
(... your code ...)
}
At this point, your controller is prepared to receive the information sent from your form, that we also need to modify.
<form th:action="#{'/project_collaborators/' + ${project.id}}" method="post" th:object="${rolesCollaborators}">
<label th:text="'Edit Collaborators: ' + ${project.name}">Edit Collaborators: Website Project</label>
<ul class="checkbox-list">
<li th:each="a, stat : ${assignments}">
<span th:text="${a.role.name}" class="primary">Developer</span>
<div class="custom-select">
<input type="hidden" th:id="rolesCollaborators[__${stat.index}__].roleId" th:name="rolesCollaborators[__${stat.index}__].roleId" th:value="${a.role.id}" />
<span class="dropdown-arrow"></span>
<select th:field="*{rolesCollaborators[__${stat.index}__].collaboratorId}">
<option th:each="collaborator : ${allCollaborators}" th:value="${collaborator.id}" th:text="${collaborator.name}">Michael Pemulis</option>
</select>
</div>
</li>
</ul>
<div class="actions">
<input type="submit" value="Save" class="button"/>
Cancel
</div>
</form>
Here there are a few changes:
As I pointed out in The cause of the exception you need to change th:object="${project}" to th:object="${rolesCollaborators}", as rolesCollaborator is the instance name from where you will receive the values from your GET controller method and where you will be sending the values to your POST controller method.
I added a hidden input; this input will store the role id that will be send in association to the collaborator id the user picks from the interface using the select element. Take a look at the syntax used.
I changed the th:field value of your select element to refer to a field in the rollesCollaborators object we use in th:object="${rolesCollaborators}" form attribute. This will set the value of the collaborator in the RoleCollaborator element of the RolesCollaborators wrapped list.
With these changes your code will work. Of course, you can improve it with some other modifications, but I tried to not introduce more modifications to focus on your problem.

Infuriating empty list in Spring form / controller

I'm trying to create a list of objects from form inputs. The objects are the same but their values may differ, it's essentially a menu.
I'm still getting to grips with Spring/Thymeleaf which is adding some level of complexity to what feels like a simple task.
I've a class for the menu, a simple POJO, there is then a list of these defined as a data member in the bean itself:
private ArrayList<GuestMenuOptions> guestMenus;
I've read many posts, tried many things and am on the verge of softly resting my head against the table.
I've had several errors, most of which either tell me that the list cannot be found or that the list is empty - it's currently in stable condition where the list, no matter what I try, will not be populated, even when I load in default values...unfortunately my debugger has died which is not helping.
Any help is appreciated. thank you
EntryController:
#RequestMapping(method=RequestMethod.GET, value="/")
public String indexPage(Model model) {
model.addAttribute("childMenuOptions", generateChildMenus());
//not sure if this is neccesary...
ArrayList<GuestMenuOptions>guestMenus = new ArrayList<>();
GuestMenuOptions ad1 = new GuestMenuOptions();
GuestMenuOptions ad2 = new GuestMenuOptions();
guestMenus.add(ad1);
guestMenus.add(ad2);
GuestContactBean ctb = new GuestContactBean();
ctb.setGuestMenus(guestMenus);
model.addAttribute("guestContactBean", ctb);
model.addAttribute("formBackingBean", new FormBackingBean());
return "index";
}
Form:
<form modelAttribute="guestBean" class="contact_form" name="rsvp" role="form" th:object="${formBackingBean}" th:action="#{/sendRsvp}" method="post">
<div class="row">
<div class="form-group">
<select name="ad1Starter" id="starterMealAdult1">
<option value="!!!">-Starter-</option>
<option th:field="${guestContactBean.guestMenus[0].starter}" th:each="entry : ${adultMenuOptions.get('starter').entrySet()}" th:value="${entry.key}" th:text="${entry.value}">
</option>
</select>
</div>
<input type="submit"guest name="submit" class="btn default-btn btn-block" value="Send RSVP">
RequestController:
#RequestMapping(value = "/sendRsvp", method = RequestMethod.POST)
public String sendRsvp(#ModelAttribute("guestContactBean") GuestContactBean guestContactBean,
#ModelAttribute("guestMenus") ArrayList<GuestMenuOptions>menus,
BindingResult result) throws MessagingException {
smtpMailSender.send(guestContactBean);
return "thanksMessage";
}
Beans:
FormBacking is POJO with no reference to the menus at all.
GuestMenuOptions is the same with just starter, desert members
guestContactbean has not much more going on, basic fields with the addition of the list of GuestMenuOptions
private String numberOfAdults;
private String eventAttending;
private ArrayList<GuestMenuOptions> guestMenus;
public ArrayList<GuestMenuOptions> getGuestMenus() {
return guestMenus;
}
EDIT:
The field that populates the drop downs in working fine, it's declared as private Map<String, Map<String, String>> adultMenuOptions;
private Map<String, Map<String, String>> childMenuOptions;
they are then built in the controller so that each may have several options under 'starter', 'main' and desert' for example:
starter.put("salmon", "Smoked Salmon");
starter.put("pate", "Chicken Liver Pate");
this is then populating both the value and text of the dropdown.
If I could save the state of this Map and pass it back to the controller instead, that would also be fine but I wasn't able to why then spawned the creation of the there wrapper list.
Please revisit http://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#dropdownlist-selectors. It should be as simple as
class Animal {
int id;
String name;
}
then in your template:
<select th:field="*{animalId}">
<option th:each="animal : ${animals}"
th:value="${animal.id}"
th:text="${animal.name}">Wireframe</option>
</select>
I think your code is all over the place and you're mixing up menu selection with menu item types.

How to get selected item id and value of form:select in a Spring MVC

I'm new to Spring and I would like to get id and value of selected item of a dropdown. Here is a simple example
class MaritalStatus{
private int id;
private String status;
}
class regForm{
private MaritalStatus maritalStatus;
...
}
//Simple Controller to fill the list
#RequestMapping(value = "/save")
public String init(Model model){
List<MaritalStatus> maritalList = new ArrayList<MaritalStatus>();
maritalStatus.setId(1)
maritalStatus.setStatus("Married")
maritalList.add(maritalStatus);// add all status to the list....
model.addAttribute("maritalList",maritalList);
...
}
jsp page
<form:form commandName="regForm" action="save">
<form:select path="maritalStatus.id">
<form:options items="${maritalList}" itemValue="id" itemLabel="status" />
</form:select>
</form:form>
This is where I want to get selected item id and value (1 and Married)
#RequestMapping(value = "/save")
public String save(Model model,#ModelAttribute("regForm") RegForm regForm){
// here I want to get selected item Id and Status(Label)
//regFrom.getMaritalStatus().getId() and regFrom.getMaritalStatus().getStatus()
}
You can achieve it at least in 2 ways:
Send only the id of the selected MaritalStatus (you actually do it in your jsp), bind it directly to regForm.maritalStatusId and then (when you need it) get the MaritalStatus from the maritalList by the selected id (you have to keep the maritalList or create it somewhere, you do it anyway)
Bind your select directly to regForm.maritalStatus <form:select path="maritalStatus"> and write a specialized formatter that can convert from id to MaritalStatus object and vice versa. You'll find more information how to do it here: http://docs.spring.io/spring/docs/4.0.0.RELEASE/spring-framework-reference/html/validation.html#format
[You could also send the id of the selected field and additionally its value in the hidden field and then try to build from those the MaritalStatus on the server side, but it is not elegant.]
You can get status by one hidden variable in jsp and a javascript function.
function changeStatus() {
var statusSelected = document.getElementById("maritalStatus");
var option = cookerModeIdSelected.options[statusSelected.selectedIndex];
var selectedValue = option.getAttribute("data-status");
document.getElementById("regForm").submit();
}
<form:form commandName="regForm" action="save">
<form:hidden id="maritalStatusValue" path="maritalStatusValue"/>
<form:select path="maritalStatus.id" id="maritalStatus">
<form:options items="${maritalList}" itemValue="id" itemLabel="status" data-status="${status}"/>
</form:select>
</form:form>

What is the "correct" or "safe" way to update a persisted object with Spring MVC 3 + Hibernate?

Given a very simple object:
class User {
private Integer id;
private String name;
public User() {}
public Integer getId() { return id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
and a very simple controller action:
#RequestMapping(value="/edit/{id}/**", method=RequestMethod.POST)
public String editFromForm(#PathVariable("id") Integer id, #Valid User user, BindingResult bindingResult, Model model) {
// If we have errors, don't save
if(bindingResult.hasErrors()) {
// Put what they did in the model and send it back
model.addAttribute(user);
return "users/edit";
} else {
userDAO.save(user);
}
// Show them the updated page on success
return "redirect:/users/" + user.getId() + "/" + user.getName();
}
and a very simple form:
<sf:form method="POST" modelAttribute="user">
<label for="user_name">Name:</label>
<sf:input path="name" id="user_name" />
<input type="submit" value="save" /><sf:errors path="name" cssClass="error" />
</sf:form>
How should I be updating the entity in the database? Currently (since saveOrUpdate() is the actual hibernate call behind my DAO's save() method, a new object is persisted instead of updating the existing one because the id field is not being set on the object created from the form submission.
A couple of possible solutions have come to me, but I am not sure which is best in terms of keeping things clean and also secure (so that a malicious user cannot just fire in edits to whatever object Id's they wish).
Insert the id from the URL parameter into the object coming from the model binder
Have a hidden id field in the form and let the model binder attach the id
In both of those cases, there is no check in place to make sure that the object is still the same one, such as a checksum of some sort. How do others deal with this? Are there any clear example that walk through this issue?
Another issue that comes up is that I'd rather not need a setId() method since Hibernate is managing all of the id's. From what I have been able to determine, the Spring MVC model binder can only bind a field if it has the expected getter and setter. Is there some other way to apply the new state, such as getting the current User from the db by the URL's id parameter and then applying the new state to it, but without having to explicitly code all of the field copies?
I am sure there is a fairly simple, straightforward way to handle this, but my heat-addled brain does not seem to be able to come up with a suitable solution.
I'm fairly new to Spring + Hibernate so forgive me if this is one of those mundane, highly covered topics, but I have not been able to find any clear example that covers my very simple situation. If this has been covered sufficiently elsewhere, please point me in the right direction.
A couple of possible solutions have come to me, but I am not sure which is best in terms of keeping things clean and also secure (so that a malicious user cannot just fire in edits to whatever object Id's they wish).
Neither of the two approaches you mention will really handle a user who is attempting to edit objects that the user is not authorized to. At the end of the day, the user submitting the form needs to tell you which object they are submitting data for - whether it is in the URL parameter or in a hidden form parameter. I would say which of the two you choose is a matter of style and personal preference, really.
But what you need to be doing regardless of choice is to verify that the currently-logged-in user is authorized to change the object when processing the form submission. This would mean you need to check that this user is entitled to edit the current object ID, using whatever logic comprises "is allowed to do this" for your application.
Still have the same User POJO
class User {
private Integer id;
private String name;
public User() {}
public Integer getId() { return id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
#RequestMapping("/create/userCreate.do")
public String createUser(Model model) {
User user = new User();
model.addAttribute(user);
return "userCreate";
}
Here I don't send the Id or any parameter, just pass the User form model attribute
#RequestMapping(value="/edit/userEdit.do", method=RequestMethod.POST)
public String editFromForm(#ModelAttribute("user") #Valid User user, BindingResult bindingResult, Model model) {
// If we have errors, don't save
if(bindingResult.hasErrors()) {
// Put what they did in the model and send it back
model.addAttribute(user);
return "users/edit";
} else {
userDAO.saveOrUpdate(user);
}
// Show them the updated page on success
return "redirect:/users/" + user.getId() + "/" + user.getName();
}
Here, the User object is bind user name and id. If there is no Id then the object will be saved and if the user object already is having Id then it will be updated. You don't have to pass id or parameter and make it visible on the address bar. Sometimes I don't like the idea of showing parameters to the users. I hope that helps
<%#taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%#taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%#taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<head>
<title> User Edit
</title>
</head>
<body>
<form:form method="post" action="userEdit.do" commandName="user">
<form:hidden path="id" />
<form:label path="username">User name:</form:label>
<form:input path="username" type="text" maxlength="20" />
<form:errors path="username" />
</form:form>
</body>
</html>
Im a bit late, but maybe it will be useful to someone.
Today I solved the same problem. I think your problem was there:
} else {
userDAO.save(user);
}
If it was hibernate session save, then new id was generated each time it was called. I used
session.saveOrUpdate(user)
instead of
session.save(user)
I agree 100% on what #matt b says. I have worked with similar products where at the end of the day you don't have any way of knowing if the user is editing the right object. Your only option is to make sure the user is permitted to do so. If you have a user who is editing their profile then obviously they shouldn't be editting any other person.
Now the easiest way to do what you are trying to do is actually niether. YOu don't want to use the url or hidden attributes. YOu should be able to use #ModelAttribute to do this. Here is an example
#RequestMapping(method = RequestMethod.GET)
public ModelAndView setupForm(#RequestParam("petId") int petId) {
User user = ...
model.addAttribute("user", user);
return model;
}
Then you should be able to do
#RequestMapping(method = RequestMethod.POST)
public ModelAndView submit(#ModelAttribute("user") User user) {
...
}
There is no setters or hidden fields. All the loading and binding is handled via Spring. More documentation here.

Categories