Tuesday, 26 July 2011

Writng a JSR 303 Custom Constraint

Yesterday’s blog demonstrated how to write a Spring custom validator for an address web-app. At the same time I mentioned how that this method should really be marked as deprecated in favour of a JSR 303 custom constraint, and writing a custom constraint isn’t that complicated, it’s just a matter of implementing two basic steps:
  1. Create a constraint annotation
  2. Create a constraint validator
If the Hibernate Validator is on your path then these classes are picked up automatically, so there’s no need to hook things together.

I’m going back to yesterday’s contrived scenario of a Birmingham postal code validator for our Address object. As the code below demonstrates, all we want to do is to apply an @BirminghamPostCode to the post code field of following Address bean, together with a couple of built-in constraints: @NotEmpty and @Size.

public class Address {

 
@NotEmpty
  @Size
(min = 2, max = 25)
 
private String street;

 
private String town;

 
private String country;

 
@BirminghamPostCode(message = "This should be a Birmingham post code")
 
private String postCode;

 
public String getStreet() {

   
return street;
 
}

 
public void setStreet(String street) {

   
this.street = street;
 
}

 
public String getTown() {

   
return town;
 
}

 
public void setTown(String town) {

   
this.town = town;
 
}

 
public String getCountry() {

   
return country;
 
}

 
public void setCountry(String country) {

   
this.country = country;
 
}

 
public String getPostCode() {

   
return postCode;
 
}

 
public void setPostCode(String post_code) {

   
this.postCode = post_code;
 
}

}

Creating the constraint annotation seems to be a matter of writing some boiler plate code and filling in the blanks. This is the Birmingham post code annotation:

@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = BirminghamPostCodeValidator.class)
public @interface BirminghamPostCode {

 
String message() default "{birmingham.postcode.constraint}";

  Class<?>
[] groups() default {};

  Class<?
extends Payload>[] payload() default {};
}

The annotation interface has three mandatory attributes: message, groups and payload, which I’m going to talk about later, all except the message string as it simply holds the default error message.

The second step is a matter of writing a class that implements the ConstraintValidator<A,T> interface, a generic class where the first argument A is the constraint annotation and the second, T is the type of object being validated; hence the method signature in this case will be:

public class BirminghamPostCodeValidator implements
   
ConstraintValidator<BirminghamPostCode, String>

The validator code has been updated yet again for this example. The Birmingham post code check now checks the format of the String using a regular expression (b|B)[0-9]{1,2} [0-9]{1}[a-zA-Z]{2} and yes, I know that the built-in validators come complete with a regular expression constraint, but I wanted to demonstrate the use of the

  @Override
 
public void initialize(BirminghamPostCode constraintAnnotation)

...method that can be used to initialise your constraint validator. For the complete validator take a look at the code below:

public class BirminghamPostCodeValidator implements
   
ConstraintValidator<BirminghamPostCode, String> {

 
private static Pattern postCodePattern;

 
/**
   * Implementing this method is optional and is usually blank in example code. Use it to
   * setup your constraint validator. In this case, I've created a Pattern object to test the
   * post code.
   *
   *
@see javax.validation.ConstraintValidator#initialize(java.lang.annotation.Annotation)
   */
 
@Override
 
public void initialize(BirminghamPostCode constraintAnnotation) {

   
if (postCodePattern == null) {
     
postCodePattern = Pattern.compile("(b|B)[0-9]{1,2} [0-9]{1}[a-zA-Z]{2}");
   
}
  }

 
/**
   * Use this method to test the constraint.
   *
   *
@see javax.validation.ConstraintValidator#isValid(java.lang.Object,
   *      javax.validation.ConstraintValidatorContext)
   */
 
@Override
 
public boolean isValid(String value, ConstraintValidatorContext context) {

   
boolean result = false;

   
if (isNotNull(value)) {
     
result = matchPostCode(value);
   
}

   
return result;
 
}

 
private static boolean isNotNull(Object obj) {

   
return obj != null;
 
}

 
private boolean matchPostCode(String value) {

   
Matcher matcher = postCodePattern.matcher(value);
   
return matcher.matches();
 
}
}

Having got three of the four bits of code required to demonstrate custom constraints, all that's left is to write a few quick unit tests.

public class PostCodeTest {

 
/**
   * The validator to be used for object validation. Will be retrieved once for all test
   * methods.
   */
 
private static Validator validator;

 
private Address address;

 
/**
   * Retrieves the validator instance.
   */
 
@BeforeClass
 
public static void setUpClass() {

   
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    validator = factory.getValidator
();
 
}

 
@Before
 
public void setUp() {

   
address = new Address();
    address.setStreet
("Gold Avenue");
 
}

 
/**
   * One constraint violation owing to postcode being null
   */
 
@Test
 
public void postCodeIsNull() {

   
Set<ConstraintViolation<Address>> constraintViolations = validator.validate(address);

    assertEquals
(1, constraintViolations.size());

    printViolations
(constraintViolations);
 
}

 
private void printViolations(Set<ConstraintViolation<Address>> constraintViolations) {

   
for (ConstraintViolation<Address> violation : constraintViolations) {

     
String invalidValue = (String) violation.getInvalidValue();
      String message = violation.getMessage
();
      System.out.println
("Found constraint violation. Value: " + invalidValue
          +
" Message: " + message);
   
}
  }

 
/**
   * One constraint violation owing to postcode being invalid
   */
 
@Test
 
public void postCodeIsInvalid() {

   
address.setPostCode("CA23 3ER");

    Set<ConstraintViolation<Address>> constraintViolations = validator.validate
(address);
    assertEquals
(1, constraintViolations.size());
    printViolations
(constraintViolations);
 
}

 
/**
   * Valid post code, so no violations.
   */
 
@Test
 
public void postCodeIsValid() {

   
address.setPostCode("B15 1NP");

    Set<ConstraintViolation<Address>> constraintViolations = validator.validate
(address);
    assertEquals
(0, constraintViolations.size());
 
}
}

As I mentioned above, the Hibernate validator does not require any configuration. The JUnit’s @BeginClass method merely calls Validation.buildDefaultValidatorFactory() to get hold of all the constraint classes on the path. After building and creating a validator, the final step is to call:

    Set<ConstraintViolation<Address>> constraintViolations = validator.validate(address);

If the instance under test passes validation, the constraintViolations is an empty set.

3 comments:

Unknown said...

Roger, nice post.

Ross

Aleksandar Stoisavljevic said...

Hi,
nice article.

I have my own custom annotation validation implementation.
But my implementation depends on service that it will be injected at moment of runtime.

If I follow your approach, problem occurs at the following line:

Set> constraintValidations = validator.validate(myObject);

IMHO, problem lies in fact that my validator expects to have that service.

I would like to test it without involving context files. I do not want to test it like integration test but just as unit test.

I have experience with Mockito, so some kind of that approach would suffice and should work? Do you have any idea how this can be achieved?


Thank You

Roger Hughes said...

I often use Mockito and combine it with Spring's ReflectionTestUtils.setField(...) method to inject mock objects in to Spring beans without the need to use the context files.