One other integrity constraint we have to consider is about the
allowed values of the year
property, which must be in the
interval [1459, nextYear()] where nextYear() is a function invocation
expression. We may have the idea to use @Min
and
@Max
to specify the interval constraint, but this is not
possible because the @Max
annotation (as well as any other
annotation) does not allow expressions, but only data literals. So, while
we can express the interval's lower bound with @Min(
value=1459)
, we need another solution for expressing the upper
bound.
Fortunately, 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 the following steps:
Create the annotation interface UpToNextYear
with
the following code:
@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 {}; }
The interface needs to define three methods,
message
(returns the default key or error message if
the constraint is violated), groups
(allows the
specification of validation groups, to which this constraint
belongs) and payload
(used by clients of the Bean
Validation API to assign custom payload objects to a constraint -
this attribute is not used by the API itself). Notice the
@Target
annotation, which defines the element types
that can be annotated (fields/properties and methods in our case).
The @Constraint
annotation allows to specify the
implementation class that will perform the validation, i.e.
UpToNextYearImpl
in our case.
Create an implementation class with the validation code:
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 type parameters: the annotation interface defined before (i.e.
UpToNextYear
), and the type of elements the validator can handle (i.e.
Integer
, so implicitly also the compatible primitive type int
). The
initialize
method allows initializing variables required for performing the
validation check. The isValid
method is responsible for performing the
validation: it must return true
if the validation succeeds, and
false
otherwise. The first parameter of the isValid
method
represents the value to be validated and its type must be compatible with the type defined by
the second type parameter of the ConstraintValidator
(Integer
in our
case).
Annotate the property or method concerned:
@Entity
@Table( name = "books")
public class Book {
// ...
@Min( value = 1459)
@UpToNextYear
private Integer year;
//...
}