42 Lines

Integrating hibernate validator and wicket

I've made some code public that integrates Hibernate Validator with Wicket.  The code is available via anonymous svn via:

  svn co https://svn.42lines.net/public/examples/wicket/hibernate-validator-example

and I'll go through how it works in this post. 

Background

Hibernate Validator is an annotations-based framework for applying domain constraints to properties of JPA models.  This allows you to declaratively enforce rules before objects are persisted, in Java rather than at the database level (or in addition to database constraints, which is what we do in most cases.)  The package comes with several built in validators, and you can also build your own to extend it.  My example's usage is:

/* Setters and getters removed for clarity */
@Entity
public class TestModel implements Serializable {
 
	@Id @GeneratedValue
	Long id;
 
	@Length(min=5)
	String atLeastFiveCharacters;
 
	@Email
	@NotNull
	String mustBeEmailAddress;

 

In the above code, the field atLeastFiveCharacters will be forced to be at least five characters long (though it can be null), while the field mustBeEmailAddress will be required to be non-null and reasonably resemble an email address.

The code in Hibernate Validator served as the inspiration for JSR 303, and Hibernate Validator 4.0 (currently in alpha) is the reference implementation for that spec.   The code in my example is based on the 3.1.0 GA release, and I'll upgrade it to 4.0 once it's final.

Wicket Integration

Wicket has its own validation framework, centered on the IValidator interface and its implementations, which can be added much like behaviors to any FormComponent.  Validation is run after form input has been converted but before it is applied to the model.  Wicket validators typically subclass AbstractValidator, and when it is determined that form input is not valid, they invoke error() on the base class, which propagates the message (usually a resource key along with a parameter map of substitutions for that key) to the FormComponent, which adds it as an error to the session.

There's a little bit of an impedance mismatch when making this work with hibernate validator, because it has its own error message construction code, which is also based on resources.  It's feasible that the two could be bridged, by providing hibernate's ClassValidator with a MessageInterpolator that knows how to lookup wicket resources, or that just returns the message key itself and allows the glue code using ClassValidator to construct an IValidationError based on the hibernate error key and the target bean.  But that was beyond my free-time-quota for this first stab.  So I just add the hibernate validator messages, fully interpolated by hibernate and with a prefix of the wicket component id, to the target component.

The integration classes

There are two top level classes that make up this integration.  The first is an INullAcceptingValidator implementation named HibernateValidator.  There are two things that this validator needs in order to do its job.  One is the model containing the hibernate entity itself, and the other is the name of the property to validate.  There are two ways to provide it with this information.

The most flexible (and most verbose) way to do this is through its constructor, as parameters:

      form.add(myTextField = new TextField<String>("someField"));
      myTextField.add(new HibernateValidator<String>(form.getModel(),"someField"));

 

This is useful if you use multiple models in your form.  However, if you use a single form model with CompoundPropertyModels for the fields, you can just construct the HibernateValidator with no parameters:

      form.add(myTextField = new TextField<String>("someField"));
      myTextField.add(new HibernateValidator<String>());

 

 and upon being added to the FormComponent it will use that component's AbstractPropertyModel to obtain the property name and the model for the entity (through model.getChainedModel()).  Note that if you go this route, you have to add the behavior to the component after it has been added to the form, because otherwise the behavior can't access the form's model on add().

 HibernateValidatorBehavior

This is still somewhat tedious, especially if you have a lot of properties.  It also would require you to remember to add the behavior when you add a constraint to a model field that did not previously have one, which violates DRY.  As an alternative, you can just do:

    	form.add(new HibernateValidatorBehavior());

 

and the behavior will automatically add validators to all text fields that follow the above pattern, on the first call to onBeforeRender().   This is the easiest, most concise, but least flexible of the approaches.  It probably works best for simple CRUD forms.

Setup

The example is based on the wicket quickstart archetype, uses Wicket 1.4-rc4, is built with maven, and can be used to generate an eclipse project either through File -> Import -> Maven project with m2eclipse or with "mvn eclipse:eclipse."  Once built, it can be run within eclipse by right clicking on Start.java and choosing Run As -> Java Application, and then hitting http://localhost:8080 with your browser.

The example also uses:

  • An in memory hsqldb embedded database for persistence
  • jetty for running in eclipse
  • jetty-plus to provide jndi (for the datasource)
  • JPA/Hibernate for persistence
     

How the example works

It contains a single page with text fields, and if you enter in invalid input into those fields and hit submit, it will let you know through a feedback panel.  The code in question uses a model named TestModel which has hibernate validator annotations.  It adds text fields mapped to this model, and adds a single HibernateValidatorBehavior as in the above description.  Its submit button persists the changes with JPA and let's you know that the changes are saved. 

I think the integration classes could eventually migrate to wicketstuff, but I'd appreciate feedback first.  In particular, I'm open to suggestions as to the best approach to make this work with wicket's resources in a fashion similar to the other validators.

Random final musings

I'm a big fan of domain driven design, and of annotation frameworks in general, so using annotations to concisely constrain domain object behavior makes the model smarter and avoids moving business logic out of the model, which all too often happens.  Having the view layer make use of this metadata is also great, as it follows the DRY principle.  In addition, once model constraints are in place, a flush() of the persistence context will result in an error and a dead JPA session if the constraints aren't obeyed.  So you really do want to mimic the validation in your view layer that hibernate validator is already going to do, lest you get an exception propagating to your user (or caught and redirected.) 

However, I also believe that a UI should do a better job of explaining what is wrong with form input and why, or even better, it should provide input components that don't allow you to make mistakes in the first place.  So while I think the above approach is great for rapid prototyping of usable forms, and for places where things are allowed to be a little uglier (say, administrator reporting tools), I don't think it's the best solution for usable interfaces.  It is a nice line of defense though, and it's a no brainer to add it if you are already using Hibernate Validator.

Peace,

-Clint

Comments

Bug

Thanks for your post. It works, though I did find a bug. If the underlying property not a direct property of the base object but rather somewhere up the tree you get a nasty exception. For example:
if you have a Person class and it has as property an Address class which has a property "street". If you now pass a PropertyModel as follows to your FormComponent that you want validation on:

PropertyModel(yourBaseObjectModel, "address.street")

You will not get a nice message on your feedbackPanel but rather a nasty exception. I could not find a direct solution, only avoiding above situation by creating a "in-between" model seems to work. So in other words, you need to do as follows:
PropertyModel inBetweenModel = new PropertyModel(yourBaseObjectModel, "address");

PropertyModel yourModel = new PropertyModel(inBetweenModel, "street");

And pass "yourModel" on to the FormComponent that will be validated.

Seam ModelValidator

Looks great. How does this compare with Seam's ModelValidator? Or is one the inspiration for the other?

P.S. Is there a guest username and password to check out from your svn repository?

-Paul