Category: it's Spring !

Spring Web Flow tutto d’un fiato ! [Part 1/2]

spring-webflowSpring Web Flow è un framework basato su Spring MVC che permette lo sviluppo di applicazioni flow-based. In questo articolo vedremo come aggiungere Spring Web Flow ad applicazioni Spring-based e definire alcuni flussi tra le applicazioni e i loro utenti.
Iniziamo col dire che tutti i flussi (da ora in avanti li chiameremo con loro vero nome, flow) sono costituiti da 3 elementi base, ovvero:

  • States
  • Transitions
  • Flow data

States: indica ciò che accade all’interno di un flow. Spring Web Flow definisce 5 tipi di States differenti: View, Active, End, Subflow, Decision. Vedremo più avanti come questi tipi di stato partecipano alla creazione di un flow.
Transitions: sono di fatto il mezzo di collegamento tra i vari States. Una View state, può contenere al suo interno un certo numero di transizioni ciascuna delle quali collega altri stati.
Flow data: all’interno di un flow vengono collezionati alcuni dati che dipendono dagli stati in cui il flow si trova o si trovava. La visibilità dei dati di ciascun flow dipende dallo scope con cui questo viene definito. Per la precisione esistono 5 tipi di scope differenti in SWF che sono:
 

  • Flow scope : viene creato all’inizio del flow e distrutto quando il flow termina. I dati all’interno del Flow scope sono accessibili e disponibili per tutta la durata del flow
  • Conversation scope : viene creato all’inizio di un top-level flow e distrutto quanto il flow termina. Il conversation scope è simile al flow scope, eccetto per l’accesso ai dati. Infatti mentre il flow scope è accessibile solo dal flow che l’ha creato, il conversation flow può essere acceduto sia dal top-level flow che da tutti i suoi subflow.
  • Request scope : viene creato all’inizio di una richiesta HTTP e distrutto al termine di tale richiesta. I dati all’interno del request scope sono disponibili a tutti gli stati del flow per l’intera durata della richiesta.
  • Flash scope : viene creato quando il flow ha inizio, ripulito quando si entra all’interno di una view e distrutto alla fine del flow. I dati all’interno del flash scope sono disponibili a tutti gli states per l’intera durata del flow.
  • View scope : viene creato quando il flow entra all’interno di una view e distrutto quando la view viene renderizzata. E’ il flow che ha più breve durata, per questo motivo i dati all’interno di questo scope sono disponibili solo alla view che li ha creati.

Ciò detto vediamo come fare ad installare ed utilizzare SFW in pochi semplice passi. Naturalmente per poter iniziare a lavorare con SWF è necessario scaricare i jar che lo contengono, questa pagina costituisce il punto di partenza da cui potete trovare info dettagliate sul framework e dal link download potete scaricare le librerie. Le librerie necessario da aggiungere al classpath della propria applicazione sono :

  • org.springframework.WebFlow-2.0.8.RELEASE.jar
  • org.springframework.binding-2.0.8.RELEASE.jar
  • org.springframework.faces-2.0.8.RELEASE.jar
  • org.springframework.js-2.0.8.RELEASE.jar

in realtà WebFlow e Binding sono le librerie principali, mentre faces e js mettono a disposizione il supporto a JSF e Javascript/Ajax. In aggiunta a queste librerie è necessario aggiungere il supporto al linguaggio che aiuta a definire i Flow.
SWF usa OGNL.

SWF come detto si basa su Spring MVC, quindi così come per tutte le applicazioni spring-based, le richieste passano attraverso il DispatcherServlet. Per configurare SWF basta aggiungere le seguenti righe di codice al file servlet.xml :

<servlet>
    <servlet-name>DevMeApp</servlet-name>   
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

Bene, a questo punto non rimane che indicare quale richiesta debba essere "catturata" da spring e quindi girata a SWF. Per fare questo, ci basterà configurare il nostro DispatcherServlet con un handle per tutte le richieste il cui URL fa match con la stringa "devme"…tradotto:

