Spring Validation - Programmatically bind to a Validator - java

I am trying to use Spring validation to validate my model populated by Jackson converter.
So I have a java class,
class MyClass(){
private String myString;
}
This class is populated by Jackson and I have the instance in my Java code.
Have also defined a validator class like,
class MyValidator implements Validator {
public boolean supports(Class<?> clazz) {
return MyClass.class.equals(clazz);
}
public void validate(Object object, Errors errors) {
//Validation logic here
}
}
Now what I wanted to do was to send the object to the validator and get the errors object, examine and proceed further. But, for calling
validate(Object object,Errors errors)
I need an errors instance which I dont have. I cant use BindingResult or Errors. Please advise on how to proceed further.

You can do this manually in code with a DataBinder:
MyClass toValidate = new MyClass();
DataBinder binder = new DataBinder(toValidate);
binder.setValidator(new MyValidator());
binder.validate();
if (binder.getBindingResult().hasErrors()) {
// oh noes!
}
Although if you've got a #ModelAttribute defined in a #Controller in spring-mvc, something like this should work (placed inside the relevant #Controller):
#ModelAttribute("myclass")
public MyClass myClass() {
return new MyClass();
}
#InitBinder("myclass")
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new MyValidator());
}
#RequestMapping(value = "/do/something", method = POST)
public ModelAndView validatedRequest(#Valid #ModelAttribute("myclass") MyClass profile,
BindingResult result) {
if (result.hasErrors()) {
// oh noes!
}
}

Related

Access resource method arguments from inside a Jersey filter or interceptor. Or use AOP with resource method

I'm trying to enrich the SLF4J MDC on each request with the user's ID. The problem is that the ID can be passed in many ways, sometimes as a path parameter, sometimes in the body, and sometimes injected by a custom ValueFactoryProvider that first decrypts it.
If I could somehow access all the injected (i.e. already deserialized) parameter values, I could handle all these cases easily.
E.g.
For a resource such as:
#GET
//#Encrypted params are injected by a custom ValueFactoryProvider
public Something getSomething(#Encrypted("userId") String userId) {
return ...;
}
#POST
public Something getSomething(#RequestBody RequestWithUserId requestWithUserId) {
return ...;
}
I could have a filter such as:
public class MdcFilter implements ContainerRequestFilter, ContainerResponseFilter {
#Context
private ResourceInfo resourceInfo;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
Method theMethod = resourceInfo.getResourceMethod();
for (Parameter parameter : theMethod.getParameters()) {
//Deal with the #Encrypted case
if (parameter.isAnnotationPresent(Encrypted.class) && parameter.getAnnotation(Encrypted.class).value().equals("userId")) {
MDC.put("userId", somehowGetTheValue());
}
//Deal with the #RequestBody case
if (parameter.isAnnotationPresent(RequestBody.class) && parameter.getType().equals(RequestWithUserId.class)) {
MDC.put("userId", ((RequestWithUserId)somehowGetTheValue()).getUserId());
}
... //other possibilities
}
}
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
MDC.clear();
}
}
But I don't see a way to implement somehowGetTheValue either from a ContainerRequestFilter an interceptor or anything else...
Jersey uses HK2 under the hood for dependency injection. And HK2 has AOP support. One option for your use case would be use this AOP support. All you need to do is implement a MethodInterceptor and an InterceptionService. In the MethodInterceptor, you can get all the arguments from the MethodInvocation and you can get parameter annotation from the Method
class MyMethodInteceptor implements MethodInterceptor {
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
Object[] args = invocation.getArguments();
// do your logging or whatever with the args.
// invoke method and get return value.
Object returnValue = invocation.proceed();
// if you want to do something with the return
// value before returning it, you can.
return returnValue;
}
}
To use the interceptor, you configure the InterceptionService.
public class MyInterceptionService implements InterceptionService {
private final static MethodInterceptor METHOD_INTERCEPTOR
= new MyMethodInterceptor();
private final static List<MethodInterceptor> METHOD_LIST
= Collections.singletonList(METHOD_INTERCEPTOR);
#Override
public Filter getDescriptorFilter() {
return BuilderHelper.allFilter();
}
#Override
public List<MethodInterceptor> getMethodInterceptors(Method method) {
// you implement shouldIntercept
if (shouldIntercept(method)) {
return METHOD_LIST;
}
return null;
}
#Override
public List<ConstructorInterceptor> getConstructorInterceptors(Constructor<?> constructor) {
return null;
}
}
You determine which method should be intercepted in the getMethodInterceptors() method. If the method should be intercepted, then return a list of interceptors, otherwise return null. A common way of handling this is to create a custom annotation and just annotate the method. The in the above method, just check
if (method.isAnnotationPresent(YourAnno.class)) {
return METHOD_LIST;
}
To make it all work, you just need to register the InteceptionService with HK2. You can do that in an AbstractBinder, which is what is used in a Jersey app to configure your DI.
ResourceConfig config = new ResourceConfig();
config.register(new AbstractBinder() {
#Override
protected void configure() {
bind(MyInterceptionService.class)
.to(InterceptionService.class)
.in(Singleton.class);
}
});
You can see a complete example in this GitHub repo. There is also an official example in the HK2 site. Just see "AOP support" the link at the top of the post.
You can get it like this
StringWriter stringWriter = new StringWriter();
IOUtils.copy(new InputStreamReader(requestContext.getEntityStream()), stringWriter);
System.out.println(stringWriter.toString());// String representation of the payload
requestContext.setEntityInputStream(new ByteArrayInputStream(requestEntity));
Basically the idea is to copy the stream and do any processing and then set the stream back. Because if you don't do that, then in your controller method you would get null, becuase the stream was already read.

