4.2.13.1. Задание ограничений

Ограничения bean validation задаются с помощью аннотаций пакета javax.validation.constraints или собственных аннотаций. Аннотации указываются на декларации класса сущности или POJO, на поле или getter-методе, а также на методе сервиса middleware.

Пример использования стандартных аннотаций валидации на полях сущности:

@Table(name = "DEMO_CUSTOMER")
@Entity(name = "demo$Customer")
public class Customer extends StandardEntity {

    @Size(min = 3) // length of value must be longer then 3 characters
    @Column(name = "NAME", nullable = false)
    protected String name;

    @Min(1) // minimum value
    @Max(5) // maximum value
    @Column(name = "GRADE", nullable = false)
    protected Integer grade;

    @Pattern(regexp = "\\S+@\\S+") // value must conform to the pattern
    @Column(name = "EMAIL")
    protected String email;

    //...
}

Пример использования собственной аннотации уровня класса (см. ниже):

@CheckTaskFeasibility(groups = {Default.class, UiCrossFieldChecks.class}) // custom validation annotation
@Table(name = "DEMO_TASK")
@Entity(name = "demo$Task")
public class Task extends StandardEntity {
    //...
}

Пример валидации параметров и возвращаемого значения метода сервиса:

public interface TaskService {
    String NAME = "demo_TaskService";

    @Validated // indicates that the method should be validated
    @NotNull
    String completeTask(@Size(min = 5) String comment, @NotNull Task task);
}
Группы ограничений

Группы ограничений позволяют применять подмножество всех заданных ограничений в зависисмости от логики приложения. Например, вы можете заставить пользователя ввести значение некоторого атрибута сущности в UI, и а то же время иметь возможность установить данный атрибут в null в некотором внутреннем механизме. Для этого необходимо указать атрибут groups в аннотации ограничения, и оно будет действовать только когда эта же группа передается в механизм валидации.

Платформа передает в механизм валидации следующие группы ограничений:

  • RestApiChecks - при валидации в REST API.

  • ServiceParametersChecks - при валидации параметров сервисов.

  • ServiceResultChecks - при валидации возвращаемых значений сервисов.

  • UiComponentChecks - при валидации отдельных полей в UI.

  • UiCrossFieldChecks - при валидации ограничений уровня класса на коммите экрана редактора сущности.

  • javax.validation.groups.Default - данная группа передается во всех случаях кроме коммита экрана редактора сущности.

Сообщения валидации

Ограничения могут иметь сообщения для отображения пользователям.

Сообщения могут быть указаны непосредственно в аннотациях валидации, например:

@Pattern(regexp = "\\S+@\\S+", message = "Invalid format")
@Column(name = "EMAIL")
protected String email;

Сообщения можно также поместить в пакет локализованных сообщений и использовать следующий формат указания сообщения в аннотации: {msg://message_pack/message_key}. Например:

@Pattern(regexp = "\\S+@\\S+", message = "{msg://com.company.demo.entity/Customer.email.validationMsg}")
@Column(name = "EMAIL")
protected String email;

Сообщения могут содержать параметры и выражения. Параметры заключаются в фигурные скобки {} и представляют собой либо указатели на локализованные сообщения (см. выше) или параметры аннотации, например {min}, {max}, {value}. Выражения заключаются в фигурные скобки со знаком доллара ${} и могут включать валидируемое значение в виде переменной validatedValue, параметры аннотации типа value или min, и выражения JSR-341 (EL 3.0). Например:

@Pattern(regexp = "\\S+@\\S+", message = "Invalid email: ${validatedValue}, pattern: {regexp}")
@Column(name = "EMAIL")
protected String email;

Значения локализованных сообщений также могут содержать параметры и выражения.

Собственные ограничения

В проекте можно создать собственные ограничения с программной или декларативной валидацией.

Для создания ограничения с программной валидацией выполниет следующее:

  1. Создайте аннотацию в модуле global проекта и добавьте ей аннотацию @Constraint. Ваша аннотация должна содержать атрибуты message, groups и payload:

    @Target({ ElementType.TYPE })
    @Retention(RUNTIME)
    @Constraint(validatedBy = TaskFeasibilityValidator.class)
    public @interface CheckTaskFeasibility {
    
        String message() default "{msg://com.company.demo.entity/CheckTaskFeasibility.message}";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    }
  2. Создайте класс валидатора в модуле global проекта:

    public class TaskFeasibilityValidator implements ConstraintValidator<CheckTaskFeasibility, Task> {
    
        @Override
        public void initialize(CheckTaskFeasibility constraintAnnotation) {
        }
    
        @Override
        public boolean isValid(Task value, ConstraintValidatorContext context) {
            Date now = AppBeans.get(TimeSource.class).currentTimestamp();
            return !(value.getDueDate().before(DateUtils.addDays(now, 3)) && value.getProgress() < 90);
        }
    }
  3. Используйте аннотацию:

    @CheckTaskFeasibility(groups = UiCrossFieldChecks.class)
    @Table(name = "DEMO_TASK")
    @Entity(name = "demo$Task")
    public class Task extends StandardEntity {
    
        @Future
        @Temporal(TemporalType.DATE)
        @Column(name = "DUE_DATE")
        protected Date dueDate;
    
        @Min(0)
        @Max(100)
        @Column(name = "PROGRESS", nullable = false)
        protected Integer progress;
    
        //...
    }

Собственные аннотации могут также быть созданы как композиции имеющихся, например:

@NotNull
@Size(min = 2, max = 14)
@Pattern(regexp = "\\d+")
@Target({METHOD, FIELD, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = {})
public @interface ValidProductCode {
    String message() default "{msg://om.company.demo.entity/ValidProductCode.message}";

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

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

При использовании композитных ограничений результирующий набор нарушений ConstraintViolation будет содержать отдельные записи для каждого включенного ограничения. Для того, чтобы получить одну запись нарушения, добавьте @ReportAsSingleViolation классу вашей аннотации.