<servlet-mapping>
  <servlet-name>DevMeApp</servlet-name>
  <url-pattern>/devme/*</url-pattern>
</servlet-mapping>

Ora che abbiamo configurato Spring passiamo alla configurazione di SWF. La configurazione avviene tramite compilazione di file XML di Spring aggiungendo a tali file il namespace di SWF, in modo tale da poter utilizzare i tag messi a disposizione dal framework. Quindi partendo da un file di spring aggiungiamo i seguenti namespace:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:webflow="http://www.springframework.org/schema/webflow-config"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/webflow-config
           http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd">
</beans>

quindi aggiungiamo la nostra configurazione all’interno del tag beans.
La prima cosa da fare è aggiungere un riferimento al flow executor, ovvero all’elemento che gestisce l’esecuzione del flow. Aggiungiamo quindi il tag:

<webflow:flow-executor id="flowExecutor"/>

Il secondo step è quello di indicare a SWF dove trovare il file xml della definizione del flow. Questo viene fatto attraverso il tag flow registry che permette di indicare la posizione su file system in cui risiedono i file XML dei vari flow. Per esperienza, è molto comodo creare i file di flusso separati all’interno di ogni singolo file.
Aggiungiamo quindi il flow registry:

<webflow:flow-registry id="flowRegistry" base-path="/WEB-INF/flows-defs">
	<webflow:flow-location id="devme" path="/WEB-INF/flows/devme-flow.xml" />
</webflow:flow-registry>

l’attributo id del tag flow location permette di assegnare un id al flow di modo che possa essere riferito all’occorrenza. In assenza dell’attributi id, l’id del flow corrisponderà al nome del file del flow.

SWF fornisce un handler adapter (spring based) che permette che fa da bridge tra il DispatcherServlet e il flow executor, gestendo la richiesta e manipolando il flow. E’ necessario configurare l’handler all’interno del file di configurazione affinché possa essere invocato dal framework. Aggiungiamo quindi il seguente tag bean:

<bean class="org.springframework.WebFlow.mvc.servlet.FlowHandlerAdapter">
  <property name="flowExecutor" ref="flowExecutor" />
</bean>
<bean class="org.springframework.WebFlow.mvc.servlet.FlowHandlerMapping">
  <property name="flowRegistry" ref="flowRegistry" />
</bean>

in questo modo il DispatcherServlet è in grado di stabilire a chi girare la richesta consultando uno o più handler. Nel codice di sopra abbiamo aggiunto un riferimento al FlowHandlerMapping il quale mantiene un riferimento al FlowRegistry in modo tale da girare la richiesta al flow corretto. Un’applicazione basata su SWF tipicamente contiene più di un flow, per questo il collegamento tra FlowHandlerMapping e flow registry consente di selezionare il flow corretto sulla base della richiesta ricevuta.

Dopo aver configurato la nostra applicazione Spring fornendo il supporto a SWF vediamo come definire un file di flow. Un file di flow è un file XML diverso da quello visti finora, nel senso che ha un nodo radice diverso il quale appartiene ad un namespace differente.
 

<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">
	<-- Add your definition here -->
</flow>

Vediamo quali sono gli elementi che contribuiscono alla definizione di un flow. Di seguito viene mostrato un elenco di tali elementi con per ciascuno una breve descrizione.

  • <action-state>, definisce una o più azioni, al termine delle quali si può transire verso uno stato successivo all’interno del flow.
  • <attribute>, consente di definire un attributo da memorizzare all’interno del flow. Viene utilizzato assieme al tag <value>.
  • <bean-import>, importa un elemento bean definito dall’utente.
  • <decision-state>, permette di valutare un’espressione sulla base della quale decidere verso quale stato transire.
  • <end-state>, indica lo stato finale del flow. La transizione su questo stato comporta il termine del flow.
  • <exception-handler>, indica un bean che può gestire eccezioni per questo flow.
  • <global-transition>, definisce una o più transizioni che sono disponibili da tutti gli stati.
  • <input>, definisce un input per questo flow.
  • <on-end>, evento che definisce l’azione che viene richiamata quando il flow termina.
  • <on-start>, evento che definisce l’azione che viene richiamata quando il flow inizia.
  • <output>, definisce un output per questo flow.
  • <persistence-context>, crea e alloca un contesto quando il flow ha inizio. Viene usato assieme al transaction manager.
  • <secured>, viene usato per restringere l’accesso ad un determinato stato.
  • <subflow-state>, invoca un nuovo flow come sotto-flow di quello corrente.
  • <var>, definisce una variabile con flow scope.
  • <view-state>, indica uno stato che si presenta all’utente tipicamente caricando un certo output e richiedendo l’interazione da parte dell’utente.

Per evitare di dlungarmi più di quanto abbia già fatto rimando la continuazione di questo articolo al post successivo. Continuo a scrivere quandi non passerà molto dalla pubblicazione seguente…prometto :P  !
Stay tuned.

Crittare l’url di un immagine in jsp con Spring

DataObfuscationIn questo articolo voglio far vedere come nascondere l’href di un immagine in una pagine JSP, pur visualizzando l’immagine come elemento HTML in modo standard.
Supponiamo di dover sviluppare un’applicazione web in cui ciascun utente mantiene una libreria multimediale, costituita per semplicità da soli elementi immagine. Ogni immagine viene memorizzata all’interno di un database assieme alla relative informazioni dell’utente a cui appartiene.
Affinché l’immagine non sia accessibile semplicemente attraverso l’indirizzo indicato nell’HREF, è necessario offuscare il suo contenuto attraverso una codifica il cui funzionamento è noto al sistema. Lo stesso sistema sarà in grado così di decodificare l’HREF in modo da poter restituire la risorsa e quindi visulalizzarla all’interno della pagina.
Vediamo il seguente esempio:

1
2
3
4
5
   <!-- Immagine in chiaro -->
   <img src="http://host/images/img.jpg" title="Immgine in chiaro" alt="" />
 
   <!-- Immagine offuscata -->
   <img src="http://host/image.htm?a=QVMyJCU0c2Rm%3D&b=QVMyJCU0c2RmMzI0M3NkdzI%3D" title="Immgine in chiaro" alt="" />

Nell’esempio di sopra viene mostrata un’immagine in cui l’href è crittata secondo un algoritmo che ora vedremo. E’ chiaro che nel secondo caso, affinché l’immagine possa essere visualizzata è necessaria la presenza di un elemento capace di interpretarne il contenuto, ottenere e restituire la risorsa.
Nell’ambito di Spring tutto questo può essere tradotto nel modo seguente: è necessario un Controller che venga richiamato ogni volta in cui nella pagina è presente un’immagine.
Il Controller dovrà prelevare il contenuto dell’HREF decodificarlo e restituire l’immagine, se corretto. Procediamo quindi per step.
Primo Step configuriamo il Controller (Servlet) all’interno di Spring…quindi editiamo il file di configurazione di spring ed aggiungiamo le seguenti righe di codice:

1
2
3
4
5
6
7
8
    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" id="urlMapping"> 
        <property name="mappings">
	<value>/image.htm=mediaResolver</value>
        <property value="true" name="alwaysUseFullPath"></property>
    </bean> 
    <bean class="it.devme.obfuscator.media.MediaResolver" id="mediaResolver">
        <property name="dbManager"><ref bean="dbManager"></ref></property>     
    </bean>

L’invocazione della risorsa virtuale index.htm provoca l’invocazione del Controller MediaResolver. Vediamo la sua struttura di seguito:

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
public class MediaResolver extends BaseCommandController {
 
    private DBManager dbManager;
    public void setDbManager (DBManager dbManager) {
	this.dbManager = dbManager;
    }
 
    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	Object mediaid = req.getParameter("a");
	Object uid = req.getParameter("b");
 
	if (mediaid==null || uid==null) 
	    return new ModelAndView(new MediaView("image/jpeg", "no-image.jpg", rs.getString("basePath"), MediaView.USE_DEFAULT_IMAGE));
 
	Encrypter dec = Encrypter.getInstance();
	int idaccount = Integer.parseInt(URLDecoder.decode(dec.decrypt(uid.toString()), "UTF-8"));
	int idelement = Integer.parseInt(URLDecoder.decode(dec.decrypt(mediaid.toString()), "UTF-8"));
 
	MediaBean media = null;
	File f = null;
	String mediaPath = null;
 
	/* Check if media item exists */
	media = dbManager.getImage(idelement, idaccount);
	if (media==null) return new ModelAndView();
	mediaPath = media.getPath();
	f = new File(UPLOAD_PATH + File.separator + mediaPath);
 
	MimetypesFileTypeMap mime = new MimetypesFileTypeMap();
 
	resp.setHeader("Cache-Control", "private,no-cache,no-store");
	resp.setContentType(mime.getContentType(f));
	return new ModelAndView(new MediaView(mime.getContentType(f), mediaPath, rs.getString("basePath"), !MediaView.USE_DEFAULT_IMAGE));
    }
 
}