Spring: Register Handler Method dependening on Order annotation

I have two Controllers:
#Controller
#Order(Ordered.LOWEST_PRECEDENCE)
public class BaseController {
#RequestMapping("/hello.html")
public String hello(ModelMap model) {
model.addAttribute("hello", "world");
return "hello";
}
}
#Controller
public class ProjectSpecificController {
#Autowired
private BaseController baseController;
#Override
#RequestMapping("/hello.html")
public String hello(ModelMap model) {
model.addAttribute("project", "name");
return baseController.hello(model);
}
}
As Spring would trigger this Exception: java.lang.IllegalStateException: Ambiguous mapping found. Cannot map 'baseController' bean method public java.lang.String com.example.BaseController.hello(org.springframework.ui.ModelMap) to {[/hello.html],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}: There is already 'projectSpecificController' bean method public java.lang.String com.example.ProjectSpecificController.hello(org.springframework.ui.ModelMap) mapped.
I would like to use the #Order annotation to map ProjectSpecificController.hello first and if there's already a mapping found for /hello.html ignore the other mappings and do not register their methods:
public class OrderedRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
#Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
try {
super.registerHandlerMethod(handler, method, mapping);
} catch (IllegalStateException e) {
// mapping already happened for a controller of higher precedence, so ignore
}
}
}
Is it enough to catch the exception or do I have to look for the #Order annotation myself? If I have to take care of the #Order annotation myself: What's the best practice to realize my plan?
If I were you, I would not try to go that way.
If I correctly understand, you have one BaseController and you want to override the processing of an URL. I already did something not too far from that by :
delegate processing to a normal method (not #RequestMapping annotated) in base controller
override that method in a class extending the controller
use the subclass as a bean (and not the parent class)
The hard part is that you have to explicitely declare the proper controller bean. I did it with XML so it was easy (just a line to change i the xml file). In Java config, I would explicitely declare the controller bean in a #Configuration annotated class.
Globally it would look like :
public class BaseController {
#RequestMapping("/hello.html")
public String hello(ModelMap model) {
return doHello(model);
}
protected String doHello(ModelMap model) {
model.addAttribute("hello", "world");
return "hello";
}
}
public class ProjectSpecificController extends BaseController{
#Override
protected String doHello(ModelMap model) {
model.addAttribute("project", "name");
return super.doHello(model);
}
}
#Configuration
class HelloConfig {
// other configuration elements ...
#Bean
public BaseController helloController() {
// implement the logic to choose the right implementation
return (specific ? new ProjectSpecificController() : new BaseController());
}
// other configuration elements ...
}

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

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

#Valid JSON request with BindingResult causes IllegalStateException

I have a REST service which takes a JSON request. I want to validate the JSON request values that are coming in. How can I do that?
In Spring 3.1.0 RELEASE, I know one wants to make sure they are using the latest support classes listed at 3.1.13 New HandlerMethod-based Support Classes For Annotated Controller Processing
The old ones are items like: AnnotationMethodHandlerAdapter. I want to make sure I am using the latest such as RequestMappingHandlerAdapter.
This is because I hope it fixes an issue where I see this:
java.lang.IllegalStateException: Errors/BindingResult argument declared without preceding model attribute. Check your handler method signature!
My #Controller handler method and associated code is this:
#Autowired FooValidator fooValidator;
#RequestMapping(value="/somepath/foo", method=RequestMethod.POST)
public #ResponseBody Map<String, String> fooBar(
#Valid #RequestBody Map<String, String> specificRequest,
BindingResult results) {
out("fooBar called");
// get vin from JSON (reportRequest)
return null;
}
#InitBinder("specificRequest") // possible to leave off for global behavior
protected void initBinder(WebDataBinder binder){
binder.setValidator(fooValidator);
}
FooValidator looks like this:
#Component
public class FooValidator implements Validator {
public boolean supports(Class<?> clazz) {
out("supports called ");
return Map.class.equals(clazz);
}
public void validate(Object target, Errors errors) {
out("validate called ");
}
private void out(String msg) {
System.out.println("****** " + getClass().getName() + ": " + msg);
}
}
If I remove the BindingResult, everything works fine except I won't be able to tell if the JSON validated.
I am not strongly attached to the concept of using a Map<String, String> for the JSON request or using a separate validator as opposed to a Custom Bean with validation annotation (How do you do that for a JSON request?). Whatever can validate the JSON request.
3.1.17 #Valid On #RequestBody Controller Method Arguments says that:
An #RequestBody method argument can be annotated with #Valid to invoke automatic validation similar to the support for #ModelAttribute method arguments. A resulting MethodArgumentNotValidException is handled in the DefaultHandlerExceptionResolver and results in a 400 response code.
In other words, if you use #Valid #RequestBody then Spring will reject an invalid request before it gets as far as calling your method. if you method is invoked, then you can assume the request body is valid.
BindingResult is used for validation of form/command objects, rather than #RequestBody.
I had to do something similar once. I just ended up making my life simpler by creating a Java object that the JSON could be convert into and used GSON to do the conversion.
It was honestly as simple as:
#Autowired
private Gson gson;
#RequestMapping(value = "/path/info", method = RequestMethod.POST)
public String myMethod(#RequestParam(value = "data") String data,
Model model,
#Valid MyCustomObject myObj,
BindingResult result) {
//myObj does not contain any validation information.
//we are just using it as as bean to take advantage of the spring mvc framework.
//data contains the json string.
myObj = gson.fromJson(data, MyCustomObject.class);
//validate the object any way you want.
//Simplest approach would be to create your own custom validator
//to do this in Spring or even simpler would be just to do it manually here.
new MyCustomObjValidator().validate(myObj, result);
if (result.hasErrors()) {
return myErrorView;
}
return mySuccessView;
}
Do all your validation in your custom Validator class:
public class MyCustomObjValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return MyCustomObj.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
MyCustomObj c = (MyCustomObj) target;
Date startDate = c.getStartDate();
Date endDate = c.getEndDate();
if (startDate == null) {
errors.rejectValue("startDate", "validation.required");
}
if (endDate == null) {
errors.rejectValue("endDate", "validation.required");
}
if(startDate != null && endDate != null && endDate.before(startDate)){
errors.rejectValue("endDate", "validation.notbefore.startdate");
}
}
}
MyCustomObject does not contain any annotation for validation, this is because otherwise Spring will try to validate this fields in this object which are currently empty because all the data is in the JSON String, it could for example be:
public class MyCustomObject implements Serializable {
private Date startDate;
private Date endDate;
public Date getStartDate() {
return startDate;
}
public Date getEndDate() {
return endDate;
}
public void setStartDate(Date theDate) {
this.startDate = theDate;
}
public void setEndDate(Date theDate) {
this.endDate = theDate;
}
}
Try using the following:
#Autowired
private FooValidator fooValidator;
#InitBinder("specificRequest") // possible to leave off for global behavior
protected void initBinder(WebDataBinder binder){
binder.setValidator(fooValidator);
}
#ModelAttribute("specificRequest")
public Map<String, String> getModel() {
return new HashMap<String, String>();
}
This will make your controller serialize the request into the type you specify it to be.
I have to say i normally dont make a service (autowired) of the validator, but it might be better.
Your handler looks like this now:
#RequestMapping(value="/somepath/foo", method=RequestMethod.POST)
public #ResponseBody Map<String, String> fooBar(
#Valid #ModelAttribute("specificRequest")
Map<String, String> specificRequest, BindingResult results) {
out("fooBar called");
// get vin from JSON (reportRequest)
return null;
}
To my knowledge this works perfectly and addresses the error you are receiving.

Categories