Convert JSR-303 validation errors to Spring's BindingResult - java

I have the following code in a Spring controller:
#Autowired
private javax.validation.Validator validator;
#RequestMapping(value = "/submit", method = RequestMethod.POST)
public String submitForm(CustomForm form) {
Set<ConstraintViolation<CustomForm>> errors = validator.validate(form);
...
}
Is it possible to map errors to Spring's BindingResult object without manually going through all the errors and adding them to the BindingResult? Something like this:
// NOTE: this is imaginary code
BindingResult bindingResult = BindingResult.fromConstraintViolations(errors);
I know it is possible to annotate the CustomForm parameter with #Valid and let Spring inject BindingResult as another method's parameter, but it's not an option in my case.
// I know this is possible, but doesn't work for me
public String submitForm(#Valid CustomForm form, BindingResult bindingResult) {
...
}

A simpler approach could be to use Spring's abstraction org.springframework.validation.Validator instead, you can get hold of a validator by having this bean in the context:
<bean id="jsr303Validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
#Autowired #Qualifier("jsr303Validator") Validator validator;
With this abstraction in place, you can use the validator this way, passing in your bindingResult:
validator.validate(obj, bindingResult);

Spring uses a SpringValidatorAdapter to convert javax.validation.ConstraintViolation objects to ObjectError or FieldError objects, as found in the binding result.
The BindStatus then uses a message source (like the web application context itself) to translate the errors.
In short, you could do:
SpringValidatorAdapter springValidator = new SpringValidatorAdapter(validator);
BindingResult bindingResult= new BeanPropertyBindingResult(myBeanToValidate, "myBeanName");
springValidator.validate(myBeanToValidate, bindingResult);
This is easier when writing a unit test, because you don't even need to create a Spring context.

Expanding on Kristiaan's answer, for testing purposes it is not necessary to create a spring context to validate using Spring's bindingResult. The following is an example:
public class ValidatorTest {
javax.validation.Validator javaxValidator = Validation.buildDefaultValidatorFactory().getValidator();
org.springframework.validation.Validator springValidator = new SpringValidatorAdapter(javaxValidator);
#Test
public void anExampleTest() {
JSR303AnnotatedClassToTest ctt = new JSR303AnnotatedClassToTest( ..init vars..)
... test setup...
WebDataBinder dataBinder = new WebDataBinder(ctt);
dataBinder.setValidator(springValidator);
dataBinder.validate();
BindingResult bindingResult = dataBinder.getBindingResult();
... test analysis ...
}
}
This approach doesn't require creating a binding result ahead of time, the dataBinder builds the right one for you.

#RequestMapping(value = "/submit", method = RequestMethod.POST)
public String submitForm(CustomForm form) {
Set<ConstraintViolation<CustomForm>> errors = validator.validate(form);
BindingResult bindingResult = toBindingResult(errors, form, "form");
...
}
private BindingResult toBindingResult(ConstraintViolationException e, Object object, String objectName) {
BindingResult bindingResult = new BeanPropertyBindingResult(object, objectName);
new AddConstraintViolationsToErrors().addConstraintViolations(e.getConstraintViolations(), bindingResult);
return bindingResult;
}
private static class AddConstraintViolationsToErrors extends SpringValidatorAdapter {
public AddConstraintViolationsToErrors() {
super(Validation.buildDefaultValidatorFactory().getValidator()); // Validator is not actually used
}
#SuppressWarnings({"rawtypes", "unchecked"})
public void addConstraintViolations(Set<? super ConstraintViolation<?>> violations, Errors errors) {
// Using raw type since processConstraintViolations specifically expects ConstraintViolation<Object>
super.processConstraintViolations((Set) violations, errors);
}
}
Unlike the other answers to this question, this solution handles the case where there already exists a Set<ConstraintViolation<?>> which needs to be converted to to a BindingResult.
Explanation
Spring provides the SpringValidatorAdapter class to perform bean validations, storing the results in an Errors instance (note that BindingResult extends Errors). The normal manual use of this class would be to use it to perform the validations via the validate method:
Validator beanValidator = Validation.buildDefaultValidatorFactory().getValidator();
SpringValidatorAdapter validatorAdapter = new SpringValidatorAdapter(beanValidator);
BindException bindException = new BindException(form, "form");
validatorAdapter.validate(form, bindException);
However, this doesn't help in the case where there already exists a Set<ConstraintViolation<?>> which needs to be converted to a BindingResult.
It is still possible to achieve this goal, though it does require jumping through a couple extra hoops. SpringValidatorAdapter contains a processConstraintViolations method which converts the ConstraintViolation objects into the appropriate Spring ObjectError subtypes, and stores them on an Errors object. However, this method is protected, limiting its accesibility to subclasses.
This limitation can be worked around by creating a custom subclass of SpringValidatorAdapter which delegates to or exposes the protected method. It is not a typical usage, but it works.
public class AddConstraintViolationsToErrors extends SpringValidatorAdapter {
public AddConstraintViolationsToErrors() {
super(Validation.buildDefaultValidatorFactory().getValidator()); // Validator is not actually used
}
#SuppressWarnings({"rawtypes", "unchecked"})
public void addConstraintViolations(Set<? super ConstraintViolation<?>> violations, Errors errors) {
// Using raw type since processConstraintViolations specifically expects ConstraintViolation<Object>
super.processConstraintViolations((Set) violations, errors);
}
}
This custom class can be used to populate a newly created BindingResult, achieving the goal of creating a BindingResult from a Set<ConstraintViolation<?>>.
private BindingResult toBindException(ConstraintViolationException e, Object object, String objectName) {
BindingResult bindingResult = new BeanPropertyBindingResult(object, objectName);
new AddConstraintViolationsToErrors().addConstraintViolations(e.getConstraintViolations(), bindingResult);
return bindingResult;
}