La classe di sopra estende BaseCommandController e quando invocata viene richiamato l’unico metodo di cui è stato fatto l’override che si occupa di decodificare e restituire la risorsa. Vediamo come.
Sostanzialmente recupera i parametri dalla GET (nel nostro caso sono a e b). Li decodifica utilizzando un algoritmo di crittografia a noi noto (si può usare qualunque algoritmo a nostro piacimento basato su una chiave provata) e quindi ottiene per il parametro a, l’id dell’utente a cui appartiene la risorsa e per il parametro b, l’id della risorsa a cui si vuole accedere. Questi dati vengono passati in input ad un metodo che effettua una query su db (getImage(idelement, idaccount)) e verifica se effettivamente la risorsa richiesta appartiene all’utente corrente.
In caso contrario viene restituita una View vuota, altrimenti si determina il mime-type della risorsa e si restituisce una view creata a partire dalla classe MediaView.

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
public class MediaView extends AbstractView {
 
    private String media;
    private String basePath;
    private boolean noImage;
 
    public MediaView(String mime, String media, String basePath, boolean noImage) {
	this.media = media; 
	setContentType(mime);
	this.basePath = basePath;
    }
 
    @Override
    protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
	String rpath = 	null;
 
	rpath = this.basePath + request.getContextPath().substring(1) +
		MediaResolver.UPLOAD_PATH + File.separator + this.media;
 
 
	File f = new File(rpath);
	byte[] bytes = FileUtils.readFileToByteArray(f);
 
 
        // Write content type and also length (determined via byte array).
        response.setContentType(getContentType());
        response.setContentLength(bytes.length);
 
