Выбрать главу
Листинг 3.25. Ограничивающая аннотация @ZipCode

@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.

Листинг 3.26. Реализация ограничения ZipCodeValidator

public class ZipCodeValidator implements ConstraintValidator<ZipCode, String> {

··@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);

··}

}

Листинг 3.27. Квалификатор USACDI

@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, а в другом создается объект с недопустимым адресом электронной почты и проверяется, окончится ли валидация ошибкой.

Листинг 3.28. Интеграционный тест CustomerIT

public class CustomerIT {

··private static ValidatorFactory vf;

··private static Validator validator;

··@BeforeClass

··public static void init() {

····vf = Validation.buildDefaultValidatorFactory();

····validator = vf.getValidator();

··}

··@AfterClass

··public static void close() {

····vf.close();

··}

··@Test

··public void shouldRaiseNoConstraintViolation() {

····Customer customer = new Customer("John", "Smith", "jsmith@gmail.com");

····Set<ConstraintViolation<Customer>> violations = validator.validate(customer);

····assertEquals(0, violations.size());

··}

··@Test

··public void shouldRaiseConstraintViolationCauseInvalidEmail() {

····Customer customer = new Customer("Джон", "Смит", "DummyEmail");

····Set<ConstraintViolation<Customer>> violations = validator.validate(customer);

····assertEquals(1, violations.size());

····assertEquals("invalid email address", violations.iterator(). next(). getMessage());

····assertEquals("dummy", violations.iterator(). next(). getInvalidValue());

····assertEquals("{org.agoncal.book.javaee7.chapter03.Email.message}",

·················violations.iterator(). next(). getMessageTemplate());

··}

}

Листинг 3.29 построен по такому же принципу (Validator создается с помощью фабрики, происходит валидация компонента, фабрика закрывается), но проверяет компонент Address.