I've encountered a similar issue and this is how I resolved it.
Given your example, this is how I implemented it
First, I used a smart validator, and in the method I let spring inject the BindingResult
#Autowired
private org.springframework.validation.SmartValidator validator;
#RequestMapping(value = "/submit", method = RequestMethod.POST)
public String submitForm(CustomForm form, BindingResult bindingResult) {
Set<ConstraintViolation<CustomForm>> errors = validator.validate(form);
...
}
And then using that binding result i pass it in the SmartValidator so that any errors will be bounded to BindingResult.
validator.validate(form, bindingResult);
if(bindingResult.hasErrors()) {
throw new BindException(bindingResult);
}

Related

Get Custom Validation Errors into BindingResult

I'm trying to remove repeating code from my Spring Controllers, specifically - removing the need to execute the validator.validate(form, bindingResult) from the start of many of my functions.
I have a few classes that have corresponding validator classes that implement Spring's validator interface. I have searched around to try and find an answer but I'm having trouble finding one that really matches this.
Snippet of Person Form Class with annotated attributes
public class Person {
#Size(min=1, message="Name missing")
private String name;
#Size(min=1, message="Age missing")
private String age;
.... getters and setters etc.
Person Validator Class
#Component
public class PersonValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Person.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
errors.reject("No sir!");
}
}
Ideally, I'd like to be able to have all the errors contained within the BindingResult, including the errors from the validator class. So that when I use the #Validated annotation my BindingResult is fully populated with all the errors from both the simple annotations and the custom validator.
Desired Outcome
#RequestMapping(value="/save", method=RequestMethod.POST)
public #ResponseBody String save(#Validated #RequestBody Person personForm, BindingResult bindingResult, HttpServletRequest request)
{
bindingResult.getAllErrors(); <-- fully pop with annotation and custom validator errors
Instead of:
#RequestMapping(value="/save", method=RequestMethod.POST)
public #ResponseBody String save(#Validated #RequestBody Person personForm, BindingResult bindingResult, HttpServletRequest request)
{
personValidator.validate(person, bindingResult) <-- Populate bindingResult with customer validator errors, if any
bindingResult.getAllErrors();
Has anyone got any neat examples they can share to get around this?
Thanks!
You need to add the validator to the databinder for multiple validators to work. In your code add an #InitBinder method and add the PersonValidator to the WebDataBinder.
#InitBinder("personForm")
public void initBinder(WebDataBinder wdb) {
wdb.addValidators(personValidator);
}
Will bind a validator to the personForm model object.
This will configure a global rule that this validator is applied to all bindings/conversions. If you want to limit this to a certain model you can specify the name of the model in the #InitBinder.
#InitBinder
public void initBinder(WebDataBinder wdb) {
wdb.addValidators(personValidator);
}
As possible solution, you can define your own custom annotation and CustomConstraintValidator that will implement interface ConstraintValidator<A extends Annotation, T>.
At the end BindingResult will contain either default validator and your custom validator errors.
Here is a good example. If I understand your question correctly of course.

Multiple Spring MVC validator for the same Controller

I am having a Spring controller with a Validator defined as:
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new MyValidator(myService));
}
And calling it:
public ResponseEntity<?> executeSomething(
#ApiParam(name = "monitorRequest", required = true, value = "") #Valid #RequestBody MonitorRequest monitorRequest,
HttpServletRequest request, HttpServletResponse response) throws RESTException
I need to add one more Validator for this controller that could be called from some specific methods of this controller. Is there any way to achieve this?
EDIT: I am handling the Error by:
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseBody
public ResponseEntity<?> processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<FieldError> fieldErrors = result.getFieldErrors();
ValidationErrorObj obj = processFieldErrors(fieldErrors);
ResponseEntity r = new ResponseEntity(obj, HttpStatus.BAD_REQUEST);
return r;
}
You can have more than one InitBinder method in a controller. It is controlled by the optional value parameter . For the javadoc of InitBinder : String[] value : The names of command/form attributes and/or request parameters that this init-binder method is supposed to apply to ... Specifying model attribute names or request parameter names here restricts the init-binder method to those specific attributes/parameters, with different init-binder methods typically applying to different groups of attributes or parameters.
Another way would be to explicely call a complementary Validator in specific methods.
BTW : I can't see any Errors or BindingResult in your controller method signature : where do you find whether errors occured ?
For those who are still trying to figure out how to solve this in 2017. I was facing similar issues while trying to implement 2 validators in my RestController. I followed the approach mentioned above by #Serge Ballasta.
I ended up making 2 Model each of linked to their specific Validators. The Controller methods look something like
#RequestMapping(value = "register", method = RequestMethod.POST)
public ResponseEntity<User> register(#Valid #RequestBody UserRegisterRequest userRegisterRequest) {
return null;
}
#RequestMapping(value = "test", method = RequestMethod.POST)
public ResponseEntity<?> test(#Valid #RequestBody TestRequest testRequest) {
return null;
}
and I created 2 initBinders to wire these validators in the controller like
#InitBinder("testRequest")
public void setupBinder(WebDataBinder binder) {
binder.addValidators(testValidator);
}
#InitBinder("userRegisterRequest")
public void setupBinder1(WebDataBinder binder) {
binder.addValidators(userRegistrationRequestValidator);
}
Please note that the #RequestBody attributes (userRegisterRequest , testRequest) had to be provided as values in the #InitBinder() annotations.
By the way the in my code I handle the bindingResult in a custom ExceptionHandler class which extends ResponseEntityExceptionHandler which gives me freedom to do custom handling of the response.

