JSR 303 set custom validator for cascade #valid - spring

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.

Related

Spring Boot + Thymeleaf - form validation

i have problem with Thymeleaf when validating form. I'm trying to create simple user register form to learn Spring and i'm unfortunately stuck.
Here is my UserForm class
public class UserForm {
#NotEmpty
private String username;
#NotEmpty
private String password;
#NotEmpty
private String passwordConfirm;
\\ Getters and Setters
}
First problem is when I add my custom validator class in initBinder
#Autowired
private UserFormValidator formValidator;
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setValidator(formValidator);
}
"Default" annotated by #NotEmpty validation stops working. This is exptected behavior?
Second problem is how can I show global reject messages in thymeleaf?
My validator class is like below
public class UserFormValidator implements Validator {
#Autowired
UserService userService;
#Override
public boolean supports(Class<?> clazz) {
return UserForm.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
UserForm userForm = (UserForm) target;
if(!userForm.getPassword().equals(userForm.getPasswordConfirm())) {
errors.reject("passwords.no.match", "Passwords not match");
}
if(userService.findOneByUsername(userForm.getUsername()).isPresent()) {
errors.reject("user.exist", "User already exists (default)");
}
}
}
and post mapping from controller
#PostMapping("/create")
public String registerUser(#ModelAttribute("form") #Valid final UserForm form, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return "newuser";
}
userService.saveUser(form);
return "redirect:/";
}
As "default" validation errors i can show by using exth:if="${#fields.hasErrors('passwordConfirm')}" i have no idea how can i show message for error passwords.no.match or check if this error occured?
By default spring boot uses bean validation to validated form object annotated with #Valid. If you want to use your custom validator and register it through #InitBinder, then bean validation will not take place, this is expected behavior. If you want to bean validation also works with your custom validation you need to do it manually inside your validator class or even in controller.
Here comes your second problem to show password not match error message. Inside your custom validator UserFormValidator.class while rejecting any value you need to use rejectValue() method like below:
#Override
public void validate(Object target, Errors errors) {
UserForm userForm = (UserForm) target;
if(!userForm.getPassword().equals(userForm.getPasswordConfirm())) {
errors.rejectValue("passwordConfirm", "passwords.no.match", "Passwords not match");
}
if(userService.findOneByUsername(userForm.getUsername()).isPresent()) {
errors.rejectValue("username", "user.exist", "User already exists (default)");
}
}
The rejectValue() method is used to add a validation error to the Errors object.
The first parameter identifies which field the error is associated with. The second parameter is an error code which acts a message key for the messages.properties file (or messages_en.properties or messages_fr.properties etc, if these are being used). The third parameter of rejectValue() represents the fallback default message, which is displayed if no matching error code is found in the resource bundle.
Now you can show error messages using th:if="${#fields.hasErrors('passwordConfirm')} inside your form.

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!

How to view data in Jsp when added in a model using spring mvc

I have the following flows for an application when a submit button is clicked:
1)The viewActivity method is called from ActivityController.java
ActivityController.java
#ActionMapping(params = "ActivityController=showActivity")
public void viewActivity(#RequestParam Integer index, ActionResponse response, Model model, #ModelAttribute Header header,
....
model.addAttribute("recoveryForm", new RecoveryForm(detailsResult.getDetails()));
response.setRenderParameter("ServiceController", "showService");
}
2) Then showRecovery method is called from serviceConroller as show below:
ServiceController.JAVA
#RenderMapping(params = "ServiceController=showService")
public String showRecovery(#ModelAttribute recoveryForm form, #ModelAttribute header header) {
.....
return service;
}
Then my service.jsp is displayed
Basically i have to display the value of a variable which is detailName found in DetailsResult.getDetails() object which i have added to my model as
it can be seen in viewActivity method found in ActivityController.java showed ealier.
I know when we add model.addAttribute it should be able to be displayed on this jsp using the following tag :
<form:input path="..." />
But in this case it is added to as a constructor argument as shown below:
model.addAttribute("recoveryForm", new RecoveryForm(detailsResult.getDetails()));
I have the following variable on my RecoveryForm:
public class RecoveryForm implements Serializable {
private CDetails Cdlaim;
private Action addAction;
private String addRemark;
private String remarks;
public RecoveryForm(CDetails Cdlaim) {
...
}
...
}
However i don't have the detailsResult in my RecoveryForm.
Any idea how i can get a value which is in DetailsResult.getDetails() in my service.jsp?
I believe you are looking at this the wrong way. The value of DetailsResult.getDetails() is obviously stored in RecoveryForm as a property somehow. So, I'm going to assume your RecoveryForm looks something like:
public class RecoveryForm {
private String details;
public RecoveryForm(String details) {
this.details = details;
}
public String getDetails() {
return details;
}
}
When you bind to a form in your jsp, you need to nest your <form:input ...> tag in a <form:form ...> tag:
<form:form commandName="recoveryForm" ...>
<form:input path="details" ... />
</form:form>
The commandName is key to telling the form the model object from which you will be pulling form values. In this case you are getting the details property from the RecoveryForm instance named recoveryForm. Make sense?

Spring MVC binding extra objects

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
}

Hibernate Validator custom messages key with class name and field name

I've been trying to add custom messages for validation errors for a REST Service managed by Spring MVC within a #Controller class.
The Employee class:
public class Employee {
#NotNull
#NotEmpty
private String company;
...
}
My REST Service:
#ResponseStatus(value = HttpStatus.CREATED)
#RequestMapping(method = RequestMethod.POST)
public void add(#RequestBody #Valid Employee employee) {
employees.add(employee);
}
And the validation errors parses
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ExceptionHandler(MethodArgumentNotValidException.class)
public #ResponseBody
List<String> validationExceptions(MethodArgumentNotValidException e) {
List<String> errors = new ArrayList<String>();
for (FieldError error : e.getBindingResult().getFieldErrors()) {
errors.add(error.getDefaultMessage());
}
return errors;
}
So I've put a ValidationMessages.properties on the root of my classpath, and I'm not able to get my custom messages with the following key NotEmpty.employee.company.
I know there are many ways to do this with a ResourceBundle and error.getCode(), or even with the key org.hibernate.validator.constraints.NotEmpty.message, but I'd like have specific messages to specific field of specific objects.
I also don't want to do this with #NotEmpty(message = "NotEmpty.employee.company}"). I want it the simplest.
What should I do?
Have you tried to implement your own
org.springframework.validation.MessageCodesResolver
and then declaring your implementation in the config file:
<mvc:annotation-driven message-codes-resolver="org.example.YourMessageCodesResolverImpl"/>
I'd give it a try, it seems this one is able to build custom error codes like the ones you want:
String[] resolveMessageCodes(String errorCode, String objectName, String field, Class<?> fieldType)
The only and important thing I'm not sure is whether it'll override the error codes generated by the hibernate validators...
I hope it helps (and works).
Cheers,
Chico.

Resources