Dopo aver visto come SWF realizza il mapping tra i dati di un modello e i campi di un form, vediamo ora come creare il meccanismo di validazione che consente di verificare il valore di ciascun campo inserito all’interno di un form. In questo articolo abbiamo visto come viene realizzata il binding dei dati e teniamolo come punto di partenza per iniziare a vedere il meccanismo della validazione.
Riprendiamo il codice sorgente del file di configurazione del flow e la pagina HTML all’interno della quale è presente il form:
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<var name="flight" class="it.devme.flight.Flight" />
<view-state id="start" view="flight/insertFlightInfo" model="flight">
<transition on="next" to="confirm" />
</view-state>
<view-state id="confirm" view="flight/showFlightInfo" />
</flow>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>DevMe - SWF Flight info test - </title>
</head>
<body>
<h1>Flight information </h1>
<p>Inserisci le informazioni sul volo:</p>
<form:form id="flightDetails" modelAttribute="flight">
<table>
<tr>
<td>Numero del volo: </td>
<td><form:input path="number"/></td>
</tr>
<tr>
<td>Ora di partenza: </td>
<td><form:input path="time"/></td>
</tr>
<tr>
<td>Gate: </td>
<td><form:input path="gate"/></td>
</tr>
<tr>
<td>Volo in ritardo: </td>
<td><form:radiobutton path="isDelayed" value="false" />
<form:radiobutton path="isDelayed" value="true" /></td>
</tr>
<tr>
<td><input type="submit" name="_eventId" value="next"></td>
</tr>
</table>
</form:form>
</body>
</html>
Bene. Come già ampiamente discusso, il flow definito dal file XML di sopra è molto semplice. Abbiamo una sola transizione da uno stato all’altro, ovvero si naviga da una pagina ad un altra pagina. Supponiamo ora, di voler validare i dati del form della pagina di partenza, e spostarsi nella pagina di arrivo, solo se non esistono errori di validazione secondo il nostro personale schema di validazione. Il nostro schema, prevede per semplicità che almeno uno dei tre campi presenti nel form sia indicato, e che se indicato il numero del volo questi dovrà essere un valore numero e eventuali caratteri alfabetici verranno rigettati.
Iniziamo con la modifica del file XML di definizione del flow:
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<var name="flight" class="it.devme.flight.Flight" />
<view-state id="start" view="flight/insertFlightInfo" model="flight">
<transition on="next" to="confirm" >
<action method="bindAndValidate" bean="flightAction" />
</transition>
</view-state>
<view-state id="confirm" view="flight/showFlightInfo" />
</flow>
Il metodo bindAndValidate in sostanza richiama la nostra classe validator, la quale effettua i controlli che andremo a definire. Da notare che il riferimento nel tag action, punta ad un metodo all’interno della nostra classe Action che in realtà non esiste, ma che viene ereditato dalla superclasse FormAction. Definendo all’interno del file XML di definizione del bean il riferimento alla nostra classe Validatore, autormaticamente verrà richiamata all’invocazione del metodo bindAndValidate. Il codice della classe che si occupa di effettuare la validazione è il seguente:
package it.devme.flight;
public class FlightValidator implements Validator {
public boolean supports(Class clazz) {
return clazz.equals(Flight.class);
}
public void validate(Object target, Errors errors) {
Flight flight = (Flight) target;
if (flight.getNumber()==0 && flight.getTime()==null && flight.getGate.equals("")) {
errors.rejectValue("gate", "Attenzione, è necessario specificare almeno 1 tra: numero del volo, ora di partenza, Gate");
}
if (errors.getErrorCount()==0) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "gate", "Il campo gate non può essere vuoto.");
}
}
}
Vediamo cosa è stato fatto. Intanto osserviamo che FlightValidator implementa la classe Validator, quindi definisce alcuni metodi essenziali che sono supports e validate. Il primo assicura il supporto al Bean indicato al suo interno. Il secondo implmenta la logica di validazione. Nel nostro caso vogliamo che almeno uno dei tre campi venga inserito, e che il campo Gate sia diverso da vuoto. Il primo controllo verifica la prima delle nostre condizioni; il secondo if, verifica prima che non ci siano stati errori in precedenza, e in caso positivo effettua il controllo di validazione sul campo Gate.
Vediamo ora come modifichiamo il file HTML per presentare il messaggio di errore:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>DevMe - SWF Flight info test - </title>
</head>
<body>
<h1>Flight information </h1>
<p>Inserisci le informazioni sul volo:</p>
<form:form id="flightDetails" modelAttribute="flight">
<table>
<tr>
<td>Numero del volo: </td>
<td><form:input path="number"/></td>
</tr>
<tr>
<td>Ora di partenza: </td>
<td><form:input path="time"/></td>
</tr>
<tr>
<td>Gate: </td>
<td><form:input path="gate"/></td>
<td><form:errors path="gate"/></td>
</tr>
<tr>
<td>Volo in ritardo: </td>
<td><form:radiobutton path="isDelayed" value="false" />
<form:radiobutton path="isDelayed" value="true" /></td>
</tr>
<tr>
<td><input type="submit" name="_eventId" value="next"></td>
</tr>
</table>
</form:form>
</body>
</html>
Per semplicità il nostro messaggio di errore comparirà sempre nello stesso posto, ovvero sotto il campo Gate. Così nel primo caso di errore, darà contezza del fatto che almeno uno dei campi di precedenti deve essere indicato, mentre nel secondo caso, sarà indicativo del campo stesso. Come vedete, l’aggiunta del tag <form:errors/> fa in modo di collegare eventuali codici di errori con il campo presentato in pagina.
Alla prossima. Stay tuned!
Nell’ultimo articolo su Spring Web Flow ho condensato la maggior parte dei concetti chiave sull’argomento. Mi piacerebbe ora tornare indietro su alcune cose cercando di porre maggiore attenzione. In questo articolo vedremo un pò più da vicino come avviene il binding dei dati in SWF, da un form HTML verso un bean.
In SWF esiste un controller, dello stesso genere di quelli utilizzati in Spring MVC (es. AbstractFormController), che permette di effettuare il binding dei parametri corrispondenti ai campi di un form HTML, quindi presente nella request HTTP verso un command object rappresentato da una semplicissima classe Java Bean. Ad una certa view è associato un certo model object sul quale verranno memorizzati i valori dei parametri del form e affinché avvenga in binding è sufficiente dichiarare l’attributo model dell’elemento <view-state>. L’esempio che segue mostra come deve essere definito il tutto nel contesto SWF affinché avvenga correttamente il binding.
Si comincia col definire la classe del nostro Java Bean, suppioniamo di voler memorizzare le informazioni riguardanti i voli in partenza da un certo aeroporto.
package it.devme.flight;
import java.io.Serializable;
import java.sql.Time;
public class Flight implements Serializable {
private static final long serialVersionUID = 1L;
private String number;
private Time time;
private String gate;
private boolean isDelayed;
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public Time getTime() {
return time;
}
public void setTime(Time time) {
this.time = time;
}
public String getGate() {
return gate;
}
public void setGate(String gate) {
this.gate = gate;
}
public boolean isDelayed() {
return isDelayed;
}
public void setDelayed(boolean isDelayed) {
this.isDelayed = isDelayed;
}
}
Fin qui niente di più semplice. Procediamo ora con il nostro flow. Il flow consiste di 2 view. La prima pagina raccoglie i dati che verranno memorizzati all’interno dei 4 attributi della classe Flight. Avanzando con il bottone next, la seconda pagina semplicemente mostrerà i dati raccolti.
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<var name="flight" class="it.devme.flight.Flight" />
<view-state id="start" view="flight/insertFlightInfo" model="flight">
<transition on="next" to="confirm" />
</view-state>
<view-state id="confirm" view="flight/showFlightInfo" />
</flow>
All’inizio del flow creiamo la variabile di flow flight la quale è un istanza della classe Flight. Fissando questa variabile come attributo model nella start view, diciamo a SWF di utilizzare questo oggetto per effettuare il binding dei dati. Implementando una semplicissima pagina HTML con un form chiudiamo il primo cerchio.
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>DevMe - SWF Flight info test - </title>
</head>
<body>
<h1>Flight information </h1>
<p>Inserisci le informazioni sul volo:</p>
<form:form id="flightDetails" modelAttribute="flight">
<table>
<tr>
<td>Numero del volo: </td>
<td><form:input path="number"/></td>
</tr>
<tr>
<td>Ora di partenza: </td>
<td><form:input path="time"/></td>
</tr>
<tr>
<td>Gate: </td>
<td><form:input path="gate"/></td>
</tr>
<tr>
<td>Volo in ritardo: </td>
<td><form:radiobutton path="isDelayed" value="false" />
<form:radiobutton path="isDelayed" value="true" /></td>
</tr>
<tr>
<td><input type="submit" name="_eventId" value="next"></td>
</tr>
</table>
</form:form>
</body>
</html>
Cliccando sul Next ci spostiamo verso la seconda view definita all’interno del flow attraverso la transition confirm. All’interno di questa transition i valori dei campi del form vengono inviati in POST e automaticamente ne viene fatto il bind verso l’oggetto flight presente nel flow scope. Successivamente, durante il caricamento della view confirm, vengono eseguite le espressioni EL rispetto al valore dei campi del bean, consentendoci quindi di visualizzarne il valore.
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Flight Information Details</title>
</head>
<body>
<h1>Flight Information Details</h1>
<p>Dettagli del volo:</p>
<form:form modelAttribute="flight" >
<table>
<tr>
<td>Numero volo: </td>
<td>${flight.number}</td>
</tr>
<tr>
<td>Orario: </td>
<td>${flight.time}</td>
</tr>
<tr>
<td>Gate: </td>
<td>${flight.gate}</td>
</tr>
<tr>
<td>Ritardo: </td>
<td>${flight.isDelayed}</td>
</tr>
</table>
</form:form>
</body>
</html>
Come vedete è molto semplice. Una piccola nota: se per qualunque ragione non si volesse proseguire durante una transition, al binding dei parametri della request è sufficiente aggiungere l’attributo bind all’interno del tag transition indicando come valore false.
<view-state id="start" view="flight/insertFlightInfo" model="flight">
<transition on="next" to="confirm" />
<transition on="cancel" to="end" bind="false" />
</view-state>
Nel prossimo articolo parleremo della validazione dei campi di un form. Alla prossima
Stay tuned!
75 milioni di richieste al giorno, ovvero quasi 900 richieste/secondo, credo sia una immensa soddisfazione per un programmatore web di alto livello, e cioè sviluppare un’applicazione web, fatta bene, che possa soddisfare quei numeri lì. E’ chiaro che un sito web pronto al decollo, non farà mai tutte quelle richieste al giorno, dovrà passare un pò di tempo, in cui dovrà essere pubblicizzato, finire al meglio sui motori di ricerca, e quant’altro. Ma è altrettanto chiaro che, se l’obiettivo, volente o nolente (vedi siti che sono diventati famosi dall’oggi al domani), è quello di cercare di sviluppare un’applicazione web che possa reggere ritmi del genere. Le cose più importanti da capire durante lo sviluppo , se si vogliono ottimizzare le prestazioni web, sono che bisogna gestire le differenti punte di traffico, discriminando tra le operazioni di lettura (READ) che sono molto economiche, e le operazioni si scrittura (WRITE) che sono molto costose, ovviamente in termini di performance. Per operazioni di READ, intendiamo l’accesso a parti statiche che sono state già memorizzate e sono disponibili all’uso immediato. Per operazioni di WRITE intendiamo le operazioni di aggiornamento di un record di un database, o l’inserimento di un record, più in generale consideriamo una qualunque operazioni che comporta un calcolo prima che l’output venga rediretto all’utente.

Lo schema in figura mostra 2 cluster di database (supponendo di utilizzare MYSQL), uno per gestire le richieste di READ, ovvero ogni richiesta dell’utente di visualizzare le pagine. Tutte le pagine sono servite da memCached. Le richieste di WRITE invece, vengono gestire dall’altro cluster, e attraverso la replicazione di MYSQL gli aggiornamenti vengono trasmessi dal cluster di WRITE a quello di READ. Seguono alcuni trick per ottimizzare le prestazioni:
- READ cluster, per velocizzare usa dei precompilati scritti in C, e non perl/php script.
- READ cluster, usa UltraDNS per il load balancing.
- WRITE cluster, minimizza il numero delle query ed il tempo di connessione per gestire le richieste.
- WRITE cluster, usa il più possibile i processi offline.
- Usa la replicazione per avere scalabilità e affidabilità del servizio.