Search code examples
springspring-mvcbean-validationspring-3hibernate-validator

Validation with Spring 3.2.0


I'm using HibernateValidator 4.3.1. Validations are performed as intended throughout the entire application.

I have registered some custom editors to perform validation globally such as for ensuring numeric values (double, int etc) in a text-field, for ensuring valid dates regarding the Joda-Time API etc.

In this type of validation, I'm allowing null/empty values by setting the allowEmpty parameter to false as usual to validate it separately especially for displaying separate user friendly error messages when such fields are left blank.

Therefore, in addition to validating with HibernateValidator and custom editors, I'm trying to use the following validation strategy. Again, this kind of validation is only for those fields which are registered for custom editors are when left blank.

The following is the class that implements the org.springframework.validation.Validator interface.

package test;

import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import validatorbeans.TempBean;

@Component
public final class TempValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        System.out.println("supports() invoked.");
        return TempBean.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        TempBean tempBean = (TempBean) target;

        System.out.println("startDate = " + tempBean.getStartDate() + " validate() invoked.");
        System.out.println("doubleValue = " + tempBean.getDoubleValue() + " validate() invoked.");
        System.out.println("stringValue = " + tempBean.getStringValue() + " validate() invoked.");

        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "startDate", "java.util.date.nullOrEmpty.error");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "doubleValue", "java.lang.double.nullOrEmpty.error");
    }
}

The class is designated with the @Component annotation so that it can be auto-wired to a specific Spring controller class. The debugging statements display exactly based on the input provided by a user.

The following is the controller class.

package controller;

import customizeValidation.CustomizeValidation;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import javax.validation.groups.Default;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.DataBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import test.TempValidator;
import validatorbeans.TempBean;

@Controller
public final class TempController {

    @Autowired
    private TempService tempService;

    private TempValidator tempValidator;

    public TempValidator getTempValidator() {
        return tempValidator;
    }

    @Autowired
    public void setTempValidator(TempValidator tempValidator) {
        this.tempValidator = tempValidator;
    }

    @RequestMapping(method = {RequestMethod.GET}, value = {"admin_side/Temp"})
    public String showForm(@ModelAttribute("tempBean") @Valid TempBean tempBean, BindingResult error, Map model, HttpServletRequest request, HttpServletResponse response) {
        return "admin_side/Temp";
    }

    @RequestMapping(method = {RequestMethod.POST}, value = {"admin_side/Temp"})
    public String onSubmit(@ModelAttribute("tempBean") @Valid TempBean tempBean, BindingResult errors, Map model, HttpServletRequest request, HttpServletResponse response) {
        //tempValidator.supports(TempBean.class);
        //tempValidator.validate(tempBean, errors);

        DataBinder dataBinder = new DataBinder(tempBean);
        dataBinder.setValidator(tempValidator);
        dataBinder.validate();

        //errors=dataBinder.getBindingResult();
        if (CustomizeValidation.isValid(errors, tempBean, TempBean.ValidationGroup.class, Default.class) && !errors.hasErrors()) {
            System.out.println("Validated");
        }

        return "admin_side/Temp";
    }
}

I'm invoking the validator from the Spring controller class itself (which I indeed want) by

DataBinder dataBinder = new DataBinder(tempBean);
dataBinder.setValidator(tempValidator);
dataBinder.validate();

The validator is called but the validation which is expected is not performed.

If only I invoke the validator manually using the following statement (which is commented out above),

tempValidator.validate(tempBean, errors);

then validation is performed. So I don't believe my validator is correctly working. Why does it fail to work with DataBinder?

In my application-context.xml file, this bean is simply configured as follows.

<bean id="tempValidator" class="test.TempValidator"/>

This many packages as below including the test package which the TempValidator class is enclosed within are auto-detected.

<context:component-scan base-package="controller spring.databinder validatorbeans validatorcommands test" use-default-filters="false">
    <context:include-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
    <context:include-filter expression="org.springframework.web.bind.annotation.ControllerAdvice" type="annotation"/>  
</context:component-scan>

I have even tried to put

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

In my dispatcher-servlet.xml file.

What am I overlooking here?


Solution

  • If I understand well what you try to achieve - distinguish between blank fields and incorrect values entered - you can use MUCH MORE SIMPLER approach:

    public class MyBean {
    
        @NotNull
        @DateTimeFormat(pattern="dd.MM.yyyy HH:mm") 
        private DateTime date;
    
        @NotNull
        @Max(value=5)
        private Integer max;
    
        @NotNull
        @Size(max=20)
        private String name;
    
        // getters, setters ... 
    } 
    

    Controller mapping:

    public void submitForm(@ModelAttribute @Valid MyBean myBean, BindingResult result) {
    
        if (result.hasErrors){
           // do something}
        else{
           // do something else
        }        
    }
    

    Validation messages:

    NotNull=Required field.
    NotNull.date=Date is required field.
    NotNull.max=Max is required field.
    Size=Must be between {2} and {1} letters.
    Max=Must be lower than {1}.
    typeMismatch.java.lang.Integer=Must be number.
    typeMismatch.org.joda.time.DateTime=Required format dd.mm.yyyy HH:mm
    

    Spring configuration:

    @Configuration
    public class BaseValidatorConfig {
    
        @Bean
        public LocalValidatorFactoryBean getValidator() {
    
            LocalValidatorFactoryBean lvfb = new LocalValidatorFactoryBean();
            lvfb.setValidationMessageSource(getValidationMessageSource());
            return lvfb;
        }
    
        protected MessageSource getValidationMessageSource() {// return you validation messages ...}
    }
    

    I can provide more details and explanation, if needed.