Sunday, January 16, 2011

Bookmark and Share

Earlier this week the first beta version of Hibernate Validator 4.2 was released. Hardy's announcement on in.relation.to describes in detail what's new in this release, so be sure to check it out.

One of the new features I'm really excited about is method validation, which I therefore would like to discuss in more detail in the following.

What's it about?

Up to now constraint annotations were only supported at fields or at getter methods for property constraints and at type declarations for class-level constraints.

With HV 4.2 it is now also possible to use constraint annotations for a programming style known as "Programming by Contract". More specifically this means that any Bean Validation constraints (built-in as well as custom constraints) can be used to specify

  • any preconditions that must be met before a method invocation (by annotating method parameters with constraints) and
  • any postconditions that are guaranteed after a method invocation (by annotating methods)

As example let's consider the following class from a fictitious video rental application:

1
2
3
4
5
6
7
8
9
public class MovieRepository {

    public @NotNull Set<Movie> findMoviesByTitle( 
        @NotNull @Size(min = 3) String title) {

        //search movie in database ...
    }   

}

The following pre- and postconditions will hold here (provided the method validation is triggered automatically by some integration layer, see below):

  • The title parameter is guaranteed to be not null and at least 3 characters long
  • The return set of movies is guaranteed to be not null

Compared to traditional ways of parameter or result value checking this approach has two big advantages:

  • These checks don't have to be performed manually, which results in less code to write and maintain. You'll never again have to throw IllegalArgumentExceptions or similar by hand.
  • The pre- and postconditions of a method don't have to be expressed again in the method's JavaDoc, since any of it's annotations will automatically be included in the generated JavaDoc. This means less redundancy which reduces the chance of inconsistencies between implementation and comments.

Method validation is not restricted to constraints related to parameters or to return values themselves. Using the @Valid annotation it is also possible to recursively validate parameters or return values:

1
2
3
4
5
6
7
8
public class UserRepository {

    public void persistUser(@NotNull @Valid User user) {

        //persist user in database ...
    }   

}

In this example not only the @NotNull constraint at the user parameter would be checked, but also any constraints defined at the User type. As known from standard Bean Validation the @Valid annotation could also be applied to array-, Iterable- and Map-typed parameters or return values.

The MethodValidator interface

For the purposes of method validation Hibernate Validator defines the new interface MethodValidator (any type or method names are working titles and might change until HV 4.2 is final), which provides methods for parameter and return value validation:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface MethodValidator {

    public <T> Set<MethodConstraintViolation<T>> validateParameter(
        T object, Method method, Object parameterValue, 
        int parameterIndex, Class<?>... groups);

    public <T> Set<MethodConstraintViolation<T>> validateParameters(
        T object, Method method, Object[] parameterValues, Class<?>... groups);

    public <T> Set<MethodConstraintViolation<T>> validateReturnValue(
        T object, Method method, Object returnValue, Class<?>... groups);

}

To get hold of a method validator, unwrap an instance of HV's Validator implementation as follows:

1
2
3
4
5
MethodValidator validator = Validation.byProvider( HibernateValidator.class )
    .configure()
    .buildValidatorFactory()
    .getValidator()
    .unwrap( MethodValidator.class );

All of MethodValidator's methods return a set of MethodConstraintValidations which describe in detail any constraint violations that occurred during validation. MethodConstraintValidation extends the standard ConstraintViolation and provides additional information such as method and parameter causing a constraint violation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface MethodConstraintViolation<T> extends ConstraintViolation<T> {

    public static enum Kind {

        PARAMETER,

        RETURN_VALUE
    }

    Method getMethod();

    Integer getParameterIndex();

    String getParameterName();

    Kind getKind();
}

Unfortunately the byte code of a Java program normally doesn't contain any information on parameter names, therefore getParameterName() for now returns synthetic names such as arg0 etc. For a future release we are investigating approaches for parameter naming, e.g. by leveraging JSR 330's @Named annotation. I created a JIRA issue for this, so if you have any feedback on this just add a comment to the ticket.

Triggering method validation

