Bean Validation 2.0

Introduzione

Una delle attività più tediose ma ineluttabile nella realizzazione di applicazioni è la validazione dei dati di input. In questo articolo affrontiamo una soluzione standardizzata che utilizza framework conformi alla specifica JSR 380 altrimenti nota come Bean Validation 2.0. JSR 380 è parte della specifica JavaEE e introduce un insieme di annotazioni che consentono di specificare i criteri che le proprietà di un bean devono soddisfare per la validazione dei dati.

Dipendenze

Per descrivere le principali annotazioni della specifica utilizzeremo un progetto Maven al quale dovremo aggiungere le necessarie dipendenze. Innanzitutto dobbiamo introdurre la dipendenza al jar che definisce le API della specifica JSR 380.

L’implementazione di riferimento della specifica è invece quella fornita da Hibernate attraverso il pacchetto hibernate-validatior, che è interamente separato ed indipendente dalle componenti di persistenza di Hibernate.

JSR 380 inoltre fornisce il supporto all’utilizzo delle espressioni all’interno dei messaggi di validazione. Per il parsing di tali espressioni, è necessario aggiungere le dipendenza sia dall’Expression Language API che alla sua implementazione di riferimento GlassFish.

Validazione

Molti dei principali framework java come Spring, JSF, Hibernate, Struts, etc., forniscono direttamente il supporto alle annotazioni della specifica JSR380 ed eseguono automaticamente la validazione dei bean trattati all’interno dei rispettivi processi. In generale quindi  difficilmente capiterà di dover avviare la validazione manualmente. Per gli scopi dell’articolo, però, abbiamo necessità di utilizzare  un approccio diretto. Per farlo dobbiamo innanzitutto ottenere un oggetto Validator attraverso la classe factory Validatorfactory, e procedere poi alla validazione del bean utilizzando il metodo validate() di tale classe. Il metodo, in caso di violazioni, restituirà un set di oggetti di tipo ConstraintViolation.

Annotazioni

Come anticipato nell’introduzione i criteri di validazione sono espressi attraverso specifiche annotazioni con cui decorare le proprietà o i metodi di un bean. Tutte le annotazioni definite nella specifica, oltre a proprietà che sono funzionali al tipo di vincolo che esprimono, sono caratterizzate dalle tre seguenti proprietà:

message Stringa utilizzata per l’identificazione del messaggio di errore che è restituito in caso di violazione del vincolo. Tutti i tag sono caratterizzati da messaggi di default nella forma {javax.validation.constraints.[NAME].message} che possono essere modificati utilizzando il Resource Bundle.
 groups Questa proprietà può essere utilizzata per specificare l’ordine di validazione delle proprietà o per eseguire validazioni parziali del bean. Per farlo al metodo validate(), oltre al bean, può essere passato in input:

  1. un gruppo: e quindi limitare la validazione alle sole proprietà caratterizzate dal gruppo specificato;
  2. più gruppi: definendo così un ordine di validazione.

Se non specificato assume il valore di default javax.validation.groups.Default.

 payload  Tale proprietà è utilizzata per portare informazioni specifiche associate all’errore verso il client. Non si tratta quindi di una proprietà utilizzata dal framework ma implementa un meccanismo per arricchire la descrizione dell’errore con informazioni aggiuntive. Ad esempio è possibile definire una severity  level che sarà poi utilizzata dalle maschere di input per visualizzare il messaggio con colori diversi.

Vediamo ora come esempio il bean Usere descriviamo alcune delle annotazioni principali di JSR 380:

Il significato delle annotazioni menzionate nel bean è sufficientemente intuitivo:

  • @NotNull: specifica che la proprietà annotata non può essere NULL;
  • @AssertTrue: specifica che la proprietà annotata deve assumere il valore TRUE;
  • @Size: applicata ai tipi String, Collenction, Map e array, specifica che il valore della proprietà annotata deve avere lunghezza tra min and max;
  • @Min: indica che la proprietà annotata non può assumere un valore inferiore alla proprietà value;
  • @Max: indica che la proprietà annotata non può assumere un valore superiore alla proprietà value;
  • @Email: specifica che il valore della proprietà annotata deve essere una email di formato valido;
  • @Past: indica che il valore della proprietà annotata deve essere una data nel passato;
  • @Positive: specifica che il valore della proprietà annotata deve essere positivo.

