This is the final (and way, way overdue) article in a series of blogs describing how you can effectively use Hibernate validators. The fifth article described how to bypass Hibernate validation in specific use cases, for example if you need to save a "draft" object that should not be validated yet. In this article I'll describe how the Hibernate Validator can be integrated into web applications so that validation errors propagate seamlessly from the data access code back up through the web tier and to the end user as nicely formatted error messages. For this article I'm using Spring MVC, but the basic concept should be applicable no matter which of the 100 billion Java web frameworks you are using.

The basic concept is this: When a user submits a form, you first want to bind the form values to your domain object (making sure of course that you only allow user-editable fields to be bound to the object). You then want to validate the domain object with the updated values. Finally, you want the Hibernate Validator validation errors translated into your web framework's native error validation mechanism so it can inform the user of the errors and do things like display the errors to the user next to the problematic fields. Stated more succinctly, the following steps must occur: submit form, data binding to domain object, validate domain object using Hibernate Validator, translate Hibernate Validator errors to web framework errors, re-display form with nicely formatted error messages to user for correction.

The only piece we don't have is the translation of Hibernate Validator errors into web framework errors. Since I'm using Spring MVC in this case, I'll need to take the Hibernate Validator errors and translate them into a Spring Errors object. For this I can use Spring MVC's Validator interface to implement the error translation in a generic fashion. For this blog the implementation is going to be simple and naive, and won't take into account things like nested properties or internationalization because I want to keep things relatively simple.

So, let's look at the HibernateAnnotationSpringValidator class which is responsible for validating any of our domain objects (which are all assumed to ultimately extend from a custom BaseEntity class for this example) and then translating the Hibernate Validator InvalidValue objects into Spring MVC Errors.

package com.nearinfinity.common.spring.validation.hibernate;

// imports...

public class HibernateAnnotationSpringValidator implements Validator {

  private Map validatorCache = new HashMap();

  public boolean supports(Class clazz) {
    return BaseEntity.class.isAssignableFrom(clazz);
  }

  @SuppressWarnings(CompilerWarnings.UNCHECKED)
  public void validate(Object value, Errors errors) {
    Class type = value.getClass();
    ClassValidator validator = validatorCache.get(type);
    if (validator == null) {
      validator = new ClassValidator(type);
      validatorCache.put(type, validator);
    }
    InvalidValue[] invalidValues = validator.getInvalidValues(value);
    translateToSpringValidationErrors(invalidValues, errors);
  }

  private void translateToSpringValidationErrors(InvalidValue[] invalidValues, Errors errors) {
    for (InvalidValue invalidValue : invalidValues) {
      String propertyName = invalidValue.getPropertyName();
      if (propertyName == null) {
        errors.reject(null, invalidValue.getMessage());
      }
      else {
        String titleCasedPropertyName = StringUtils.camelCaseToTitleCase(propertyName);
        String errorMessage = titleCasedPropertyName + " " + invalidValue.getMessage();
        errors.rejectValue(invalidValue.getPropertyPath(), null, errorMessage);
      }
    }
  }

}

The most important things in the above code are the validate and translateToSpringValidationErrors methods. As expected, validate expects a domain object that extends from BaseEntity and uses Hibernate Validator to validate it. This validator caches Hibernate ClassValidator instances and so one instance of HibernateAnnotationSpringValidator could be used in all of your Spring MVC controllers if desired. It then validates the object and gets back an array of InvalidValue objects from Hibernate Validator. Finally, it calls translateToSpringValidationErrors.

The translateToSpringValidationErrors method iterates through the InvalidValues and transforms them into Spring MVC errors. This implementation is very simplistic and not i18n-ready as it uses the domain object property names to create the error messages using a utility method I wrote called camelCaseToTitleCase. For example, if the property name is "firstName" then the "titleCasedPropertyName" is simply "First Name." So, if the InvalidValue's getMessage() method returns "is required" then the error message would be "First Name is required." Obviously for production use you'll want to make something more robust than this and support i18n if you need to.

Now you have all the pieces and simply need to plug-in the HibernateAnnotationSpringValidator and use as you would any Spring Validator which is documented extensively in the Spring docs and lots of books, blogs, twitters, etc. Obviously this example only works with Spring MVC. To make it work for the web framework you are using, you'll need to hook into your framework's validation mechanism and do something similar. Again, the two most important things are (1) using Hibernate Validator to validate an object (doesn't even need to be a domain object for that matter since Hibernate Validator can be used on its own independent of Hibernate even) and (2) translating the Hibernate validation errors into your web framework's native errors. That's it!



Post a Comment:
Comments are closed for this entry.