I'm adding a user validator using the initBinder method:
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new UserValidator());
}
Here is the UserValidator
public class UserValidator implements Validator {
public boolean supports(Class clazz) {
return User.class.equals(clazz);
}
public void validate(Object target, Errors errors) {
User u = (User) target;
// more code here
}
}
The validate method is getting properly called during the controller method call.
#RequestMapping(value = "/makePayment", method = RequestMethod.POST)
public String saveUserInformation(#Valid User user, BindingResult result, Model model){
// saving User here
// Preparing CustomerPayment object for the payment page.
CustomerPayment customerPayment = new CustomerPayment();
customerPayment.setPackageTb(packageTb);
model.addAttribute(customerPayment);
logger.debug("Redirecting to Payment page.");
return "registration/payment";
}
But while returning to the payment screen I'm getting this error:
java.lang.IllegalStateException: Invalid target for Validator [com.validator.UserValidator#710db357]: com.domain.CustomerPayment[ customerPaymentId=null ]
org.springframework.validation.DataBinder.setValidator(DataBinder.java:476)
com.web.UserRegistrationController.initBinder(UserRegistrationController.java:43)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
java.lang.reflect.Method.invoke(Method.java:597)
org.springframework.web.bind.annotation.support.HandlerMethodInvoker.initBinder(HandlerMethodInvoker.java:393)
org.springframework.web.bind.annotation.support.HandlerMethodInvoker.updateModelAttributes(HandlerMethodInvoker.java:222)
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:429)
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:414)
This might be because I'm returning a CustomerPayment and there is not validator defined for that.
I'm also not able to add multiple validators in initBinder method.
How can I fix this?
You need to set the value of the #InitBinder annotation to the name of the command you want it to validate. This tells Spring what to apply the binder to; without it, Spring will try to apply it to everything. This is why you're seeing that exception: Spring is trying to apply the binder - with your UserValidator - to a parameter of type CustomerPayment.
In your specific case, it looks like you need something like:
#InitBinder("user")
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new UserValidator());
}
To your second question, as Rigg802 explained, Spring does not support attaching multiple validators to a single command. You can, however, define multiple #InitBinder methods for different commands. So, for example, you could put the following in a single controller and validate your user and payment parameters:
#InitBinder("user")
protected void initUserBinder(WebDataBinder binder) {
binder.setValidator(new UserValidator());
}
#InitBinder("customerPayment")
protected void initPaymentBinder(WebDataBinder binder) {
binder.setValidator(new CustomerPaymentValidator());
}
It's a bit tricky to do, 1 controller has only 1 validator on 1 command object.
you need to create a "Composite Validator" that will get all the validators and run them seperately.
Here is a tutorial that explains how to do it: using multiple validators
You can add multiple validators by iterating over all org.springframework.validation.Validator in an ApplicationContext and set up suitable ones in #InitBinder for each request.
#InitBinder
public void setUpValidators(WebDataBinder webDataBinder) {
for (Validator validator : validators) {
if (validator.supports(webDataBinder.getTarget().getClass())
&& !validator.getClass().getName().contains("org.springframework"))
webDataBinder.addValidators(validator);
}
}
See my project for examples and simple benchmarks. https://github.com/LyashenkoGS/spring-mvc-and-jms-validation-POC/tree/benchamark
I do not see a reason why Spring does not filter out all validators which are not applicable to the current entity by default which forces to use things like CompoundValidator described by #Rigg802.
InitBinder allows you to specify name only which give you some control but not full control over how and when to apply your custom validator. Which from my perspective is not enough.
Another thing you can do is to perform check yourself and add validator to binder only if it is actually necessary, since binder itself has binding context information.
For example if you want to add a new validator which will work with your User object in addition to built-in validators you can write something like this:
#InitBinder
protected void initBinder(WebDataBinder binder) {
Optional.ofNullable(binder.getTarget())
.filter((notNullBinder) -> User.class.equals(notNullBinder.getClass()))
.ifPresent(o -> binder.addValidators(new UserValidator()));
}
There is a simple hack, always return true in supports method, and delegate the class checking to validate. Then basically you can add multiple validator in the initBinder without issue.
#Component
public class MerchantRegisterValidator implements Validator {
#Autowired
private MerchantUserService merchantUserService;
#Autowired
private MerchantCompanyService merchantCompanyService;
#Override
public boolean supports(Class<?> clazz) {
return true; // always true
}
#Override
public void validate(Object target, Errors errors) {
if (!RegisterForm.getClass().equals(target.getClass()))
return; // do checking here.
RegisterForm registerForm = (RegisterForm) target;
MerchantUser merchantUser = merchantUserService.getUserByEmail(registerForm.getUserEmail());
if (merchantUser != null) {
errors.reject("xxx");
}
MerchantCompany merchantCompany = merchantCompanyService.getByRegno(registerForm.getRegno());
if (merchantCompany != null) {
errors.reject("xxx");
}
}
}
Multiple validator on one command is supported with Spring MVC 4.x now. You could use this snippet code:
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new UserValidator(), new CustomerPaymentValidator());
}
The safest way is to add a generic validator handling that Controller:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setValidator(new GenericControllerOneValidator());
}
Then, in the generic validator you can support multiple request body models and based of the instance of the object, you can invoke the appropriate validator:
public class GenericValidator implements Validator {
#Override
public boolean supports(Class<?> aClass) {
return ModelRequestOne.class.equals(aClass)
|| ModelRequestTwo.class.equals(aClass);
}
#Override
public void validate(Object body, Errors errors) {
if (body instanceof ModelRequestOne) {
ValidationUtils.invokeValidator(new ModelRequestOneValidator(), body, errors);
}
if (body instanceof ModelRequestTwo) {
ValidationUtils.invokeValidator(new ModelRequestTwoValidator(), body, errors);
}
}
}
Then you add your custom validations inside for each model validator implementatios. ModeRequestOneValidator and ModeRequestTwoValidator still need to implement the Validator interface of org.springframework.validation
Also, do not forget to use #Valid ModeRequestOne and #Valid ModeRequestTwo inside the controllers method call.
One addition to Annabelle's answer:
If controller has this method parameter and you want to validate that one specifically
#RequestMapping(value = "/users", method = RequestMethod.POST)
public String findUsers(UserRequest request){..}
Then the binding should be lower case of the class name (but just the first letter, and not everything else)
#InitBinder("userRequest")
protected void initUserBinder(WebDataBinder binder) {
binder.setValidator(new YourValidator());
}
Declare request as
(... , Model model,HttpServletRequest request)
and change
model.addAttribute(customerPayment);
to
request.setAttribute("customerPayment",customerPayment);
Related
I want to pass org.springframework.validation.Errors to CodeValidator class.
But, since I am not using RequestBody/RequestPart/ModelAttribute, I cannot put Errors in method param after variable.
I use #RequestParam for code variable, and I want to validate that using CodeValidator class that implement org.springframework.validation.Validator.
Here is my code
#RequestMapping(value = "/check-code", method = RequestMethod.POST)
public ResponseEntity<Object> checkCode(#RequestParam("code") String code, Errors errors) {
codeValidator.validate(code, errors);
if(errors.hasErrors()) {
return ResponseEntity.badRequest().body("Errors");
}
return ResponseEntity.ok("");
}
and here error result for my code:
An Errors/BindingResult argument is expected to be declared immediately after the model attribute, the #RequestBody or the #RequestPart arguments to which they apply: public org.springframework.http.ResponseEntity com.example.myapp.controller.CodeController.checkCode(java.lang.String,org.springframework.validation.BindingResult)
what should I do to be able using CodeValidator with #RequestParam?
Updated:
Code for CodeValidator
#Service
public class CodeValidator implements Validator {
#Override
public void validate(Object target, Errors errors) {
String code = ((String) target);
if(code == null || code.isEmpty()) {
errors.rejectValue("code", "", "Please fill in Code.");
}
}
}
Did you create an annotation with your validator?
Otherwise take a look at a small example/tutorial for custom validating with spring: https://www.baeldung.com/spring-mvc-custom-validator
(edit) if you are using spring boot you might need add a MethodValidationPostProcessor bean to your spring config to enable custom valdation for the #requesParam
My controller. Note the custom #Exists annotation:
#RestController
public class ClientApiController {
#RequestMapping(path = "/{client}/someaction", method = RequestMethod.GET)
String handleRequest(#Exists Client client) {
// ...
}
}
The Exists annotation:
/**
* Indicates that a controller request mapping method parametet should not be
* null. This is meant to be used on model types to indicate a required entity.
*/
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Exists {}
The converter which converts the String from the path variable into a Client instance:
#Component
public class StringToClient implements Converter<String, Client> {
#Autowired
private ClientDAO clientDAO;
#Override
public Client convert(String source) {
return clientDAO.getClientById(source);
}
}
The ResourceNotFoundException exception used to trigger a 404
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
}
My controller method receives the converted Client as desired. If the client id used in the URL matches a client, everything works fine. If the id doesn't match, the client parameter is null empty (uses default constructor) in the handle() controller method.
What I can't get to work now is declarative checking that the Client is not null (i.e. that the id refers to an existing client). If it's null, a ResourceNotFoundException should be thrown. Checking whether the argument is null in the method body and throwing my custom ResourceNotFoundException is easy to do, but repetitive (like this one does). Also, this declarative approach should work for all model classes implementing the interface ModelWithId so it can be used for multiple model types.
I've searched the Spring documentation and I haven't found how to achieve this. I need to insert some processing somewhere after type conversion and before the controller's handleRequest method.
I'm using Spring Boot 1.3.3
After type conversion and before the controller's method there is a validation. You can implement custom validator and raise exception in it. Add new validator to DataBinder, and mark method's parameter as #Validated:
#RestController
public class ClientApiController {
#InitBinder
public void initBinder(DataBinder binder){
binder.addValidators(new Validator() {
#Override
public boolean supports(Class<?> aClass) {
return aClass==Client.class;
}
#Override
public void validate(Object o, Errors errors) {
Client client = (Client)o;
if(client.getId()==null) throw new ResourceNotFoundException();
}
});
}
#RequestMapping(path = "/{client}/someaction", method = RequestMethod.GET)
String handleRequest(#Validated #Exists Client client) {
// ...
}
#RequestMapping(path = "/{client}/anotheraction", method = RequestMethod.GET)
String handleAnotherRequest(#Validated #Exists Client client) {
// ...
}
}
Of course, you can declare validator as separate class, and use it repeatedly in other controllers. Actually, you can raise exception right in your converter, but there is possibility, that you'll need the conversion without exception in other places of your application.
If I use #InitBinder without limiting it,it is working fine with #RequestBody to validate my objects.
#InitBinder
private void initBinder(WebDataBinder binder) {
binder.setValidator(validator);
}
#RequestMapping(method=RequestMethod.POST)
public CustomerQuickRegisterEntity saveNewCustomer(#Valid #RequestBody CustomerQuickRegisterEntity customerEntity,BindingResult result)
{
if(result.hasErrors())
{
return new CustomerQuickRegisterEntity();
}
return customerQuickRegisterRepository.save(customerEntity);
}
But problem is that when i limit it to just one object by doing it as #InitBinder("customerEntity") it is not validating the object. So I have searched through stackoverflow and found that #InitBinding only works with objects annotated with #ModelAttribute. Then my question is that it is working fine with #RequestBody when I use it as #InitBinder but does not work well when I use it as #InitBinder("customerEntity") ...why is it so?
Is there any other way to validate Objects(Not properties of object Individually) associated with #RequestBody
This is an old question, but I've managed to get the #InitBinder annotation to bind my custom Validator to a #Valid #RequestBody parameter like this:
#InitBinder
private void bindMyCustomValidator(WebDataBinder binder) {
if ("entityList".equals(binder.getObjectName())) {
binder.addValidators(new MyCustomValidator());
}
}
If you try to filter the bound argument by setting the value of the annotation, then it won't work for a #RequestBody argument. So here I check the object name instead. My method parameter is actually called entities, but Spring had decided to call it entityList. I had to debug it to discover this.
From the docs,
Default is to apply to all command/form attributes and all request
parameters processed by the annotated handler class. 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.
Please have a look here
You can try my solution:
#InitBinder
private void initBinder(WebDataBinder binder) {
if (CustomerQuickRegisterEntity.class.equals(binder.getTarget().getClass())) {
binder.addValidators(new YourValidator());
}
}
Spring MVC support request parameter to javabean. But when I want to bind boolean,
It is still not work in javabean.
If the code in controller is:
public void test(#RequestParam(value="isCheck") boolean isCheck)
I can get the isCheck boolean value.
But when the code in contoller is:
public void test(TestVO testVO)
TestVO javabean:
public class TestVO {
private boolean isPrecheck;
public boolean isPrecheck() {
return isPrecheck;
}
public void setPrecheck(boolean isPrecheck) {
this.isPrecheck = isPrecheck;
}
}
I can not set the request parameter to this Javabean.
Anyone has ideas?
You can you the PropertiesEditor of the Spring.
http://static.springsource.org/spring/docs/3.2.x/javadoc-api/org/springframework/beans/propertyeditors/CustomBooleanEditor.html
Inside your controller create a method to set the editor
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Boolean.class, new CustomBooleanEditor(allowEmpty???));
}
This is due to boolean naming convention and associated ambiguity with its getter methods.
Rename your boolean from isPrecheck to precheck and it will work.
Is it possible to do the same kind of behavior as this :
protected void onBind(HttpServletRequest request, Object command, BindException bindException) throws Exception {
Invoice invoice = (Invoice) command;
invoice.getLineItems().removeAll(Collections.singletonList(null));
}
When using annotation ?
I'm using the #controller annotation so i don't have the onBind function. I want to manipute a List of element (remove item from the list).
New way is to annotate method with #InitBinder annotation:
#InitBinder
protected void initBinder(WebDataBinder binder) {
...
}
Also you may register PropertyEditor for transforming values by calling WebDataBinder.registerCustomEditor method. Also for transforming to collections there is CustomCollectionEditor which may be convenient.