Announcement Announcement Module
Collapse
No announcement yet.
How can I extend the JSR-303 validator with custom validation logic Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • How can I extend the JSR-303 validator with custom validation logic

    With build-in JSR-303 validator, my entity properties can be validated against those standard constraints @NonNull, @Max, @Min, etc.

    However, sometimes, it's required to validate additional conditions.

    Here is my case:
    I have defined a LoginUser entity to represent the login user info:
    Code:
    @Entity
    @Table(name="usr")
    public class LoginUser {
        @NotNull
        private String orgCode;
        @NotNull
        private String loginId;
        @NotNull
        private String password;
    ....
    }
    When login, normally, it's required to validate the not null of the fields required and the validity of the password compared to DB.
    My LoginController looks like:
    Code:
    @Controller
    public class LoginController {
    	@Autowired
    	private Validator loginValidator;
    
    	/**
    	 * DataBinder is only used for @ModelAttribute
    	 * @param binder WebDataBinder
    	 */
    	@InitBinder
    	protected void initBinder(WebDataBinder binder) {
    		binder.setValidator(loginValidator);
    	}
    	@RequestMapping("/login")
    	public ModelAndView login(@Valid @ModelAttribute("loginUser") LoginUser loginUser, BindingResult result) {
    		ModelAndView view = new ModelAndView();
    		if (result.hasErrors()) {
    			view.setViewName("home");
    			view.addObject(loginUser);
    			view.addObject(result.getAllErrors());
    		} else {
    			view.setViewName("dashboard");
    		}
    		return view;
    	}
    }
    My LoginValidator is:
    Code:
    @Component
    public class LoginValidator extends LocalValidatorFactoryBean implements Validator {
           @Autowired
    	private PasswordManager passwordManager;
    
    	public boolean supports(Class<?> clazz) {
    		return LoginUser.class.equals(clazz);
    	}
    
    	public void validate(Object obj, Errors errors) {
    	    super.validate(obj, errors);
    		
    		LoginUser user = (LoginUser) obj;
    		if (errors.getErrorCount() == 0 && !passwordManager.isPasswordValid(
    				user.getOrgCode(), user.getLoginId(), user.getPassword())) {
    			errors.rejectValue("password", "invalidLogin");
    		}
    	}
    
    	public void setPasswordManager(PasswordManager passwordManager) {
    		this.passwordManager = passwordManager;
    	}
    }
    I am trying to extend my LoginValidator to LocalValidatorFactoryBean, however, calling super.validate() doesn't work and so it does not give any error if those fields are empty.

    Am I doing something wrong?

  • #2
    I have tried this, and it works find. I can't see why it won't work for you.

    When you say "calling super.validate() doesn't work", what do you mean? Does super.validate() simply return without adding anything to the passed Errors object, or does it do something else?

    Comment


    • #3
      You do try that and works fine?

      Just as what I said in first post, it just simply return without adding anything to the passed Errors object.

      I add debug message:
      Code:
      super.validate(obj, errors);
      System.out.println("Error count: " + errors.getErrorCount());
      It just shows 0 for error count. Hence, I really don't know where I have messed it up. model LoginUser or Validator?
      Below is the dependency set in pom.xml
      Code:
              <!--  JSR 303 with Hibernate Validator -->
              <dependency>
                  <groupId>javax.validation</groupId>
                  <artifactId>com.springsource.javax.validation</artifactId>
                  <version>1.0.0.GA</version>
              </dependency>
              <dependency>
                  <groupId>org.hibernate</groupId>
                  <artifactId>com.springsource.org.hibernate.validator</artifactId>
                  <version>4.0.2.GA</version>
              </dependency>

      Comment


      • #4
        Yes, I did try this and it worked fine for me. I had an existing Validator class, which I modified by extending LocalValidatorFactoryBean and adding the call to super.validate() in the existing validate() method. I then added a @NotNull annotation to a field of the domain object being validated. The annotations on the object were detected and I got a validation error back from super.validate().

        I do not have the dependency on javax.validation/com.springsource.javax.validation in my pom.xml. I just have the usual set of Spring dependencies (core, beans, web, webmvc, and oxm) and the same Hibernate validator dependency that you have. I don't know why the com.springsource.javax.validation would be necessary. It looks like LocalValidatorFactoryBean and related classes come from spring-context.jar in my case (I am using Spring 3.0.1 currently). You might try removing the com.springsource.javax.validation dependency to see what happens.

        Otherwise, I would guess that your problem might be in the Spring XML configuration, but I don't know what could be wrong there. Maybe if you post your XML config file someone can spot something.

        Comment


        • #5
          Hi scottyfred,

          The com.springsource.javax.validation is just the javax.validation, the artifactId in that way is just I am getting those jar from Spring maven repository Bundle and that is Spring's standard.

          What do you mean Spring XML configuration? xxx-servelet.xml and applicationContext.xml?

          It's as below:
          xxx-servlet.xml
          Code:
                  <context:component-scan base-package="com.xxx"/>
          
                  <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
          
          	<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
          		<property name="exceptionMappings">
          			<props>
          				<prop key="com.xxx.exception.PageNotFoundException">pageNotFound</prop>
          				<prop key="org.springframework.dao.DataAccessException">dataAccessFailure</prop>
          				<prop key="org.springframework.transaction.TransactionException">dataAccessFailure</prop>
          			</props>
          		</property>
          		<property name="defaultErrorView"><value>uncaughtException</value></property>
          	</bean>
          
          	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
          		<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
          		<property name="prefix" value="/WEB-INF/views/jsp/"/>
          		<property name="suffix" value=".jsp"/>
          	</bean>
          
          	<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"
          			p:basename="messages"/>
          
              <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
                  <property name="messageConverters">
                      <list>
                          <bean id="mappingJacksonHttpMessageConverter"
                                class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
                              <property name="supportedMediaTypes">
          			            <list>
          			              <bean class="org.springframework.http.MediaType">
          				              <constructor-arg index="0" value="application"/>
          				              <constructor-arg index="1" value="json"/>
          				              <constructor-arg index="2" value="UTF-8"/>
          			              </bean>
          			            </list>
          			        </property>
                          </bean>
                          <bean id="formHttpMessageConverter"
                              class="org.springframework.http.converter.FormHttpMessageConverter">
                         </bean>
                         <bean id="stringHttpMessageConverter"
                              class="org.springframework.http.converter.StringHttpMessageConverter">
                         </bean>
                         <bean id="byteArrayHttpMessageConverter"
                              class="org.springframework.http.converter.ByteArrayHttpMessageConverter">
                         </bean>
                      </list>
                  </property>
              </bean>
          ApplicationContext.xml
          Code:
              <!-- import the dataSource definition -->
              <import resource="applicationContext-dataSource.xml"/>
          	<context:property-placeholder location="classpath:jdbc.properties"/>
          
          	<!-- Hibernate SessionFactory -->
          	<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
          			p:dataSource-ref="dataSource">
          		<property name="hibernateProperties">
          			<props>
          				<prop key="hibernate.dialect">${hibernate.dialect}</prop>
          				<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
          				<prop key="hibernate.generate_statistics">${hibernate.generate_statistics}</prop>
                          <prop key="hibernate.use_outer_join">true</prop>
                          <prop key="hibernate.default_lazy">true</prop>
                          <prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>
                          <prop key="hibernate.cache.use_query_cache">true</prop>
                          <prop key="hibernate.cache.use_second_level_cache">true</prop>
          			</props>
          		</property>
                  <property name="packagesToScan">
          		    <list>
          		        <value>com.xxx.**.*</value>
          		    </list>
          		</property>
          	</bean>
          
          	<!-- Transaction manager for a single Hibernate SessionFactory (alternative to JTA) -->
          	<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"
          			p:sessionFactory-ref="sessionFactory"/>
          
                  <context:annotation-config/>
          
                  <tx:annotation-driven/>
          Last edited by chengusky; Mar 30th, 2010, 10:14 PM.

          Comment


          • #6
            I have a clue on it, it's said in Spring Reference:
            The Spring MVC configuration required to enable JSR-303 support is shown below:
            Code:
            <?xml version="1.0" encoding="UTF-8"?>
            <beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:mvc="http://www.springframework.org/schema/mvc"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
            <!-- JSR-303 support will be detected on classpath and enabled automatically -->
            <mvc:annotation-driven/>
            </beans>
            However, after i adding <mvc:annotation-driven/> to my ApplicationContext.xml, originally I can access my home page, while now it's complaining page not found.

            And I don't understand originally, @Valid annotation is already usable for me to trigger the validator, why would possibly still need the <mvc:annotation-driven/>?

            Anyone can give me a brief hint / guide on Spring MVC setting? My working LoginController is as below, I don't know why the RequestMapping on it is not working after adding <mvc:annotation-driven/>.
            Code:
            @Controller
            public class LoginController {
            	Logger logger = LoggerFactory.getLogger(LoginController.class);
            
            	@Autowired
            	private Validator loginValidator;
            
            	/**
            	 * @param binder WebDataBinder
            	 */
            	@InitBinder
            	protected void initBinder(WebDataBinder binder) {
            		binder.setValidator(loginValidator);
            	}
            
                /**
                 * Custom handler for the home view.
                 * <p>
                 * Note that this handler relies on the RequestToViewNameTranslator to
                 * determine the logical view name based on the request URL: "/home.do"
                 * -&gt; "home".
                 */
                @RequestMapping("/")
                public ModelAndView home() {
                    ModelAndView home = new ModelAndView();
                    home.setViewName("home");
                    home.addObject(new LoginUser());
                    return home;
                }
            
            	@RequestMapping("/login")
            	public ModelAndView login(@Valid @ModelAttribute("loginUser") LoginUser loginUser, BindingResult result) {
            		ModelAndView view = new ModelAndView();
            		if (result.hasErrors()) {
            			view.setViewName("home");
            			view.addObject(loginUser);
            			view.addObject(result.getAllErrors());
            		} else {
            			view.setViewName("dashboard");
            		}
            		return view;
            	}
            }

            Comment


            • #7
              The <mvc:annotation-driven/> tag is a meta-tag, or a shortcut for using a bunch of default Spring configuration. This JIRA issue has a good description of the tag: https://jira.springsource.org/browse/SPR-6306.

              If you use <mvc:annotation-driven/> and also define beans of the same type that <mvc:annotation-driven/> expands into, strange things happen. There are several forum posts about this (like this one: http://forum.springsource.org/showthread.php?t=81238). The bottom line seems to be: if you need to customize any of what <mvc:annotation-driven/> expands into, just unroll the tag and don't use <mvc:annotation-driven/>.

              In the case of this validation issue, just declaring a LocalValidatorFactoryBean should enable annotation-based validation. This is the case in my Spring config XML (i.e. I don't use <mvc:annotation-driven/>).

              I can't see what is keeping your validation from working, but I will keep looking to see if I can spot anything.

              Scott

              Comment


              • #8
                Hi scottyfred,

                1000 thanks for your feedback. After I remove all my customized configuration and use <mvc:annotation-driven/> for simplicity and testing, the Validation finally works for me.

                Also if I remove <mvc:annotation-driven/> and use my customized configuration

                Comment

                Working...
                X