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.
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.
@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.
@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.
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.
@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.
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.
@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.
@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
- Hibernate Validation Framework
- Taylor Validation
- Commons Jexl