        // Flush byte array to servlet output stream.
        ServletOutputStream out = response.getOutputStream();
        out.write(bytes);
        out.flush();
    }
 
}

La classe MediaView non fa altro che leggere fisicamente i byte dell’immagine e restituirla nella response utilizzando un OutputStream. Il risultato è che l’immagine scritta nella response verrà visualizzata in pagina come un elemento HTML standard, mantenendo perà l’HREF dell’immagine crittato.
Alla prossima.

SimpleCaptcha….not too simple.

devme - captchaL’ultima volta ci siamo lasciati con la promessa di rincontrarci per parlare del captcha, l’ormai famoso sistema che rende più sicure le nostre procedure web guidate. Per dovere di cronaca, sinteticamente, segue una breve descrizione. Il captcha è un sistema che genera un immagine casuale, contenente caratteri alfanumerici, non interpretabili da alcuna procedura automatica. Per completare uno step di una qualunque procedura web che contiene un capthca, è necessario inserire ciò che l’immagine raffigura in un campo di testo associato, in modo che server-side si possa effettuare il controllo. Se il contenuto dell’immagine differisce da ciò che è stato inserito nel campo di testo, allora la procedura fallisce; la procedura ha successo in caso contrario. Come contestualizziamo il tutto, in Spring Web Flow? Intanto scegliamo la libreria che implementa il captcha. Le papabili sono molte, la più citata in rete è JCapthca, che avevo anche utilizzato, se non fosse per i miei colleghi che non l’hanno voluta perché non gradivano le immagini generate dal punto di vista grafico. Quindi cercando un altro pò per la rete mi sono imbattuto in simplecatptcha. E’ una libreria molto light, semplice da utilizzare al tempo stesso efficace. Genera immagini carine, con alcuni effetti notevoli, quali ad esempio la distorsione che ho utilizzato. Let’s go. Al solito, si consideri una procedura di registrazione web composta, per semplicità da 3 step, inserimento dei dati personali, riepilogo e conferma. Scarichiamo e mettiamo nel classpath della nostra applicazione la libreria. Successivamente dobbiamo configurare il file web.xml in modo da "installare" il captcha, configurando l’apposita servlet che si occuperà di creare l’immagine casuale.