Spring: custom validator is not being called

I was looking at other questions about Spring custom validators but unfortunately I could not solve my problem with the proposed answers.
My problem is the following: I have an entity (Account) and I created a custom validator (AccountValidator) which I use in a controller (RegisterController), but it is never invoked, using the default Validator.
Am I forgetting something? I attach part of the code to help understand better my problem.
Validator:
public class AccountValidator implements Validator{
#Override
public boolean supports(Class<?> clazz) {
return (Account.class).isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
//Validation code
}
}
Controller:
#Controller
#RequestMapping(value = "/register")
public class RegisterController {
#Autowired
private AccountValidator accountValidator;
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setDisallowedFields("id");
binder.setValidator(accountValidator);
}
#RequestMapping(method = RequestMethod.GET)
#ModelAttribute
public Account register(Locale currentLocale){
Account account = new Account();
return account;
}
#RequestMapping(method = RequestMethod.POST)
public String handleRegister(#Valid #ModelAttribute Account account, BindingResult result){
if(result.hasErrors()){
return "/register";
}
return "home";
}
}
I checked my debug messages in the log, and the initBinder method is being called, but the validation method is never being executed.
Can anyone help me?
I was facing the same issue and i fixed it by declaring the class AccountValidator in context xml file and using #validated in place of #valid.
After going through the source code, the only reason I can find for the WebDataBinder not to invoke your Validator is that your variable is null. This field
#Autowired
private AccountValidator accountValidator;
must be null. I don't know how you got there, Spring would complain if it couldn't autowire a field.
At the moment, I can't tell you why the Validator isn't being called when registered with the WebDataBinder, but here's the workaround:
Get rid of the
binder.setValidator(accountValidator);
and add the the Validator call in the handler method
#RequestMapping(method = RequestMethod.POST)
public String handleRegister(#Valid #ModelAttribute Account account, BindingResult result){
accountValidator.validate(account, result);
if(result.hasErrors()){
return "/register";
}
return "home";
}
Spring will perform default validation (based on your validation provider, ex. Hibernate) and then you apply your custom validation.
I had this same problem and it turned out I was getting an SQL exception, seemingly bypassing my validator because I had my method annotated with #Transactional.
I think you want to use binder.addValidator(accountValidator); instead of binder.setValidator(accountValidator);

Can I add value in a BindingResult before checking Errors in Spring?

