Spring MVC binding extra objects - spring

I'm getting some weird binding issue with Spring MVC 3.
My Controller request mapping looks like this:
#RequestMapping
public String save(HttpServletRequest req,
#ModelAttribute("userEditForm") UserEditForm form,
BindingResult formBindingResult,
ModelMap model,
#ModelAttribute("session") AdminSession session) {
// some validation etc
}
The UserEditForm:
public class UserEditForm {
private User user;
public User getUser() { ... }
public void setUser(User user) { ... }
}
The AdminSession:
public class AdminSession {
private User user;
public User getUser() { ... }
public void setUser() { ...}
}
What's happening is that when I submit my form, Spring is binding the User as I expect in my UserEditForm object, however, the AdminSession is also having it's User bound by Spring, in so far as it's property values are also updated.
I'm going to assume it's due to having a user property in both #ModelAttribute objects.
I thought that having the BindingResult after the UserEditForm form in the method signature would stop this? The objects are separate instances, and my form elements reference the UserEditForm object:
<#spring.bind "userEditForm.user.name" />
<input name="${spring.status.expression}" />
I've noticed that in the generated HTML it's outputting:
<input name="user.name" />
Hardcoding the name as userEditForm.user.name gives me errors, so that's not the way forward.
Is there anyway to stop this from happening?

That's the default behavior when you annotate a handler method parameter with the #ModelAttribute. Spring takes the request properties and matches them to properties of the objects annotated with #ModelAttribute. That's what Spring looks at when deciding what to do: your annotations.
Since both UserEditForm and AdminSession are annotated with #ModelAttribute and both have a User property, a request property named user.name will get bound to both User properties.
You tried to include the command name in the input name and got an error. That's because when binding occurs it occurs on your command object and Spring looks for properties on it (the bindinf path is relative to the command object) and off course the expression does not find any property with that name. If you want to use a full name you could wrap the form in another object and use that for your command instead, something like this:
public class UserEditFormWrapper {
private UserEditForm form;
public UserEditForm getForm() {
return form;
}
public void setForm(UserEditForm form) {
this.form = form;
}
}
Now you can use an expression like this in your inputs: form.user.name and when you submit to your handler method that now looks like this:
#RequestMapping
public String save(HttpServletRequest req,
#ModelAttribute("userEditForm") UserEditFormWrapper formWrapper,
BindingResult formBindingResult,
ModelMap model,
#ModelAttribute("session") AdminSession session) {
UserEditForm form = formWrapper.getForm();
// some validation etc
}
the binding won't be triggered since AdminSession does not have a form property.
That's one way to solve this but it's kind of a hack. You don't want to have the request parameters bound to AdminSession but that's part of your model so you must have created it somewhere and placed it on the model, right? If so, then remove it from the method's parameters and just get it from the model, something like:
#RequestMapping(value = "/test", method = { RequestMethod.POST })
public String handlePost(HttpServletRequest req,
#ModelAttribute("userEditForm") UserEditForm form,
BindingResult formBindingResult, ModelMap model) {
AdminSession session = (AdminSession) model.get("session");
// some validation etc
}

Related

spring: hook between data binding and validation

