|
Continuiamo il nostro viaggio all’interno del framework di Struts e delle classi che lo costituiscono. Mentre nel secondo articolo siamo andati ad analizzare il funzionamento di alcune classi di Struts che generalmente non verranno modificate dal programmatore, in questo articolo andremo a vedere delle classi a cui bisogna fare riferimento per sviluppare la propria applicazione. Quindi le classi che presenteremo qui (o almeno alcune) verranno estese dallo sviluppatore ed adattate per i propri scopi utilizzandone le proprietà ed i metodi già forniti dal framework. La classe org.apache.struts.action.ActionMapping 
Fig. 5 Terza Parte Incominciamo con una di quelle classi che non verranno estese dal programmatore (o almeno di solito, molti preferiscono estendere anche questa classe per aggiungere dei parametri) : la classe ActionMapping. La ActionMapping contiene le informazioni con cui determinare a quale Action è associato un particolare evento. Queste informazioni vengono fornite in fase di startup, prelevandole dallo struts-config.xml, e trasformate in un set di ActionMapping inserite all’interno di un container (la classe ActionMappings : nel framework le classi che finiscono con una s sono dei contenitori) da cui verranno riprese quando bisognerà avere accesso ad ogni tipo di parametro di configurazione inserito nel tag <action>. Riprendendo quindi una parte di codice dallo struts-config.xml possiamo vedere che all’elemento qui sotto corrisponde una classe ActionMapping che mappa il percorso “/Path” alla Action “percorso.della.classe.NomeAction”. .... <action path="/Path" type="percorso.della.classe.NomeAction"> <forward name="success" path="/AltraAction.do" /> <forward name="failure" path="/jspDiFailure.jsp" /> </action> …. Quindi quando il controller riceverà una richiesta con il percorso contenente la stringa “/Path” invocherà il metodo perform() della Action “percorso.della.classe.NomeAction”, inoltre a seconda dell’esito reinderizzerà il tutto o ad un’altra Action associata al percorso “/AltraAction” o ad una pagina di errore “/jspDiFailure.jsp”. A partire dalla versione 1.1 questa classe estende org.apache.struts.config.ActionConfig, che avrebbe dovuto prendere il suo posto rendendola deprecata, ma per problemi di compatibilità con le precedenti versioni di Struts questa classe viene ancora utilizzata. Come abbiamo detto prima, alcuni sviluppatori hanno la necessità di estendere la classe ActionMapping per aggiungere dei parametri o degli attributi da utilizzare in seguito nelle classi Actions. Ma quali sono i passi da compiere per estendere le funzionalità di questa classe? Per prima cosa bisogna creare una classe che estenda ActionMapping: anche nella nuova versione di Struts non bisogna estendere ActionConfig perché la ActionMapping è ancora ampiamente utilizzata nel framework (per esempio viene passata come parametro nel metodo execute() della Action). A questo punto creata la classe che chiameremo qui “mia.classe.MiaActionMapping” la vado ad aggiungere nello struts-config.xml : …. <action-mappings> <!-- con l’attributo className specifico al framework che quando processa questa richiesta deve utilizzare la mia classe MiaActionMapping e non quella di default --> <action className=”mia.classe.MiaActionMapping” path=..... type=.... > <!-- uso il tag set-property se devo inizializzare delle proprietà all’interno della classe --> <set-property property=”miaproprietà” value=”valore” /> ..... </action> ..... </action-mappings> ..... Inoltre poichè stiamo utilizzando una classe da noi implementata ma nei metodi perform() e execute() delle Actions ricevo una classe di tipo ActionMapping, per vedere le mie nuove proprietà dovrò ricordarmi di fare il casting all’oggetto passato. La classe org.apache.struts.action.Action La classe Action può avere compiti diversi a seconda del tipo di applicazioni in cui viene utilizzata. Nelle applicazioni più semplici si occupa di gestire la parte di business logic associata con una richiesta, mentre in altri casi la classe Action invocherà un altro oggetto (EJBs, oggetti CORBA, classi DAO) che si occuperà lui della parte di business logic; compito della Action sarà allora quello di gestire gli errori e di interpretare i dati nelle HttpServletRequest per passarli come variabili Java alle classi che si occupano delle varie operazioni. Quindi la Action non viene utilizzata propriamente per gestire la business logic, ma si occupa di altre funzioni, come il logging, il controllo della sessione, l’autenticazione ed autorizzazione di una richiesta, demandando gli altri servizi a delle classi esterne al framework. In questo modo possiamo riutilizzare con maggiore semplicità queste classi esterne anche in altre applicazioni ed inoltre, nel caso venga modificato qualcosa nel servizio, la classe Action non dovrà essere re-implementata. Un esempio sostanziale potrebbe essere quello delle chiamate al Database; se scriviamo il codice che tira su la connessione e chiama le query all’interno della classe Action, allora dovremo riscriverlo per ogni Action e non potremmo riutilizzarlo all’interno di un’altra applicazione. Invece se ad occuparsi delle connessioni c’è un’altra classe invocata dalla Action allora potremmo riutilizzarla anche in altri parti ed in caso di modifiche (non so utilizzo di un DataSource al posto di una connessione singola etc.) la classe Action rimarrà tale e quale. Come abbiamo visto nel precedente articolo nel metodo process() della ActionServlet della versione 1.0.2 e del RequestProcessor della versione 1.1, venivano create e gestite queste classi Actions il cui unico compito è quello di essere il ponte tra le richieste dei clients e la parte di business. Quindi nel metodo processActionCreate() della classe di controller si verificherà se esiste già una istanza della classe Action trovata nell’ActionMapping, cercando se è già contenuta dentro un HashMap, dove le chiavi sono i nomi delle Actions e i valori sono le loro istanze; se non troverà nulla allora creerà una nuova istanza e la inserirà all’interno dell’HashMap creando quindi una sola istanza per ogni Action del framework. Per garantire che un solo thread creerà l’istanza da utilizzare nell’applicazione questa parte di codice sarà sincronizzata (utilizzando quindi synchronized (actions) {…. } ). Lo sviluppatore dovrà quindi fare molta attenzione quando dovrà implementare le sue classi Actions, stando molto accorto a gestire nella maniera più appropriata possibile un ambiente multi-threading. Tutte le richieste che avvengono da parte dei clients faranno riferimento all’unica istanza creata dal framework, così come già fanno riferimento ad un’unica istanza della ActionServlet, quindi fate bene attenzione a non utilizzare all’interno della Action variabili globali che rappresentino lo stato di un singolo client. Una volta istanziata la classe da utilizzare, oppure recuperata la sua istanza dall’HashMap, viene chiamato il metodo processActionPerform() che non fa altro che invocare il metodo contenuto nella vostra classe. Eh si perché da questo momento entrate in gioco voi; le classi di Action vengono implementate completamente da voi secondo le vostre esigenze. Quindi non vi resta che creare una classe che estenda la classe Action ed implementare al suo interno nel caso si stia utilizzando la versione 1.0.2 il metodo perform(), mentre nella versione 1.1 execute(). Entrambi i metodi ricevono gli stessi parametri in ingresso e ritornano un oggetto di tipo ActionForward, ma allora perché si è sentito il bisogno di crearne uno nuovo? Semplicemente perchè il metodo perform() della classe Action nella versione 1.0.2 gestiva soltanto delle eccezioni di tipo IOException e ServletException, mentre il metodo execute() gestisce una eccezione più generica Exception; la decisione di scrivere un altro metodo piuttosto che riscrivere quello precedente è stata quindi un passo obbligato per mantenere la compatibilità con le versioni precedenti. Il bisogno di rimanere compatibili con i vecchi Struts comporterà altre variazioni di questo tipo ed il “salvataggio” di alcune classi che altrimenti sarebbero sparite. Uno degli errori più comuni quando si sviluppa le prime volte con Struts e non si usano degli IDE che ci aiutino nella creazione delle classi Action è proprio durante l’ override del metodo perform() (o execute(), che poi non fa altro che chiamare il metodo perform()). Infatti molto spesso si sbaglia la signature del metodo, errore che non verrà segnalato né durante la compilazione delle classi (perché il metodo può essere non implementato) né in runtime (perché viene invocato il metodo di default che ritorna null). Estendendo la classe Action ne ereditiamo i metodi, tra cui quello perform(), che però non essendo astratto e ritornando un valore null può non essere implementato. Una volta scritta la nostra classe e la business logic all’interno del metodo perform(), non ci resta che specificare nello struts-config.xml quando questa Action deve essere chiamata in causa. …. <action path="/Path" type="percorso.della.classe.NomeAction"> <forward name="success" path="/AltraAction.do" /> <forward name="failure" path="/jspDiFailure.jsp" /> </action> …. Oltre ai metodi perform() ed execute() visti finora, la classe Action fornisce ulteriori funzionalità utili per migliorare la gestione della nostra applicazione : gestione degli errori, gestione dei messaggi etc. Utilizzando quindi i metodi saveErrors() e saveMessages() (introdotto nella versione 1.1) è possibile salvare sia dei messaggi di errore che dei semplici messaggi all’interno della richiesta, associando ognuno di questi ad una chiave specifica. In questo modo potremo visualizzare degli errori o dei messaggi di warnings all’interno delle pagine di view tramite i tags <html:errors> e <html:messages>. Sicuramente una delle funzionalità più interessanti è la gestione del flusso dell’applicazione, che nel framework assume il nome di token, definizione presa da “Core Pattern” di SUN. In pratica si controlla che l’utente non possa entrare nel flusso e digitare il tasto di invio di un form per due volte, dopo essere tornato indietro con il tasto Back del browser. Vediamo come effettuare questo tipo di controllo: nella action che invocherà la pagina di view, con il form da non “postare” per due volte con il “trucchetto” del tasto Indietro, salvo il token all’interno della sessione(saveToken()); quando la Action effettua il forward verso la pagina di view con il form questo token verrà salvato nella pagina attraverso un campo “hidden”; quando invio i dati del form compilato faccio partire un’altra Action dove controllo se il token che ho in sessione corrisponde a quello che ho in sessione (isTokenValid()). Se non sono uguali torno un errore, altrimenti continuo le mie operazioni, non prima però di aver eliminato il token dalla sessione (resetToken()) rendendo irrealizzabile il trucco visto prima.
Quindi gli “attori” che ci aiutano in questa funzionalità sono : protected boolean isTokenValid(HttpServletRequest request, boolean reset) , effettua un controllo sulla sessione per vedere se ad una transazione è associato un token. Può tornare “false” se nessuna sessione è associata alla richiesta, se nessun token è stato salvato nella sessione o nella richiesta, se il token che si trova nella sessione non è uguale a quello della transazione corrente; protected void resetToken(HttpServletRequest request) , ripulisce la sessione dal token salvato, che non sarà più necessario nella prossima richiesta; protected String generateToken(HttpServletRequest request) , per permettere solo una richiesta singola in quella transazione genera un token, una stringa esadecimale costituita da un id di sessione e dal tempo corrente in millisecondi, codificata utilizzando la classe MessageDigest(questo metodo viene invocato all’interno del metodo saveToken()); protected void saveToken(HttpServletRequest request) , salva il token all’interno della sessione.
La classe org.apache.struts.actions.DispathAction

