Multiple Spring MVC validator for the same Controller - java

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.

Related

Access BindingResult of Spring WebDataBinder without putting in the argument list

I am trying to use Spring validation with a controller interface generated by swagger-codegen. The swagger code generation supplies an abstract class for a controller. Our controller implements the codegen class and provides the actual logic. I would like to access the BindingResult in my controller methods, but swagger-codegen does not generate that parameter in its interface. Is there any way to get ahold of the BindingResults object other than specifying it as a parameter?
To make this more concrete, the codegen makes the endpoint like this (noisy code removed):
#RequestMapping(value = "/api/repository/v1/datasets",
produces = { "application/json" },
consumes = { "application/json" },
method = RequestMethod.POST)
default ResponseEntity<JobModel> createDataset(#Valid #RequestBody DatasetRequestModel dataset) {
...
}
We implement a controller with the usual binder setup like:
#InitBinder
protected void initBinder(final WebDataBinder binder) {
binder.addValidators(requestValidator)
}
but within the endpoint, we have no way to get the BindingResult since it has to match the signature of the codegen entry:
public ResponseEntity<StudySummaryModel> createStudy(#Valid #RequestBody StudyRequestModel studyRequest) {
...
}
I think the most straightforward solution may be to skip using WebDataBinder. Instead, I can have each controller endpoint call validators directly.
I found another approach besides hand coding the validation; using an #ControllerAdvice class that extends ResponseEntityExceptionHandler.
There is a nice example here: Spring Validation Example
Here is my code based on that example that formats the error:
#ControllerAdvice
public class ApiValidationExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request
) {
BindingResult bindingResult = ex.getBindingResult();
List<String> errorDetails = bindingResult
.getFieldErrors()
.stream()
.map(err -> err.getCode() + " error on '" + err.getObjectName() + "': " + err.getDefaultMessage())
.collect(toList());
ErrorModel errorModel = new ErrorModel()
.message("Validation errors - see error details")
.errorDetail(errorDetails);
return new ResponseEntity<>(errorModel, HttpStatus.BAD_REQUEST);
}
}

Spring 3.2 Validate Request Param, ControllerAdvice not formatting response

