Sunday, February 22, 2009

Bookmark and Share

JSR 303 ("Bean Validation") specifies a fairly simple to use bootstrapping API for the creation of Validator instances:

1
2
3
4
...
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
...

To make things even easier, one can use a dependency injection container such as the Spring container to obtain Validator instances. That way you don't have to cope with the ValidatorFactory in your application logic but rather can have Validator instances simply injected. All you have to do for that is to create a Spring application context, that defines two Spring beans validatorFactory and validator as follows:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<beans 
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="validatorFactory" class="javax.validation.Validation"
        factory-method="buildDefaultValidatorFactory" />

    <bean id="validator" factory-bean="validatorFactory"
        factory-method="getValidator" />
</beans>

The validationFactory bean is created by instructing Spring to call the static factory method Validation.buildDefaultValidatorFactory(). The validator bean in turn is created by letting Spring call the getValidator() method on the validatorFactory bean.

As we don't specify any scope for the two beans explicitely, the default scope Singleton will be used. That way the Spring container will instantiate both beans exactly once and return these instances whenever requested. We are fine with that, as the Bean Validation specification states, that ValidatorFactory and Validator implementations have to be thread-safe.

The validator bean can now be injected into any other bean we create. Using Spring's test framework we can have the comfort of dependency injection also in JUnit tests. Let's try it out by using an injected Validator to validate instances of the exemplary Car class from my introductory post on the Bean Validation API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package de.gmorling.bvspring;

import static org.junit.Assert.*;

import java.util.Set;

import javax.annotation.Resource;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class CarTest {

    @Resource
    private Validator validator;

    @Test
    public void licensePlateTooShort() {

        Car car = new Car("Morris", "D", 4);

        Set<ConstraintViolation<Car>> constraintViolations = validator
                .validate(car);

        assertEquals(1, constraintViolations.size());
        assertEquals("size must be between 2 and 14", constraintViolations
                .iterator().next().getInterpolatedMessage());
    }

    @Test
    public void carIsValid() {

        Car car = new Car("DD-AB-123");

        Set<ConstraintViolation<Car>> constraintViolations = validator
                .validate(car);

        assertEquals(0, constraintViolations.size());
    }

}

With JUnit's @RunWith annotation we declare that the test runner SpringJUnit4ClassRunner shall be used to run the test, which enables Spring to inject dependencies into the test class.

The @ContextConfiguration stems from Spring's test framework and specifies the application context to be used. As we don't provide any name, Spring will use the default name, which is de/gmorling/bvspring/CarTest-context.xml in our case.

The @Resource annotation finally is defined by JSR 250 ("Common Annotations for the JavaTM Platform") and is used here to signal, that the validator field shall be populated by Spring. Spring processes the @Resource annotation following by-name semantics, meaning that a Spring bean with the name "validator" will be searched in the application context and injected into the field.

One final word on a version incompatibility between Spring and JUnit I noticed. Using JUnit 4.5 gave me the following error:

1
java.lang.NoSuchMethodError: org.junit.runner.notification.RunNotifier.testAborted(Lorg/junit/runner/Description;Ljava/lang/Throwable;)

So be sure to use JUnit 4.4. A complete Maven project with this post's source files can be downloaded here.

6 comments:

Stefan Bley said...

Vergleichst du in Zeile 31 die Fehlertexte?

Yuri said...

Hi Gunnar!

I found your blog post very useful. Thanks a lot for posting it.

Do you have any thoughts about client side validation with jsr-303?
how to define JavaScript code and associate it with bean property?

Gunnar Morling said...

@Stefan

Yes - I am comparing the error messages in order to ensure that really the expected constraint is violated.

Alternatively one could use the meta data API provided by JSR 303:

Annotation annotation =
  constraintViolations.iterator().
  next().getConstraintDescriptor().
  getAnnotation();

assertEquals(
  Size.class, annotation.annotationType());
assertEquals(
  2, ((Size) annotation).min());
assertEquals(
  14, ((Size) annotation).max());

That way the test is more robust (in case the error text changes) but also a bit lengthier.

Gunnar Morling said...

@Yuri

Great to hear, that you like the post.

Right now I am not aware of any support for client-side validation using JavaScript or similar by JSR 303, but you might ask that question in the official feedback forum.

I think this really depends on the web framework to be used and how Bean Validation will be integrated with it. Probably one could come up with custom validator implementations that provide the JavaScript code in some form.

sristi said...

Hi Gunnar,

Your blog is very useful.

But I am unable to perform testing on it.
It gives an exception
org.junit.runner.notification.RunNotifier.testAborted(Lorg/junit/runner/Description;Ljava/lang/Throwable;)V

Kindly check the same and revert back as soon as possible.

You can mail me at sristi.lohani@yahoo.com

Regards,
Sristi

Gunnar Morling said...

Sristi, from the snippet you posted it is impossible to tell what the real cause was.

Maybe you are running into the Spring/JUnit version problem I described in the last section (though I don't think that problem still exists with current versions of Spring/JUnit).

Also note that some time passed since this post was written and that Spring 3 comes with Bean Validation support out of the box.

So you can have Validators and ValidatorFactories easily injected by setting up a LocalValidatorFactoryBean in your application context.