Can I add value in a BindingResult before checking Errors in Spring?
#InitBinder("memberrequest")
public void initMemberRequestBinder(WebDataBinder binder) {
binder.setValidator(new MemberRequestValidator());
}
#PreAuthorize("isAuthenticated()")
#RequestMapping(value = "/save", method = RequestMethod.POST)
public ModelAndView saveRequest(#Valid #ModelAttribute("memberrequest") MemberRequest mr, BindingResult result, HttpSession session) {
session.setAttribute("phone", mr.getPhonenumber());
mr.setWelfare((String)session.getAttribute("welfare"));
mr.setSchool((String)session.getAttribute("school"));
mr.setTitle((String)session.getAttribute("title"));
mr.setDistrict((String)session.getAttribute("district"));
mr.setName((String)session.getAttribute("name"));
mr.setFile((String)session.getAttribute("file"));
mr.setQueue((String)session.getAttribute("queue"));
mr.setRequestor(getUser());
mr.setSchool_id((String)session.getAttribute("school_id"));
mr.setBorough_id((String)session.getAttribute("borough_id"));
mr.setRetiree((String)session.getAttribute("retiree"));
if (result.hasErrors()) {
LOGGER.debug("Pages had errors on it... returning to input page");
return new ModelAndView("w-question");
} else {
I have the above code in my Spring controller but the issue is that I need to take some values out of the session and move them into the BindingResult (Bean) before Validator runs on it..
Can this be done someone? the issues is some of the values I keep in the session.. please me know if this can be dont and how is the best way to do it..
In your controler define method for creating your model atribute and annotate it with #ModelAttribute annotation.
Actually you will not modify the binding result object itself but the validation target and then you can change your validator behavior to change binding result as you need.
#ModelAttribute("memberrequest")
public MemberRequest getMemberRequest(HttpSession session) {
MemberRequest mr = new MemberRequest();
mr.setWelfare((String)session.getAttribute("welfare"));
mr.setSchool((String)session.getAttribute("school"));
mr.setTitle((String)session.getAttribute("title"));
mr.setDistrict((String)session.getAttribute("district"));
mr.setName((String)session.getAttribute("name"));
mr.setFile((String)session.getAttribute("file"));
mr.setQueue((String)session.getAttribute("queue"));
mr.setRequestor(getUser());
mr.setSchool_id((String)session.getAttribute("school_id"));
mr.setBorough_id((String)session.getAttribute("borough_id"));
mr.setRetiree((String)session.getAttribute("retiree"));
return mr;
}
this method will be called before the binding ocures, but have in mind that this method will be called before each controler method wich is using #ModelAttribute("memberrequest") as parameter.

Spring MVC and JSR-303 hibernate conditional validation

I've a form I want to validate. It contains 2 Address variables. address1 has always to be validated, address2 has to be validated based on some conditions
public class MyForm {
String name;
#Valid Address address1;
Address address2;
}
public class Address {
#NotEmpty
private String street;
}
my controller automatically validates and binds my form obj
#RequestMapping(...)
public ModelAndView edit(
#ModelAttribute("form")
#Valid
MyForm form,
BindingResult bindingResult,
...)
if(someCondition) {
VALIDATE form.address2 USING JSR 303
the problem is that if I use the LocalValidatorFactoryBean validator i can't reuse the BinidingResult object provided by Spring. The bind won't work as the target object of 'result' is 'MyForm' and not 'Address'
validate(form.getAddress2(), bindingResult) //won't work
I'm wondering what's the standard/clean approach to do conditional validation.
I was thinking in programmatically create a new BindingResult in my controller.
final BindingResult bindingResultAddress2 = new BeanPropertyBindingResult(address2, "form");
validate(form.getAddress2(), bindingResultAddress2);
but then the List of errors I obtain from bindingResultAddress2 can't be added to the general 'bindingResult' as the field names are not correct ('street' instead of 'address2.street') and the binding won't work.
Some dirty approach would be to extend BeanPropertyBindingResult to accept some string to append to the fields name.. do you have a better approach?
The standard approach for validating hierarchical structures is to use pushNestedPath()/popNestedPath(), though I'm not sure how it plays with JSR-303:
bindingResult.pushNestedPath("address2");
validate(form.getAddress2(), bindingResult);
bindingResult.popNestedPath();
I've never tried myself, but I think the correct approach is using validator groups.
First of all, let's see #javax.validation.Valid API
Mark an association as cascaded. The associated object will be validated by cascade.
When Spring framework uses #Valid as a marker to validate its command objects, it corrupts its purpose. Spring should instead create your own specific annotation which specifies the groups which should be validated.
Unfortunately, you should use Spring native Validator API if you need to validate some groups
public void doSomething(Command command, Errors errors) {
new BeanValidationValidator(SomeUserCase.class, OtherUserCase.class)
.validate(command, errors);
if(errors.hasErrors()) {
} else {
}
}
BeanValidationValidator can be implemented as
public class BeanValidationValidator implements Validator {
javax.validation.Validator validator = ValidatorUtil.getValidator();
private Class [] groups;
public BeanValidationValidator(Class... groups) {
this.groups = groups;
}
public void validate(Object command, Errors errors) {
Set<ConstraintViolation<Object>> constraintViolationSet = validator.validate(command, groups);
for(ConstraintViolation<Object> constraintViolation: constraintViolationSet) {
errors.rejectValue(constraintViolation.getPropertyPath().toString(), null, constraintViolation.getMessage());
}
}
}

Categories