Friday, December 9, 2016

Creating a Constraint Validator

In Bean Validation, constraints can inherit from one another. Of course, this is not the same thing as one class inheriting from another because you cannot extend annotations. However, per convention, constraint annotations usually include a target of ElementType.ANNOTATION_TYPE.
When a constraint annotation is located, the Validator determines if the annotation definition is annotated with any other constraints. If so, it combines all the additional constraints with the logic defined by the original constraint (if any) into a single, composite constraint. In this sense, the constraint inherits all the constraints with which it is annotated. If for some reason you need to create a constraint that cannot be inherited, you simply omit ElementType.ANNOTATION_TYPE from the definition. With all this in mind, take a look at the @Email definition.

@Target(
{ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {})
@Pattern(regexp = "^[a-z0-9`!#$%^&*'{}?/+=|_~-]+(\\.[a-z0-9`!#$%^&*'{}?/+=|"
+ "_~-]+)*@([a-z0-9]([a-z0-9-]*[a-z0-9])?)+(\\.[a-z0-9]" + "([a-z0-9-]*[a-z0-9])?)*$", flags =
{ Pattern.Flag.CASE_INSENSITIVE })
@ReportAsSingleViolation
public @interface Email
{
String message() default "{ml.javasopenblog.site.validation.Email.message}";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

@Target(
{ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR,
ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Documented
static @interface List
{
Email[] value();
}
}

There’s a lot going on here, so take a look at it line by line, starting with the annotations:

@Target : This annotation indicates which language features this annotation can be placed on. The values listed are pretty standard and should be used for most constraints.

@Retention : Indicates that the annotation must be retained at run time. If not, Bean Validation will not detect it.

@Documented : This means that the Javadoc of targets marked with this annotation should indicate the annotation’s presence. This is especially useful when programming in an IDE because it makes the contract more visible.

@Constraint : This is a must: It’s what indicates that this annotation represents a Bean Validation constraint, so all constraint definitions have to be annotated with this. Without this, your constraint is ignored. @Constraint also indicates which ConstraintValidator implementation or implementations are responsible for validating your constraint. However, in this case no ConstraintValidator is necessary.

@Pattern : This is another constraint, indicating that this constraint inherits the constraint declared with @Pattern. This is the same regular expression seen earlier, but now you won’t have to duplicate the regular expression every time you use it. You can just use the @Email annotation, instead.

@ReportAsSingleViolation : Indicates that the composite constraint should be considered one constraint and use @Email’s message instead of @Pattern’s message. It is very rare that you should ever create a constraint that inherits other constraints without using @ReportAsSingleViolation.

Within the annotation are three attributes: messagegroups, and payload. These are the standard attributes that must be present in all constraints. Without one or more of these, use of @Email would result in a ConstraintDefinitionException. The @Email.List inner annotation, like all the bean validation list annotations, defines a way to specify multiple @Email constraints on a target.


The @NotBlank constraint looks nearly identical to @Email. For the most part, it has the same annotations, attributes, and features. Instead of being annotated with @Pattern, it’s annotated with @NotNull. In this case @NotBlank should imply non-null, so you inherit the @NotNull constraint to accomplish this. (If you anticipate needing to define targets that can be null but cannot be blank strings, you would simply remove @NotNull from this annotation.)

@Target(
{ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy =
{ NotBlankValidator.class })
@NotNull
@ReportAsSingleViolation
public @interface NotBlank
{
String message() default "{ml.javasopenblog.site.validation.NotBlank.message}";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

@Target(
{ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR,
ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Documented
static @interface List
{
NotBlank[] value();
}
}

However, unlike @Email, it can’t inherit all its functionality. It needs a ConstraintValidator to test whether the value is blank. The following NotBlankValidator class, declared in the @Constraint annotation on the @NotBlank annotation, accomplishes this.

public class NotBlankValidator implements ConstraintValidator<NotBlank, CharSequence>
{
@Override
public void initialize(NotBlank annotation)
{

}

@Override
public boolean isValid(CharSequence value, ConstraintValidatorContext context)
{
if (value instanceof String)
return ((String) value).trim().length() > 0;
return value.toString().trim().length() > 0;
}
}

No comments:

Post a Comment