Выбрать главу
Листинг 3.7. Ограничивающая аннотация для работы с URL

@Constraint(validatedBy = {URLValidator.class})

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})

@Retention(RUNTIME)

public @interface URL {

··String message() default "Malformed URL";

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

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

··String protocol() default "";

··String host() default "";

··int port() default -1;

}

В листинге 3.7 мы могли бы агрегировать уже имеющиеся ограничения, например @NotNull. Но основное различие между объединением ограничений и созданием обобщенного ограничения заключается в применении класса реализации, объявляемого в атрибуте validatedBy (здесь он ссылается на класс URLValidator.class).

В листинге 3.8 показан класс реализации URLValidator. Как видите, он реализует интерфейс ConstraintValidator и, следовательно, методы initialize и isValid. Здесь важно отметить, что класс URLValidator имеет три атрибута, определенные в аннотации (protocol, host и port), и инициализирует их в методе initialize(URL url). Этот метод вызывается на этапе инстанцирования валидатора. В качестве параметра он получает ограничивающую аннотацию (здесь URL), поэтому может извлекать значения и использовать их при валидации. Так можно поступить с атрибутом itemURL protocol, который в листинге 3.6 имеет строковое значение "http".

Листинг 3.8. Реализация ограничения для работы с URL

public class URLValidator implements ConstraintValidator<URL, String> {

··private String protocol;

··private String host;

··private int port;

··public void initialize(URL url) {

····this.protocol = url.protocol();

····this.host = url.host();

····this.port = url.port();

··}

··public boolean isValid(String value, ConstraintValidatorContext context) {

····if (value == null || value.length() == 0) {

····return true;

··}

··java.net.URL url;

··try {

····// Преобразуем URL в java.net.URL для проверки того,

····// имеет ли URL допустимый формат

····url = new java.net.URL(value);

··} catch (MalformedURLException e) {

····return false;

··}

··// Проверяет, имеет ли атрибут протокола допустимое значение

··if (protocol!= null && protocol.length() > 0 && 

!url.getProtocol(). equals(protocol)) {

····return false;

··}

··if (host!= null && host.length() > 0 &&!url.getHost(). startsWith(host)) {

····return false;

··}

··if (port!= -1 && url.getPort()!= port) {

····return false;

··}

··return true;

··}

}

Метод isValid реализует алгоритм валидации URL, показанный в листинге 3.8. Параметр value содержит значение объекта, который требуется валидировать (например, file://www.cdbookstore.com/item/123). Параметр context инкапсулирует информацию о контексте, в котором осуществляется валидация (подробнее об этом ниже). Возвращаемое значение является логическим и указывает, успешно ли прошла валидация.

Основная задача валидационного алгоритма в листинге 3.8 — привести переданное значение к java.net.URL и проверить, правильно ли оформлен URL. После этого алгоритм также проверяет валидность атрибутов protocol, host и port. Если хотя бы один из них окажется невалидным, то метод вернет false. Как будет показано далее в разделе «Валидация ограничений», поставщик валидации компонентов задействует это логическое значение при создании списка ConstraintViolation.

Обратите внимание: метод isValid расценивает нуль как валидное значение (if (value == null… return true)). Спецификация Bean Validation считает такую практику рекомендуемой. Так удается не дублировать код ограничения @NotNull. Пришлось бы одновременно использовать ограничения @URL и @NotNull, чтобы указать, что вы хотите представить валидный ненулевой URL (такой как атрибут itemURL в листинге 3.6).

Сигнатура класса определяет тип данных, с которым ассоциируется ограничение. В листинге 3.8 URLValidator реализован для типа String (ConstraintValidator<URL, String>). Это означает, что если вы примените ограничение @URL к другому типу (например, к атрибуту lastConnectionDate), то получите при валидации исключение javax.validation.UnexpectedTypeException, так как не будет найден подходящий валидатор для типа java.util.Date. Если вам требуется ограничение, которое будет применяться сразу к нескольким типам данных, то необходимо либо использовать суперклассы, когда это возможно (скажем, можно было бы определить URLValidator для CharSequence, а не для строки, выразив его так: ConstraintValidator<URL, CharSequence>), либо применить несколько классов реализации (по одному для String, CharBuffer, StringBuffer, StringBuilder) в случае иного валидационного алгоритма.