Monday, August 24, 2009

Bookmark and Share

JSR 303 ("Bean Validation") is the upcoming standard for the declaration and evaluation of constraints at Java object models, either by using annotations or XML descriptors.

Having been in the works for quite a while, the Bean Validation API comprises a lot of the features of previously existing validation frameworks such as Hibernate Validator or the OVal framework.

One feature I'm particularly loving about OVal is not part of the new standard API: the possibility to define constraints using scripting or expression languages. This is quite practical, if the validation of one bean property depends on the value of another property of the same bean. Imagine for instance a calendar application, where the start date of a calendar event shall always be earlier than the end date.

Using the Bean Validation API, this problem could be solved by implementing a custom class-level constraint. Unfortunately this requires you to implement a dedicated constraint, whenever such inter-property validation is required.

With the help of a generic script annotation that takes an arbitrary script expression to be evaluated, this effort can be saved (at the cost of losing some compile-time safety, though). By using a simple Groovy expression for instance the constraint mentioned above could be expressed like this:

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
@ScriptAssert(lang = "groovy", script = "_this.startDate.before(_this.endDate)")
public class CalendarEvent {

    private String title;

    private Date startDate;

    private Date endDate;

    public CalendarEvent(String title, Date startDate, Date endDate) {

        this.title = title;
        this.startDate = startDate;
        this.endDate = endDate;
    }

    public String getTitle() {
        return title;
    }

    public Date getStartDate() {
        return startDate;
    }

    public Date getEndDate() {
        return endDate;
    }

}

But how to implement the ScriptAssert constraint shown in the example? Luckily the JSR 303 spec was designed to make it really easy to create custom constraints, so this is not a big dial. Basically it takes three steps to create a custom constraint:

  • define a constraint annotation
  • implement a validator class able to check the constraint
  • define an error message for the case that the constraint is violated

For the evaluation of script statements, we will leverage the scripting API defined by JSR 223 ("Scripting for the JavaTM Platform"), which is part of the JDK since Java 6. By doing so arbitrary scripting and expression languages, for which a JSR 223 compatible engine exists, can be used in the ScriptAssert annotation (a list of such engines can be found here).

Defining the annotation