Hibernate Validator itself only provides the engine for method validation but it does not deal with triggering such a validation.

Instead this is typically done using frameworks/APIs which provide AOP or method interception services such as AspectJ or CDI. But also plain Java dynamic proxies can basically do the trick.

Typically such a validation interceptor does the following:

  • intercept each method call to be validated,
  • validate the invocation's parameter values,
  • proceed with the method invocation and
  • finally validate the invocation's return value.

If any of the two validation steps yields one ore more constraint violations the interceptor typically throws a MethodConstraintViolationException containing the constraint violations that have occurred.

Trying it out yourself

To see whether method validation works out as intended I started a project at GitHub called methodvalidation-integration which provides validation handlers for some interception technologies popular these days.

Currently there are implementations for CDI as well as Google Guice and an InvocationHandler which can be used with JDK dynamic proxies. I also plan to add support for Spring AOP/AspectJ when time permits.

To try method validation out yourself just get the bits from GitHub and have a look at the test in each of the sub-projects. Just give it a try and let me know how everything works.

Future steps

This post introduced the new method validation feature which will be part of Hibernate Validator 4.2. The first beta release contains the basic implementations of this, so we are more than interested in you feedback on this. Please report any bugs in HV's JIRA instance, any questions or ideas for improvement are also highly welcome in HV's forum.

For HV 4.2 Beta2 we plan to add documentation on the method validation feature to HV's reference guide which will also describe some advanced topics such as using method validation with group sequences etc. We will also add support for method level constraints to the constraint meta-data API.

Two further ideas scheduled for after 4.2 are providing the ability to specify method level constraints using XML and using the programmatic constraint configuration API.

17 comments:

Pendyshchuk Max said...

I took a look at code posted to GitHub, especially CDI. Just wondering, does it mean that if bean is CDI managed bean, then I don't need to trigger validation, it will be done by container? Particularly I'm interesting in validating ejb methods - will I have to intercept methods, or container will do it for me?

Gunnar Morling said...

Right, the validation is triggered automatically by the container for any CDI managed bean, which is annotated with @AutoValidating:

@AutoValidating
public class SomeClass {

  public void doSomething(@NotNull String foo) {
    //...
  }

}

Have a look at the test for an example.

If you are running on Java EE 6 this should also work for EJBs as they are CDI managed beans, too.

For Java EE 5 the interceptor would have to be adapted somewhat and it would have to be registered using @Interceptors or ejb-jar.xml. I'll put an EE 5 version of the project on GitHub if I find the time.

Pendyshchuk Max said...

Danke schoen! I work with JBoss 6 AS, so I hope when new version of HV comes I can just update it and use all advantages of that promising approach (for now I just check "manually" and throw Exceptions; HV will make my code cleaner).

Martin said...
This comment has been removed by the author.
Martin said...

Hi, first of all, thanx, nice example.

I tested spring part. However @AutoValidating works only on service implementation (class), not on service definition (interface).

If I have service interface:

@AutoValidating
public interface SomeIntf {
public void doSomething(@NotNull String foo);
}

and service implementation:

public class SomeClass implements SomeIntf {
public void doSomething(String foo) {
//...
}
}

method params are not validated. I need to put @AutoValidating to SomeClass (but param annotations defined at interface are used by interceptor).

My motivation is to have all annotations on the interface - contract level.

Do I need to change the Interceptor's pointcut expressions?

Gunnar Morling said...

Martin, thanks for your feedback. Right, currently the implementation must be annotated with @AutoValidating.

If you feel like you might create a ticket in the project's issue tricker at GitHub. I'll look into this then if I find some time.

Pedro said...

This a *really nice* feature.

To better support Contract Based Programming, I would love to have support for class invariant checks - I think this is is equivalent to just checking all constraints on method exit.

I implemented this by modifying your ValidationInterceptor class to call the validator's validate() method on method exit and check there are no constraint violations -but only for public methods, non-public methods should be allowed to leave the object unstable!

But, yes, this makes things more complex (consider group handling, for example), and slower. And I can see how easily the "Contract Based Programming" support can turn into a religious war.

