Monday, June 28, 2010

Bookmark and Share

I'm very proud to report that today Hibernate Validator 4.1.0 Final has been released. Besides many bug fixes this release adds also a lot of new features to the code base.

The changes fall into four areas, which I'm going to discuss in detail in the following:

  • New constraint annotations
  • ResourceBundleLocator API
  • API for programmatic constraint creation
  • Constraint annotation processor

New constraint annotations

In addition to the constraints defined in the Bean Validation spec and those custom constraints already part of HV 4.0, the new release ships with the following new constraint annotations:

  • @CreditCardNumber: Validates that a given String represents a valid credit card number using the Luhn algorithm. Useful to detect mis-entered numbers for instance.
  • @NotBlank: Validates that a given String is neither null nor empty nor contains only whitespaces.
  • @URL: Validates that a given String is a valid URL. Can be restricted to certain protocols if required: @URL(protocol = "http") private String url;
  • @ScriptAssert: Allows to use scripting or expression languages for the definition of class-level validation routines.

Let's have a closer look at the @ScriptAssert constraint, which I'm particularly excited about as I have implemented it :-).

The intention behind it is to provide a simplified way for expressing validation logic that is based on multiple attributes of a given type. Instead of having to implement dedicated class-level constraints the @ScriptAssert constraint allows to express such validation routines in an ad hoc manner using a wide range of scripting and expression languages.

In order to use this constraint an implementation of the Java Scripting API as defined by JSR 223 ("Scripting for the JavaTM Platform") must be part of the class path. This is automatically the case when running on Java 6. For older Java versions, the JSR 223 RI can be added manually to the class path.

As example let's consider a class representing calendar events. The start date of such an event shall always be earlier than the end date. Using JavaScript (for which an engine comes with Java 6) this requirement could be expressed as follows:

1
2
3
4
5
6
7
8
9
@ScriptAssert(lang = "javascript", script = "_this.startDate.before(_this.endDate)")
public class CalendarEvent {

    private Date startDate;

    private Date endDate;

    //...
}

So all you have to do is to specify a scripting expression returning true or false within the script attribute. The expression must be implemented in the language given within the language attribute. The language name must be the language's name as registered with the JSR 223 ScriptEngineManager. Within the expression the annotated element can be accessed using the alias _this by default.

The cool thing is that the @ScriptConsert constraint can be used with any other scripting language for which a JSR 223 binding exists. Let's take JEXL from the Apache Commons project for instance. Using Maven you only have to add the following dependency:

1
2
3
4
5
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-jexl</artifactId>
    <version>2.0.1</version>
</dependency>

With JEXL dates can be compared using the "<" operator. Using a shorter alias for the evaluated object the constraint from above therefore can be rewritten as follows:

1
2
3
4
5
6
7
8
9
@ScriptAssert(lang = "jexl", script = "_.startDate < _.endDate", alias = "_")
public class CalendarEvent {

    private Date startDate;

    private Date endDate;

    //...
}

Very likely one will work with only one scripting language throughout all @ScriptAssert constraints of an application. So let's leverage the power of constraint composition to create a custom constraint which allows for an even more compact notation by setting the attributes lang and alias to fixed values:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Target({ TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = {})
@Documented
@ScriptAssert(lang = "jexl", script = "", alias = "_")
public @interface JexlAssert {

    String message() default "{org.hibernate.validator.constraints.ScriptAssert.message}";

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

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

    @OverridesAttribute(constraint = ScriptAssert.class, name = "script")
    String value();
}

Note how the script attribute of the composing @ScriptAssert constraint is overridden using the @OverridesAttribute meta-annotation. Using this custom constraint the example finally reads as follows:

1
2
3
4
5
6
7
8
9
@JexlAssert("_.startDate < _.endDate")
public class CalendarEvent {

    private Date startDate;

    private Date endDate;

    //...
}

As shown the @ScriptAssert constraint allows to define class-level constraints in a very compact way.

But there is also a price to pay. As scripting languages are used, compile-time type-safety is lost. When for instance the startDate attribute is renamed the script expression would have to be adapted by hand. Also evaluation performance should be considered when validation logic is getting more complex.

So I recommend to try it out and choose what ever fits your needs best.

The ResourceBundleLocator API

The Bean Validation API defines the MessageInterpolator interface which allows to plug in custom strategies for message interpolation and resource bundle loading.

As it turned out, most users only want to customize the latter aspect (e.g. in order to load message bundles from a database) but would like to re-use the interpolation algorithm provided by Hibernate Validator.

Therefore Hibernate Validator 4.1 introduces the interface ResourceBundleLocator which is used by HV's default MessageInterpolator implementation ResourceBundleMessageInterpolator to do the actual resource bundle loading.

