JSR-303 – przepis na fasolkę. Bardzo dobrą poniekąd. Cz. 1

post_img

Finalna wersja specyfikacji JSR-303: Bean Validation ukazała się światu pod koniec ubiegłego roku (2009). Jest to kolejny przysmak a’la carte z renomowanej kuchni Gavin’a King’a i spółki, który doczekał się formalnej specyfikacji. Zatem – delektujmy się przez chwilę…

Dla osób niewtajemniczonych wyjaśniam, że mam na myśli produkt hibernate-validator, który jest protoplastą specyfikacji.
Wraz z nią otrzymaliśmy jej implementację referencyjną w postaci produktu hibernate-validator w wersji 4.
Świadomy tego, że bezapelacyjnie każdego ta historia urzekła przejdę do konkretów.
Dlaczego w ogóle o tym piszę? istnieje co najmniej kilka powodów:

  1. Jest to nowy standard w ramach Javy.
  2. Będzie wchodził w skład JEE 6.
  3. Bardzo łatwo się go używa – również na platformie SAP NetWeaver. 🙂
  4. Posiada nowe rozbudowane API oraz nowe możliwości rozszerzeń (np. tzw. „composite constraints”).
  5. W sposób przezroczysty integruje się z już posiadanym kodem.

Ponieważ jest to artykuł subiektywny, nie zamierzam w nim wymieniać wszystkich właściwości oraz różnic w specyfikacji JSR 303 w stosunku do starego hibernate-validator’a. Ze szczegółami można zawsze zapoznać się pobierając specyfikację ze stron Java Community Process.

Moim zdaniem bardzo istotna jest rozbudowa specyfikacji o możliwość definiowania mechanizmów walidacji za pomocą deskryptorów XML. Być może niektórzy czytelnicy posądzą mnie tutaj o herezję, ponieważ dziesiątki deskryptorów dostępnych w JEE to swoiste „małe piekiełko”. W przypadku walidatora jest to bardzo dobry pomysł o czym świadczą silne argumenty:

  1. Deskryptory pozwalają na zastosowanie walidacji w beanach dostarczanych w postaci bibliotek,
  2. Definicje w deskryptorach uniezależniają walidowane komponenty od API walidatora oraz własnych walidatorów w formie rozszerzeń.

Poniżej przedstawiam przykład kodu z zastosowaniem adnotacji oraz odpowiednika z zastosowaniem deskryptora xml.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// Person.java
package test.beans;
import javax.validation.constraints.NotNull;
import javax.validation.Size;
import javax.validation.constraints.Past;
import java.util.List;
import java.util.Date;
import javax.validation.Valid;

public class Person {

    // wymuszenie wartości różnej niż null
    @NotNull(message="Imię nie może być puste")
    private String name;

    // wymuszenie wartości różnej niż null
    @NotNull(message="Nazwisko nie może być puste")
    private String surname;

    // wymuszamy datę przeszłą w stosunku do czasu systemowego
    @Past(message="Należy podać datę przeszłą")
    private Date birthDate;

    // oczekiwanie, że w kolekcji będzie co najmniej jeden element
    @Size(min=1,message="Należy podać co najmniej jeden adres")
    @Valid // wskazanie aby implementacja wykonała również walidację adresów
    private List<Address> adrList;
    (...)
}
----------------------------------------------------------
//  Address.java
package test.beans;
import javax.validation.constraints.NotNull;


public class Address {

    // wymuszenie wartości różnej niż null
    @NotNull(message="Miasto nie może być puste")
    private String city;

    (...)
}

Stan analogiczny do powyższego kodu możemy uzyskać za pomocą następujących definicji w deskryptorze xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="UTF-8"?>
<!-- sample-constraints.xml -->
<constraint-mappings
   xmlns="http://jboss.org/xml/ns/javax/validation/mapping"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/mapping validation-mapping-1.0.xsd">

    <default-package>test.beans</default-package>

    <bean class="Person" ignore-annotations="true">
        <field name="name">
            <constraint annotation="javax.validation.constraints.NotNull">
                <message>Imię nie może być puste</message>
            </constraint>
        </field>
        <field name="surname">
            <constraint annotation="javax.validation.constraints.NotNull">
                <message>Nazwisko nie może być puste</message>
            </constraint>
        </field>
        <field name="birthDate">
            <constraint annotation="javax.validation.constraints.Past">
                <message>Należy podać datę przeszłą</message>
            </constraint>
        </field>
        <field name="adrList">
            <constraint annotation="javax.validation.constraints.Size">
                <message>Należy podać co najmniej jeden adres</message>
                                <element name="min">1</element>
            </constraint>
            <valid/>
        </field>
    </bean>

    <bean class="Address" ignore-annotations="true">
        <field name="city">
            <constraint annotation="javax.validation.constraints.NotNull">
                <message>Miasto nie może być puste</message>
            </constraint>
        </field>
    </bean>

</constraint-mappings>

Nie jest trudno wywnioskować, że wszystkie ograniczenia zdefiniowane w kodzie źródłowym mają swoje odpowiedniki w deskryptorze. Aby powyższe przykłady uruchomić, konieczne jest jeszcze skonfigurowanie całego „silnika” walidacji.
Konfigurację realizuje się przez zamieszczenie w katalogu META-INF pliku o nazwie validation.xml, który jest deskryptorem konfiguracji JSR-303.
Oto przykład pliku:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<validation-config
 xmlns="http://jboss.org/xml/ns/javax/validation/configuration"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/configuration validation-configuration-1.0.xsd">

    <default-provider>org.hibernate.validator.HibernateValidator</default-provider>
    <constraint-mapping>META-INF/sample-constraints.xml</constraint-mapping>
</validation-config>

W powyższym pliku określono następujące właściwości silnika:

  1. Default-provider wskazuje dostawcę implementacji JSR-303, którym jest oczywiście hibernate validator
  2. Constraint-mapping wskazuje na dodatkowy deskryptor xml z definicją ograniczeń, można zdefiniować wiele plików z definicją ograniczeń

Na deser pozostał mi jeszcze prościutki przykład uruchomienia walidacji w kodzie z zastosowaniem poprzednich przykładów.
Oto on:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ValidationTest.java
package test.beans;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;

public class ValidationTest {

    public static void main(String[] argv) {
        ValidationTest vt = new ValidationTest();
        Person p = new Person();
        p.name= "John";
        t.validateBean(p);
        return;
    }

    public void validateBean(Person p) {
        ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
        Set<ConstraintViolation> violationSet = vf.getValidator().validate(p);
        for(ConstraintViolation cv : violationSet ) {
             System.out.println(cv.getMessage());
        }
        return;
   }

}

Smacznej walidacji 🙂

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *