BEAN VALIDATION Waldemar Korłub Narzędzia i aplikacje Java EE KASK ETI Politechnika Gdańska
Bean Validation Uniwersalny mechanizm walidacji danych we wszystkich warstwach aplikacji Warstwa interfejsu, np. JSF Warstwa biznesowa, np. EJB, CDI Warstwa dostępu do danych, np. JPA Jednorazowo definiowane walidatory i reguły walidacji do wykorzystania we wszystkich warstwach Nie ma potrzeby duplikowania logiki walidującej
Standardowe walidatory Adnotacja Typ pola Opis @NotNull Dowolny Wartość inna niż null @Null Dowolny Wartość równa null @Size( Ciągi znaków, Rozmiar w przedziale [X, Y] min=x, max=y) kolekcje @Pattern(regex=X) Ciągi znaków Ciąg znaków pasuje do wyrażenia regularnego @Min(value=X) Liczby Wartość liczbowa minimum X @Max(value=X) Liczby Wartość liczbowa maksimum X @Future Daty Data w przyszłości @Past Daty Data w przeszłości @AssertTrue Boolean Flaga ma wartość true @AssertFalse Boolean Flaga ma wartość false
Dodatkowe walidatory: Biblioteka Hibernate Validator Adnotacja Typ pola Opis @NotEmpty Ciągi znaków, kolekcje Element nie zawiera wartości null i nie jest pusty @NotBlank Ciągi znaków Element nie zawiera wartości null i nie jest pusty, ignoruje białe znaki na końcu wartości @Range(min=X, max=y) @Length(min=X, max=y) Liczby Ciągi znaków Liczba leży w podanym zakresie (łączy @Min i @Max w jednej adnotacji) Długość ciągu znaków mieści się w przedziale [X, Y]
Dodatkowe walidatory: Biblioteka Hibernate Validator Adnotacja Typ pola Opis @Email Ciągi znaków Ciąg znaków zawiera poprawny adres e-mail @URL Ciągi znaków Ciąg znaków zawiera poprawny adres URL @CreditCardNumber Ciągi znaków Ciąg znaków zawiera poprawny numer karty kredytowej @SafeHtml Ciągi znaków Ciąg znaków zawiera bezpieczny z punktu widzenia ataków XSS kod HTML
Przykłady użycia @Entity public class Book { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @NotBlank private String title; @Past @Temporal(TemporalType.TIMESTAMP) private Date publishdate; @Size(min = 1) @ManyToMany private List<Author> authors = new ArrayList<>();
Własny walidator Definicja własnego walidatora obejmuje dwa elementy: Klasę z interfejsem ConstraintValidator n Zawiera logikę weryfikującą poprawność przekazywanej wartości Adnotację znakującą n Pozwala wskazać pola mające podlegać walidacji n Opatrzona adnotacją @Constraint n Może posiadać dodatkowe parametry do sprecyzowania reguł walidacji
Adnotacja znakująca @Target({ElementType.FIELD, ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = InPastValidator.class) @Documented public @interface InPast { String message() default "{pl.gda.pg.eti.kask.inpast.in_past"; Class<?>[] groups() default {; Class<? extends Payload>[] payload() default {; String date() default "";
Meta-adnotacje Semantykę adnotacji definiuje się za pomocą jakże by inaczej innych adnotacji @Target określa jakie elementy mogą być oznaczane adnotacją @Retention określa jak długo będzie dostępna informacja o obecności adnotacji: RUNTIME w czasie działania aplikacji CLASS w skompilowanych klasach SOURCE tylko w kodzie źródłowym @Documented wystąpienia adnotacji zostaną udokumentowane w klasach, które ich używają
Meta-adnotacja @Constraint Adnotacje opatrzone meta-adnotacją @Constraint są rozpoznawane jako adnotacje znakujące walidatorów Atrybut validatedby określa klasę z logiką walidatora Adnotacja znakująca musi zawierać pola: message komunikat informujący o błędzie walidacji n Może odnosić się do klucza wielojęzycznego zasobu (ValidationMessages.properties w domyślnym pakiecie) groups lista grup, w których reguła walidacji jest aktywna payload dodatkowa informacja do wykorzystania przez dewelopera (nie jest używana przez Bean Validation API)
Klasa walidująca public class InPastValidator implements ConstraintValidator<InPast, Date> { public static final String DATE_PATTERN = "yyyy-mm-dd"; private Date date = new Date(); @Override public void initialize(inpast constraintannotation) { if (!constraintannotation.date().equals("")) { try { date = new SimpleDateFormat(DATE_PATTERN).parse(constraintAnnotation.date()); catch (ParseException ex) { @Override public boolean isvalid(date value, ConstraintValidatorContext context) { return date.after(value);
Przykłady użycia @Entity public class Book { //... @InPast(date = "2013-10-19") @Temporal(TemporalType.TIMESTAMP) private Date publishdate;
Walidacja powiązanych pól W wielu sytuacjach obiekty posiadają pola o powiązanych ze sobą wartościach, np.: Nowe hasło oraz powtórz nowe hasło (w formularzu rejestracji/zmiany hasła) Miasto oraz kod pocztowy (w formularzu zamówienia) Walidacja wymaga dostępu do wartości wszystkich powiązanych pól Bean Validation umożliwia zadeklarowanie walidatora na poziomie klasy Z dostępem do wszystkich składowych obiektów tej klasy
Przykład: formularz rejestracji public class RegistrationForm { @NotBlank String login; @NotBlank String password; @NotBlank String passwordrepeat;
Adnotacja znakująca @Target({TYPE) @Retention(RUNTIME) @Constraint(validatedBy = RepeatPasswordValidator.class) @Documented public @interface RepeatPassword { String message() default "Hasła muszą być takie same"; Class<?>[] groups() default {; Class<? extends Payload>[] payload() default {;
Klasa walidująca public class RepeatPasswordValidator implements ConstraintValidator<RepeatPassword, RegistrationForm> { @Override public void initialize(repeatpassword constraintannotation) { @Override public boolean isvalid(registrationform value, ConstraintValidatorContext context) { return value.getpassword().equals(value.getpasswordrepeat());
Wykorzystanie walidatora @RepeatPassword public class RegistrationForm { @NotBlank String login; @NotBlank String password; @NotBlank String passwordrepeat;
Grupowanie reguł walidacji Ten sam obiekt może wymagać innych reguł walidacji na różnych etapach przetwarzania Dane wpisywane w formularzu przez użytkownika Pola o wartościach wyliczanych w warstwie logiki biznesowej Weryfikacja wszystkich pól przed zapisem do bazy danych Bean Validation pozwala na przypisywanie reguł walidacji do grup W czasie walidacji należy wskazać, która grupa ma zostać zweryfikowana
Grupowanie reguł walidacji Grupy deklarowane są jako interfejsy znakujące (interfejsy bez metod) z braku bardziej adekwatnego mechanizmu w języku Przykładowo: Grupa dla informacji wprowadzanych przez użytkownika w formularzu na stronie: public interface UserInput { Grupa dla informacji dodawanych przez wydawcę: public interface PublisherAddons {
Przypisywanie grup @Entity public class Book { @Id private Integer id; @NotBlank private String title; Grupa domyślna (Default.class) @Past(groups = PublisherAddons.class) @Temporal(TemporalType.TIMESTAMP) private Date publishdate; @Pattern(regexp = "...", groups = PublisherAddons.class) private String isbn; @Size(min = 1, groups = UserInput.class) @ManyToMany private List<Author> authors = new ArrayList<>();
Walidacja wybranych grup Obiekt klasy Validator sterowanie procesem walidacji: Validator validator = /*...*/; Tylko grupa domyślna: Set<ConstraintViolation<Book>> errors = validator.validate(book); Grupa domyślna oraz UserInput validator.validate(book, Default.class, UserInput.class); Tylko grupa PublisherAddons validator.validate(book, PublisherAddons.class); W widoku JSF: <f:validatebean validationgroups="pl.edu.pg.eti.userinput"/>
Pytania?