1
2
3
4
5
6
7
8
9
<servlet>
    <servlet-name>Captcha</servlet-name>
    <servlet-class>nl.captcha.servlet.CaptchaServlet</servlet-class>
     .....some setup of capthca....
</servlet>
<servlet-mapping>
    <servlet-name>Captcha>/servlet-name>
    <url-pattern>/Captcha.jpg>/url-pattern>
</servlet-mapping>

Sul sito di simplecaptcha troverete alcuni setup iniziali che si possono dare alle immagini generate, ad esempio, impostare un background, utilizzare la distorsione, forzare ciò che viene generato, etc, etc. Noterete che i setup sono della forma di tag del tipo init-param che vengono inseriti del tag servlet, lì dove vedete " …..some setup of capthca….". Ok, quindi ora possiamo usare la libreria, e non solo, se ci riferiamo all’interno delle nostre pagine all’indirizzo /Captcha.jpg invocheremo la libreria che si occuperà di generare l’immagine desiderata. Quindi metteremo un bel tag img nel primo step della nostra procudura:

1
2
3
4
5
6
7
8
9
10
<spring:bind path="LCCaptcha">
    <div class="field <c:if test="${status.error}">wrong</c:if>">
        <label for="LCCaptcha">Inserire la scritta che compare nell'immagine</label>
        <img src="<c:url value="Captcha.jpg" />" title="Captcha" alt="Captcha">
        <input type="text" id="LCCaptcha" name="LCCaptcha" value="">
        <c:if test="${status.error}">
            <div class="error">${status.errorMessage}</div>
        </c:if>
    </div>
</spring:bind>

Dando per scontato di conoscere Spring Web Flow e quindi Spring, notiamo che l’elemento img si trova dentro un elemento spring:bind che si preoccupa al submit della pagina di fare bind del contenuto del campo di input (in questo caso) con la corrispondente varibile del bean associato. La descrizione precisa del funzionamento di SWF esula dal contenuto del post. Ciò detto, vediamo che l’url dell’immagine corrisponde all’invocazione dell’indirizzo, configurato all’interno del web.xml che invoca la libreria del captcha. Risultato vedremo l’immagine generata, che cambierà ad ogni ricarica della pagina. Contestualmente alla generazione dell’immagine, la libreria simplecaptcha genererà il valore di ciò che viene visualizzato nell’immagine, rendendolo disponibile come attributo di sessione, dalla chiave SIMPLE_CAPCHA_SESSION_KEY. Supponiamo quindi di inserire i dati presenti nello step1 e fare submit della pagina, spostandoci quindi nel riepilogo ovvero allo step2 della procedura. Ricordiamo che utilizzando SWF per lo sviluppo di applicazioni web, è necessario definire un flusso di operazioni che specificano come avverrà la navigazione delle pagine. Ad esempio, nel flusso di navigazione verrà definito che dallo step1 si andrà allo step2, previa validazione dei dati. Di seguito mostriamo lo scorcio di codice xml del flusso di navigazione che definisce quanto detto sopra:

