24 enero 2009

Extending the Hibernate Validation Framework for Complex Rules

Extending the Hibernate Validation Framework for Complex Rules

Posted by jgilbert01 under Components
[8] Comments

@Entity
public class Loan {
@NotNull(message="The Amount must be provided.")
private Double amount;
...
}

The Hibernate Validation Framework leverages annotations to define data validation rules on entity beans. Out of the box it comes with many useful annotations for defining simple validation rules such as, NotNull, Min, Max, etc... However, for complex rules that involve multiple properties you will have to write your own validators. This posting shows two approaches to make this easier by using expression language and custom code fragments.

Simple Validations


Here is an example of a simple validation. The annotation on the amount property is scoped just to that property. Thus only simple rules can be defined.

@Entity
public class Loan {
@NotNull(message="The Amount must be provided.")
private Double amount;
...
}

Complex Validation


Here is an example of a complex validation. The annotation on the class is scoped to the whole class. Thus complex rules can be defined that involve any property in the class.

@Entity
@StartDateBeforeEndDate(message="The start date must be before the end date.")
public class Loan {
private Date startDate;
private Date endDate;
...
}

These features pretty much cover all the various validation requirements. However, defining complex validation rules requires writing both a custom annotation and a custom validator for each rule, which can become very cumbersome. Here are two ways around this issue.

Expression Language Validator


By using expression language we can write a generic validator that can be reused for various rules. The generic Expression annotation is used to define the rule.


@Entity
@Expression(value="value.startDate <>", message="The start date must be before the end date.")
public class Loan {
private Date startDate;
private Date endDate;
...
}

The ExpressionValidator class uses the commons-jexl library to evaluate the expression. The value variable in the expression equals the instance of the object being validated. In this case value is a loan.

public class ExpressionValidator implements Validator {
private String expression;
public void initialize(Expression expression) {
this.expression = expression.value();
}

public boolean isValid(Object value) {
org.apache.commons.jexl.Expression e = ExpressionFactory.createExpression(expression);
JexlContext jc = JexlHelper.createContext();
jc.getVars().put("value", value);
return (Boolean) e.evaluate(jc);
}
}

Custom Assertion Validator


There are times when you will just have to write custom validation code. But instead of writing a custom annotation and validator class it would be nice to encapsulate the rules in the same class for which the rules apply. This generic Assertion annotation and IAssertion interface do the trick.

@Entity
@Assertion(value=Loan.StartDateBeforeEndDateAssertion.class, message="The start date must be before the end date.")
public class Loan {
private Date startDate;
private Date endDate;
...
public static class StartDateBeforeEndDateAssertion implements IAssertion
{
public boolean isValid(Object value) {
Loan loan = (Loan) value;
return
loan.getStartDate().before(loan.getEndDate());
}
}
}

The AssertionValidator class will create an instance of the IAssertion and execute it against the object being validated.

public class AssertionValidator implements Validator {
private IAssertion assertion;
public void initialize(Assertion assertion) {
this.assertion = (IAssertion) assertion.value().newInstance();
}

public boolean isValid(Object value) {
return assertion.isValid(value);
}
}

Hopefully the expression appoach will satisfy all your needs and you wont need this custom code approach. For example, you could add an isStartDateBeforeEndDate() method to your entity and call it in a simple expression as show below. But the custom code approach is here just in case.

@Entity
@Expression(value="value.startDateBeforeEndDate", message="The start date must be before the end date.")
public class Loan {
...
public boolean isStartDateBeforeEndDate() {
return startDate.before(endDate);
}
}

Multiple Rules


There is still another piece to this puzzle. We need to be able to apply multiple expressions or assertions. To do this we need a container annotation.

@Entity
@Expressions ({
@Expression(value="value.startDate <>", message="..."),
@Expression(value="...", message="...")
})
public class Loan {
private Date startDate;
private Date endDate;
...
}

Note that this capability relies on a requested Hibernate enhancement. You can vote for this enhancement here: JIRA Issue ANN-513. Or you can apply the patch yourself.

Resources



1 comentario:

Francisco Philip dijo...

hola

tengo unos validators similares, pero no puedo hacer q en seam se validen en la fase en que lo hacen los metodos en faces, y solo saltan en la persistencia...

alguna idea?