Wednesday, January 25, 2012

JSR: Part 1- Getting started with JSR 303 (Bean Validation)

Validating objects against different criteria is a requirement in basically every software development project. Examples are countless:
  • A given object might never be allowed to be null
  • A number could be required to be always in the interval [1 ... 100]
  • A String might be expected to match some regular expression or to represent a valid e-mail address, credit card number, license plate etc.
Where Java is concerned, a couple of dedicated validation frameworks have been around for quite a long time. The probably best-known ones are Commons Validator from the Apache Commons project and Hibernate Validator. Other options are iScreen and OVal, and the ubiquitous Spring framework comes with its own validation package, too.

With JSR 303: "Bean Validation" efforts are under-way, to establish a standard validation API. The JSR tries to combine the best features of the different validation frameworks, while making you independent from any concrete implementation.


Try it out yourself

You can already get a first impression, what it will feel like to use the standardized validation API. At the time of writing I am aware of two projects that aim for an implementation of the JSR 303 spec:
  •  The reference implementation, which is based on Hibernate validation and developed at JBoss, mainly by the people behind the spec.
  •  agimatec validation, based on a validation framework by the German software company agimatec GmbH
In the following I will describe the first steps using the reference implementation. Help on using agimatec-validation can be found here.

Use Bean Validation in your own project

Now after downloading the libraries add the dependencies to the Bean Validation API and the reference implementation to your project's classpath and dependencies.

Also make sure – since JSR 303 is all about annotations – that you are using at least Java 5+ as source/target compliance level.

Open the project in the IDE of your choice and create the following class as an exemplary domain object:

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class Car {

    @NotNull
    private String manufacturer;

    @NotNull
    @Size(min = 2, max = 14)
    private String licensePlate;

    public Car(String manufacturer, String licencePlate) {

        super();
        this.manufacturer = manufacturer;
        this.licensePlate = licencePlate;
    }

    //getters and setters ...

}
@NotNull and @Size are so-called constraint-annotions, that we use to declare certain constraints, which shall be applied to the fields manufacturer (shall never be null) and licensePlate (shall never be null and must be between 2 and 14 characters long).

To perform a validation of these constraints, we use the Validator interface defined by the specification. Let's try it in a test for our Car class:

import static org.junit.Assert.*;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.junit.BeforeClass;
import org.junit.Test;
public class CarTest {
    private static Validator validator;
    @BeforeClass
    public static void setUp() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
    } // line 22
    @Test
    public void testManufacturerIsNull() {
        Car car = new Car(null, "DD-AB-123");
        Set<ConstraintViolation<Car>> constraintViolations =
            validator.validate(car); // line 31
        assertEquals(1, constraintViolations.size());
        assertEquals(
            "may not be null", constraintViolations.iterator().next().getMessage());
    }
    @Test
    public void testLicensePlateTooShort() {
        Car car = new Car("Audi", "D");
        Set<ConstraintViolation<Car>> constraintViolations = 
            validator.validate(car);
        assertEquals(1, constraintViolations.size());
        assertEquals(
            "size must be between 2 and 14", constraintViolations.iterator().next().getMessage());// line 46
    }
    @Test
    public void testCarIsValid() {
        Car car = new Car("Audi", "DD-AB-123");
        Set<ConstraintViolation<Car>> constraintViolations =
            validator.validate(car);
        assertEquals(0, constraintViolations.size());
    }// line 59
}

On line 22 we get a Validator object from the ValidatorFactory. Then, we use this validator to validate the car object. The validate() method returns a set of ConstraintViolation objects, which we can iterate through in order to see which validation errors occurred.

The first two validate() calls return a violation of the @NotNull constraint on manufacturer (line 31) and of the @Size constraint on licensePlate (line 46). If the object could be validated successfully (as in line 59), validate() returns an empty set.

Note that we only use classes from the package javax.validation, which stems from the Bean Validation standard API.

The concrete implementations, e.g. for ValidatorFactory and Validator, are hidden from us. Therefore it would be no problem to switch to another implementation of the API, should that need arise.

Part 2: in part two i will talk about using bean validation in jee6 managed components and integration with JPA callbacks methods.


References: Beans Validation by Gunnar Morling

No comments :

Post a Comment