1
2
3
4
5
6
7
8
9
10
11
12
13
.....
.....
<view-state id="start" view="step1">
    <render-actions>
        <action method="setupForm" bean="DevMeAction" />
    </render-actions>
    <transition on="regUserStep1" to="step2">
        <action method="bindAndValidate" bean="DevMeAction" />
    </transition>
    <transition on="regUserCancel" to="userCancel" />
</view-state>
.....
....

Quindi da quanto scritto sopra, si ha che al momento del submit dei dati, andremo allo step2 se la procedura di validazione andrà a buon fine. In caso contrario, rimarremo nella stessa view, visualizzando opportunamente i messagi di errori dei campi non validi. Qui ho usato un piccolo trick, se così possiamo chiamarlo, non sapendo se è la soluzione migliore nell’ambito SWF. Subito prima di chiamare il metodo che effettua la validazione del form, SWF richiama un suo metodo interno doBind(…) che si preoccupa di prendere i dati nel form e assegnarli alle corrispondenti variabili definite all’interno del bean associato al form…il tutto avviene in automatico. Facendo override del metodo, l’idea è quella di recuperare il valore dalla sessione e assegnarlo, nel mio caso ad una variabile del bean, ma nulla vieta di creare un attributo del flusso e assegnarlo.

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void doBind(RequestContext context, DataBinder binder) throws Exception {
    super.doBind(context, binder);
    /* Get user data from flow */
    DevMeBean devme = (DevMeBean) context.getFlowScope().get("devme");
    /* Retrive session value only in step 2*/
    String captcha_id = context.getExternalContext().getSessionMap().
                        getRequiredString("SIMPLE_CAPCHA_SESSION_KEY");
    if (captcha_id!=null) {
        user.setLCCaptcha_id(captcha_id);
    }
}

Per il mio caso risulta più comodo averlo nel bean, così poi il metodo di validazione, cha ha visibiltà del bean, può accedere comodamente sia al valore del campo di input e sia al valore dell’attributo di sessione, in modo da poter effettuare il controllo.

1
2
3
4
5
6
7
8
public void validate(Object target, Errors errors) {
    DevMeBean devme = (DevMeBean ) target;
    String captcha_id = devme.getLCCaptcha_id();
    String captcha = devme.getLCCaptcha();
    if (!captcha_id.equals(captcha)) {
        errors.rejectValue("LCCaptcha", "error.captcha.invalid");
    }
}

E il gioco è fatto. Se tutto va bene, e quindi niente errori, allora si va allo step2 della procedura….altrimenti si rimane allo step1, e si visualizza l’errore. A questo punto vi chiedere, bhe se tutto è così semplice allora perchè nel titolo hai messo "…not too simple" ? Ora vi dico. L’applicazione a cui sto lavorando dovrà girare su un sistema Linux Debian senza x-window, ovvero senza ambiente grafico. Tipicamente le applicazioni Java che hanno a che fare con la manipolazione delle immagini si appoggiano sul sottosistema grafico del sistema operativo sul quale girano. Se nessun sottosistema è installato, allora bisogna informare la JVM e lanciarla in esecuzione con una specifica opzione:

1
-Djava.awt.headless=true

, è possibile reperire questo dettaglio direttamente dal sito di simplecaptcha. Naturalmente ho fatto quanto detto, ma ovviamente la libreria non né voleva sapere di funzionare, e quindi l’immagine non veniva visualizzata. Gira che ti rigira, ho trovato che simplecaptcha, che premetto è alla versione 0.3, ha un piccolo bug. Per risolverlo è necessario scaricare i sorgenti, e rifare il deploy dell’applicazione, con 1000 sbattimenti annessi al recupero di alcune librerie che non sono, diciamo così standard. Il bug lo potete trovare direttamente sul forum qui, ma vi assicuro che il jar bug-free no, quindi lo rendo disponibile per dovere informatico. See you later.

