Announcement Announcement Module
Collapse
No announcement yet.
JSR-303 validation with RESTful MVC and OXM Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • JSR-303 validation with RESTful MVC and OXM

    I am using Spring 3 MVC to implement RESTful services. The MessageConverter and OXM mechanism (specifically MarshallingHttpMessageConverter) is used to convert the incoming XML to a request bean and the outgoing response bean to XML.

    I am also trying to use the JSR-303 validation support in Spring 3, using annotations.

    It appears that the JSR-303 annotation-driven validation is invoked by the DataBinder mechanism in Spring MVC. I assumed the validations would also be invoked when using the MessageConverter, but the validation is not being done even though I think the configuration is correct.

    Can anyone verify whether the validation should get invoked when using MessageConverters?

    Here is the input bean that I want to be validated:

    Code:
    @XmlRootElement(name = "request")
    public class HelloRequest {
      @NotNull
      private String name;
    
      // getter and setter
    }
    And the Controller:

    Code:
    @Controller
    @RequestMapping("/HelloMvc")
    public class HelloMvcController {
      @RequestMapping(value = "/greet", method = RequestMethod.POST)
      @ResponseBody
      public HelloResponse greet(@RequestBody @Valid HelloRequest request) {
         return new HelloResponse("Hello " + request.getName());
      }
    }

    And the relevant Spring XML configuration:

    Code:
      <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
      <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        <property name="messageConverters">
          <list>
            <bean id="marshallingHttpMessageConverter"
                  class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"
                  p:marshaller-ref="marshaller" p:unmarshaller-ref="marshaller"/>
          </list>
        </property>
      </bean>
    
      <oxm:jaxb2-marshaller id="marshaller">
        <oxm:class-to-be-bound name="com.xxx.HelloRequest"/>
        <oxm:class-to-be-bound name="com.xxx.HelloResponse"/>
      </oxm:jaxb2-marshaller>
    
      <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
      <mvc:annotation-driven/>
    
      <bean name="helloMvc" class="com.xxx.HelloMvcController"/>

  • #2
    I am not sure, but I think you have two AnnotationMethodHandlerAdapters since mvc:annotation-driven creates one for you, and you have declared one yourself. Look here for more info on what mvc:annotation-driven do.

    This means that your MarshallingHttpMessageConverter is wired into one HandlerAdapater, and the validator is wired into the other (the one that mvc:annotation-driven creates).
    Last edited by joelso; Dec 23rd, 2009, 02:12 PM.

    Comment


    • #3
      I will look into what happens when you have both an explicitly declared AnnotationMethodHandlerAdapter and mvc:annotation-driven in the same Spring config. However, this is not why the validators don't get invoked. Here is what I have found:

      There are several different ways to create controller methods for RESTful MVC. You can use the @RequestBody annotation, or the @RequestParam annotation, or you can simply provide (un-annotated) command objects as parameters to a controller method. What is not clear from reading the Spring MVC documentation is that there are two completely separate mechanisms at work under the covers, with different functionality supported.

      The @RequestParam annotation or un-annotated command objects can be used to bind parameters on an HTTP GET request to controller method parameters. In either case, the Spring DataBinder mechanism is used, which includes support for JSR-303 validation, customizable type conversion, controller-specific data binding customization (e.g. @InitBinder), etc. Most of the features described in the Spring MVC documentation apply to this method of request binding.

      The @RequestBody annotation is used to handle POST requests, where the entire body of the HTTP request is bound to the specified controller method parameter. Spring MessageConverter and OXM is used to unmarshall the request body. The DataBinder mechanism is not used, so many of the features described in the Spring MVC documentation are not supported with POST/ @RequestBody style controllers (like JSR-303 validation, one of the major features I was trying to leverage).

      I was very surprised to see that these two concepts (DataBinder and MessageConverter) were implemented so differently inside the framework, with very different features available. Most of the relevant code is in org.springframework.web.bind.annotation.support.Ha ndlerMethodInvoker.resolveHandlerArguments() if you care to see for yourself what's going on.

      I am trying some extensions to bring the functionality closer together, but I haven't come up with the ideal solution yet. Is anyone else working on a solution to this?

      Comment


      • #4
        Have you worked out a solution for this yet? I would like my validator to be aware of an instance of a class that was loaded in an interceptor and added to the request. Wondering how to get the validator to see the request or whether I should just validate elsewhere.

        Comment


        • #5
          @motobass

          What do the signatures of your controller methods look like? Are you trying to have the request objects bound using @RequestBody annotations, or @RequestParam annotations, or "naked" controller arguments?

          A few examples of your controller methods would be helpful (the signature is probably more interesting than the code in the body of the methods).

          Scott

          Comment


          • #6
            Code:
            public String setUserPicks(HttpServletRequest req,
            			@PathVariable String userId,
            			@Valid @RequestParam PickArrayList picks,
            			BindingResult errors,
            			Model model) {
            The picks the users are posting are related to possible choices that were loaded into the request.

            Comment


            • #7
              @motobass

              I assume this method and/or its containing Controller class is also annotated with @RequestMapping to cause the whole annotation style of handler mapping to be invoked?

              The combination of @Valid and @RequestParam on the "PickArrayList picks" parameter should work. (My original problem is that the combination of @Valid and @RequestBody does not work - the validator does not get invoked). If you are adding an object to the HttpServletRequest in a preHandle() method of an Interceptor, then the binding and validation mechanism should see the interceptor-added object and handle it just like any other object in the HttpServletRequest.

              I assume you have some JSR-303 annotations (i.e. @NotNull, @Max, @Min, @Pattern, etc.) on the PickArrayList object for the validation mechanism to use?

              Also make sure you have the validator configured in the Spring config file, like this:

              Code:
                
                <mvc:annotation-driven/>
                <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
              If this doesn't trigger any solutions, the next thing to do is to post the entire domain class (PickListArray, Controller, and Spring config XML file) too see if anyone can spot the problem.

              Scott

              Comment


              • #8
                Thanks Scott,

                You are correct about the @RequestMapping on the signature. It is getting to the JSR 303 validation isValid() method, but I am not sure how, if at all, I can get access to the thing in the request to compare the bean I am validating to it. Make sense? There are two things, the picks, which is annotated with my custom annotation, and the instance in the request. I can't know whether their picks are valid unless I can compare them to what is possible.

                Comment


                • #9
                  I think I have a better idea now of what you are trying to do. If I understand correctly, you have to pieces of data - the user selections ("picks") passed in the HTTP request, and a set of valid picks defined by the system. You want to validate the user picks in the request to the system-defined valid picks.

                  I think you might be better off using the Spring Validator interface instead of the JSR-303 style annotations. To do this, you would write an implementation of the Validator interface. This is described in the Spring docs here: http://static.springsource.org/sprin...alidation.html.

                  Here is some code to illustrate the idea. I haven't compiled and tested this, so treat it as pseudo-code :-).

                  Code:
                  public class PicksValidator implements Validator {
                    private List<Pick> validPicks;
                  
                    public boolean supports(Class clazz) {
                      return Picks.class.equals(clazz);
                    }
                      
                    public void validate(Object obj, Errors e) {
                      Picks picks = (Picks) obj;
                      for (Pick pick : picks.getAll()) {
                        if (!validPicks.contains(pick)) {
                          e.reject("invalid pick");
                        }
                      }
                    }
                  
                    public void setValidPicks(List<Pick> validPicks) {
                      this.validPicks = validPicks();
                    }
                  }
                  Instead of adding the list of valid picks to the HTTP request in an interceptor, inject the valid picks into the validator. I made it a List here for simplicity, but obviously it can be any type of object that can be injected as a bean and queried by the Validator. Also, look at the Errors object to see all the various forms of reject methods. The arguments to these methods get resolved to error messages using a MessageSource.

                  Once this class is built, you can wire it into the Controller and the Binding mechanism. If you only want to have the PicksValidator active for one Controller, you can do something like this:

                  Code:
                  @Controller
                  public class PicksController {
                    @Autowired
                    private PicksValidator picksValidator; 
                  
                    @InitBinder
                    protected void initBinder(WebDataBinder binder) {
                      binder.setValidator(picksValidator);
                    }
                      
                    @RequestMapping(...)
                    public String setUserPicks(HttpServletRequest req,
                  			@PathVariable String userId,
                  			@Valid @RequestParam Picks picks,
                  			BindingResult errors,
                  			Model model) {
                    }
                  }
                  Here, the Picks object passed to the request method are the user selections in the HTTP request, not the system-defined valid picks.

                  In your Spring config XML, you would add a bean like this:

                  Code:
                    <bean id="picksValidator" class="PicksValidator">
                      <property name="validPicks" value="..."/>
                    </bean>
                  If you want the PicksValidator to be visible across Controllers, the documentation says you can do something like this instead of adding the @InitBinder method to the controller (I've not tried it do I'm not sure exactly how it works):

                  Code:
                    <mvc:annotation-driven validator="picksValidator"/>
                  
                    <bean id="picksValidator" class="PicksValidator">
                      <property name="validPicks" value="..."/>
                    </bean>
                  This has gone pretty far off-topic from the original post, but I hope this helps. Let me know if it does (or doesn't for that matter).

                  Comment


                  • #10
                    Yes, I am afraid the thread has wandered off a bit. I was hoping initially that the item I needed to validate - the picks - would come in to the controller via the @RequestBody and be instantiated through oxm and then I could validate with Hibernate's JSR 303 implementation. I saw here the disconnect and was curious if anyone had brought this together. And, in addition, knew how I might get that other instance in there as well to do the comparison.

                    As it stands, I will implement it the way you have laid out. Time's-a-wastin'. And that is actually a fine way of doing it and the previous and, apparently, current standard way to do a validation of the type I need to perform.

                    Thanks for your assistance.

                    Comment


                    • #11
                      I also faced the problems like this.
                      I figure out a solution myself. I use Spring-AOP to validate beans.

                      First, you should add apectj.jar to your project.

                      xxx-servlet.xml
                      Code:
                          <!-- start AOP annotation -->
                          <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" >
                             <property name="proxyTargetClass" value="true"/>
                          </bean>
                      application-context.xml
                      Code:
                        <!--
                          Instruct Spring to retrieve and apply @AspectJ aspects which are defined
                          as beans in this context (such as the CallMonitoringAspect below).
                        -->
                        <aop:aspectj-autoproxy proxy-target-class="true"/>
                      Controller.java
                      Code:
                        // Set<ConstraintViolation<Object>> is hibernate validate error
                        @RequestMapping(...)
                        public String setUserPicks(HttpServletRequest req,
                      			@PathVariable String userId,
                      			@Valid @RequestBody Picks picks,
                      			Set<ConstraintViolation<Object>> errors,
                      			Model model) {
                      }
                      ValidationAspect.java
                      Code:
                      // aop and bean annotation
                      @Component
                      @Aspect
                      public class ValidationAspect {
                          // set proxy to all the class under *.controller
                          @Pointcut("execution(* *.controller.*.*(..))")
                          private void controllerMethod(){
                          }
                      
                          @Around("controllerMethod()")
                          public Object aroundController(ProceedingJoinPoint joinPoint) throws Throwable {
                      
                              MethodSignature joinPointObject = (MethodSignature) joinPoint.getSignature();
                              Method method = joinPointObject.getMethod();
                              String name = method.getName();
                              Class<?>[] parameterTypes = method.getParameterTypes();
                              Object target = joinPoint.getTarget();
                      
                              // get the target method
                              method = target.getClass().getMethod(name, parameterTypes);
                      	// get all parameter
                              Object[] args = joinPoint.getArgs();
                      
                              for (int i = 0; i < args.length; i++) {
                                  MethodParameter methodParam = new MethodParameter(method, i);
                      
                                  Class<?>[] group = null;
                                  Class<?> bean = null;
                                  String property = null;
                      
                                  Annotation[] paramAnns = methodParam.getParameterAnnotations();
                      
                                  System.out.println("Method interceptor----------------:" +args[i]);
                                  for (Annotation paramAnn : paramAnns) {
                                      // if para got @Valid
                                      if (Valid.class.isInstance(paramAnn)) {
                                          // get validator
                                          ValidatorFactory vf = javax.validation.Validation.buildDefaultValidatorFactory();
                                          Validator validator = vf.getValidator();
                                          // start validation
                                          Set<ConstraintViolation<Object>> set = validator.validate(args[i]);
                                          // set the validate result
                                          if(i+1<args.length && args[i+1] instanceof Set){
                                              args[i+1] = set;
                                              i++;
                                          }
                                      }
                                  }
                              }
                              // execute method
                              Object returnValue = joinPoint.proceed(args);
                              // return result
                              return returnValue;
                          }
                      }
                      Last edited by taku; Feb 9th, 2010, 06:28 PM.

                      Comment


                      • #12
                        Originally posted by scottyfred View Post
                        The @RequestBody annotation is used to handle POST requests, where the entire body of the HTTP request is bound to the specified controller method parameter. Spring MessageConverter and OXM is used to unmarshall the request body. The DataBinder mechanism is not used, so many of the features described in the Spring MVC documentation are not supported with POST/ @RequestBody style controllers (like JSR-303 validation, one of the major features I was trying to leverage).

                        I was very surprised to see that these two concepts (DataBinder and MessageConverter) were implemented so differently inside the framework, with very different features available. Most of the relevant code is in org.springframework.web.bind.annotation.support.Ha ndlerMethodInvoker.resolveHandlerArguments() if you care to see for yourself what's going on.

                        I am trying some extensions to bring the functionality closer together, but I haven't come up with the ideal solution yet. Is anyone else working on a solution to this?
                        Hi,

                        I'm part of the people having this problem so I'd like to know if you have come up with an elegant solution.

                        Actually, I think that having a @Valid annotation is not going to solve all my problems because I want to trigger the validation in tests and it is hard to test spring mvc annotations like these.

                        So I think I am going to fallback to an AOP like suggested in the last post of this thread.

                        Comment


                        • #13
                          After trying a few work-arounds, we ended up using an AOP-based solution similar to what @taku and a few others have done.

                          Details of my implementation are here: http://scottfrederick.blogspot.com/2...on-aspect.html

                          Comment


                          • #14
                            Solution Found

                            I'm replying for prosperity's sake. I ran into this exact problem and finally sorted it out. It is exactly as "joelso" had suspected. Using "<mvc:annotation-driven>" only sets up the JSR 303 validator for the AnnotationMethodHandlerAdapter instance being created by that configuration. To use custom MessageConverters, you had to create a second instance of AnnotationMethodHandlerAdapter, so you need to manually add a WebBindingInitializer to that.

                            Code:
                              <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
                                <property name="messageConverters">
                                  <list>
                                    <ref bean="myMarshallingMessageConverter"/>
                                  </list>
                                </property>
                                <property name="webBindingInitializer">
                                  <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
                                    <property name="validator">
                                      <bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
                                    </property>
                                  </bean>
                                </property>
                              </bean>

                            Comment

                            Working...
                            X