Introduzione
In
alcune situazioni, nella realizzazione di Web application,
si presenta la necessità di suddividere una elaborazione
in un flusso di elaborazione logica composto da più
step, normalmente chiamato Page Flow. Un esempio, può
essere il caso in cui si debba effettuare una registrazione,
laddove un utente debba fornire molti dati via web,
in questo caso, si possono suddividere i dati per raggruppamenti
logici e richiederli in pagine diverse. Un altro esempio
può essere una applicazione avente come dominio
applicativo l'elaborazione dei work flow.
Soluzioni
Un framework come Struts permette una elaborazione stile
wizard, ma al prezzo di legare nel codice e nello struts-config
i passi del flusso, rendendo non riutilizzabili i singoli
passi di cui è composto il flusso e non isolando
chiaramente i flussi stessi.
Spring fornisce appositamente per questo tipo di problematica
un modulo chiamato Spring Web Flow (che farà
parte della distribuzione dalla versione 1.3).
Esso permette una configurazione da file (anche da codice
se si preferisce) dei passi di cui è composto
ogni singolo flusso.
Questo approccio "Spring-Ioc Style" risulta
molto più maneggevole, flessibile, chiaro, testabile,
riutilizzabile ed elegante.
Vedremo come si utilizza Spring Web Flow, prendendo
come esempio una applicazione usa la versione PR3.
L'applicazione è una Web App realizzata per l'AVIS
(Associazione Donatori Sangue) dal Jug Sardegna, sotto
licenza Apache.
Schema
flusso logico
Il
primo passo da compiere quando si usa Spring Web Flow,
è disegnare lo schema del funzionamento desiderato,
sotto forma di transizioni tra i vari stati di funzionamento
desiderati.
Figura 1 - Flusso logico
Si
passa da uno ad un altro in base alla mappatura nel
file di descrizione del flusso.
Lo StartState è il punto di partenza, il flusso
si pone in un ViewState, dove viene semplicemente visualizzata
una pagina, e viene atteso il submit da parte dell'
utente .
La view di questa pagina, è una JSP che seleziona
un file di cui fare l' upload.
Lo stato successivo è bindAndValidate (ActionState),
dove viene fatto il bind del file di cui si è
fatto l'upload, nel caso ci sia un errore, l' utente
viene riportato allo stato iniziale per ritentare l'upload
in maniera corretta.
Un ActionState è quindi uno stato in cui vengono
fatte delle elaborazioni.
Se invece non ci sono stati errori il flusso si porta
allo stato insert.donors dove viene valorizzata una
List con gli oggetti di dominio Donor.
Se qualcosa è andato male (il file è vuoto)
si viene riportati allo stato iniziale in cui viene
richiesto il file.
Proseguendo, in caso nella insert.donors sia andato
tutto bene, si arriva allo stato confirmation.viewTest,
dove vengono mostrati i dati estratti dal file, e dove
viene richiesto il messaggio da spedire via sms alle
persone elencate, quando si procede con la spedizione
dell' sms, il flusso passa allo stato sendSms (ActionState)
dove viene effettuata la spedizione dell' sms.
Il flusso passa allo statao exit (viewState) dove viene
mostrato un messaggio che comunica la buona riuscita
o il fallimento della spedizione dell' sms.
Funzionamento
e altre caratteristiche
Oltre
agli stati ViewState e ActionState illustrati in precedenza,
Web Flow fornisce anche uno stato "decisionale"
, da utilizzare ad esempio, nel caso si debba incanalare
un flusso in un eventuale sotto flusso, oppure nel caso
si debba prendere una decisione rispetto al contesto
nel quale si può trovare il flusso.
Es. mappatura decision-state:
<decision-state
id="isPassengerInfoRequired">
<if test="${requestScope.passenger == null}"
then="enterPassengerInformation"/>
<if test="${requestScope.passenger.preferences.alwaysConfirmPassengerInfo}"
then="enterPassengerInformation" else="displayReservationVerification"/>
</decision-state>
Es.
mappatursa Sotto Flusso:
<subflow-state
id="enterPassengerInformation" flow="passenger">
<attribute-mapper>
<input value="${requestScope.passenger.id}"
as="passengerId"/>
</attribute-mapper>
<transition on="finish" to="displayReservationVerification"/>
</subflow-state>
Possono
essere definiti anche uno o più stati finali
End-State con il quale il flusso viene terminato
<end-state
id="displayConfirmation" view="reservationConfirmation"/>
<end-state
id="tryAgain" view="tryAgain"/>
<end-state
id="cancel" view="home"/>
Nel
caso dell' esempio ciò non è stato fatto
perché l' utente deve avere la possibilità
di mandare più sms di seguito.
Invece nel caso di una registrazione uno stato finale
avrebbe senso.
File
di configurazione sms-flow.xml
<?xml
version="1.0" encoding="UTF-8"?>
<!DOCTYPE
webflow PUBLIC "-//SPRING//DTD WEBFLOW//EN"
"http://www.springframework.org/dtd/spring-webflow.dtd">
<webflow
id="upload" start-state="selectFile.view">
<!--
Selezione file -->
<view-state id="selectFile.view" view="selectFile.view">
<transition on="submit" to="bindAndValidate"/>
</view-state>
<!--Validazione
file -->
<action-state id="bindAndValidate">
<action bean="upload.process"/>
<transition on="success" to="insertDonors"/>
<transition on="error" to="selectFile.view"/>
</action-state>
<!-- Creazione List di Donor dal file -->
<action-state id="insertDonors">
<action bean="insert.donors"/>
<transition on="success" to="confirmation.viewTest"/>
<transition on="error" to="selectFile.view"/>
</action-state>
<!-- Visualizzazione donatori e attende messaggio
da spedire-->
<view-state id="confirmation.viewTest"
view="insertMessage.view">
<transition on="submit" to="sendSms"/>
</view-state>
<!-- Spedisce sms -->
<action-state id="sendSms">
<action bean="send.sms"/>
<transition on="success" to="exit"/>
</action-state>
<!-- Messaggio di buona riuscita o di fallimento
spedizione -->
<view-state id="exit" view="ciao">
<transition on="submit" to="selectFile.view"/>
</view-state>
</webflow>
In questo file viene definita la composizione del flusso
attraverso gli stati, identificando il flusso stesso
con l' id upload.
Come si vede, tutti i componenti del flusso sono dei
POJO, che sono definiti in un altro file di configurazione,
compreso il componente principale che agisce da factory
per i flussi, vediamo questo file.
jug-servlet.xml
Questo
file contiene i bean del Web Tier
<?xml
version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!--
F L O W C O N T R O L L E R-->
<bean id="flowFrontController" name="/upload.htm"
class="org.springframework.web.flow.mvc.FlowController">
<property name="flow">
<ref bean="smsFlow"/>
</property>
</bean>
<!-- W E B F L O W F A C T O R Y-->
<bean id="smsFlow" class="org.springframework.web.flow.config.XmlFlowFactoryBean">
<property name="location">
<value>/WEB-INF/sms-flow.xml</value>
</property>
</bean>
<!-- A C T I O N -->
<bean id="upload.process" class="org.jugsardegna.avis.web.action.ProcessUploadAction"/>
<bean id="insert.donors" class="org.jugsardegna.avis.web.action.DonorsAction">
<property name="sender">
<ref bean="mockSender"/>
</property>
<property name="logger">
<ref bean="logger"/>
</property>
</bean>
<bean id="send.sms" class="org.jugsardegna.avis.web.action.MessageSmsAction">
<property name="sender">
<ref bean="mockSender"/>
</property>
</bean>
<!-- M U L T I P A R T R E S O L V E R -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
<!--
V I E W R E S O L V E R -->
<bean
id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/jsp/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
Il
primo bean che vediamo è flowFrontController,
il controller (il corrispondente delle action in Struts)
che mappa l' URL /upload.htm (.htm negli url usati da
Spring è una convenzione come il .do in Struts),
e al quale viene iniettato il bean smsFlow che è
una XmlFlowFactoryBean che provvede a creare il flusso
dal file dei bean.
Nella parte delle action troviamo i bean che vengono
usati nel flusso, poi troviamo il bean usato nell' upload
del file, e per ultimo il bean viewResolver che si occupa
di far corrispondere i nomi logici delle view(in questo
esempio quelle dei viewState)a delle JSP.
Dettagli
di funzionamento
Vediamo alcuni dettagli che permettono al
flusso di funzionare. Vediamo la JSP iniziale (selectFile.view)
per spiegare gli elementi fondamentali.
<%@
page session="false" %>
<%@ taglib uri="http://java.sun.com/jstl/core"
prefix="c" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01
Transitional//EN">
<HTML>
<HEAD>
</HEAD>
<BODY>
<DIV align="left"><c:if test="${!msgGeneric}"><c:out
value="${msgGeneric}"/></c:if>(Passo
1 di 3) Scegli il file contenente i numeri di telefono
delle persone a cui inviare l'sms</DIV>
<HR>
<DIV align="left">
<FORM name="submitForm" action="upload.htm"
method="post" enctype="multipart/form-data">
<INPUT type="hidden" name="_flowExecutionId"
value="<%=request.getAttribute("flowExecutionId")
%>">
<INPUT type="hidden" name="_eventId"
value="submit">
<INPUT type="hidden" name="_currentState"
value="selectFile.view">
<INPUT type="file" name="file">
</FORM>
</DIV>
<HR>
<DIV align="left">
<INPUT type="button" onclick="javascript:document.submitForm.submit()"
value="Importa il file">
</DIV>
</BODY>
</HTML>
Per
prima cosa, diciamo che l'url che viene utilizzato in
tutto il flusso è upload.htm, ed è sempre
lo stesso, quindi in tutte le JSP lo ritoveremo come
url del form action.
Di seguito troviamo ciò che permette al flusso
di sapere in quale punto si trova.
_flowExecutionId,
è l' id che permette all' oggetto che contiene
il page flow (nella sessione tipicamente) di identificare
il flusso tra le varie richieste al server, esso deve
essere presente in tutte le pagine tranne quella iniziale
e quella finale ed è associato allo stesso
flusso su uno stesso client.
La sua funzione si comprende soprattutto alla luce
del fatto che il flusso nei ViewState è "in
pausa".
_eventId, invece serve per sapere quale transizione
deve essere effettuata, nel nostro caso sul submit.
_currentState, serve per sapere in quale stato ci
si trova nel caso che l' utente invece di usare i
link o i pulsanti per la navigazione, utilizzi il
back del browser.
Le
action estendono tutte MultiAction di Web Flow.
A tutte le action viene fornito un RequestContext(Non
corrispondente alla HttpRequest), i dati possono essere
letti e messi con visibilità a livello di request
o di flow.
Altra carattersitiche non illustrata nell' articolo
è il binding e il supporto per le portlet.
Testabilità,
Code Coverage e metriche
Come
detto all' inizio dell' articolo, la costruzione di
un flusso in maniera Ioc Style porta il beneficio di
rendere fortemente disaccopiati tutti gli oggetti, rendendo
facile la testabilità, il Code Coverage e la
"misurazione" della qualità del codice
che viene prodotto, fornendo degli indicatori su dove
eventualmente migliorare il codice.
Se sui test non ci dovrebbe ormai essere bisogno di
spiegarne i benefici, vediamo in cosa ci aiuta invece
il Code Coverage.
Per il Jug Avis viene usato Clover , che segnala nell'
ambiente di sviluppo ( anche con Ant volendo, e genera
dei report per la documentazione sui punti in cui il
codice non viene eseguito), segnalando codice non usato,
o non adeguatamente coperto da test.
Figura 2 - Clover in eclipse
Figura 3 - Clover report
Metrics
invece come dice il nome stesso, misura il codice scritto
, fornendo dati che possono rivelare molte cose sul
"come" si stà scrivendo il codice e
fornisce anche dei grafi interattivi sulle dipendenze
fra le classi.
Figura 4 - Metrics
Figura 5 - Metrics dipendenze
Conclusioni
Nell'
articolo è stata mostrata solo la superficie
di questo modulo di Spring che è arrivato alla
versione PR5 e ritoveremo nelle prossime versioni di
Spring, magari con qualche modifica.
Infatti oltre a supportare il Page flow, è il
modulo deputato alla integrazione con le Portlet (con
specifica JSR 168).
Bibliografia
e riferimenti
J2EE
Development without EJB Rod Johnson, Juergen Hoeller
ed. Wrox
Spring in Action Craig Walls and Ryan Breidenbach ed.
Manning
Professional Java Development with the Spring Framework
Rod Johnson, Juergen Hoeller, Alef Arendsen, Thomas
Risberg, Colin Sampaleanu e. Wrox
Pro Spring Rob Harrop, Jan Machacek ed. Apress
[1] Spring Web Flow: http://opensource.atlassian.com/confluence/spring/display/WEBFLOW/Home
[2] Forum Spring Framework :http://forum.springframework.org/
[3] Presentazione Jug Avis Web : http://www.jugsardegna.org/vqwiki/jsp/Wiki?action=action_view_attachment&attachment=Spring_webflowJug16Luglio2005.pdf
[4] Jug Avis : http://www.jugsardegna.org/vqwiki/jsp/Wiki?JugAvis
Massimiliano
Dessì
(http://www.jugsardegna.org/vqwiki/jsp/Wiki?MassimilianoDessi)
è raggiungibile a desmax74@yahoo.it Ha iniziato
a lavorare presso la Sistemi Informativi S.p.A (IBM
come programmatore Java.
Dal 2001 anni lavora presso Atlantis S.p.A.dove utilizzando
metodologie agili quali l'eXtreme programming (Xp),
sviluppa applicazioni enterprise Web-based con tecnologia
J2EE quali portali e content management system per la
promozione del territorio. Nel poco tempo libero, cura
la versione italiana di Jetspeed, sviluppa la versione
Web del progetto Jug Avis utilizzando il framework Spring.
tiene seminari e corsi su tecnologie J2EE e collabora
con società ed enti italiani ed esteri per la
realizzazione di portali J2EE, è studente di
Ingegneria Informatica al Politecnico di Torino. E'
membro e co-fondatore del Java User Group Sardegna.
|