Spring Web Flow, DWR: perfect combination !

springDopo un lungo letargo arieccomi qui a parlare di questioni informatiche che trovo prima di tutto molto divertenti (vi chiederete: che tipo è questo?) e dopo molto interessanti. Trovandomi ultimamente occupato allo sviluppo di un applicazione web di cui spero sentirete parlare molto presto, ho provato dapprima e poi ormai adottato la tecnologia messa a disposizione da Spring Web Flow, un framework basato su Spring che permette di definire dei flussi di navigazione per un applicazione web in modo semplice, chiaro e potente…..molto potente. Tralasciamo qui la discussione dei dettagli della tecnologia, per descrivere quello che in sostanza è un integrazione tra SWF, DWR Direct Web Remoting, un framework che aggiunge il supporto AJAX alle nostre applicazioni. Come dicevo, non starò a dettagliare ciò che vogliono dire i file di configurazione, il significato, etc etc, dando tutto per scontato….in futuro se risciro magari, pubblicherò qualche tutorial su Spring framework e spring web flow, anche se in rete si trova di tutto. Ciò detto….let’s start! Occurre naturalmente scaricare la libreria: DWR. Dopo di che si procede con ordine. Sinteticamente, ricordiamo che DWR permette di richiamare attraverso una call-back, dei metodi remoti esposti server side. I risultati restituiti dalla chiamata ai metodi, che possono essere di tipo semplice (int, String, boolean) o complesso come Array di oggetti o di tipi semplice. I risultati vengono "trasformati" e forniti come varibili javascript (array, stringhe o quant’altro) le quali possono essere usati poi localmente. Editiamo il file web.xml aggiungenfo le seguenti linee:

1
2
3
4
5
6
7
8
9
10
11
12
<servlet>
    <servlet-name>dwr</servlet-name>
    <servlet-class>org.directwebremoting.spring.DwrSpringServlet</servlet-class>
    <init-param>
        <param-name>debug</param-name>
        <param-value>true</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>dwr</servlet-name>
    <url-pattern>/dwr/*</url-pattern>
</servlet-mapping>

le quali inizializzano la servlet che permetterà il riconoscimento delle chiamate verso gli script javascript per realizzare l’interazione AJAX. L’integrazione di DWR 2.0 con Spring 2.5 è molto più light rispetto alla versione precedente. Sostanzialmente basta aggiungere il namespace di dwr in testa al file di configurazione globale di spring, definire il controller e qualche mapping di URL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:dwr="http://www.directwebremoting.org/schema/spring-dwr"
          xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
          http://www.directwebremoting.org/schema/spring-dwr
         http://www.directwebremoting.org/schema/spring-dwr-2.0.xsd">
 
    <dwr:controller id="dwrController" debug="true" />
    <bean id="urlMapping" 
             class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <!-- DWR configuration -->
                <prop key="/dwr/**/*.*">dwrController</prop>
                <prop key="/dwr/**/*">dwrController</prop>
                <prop key="/dwr">dwrController</prop>
                <prop key="*.html">dwrController</prop>
            </props>
        </property>
        <property name="alwaysUseFullPath" value="true"/>
    </bean>
</beans>