At first we have to define the annotation type itself:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Target( { TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = ScriptAssertValidator.class)
@Documented
public @interface ScriptAssert {

    String message() default "{de.gmorling.beanvalidation.scriptassert}";

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

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

    String lang();

    String script();

    @Target( { TYPE })
    @Retention(RUNTIME)
    @Documented
    public @interface List {
        ScriptAssert[] value();
    }
}

The Bean Validation API requires each constraint annotation to define the three attributes

  • message (for specifying the key used to resolve the error text in case of constraint violations)
  • groups (allowing the constraint to be assigned to validation groups, if required)
  • payload (not used by the Bean Validation API itself, but might be used by validation clients to assign custom payloads to a constraint)

Besides these obligatory attributes we define the two attributes

  • lang (used to specify the name of this constraint's script language as understood by the JSR 223 ScriptEngineManager)
  • script (used to specify the script to be evaluated).

Furthermore we specify, that the constraint shall only be allowed at type declarations (by using the @Target meta-annotation) and that the class ScriptAssertValidator shall be used to evaluate the constraint.

By defining an additional inner annotation @List, which takes an array of ScriptAsserts as value, we follow the JSR's recommendation to provide a way for placing multiple constraints of the same type at one object.

Implementing the constraint validator

Having defined the constraint annotation it's time to implement the accompanying validator class:

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
public class ScriptAssertValidator implements
    ConstraintValidator<ScriptAssert, Object> {

    private String script;

    private String languageName;

    private ScriptEngineManager manager = new ScriptEngineManager();

    public void initialize(ScriptAssert constraintAnnotation) {

        this.script = constraintAnnotation.script();
        this.languageName = constraintAnnotation.lang();
    }

    public boolean isValid(Object value,
        ConstraintValidatorContext constraintValidatorContext) {

        ScriptEngine engine = manager.getEngineByName(languageName);

        if (engine == null) {
            throw new IllegalArgumentException(
                "No JSR 223 script engine found for language " + languageName);
        }

        engine.put("_this", value);

        try {
            return Boolean.TRUE.equals(engine.eval(script));
        } 
        catch (ScriptException e) {
            throw new RuntimeException(e);
        }
    }
}

In the initialize() method we store the given ScriptAssert annotation's values for the language name and the script contents.

The isValid() method is called by the Bean Validation runtime, whenever the constraint shall be evaluated. First we fetch a JSR 223 ScriptEngine for the given language. In order to make the object to be validated accessible inside the script, we put it into the engine's context under the name "_this". At last we call ScriptEngine's eval() method to evaluate the given script and check, whether the script's output equals to Boolean.TRUE.

Defining the error message

In case the constraint is violated, an error message is required to be put into the ConstraintViolation object created by the Bean Validation runtime. Error messages are defined in a resource file called ValidationMessages.properties (resp. localized derivations of that). So let's create that file with the following content:

1
de.gmorling.beanvalidation.scriptassert=Script statement "{script}" didn't evaluate to TRUE.

Testing the ScriptAssert annotation

Finally it's time to give our ScriptAssert constraint a little test run. Taking the CalendarEvent class from the introductory example, a test could look as follows:

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
public class CalendarEventTest {

    private static Validator validator;

    private static Date startDate;

    private static Date endDate;

    @BeforeClass
    public static void setUpValidatorAndDates() {

        validator = Validation.buildDefaultValidatorFactory().getValidator();

        startDate = new GregorianCalendar(2009, 8, 20).getTime();
        endDate = new GregorianCalendar(2009, 8, 21).getTime();
    }

    @Test
    public void validCalendarEvent() {

        CalendarEvent testEvent = 
            new CalendarEvent("Team meeting", startDate, endDate);

        assertTrue(validator.validate(testEvent).isEmpty());
    }

    @Test
    public void invalidCalendarEvent() {

        CalendarEvent testEvent = 
            new CalendarEvent("Team meeting", endDate, startDate);

        Set<ConstraintViolation<CalendarEvent>> constraintViolations = 
            validator.validate(testEvent);

        assertEquals(1, constraintViolations.size());
        assertEquals(
            "Script statement \"_this.startDate.before(_this.endDate)\" didn't evaluate to TRUE.",
            constraintViolations.iterator().next().getMessage());
    }

}

In validCalendarEvent() the event's start date is earlier than the end date, resulting in an empty set of constraint violations when validating the object. The opposite case is shown in invalidCalendarEvent(): We retrieve a ConstraintViolation as the event's start and end date are mixed up.

Note that as we are using Groovy as scripting language in the example, we need to have the Groovy library in the classpath (which already contains a JSR 223 compatible script engine implementation). Using Apache Maven, only the following dependency has to be added to your project's pom.xml:

1
2
3
4
5
6
7
8
...
<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy-all</artifactId>
    <version>1.6.4</version>
    <scope>test</scope>
</dependency>
...

Trying it out yourself

I put all the sources of this post into a little project over at GitHub. A binary can be found in my Maven repository. So if want to give it a test run, first add the repo to your pom.xml/settings.xml:

1
2
3
4
5
6
7
8
...
<repositories>
    <repository>
        <id>http://gunnarmorling-maven-repo.googlecode.com/svn/repo/</id>
        <url>http://gunnarmorling-maven-repo.googlecode.com/svn/repo/</url>
    </repository>
</repositories>
...

Then simply add the following dependency to your POM:

1
2
3
4
5
6
7
...
<dependency>
    <groupId>de.gmorling</groupId>
    <artifactId>script-assert</artifactId>
    <version>0.2</version>
</dependency>
...

Furthermore you need the (runtime) dependency to a Bean Validation implementation (the script-assert project only has a dependency to the API). I recommend to take the reference implementation, so add a dependency to the RI itself as well as a binding for sl4j (used for logging purposes):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>4.0.0.Beta3</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jdk14</artifactId>
    <version>1.5.6</version>
    <scope>runtime</scope>
</dependency>
...

As always any feedback on the usefulness of this post and any ideas for further improvement would be highly appreciated.

Sunday, August 23, 2009

Bookmark and Share

Finally I found some time to continue working on a little project of mine, scriptable dataset, which makes it possible to use script snippets (in languages such as JRuby, Groovy etc.) in your DBUnit dataset files. Imagine for instance, you were building a web shop application and wanted to insert an order issued 14 days ago into your order table. Using the scriptable dataset, you can do exactly that:

1
2
3
4
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <order product="CD player" orderdate="jruby:DateTime::now() - 14"/>
</dataset>

In comparison to the original release the 1.0 version contains basically some refactorings: ScriptableDataSetConfig got a new constructor, the Java 6 ServiceLoader mechanism is used to detect standard script invocation handlers, all dependencies were updated to the current versions and the entire code base was cleaned up a little bit.

Many thanks to Kevin Hutson, who contributed a test case for using Groovy as scripting language in a dataset file. It's really great to see, how GitHub is encouraging people to contribute to open source projects by making forking and merging that easy.

To allow for using the scriptable dataset in Maven based applications, I set up a Maven repository at Google code, which hosts the project's artifacts. Just add this repo to your pom.xml or settings.xml as shown below:

1
2
3
4
5
6
7
8
...
<repositories>
    <repository>
        <id>http://gunnarmorling-maven-repo.googlecode.com/svn/repo/</id>
        <url>http://gunnarmorling-maven-repo.googlecode.com/svn/repo/</url>
    </repository>
</repositories>
...

Then add scriptable dataset as dependency as well as a binding for sl4j, which is used for logging purposes:

1
2
3
4
5
6
7
8
9
10
11
12
...
<dependency>
    <groupId>de.gmorling</groupId>
    <artifactId>scriptable-dataset</artifactId>
    <version>1.0</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jdk14</artifactId>
    <version>1.5.8</version>
</dependency>
...

An example for using the scriptable dataset is shown in the project's unit test ScriptableDataSetTest.

So feel free to give it a try. I'd be glad on any comments: do you like the idea of scripting in dataset files in general, what use cases could you think of, what place for improvement do you see?

Monday, August 10, 2009

Bookmark and Share

My second article in Java Magazin is out! It's named "Objekt: Zur Prüfung bitte!" and covers the upcoming Java standard for object validation, the Bean Validation API (JSR 303).

Using a simple web shop as example, the article starts with explaining Bean Validation basics (retrieving a Validator, using its methods for validating entire beans or single properties), followed by more detail information on validation groups and group sequences.

Next it is shown how to write a custom constraint annotation and its validation routine, followed by explaining the concept of constraint composition. Finally it is analyzed, how the Bean Validation API integrates with other standards such as JPA 2.0.

So get your copy of Java Magazin 09/2009 and let me know, how you like the article.