Fig. 6 Terza Parte Abbiamo visto che la classe Action si occupa di gestire una singola operazione all’interno del metodo perform(), quindi per gestire n operazioni dovremo allora creare n classi Actions. Questo tipo di approccio può risultare molte volte svantaggioso sotto alcuni punti di vista : eccessiva produzione di classi in applicazioni grandi, scrittura di codice che porta via molto tempo etc. Per venire incontro a questo tipo di problematica è stata creata la classe DispatchAction, il cui unico obiettivo è quello di poter comprendere all’interno di un’unica classe più operazioni che abbiano un collegamento logico (per esempio in un carrello della spesa l’operazione di aggiunta e l’operazione di eliminazione di un componente). Vediamo ora cosa dobbiamo fare per utilizzare correttamente questa classe. Per prima cosa creiamo la nostra classe “mia.classe.MiaDispatchAction” che estende la classe org.apache.struts.actions.DispatchAction ed aggiungiamo un metodo per ogni funzionalità da gestire al suo interno (tenendo sempre presente che ci deve essere un collegamento logico, non perché poi la classe non funziona, ma per un più corretto sviluppo che permetta una migliore manutenzione e comprensione). I metodi creati devono ricevere in ingresso gli stessi parametri che di solito passiamo nel metodo perform() della classe Action (ActionMapping, ActionForm, HttpServletRequest, HttpServletResponse) e ritornare un oggetto di tipo ActionForward, questo perché logicamente agiscono come dei metodi execute() raggruppati all’interno di un’unica Action, quindi con una signature diversa soltanto nel nome. package mia.classe; // import da inserire public class MiaDispatchAction extends org.apache.struts.actions.DispatchAction { // i metodi creati all’interno della classe che devono essere chiamati a seguito della richiesta, devono essere public altrimenti non sono visibili al metodo perform() della DispatchAction che li invoca public ActionForward metodoUno(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { // implementazione del metodo uno } public ActionForward metodoDue(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { // implementazione del metodo due } } Come potete vedere all’interno della nostra classe non abbiamo inserito il metodo perform() e non certo per una dimenticanza. Infatti a differenza delle classi che estendono Action qui il metodo perform() è già implementato all’interno della classe DispatchAction e sarà proprio lui che si occuperà di volta in volta di chiamare il metodo associato ad una determinata funzionalità (vedremo più avanti come farà). Scritta la nostra classe non ci resta altro da fare che inserire la sua mappatura all’interno dello struts-config.xml. …. <action path="/miaDispatch" parameter="nomeParametro" scope="request" type="mia.classe.MiaDispatchAction" validate="false"> <forward name="Success" path="/paginaDiView.jsp" /> </action> ..... Rispetto al mapping di una normale Action vediamo che è stato soltanto aggiunto l’attributo “parameter”. In questo attributo noi specifichiamo il nome del parametro con cui salveremo all’interno della request il nome del metodo da invocare. Quindi nel nostro caso dovremo trovare nella request il parametro “nomeParametro” che avrà come valore la stringa “metodoUno” o “metodoDue”, a seconda di quale metodo vogliamo invocare (per esempio inserito all’interno dell’url “http://localhost:8080/miaapplicazione/miaDispatch.do?nomeParametro=metodoUno” ;attenzione state molto attenti che il nome del parametro sia perfettamente uguale al nome del metodo da chiamare anche nelle maiuscole). Così tramite l’utilizzo della reflection è possibile chiamare un metodo data la stringa che contiene il suo nome esatto. Vediamo qui un estratto del codice che si occupa di farsi ritornare il nome del metodo e di chiamarlo: … // ritorna il valore impostato nell’attributo parameter cioè “nomeParametro” String parametro = mapping.getParameter(); // ritorna il nome del metodo da invocare, per esempio “metodoUno” String nomeMetodo = request.getParameter(parametro); // ritorna il metodo da invocare, per esempio metodoUno Method metodo = this.getClass().getMethod(nomeMetodo, tipi); // invoco il metodo passandogli i parametri di ingresso (mapping, form, request, response) sotto forma di array di Object e ritorno un ActionForward ActionForward forward = (ActionForward)metodo.invoke(this, parametriDiIngresso); .... Abbiamo visto come si implementa una classe che estende DispatchAction, creando i metodi “metodoUno” e “metodoDue”, ma ricordiamo che la stessa cosa si poteva fare costruendo due Actions separate ognuna per ogni metodo. La classe org.apache.struts.actions.SwitchAction (versione 1.1) Con l’introduzione nella nuova versione di Struts della possibilità di creare più sotto-applicazioni, in modo da poter gestire in maniera più consona il lavoro in team, si è venuta anche a creare il bisogno di gestire una nuova situazione, cioè quella di supportare il passaggio da una sotto-applicazione all’altra. Così nasce la classe SwitchAction. Quando si decide di invocare questa Action lo sviluppatore deve assolutamente ricordarsi di passare nella request i seguenti parametri, se non vuole ottenere una pagina di errore: "prefix” , il prefisso dell’applicazione a cui si deve passare. Questo parametro deve essere preceduto dal carattere “/” oppure può essere uguale ad una stringa vuota “” nel caso si voglia passare all’applicazione di default; “page” , la pagina a cui reinderizzarsi una volta che si è passati nella sotto-applicazione indicata prima.
La classe org.apache.struts.actions.ForwardAction In molte occasioni quando ci spostiamo da una pagina di view all’altra non abbiamo bisogno di passare attraverso una Action per effettuare delle operazioni di business, quindi non facciamo nient’altro che collegare le due pagine con un link. Questa non è una soluzione molto corretta, perché in questo modo andiamo a violare un passaggio del pattern MVC, eliminando la parte di controller. Ciò potrebbe creare dei problemi nella visualizzazione dei messaggi nelle pagine di view, anche perché è proprio il controller che si occupa di gestire la richiesta e di inserire all’interno di questa le classi ApplicationConfig e MessageResources. Per cercare di evitare questo tipo di problema, senza nello stesso tempo costringere lo sviluppatore a costruire una classe Action che abbia come unico scopo il forward verso una pagina di view, si è creata la classe ForwardAction con l’unico scopo di collegare due pagine di view passando per il controller. A differenza delle altre Actions il percorso a cui indirizzare l’applicazione dopo l’esecuzione del metodo perform() non viene indicato nel tag <forward> annidato nel tag <action>, ma nell’attributo “parameter”di quest’ultimo. La classe org.apache.struts.action.ActionForward Abbiamo visto prima che i metodi execute() o perform() delle classi Actions ritornano un oggetto di tipo ActionForward. Ma cosa rappresenta questa classe? Nient’altro che una destinazione a cui deve puntare la ActionServlet una volta terminate le operazioni all’interno delle classi Actions. E’ andato tutto bene quindi possiamo tranquillamente invocare una pagina di view o un’altra Action, oppure ci sono state delle eccezioni e quindi dobbiamo puntare ad una pagina di errore? La ActionForward contiene le informazioni che ci servono per proseguire il flusso dell’applicazione. Queste informazioni sono impostate nelle sue proprietà: name – Il nome logico con cui bisognerebbe cercare l’istanza di questa classe all’interno dell’ActionMapping fra quelle disponibili. path – Il percorso relativo al contesto (o anche al modulo nella versione 1.1) a cui il controllo deve essere reinderizzato. redirect – Un valore booleano che se impostato a “true” indica alla controller servlet di chiamare HttpServletResponse.sendRedirect() sul percorso indicato, altrimenti il metodo RequestDispatcher.forward(). Dalla versione 1.1 questa classe estende org.apache.struts.config.ForwardConfig e ne eredita il parametro : contextRelative – Un booleano che serve per specificare se il percorso è relativo al contesto.
Anche questa classe come la ActionMapping vista prima sarebbe dovuta essere deprecata e sostituita con la classe ForwardConfig, ma anche per questa ci sono state delle complicazioni dovute alla compatibilità con le vecchie versioni di Struts di cui questa classe era una delle API fondamentali.
|