Ограничение @ZipCode написать сложнее, чем @Email. ZIP-код имеет определенный формат (например, в США он состоит из пяти цифр), который не составляет труда проверить с помощью регулярного выражения. Но чтобы гарантировать, что ZIP-код не только синтаксически верен, но и валиден, необходимо прибегнуть к внешней службе, которая будет проверять, существует ли конкретный ZIP-код в базе данных. Именно поэтому ограничивающая аннотация ZipCode в листинге 3.25 требует класса реализации (ZipCodeValidator).
@Constraint(validatedBy = ZipCodeValidator.class)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
public @interface ZipCode {
··String message() default "{org.agoncal.book.javaee7.chapter03.ZipCode.message}";
··Class >[] groups() default {};
··Class extends Payload>[] payload() default {};
··@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
··@Retention(RUNTIME)
··@interface List {
····ZipCode[] value();
··}
}
В листинге 3.26 показан класс реализации ограничивающей аннотации: ZipCodeValidator реализует интерфейс javax.validation.ConstraintValidator с обобщенным типом String. Метод isValid реализует алгоритм валидации, в рамках которого выполняется сопоставление с шаблоном регулярного выражения и происходит вызов внешнего сервиса: ZipCodeChecker. Код ZipCodeChecker здесь не показан, так как в данном случае он неважен. Но необходимо отметить, что он внедряется (@Inject) с квалификатором CDI (@USA, показан в листинге 3.27). Итак, здесь мы наблюдаем взаимодействие спецификаций CDI и Bean Validation.
public class ZipCodeValidator implements ConstraintValidator
··@Inject @USA
··private ZipCodeChecker checker;
··private Pattern zipPattern = Pattern.compile("\\d{5}(-\\d{5})?");
··public void initialize(ZipCode zipCode) {
··}
··public boolean isValid(String value, ConstraintValidatorContext context) {
····if (value == null)
······return true;
····Matcher m = zipPattern.matcher(value);
····if (!m.matches())
····return false;
····return checker.isZipCodeValid(value);
··}
}
@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD})
public @interface USA {
}
В следующих главах будет показано, как интегрировать валидацию компонентов с другими спецификациями, в частности JPA (можно добавлять ограничения к вашим сущностям) или JSF (можно ограничивать базовые компоненты).
Написание интеграционных тестов CustomerIT и AddressIT
Как мы теперь можем протестировать ограничения, налагаемые на наши компоненты? Мы ведь не можем писать модульные тесты для @Email, так как это аннотация, агрегирующая ограничения, равно как и для @ZipCode, которое может работать только для внедрения (а это контейнерная служба). Проще всего будет написать интеграционный тест, то есть использовать фабрику ValidatorFactory для получения Validator, а потом валидировать наши компоненты.
В листинге 3.28 показан класс CustomerIT, выполняющий интеграционное тестирование компонента Customer. Метод инициализирует Validator (с помощью ValidatorFactory), а метод close() высвобождает фабрику. Класс далее содержит два теста: в одном создается валидный объект Customer, а в другом создается объект с недопустимым адресом электронной почты и проверяется, окончится ли валидация ошибкой.
public class CustomerIT {
··private static ValidatorFactory vf;
··private static Validator validator;