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;
//...
}