I have a Roo generated application and use validation, spring security with a custom PermissionEvaluator and the generated web controllers. My entity has a field like this:
#NotNull
private Date creationDate;
This field is set automatically inside the controller's create method when saving the entity and is not included in the form (render="false").
#RequestMapping(method = RequestMethod.POST, produces = "text/html")
#PreAuthorize("hasPermission(#myEntity, 'create')")
public String create(#Valid MyEntity myEntity,
BindingResult bindingResult, Model uiModel,
HttpServletRequest httpServletRequest) {
// ... check binding result
myEntity.setCreationDate(new Date());
myEntity.persist();
// ...
}
The problem is, that validation always fails because it runs before the field is set. The PermissionEvaluator (called by #PreAuthorize) is also missing the value. How can I place my code somewhere between data binding and validation so that the entity is complete right from the beginning?
To solve the problem of #PreAutorize move the persistence logic to a #Service bean and call it from the controller. This way security check will be after validation. Roo can help you on it with service command.
Second, you can use validation groups to make different validation on for the same entity. This and this are two howto post.
An example:
#RequestMapping("/myEntity")
public MyEntityController {
#Autowired
MyEntityService myEntityService;
#RequestMapping(method = RequestMethod.POST, produces = "text/html")
public String create(#Validated({Account.ValidationStepOne.class}) MyEntity myEntity,
BindingResult bindingResult, Model uiModel,
HttpServletRequest httpServletRequest) {
// ... check binding result
myEntityService.save(myEntity);
//...
}
}
#Service
public MyEntityService {
#PreAuthorize("hasPermission(#myEntity, 'create')")
public save(MyEntity myEntity) {
//...
myEntity.setCreationDate(new Date());
myEntity.persist();
}
}
Good luck!

Can't access Model attributes during validation

I'm using Spring 3.1 and have web pages using validation. The field-level validation, and the display of errors, works OK. My problem is with Model attributes not being available during the validation form display.
Let's say I've code:
#RequestMapping(value="/edit", method=RequestMethod.GET)
public String getEdit(#RequestParam("id") Long id, Model model) {
model.addAttribute("mytitle", "Hello There");
return "editObject"
}
#RequestMapping(value="/edit", method=RequestMethod.POST)
public String postEdit(#RequestParam("id") Long id, #Valid #ModelAttribute("object") MyData object, BindingResult result) {
if(result.hasErrors()) {
return "editObject";
}
[snip]
}
If I have an error the hasErrors() is detected and short-circuits to the map "editObject". However, the model attributes aren't available.
What do I use here? I tried adding a Model reference to the postEdit parameter list and adding in again things like the "mytitle" attribute.
Thanks,
Jerome.
When postEdit controller render the editObject view, you are in a new request, so you are loosing the model (previously set for getEdit action).
What you need to do it re-set any values needed in a new model:
#RequestMapping(value="/edit", method=RequestMethod.POST)
public String postEdit(#RequestParam("id") Long id, #Valid #ModelAttribute("object") MyData object, BindingResult result, Model model) {
if(result.hasErrors()) {
model.addAttribute("mytitle", "Hello There");
model.addAttribute("object", object);
return "editObject";
}
If you need to keep those attributes for different views you can store them in a session for example (so that you won't have to re-set them for each new request).

Force Initialization of #ModelAttributes in Spring MVC 3.1

I am writing a wizard-like controller that handles the management of a single bean across multiple views. I use #SessionAttributes to store the bean, and SessionStatus.setComplete() to terminate the session in the final call. However, if the user abandons the wizard and goes to another part of the application, I need to force Spring to re-create the #ModelAttribute when they return. For example:
#Controller
#SessionAttributes("commandBean")
#RequestMapping(value = "/order")
public class OrderController
{
#RequestMapping("/*", method=RequestMethod.GET)
public String getCustomerForm(#ModelAttribute("commandBean") Order commandBean)
{
return "customerForm";
}
#RequestMapping("/*", method=RequestMethod.GET)
public String saveCustomer(#ModelAttribute("commandBean") Order commandBean, BindingResult result)
{
[ Save the customer data ];
return "redirect:payment";
}
#RequestMapping("/payment", method=RequestMethod.GET)
public String getPaymentForm(#ModelAttribute("commandBean") Order commandBean)
{
return "paymentForm";
}
#RequestMapping("/payment", method=RequestMethod.GET)
public String savePayment(#ModelAttribute("commandBean") Order commandBean, BindingResult result)
{
[ Save the payment data ];
return "redirect:confirmation";
}
#RequestMapping("/confirmation", method=RequestMethod.GET)
public String getConfirmationForm(#ModelAttribute("commandBean") Order commandBean)
{
return "confirmationForm";
}
#RequestMapping("/confirmation", method=RequestMethod.GET)
public String saveOrder(#ModelAttribute("commandBean") Order commandBean, BindingResult result, SessionStatus status)
{
[ Save the payment data ];
status.setComplete();
return "redirect:/order";
}
#ModelAttribute("commandBean")
public Order getOrder()
{
return new Order();
}
}
If a user makes a request to the application that would trigger the "getCustomerForm" method (i.e., http://mysite.com/order), and there's already a "commandBean" session attribute, then "getOrder" is not called. I need to make sure that a new Order object is created in this circumstance. Do I just have to repopulate it manually in getCustomerForm?
Thoughts? Please let me know if I'm not making myself clear.
Yes, sounds like you may have to repopulate it manually in getCustomerForm - if an attribute is part of the #SessionAttributes and present in the session, then like you said #ModelAttribute method is not called on it.
An alternative may be to define a new controller with only getCustomerForm method along with the #ModelAttribute method but without the #SessionAttributes on the type so that you can guarantee that #ModelAttribute method is called, and then continue with the existing #RequestMapped methods in the existing controller.

JSR 303 set custom validator for cascade #valid

I want to validate a form object which is contained in another form object. I have something like this:
#Controller
public class FormController {
#RequestMapping(method = RequestMethod.POST)
public void process(#ModelAttribute("form") #Valid FormObject formObject,
BindingResult result) {
...
#InitBinder("form")
protected void initBinder(WebDataBinder binder) {
binder.setValidator(customFormValidator);
}
}
public class FormObject {
#Valid
private FormObject2 formObject2;
}
// This is the class that needs to be validated.
public class FormObject2 {
#NotEmpty
private String name;
}
}
The problem I'm having is that I want the object formObject2 to be validated by another custom validator (e.g. "customFormValidator2"), but I can't find how to register it. If I let it like this, the spring validator will validate the second form.
I have tried inside customFormValidator to validate the second form, but then the paths for the errors in the second form are not relative to the first form and I can't display the errors in the jsp page.
I have structured my form object like this, because I might need the second form inside other forms and by doing this I make it more modularized.
Is it possible what I'm trying to do? Do you have better suggestions?
You can use custom validator to validate form Object2 like
public void process(#ModelAttribute("form") #Valid FormObject formObject,
BindingResult result) {
customFormValidator2.validate(formObject.getFormObject2(), result);
}
And remove #Valid to remove JSR validation
public class FormObject {
// #Valid REMOVE THIS
private FormObject2 formObject2;
}
Also you can use path attribute for nested classes like
<form:input path="formObject2.any_property_name">
You can also use same path for errors without any problem.
Check this link for more details.

I am confused about how to use #SessionAttributes

I am trying to understand architecture of Spring MVC. However, I am completely confused by behavior of #SessionAttributes.
Please look at SampleController below , it is handling post method by SuperForm class. In fact, just field of SuperForm class is only binding as I expected.
However, After I put #SessionAttributes in Controller, handling method is binding as SubAForm. Can anybody explain me what happened in this binding.
-------------------------------------------------------
#Controller
#SessionAttributes("form")
#RequestMapping(value = "/sample")
public class SampleController {
#RequestMapping(method = RequestMethod.GET)
public String getCreateForm(Model model) {
model.addAttribute("form", new SubAForm());
return "sample/input";
}
#RequestMapping(method = RequestMethod.POST)
public String register(#ModelAttribute("form") SuperForm form, Model model) {
return "sample/input";
}
}
-------------------------------------------------------
public class SuperForm {
private Long superId;
public Long getSuperId() {
return superId;
}
public void setSuperId(Long superId) {
this.superId = superId;
}
}
-------------------------------------------------------
public class SubAForm extends SuperForm {
private Long subAId;
public Long getSubAId() {
return subAId;
}
public void setSubAId(Long subAId) {
this.subAId = subAId;
}
}
-------------------------------------------------------
<form:form modelAttribute="form" method="post">
<fieldset>
<legend>SUPER FIELD</legend>
<p>
SUPER ID:<form:input path="superId" />
</p>
</fieldset>
<fieldset>
<legend>SUB A FIELD</legend>
<p>
SUB A ID:<form:input path="subAId" />
</p>
</fieldset>
<p>
<input type="submit" value="register" />
</p>
</form:form>
When processing POST request, Spring does the following:
Without #SessionAttributes: Spring instantiates a new instance of SuperForm (type is inferred from the signature of register()), populates its properties by values from the form fields and passes it to the register() method.
With #SessionAttributes: Spring obtains an instance of model attribute from the session (where it was placed when processing GET due to presence of #SessionAttributes), updates its properties by values from the from fields and passes it to the register() method.
That is, with #SessionAttributes , register() gets the same instance of the model attribute object that was placed into the Model by getCreateForm().
Adding on to what #axtavt said: Suppose, in getCreateForm you are putting some values for a drop-down (list or map), or you are putting some values in form that you want in register method but you don't want them to show in form (not even in hidden fields). Now suppose that an error occurred in register method and you need to show the form again. To populate drop down values and other values that you would need in next post, you would have to repopulate them in form. The #SessionAttribute helps here as #axtavt very well described above.
#Controller
#SessionAttributes("test")
public class Controller{
Customer customer;
public Controller() {
super();
customer = new Customer();
}
#ModelAttribute("test")
public Customer getCustomer() {
customer.setName("Savac");
return customer;
}
#RequestMapping({"/index"})
public ModelAndView showMainPage (#ModelAttribute("test") Customer customer, ModelMap model, method = RequestMethod.GET) {
//in the view you set the name
return new ModelAndView("index");
}
#RequestMapping(value = "customer/{customerID}", method = RequestMethod.GET)
public ModelAndView viewAdvice(#PathVariable("customerID") int customerID, #ModelAttribute("test") Customer customer, ModelMap model) {
customer.setName("AnotherName");
model.addAttribute("test", customer);
return new ModelAndView("customer");
}
}
According to Spring reference documentation #ModelAttribute annotated method argument is resolved as follows:
Retrieve from model object if it is present (normally added via #ModelAttribute annotated methods)
Retrieve from HTTP session by using #SessionAttributes.
Create using URI path variable that matches the #ModelAttribute name through a converter
Create using default constructor and add it to Model.
A handler class can be annotated with #SessionAttributes with a list of names as its arguments. This is to instruct Spring to persist (in session) those data items present in the model data which match the names specified in #SessionAttributes annotation.
Thus in the SampleController, the post method's #ModelAttribute argument is resolved with #SessionAttributes field due to the resolution method mentioned above.

Resources