One other integrity constraint we have to consider is about the values of the
year property, which should be in [1459. nextYear] range. At first
we may have the intention to use @Min and @Max to specify the range,
but we discover soon that is not possible to specify an expression as value of the
@Max annotation (as well as for the other annotations too), so we cannot
specfiy the nextYear value expression. Sure, we are still able to specify the
lower range value (i.e. 1459) by using @Min( value = 1459).
Fortunatelly, the Bean Validation API allows to define custom validation annotations with custom code performing the constraint checks. This means that we are free to express any kind of validation logic in this way. Creating and using a custom validation annotation requires to:
create the annotation interface - we call this UpToNextYear and the
corresponding code is shown
below:
@Target( {ElementType.FIELD, ElementType.METHOD})
@Retention( RetentionPolicy.RUNTIME)
@Constraint( validatedBy = UpToNextYearImpl.class)
public @interface UpToNextYear {
String message() default
"The value of year must be between 1459 and next year!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}It requires to define the three builtin methods, message (returns the
default key or text message in case the constraint is violated), groups
(allows the specification of validation groups, to which this constraint belongs) and
payload (used by clients of the Validation API to asign custom payload
objects to a constraint - this attribute is not used by the API itself). Notice the
@Target annotation, which specifies on which kind of element type we can
apply the annotation ( e.g. fields/properties and methods in our case). The
@Constraint annotation allows to specify (using the
validatedBy property) the implementation class which will perform the
validation, i.e. UpToNextYearImpl in our case.
create an implementation class where the validation code is defined:
public class UpToNextYearImpl implements
ConstraintValidator<UpToNextYear, Integer> {
private Calendar calendar;
@Override
public void initialize( UpToNextYear arg0) {
this.calendar = Calendar.getInstance();
calendar.setTime( new Date());
}
@Override
public boolean isValid( Integer year,
ConstraintValidatorContext context) {
if (year == null ||
year > this.calendar.get( Calendar.YEAR) + 1) {
return false;
}
return true;
}
}The implementation class implements the ConstraintValidator interface
which requires two Java generics parameters: first is the annotation interface defined
before (i.e. UpToNextYear), the second one represents the type of elements,
which the validator can handle (i.e. Integer, so implicitly also the
compatible primitive type int). The initialize method (as also
the name specifies) allows to create all kind of initialization are required for
performing the validation check. The isValid method is responsible to
perform the validation check: it must returns true if validation passed and
false otherwise. The first parameter of the isValid method represents the
value (current property or returned method value) to be validated and its type must be
compatible with the type defined by the second generics parameter of the
ConstraintValidator (i.e. Integer in our case).
annotate the property or method - in our case we like to validate the year
property so the corresponding code
is:
@Entity
@Table( name = "books")
public class Book {
// ...
@Min( value = 1459)
@UpToNextYear
private Integer year;
//...
}