Friday, 17 June 2011

The Spring Validator Interface and Other Animals

An important part of any application is validating your user’s input and the bitter experience of spending hours debugging code that’s failed owing to bad data only serves to underline how crucial this is.

You should virtually always perform user validation at the presentation level and a top-tip is to always factor your validation code into a distinct set of objects and to avoid interweaving it in with your presentation code.

With this point in mind, the guys at Spring came up with a re-usable validation mechanism based around two interfaces and a static utility class. The interfaces are Validator, Errors and ValidationUtils.

In examining this mechanism, the first port of call is to take a look at the Validator interface, which has two methods:

  boolean supports(Class<?> clazz);

 
void validate(Object target, Errors errors);

This is a pre-generics interface and requires the supports() method to tell the client code what type of class it’s validating. This is problematical in that the ‘supports’ check is only done at run-time.

The second method, validate, does the validation and takes two arguments: the object that is to be validated, which surprisingly can be null, and an errors object that contains the contextual state about the validation process, which can never be null.

In order to demonstrate Validator implementation, I’m going to suggest a contrived scenario of validating the post code in an address object. The arbitrary rule I’m going to enforce is that the post code must exist and that it must be a Birmingham post code. The reason for this contrived rule will become clear.

The address validator code is:

public class AddressValidator implements Validator {

 
/**
   * Return true if this object can validate objects of the given class. This is
   *
<b>cargo-cult</b> code: all implementations are the same and can be cut 'n' pasted from
   * earlier examples.
   *
   *
@param clazz The type of class to validate
   */
 
public boolean supports(Class<?> clazz) {

   
return clazz.isAssignableFrom(Address.class);
 
}

 
/**
   * Validate an object, which must be a class type for which the supports() method returned
   * true.
   *
   *
@param obj The target object to validate
   *
@param errors contextual state about the validation process (never null)
   */
 
public void validate(Object obj, Errors errors) {

   
commonValidation(errors);
    businessValidation
(obj, errors);
 
}

 
/** Use ValidationUtils to perform common validation tasks */
 
private void commonValidation(Errors errors) {

   
ValidationUtils.rejectIfEmpty(errors, "post_code", // The field to validate
       
"postcode.required", // The field from the resource bundle that contains the
                    // error message.
       
"The postcode is a required field"); // A default error message
    // TODO Validate other fields using the ValidationUtils static class.
 
}

 
/** More specific validation rules not handled by ValidationUtils */
 
private void businessValidation(Object obj, Errors errors) {

   
Address address = (Address) obj;
    String postCode = address.getPost_code
();
   
if (isNotNull(postCode) && isNotBirminghamPostCode(postCode)) {
     
errors.rejectValue("post_code", "not.birmingham.postcode",
         
"Post code should be Birmingham and begin with a letter 'B'");
   
}
  }

 
private boolean isNotNull(String postCode) {

   
return postCode != null;
 
}

 
/** The first character of the Birmingham post code is 'B' */
 
private boolean isNotBirminghamPostCode(String postCode) {

   
char val = postCode.charAt(0);
   
return val != 'B';
 
}
}

The validate method is the only one of any interest. For clarity, this has been broken into two sub-methods: commonValidation and businessValidation. commonValidation is that validation which can be simply done using the ValidationUtils static helper class. It’s easy, repeatable, cut ‘n’ past coding by convention. The businessValidation method, on the other hand, contains domain specific validation that cannot be handled by the ValidationUtils static helper class. This is where you need to put your thinking cap on and get busy.

To summarize, implementing Validator is fairly straight forward, but there are a lot of hidden details here with your implementation hinging on things that are going on in the background. For example, why can the target object of the validate method be null? If you’re validating it, then logically it can never be null. How does the ValidationUtils class test your target object’s state when it apparently doesn’t have access to the target object?

And finally... you should really be migrating to Spring 3, which uses JSR 303 Bean Validation; a totally different beast altogether. This is something that I'll be looking at pretty soon...

No comments: