I'd like to use the autowiring magic of #ModelAttribute for obtaining and passing around reference data. The problem in doing this is that anything added to the model with #ModelAttribute is assumed to be a form backing object and then is bound to the request, potentially modifying the objects.
I simply want them added to the model for the view's reference and to be able to use the parameter level #ModelAttribute to wire objects into methods annotated with #RequestMapping. Is there a way to accomplish this without some verbose #InitBinder method?
for example:
#ModelAttribute("owner")
public Person getOwner(#PathVariable("ownerId") Integer ownerId){
return getOwnerFromDatabaseById(ownerId);
}
#RequestMapping("/{ownerId}/addPet.do")
public ModelAndView addPet(HttpServletRequest request, #ModelAttribute("owner") Person owner){
String name = ServletRequestUtils.getStringParameter(request, "name");
Pet pet = new Pet();
pet.setName(name);
pet.setOwner(owner);
saveToDatabase(pet);
}
A trivial example where a pet is added to an owner. I'd like to have the owner placed in the model to be used by the view, and i'd also like to make use of autowiring the parameter in addPet(). Assume both Pet and Person have a member name. In this case, owner will automatically get bound to the request, setting its name to the pet's name. How can this be avoided?
I think you are doing it wrong, in this case the #ModelAttribute should be Pet - that is what should be used as the form backing object. To get he owner populated automatically based on the ownerId you can register a property editor for the Owner class that will have the logic you currently have in the getOwner method.
Related
Declaring session model attribute as:
#SessionAttributes ("customer")
Controller code is basically to modify the customer object :
#RequestMapping(value="/testlink", method=RequestMethod.GET)
public String testLinkHandler(ModelMap modelMap){
customerDao.getCustomer(111);
modelMap.put("customers", customerDao.getCustomers());
Customer cust = customerDao.getCustomer(115);
if (cust == null){
cust = new Customer();
}
modelMap.put("customer", cust);
return "testlink";
}
#RequestMapping(value="/testlink", method=RequestMethod.POST)
public String testLinkHandler(#ModelAttribute Customer customer){
customerDao.save(customer);
return "redirect:/testlink";
}
With above code in POST method Customer object is loaded from session & posted new customer name with proper id and hence Editing the Customer works perfectly and updates DB with modified customer Name.
But the moment I change the model variable name and the #SessionAttribute name from "customer" to say "customerModel" or "customer_model" or "model" it doesn't work anymore and above code inserts a new record in DB.
So the question is, Is there a naming convention that needs to be followed here?
public String testLinkHandler(#ModelAttribute Customer customer){ ... }
This method expects an object named customer to be available for binding. When using #ModelAttribute without attributes Spring MVC tries to deduce the name of the model attribute from the method argument name.
Now if you decide to rename your model attribute you either have to
Rename the method argument accordingly
Supply the name to the #ModelAttribute.
As I wouldn't suggest option 1, that leaves option 2.
public String testLinkHandler(#ModelAttribute("your-model-name-here") Customer customer){ ... }
With #SessionAttribute, Spring obtains an instance of model attribute from the session.
Hence model attribute field name should match with the session attribute name,in this case customer
If you require changing of the attribute name then you can use :
#SessionAttributes (types = {Customer.class})
Now, whenever you put your modelClass of the type Customer in spring Model then it would automatically be set in the session.
I have many pages with the same form where I bind my object using commandName. I try to put the object to session with name "myObject" and use it for form (commandName = "myObject"). But system throws exception (Neither BindingResult nor plain target object for bean name "myObject").
How can I bind the object to session for any controllers and requests?
That error is typical when your use a form:form tag that targets a command object that is not available in the request.
A good approach is to combine #ModelAttribute annotated method with #SessionAttributes on a Controller that intially forwards to the view that contains the form, something like
#Controller
#SessionAttributes("myObject")
public class FormController {
#ModelAttribute("myObject")
public MyObject createMyObjectBean() {
return new MyObject();
}
...
}
The initally createMyObjectBean will be called with whatever is the first request to the controller methods, but it won't be called on subsequent request as the myObject value will come from session due to #SessionAttributes
just note that for this approach to work, you must have a controller that forwards to the view that contains your form
I'm using in my project Spring MVC and Thymeleaf. Let's say I want to achieve very simple usage - editing user's form. I use SessionAttributes:
#Controller
#RequestMapping(value="/admin/")
#SessionAttributes(value={"user"})
public class UsersController implements Serializable
in request GET I,ve got simple mapping:
#RequestMapping(value={"user/{id}", "{user/{id}/}"}, method=RequestMethod.GET)
public String edit(Model model, #PathVariable(value="id") Long id, RedirectAttributes redirectAttributes){
String username = SecurityUtils.getLoggedUsername(); //for example, Spring Security
User user = userService.getByIdAndUsername(id, username);
model.addAttribute("user", user);
return "admin/user";
}
Simply enough - check if session logged user has permission to edit user with specified ID (in this example - only his ID). Field with id will not be populate on his HTML form via "hidden" field, it will be stored in SessionAttributes and merged after calling POST method.
So far so good. But there comes a problem. What if - lets say - "very smart" user insert in his HTML debugger / generate a POST request with manually added hidden input with name = "user.id" (or general = "[object name].[object property] and call POST method? SessionAttributes will not be merged, because in my HTTP request property 'ID' exists.
#RequestMapping(value="user/{id}", method=RequestMethod.POST)
public String action(#ModelAttribute("user") User user, BindingResult bindingResult, Model model, RedirectAttributes redirectAttributes){
In this way anyone is able to edit someone's ID (assuming ID of other user is known) and edit other user. How to secure this part of system?
For now I have only one solution - treat anyone user as potential burglar and check POST method in the same way as GET method:
#RequestMapping(value="user/{id}", method=RequestMethod.POST)
public String action(User user, BindingResult bindingResult, Model model, RedirectAttributes redirectAttributes){
String username = SecurityUtils.getLoggedUsername(); //for example, Spring Security
User otherButTheSameUser = userService.getByIdAndUsername(id, username); //remember to evict this user from hibernate session
if(otherButTheSameUser!=null){
userService.update(user);
}
}
This should be secure enough, but here comes SQL performance issue, because of double SQL checking querys. Is this a good way? Is there another way how to achieve that? Maybe merging #SessionAttributes with request object with sessionAttributes priority ? How to achieve that?
As far I understand, you may leverage DataBinder.setAllowedFields() or DataBinder.setDisallowedFields() methods. Excerpt from javadoc:
In the case of HTTP form POST data for example, malicious clients can
attempt to subvert an application by supplying values for fields or
properties that do not exist on the form. In some cases this could
lead to illegal data being set on command objects or their nested
objects. For this reason, it is highly recommended to specify the
allowedFields property on the DataBinder.
So, I suggest to try something like this:
#InitBinder
protected void initBinder(WebDataBinder binder) {
// please check that it's really working
binder.setDisallowedFields("user.id");
}
I am loading a user object my calling a service and then store this user as a command object in the model on GET in the controller. This user object has many properties that are not mapped in the jsp page. After submitting the form, I am getting the command object i the controller on POST. But strangely, I only see the properties in the command object which are mapped to the jsp page. All the other properties those were there when I load the object are lost. I need all the properties in object to be able to successfully save it in hte database.
Can anybody help me figure this problem? Thanks!
Update
I am adding some code to better understand it. In POST handler, I was expecting the command object to have all the properties that was loaded in GET handler in addition to the properties that are bound with jsp. Instead I am losing all propeties except those are bound to the jsp. Am I doing something wrong here?
#RequestMapping(method = RequestMethod.GET)
public String showForm(ModelMap model, HttpSession session, HttpServletRequest request) throws Exception {
UserBean user = Util.getUser(session);
UserBean command = (UserBean)userProfileService.loadByUserName(user.getUserName());
model.addAttribute("command", command);
return formView;
}
#RequestMapping(method = RequestMethod.POST)
public String onSubmit(#ModelAttribute("command") UserBean command, BindingResult result, HttpSession session) throws Exception {
UserBean user = (UserBean) command;
userProfileService.saveUser(user);
return "successView";
}
Update
I am adding some code to better understand it. In POST handler, I was expecting the command object to have all the properties that was loaded in GET handler in addition to the properties that are bound with jsp. Instead I am losing all propeties except those are bound to the jsp. Am I doing something wrong here?
#RequestMapping(method = RequestMethod.GET) public String showForm(ModelMap model, HttpSession session, HttpServletRequest request) throws Exception { UserBean user = Util.getUser(session); UserBean command = (UserBean)userProfileService.loadByUserName(user.getUserName()); model.addAttribute("command", command); return formView; }
#RequestMapping(method = RequestMethod.POST) public String onSubmit(#ModelAttribute("command") UserBean command, BindingResult result, HttpSession session) throws Exception { UserBean user = (UserBean) command; userProfileService.saveUser(user); return "successView"; }
Update
If I store the command object in session how would the jsp bind the propeties. I thought I needed to store it in model for that?
Could you explain please.
Update
storing the command object in session solves the problem. I was able to store it by using
#SessionAttributes ("command")
Thanks a lot!
That's expected behaviour. Spring does not take your existing object (how would it get it?) - it creates a new one and fills it with data.
You can use the #ModelAttribute annotated-method to specify a method which will load the existing object from storage (db?) by its ID (submitted).
#ModelAttribute annotated methods are executed before the chosen #RequestMapping annotated handler method. They effectively pre-populate the implicit model with specific attributes, often loaded from a database. Such an attribute can then already be accessed through #ModelAttribute annotated handler method parameters in the chosen handler method, potentially with binding and validation applied to it.
See 15.3.2.8 of the MVC docs
The Spring-MVC Model values become Request scope attributes in your JSP. This is a one way translation, Spring-MVC does not restore the Model values when the user POSTs a form from your page.
When I need to store information between a GET and a POST (that is, set something on a GET and read it back on a POST), I set a Session attribute.
In your case, I believe that you will need to do one of the following:
Call Util.getUser(session); in the onSubmit method.
Store the command object in the session in the showForm and retrieve it in the onSubmit method>
#ModelAttribute is used to direclty set the values in the Student object from the jsp, other wise in the servlet you have to get the properties using request.getattribute() and than call student setter method.
so u can use both the keywords in jsp page.
<form action="grade" method="get" name="contact1" modelAttribute="contact1">
</form>
or
I'm working with Spring MVC and I'd like it to bind a a persistent object from the database, but I cannot figure out how I can set my code to make a call to the DB before binding. For example, I'm trying to update a "BenefitType" object to the database, however, I want it to get the object fromthe database, not create a new one so I do not have to update all the fields.
#RequestMapping("/save")
public String save(#ModelAttribute("item") BenefitType benefitType, BindingResult result)
{
...check for errors
...save, etc.
}
There are several options:
In the simpliest case when your object has only simple properties you can bind all its properties to the form fields (hidden if necessary), and get a fully bound object after submit. Complex properties also can be bound to the form fields using PropertyEditors.
You may also use session to store your object between GET and POST requests. Spring 3 faciliates this approach with #SessionAttributes annotation (from the Petclinic sample):
#Controller
#RequestMapping("/owners/*/pets/{petId}/edit")
#SessionAttributes("pet") // Specify attributes to be stored in the session
public class EditPetForm {
...
#InitBinder
public void setAllowedFields(WebDataBinder dataBinder) {
// Disallow binding of sensitive fields - user can't override
// values from the session
dataBinder.setDisallowedFields("id");
}
#RequestMapping(method = RequestMethod.GET)
public String setupForm(#PathVariable("petId") int petId, Model model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet); // Put attribute into session
return "pets/form";
}
#RequestMapping(method = { RequestMethod.PUT, RequestMethod.POST })
public String processSubmit(#ModelAttribute("pet") Pet pet,
BindingResult result, SessionStatus status) {
new PetValidator().validate(pet, result);
if (result.hasErrors()) {
return "pets/form";
} else {
this.clinic.storePet(pet);
// Clean the session attribute after successful submit
status.setComplete();
return "redirect:/owners/" + pet.getOwner().getId();
}
}
}
However this approach may cause problems if several instances of the form are open simultaneously in the same session.
So, the most reliable approach for the complex cases is to create a separate object for storing form fields and merge changes from that object into persistent object manually.
So I ended up resolving this by annotating a method with a #ModelAttribute of the same name in the class. Spring builds the model first before executing the request mapping:
#ModelAttribute("item")
BenefitType getBenefitType(#RequestParam("id") String id) {
// return benefit type
}
While it is possible that your domain model is so simple that you can bind UI objects directly to data model objects, it is more likely that this is not so, in which case I would highly recommend you design a class specifically for form binding, then translate between it and domain objects in your controller.
I'm a little confused. I think you're actually talking about an update workflow?
You need two #RequestMappings, one for GET and one for POST:
#RequestMapping(value="/update/{id}", method=RequestMethod.GET)
public String getSave(ModelMap model, #PathVariable Long id)
{
model.putAttribute("item", benefitDao.findById(id));
return "view";
}
then on the POST actually update the field.
In you example above, your #ModelAttribute should already be populated with a method like the above method, and the properties be bound using something like JSTL or Spring tabglibs in conjunction with the form backing object.
You may also want to look at InitBinder depending on your use case.