5.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} или, в сокращённом виде, {msg://message_key} (только для сущностей). Например:

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

или, если ограничение задано для сущности и сообщение находится в пакете сообщений данной сущности:

@Pattern(regexp = "\\S+@\\S+", message = "{msg://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 классу вашей аннотации.

Аннотации валидации, заданные в CUBA

Кроме стандартных аннотаций из пакета javax.validation.constraints можно использовать следующую аннотацию, определенную в платформе:

  • @RequiredView - может быть добавлена на методы сервисов для того, чтобы во время выполнения убедиться, что в сущностях загружены все атрибуты, заданные в требуемых представлениях. Если аннотация задана на методе, то проверяется результат метода. Если аннотация задана на параметре, проверяется этот параметр. Если параметр или результат являются коллекцией, то проверяются все элементы этой коллекции. Пример использования:

public interface MyService {
    String NAME = "sample_MyService";

    @Validated
    void processFoo(@RequiredView("foo-view") Foo foo);

    @Validated
    void processFooList(@RequiredView("foo-view") List<Foo> fooList);

    @Validated
    @RequiredView("bar-view")
    Bar loadBar(@RequiredView("foo-view") Foo foo);
}