Spring Validation Error Generation

0 votes
asked Nov 18, 2010 by tom-tucker

I'm using JSR-303 for validation and the Spring MVC 3. Validation is done manually through validator.validate(bean, errors) in a controller.

I annotated the name property of the Foo class with @NotNull. When the validation fails, I noticed that the Spring MVC generated the following error codes in the Errors object.

NotNull.form.foo.name
NotNull.name
NotNull.java.lang.String
NotNull

The way the <form:errors> tag works is that it will go through all the error codes and look up the default resource bundle until it returns a non-null message. Is there any way to customize these error codes or at least the order they are listed?

The reason is that I have a custom ResourceBundle object that returns a default message derived from a given message code if none is found in a resource bundle text file. Because the tag works its way down on the error code list, it will look up NotNull.form.foo.name first in this example. The text file of course does not have this entry, so the custom ResourceBundle object will return a default message. The problem is, I already have a message defined in the text file for NotNull, but the tag will see this as it stands.

If I could somehow generate only one error code, or reverse the order of the error codes, it would work.

Any idea? Thanks.

2 Answers

0 votes
answered Nov 19, 2010 by dukethrash

I use the following class to do JSR-303 and HibernateValidator bean validation manually with a Spring validator. Perhaps it can be useful.

BeanValidator.java

import java.util.Locale;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;

public class BeanValidator implements org.springframework.validation.Validator, InitializingBean {

private static final Logger log = LoggerFactory.getLogger(BeanValidator.class.getName());

private Validator validator;

@Autowired
MessageSource messageSource;

@Override
public void afterPropertiesSet() throws Exception {
    ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
    validator = validatorFactory.usingContext().getValidator();
}

@Override
public boolean supports(Class clazz) {
    return true;
}

@Override
public void validate(Object target, Errors errors) {
    Set<ConstraintViolation<Object>> constraintViolations = validator.validate(target);
    for (ConstraintViolation<Object> constraintViolation : constraintViolations) {
        String propertyPath = constraintViolation.getPropertyPath().toString();
        String message;
        try {
            message = messageSource.getMessage(constraintViolation.getMessage(), new Object[]{}, Locale.getDefault());
        } catch (NoSuchMessageException e) {
            log.error(String.format("Could not interpolate message \"%s\" for validator. "
                    + e.getMessage(), constraintViolation.getMessage()), e);
            message = constraintViolation.getMessage();
        }
        errors.rejectValue(propertyPath, "", message);
    }
}
}

SpringMessageSourceMessageInterpolator.java

import javax.validation.MessageInterpolator;
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.NoSuchMessageException;

public class SpringMessageSourceMessageInterpolator extends ResourceBundleMessageInterpolator implements MessageInterpolator, MessageSourceAware, InitializingBean {

@Autowired
private MessageSource messageSource;

@Override
public String interpolate(String messageTemplate, Context context) {
    try {
        return messageSource.getMessage(messageTemplate, new Object[]{}, Locale.getDefault());
    } catch (NoSuchMessageException e) {
        return super.interpolate(messageTemplate, context);
    }
}

@Override
public String interpolate(String messageTemplate, Context context, Locale locale) {
    try {
        return messageSource.getMessage(messageTemplate, new Object[]{}, locale);
    } catch (NoSuchMessageException e) {
        return super.interpolate(messageTemplate, context, locale);
    }
}

@Override
public void setMessageSource(MessageSource messageSource) {
    this.messageSource = messageSource;
}

@Override
public void afterPropertiesSet() throws Exception {
    if (messageSource == null) {
        throw new IllegalStateException("MessageSource was not injected, could not initialize "
                + this.getClass().getSimpleName());
    }
}
}

applicationContext.xml

<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource" p:basename="messages"/>

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
    <property name="messageInterpolator">
        <bean class="com.company.utils.spring.SpringMessageSourceMessageInterpolator" />
    </property>
</bean>

Example bean property

@NotNull(message = "validation.mandatoryField")
private ClientGroup clientGroup;

Example validation

@Controller
public class MyController {

    @Autowired
    private BeanValidator validator;

    @RequestMapping("/foo", method=RequestMethod.POST)
    public void processFoo(ModelAttribute("foo") Foo foo, BindingResult result, Model model) {
        //...
        validator.validate(foo, result);
    }
}
0 votes
answered Nov 19, 2010 by dira

Could not find simpler solution than this one...

my-servlet.xml

<bean id="handlerAdapter" class="org.opensource.web.StandardAnnotationMethodHandlerAdapter">
</bean>

StandardAnnotationMethodHandlerAdapter.java

public class StandardAnnotationMethodHandlerAdapter extends AnnotationMethodHandlerAdapter    {
    @Override
    protected ServletRequestDataBinder createBinder(HttpServletRequest request, Object target, String objectName) throws Exception {
    MyServletRequestDataBinder dataBinder = new MyServletRequestDataBinder(target, objectName);
    return dataBinder;
   }
}

MyServletRequestDataBinder .java

public class MyServletRequestDataBinder extends ServletRequestDataBinder {

private MessageCodesResolver messageCodesResolver = new MyMessageCodesResolver();

@Override
public void initBeanPropertyAccess() {
   super.initBeanPropertyAccess();
   BindingResult bindingResult = super.getBindingResult();
   if(bindingResult instanceof AbstractBindingResult) {
       ((AbstractBindingResult)bindingResult).setMessageCodesResolver(messageCodesResolver);
   }
}

@Override
public void initDirectFieldAccess() {
  super.initDirectFieldAccess();
  BindingResult bindingResult = super.getBindingResult();
  if(bindingResult instanceof AbstractBindingResult) {
     ((AbstractBindingResult)bindingResult).setMessageCodesResolver(messageCodesResolver);
    }
 }
}

MyMessageCodesResolver .java

public class MyMessageCodesResolver extends DefaultMessageCodesResolver {

public static final String NOT_NULL_ERROR_CODE = "NotNull";

@Override
public String[] resolveMessageCodes(String errorCode, String objectName, String field, Class fieldType) {
   if(NOT_NULL_ERROR_CODE.equalsIgnoreCase(errorCode)) {
       String notNullErrorCode = errorCode + CODE_SEPARATOR + objectName + CODE_SEPARATOR + field;
       //notNullErrorCode = postProcessMessageCode(notNullErrorCode);
       return new String[] {notNullErrorCode};
    }
      return super.resolveMessageCodes(errorCode, objectName, field, fieldType);
}   
}
Welcome to Q&A, where you can ask questions and receive answers from other members of the community.
Website Online Counter

...