The interface defines only one method, in which implementations have to return the bundle for a given locale:

1
2
3
4
5
public interface ResourceBundleLocator {

    ResourceBundle getResourceBundle(Locale locale);

}

The ResourceBundleLocator to be used can be set when creating a ValidatorFactory:

1
2
3
4
5
6
7
ValidatorFactory validatorFactory = Validation
    .byProvider(HibernateValidator.class)
    .configure()
    .messageInterpolator(
        new ResourceBundleMessageInterpolator(
            new MyCustomResourceBundleLocator()))
    .buildValidatorFactory();

The default ResourceBundleLocator implementation used by Hibernate Validator is PlatformResourceBundleLocator which simply loads bundles using ResourceBundle.loadBundle(). Another implementation provided out of the box is AggregateResourceBundleLocator, which allows to retrieve message texts from multiple bundles by merging them into a single aggregate bundle. Let's look at an example:

1
2
3
4
5
6
7
8
9
10
11
HibernateValidatorConfiguration configuration = Validation
    .byProvider(HibernateValidator.class)
    .configure();

ValidatorFactory validatorFactory = configuration
    .messageInterpolator(
        new ResourceBundleMessageInterpolator(
            new AggregateResourceBundleLocator(
                Arrays.asList("foo", "bar"), 
                configuration.getDefaultResourceBundleLocator())))
    .buildValidatorFactory();

Here messages from bundles "foo" and "bar" could be used in constraints. In case the same key was contained in both bundles, the value from bundle "foo" would have precedence, as it comes first in the list. If a given key can't be found in any of the two bundles as fallback the default locator (which provides access to the ValidationMessages bundle as demanded by the JSR 303 spec) will be tried.

API for programmatic constraint creation

Using the Bean Validation API constraints can be declared using either annotations and/or XML descriptor files. Hibernate Validator 4.1 introduces a third approach by providing an API for programmatic constraint declaration.

This API can come in handy for instance in dynamic scenarios where constraints change upon runtime or for testing scenarios.

As example let's consider two classes, Customer and Order, from a web shop application for which the following constraints shall apply:

  • each customer must have a name
  • each order must have a customer
  • each order must comprise at least one order line

With help of the programmatic constraint API these constraints can be declared as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
ConstraintMapping mapping = new ConstraintMapping();

mapping
    .type(Order.class)
        .property("customer", ElementType.FIELD)
            .constraint(NotNullDef.class)
        .property("orderLines", ElementType.FIELD)
            .constraint(SizeDef.class)
                .min(1)
                .message("An order must contain at least one order line")
    .type(Customer.class)
        .property("name", ElementType.FIELD)
            .constraint(NotNullDef.class);  

As the listing shows the API is designed in a fluent style with the class ConstraintMapping as entry point. Constraints are declared by chaining method calls which specify to which property of which type which constraints should be added.

The API provides constraint definition classes such as NotNullDef etc. for all built-in constraints, which allow to access their properties (min(), message() etc.) in a type-safe way. For custom constraints you could either provide your own constraint definition class or you could make use of GenericConstraintDef which allows to identify properties by name.

Having created a constraint mapping it has to be registered with the constraint validator factory. As the programmatic API is not part of the Bean Validation spec, we must explicitely specify Hibernate Validator as the BV provider to be used:

1
2
3
4
5
6
Validator validator = Validation
    .byProvider(HibernateValidator.class)
    .configure()
    .addMapping(mapping)
    .buildValidatorFactory()
    .getValidator();

If you now use this validator to validate an Order object which has no customer set a constraint violation will occur, just as if the "customer" property was annotated with @NotNull.

Constraint annotation processor

The Hibernate Validator annotation processor might become your new favourite tool if you find yourself accidentally doing things like

  • annotating Strings with @Min to specify a minimum length (instead of using @Size)
  • annotating the setter of a JavaBean property (instead of the getter method)
  • annotating static fields/methods with constraint annotations (which is not supported)

Normally you would notice such mistakes only during run-time. The annotation processor helps in saving your valuable time by detecting these and similar errors already upon compile-time by plugging into the build process and raising compilation errors whenever constraint annotations are incorrectly used.

The processor can be used in basically every build environment (plain javac, Apache Ant, Apache Maven) as well as within all common IDEs. Just be sure to use JDK 6 or later, as the processor is based on the "Pluggable Annotation Processing API" defined by JSR 269 which was introduced with Java 6.

The HV reference guide describes in detail how to integrate the processor into the different environments, so I'll spare you the details here. As example the following screenshot shows some compilation errors raised by the processor within the Eclipse IDE (click to enlarge):

Hibernate Validator Annotation Processor in Eclipse IDE