I'm using JSR 303 #Valid to validate multiple request params in a controller, the params posted along with a MultipartFile.
The validation part of this seems to be working,
#RequestMapping(value = "/upload", method = RequestMethod.POST)
public #ResponseBody
Response upload(#RequestParam(value = "file", required = true) MultipartFile file,
#Valid ValidBean bean) {
//method
}
ValidBean is a collection of Strings, Longs and a List<String>.
public class ValidBean{
#NotNull
String someString;
#Size(min = 1, max=10)
String anotherString;
//getters, setters, random been goodness
}
It seems that the client is getting rejected if the posted params do not match what is defined in ValidBean.
Where I am having an issue is with my global #ControllerAdvice ValidationHandler.
#ControllerAdvice
public class ValidationHandler {
#ExceptionHandler
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public #ResponseBody
StatusContext handleArgumentNotValid( MethodArgumentNotValidException error ){
//format and return response
}
My #ExceptionHandler only seems to be used in methods that use #RequestBody #Valid
So this method returns a formatted response,
#RequestMapping(value = "/works", method = RequestMethod.POST)
public #ResponseBody
Formatted addUser(#RequestBody #Valid ValidBean user)
And this one does not,
#RequestMapping(value = "/noGood", method = RequestMethod.POST)
public #ResponseBody
NotFormatted addUser(#Valid ValidBean user)
Though both do seem to actually perform validation.
The documentation for #Valid #RequestBody has this to say:
Just like with #ModelAttribute parameters, an Errors argument can be
used to examine the errors. If such an argument is not declared, a
MethodArgumentNotValidException will be raised
that is the reason why your #ExceptionHandler with a method signature of MethodArgumentNotValidException gets called for #Valid #RequestBody.
On the other hand without #RequestBody but with #Valid, without an additional BindingResult parameter, a BindException gets generated, this will not be handled by the current specific signature of your #ExceptionHandler. The fix may be to make your #ExceptionHandler a little broader or add another #ExceptionHandler for BindException. Even better may be to just add BindingResult as an additional parameter.

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: bind request attribute to controller method parameter

In Spring MVC, it is easy to bind request parameter to method paramaters handling the request. I just use #RequestParameter("name"). But can I do the same with request attribute? Currently, when I want to access request attribute, I have to do following:
MyClass obj = (MyClass) request.getAttribute("attr_name");
But I really would like to use something like this instead:
#RequestAttribute("attr_name") MyClass obj
Unfortunately, it doesn't work this way. Can I somehow extend Spring functionality and add my own "binders"?
EDIT (what I'm trying to achieve): I store currently logged user inside request attribute. So whenever I want to access currently logged user (which is pretty much inside every method), I have to write this extra line user = (User) request.getAttribute("user");. I would like to make it as short as possible, preferably inject it as a method parameter. Or if you know another way how to pass something across interceptors and controllers, I would be happy to hear it.
Well, I finally understood a little bit how models work and what is #ModelAttribute for. Here is my solution.
#Controller
class MyController
{
#ModelAttribute("user")
public User getUser(HttpServletRequest request)
{
return (User) request.getAttribute("user");
}
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String HandleSomeUrl(#ModelAttribute("user") User user)
{
// ... do some stuff
}
}
The getUser() method marked with #ModelAttribute annotation will automatically populate all User user parameters marked with #ModelAttribute. So when the HandleSomeUrl method is called, the call looks something like MyController.HandleSomeUrl(MyController.getUser(request)). At least this is how I imagine it. Cool thing is that user is also accessible from the JSP view without any further effort.
This solves exactly my problem however I do have further questions. Is there a common place where I can put those #ModelAttribute methods so they were common for all my controllers? Can I somehow add model attribute from the inside of the preHandle() method of an Interceptor?
Use (as of Spring 4.3) #RequestAttribute:
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String handleSomeUrl(#RequestAttribute User user) {
// ... do some stuff
}
or if the request attribute name does not match the method parameter name:
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String handleSomeUrl(#RequestAttribute(name="userAttributeName") User user) {
// ... do some stuff
}
I think what you are looking for is:
#ModelAttribute("attr_name") MyClass obj
You can use that in the parameters for a method in your controller.
Here is a link a to question with details on it What is #ModelAttribute in Spring MVC?
That question links to the Spring Documentation with some examples of using it too. You can see that here
Update
I'm not sure how you are setting up your pages, but you can add the user as a Model Attribute a couple different ways. I setup a simple example below here.
#RequestMapping(value = "/account", method = RequestMethod.GET)
public ModelAndView displayAccountPage() {
User user = new User(); //most likely you've done some kind of login step this is just for simplicity
return new ModelAndView("account", "user", user); //return view, model attribute name, model attribute
}
Then when the user submits a request, Spring will bind the user attribute to the User object in the method parameters.
#RequestMapping(value = "/account/delivery", method = RequestMethod.POST)
public ModelAndView updateDeliverySchedule(#ModelAttribute("user") User user) {
user = accountService.updateDeliverySchedule(user); //do something with the user
return new ModelAndView("account", "user", user);
}
Not the most elegant, but works at least...
#Controller
public class YourController {
#RequestMapping("/xyz")
public ModelAndView handle(
#Value("#{request.getAttribute('key')}") SomeClass obj) {
...
return new ModelAndView(...);
}
}
Source : http://blog.crisp.se/tag/requestattribute
From spring 3.2 it can be done even nicer by using Springs ControllerAdvice annotation.
This then would allow you to have an advice which adds the #ModelAttributes in a separate class, which is then applied to all your controllers.
For completeness, it is also possible to actually make the #RequestAttribute("attr-name") as is.
(below modified from this article to suit our demands)
First, we have to define the annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface RequestAttribute {
String value();
}
Then we need a [WebArgumentResolver] to handle what needs to be done when the attribute is being bound
public class RequestAttributeWebArgumentResolver implements WebArgumentResolver {
public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest nativeWebRequest) throws Exception {
// Get the annotation
RequestAttribute requestAttributeAnnotation = methodParameter.getParameterAnnotation(RequestAttribute.class);
if(requestAttributeAnnotation != null) {
HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest();
return request.getAttribute(requestAttributeAnnotation.value);
}
return UNRESOLVED;
}
}
Now all we need is to add this customresolver to the config to resolve it:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="customArgumentResolver">
<bean class="com.sergialmar.customresolver.web.support.CustomWebArgumentResolver"/>
</property>
</bean>
And we're done!
Yes, you can add your own 'binders' to the request attribute - see spring-mvc-3-showcase, or use #Peter Szanto's solution.
Alternatively, bind it as a ModelAttribute, as recommended in other answers.
As it's the logged-in user that you want to pass into your controller, you may want to consider Spring Security. Then you can just have the Principle injected into your method:
#RequestMapping("/xyz")
public String index(Principal principle) {
return "Hello, " + principle.getName() + "!";
}
In Spring WebMVC 4.x, it prefer implements HandlerMethodArgumentResolver
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(RequestAttribute.class) != null;
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return webRequest.getAttribute(parameter.getParameterAnnotation(RequestAttribute.class).value(), NativeWebRequest.SCOPE_REQUEST);
}
}
Then register it in RequestMappingHandlerAdapter

Convert JSR-303 validation errors to Spring's BindingResult

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);
}

Categories