Would love to know what's your take on the subject of providing even more support for Contract Based Programming via Hibernate Validator.

Thanks for the great work!

Valeriy Felberg said...

First of all, thanks for the very useful posting. In our project, we use Spring and it was easy to adapt your example from GitHub and achieve a working solution. We have used AspectJ to be able to validate non-publich methods as well. One limitation remains though: ValidatorImpl does not want to validate static methods and requires the target object to be non-null. IMHO, this restriction is not necessary since all constraint annotations are declared on the method. It would be really nice if all types of methods, inclusively the static ones, could be validated using JSR 303.

Gunnar Morling said...

Pedro, thanks for your feedback. Checking class invariants upon method invocations sounds interesting.

In parallel to HV itself I created Seam Validation, a Seam 3 module which integrates method validation with CDI (I plan to blog on this if I find some time). If you feel like you might add a ticket to Seam Validation's JIRA with your feature request.

Gunnar Morling said...

Valeriy, thanks for your comment.

The Bean Validation API (JSR 303) (and hence HV) generally requires all validated methods/fields to be non-static. To be consistent this also applies to HV's method validation feature.

That said, I agree that validating static methods generally seems possible. You might add a feature request in the JSR 303 issue tracker for adding support for static members to BV. Then this could be considered for method validation, too.

Daniel said...

Thanks alot gunnar, tried your guicy way (after a first fail attemp on my own) and it worked out of the box! As someone else posted before, it would still be nice to see method validation in javax.validation to be code-independent from hibernate.

kind regards,
avi

Gunnar Morling said...

Daniel, thanks for your feedback. Method validation is being considered in the discussions for BV 1.1 (actually it was already mentioned in the appendix of BV 1.0 but not yet part of the official spec).

You might be interested in Emmanuel's blog post on this and the discussion taking place there, so make sure to raise your voice for the features you consider useful.

Anonymous said...

can you verify if this integrates well with EJB 3.0 ?
I wrote a EJB 3.0 interceptor to do pretty much what you do in cdi-integration interceptor & I keep getting the error -


"Only the root method of an overridden method in an inheritance hierarchy may be annotated with parameter constraints.The following method itself has no parameter constraints but it is not defined one a sub-type of ...


even though i declared the constraint on the POJO interface method only & NOT on the EJB Implementation bean.

interface Foo {
void bar(@NotNull String name);
}

@Local
interface FooLocal extends Foo {

}

@Remote
interface FooRemote extends Foo {

}

class FooBean implements FooLocal, FooRemote, Foo {

void bar(@NotNull String name){

}

}

This is on weblogic 10.x

Anonymous said...

Sorry, the Bean Impl does not have the constraint -

class FooBean implements FooLocal, FooRemote, Foo {

void bar(String name){

}

}

Gunnar Hillert said...

Great blog post and a wonderful Hibernate Validator feature. Can't wait to see this being part of the official Bean Validation 1.1 spec!

Also, Spring 3.1 now has direct support for method level Bean Validation. I wrote up a blog post with a example providing some more information:

http://hillert.blogspot.com/2011/12/method-validation-with-hibernate.html

Viele GrĂ¼sse,

Gunnar (Hillert)

Gunnar Morling said...

@anonymous: You might be affected by HV-482 (this issue also outlines the rationale behind the exception).

We couldn't solve this back then, though. If I remember correctly it seemed to be an issue with the way how proxies are generated by WLS. If you can verify this and think there is something which could be done on the HV side, feel free to create a new ticket at HV's Jira instance.

Gunnar Morling said...

@Gunnar

Thanks for your feedback, it's really great to see that method validation is now directly supported by Spring. This renders the Github project more or less obsolete (the CDI part is covered by Seam Validation), but it was basically created as placeholder in the first place until others such as Spring stepped up.

ATM moment I'm working on the standardization of the method validation feature for BV 1.1. So if you have any input don't hesistate to contact the JSR 349 EG and let us know about your ideas.

I also read your blog post, great work :)