Just for the record it should be noted that the annotation processor already works pretty well but is currently still under development and is considered as an experimental feature as of HV 4.1. If you are facing any problems please report them in JIRA. Some known issues are also discussed in the reference guide.

Summary

The focus for Hibernate Validator 4.0 was to provide a feature-complete, production-ready reference implementation of the Bean Validation spec.

While strictly staying spec-compliant, HV 4.1 goes beyond what is defined in JSR 303 and aims at generating even more user value by providing new constraints, new API functionality as well as an annotation processor for compile-time annotation checking.

In order to try out the new features yourself just download the release from SourceForge. Of course HV 4.1 can also be retrieved from the JBoss Maven repository.

If you are already using HV 4.0.x, the new release generally should work as drop-in replacement. The only exception is that we had to move the class ResourceBundleMessageInterpolator to the new package messageinterpolation. So if you're directly referencing this class, you'll have to update your imports here.

The rationale behind this relocation was to clearly separate those packages which can safely be accessed by HV users from packages intended for internal use only. The public packages are:

  • org.hibernate.validator
  • org.hibernate.validator.cfg
  • org.hibernate.validator.constraints
  • org.hibernate.validator.messageinterpolation
  • org.hibernate.validator.resourceloading

Of course we'd be very happy on any feedback. Questions and comments can be posted in the HV forum, while any issues or feature requests should be reported in JIRA.

11 comments:

Willie Wheeler said...

Nice work on @ScriptAssert. That was a big gap. Now it will be a lot easier to do password confirmations. :-)

Gunnar Morling said...

Thanks for your feedback, great to hear that this is useful :-) Which scripting language you plan to use with the @ScriptAssert constraint?

Kevin Hutson said...

Thank for your work on this library.

I was wondering if you could answer a question?

I am trying a very simple example using Javascript as the lang to validation 2 fields on the POJO.

Unfortunately, we are still on Java 1.5. And, I'm getting an exception.

Example:

@ScriptAssert(lang="javascript",script="_this.foo1.equals(_this.foo2)",message="something bad happened" )

I have pasted the sample code here:
https://gist.github.com/734106

I have added these jar's to my classpath:
hibernate-validator-4.2.0-20101202.092637-16.jar
validation-api.jar
js.jar
script.jar
The last 2 are from the reference implementation of JSR-223 I downloaded. Not impressed with the lack of version information.
And, I do have some more jars on my classpath, but I don't think that's the issue.

Any thoughts? I had to switch to 4.2 of the validator b/c 4.1 had an issue that wouldn't work with Java 1.5 (HV-382).

Have you tried running a sample like this against JDK 1.5?

thanks so much!

Kevin

Gunnar Morling said...

Hi Kevin,

the root exception is thrown because the JSR 223 runtime could find no script engine for your language name "javascript".

Which scripting engine do you use? I could imagine your JSR 223 library comprises only the API itself but no language binding such as Rhino for JavaScript. In that case you would have to add such an implementation to your classpath as well.

You can find out which languages engines are registered with ScriptEngineManager#getEngineFactories().

Kevin Hutson said...

I'm using Javascript. I guess I thought the js.jar that came with the reference implementation would work.

Let me try again and try invoking getEngineFactories myself. It's probably that simple :-)

thanks again!
Kevin

ravi said...

Hi, It is really nice to read your article. I stuck with glassfishv3 and its bean-validator.jar I have included hibernate-Validator4.1.0 with project, But has no effect, whenever i use a class level annotation it fires the exception.
javax.validation.ValidationException: Call to TraversableResolver.isReachable() threw an exception.

Can you please show me some way to get rid of this issue.
Thanks
Ravi

ravi said...

Hi,

Thanks just informing i got one way to solve it to create a TraversalResolver that has actual problem which is bundeled with HiberanteValidator's earlier version.
However may be there is other way out.
If you please show me that too..
Thanks
Ravi.

Gunnar Morling said...

Ravi, have you checked your program's output/log files, you should find the original exception thrown within the call to isReachable() there.

Btw. which version of GlassFish do you use? 3.0.x is bundled with HV 4.0.x, if you want to use HV 4.1 I recommend to upgrade to GF 3.1 once it is released.

j2eestandardsexperience said...

Thanks for reply,

You are right I am using 3.0.x. currently and have got that isReachable() problem. And yes hope
glassfish v3.1 will not have this problem.

Tariq Ahsan said...

I know it should be pretty simple. Trying to create a class level custom constraint. Is it possible to find some examples how to do this? Again, much appreciated for all your excellent postings.

Gunnar Morling said...

Tariq, you can find out more about class-level constraints in the Hibernate Validator reference guide. You can also study @ScriptAssert as an example.