Successivamente ci rimane da dichiarare il metodo remoto che si vuole esporre e la eventuale classe che si desidera gestire dal client con javascript. Il tutto si fa dal file xml, che per chiarezza scrivo sempre a parte e poi lo includo in web.xml come facente parte della configurazione.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
File: dwr.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:dwr="http://www.directwebremoting.org/schema/spring-dwr"
          xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
          http://www.directwebremoting.org/schema/spring-dwr
          http://www.directwebremoting.org/schema/spring-dwr-2.0.xsd">
    <bean id="dwrexposure" class="it.devme.dwr.exposure.DWRExposure">
        <dwr:remote javascript="DWR">
            <dwr:include method="retriveProductCompanyList" />
        </dwr:remote>
        <property name="baseManager"><ref bean="baseManager"/></property>
    </bean>
    <dwr:configuration>
        <dwr:convert type="bean" class="it.devme.dwr.exposure.ProductsVO" />
    </dwr:configuration>
</beans>

Praticamente abbiamo fatto, DWRExposureè la classe che contiene il metodo retriveProductCompanyList che intendiamo esporre, che restituisce un array di oggetti ProductsVO i quali vengono convertiti e resi utilizzabili da locale da DWR, con il tag dwr:convert che indichiamo. L’oggetto ProductsVO è un semplicissimo Java Bean contenente 2 campi che sono l’id del prodotto e la sua descrizione. Da client side, includiamo i seguenti script:

1
2
3
<script type="text/javascript" src="<c:url value="dwr/interface/DWR.js"/>"></script>
<script type="text/javascript" src="<c:url value="dwr/engine.js"/>"></script>
<script type="text/javascript" src="<c:url value="dwr/util.js"/>"></script>

Il primo viene creato da DWR e contiene la logica che permette l’invocazione del metodo remoto e il recupero del risultato. Le altre 2 inclusioni permettono l’utilizzo di alcune funzioni di utilità molto comode. Vediamo ora come fare a usare il tutto. Use case: 2 select, uno che contiene una lista di utenti. L’altro conterrà la lista dei prodotti associati all’utente. La selezione del primo, fa scattare l’invocazione del metodo remoto che riempie il secondo select con i dati dei prodotti associati all’utente selezionato:

1
2
3
4
5
6
7
8
<select name="user_id" id="user_id" onchange="loadProduct();">
    <option value="0">Scegli</option>
    <option value="1">Utente1</option>
    <option value="2">Utente2</option>
</select>
<select name="product_id" id="product_id" >
    <option value="0">Scegli</option>
</select>

Quindi selezionando l’utente dalla lista, verrà richiamato il codice javascript che andremo a scrivere, che richiamerà il metodo remoto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/** Call Remote method and
load products data */
function loadProduct() {
    var user_id = DWRUtil.getValue("user_id");
    if (user_id>0) {
        DWR.retriveProductsList(user_id, gotProducts);
    } else {
        dwr.util.removeAllOptions("product_id");
        dwr.util.addOptions("product_id", [ { name:'Scegli..', id:'0' } ], "id", "name");
        document.campaign.product_id.disabled=true;
    }
}
function gotProducts(products) {
    dwr.util.addOptions("product_id", products, "idproduct", "name");
    document.campaign.product_id.disabled=false;
}

La prima funzione loadProduct() richiamerà il metodo remoto, selezionando l’utente dall’elemento select attraverso la funzione di utilità DWRUtil.getValue("user_id"). Il metodo richiamato restituirà il risultato alla funziona gotProducts(products) fornendolo come parametro. L’array di oggetti a questo punto, conterrà le informazioni sui prodotti associati all’utente, e può essere assegnato al select dei prodotti attraverso l’altra funzione di utilità dwr.util.addOptions("product_id", products, "idproduct", "name"). E il gioco è fatto…con una semplicità incredibile, senza requirements mostruoso o complessi si aggiunge il supporto ad AJAX alla nostra applicazione Java, in particolare Spring. Con queste informazioni in mani ci si può sbizzarire creando il proprio personale componente AJAX. Nel prossimo articolo vedremo come integrare il supporto per JCaptcha per rendere più sicure le nostre applicazione. Alla prossima.

 

You need to log in to vote

The blog owner requires users to be logged in to be able to vote for this post.

Alternatively, if you do not have an account yet you can create one here.

Powered by Vote It Up

WordPress Themes