Generiamo ora un bean di tipo User in modo da attivare tutte le violazioni:

Come risultato saranno stampate sullo standard error i seguenti messaggi:

Altre Annotazioni

Altre annotazioni utili alla validazione dei bean sono:

  • @NotEmpty: applicata ai tipi String, Collection, Map e Array indica che la proprietà non può essere NULL o vuota (empty).
  • @NotBlank: applicata alle stringhe indica che il valore non può essere NULL o composto da soli whitespace.
  • @PositiveOrZero: applicato a tipi numerici indica che che il valore deve essere positivo o zero.
  • @Negative and @NegativeOrZero: applicato a tipi numerici indicano che il valore deve essere strettamente negativo oppure negativo o uguale a zero.
  • @PastOrPresent: indica un vincolo sulle date che devono riferirsi al passato o al presente.
  • @Future and @FutureOrPresent: indica un vincolo sulle date che devono riferirsi al futuro oppure al futuro incluso il presente.

Raggruppamento

Abbiamo anticipato precedentemente che tutte le annotazioni hanno la proprietà comune groups, che è utilizzabile per modificare l’ordine di validazione delle proprietà o per eseguire validazioni parziali del bean. Supponiamo ad esempio di voler realizzare la validazione dei campi del bean User esclusivamente ai fini di identificare utenti che posseggono una licenza di guida. Per farlo introduciamo l’interfaccia DriverChecks ed associamola alle proprietà di interesse ovvero:

A questo punto possiamo validare il bean limitatamente alle proprietà che identificano un utente con licenza di guida passando l’interfaccia DriverChecks al metodo di validazione, nel modo seguente:

In generale non è specificato alcun ordine di validazione dei campi che compongono il bean. Nei casi in cui l’ordine sia importante è possibile utilizzare la proprietà groups e definire una interfaccia annotata con @GroupSequence. Ad esempio supponiamo di voler validare prima i campi di default (si ricordi che dove non specificato all’annotazione è associato il gruppo Default) e poi quelli associati a DriverChecks. A tale scopo definiamo l’interfaccia:

quindi validiamo il bean specificando tale interfaccia:

La validazione procederà inizialmente verificando i campi associati al gruppo di default e nel caso di errore terminerà immediatamente senza verificare i vincoli associati al gruppo DriverChecks.

Personalizzazione dei Messaggi

Il messaggio di errore associato al vincolo è configurabile attraverso la proprietà message. In particolare abbiamo visto nell’esempio del bean User come modificarne il valore che per default è {javax.validation.constraints.[NAME].message}. Inserire il messaggio direttamente nelle annotazioni in generale non è auspicabile, soprattutto se vogliamo supportare l’internazionalizzazione dell’applicazione.

E’ preferibile invece creare un file ValidationMessages.properties o ValidationMessages_[locale].properties che è utilizzato dal Resource Bundle ValidationMessages per l’interpolazione dei messaggi. La personalizzazione del messaggio può avvenire in due modi:

  1. Ridefinendo la proprietà javax.validation.constraints.[NAME].message ma questo implica che il messaggio sarà associato a tutti campi annotati con [NAME].
  2. Definendo uno nuova messaggio ed associandone la chiave alla proprietà message.

Ad esempio possiamo ridefinire il messaggio di default associato all’annotazione @Past e definire una nuovo messaggio da associare alla proprietà rating del bean User inserendoli nel file ValidationMessages_it_IT.properties da collocare nella cartella resources del progetto:

naturalmente l’annotazione associata alla proprietà rating dovrà essere così modificata:

Nella definizione del messaggio di errore è anche possibile utilizzare espressioni che saranno valutate prima della costruzione dello stesso. In particolare nel contesto EL il validatore inserirà i seguenti valori utilizzabili nell’espressione:

  • le proprietà definite nell’annotazione (es. value dell’annotazione @Min);
  • il valore assegnato alla proprietà identificato dalla chiave validatedValue.

Un esempio di messaggio associato alla proprietà age del bena User potrebbe essere così definito:

generando un messaggio di errore del tipo:

Codice Sorgente

Il codice sorgente per tutti gli esempi presentati è scaricabile qui bean-validation.