Sei sulla pagina 1di 314

Prefazione

La strada che porta alla realizzazione di ottime web application lastricata con il sudore e le angosce di coloro che ci hanno preceduti. L'idea di prendere una complessa applicazione client e farla funzionare su Internet stata certo coraggiosa, ma forse anche un po' avventata. Quando si parla di web applications non si intendono quelle piccole, semplici applicazioni che accettano input in uno o due moduli e poi trasferiscono tali informazioni a un database. Si intende parlare, invece, di quelle grandi applicazioni che attraversano svariati strati (tiers), utilizzando molte tecnologie differenti e interessando l'ambito dell'analisi e dell'utilizzo dei modelli architetturali (design patterns). La progettazione e la realizzazione di questo tipo di applicazioni spingono gli sviluppatori al limite di ci che logicamente e fisicamente possibile. Sono state proposte migliaia di soluzioni per i problemi posti da tali applicazioni: alcune soluzioni hanno preso campo e altre no, come ovvio quando non si risolvono le necessit degli utenti. Ma, come accaduto con l'evoluzione umana, le caratteristiche dimostratesi valide sono state trasmesse ai posteri, mentre quelle inadatte agli scopi prefissi sono scomparse con il procedere del tempo. L'interfaccia utente (User Interface, UI) presente in Smalltalk-80 era stata progettata sulla base dell'infrastruttura (framework) Model-View-Controller (MVC). Con il passare del tempo, questa infrastruttura stata adottata come pattern architetturale classico, ed stata utilizzata in molti scenari diversi. Altre infrastrutture UI, come quella di Java Swing, utilizzano concetti simili. Chi ha progettato l'architettura delle specifiche JavaServer Pages (TSP) ha introdotto in maniera formale il concetto astratto di due tipi di modello per le soluzioni basate sul web: il Model 1 e il Model 2. La caratteristica che li distingue che il Model 2 utilizza un componente separato per gestire le responsabilit del controller. Ci consente alle JSP di concentrare la loro azione esclusivamente sulla resa della vista utilizzando come modello i JavaBeans, divenendo cos in effetti una implementazione astratta di una architettura MVC web-based. L'infrastruttura Struts, creata da Craig R. McClanahan e donata alla Apache Software Foundation (ASF) nel 2000, un'implementazione open source dei concetti alla base del Model 2. Questo libro tratta della versione 1.1 di Struts ma, dal momento che la retrocompatibilit stato uno degli obiettivi nella progettazione della 1.1, anche chi utilizza la versione 1.0 di Struts pu trarre vantaggio dalla lettura del testo. Se c' un aspetto davvero importante da imparare con questo libro, che i framework come Strati rappresentano un ottimo investimento. Se si realizzano applicazioni, sia del tipo web-based che non, sar il caso di utilizzare almeno una infrastruttura di questo tipo e, nel campo dei framework web, Struts uno dei migliori

Struttura del libro


Per gettare le fondamenta su cui si basa il resto del materiale, il libro comincia con una trattazione preliminare: un ripasso per alcuni, una serie di concetti nuovi per altri. A partire da questo punto, poi, si esploreranno i componenti dell'implementazione MVC Struts, compresa una panoramica sui custodi tags delle JSP che fanno parte del framework. Poi, per completare la comprensione del valore dell'infrastruttura Struts, si passeranno in rassegna svariati argomenti complicati ma importanti relativi alla costruzione di applicazioni basate sul web. Capitolo 1 Introduzione

Questo capitolo tratta alcuni concetti preliminari, quali il modello MVC, il Model 2 e le caratteristiche di una infrastruttura software. Probabilmente molti sviluppatori saranno gi a conoscenza di alcune o di tutte le informazioni qui presentate, ma necessario garantire che tutti i lettori partano dalla stessa base di conoscenze: i concetti presentati in questo capitolo contribuiscono a creare le fondamenta per il resto del volume. Capitolo 2 All'interno dello strato web L'infrastruttura Struts framework si basa sulla tecnologia Java Servlet e, in misura minore, su quella delle JavaServer Pages, risultando perci strettamente correlata a un container web. Per gli sviluppatori Struts, la comprensione del modo in cui il web container elabora le richieste del client fondamentale ai fini della comprensione globale del framework stesso. Questo capitolo tratta i componenti del web container e le responsabilit di ciascuno di essi. Capitolo 3 Panoramica sul framework Struts Questo capitolo fornisce una panoramici dell'infrastruttura Struts, pur non trattandone tutte le caratteristiche o entrando in estremo dettaglio. Viene sottolineato il modo in cui tutte le parti si integrino nell'ambito dell'architettura MVC e Model 2 presentata nel Capitolo 1 Capitolo 4 Configurare le applicazioni Struts L'infrastruttura Struts utilizza due tipi separati di file di configurazione, i quali sono comunque interrelati. Tali file devono essere configurati in maniera corretta prima che un'applicazione possa funzionare nel modo corretto. Vista la diffusione e la flessibilit del linguaggio XML, entrambi i tipi di file di configurazione sono basati su di esso: il capitolo presenta la sintassi di questi file. Capitolo 5 I componenti controller di Struts L'infrastruttura Struts utilizza una servlet per elaborare le richieste in entrata; ci nonostante, essa pu fare affidamento su molti altri componenti che sono parte del dominio del controller per aiutarlo a portare a termine i suoi compiti. Questo capitolo tratta in maniera approfondita i componenti che gestiscono la funzionalit del controller nel framework. Capitolo 6 I componenti model di Struts Questo capitolo tratta i componenti che costituiscono la porzione model di una applicazione Struts. Il model rappresenta i dati business per un'applicazione e dovrebbe riprodurre in maniera molto fedele le entit reali e i processi logici dell'organizzazione. Questo capitolo esplora i ruoli e le responsabilit dei componenti del modello all'interno del framework Struts e si concentra sulla costruzione di una implementazione, corretta dal punto di vista dell'architettura, della applicazione Storefront. Particolare attenzione viene data all'utilizzo di un'infrastruttura di persistenza che possa essere integrata in un'applicazione Struts in maniera semplice e senza sforzo. Capitolo 7 I componenti view di Struts Il Capitolo 7 presenta i componenti che costituiscono la porzione view dell'infrastruttura Struts. Il framework utilizza i componenti della view per mostrare il contenuto dinamico al client. Basati principalmente sulle JavaServer Pages, tali componenti forniscono supporto per l'internazionalizzazione delle applicazioni al pari che per l'immissione dati da parte dell'utente, l'acccttazione e la validazione degli stessi e la gestione degli errori: tutto ci rende pi facile per lo sviluppatore concentrarsi sui requisiti dell'attivit. Il capitolo conclude la discussione sulle tre parti con cui il framework Struts implementa il pattern MVC. Capitolo 8 Librerie di tag JSP personalizzati Questo capitolo parla delle diverse categorie di tag e del modo in cui essi possono contribuire a rendere ancor pi semplice lo sviluppo di applicazioni con l'infrastruttura Struts. L'intento non di fornire un quadro di riferimento esaustivo su tutti i tag che fanno parte delle tag library di Struts tali informazioni sono reperibili all'interno del manuale utente di Struts o nei JavaDocs. Il vero scopo di questo capitolo di mettere in luce i benefici dell'utilizzo di tali librerie di tag di Struts e di fornire alcune strategie che possono rendere meno problematico il passaggio all'utilizzo dei tag. Capitolo 9 Estendere il framework Struts Uno dei maggiori vantaggi dell'utilizzo di un framework la possibilit di estenderlo e personalizzarlo sulla base delle necessit dell'applicazione. Il framework Struts non fa eccezione, e fornisce svariati e importanti punti di estensione per gli sviluppatori. Questo capitolo passa velocemente in rassegna alcuni di questi punti di estensione ed esamina i vantaggi e gli svantaggi dell'estensione dell'infrastruttura.

Capitolo 10 Gestione delle eccezioni

In questo capitolo si tratta del modo in cui usare il meccanismo di gestione delle eccezioni di Java nell'ambito delle proprie applicazioni Struts, per renderle pi robuste e metterle nelle condizioni di reagire in maniera elegante quando le cose non vanno come ci si aspettava. Viene prestata particolare attenzione alle differenze che sussistono tra la gestione delle eccezioni in maniera programmatica e quella effettuata in maniera dichiarativa tramite la nuova apposita funzionalit inserita nella versione 1.1 di Struts. Capitolo 11 Il framework Validator Questo capitolo presenta il framework Validator, appositamente creato per funzionare con i componenti di Struts. Il Validator consente di configurare in maniera dichiarativa le procedure di validazione; per un'applicazione Struts senza dover programmare una speciale logica di validazione Capitolo 12 Internazionalizzazione e Struts Il capitolo si concentra su quanto occorre fare per rendere una applicazione Struts disponibile a clienti di tutto il mondo, indipendentemente dalla lingua o dalla collocazione geografica. Come accade spesso nello sviluppo del software, una accurata pianificazione rappresenta uno dei passi pi importanti per contribuire al successo. Dopo la lettura del capitolo, il lettore dovrebbe essere in grado di realizzare applicazioni Struts che possano andare bene per una vasta gamma di destinatari. Capitolo 13 Struts ed Enterprise JavaBeans Il Capitolo 13 tratta gli argomenti che necessario prendere in considerazione quando si sviluppa un'interfaccia tra le azioni Stnits e lo strato di un'applicazione. Ci si concentra sull'interfacciamento con un modello costruito con gli Enterprise JavaBeans (EJB). Capitolo 14 Usare Tiles Questo capitolo tratta del framework Tiles, che fa ora parte della distribuzione di riferimento di Struts. L'infrastruttura (Tiles un framework di template (modelli standard di riferimento) che riduce la quantit di codice ridondante contenuta in una web application e consente agli sviluppatori una migliore separazione tra contenuto e presentazione. Capitolo 15 Il logging nelle applicazioni Struts Questo capitolo prende in esame il modo in cui l'utilizzo del logging, ovvero della registrazione delle attivit tramite opportuni file di log, possa aiutare a identificare i difetti prima che le applicazioni Struts vadano in produzione o, nel caso il software sia gi giunto a essere utilizzato in tale fase, il modo in cui il logging possa aiutare a identificare i problemi e ad arrivare alle soluzioni in maniera pi rapida. Capitolo 16 Packaging delle applicazioni Struts Questo capitolo tratta delle pratiche rivelatesi migliori per il confezionamento in package e per il dispiegamento (deploy) di una applicazione Struts, e di ci che necessario per automatizzare il processo di compilazione per il proprio ambiente. Particolare attenzione viene data ad Ant, il tool di compilazione basato su Java che disponibile in Jakarta. Capitolo 17 Attenzione alle prestazioni Il capitolo analizza le implicazioni sulle prestazioni derivanti dall'uso del framework Struts e delle tecnologie ad esso associate per realizzare web application, e tratta il modo in cui determinate decisioni sull'architettura e sulla programmazione influiscano sul complesso delle prestazioni delle applicazioni. Vengono trattate le verifiche sulle prestazioni, sul carico e sullo stress, con tutti i passi necessari per portarle a termine Appendice A Cambiamenti rispetto a Struts 1.0 L'appendice elenca le nuove caratteristiche della release 1.1. Appendice B Download e installazione di Struts L'appendice tratta le procedure da seguire per scaricare e installare Struts nel proprio ambiente operativo. Appendice C Risorse Questa appendice elenca svariate risorse che possono contribuire a un approfondimento delle conoscenze acquisite tramite questo libro.

Convenzioni grafiche
In questo libro vengono usate le seguenti convenzioni grafiche: il carattere cosivo utilizzato per: percorsi Unix, nomi di file e programmi indirizzi Internet, nomi di dominio e URL termini nuovi la prima volta che vengono definiti
Il carattere grassetto utilizzato per:

nomi di elementi d GUI (nomi di finestre, bottoni, menu di selezione, etc.)


Il carattere senza grazie del tipo Constant width utilizzato per:

linee di comando e opzioni che dovrebbero essere digitate cos come sono presentate nomi e parole chiave di programmi Java, compresi nomi di metodi, di variabili e di classi. nomi di elementi e tag XML, di attributi, e altri costrutti XML che appaiono cos come sono realmente in un documento XML L'immagine a fianco, associata al carattere piccolo, denota un'indica un consiglio o una zione, nota in genere.

L'immagine a fianco, associata al carattere piccolo, indica di prestare attenzione o cautela

Commenti e domande
possibile indirizzare all'editore commenti e domande su questo libro:
O'Reilly & Associates, Inc. 1005 Gravenstein Highway North Sebastopol, CA 95472 800-998-9938 (in the U.S. or Canada) 707-829-0515 (international or local) 707-829-0104 (fax)

Esiste una pagina web relativa al presente libro, che contiene glierrata corrige [nella traduzione italiana, gli errori segnalati sono gi stati corretti, N.d.T.],gli esempi e ulteriori informazioni. L'indirizzo di tale pagina :
http://www.oreilly.com/catalog/jakarta/

L'e-mail a cui inviare commenti o fare richieste di tipo tecnico concernenti il libro :
bookquestions@oreilly.com http://www.oreilly.com

Per ulteriori informazioni su libri, conferenze, Resoti ree Centcrs e la O'Reilly Network, si pu visitare il sito web O'Reilly:

Ringraziamenti
Scrivere un libro come questo non comporta mai solamente il lavoro di un'unica persona; ci vuole davvero un esercito di soldati temprati e questo libro non fa eccezione. A partire dagli editor, per passare all'organizzazione commerciale, fino a chi ha corretto il manoscritto, semplicemente questo libro non sarebbe stato possibile senza il gruppo appassionato di persone che hanno rinunciato spesso al riposo e ai fine settimana per garantire che la qualit fosse la pi elevata possibile. Tutti i meriti vanno alle eccezionali persone che hanno preso parte a tale realizzazione, mentre mi assumo la responsabilit di ogni errore. Per primi, devo ringraziare i miei due editor alla O'Reilly, Brctt McLaughlin e Robert Eckstein, tra i redattori pi appassionati e sinceri con cui abbia mai lavorato. Mi auguro che ci sia la possibilit di lavorare ancora irfsieme su progetti futuri. I vostri consigli e la vostra guida hanno reso il compito molto pi facile di quanto avessi potuto immaginare. Vorrei inoltre ringraziare Kyle Hart della divisione commerciale per il suo aiuto, davvero molto apprezzato. Vorrei poi ringraziare la squadra di ottimi sviluppatori con i quali ho inizialmente imparato Struts. Il legame della nostra piccola squadra NV stato qualcosa di speciale che si verifica solo di quando in quando: ho davvero apprezzato il lavoro insieme a tutti voi. Le seguenti persone hanno svolto un ruolo speciale, fornendomi consigli sul contenuto del libro e sull'indirizzo da tenere: Steve Ardis, Jill Entinger, See Yam Lim e Ted Husted (anche se Ted probabilmente non si render conto del suo contributo in tal senso). Quando ho deciso di rendere disponibili le bozze dei capitoli del libro, dissi che, se chi li leggeva avesse avuto la gentilezza di fornire dei riscontri, avrei ringraziato i "revisori" in questa sezione del libro... be' non mi ero reso conto di quanti mi avrebbero preso in parola! Si tratta di cos tante persone che non posso ringraziarle tutte singolarmente. Pi di cento lettori hanno fornito feedback per uno o pi capitoli del libro. La comunit Struts di certo una delle migliori: quasi tutti gli iscritti alla mailing list di Struts hanno fornito il loro contributo, in un modo o nell'altro. A ciascuno di loro va il mio grazie. Grazie a: John Guthrie, David Karr, Brent Kyle, Stefano Scheda e Rick Reumann per aver posto le giuste domande, a Mark Galbreath per avermi fatto ricordare della mia insegnante di Inglese delle scuole medie e a James Mitchcll e James Holmes per avermi coinvolto nel gruppo Struts di Atlanta.

Devo un ringraziamento speciale a Tim Drury per il suo aiuto sul Capitolo 16. Le sue competenze su Ant sono riconosciute a livello mondiale. Ringraziamenti speciali anche a Brian Keeton, che ha scritto il Capitolo 13 perch io sono troppo lento: il miglior sviluppatore di EJB che conosca. Si tratta in entrambi i casi di ottimi amici, semplicemente tra gli esseri umani migliori che io abbia mai incontrato: il solo fatto di conoscerli mi ha reso una persona migliore. Mi auguro sia possibile lavorare ancora insieme. Il gruppo di TheServerSide.com merita uno speciale grazie da parte mia e dei revisori per aver reso disponibili in download le bozze dei capitoli di questo libro: un esempio notevole di ci che il futuro riserva alle pubblicazioni librarie. Sarebbe una grave negligenza non ringraziare l'intera comunit che gravita intorno a Struts: ho intrattenuto corrispondenza con molti intelligenti sviluppatori che si sono oltretutto rivelati persone divertentissime con cui trascorrere il tempo, nei casi in cui poi ci siamo conosciuti di persona. Il feedback e i consigli ricevuti durante questo processo sono stati formidabili: spero che tutti ne abbiano tratto un qualche beneficio e apprezzo la pazienza di tutti. Infine, tutti gli sviluppatori Struts dovrebbero ringraziare Craig McClanahan che ha avuto l'intuizione e le capacit per creare questo framework e renderlo disponibile come software libero per la comunit. Avrebbe potuto pensare di "farci i soldi", mettendo su una "Struts, Inc.", ma si reso conto che, seguendo l'altra strada, sarebbe comunque stato ricompensato moltissimo, e credo che cos sia successo. Craig, un ottimo framework, di cui devi andare orgoglioso. E infine, a tutti coloro che si sono impegnati nel progetto Struts: che cosa sarebbe stato veramente, senza di voi? Grazie per tutto il vostro aiuto.

CAPITOLO 1

Introduzione

Il framework open source Struts stato creato per facilitare chi sviluppa web application basate sulle tecnologie Servlet Java e JavaServer Pages (JSP). Come nel caso di un edificio, una web application deve avere delle solide fondamenta a partire dalle quali pu essere eretta la struttura restante. Il framework Struts fornisce agli sviluppatori una infrastruttura unificata sulla base della quale possono essere costruite applicazioni Internet. Usare Struts come base di partenza consente agli sviluppatori di concentrarsi sulla costruzione delle applicazioni rispondenti alle esigenze reali invece di focalizzarsi sull'infrastruttura stessa. L'infrastruttura Struts stata realizzata da Craig R. McClanahan ed stata donata alla Apache Software Foundation (ASF) nel 2000. Il progetto coinvolge adesso in maniera cospicua diversi soggetti in tutto il mondo, e molti sono gli sviluppatori che contribuiscono alla buona riuscita del framework. Struts uno dei molti progetti ben noti e coronati da successo che fanno parte del grande quadro Apache Jakarta, tra i quali si possono ricordare Ant, log4j e Tomcat. L'obiettivo finale del progetto Jakarta di fornire, in maniera aperta e cooperativa, soluzioni server basate sulla piattaforma Java che possanoreggere gli standard di mercato.

1.1 Breve storia del web


Nessun libro sulle tecnologie applicate al Web potrebbe dirsi completo senza un breve sguardo al modo in cui il World Wide Web (WWW) ha raggiunto la sua attuale diffusione. Molte cose sono successe da quando i primi documenti ipertestuali furono inviati attraverso Internet. Nel 1989, quando i fisici del laboratorio del CERN proposero l'idea di condividere le informazioni scientifiche tra i ricercatori attraverso documenti ipertestuali, non avevano idea di quanto sarebbe cresciuto il Web o di quanto sarebbe dive nuto essenziale alla vita quotidiana per la maggior parte del mondo industrializzato. Il Web fa ormai parte del nostro sistema di comunicazione. Ci volle un p prima che i benefci derivanti dall'uso del Web divenissero chiari al di fuori del CERN ma, come ormai ben noto, tutto ci sfociato in quel che oggi usiamo quotidianamente. Ai suoi inizi, il Web fu progettato per trattare documenti statici, ma stato naturale poi progradire verso la possibilit di generare il contenuto dei documenti in maniera dinamica. Lo standard Common Gateway Interface (CGI) stato creato proprio a tale scopo. Il CGI consente ai server web di interagire con applicazioni esterne in maniera tale che le pagine ipertestuali non debbano pi necessariamente essere statiche. Un programma CGI pu recuperare risultati da un database e inserirli in una tabella o in un documento ipertestuale. Analogamente, i dati presenti in una pagina di ipertesto possono essere inseriti nel database. Questa tecnologia ha aperto possibilit infinite e, in effetti, a met degli anni Novanta del secolo scorso, ha fatto nascere la "mania" per Internet che continua fino ai nostri giorni. Sebbene le applicazioni CGI svolgano molto bene i loro compiti, tale approccio ha comunque alcune serie limitazioni. Da un lato, le applicazioni CGI sono molto esigenti in termini di risorse. Per gestire ogni richiesta proveniente da un browser viene creato un nuovo, processo del sistema operativo (OS), piuttosto pesante per la macchina. Una volta che lo script CGI ha concluso le sue operazioni, il processo deve essere distrutto dal sistema operativo. Questo continuo avvio e arresto di processi pesanti terribilmente inefficiente. Si pu immaginare facilmente quanto possa essere lento il tempo di risposta quando migliaia di utenti effettuino pi o meno contemporaneamente delle richieste alla stessa web application. Un altro grande limite di CGI sta nel fatto che difficile collcgarlo ad altre fasi dell'elaborazione delle richieste, perch funziona come processo separato dal server web. Ci rende difficile gestire attivit quali autorizzazioni, workflow e logging. Sono state proposte alcune alternative alle applicazioni standard CGI. Una di queste rappresentata da FastCGl, un'estensione per CGI, indipendente dal linguaggio, che non ha lo stesso modello di elaborazione di CGI standard. Questa estensione in grado di creare un solo processo pesante per ciascun programma FastCGI, consentendo cos che richieste multiple vengano eseguite all'interno di un unico processo. Ci nonostante, quando i client interagiscono in maniera concorrente con lo stesso programma FastCGI, esso deve creare un pool di processi per gestire ciascuna richiesta: e cos non si va tanto meglio rispetto al normale) CGI. Un altro problema delle applicazioni FastCGI che la loro portabilit limitata da quella del linguaggio in cui sono scritte. Altre alternative a CGI comprendono mod_perl per Apache, NSAPI per Netscape e ISAPI per il web server US di

Microsoft. Sebbene queste soluzioni forniscano spesso migliori prestazioni e maggiore scalabilit rispetto ai programmi CGI standard, hanno comunque i loro problemi di portabilit. Nel 1997, quando il linguaggio Java stava attraversando un periodo di straordinaria crescita e diffusione tra gli sviluppatori di applicazioni, fu creata la tecnologia Java Servlet. Questa nuova tecnologia per il web ha aperto una nuova strada pronta per essere esplorata dagli sviluppatori web.

1.2 Che cosa sono le servlet Java?


Le servlet Java sono diventate l'elemento principale per estendere e migliorare le web application usando la piattaforma Java. Forniscono un metodo component-based, e indipendente dalla piattaforma, per costruire web application. Le servlet non hanno gli stessi limiti di prestazione cui vanno incontro le applicazioni CGI standard. Le servlet sono pi efficienti del modello di thread dello standard CGI poich esse creano un solo processo e consentono a ciascuna richiesta utente di utilizzare un thread molto pi leggero, che viene gestito dalla Java Virtual Machine (JVM), per ottemperare alla richiesta. Molteplici richieste utente possono essere sistemate in thread nella stessa istanza di ima servlet. Una servlet viene mappata a uno o pi uniform resource locator (URL) e, quando il server riceve una richiesta a uno degli URL della servlet, viene invocato il metodo di servizio nella servlet che risponde. Dal momento che ciascuna richiesta associata con un thread separato, thread o utenti multipli possono invocare il metodo service contemporaneamente. Questa natura multithread delle servlet una delle ragioni principali per cui sono pi scalabili rispetto alle applicazioni standard CGI. Inoltre, dal momento che le servlet sono scritte in Java, esse non risultano limitate a una sola piattaforma o a un unico sistema operativo. Un altro vantaggio significativo del latto che sono scritte in linguaggio Java che le servlet sono in grado di sfruttare l'intera serie delle application programming interfaces (API) Java, tra cui Java DataBase Connectivity (JDBC) ed Enterprise JavaBeans (EJB). Questo stato uno dei fattori importanti nella veloce diffusione generale delle servlet: esisteva gi una ricca libreria Java su cui fare leva. Le servlet non vengono eseguite direttamente da un web server, ma hanno necessit di un loro contenitore, detto servlet container, al quale a volte ci si riferisce come servlet engine, che le ospiti. Questo container di servlet accoppiato in maniera flessibile a una particolare istanza di un server web e, insieme, questi due componenti lavorano insieme per soddisfare le richieste. La Figura l-l. illustra il modo in cui un web server e un servlet container cooperano per rispondere a una richiesta proveniente da un browser web.

Figura l-l. Elaborazione di una richiesta del client

Per le proprie servlet, gli sviluppatori sono liberi di scegliere tra i molti container disponibili: non dipendono da un particolare produttore o da una specifica piattaforma. Le servlet sono portabili e possono migrare attraverso diversi container, senza il bisogno di ricompilare il codice sorgente o di effettuare delle modifiche. Tutto ci porta a una situazione in cui si salvano "capra e cavolo", ideale per le web application: il miglior prodotto o componente per una particolare esigenza, evitando allo stesso tempo gli alti rischi legati normalmente alla scelta di una singola soluzione. Sul mercato sono disponibili diversi servlet container che hanno raggiunto ampia diffusione. Alcuni di essi sono container standalone che, per funzionare, devono essere connessi a un web server esterno, mentre altri forniscono sia il web server che il servlet container nello stesso prodotto. Ce ne sono anche alcuni che sono integrati in application server e forniscono tante funzionalit in pi rispetto a un semplice servlet container. Alcuni sono prodotti commerciali, mentre altri hanno un costo piccolo o insignificante.

Tabella I-I. Servlet container di larga diffusione

Servlet container Bluestone Borland Enterprise Server iPlanet Application Server Orbix E2A (formally iPAS) Jetty JRun Orion Application Server Resin SilverStream Apache Tomcat Weblogic Application Server WebSphere EAServer http://www.bluestone.com http://www.inprise.com

URL

http://www.sun.com/software/iplanet/ http://www.iona.com http://www.mortbay.com http://www.macromedia.com/software/jrun/ http://www.orionserver.com http://www.caucho.com http://www.silverstream.com http://jakarta.apache.org/tomcat/ http://www.bea.com http://www-4.ibm.com/software/webservers/appserv/ http://www.sybase.com

Per un elenco pi completo di servlet container disponibili, si pu visitare il sito della Sun dedicato all'industria delle servlet, all'indirizzo http://java.sun.com/products/servlet/industry.html. Sebbene le servlet siano ottime nello svolgere i loro compiti, presto risultato evidente che incorporare stabilmente output HyperText Markup Language (HTML) all'interno di una servlet come risposta a una richiesta presentava alcuni seri limiti. Innanzitutto, risultava difficile effettuare cambiamenti all'HTML, poich per ogni cambiamento era necessario ricompilare la servlet. In secondo luogo, il supporto per lingue diverse era difficile poich l'HTML inglobato stabilmente (hardcoding). Determinare lingua, regione e altre varianti relative all'utente e poi mostrare l'output qualcosa che si riesce a fare con difficolt. Molte applicazioni web costruite con le servlet tralasciano volutamente tutti i problemi di internazionalizzazione [1] rendendo disponibili servlet differenti, una per ogni locale supportato.
[1]

Ci si riferisce comunemente all'internazionalizzazione con la sigla "I18N" derivante dal fatto che la parola inglese internationalization comincia con la lettera I, finisce con la lettera N, e tra queste due sono contenuti 18 caratteri.

Infine, dal momento che l'HTML era inglobato all'interno della servlet, c'era un problema con le responsabilit. I web designer costruiscono pagine HTML ma la maggior parte di loro non ha, in genere, grande esperienza di linguaggio Java, per non parlare di profonda conoscenza delle architetture e della programmazione objectoriented. Quando si mescolano codice HTML e Java dentro una servlet, diventa difficile separare il design della pagina e i compiti di programmazione. Persino quando uno sviluppatore ha le abilit necessarie per svolgere entrambe le funzioni, qualunque modifica all'impianto grafico della pagina richiede una ricompilazione, che va ad aumentare i tempi di sviluppo e di verifica. La programmazione delle servlet un argomento cos vasto che non pu essere trattato qui con grande dettaglio. Maggiori informazioni sulla tecnologia delle Java Servlet si possono trovare nell'ottimo Java Servlet Programming di Jason Hunter [ed. originale O'Reilly, pubblicato in Italia da Hops Libri, N.d.T.]. anche possibile visitare il sito della Sun dedicato a tale tematica (http://java.sun.com/products/servlet/index.html). Le JavaServer Pages hanno rappresentato il passo successivo nella progressione di sviluppo delle tecnologie web basate sulla piattaforma Java. L'introduzione delle pagine JSP ad esse ci si riferisce normalmente cos ha contribuito a superare i limiti delle serviet appena ricordati, aprendo molte nuove porte per gli sviluppatori web.

1.3 JavaServer Pages


Il primo aspetto relativo alle JavaServer Pages che va compreso bene che esse sono la naturale estensione della tecnologia Java Serviet. In effetti, dopo una preventiva elaborazione da parte di un traduttore, le pagine JSP finiscono per essere niente di pi che servlet Java. Si tratta di un punto che molti sviluppatori inesperti hanno difficolt a comprendere. Le pagine JSP sono documenti di testo con estensione .jsp contenenti una combinazione di HTML statico e tag tipo XML e di scriptlet. I tag e gli scriptlet incapsulano la logica che genera il contenuto delle pagine. I file jsp vengono elaborati e trasformati in file java. A questo punto, un compilatore Java compila il sorgente e crea un file .class che pu andare in esecuzione in un servlet container.

Il "traduttore" che trasforma il file jsp in uno java si fa carico del noioso lavoro di creare una serviet Java a partire dalla pagina JSP. La Figura 1-2 illustra il modo in cui una pagina JSP viene tradotta e compilata in una servlet.

Figura l-2. Una pagina JSP viene tradotta e compilata in una servlet Java

La tecnologia delle JSP diventata una soluzione estremamente diffusa per costruire web application utilizzando la piattaforma Java. JSP presenta diversi vantaggi rispetto alle tecnologie concorrenti: JSP una specifica, non un prodotto. Gli sviluppatori possono scegliere la loro implementazione ideale. Le pagine JSP sono compilate, non interpretate, con un miglioramento delle prestazioni. Le pagine JSP supportano sia gli script che un accesso completo al linguaggio Java e possono essere estese tramite custom tag. Le pagine JSP condividono la caratteristica Write Once, Run Anywhere della tecnologia Java. Come accennato nella sezione precedente, uno dei limiti dell'hardcoding di HTML dentro le servlet quello di separare le responsabilit del design della pagina dalla programmazione della logica dell'applicazione. Tale separazione risulta pi facile con le pagine JSP perch i grafici HTML sono liberi di creare pagine web con lo strumento che preferiscono (gran parte dei tool pi diffusi oggigiorno sono in grado di lavorare con JSP e custom tag). Quando il layout della pagina stato stabilito dal web designer, gli sviluppatori JSP possono inserire scriptlet JSP e custom tag e salvare il file con estensione .jsp. Tutto qui. Quando arriva il momento di modificare o la grafica, oppure la logica della pagina, lo sviluppatore modifica la pagina JSP come necessario ed essa viene ricompilata automaticamente. Nel loro insieme, pagine JSP e servlet sono un'attraente alternativa rispetto ad altri tipi di programmazione dinamica per il web. Poich sono entrambe basate sul linguaggio Java, garantiscono indipendenza dalla piattaforma, estensibilit nell'ambito enterprise e, cosa ancora pi importante, semplicit di sviluppo.

JSP Scriptlets o tag libraries?


Molti sviluppatori ritengono che nelle pagine JSP dovrebbero essere impiegati i custom tag, piuttosto che gli scriptlet o le espressioni. Le ragioni sono: Gli scriptlet mescolano la logica con la presentazione. Gli scriptlet violano la separazione dei ruoli. Gli scriptlet rendono le pagine JSP difficili da leggere e manutenere. I custom tag, d'altro canto, accentrano il codice in un posto preciso e contribuiscono a mantenere la separazione delle responsabilit. Essi inoltre ottemperano alla logica del riutilizzo, dal momento che il medesimo tag pu essere inserito in diverse pagine mentre l'implementazione risiede in una sola collocazione. Con i custom tag vi inoltre minore ridondanza e si meno soggetti agli errori di copia e incolla.

1.4 Architecture JSP Model 1 e Model 2


Le primissime specifiche JSP presentavano due modalit diverse per la costruzione di web application tramite la tecnologia JSP. Si trattava delle architetture JSP Model 1 e Model 2. Sebbene si tratti di termini che non sono pi utilizzati nelle specifiche JSP, vengono ancora molto usati presso la comunit degli sviluppatori web. Le due architetture JSP si differenziano in alcuni aspetti fondamentali. La differenza principale sta nel modo in cui viene gestita l'elaborazione di una richiesta e da quale componente viene eseguito tale compito. Con l'architettura Model 1, la pagina JSP gestisce tutta l'elaborazione della richiesta e ha la responsabilit di mostrare il risultato al client, come si pu vedere nella Figura 1-3.

Figura l-3. Architettura JSP Model 1

Va notato che nel processo non viene coinvolta alcuna servlet extra. La richiesta del client viene inviata direttamente a una pagina JSP, la quale potrebbe comunicare con dei JavaBeans o altri servizi, ma in definitiva la JSP che seleziona la pagina successiva per il client. La vista successiva viene determinata sulla base della JSP selezionata oppure di parametri contenuti nella richiesta del client. Diversamente, nell'architettura Model 2 la richiesta del client viene intercettata prima da una servlet, detta controller servlet. Questa gestisce l'elaborazione iniziale della richiesta e determina quale pagina JSP deve essere visualizzata come successiva. Questo approccio viene illustrato in Figura 1-4.

Figura l-4. Architettura JSP Model 2

Come si vede, nell'architettura Model 2 un client non invia mai direttamente una richiesta a un pagina JSP. Ci permette alla servlet di effettuare una elaborazione in front-end comprendente autenticazione e autorizzazione, logging centralizzato, e internazionalizzazione. Una volta che stata completata l'elaborazione della richiesta, la servlet dirige la richiesta alla giusta pagina JSP. Il modo in cui viene determinata la pagina successiva da mostrare varia a seconda delle applicazioni. Per esempio, in applicazioni pi semplici, la pagina JSP successiva da mostrare potrebbe essere inglobata nella servlet, basandosi su richiesta, parametri e stato corrente dell'applicazione. In web application pi sofisticate potrebbe essere utilizzato un motore di flusso/regole. Come si evince, la differenza principale fra le due architetture sta nel fatto che il Model 2 inserisce una servlet controller, la quale fornisce un punto di accesso singolo e incoraggia un riutilizzo e una estensibilit maggiore rispetto all'approccio del Model 1. Con l'architettura Model 2, c' chiara separazione tra business logic, visualizzazione presentata ed elaborazione della richiesta. Si fa spesso riferimento a questa separazione come a un pattern Model-View-Controller (MVC). Sebbene l'architettura Model 2 possa sembrare eccessivamente

complicata, in pratica essa pu semplificare enormemente un'applicazione. Le web application costruite usando l'architettura del Model 2 hanno in genere una manutenzione pi facile e possono essere estese maggiormente rispetto ad applicazioni confrontabili che per sono costruite sull'architettura Model 1.

1.5 L'importanza del Model-View-Controller


Il pattern architetturale MVC non legato direttamente alle web application. In effetti, esso abbastanza comune nelle applicazioni Smalltalk, che in genere non hanno nulla a che fare con il Web. Come si visto sopra, l'approccio Model 2 si preoccupa di separare le responsabilit nelle web application. Consentire a una pagina JSP di gestire le responsabilit di ricevere una richiesta, eseguire parte della business logic, e poi determinare la vista successiva da mostrare, potrebbe rendere la JSP stessa ben poco attraente, per non parlare dei problemi di estensione e manutenzione che questo ampliamento delle responsabilit finisce per comportare. Sviluppo e manutenzione di una applicazione sono molto pi semplici se i diversi componenti di una web application hanno responsabilit chiare e distinte. Immaginiamo, ad esempio, di voler integrare la sicurezza nel proprio sito web. La prima pagina, in questo caso, di solito una schermata di login, che raccoglie e convalida user-name e password. Questa pagina dirige poi l'utente verso un'altra schermata che consente di continuare in maniera sicura. Per, se non c' nulla che impedisca all'utente di raggiungere direttamente una pagina, ciascuna pagina (in una architettura non MVC) deve avere le sue funzioni di sicurezza, il che si pu ottenere con un controllo di sicu rezza in ogni pagina: ma ci pu risultare poco pratico specialmente se ci sono alcune pagine che devono essere sicure e altre che non hanno tale necessit. Con l'architettura Model-View-Controller, la sicurezza risiede all'interno dell'oggetto controller. Dal momento che l'interfaccia verso l'utente viene fatta passare attraverso l'oggetto controller, il punto di accesso unico e quindi diventa unico anche il punto in cui effettuare i controlli di sicurezza. L'architettura MVC viene catalogata come design pattern in molti libri di progettazione software. Sebbene esistano alcune discrepanze sulla definizione precisa del pattern, alcune idee fondamentali sono condivise da tutti. Il pattern MVC ha tre componenti chiave:
Model View Controller

Il "modello" si occupa della conoscenza del dominio dell'applicazione La "vista" si occupa della visualizzazione del dominio dell'applicazione Il "meccanismo di controllo" si occupa di verificare il flusso e Io stato dei dati inseriti dall'utente

Con il pattern MVC, si verifica di solito una notifica degli eventi per comunicare alla View che qualche parte del Model cambiata. Tuttavia, dal momento che nella tipica web application un browser mantiene una connessione stateless, la notifica dal modello alla vista non si verifica in maniera semplice. [2] Ovviamente, un'applicazione potrebbe effettuare un qualche tipo di meccanismo di push (= "spingere") per inviare notifiche o dati a un client, ma ci eccessivo per la maggior parte delle web application. Un utente pu chiudere il browser in ogni momento e, di solito, nessuna notifica viene inviata al server. Occorre un impegno eccessivo per gestire client remoti dal lato server. Questo tipo di comportamento non necessario per le tipiche web application B2C e B2B.
[2] Le web application sono considerale stateless (= "prive di stato") poich il browser non mantiene costantemente una connessione aperta con il server web. Ci nonostante, una web application pu comunque mantenere dati di sessione per un utente o addirittura salvare i dati all'interno del browser su richiesta dell'utente.

Con normali web application, un client di solito invia un'altra richiesta al server per sapere se ci sono stati cambiamenti al Model. Questo viene generalmente definito come approccio di tipo pull (= "tirare"). Per esempio, se un utente sta osservando le informazioni relative al prezzo di un articolo e proprio in quell'istante l'amministratore cambia proprio quel prezzo, l'utente non sapr che il costo dell'articolo cambiato fino a che non effettuer un nuovo caricamento della pagina.

1.5.1 MVC : il Model


A seconda del tipo di architettura utilizzata dalla propria applicazione, la parte "modello" del pattern MVC pu assumere diverse forme. In un'applicazione two-tier, ovvero a due strati, dove il tier web interagisce direttamente con una sorgente di dati come un database, le classi del Model possono essere una serie di oggetti Java, i quali possono essere popolati manualmente da un ResultSet restituito da una query al database, oppure possono essere istanziate e popolate automaticamente da una infrastruttura che effettui il mapping di dati a oggetti verso dati relazionali (ORM, object-to-relational mapping) tipo TopLink o CocoBase.

In una applicazione enterprise pi complessa (dove il tier web comunica con un server EJB, per esempio) la porzione Model del pattern MVC sar rappresentata da Enterprise JavaBeans. Sebbene le specifiche EJB 2.0 abbiano segnato un miglioramento delle prestazioni attraverso l'utilizzo di interfacce local, si pu ancora verificare un impatto significativo sulle prestazioni se il tier web tenta di utilizzare gli entity bean direttamente, come porzione Model dell'applicazione, a causa del peso di dover effettuare chiamate remote. In molti casi, JavaBeans vengono restituiti da session bean e usati all'interno del tier web. Questi JavaBeans, di solito detti data transfer object o value object, vengono utilizzati all'interno delle View per costruire il contenuto dinamico.

1.5.2 MVC :la View


Le View nel pattern web tier MVC sono tipicamente costituite di pagine HTML e JSP. Le pagine HTML servono per il contenuto statico, mentre quelle JSP possono essere utilizzate sia per il contenuto statico che per quello dinamico. La maggior parte del contenuto dinamico viene generata nel tier web, ma alcune applicazioni possono richiedere dei JavaScript lato client. Ci non va a intralciare o violare i concetti del pattern MVC. HTML e JSP non sono la sola scelta possibile per le viste, poich facile supportare il WML, per esempio, invece dell'HTML. Dal momento che la View disaccoppiata dal Model, si possono supportare viste multiple una per ogni tipo diverso di client utilizzando comunque i medesimi componenti del Model.

1.5.3 MVC : il Controller


La porzione Controller del pattern web tier MVC di solito costituita da una servlet Java. Il Controller in una applicazione web tier svolge i seguenti compiti: 1. 2.
3.

4. 5.

Intercetta le richieste HTTP provenienti da un client Traduce ciascuna richiesta in una specifica operazione appartenente alla logica business da effettuare Invoca tale operazione o la delega a un handler (meccanismo di riferimento) Contribuisce alla scelta della successiva vista che deve essere mostrata al client Restituisce la View al client

Il pattern Front Controller, che fa parte dei design pattern della piattaforma Java 2 Enterprise Edition (J2EE) (ulteriori informazioni presso http://java.sun.com/blueprints/patterns/index.html), descrive il modo in cui dovrebbe essere implementato il controller di un tier web. Dal momento che tutte le richieste del client e le risposte ad esso passano attraverso il controller, esiste un punto di controllo centralizzato per la web application. Ci ha i suoi vantaggi quando si tratta di aggiungere nuove funzionalit. Codice che andrebbe normalmente messo in ogni pagina JSP pu invece essere sistemato nella servlet controller, la quale elabora tutte le richieste. Il controller contribuisce inoltre alla separazione dei componenti di presentazione (viste) dalle operazioni business dell'applicazione, con un ulteriore facilitazione dello sviluppo.

1.6 Che cosa un framework?


Il termine framework (= "infrastruttura") stato utilizzato finora senza darne una precisa definizione, n spiegare il modo in cui un framework contribuisce allo sviluppo del software. Nella sua forma pi semplice, un framework una serie di classi e interfacce che cooperano per risolvere un determinato tipo di problema software. Un framework ha le seguenti caratteristiche: Un framework comprende molte classi o componenti, ciascuna delle quali pu fornire un'astrazione di un particolare concetto. Il framework definisce il modo in cui queste astrazioni collaborano per risolvere un problema. I componenti di un framework sono riutilizzabili. Un framework organizza i pattern a un livello pi elevato. Un buon framework dovrebbe fornire un comportamento generico di cui possano far uso molte diversi tipi di applicazione. Esistono molte interpretazioni di ci che costituisce un framework. Alcuni potrebbero considerare come framework le classi e le interfacce fornite dal linguaggio Java, ma in realt si tratta piuttosto di una libreria. Esiste una differenza sottile, ma molto importante, tra una library e un framework. Una libreria software contiene funzioni o routine a cui la nostra applicazione pu fare delle invocazioni. Un framework, invece, fornisce

componenti generici che collaborano tra loro, i quali possono essere estesi dalla nostra applicazione per fornire una serie particolare di funzioni. I "luoghi" in cui il framework pu essere esteso sono detti punti di estensione. Ci si riferisce spesso ai framework come a "librerie alla rovescia" per il modo alternativo in cui essi operano. La Figura 1-5 illustra le sottili differenze tra framework e library.

Figura l-5. Un framework e una library non sono la medesima cosa

1.7 La creazione del framework Struts


Il framework Struts stato creato da Craig R. McClanahan ed stato donato alla ASF nel 2000. Craig fa attivamente parte del gruppo di esperti sulle specifiche delle Servlet e delle JSP, e ha scritto una gran parte dell'implementazione di Tomcat 4.0. anche uno dei relatori di vari congressi, tra cui JavaOne e ApacheCon. Diversi committer [3] si sono uniti al progetto Struts e, un numero ancora maggiore di sviluppatori ha dedicato volontariamente tempo e fatica per migliorare l'infrastruttura e accrescerne il valore. In conseguenza a ci, Struts ha attraversato diverse versioni beta e alcune release general availability (GA): sebbene siano state aggiunte molte nuove funzioni, il framework non si allontanato dai suoi principi fondamentali.
[3]

Un committer quello sviluppatore esperto che, assumendo un ruolo di "timoniere", fornisce indicazioni e consigli per mantenere sulla giusta rotta lo sviluppo dell'infrastruttura Struts. Un committer ha la possibilit di modificare il repository del codice sorgente e il diritto di voto nelle questioni che influiscono sul futuro di Struts.

1.7.1 Contribution to the Struts Project


Il gruppo Struts accetta di buon grado nuovi partecipanti. Per diventare contributore necessario prima entrare a far parte della mailing list degli utenti Struts. Se ci che trovate nella lista di discussione vi piace e interessa, potete entrate a far parte della mailing list degli sviluppatori Struts. il modo migliore per cominciare a prendere confidenza con la direzione del progetto. Prima di iscriversi, bene leggere le linee guida della mailing list all'indirizzo http://jakarta.apache.org/site/mail.html. E possibile iscriversi a una o pi mailing list del progetto Jakarta, compresa quella su Struts, a partire dall'URL http://jakarta.apache.org/site/mail2.html. Il sito del progetto principale su Struts, si trova all'indirizzo http://jakarta.apache.org/struts/. Per ulteriori informazioni sul download e l'installazione di Struts, si veda l'Appendice B

1.8 Alternative a Struts


Prima di addentrarci troppo nella nostra discussione sul framework Struts ci dedicheremo in questa sezione a una breve analisi di alcune alternative a Struts. Dal momento che versioni e caratteristiche possono cambiare con ogni nuova release, bene che il lettore effettui gli approfondimenti del caso. Questo elenco di alternative non in alcun modo esaustivo, ma rappresenta un punto di partenza. Forse si poteva mettere questa sezione al termine del libro, ma preferisco che il lettore prenda confidenza con altri framework in maniera da poterli mettere a confronto con Struts a mano a mano che si procede nella lettura. pi adatto a un libro su JSP e servlet. Oltre a ci, Struts va ben oltre ci che offerto dalle sole pagine JSP e non avrebbe molto senso paragonare ASP o altre tecnologie simili a Struts.

1.8.1 Costruirsi il proprio framework

Certo, pu sembrare strano presentare la possibilit di costruirsi il proprio framework come alternativa all'uso di Struts. Perch mai costruirsi framework a partire da zero, quando gi ne esistono di tipi diversi? La ragione la stessa per cui vengono sviluppati altri prodotti open source o commerciali. La selezione disponibile di prodotti potrebbe semplicemente non rispondere in maniera sufficientemente adeguata al framework di cui si ha veramente bisogno, e potrebbe quindi essere preferibile realizzarne uno "in casa". Il miglior consiglio che posso fornire sulla "autocostruzione" di un framework di porsi una serie di domande: 1. 2. 3.
4.

Ho impiegato tutto il tempo necessario per rendermi conto di ci che gi esiste e per costruire un prototipo utilizzando un framework gi esistente? Di che cosa ha veramente bisogno la mia applicazione che non si trovi in uno dei framework esistenti? Posso estendere un framework che gi esiste affinch soddisfi le mie necessit, oppure trovare ci che mi serve da un'altra parte e aggiungerla al framework? Sono sufficientemente preparato sulla costruzione di tale tipo di infrastrutture per essere in grado di raggiungere i requisiti prefissi?

Sulla base delle risposte oneste a questi interrogativi, si potrebbe scoprire che costruirsi da soli il proprio framework non la decisione migliore. Una buona regola su cui mettono la firma molti esperti dell'industria di sviluppo del software questa: "se si tratta di qualcosa relativo al nucleo fondamentale delle proprie attivit, meglio costruirselo da s; ma se il componente software non legato direttamente al nucleo fondamentale della propria attivit, meglio ricercarlo gi pronto". Occorre fare affidamento sui punti di forza della propria squadra e ridurre al minimo i punti deboli.

1.8.2 Barracuda
Il framework di presentazione Barracuda un tipo di architettura Model 2 simile a Struts, che per si spinge ancor pi in l fornendo un meccanismo di notifica degli eventi per il Model. A differenza di un approccio rigidamente JSP, il framework Barracuda ha creato un componente engine per template che dovrebbe garantire flessibilit e possibilit di estensione maggiori. Il framework fa leva sulla separazione tra codice e contenuto fornito dall'approccio XMLC di creare interfacce utente. XMLC un compilatore basato su Java che usa un documento HTML oppure XML per creare classi Java che, una volta mandate in esecuzione, possano ricreare il documento stesso. Le classi Java generate possono essere utilizzate per inserire contenuto dinamico nel documento a runtime agendo su interfacce DOM (Document Object Model). La separazione dei marcatori e della logica dell'applicazione fa s che i web designer possano concentrarsi sui marcatori e i programmatori sul codice. L'unico inconveniente della scelta di tale framework sta nella pi ripida curva di apprendimento da parte degli sviluppatori. L'utilizzo di XMLC e il fatto che vengano create classi Java a partire da documenti HTML o XML possono confondere gli sviluppatori meno esperti. La versione 1.0 del framework Barracuda stata rilasciata non molto tempo fa: maggiori informazioni si possono trovare all'indirizzohttp://barracuda.enhydra.org.

1.8.3 Cocoon
Stefano Mazzocchi ha fatto partire il progetto Cocoon nel gennaio del 1999 come iniziativa open source nell'ambito della ASF. Lo scopo di Cocoon di contribuire a separare lo stile, la logica e le funzioni di gestione per siti web basati su XML. Cocoon fa uso delle tecnologie XML, XSLT (Extensible Stylesheet Language Transformations) e SAX (Simple API for XML) per creare applicazioni server XML, effettuarne il deploy e curarne il mantenimento. Cocoon attualmente giunto alla release 2.0. E previsto il supporto per la maggior parte delle fonti di dati, tra cui RDBMS, LDAP e File Systems. Maggiori informazioni su Cocoon possono essere trovate all'indirizzo http://xml.apache.org/cocoon/.

1.8.4 Expresso
Expresso, prodotto dalla Jcorporate, un framework di sviluppo di applicazioni che fornisce un'infrastruttura a componenti per lo sviluppo di web application basate su database. Il framework Expresso pu essere integrato in Struts, apportando cos caratteristiche per la sicurezza, il mapping object-to-relational, la gestione e la programmazione di lavori in background e molte altre funzioni. Expresso rappresenta pi un complemento a Struts che non un prodotto concorrente. attualmente giunto alla release 4.0. Maggiori informazioni sono reperibili all'indirizzo http://www.jcorporate.com.

1.8.5 Freemarker, Velocity, e WebMacro


Si tratta di tre prodotti simili, che rappresentano dei template engine. Freemarker un prodotto open source: si tratta di un motore che gestisce template HTML per servlet Java. Con Freemarker, si salva l'HTML in template, i quali alla fine vengono compilati in "oggetti template". Questi oggetti template generano poi l'HTML in maniera dinamica, utilizzando dati forniti dalle servlet. Freemarker utilizza un suo proprio linguaggio per i template che si afferma ha una velocit prossima a quella delle pagine HTML statiche. Si tratta di software libero, distribuito sotto licenza GNU Library Public License. Al momento ha raggiunto la release 2.0. Maggiori informazioni si trovano presso http://freemarker.sourceforge.net . Velocity un altro progetto dell'ambito Jakarta, come Struts. Si tratta di un template engine basato su Java, simile per molti versi a Freemaker ma capace di andare oltre la semplice creazione di contenuti dinamici per i siti web. Velocity, a partire dai template, pu generare SQL, PostScript e XML e pu essere utilizzato sia come strumento standalone per generare codice sorgente e report che come componente integrato in altri sistemi. Velocity fornisce inoltre servizi template per il framework per web application Turbine. Molti altri framework supportano la sintassi di script di Velocity o proprio si basano su di essa. Per ulteriori informazioni su Velocity, visitare
http://jakarta.apache.org/velocity/.

WebMacro un framework open source per servlet Java usato da vari grandi siti web. WebMacro utilizza un linguaggio di script leggero che consente la separazione tra aspetto e logica della pagina. WebMacro pu funzionare in modalit standalone o con un servlet container. Attualmente disponibile la release 1.0. Maggiori informazioni su WebMacro si possono trovare all'indirizzo http://www.webmacro.org.

1.8.6 Maverick
Il framework MVC Maverick offre la possibilit di mostrare viste utilizzando le JSP, il linguaggio di script di Velocity oppure l'XSLT. Maverick un'architettura di tipo MVC, ma in realt fornisce un meccanismo di template per le view. Un'ottima funzione di Maverick sta nel fatto di poter utilizzare la reflection sui JavaBeans nello strato di presentazione per creare un'interfaccia DOM, in maniera tale che non sia richiesta la generazione o il parsing di codice XML. Questo permette minor disordine e forse anche migliori prestazioni quando si utilizza XSLT per generare le viste. Maggiori informazioni su Maverick si trovano all'indirizzo http://mav.sourceforge.net.

1.8.7 SiteMesh
SiteMesh un sistema per il layout e l'integrazione di pagine web che rende pi facile creare siti web che necessitano di un look & feel coerente. SiteMesh intercetta le richieste dirette verso una qualsiasi pagina, che sia statica o dinamica, effettua il parsing sul contenuto e genera una pagina finale. Questo processo si basa sul famoso pattern Decorator. [4]
[4]

Il Decorator un pattern strutturale di progettazione di cui si parla nel libro Design Patterns (Addison Wesley), di Gamma, Helm, Johnson e Vlissides, conosciuti con il nomignolo di gang of four ("la banda dei quattro").

SiteMesh costruito con tecnologia Servlet, JSP e XML, il che lo rende adatto alle applicazioni J2EE. Ci nonostante, si afferma anche una facile integrazione con altre tecnologie web, quali CGI. Ulteriori informazioni sono disponibili sul sito http://www.opensymphony.com/sitemesh/.

1.8.8 Jakarta Turbine


Turbine un framework basato sulle servlet e fa parte del progetto open source Jakarta. Attualmente, la documentazione disponibile non molta ma esso sembra essere simile a Struts, con alcune importanti differenze. Per prima cosa, non sembra accoppiato alle JSP. Lo scopo principale di Turbine sembra essere quello di fornire una raccolta di componenti riusabili: nel framework inclusa un'ampia serie di componenti, dalle caratteristiche anche piuttosto disparate. Sembra trattarsi di qualcosa che va oltre la semplice libreria di componenti, ma la mancanza di documentazione rende difficile farsi un'idea chiara della sua architettura completa. Maggiori informazioni all'indirizzo http://jakarta.apache.org/turbine/.

1.8.9 WebWork
WebWork un piccolo framework per web application che utilizza il design Pull HMVC (Hierarchical Model View Controller). In un'architettura MVC standard, i cambiamenti effettuati al Model vengono inviati alla View. Nel caso di WebWork, sono le viste a prendersi i dati ogni volta che ne hanno bisogno. Fatto interessante, WebWork non sembra essere legato strettamente alle servlet, pertanto pu supportare altri tipi di client quali Swing. Ulteriori informazioni su http://sourceforge.net/projects/webwork/.

1.8.10 JavaServer Faces


Al momento in cui viene scritto questo libro, esiste una Java Specification Request (JSR) per creare una nuova tecnologia Java denominata JavaServer Faces. La specifica definisce l'architettura e una serie di API per la creazione e il mantenimento di web application Java server. L'idea di creare una serie standard di tag JSP e classi Java per aiutare gli sviluppatori a realizzare form HTML complessi HTML e altri componenti per interfacce grafiche utente (GUI) basati sulle tecnologie Servlet e JSP. L'internazionalizzazione (I18N) e la validazione dell'input sembrano rivestire un ruolo importante. JavaServer Faces sar una specifica per definire una serie di API standard e non una implementazione pratica. I diversi produttori avranno cos la possibilit di creare le loro implementazioni reali, in maniera che gli sviluppatori possano scegliere il prodotto pi adatto tra quelli disponibili. La JSR indica come i creatori di JavaServer Faces siano a conoscenza che gi altri prodotti, come Struts, hanno affrontato molti dei problemi che questa specifica tenta di risolvere. La JSR punta a creare uno standard che contribuir all'unificazione di questa area ancora piuttosto frammentata. Sar bene rimanere attenti agli sviluppi di tale specifica, perch potrebbe avere un impatto enorme su Struts e sull'intera area delle web application. Recentemente, Craig McClanahan stato nominato responsabile delle specifiche JavaServer Faces. Ci contribuir sicuramente a una transizione morbida per quanto riguarda il framework Struts. Maggiori informazioni si trovano all'indirizzo http://www.jcp.org/jsr/detail/127.jsp.

CAPITOLO 2

Allinterno dello strato web

Questo capitolo tratta della relazione tra gli strati presenti nell'architettura e il loro ruolo in un'applicazione. Specifica attenzione viene riservata al tier web, che consente a un'applicazione di comunicare e interagire con i client presenti sul Web. In particolare, questo capitolo si concentra sugli aspetti fisici e logici della progettazione e dell'utilizzo di uno strato web per le applicazioni. Il framework Struts si basa sulle tecnologie Java Servlet e, in misura minore, JavaServer Pages, e necessita quindi di un container web. Per gli sviluppatori Struts, capire il modo in cui il container web elabora le richieste del client fondamentale per un a pi approfondita comprensione dell'infrastruttura stessa. Questo capitolo tratta i vari componenti che fanno parte del container web e le responsabilit di ciascuno di essi.

1.1 Una panoramica sullarchitettura


Questa sezione presenta una panoramica ad alto livello su un'applicazione Struts. Sebbene venga illustrata l'architettura di una applicazione enterprise, non tutte le applicazioni scritte usando Struts avranno queste dimensioni e questa composizione. Tuttavia, questo tipo di applicazione buona per mostrare le molte sfaccettature della configurazione. Molte applicazioni specie quelle J2EE possono essere descritte nei termini dei loro strati (tier). Le funzionalit dell'applicazione vengono separate tra i vari tier, o strati funzionali, per garantire la separazione delle responsabilit, il riuso dei componenti, una migliore scalabilit, e molti altri aspetti vantaggiosi. La separazione degli strati pu essere fisica, con ogni tier collocato su una risorsa hardware differente, oppure semplicemente logica. In questo secondo caso, sulla stessa risorsa hardware sono collocati diversi tier, ma c' una separazione in termini di software. La Figura 2-1 illustra gli strati utilizzabili in una tipica applicazione Struts. Non tutte le applicazioni Struts conterranno tutti gli strati illustrati nella Figura 2-1. Per molte piccole applicazioni, il middle tier potr essere sostanzialmente un container web che interagisce direttamente con un database nello EIS tier (Enterprise Information System).

Figura 2-1. Strati funzionali di un 'applicazione

Che cos un container?


Esistono molti diversi tipi di container: container EJB, container web, container per servlet e cos via. In generale, i container forniscono un ambiente che ospita componenti software che vanno in esecuzione all'interno, di tale "contenitore". I container forniscono dei servizi generali che i componenti all'inferno di tale ambiente possono usare; in questo modo gli sviluppatori dei vari componenti non devono preoccuparsi di escogitare un modo per erogare tali servizi. Un web container permette di effettuare il deploy e di eseguire al suo interno servlet, JSP e altre classi Java. Servizi quali il JNDI (Java Naming and Directory Interface), il pool delle connessioni e quelli inerenti alle transazioni possono essere configurati a livello del container in maniera simile al modo in cui i container per EJB gestiscono sicurezza, transazioni e pool dei bean e gli sviluppatori di componenti non devono preoccuparsi di gestire tali risorse. Utilizzare i servizi forniti da un container implica che lo sviluppatore di componenti debba rinunciare in parte al controllo dall'ambiente-contenitore. I container sono realizzati da produttori terze parti, che devono attenersi a determinate linee guida esplicitamente definite nelle specifiche pubbliche; sebbene alcune porzioni del container possano essere implementate in maniera proprietaria da ciascun produttore, si devono comunque seguire scrupolosamente le specifiche, al line di garantire la portabilit delle applicazioni.

2.1.1 Lo strato client


Lo strato client fornisce agli utenti un modo per interagire con l'applicazione, che si tratti di un'interazione tramite browser web, o di programmazione attraverso un'interfaccia per web service. Indipendentemente dal tipo di client, l'interazione prevede di inviare una richiesta e di ricevere un qualche tipo di risposta dallo strato intermedio (middle tier). Nel caso del framework Struts, il tipo di client pi comune il browser web. comunque possibile avere come client anche delle apparecchiature wireless o delle applet Java.

2.1.2 Lo strato web


La Figura 2-1 mostra lo strato intermedio come una combinazione dello strato web e di qualche tipo di applicazione in esecuzione all'interno di un application server (in questo caso, un container EJB). Questi due strati sono spesso combinati e molti application server hanno delle funzionalit web tier. Lo strato web consente allo strato client di comunicare e interagire con la logica dell'applicazione, la quale risiede su altri strati. In web application pi tradizionali, non raro che pane o tutta la logica dell'applicazione sia presente su questo strato. In applicazioni di scala pi ampia, a livello enterprise, il web tier agisce come "traduttore", effettuando un mapping delle richieste HTTP verso invocazioni di servizi sul middle tier. Lo strato web ha inoltre la responsabilit di gestire il flusso degli screen basandosi sullo stato dell'applicazione e dell'utente. Lo strato web comunica o con un database oppure, nel caso di un'applicazione enterprise, con un application server. Il tier web il collante che lega le applicazioni clicnt al nucleo fondamentale di sistemi business in backend. I componenti che risiedono nello strato web consentono agli sviluppatori di estendere le funzionalit fondamentali di un web service. Nel caso di Struts, ci avviene tramite com ponenti dell'infrastruttura che vanno in esecuzione all'interno di un servlet container.

2.1.3 Lo strato intermedio


Lo strato intermedio (middle tier) viene spesso detto anche application tier o server. Ci dovuto in parte al fatto che all'interno di questo strato spesso contenuto un application server. Non tutte le applicazioni Struts possiedono un application tier; ci vale specialmente per piccole web application: molti progetti piccoli fanno a meno di grandi application server e comunicano direttamente con un database o con altri sistemi di immagazzinamento dei dati. Quando presente un application server, lo strato web comunica con questo

utilizzando delle varianti di RMI (Remote Method Invocation). Se nello strato intermedio presente un server EJB, il protocollo di comunicazione RMI over IIOP (Internet Inter-ORB Protocol).

RMI over IIOP


RMI (Remote Method Invocation) un protocollo che permette l'invocazione di metodi su oggetti remoti. L'implementazione Java di RMI, denominata Java Remote Method Protocol (JRMP), ormai disponibile da diverso tempo ed e progettata specificamente per comunicazioni remote Java-to-Java. Uno dei problemi di JRMP che esso richiede necessariamente una JVM esecuzione sia sul client che sul server. Con tutte le applicazioni cosiddette legacy scritte in linguaggi tipo C++ e altri, Java necessita di un modo per comunicare con questi sistemi. Ed in questi casi che RMI over IIOP fornisce il suo contributo. Il protocollo IIOP (Internet Inter-ORB Protocol) fu progettato per consentire a componenti distribuiti di comunicare tra loro usando il protocollo TCP/IP. IIOP indipendente dal linguaggio e dalla piattaforma. Utilizzando RMI sopra la base IIOP, Java pu comunicare con applicazioni scritte in molti altri linguaggi e su varie piattaforme. RMI/IIOP ( questa la notazione con cui spesso si descrive tale protocollo), deve essere supportato da tutti i server EJB ed contemplato nelle specifiche EJB e J2EE.

Quando presente, l'application tier pu fornire una architettura maggiormente scalabile, pi fault-tolerant e ad alta disponibilit. Uno degli scopi fondamentali per l'uso di un application tier di separare le responsabilit della presentazione da quelle del modello e delle business mie dell'applicazione. Attualmente, molte web application usano server EJB per i loro middle tier. Spesso non utilizzano tutti gli aspetti disponibili dell'architettura J2EE, quali ad esempio i componenti EJB ma, in un server J2EE esistono tante altre caratteristiche vantaggiose su cui si pu fare leva.

2.1.4 Lo strato EIS (Enterprise Information System)


Lo strato EIS contiene dati e servizi che vengono usati in tutto il sistema enterprise, e fornisce l'accesso a dati e servizi usati per tutto il sistema, come database, mainframe, applicazioni di gestione dei clienti (CRM, Customer Relationship Management) e sistemi di pianificazione delle risorse. Il middle tier comunica con i componenti presenti nello strato EIS utilizzando protocolli specifici per la risorsa. Per esempio, per comunicare con un database relazionale, il middle tier normalmente utilizzer un driver JDBC. Per i sistemi di pianificazione delle risorse enterprisc (ERP, Enterprise Resource Planning), verr utilizzato un adattatore proprietario, sebbene alcuni sistemi ERP e altre risorse enterprise abbiano cominciato a supportare modalit di accesso di tipo simile ai web service.

2.1.5 Dove si inserisce Struts?


Come mostrato in Figura 2-2, l'infrastruttura Struts risiede nello strato web. Le applicazioni Struts sono ospitate da un web container e possono fare uso di servizi forniti dal container stesso, quali la gestione delle richieste tramite i protocolli HTTP e HTTPS. Questo d agli sviluppatori la libert di concentrarsi sulla realizzazione di applicazioni che risolvono i problemi reali modellati dall'applicazione.

Figura 2-2. Il framework Struts viene utilizzato all'interno dello strato web

2.2 La fase request/response HTTP


Per illustrare meglio il modo in cui il web server e il servlet container collaborano per soddisfare le richieste del client, in questa sezione si parla del protocollo request/response HTTP, dal momento in cui viene ricevuta una richiesta da parte del client fino a quando il server restituisce la risposta. Struts utilizza molto gli oggetti request e response, e una comprensione globale del processo nel suo insieme contribuir a rendere pi chiari alcuni argomenti che saranno trattati pi avanti nel libro.
Sebbene il browser non sia l'unico tipo di client utilizzabile con Struts, si tratta certamente del pi comune. Sempre pi sviluppatori cominciano a usare Struts per applicazioni wireless e anche per qualche interazione con i web service, ma il web browser resta ancora il client predominante.

L'HTTP si basa su un modello richiesta/risposta e quindi esistono due tipi di messaggi HTTP: richiesta e risposta. Il browser apre una connessione verso un server ed effettua una richiesta. Il server elabora la richiesta del client e restituisce una risposta, come illustrato nel processo di Figura 2-3.

Figura 2-3. Il modello request/response HTTP

Entrambi i tipi di messaggio consistono di una riga di inizio, zero o pi campi di intestazione e di una riga vuota indicante che l finiscono gli header del messaggio. Entrambi i tipi di messaggio possono inoltre contenere un corpo del messaggio (facoltativo). Il formato e la struttura dei messaggi di richiesta e di risposta sono molto simili, ma con alcune differenze. Si analizzer separatamente ciascun tipo di messaggio.

2.2.1 La HTTP Request


La riga iniziale di una richiesta HTTP detta request line. sempre la prima riga del messaggio di request e contiene tre campi: Un metodo HTTP Un URI (Universal Resource Identifier) Una versione del protocollo HTTP

Sebbene esistano diversi metodi HTTP per recuperare dati da un server, i due utilizzati pi frequentemente sono GET e POST. Il metodo GET richiede al server la risorsa indicata dall'URI presente nella request. Se l'URI punta a una risorsa che produce dati, come una servlet, i dati saranno restituiti all'interno del messaggio di risposta. Sebbene il messaggio GET possa passare informazioni nella stringa di query, si usa il metodo POST per passare esplicitamente al server i dati che possono essere usati per l'elaborazione dall'URI della request. L'URI identifica la risorsa che dovrebbe processare la request. Per gli scopi di questa discussione, pu trattarsi di un path sia assoluto che relativo. Una request con un URI non valido, restituir un codice di errore (di solito il 404). La versione del protocollo HTTP della request rende evidente al server a quale versione delle specifiche HTTP si conforma la request. L'esempio seguente illustra la request line di una tipica richiesta GET: GET /index.html HTTP/1.0 possibile eseguire questo esempio aprendo una sessione Telnet su un server che manda in esecuzione un web server. (necessario specificare il nome dell'host e il numero della porta del web server. Per esempio: telnet localhost 80 Poi si pu digitare il comando GET. Sar necessario premere due volte il tasto Enter dopo aver inserito il comando: la prima volta per indicare il termine della request line e la seconda per far capire al server che la request finita. Supponendo l'esistenza di un file denominato index.html nella directory root, sar restituita la response HTML (per la precisione, si avr sempre una risposta, anche se potrebbe non trattarsi di quella che ci si aspetta). Pi avanti nel capitolo si parler ancora di Telnet come strumento di comunicazione con un web server quando si tratteranno redirect e forward. Come gi accennato, una request HTTP pu contenere zero o pi campi header. I campi di intestazione della request consentono al client di passare al server delle informazioni aggiuntive sulla request e sul client stesso. Il formato di uno di questi campi header, sia per la request che per la response, il nome del campo intestazione, seguito dal segno "due punti" (:) e dal valore. Se in un singolo campo header sono specificati valori multipli, essi devono essere separati con virgole (,). La Tabella 2-1 elenca alcuni degli header di request maggiormente utilizzati.
Tabella 2-1. Tipici campi header di una request HTTP

Nome

Scopo Indica i tipi di media accettati nella response. Se non presente alcun campo header Accept, il server riterr che il client accetta tutti i tipi di media. Un esempio di valore per l'header Accept pu essere "image/gif, image/jpeg". Indica i set di caratteri accettati nella response. Se non presente alcun campo header Accept-Charset, il server riterr che il client accetta tutti i set di caratteri. Si ritiene ragionevolmente che il set di caratteri ISO-8859-1 sia accettabile per tutti gli utenti. Simile al campo header Accept, ma restringe ulteriormente i valori di codifica del contenuto accertati dal client. Un esempio di valore per l'header Accept-Encoding pu essere "compress, gzip". Indica la lingua preterita dal client per la response. Esempi di valori per l'header AcceptLanguage possono essere "en-us, de-li, es-us". Indica quale meccanismo di codifica stato applicato al body del messaggio e. pertanto, quale

Accept

Accept-Charset

Accept-Encoding Accept-Language

Content-Encoding meccanismo di decodifica dovr essere usato. Un esempio di valore per l'header ContentEncoding pu essere "gzip". Content-Type Host Referer
Indica il tipo di media del body inviato al ricevente. Un esempio di valore per l'header Content-Type pu essere "text/html; charset=ISO-8859-1". Indica il nome dell'host e il numero della porta della risorsa che si richiede, ricavato dall'URL originale. Un esempio di valore per l'header Host pu essere "www.somehost.com". Permette al client di specificare l'indirizzo (URI) della risorsa dalla quale stato ottenuto l'URI della request. Questo header viene utilizzato principalmente per fini di mantenimento e tracciabilit. Contiene informazioni sul client che ha originato la request. Questo header viene utilizzato principalmente per scopi statistici e per il tracciabilit delle violazioni di protocollo. Un esempio di valore per l'header User-Agent pu essere "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)".

User-Agent

Il corpo del messaggio di una request viene utilizzato per trasportare al server i dati associati alla request. I dati inclusi nel corpo sono diversi dai valori utilizzati nei campi di intestazione, sia in termini di formato che di contenuto. Si pu pensare ai campi header come a dei metadata riguardanti il body del messaggio.

2.2.2 La response HTTP


Una volta che il server ha ricevuto e processato la richiesta, deve restituire un messaggio di risposta HTTP. Il messaggio di response consiste di una riga di stato e di zero o pi campi di intestazione, seguiti da una riga vuota. Pu anche avere un corpo del messaggio (facoltativo). La prima riga della response HTTP viene detta status line. Si tratta della versione del protocollo HTTP a cui la response si conforma, seguita da un codice numerico di stato e dalla sua spiegazione testuale. Ciascun campo viene separato dal successivo attraverso uno spazio. Un esempio di linea di stato per una response mostrato di seguito: HTTP/1.1 200 OK Il codice di stato un valore numerico a tre cifre corrispondente al codice risultante dal tentativo del server di soddisfare la request. Il codice di stato serve per applicazioni di programmazione, mentre il testo che lo accompagna si rivolge agli utenti umani. La prima cifra del codice di stato definisce la categoria del risultato. La Tabella 2-2 elenca le cifre consentite al primo posto e le corrispondenti categorie.
Tabella 2-2. Categorie dei codici di stato

Valore Numerico 100-199 200-299 300-399 400-499 500-599

Significato Informativo La request stata ricevuta e viene elaborata Successo L'azione stata ricevuta, capita e accettata con successo. Redirezione Devono essere intraprese ulteriori azioni per il completamento della request. Errore client La request contiene sintassi sbagliata e non pu essere soddisfatta. Errore server Il server non riuscito a soddisfare una request apparentemente valida.

Sono stati definiti non pochi codici di stato. Essi sono inoltre estensibili, il che permette alle applicazioni di estendere il comportamento del server. Se un'applicazione client non riconosce un codice di stato restituito dal server, pu determinare il significato generico della risposta sulla base della prima cifra. La Tabella 2-3 elenca alcuni dei codici di stato pi comuni.
Tabella 2-3. Codici di stato pi comuni nelle response HTTP

Codice 200 302 400 401 403 404 500 OK La request andata a buon fine

Significato Moved temporarily La request risiede momentaneamente su un URI diverso. Se il nuovo URI una location, il campo intestazione Location della response fornir la nuova URL. Questo codice viene di solito utilizzato quando il client subisce una redirezione. Bad request Il server non riuscito a comprendere la request a causa di sintassi errata. Unauthorized La request necessita di autenticazione e/o autorizzazione. Forbidden Il server ha compreso la request ma, per qualche ragione, si rifiuta di soddisfarla. Il server pu o meno rivelare le ragioni per cui rifiuta la richiesta. Not found Il server non ha trovato nulla che corrispondesse all'URI della request. Internal server error Il server si trovato in una condizione inattesa che gli ha impedito di soddisfare la request.

I campi intestazione nella response sono simili nel formato a quelli che si incontrano nel messaggio di request. Consentono al server di inviare al client ulteriori informazioni che non possono essere posizionate nella status line. Questi campi forniscono informazioni sul server e su un ulteriore accesso all'URI contenuto entro la request. Dopo l'ultimo header della response, seguito da una riga vuota, il server pu inserire il corpo del messaggio di risposta. In molti casi, il corpo del messaggio rappresentato da output HTML. La Figura 2-4 illustra una risposta di esempio alla seguente request: GET /hello.html HTTP/1.0

Figura 2-4. Un esempio di messaggio di response HTTP

2.2.3 HTTP a confronto con HTTPS


Avrete probabilmente notato che il testo dei messaggi di request e response mostrati negli esempi precedenti sono tutti in testo standard leggibile: ottimo quando non c' bisogno di proteggere i dati, ma inadatto quando si tratta di dati confidenziali. Quando necessario assicurare l'integrit e la riservatezza di informazioni che vengono inviate su una rete, specie se si tratta di una rete aperta come Internet, una delle opzioni possibili il protocollo HTTPS invece dell'HTTP standard. HTTPS e normale HTTP inglobato in un Secure Sockets Layer (SSL). SSL un sistema di comunicazione che garantisce la privacy quando si comunica con altre applicazioni abilitate all'SSL. Si tratta semplicemente di un protocollo che funziona sullo strato TCP/IP. Effettua la cifratura dei dati attraverso l'uso della cifratura simmetrica e dei certificati digitali. Una connessione SSL pu essere stabilita tra un client e un server solo quando entrambi i sistemi sono in esecuzione in modalit SSL e hanno la capacit di autenticarsi reciprocamente. II fatto che il protocollo SSL effettui la cifratura dei dati trasmessi non ha alcun impatto sui sottostanti messaggi di request e response. La cifratura e la successiva decifratura sull'altro lato si verificano dopo che il corpo del messaggio stato costruito ed disaccoppiato dalla porzione HTTP del messaggio.

2.3 Struts e Scope


Il framework Struts utilizza diverse aree di risorse condivise per immagazzinare gli oggetti. Le aree di risorse condivise sono tutte sottoposte a regole inerenti il periodo di vita e la visibilit che definisce il cosiddetto scope della risorsa. Questa sezione tratta di queste risorse, del loro scope e del modo in cui ci e utilizzato dal framework.

2.3.1 Scope di request


Ogni volta che un client emette una request HTTP, il server crea un oggetto che implementa l'interfaccia javax.servlet.http.HttpServletRequest Tra l'altro, questo oggetto contieneun insieme di coppie di attributi chiave/valore che pu essere usato al fine di salvare gli oggetti per il periodo di vita della request. La chiave di ogni coppia e una String, mentre il valore pu essere un qualsiasi tipo di Object. I metodi per immagazzinare gli oggetti nello scope della request e per recuperarli da esso sono: public void setAttribute( String name, Object obj ); public Object getAttribute( String name ); Gli attributi dello scope della request possono essere eliminati utilizzando il metodo removeAttribute(); tuttavia, dal momento che lo scope dura solamente per il periodo di vita della request, non cos importante rimuoverli quanto lo per altri attributi. Una volta che il server ha fatto fronte alla request, e che una response stata inviata al client, la request e i suoi attributi non sono pi disponibili al client e possono essere inviati al garbage collector della JVM. Il framework Struts fornisce la possibilit, di immagazzinare JavaBeans in una request, in maniera tale che possano essere utilizzati da componenti di presentazione quali le pagine JSP. Ci rende pi facile l'accesso ai dati dei JavaBeans, senza dover effettuare in seguito una ripulitura manuale degli oggetti. Raramente c' la necessit di rimuovere oggetti dallo scope della request, visto che se ne occupa il web container. Oggetti immagazzinati al livello di request sono visibili solo alle risorse che hanno accesso a tale request. Una volta che la response stata, restituita al client, la visibilit va perduta. Gli oggetti immagazzinati in una request non sono visibili a nessuna altra request del client.

2.3.2 Scope di sessione


Andando verso l'alto, il successivo livello di visibilit lo scope di sessione. Il web container crea un oggetto che implementa l'interfacciajavax.servlet.http.HttpSession per identificare un utente attraverso richieste multiple di pagine. Il momento in cui viene creata la sessione dipende dall'applicazione e dall'implementazione del container. La sessione dell'utente persister per un periodo di tempo dipendente dalla frequenza con cui l'utente stesso effettua le request. Il tempo di inattivit configurabile attraverso il descrittore di deploy dell'applicazione.

La sessione pu essere anche distrutta prematuramente con un'invocazione al metodo invalidate()presente sull'oggetto session. La sessione consente anche di salvare temporaneamente un insieme di oggetti basandosi su uno schema di coppie chiave/valore, come con l'oggetto request. La sola differenza tra questo schema e quello della request sta nella durata degli oggetti. Dal momento che le sessioni si protraggono per molteplici richieste, gli oggetti immagazzinati nello scope di sessione vivono pi a lungo di quelli esistenti a livello di request. Il framework Struts utilizza ampiamente gli attributi di sessione. Un esempio di un oggetto che pu essere immagazzinato come attributo di sessione l'oggetto Locale dell'utente. Ci consente all'intero framework di avere accesso al locale dell'utente per avere un comportamento coerente con il locale (le convenzioni linguistiche) impostato. Gli oggetti salvati nella sessione di un utente non sono visibili a utenti con una diversa sessione. Il container web non fornisce alcuna sincronizzazione per gli oggetti immagazzinati nella sessione. Se thread multipli tentano di accedere a un oggetto salvato nella sessione per modificarne il valore, garantire la sincronizzazione compito dello sviluppatore. Sebbene la necessit di sincronizzare l'accesso alla sessione sia piuttosto bassa, lo sviluppatore ha la responsabilit di proteggere le risorse. Per esempio, se l'applicazione in questione utilizza i frame, o in presenza di un processo che necessita di tempi lunghi per essere completato, dei thread multipli potrebbero tentare l'accesso alla sessione nel medesimo istante.

2.3.3 Scope di applicazione


A un livello di visibilit e durata ancor pi elevato stanno gli oggetti immagazzinati nello scope di applicazione. Gli oggetti nello scope dell'applicazione sono visibili a tutti i client e a tutti i thread della web application corrente. La loro vita si protrae finch non vengono eliminati tramite esplicite istruzioni nel codice, o non termina l'applicazione. Il servlet container crea un oggetto che implementa l'interfaccia javax.servlet.ServletContext per ciascuna delle

web application installate all'interno del container. Ci si verifica quando il container viene avviato per la prima volta. Oltre che lo scope per gli oggetti request e session, la ServletContext permette agli oggetti application di essere immagazzinati e recuperati dall'intera applicazione e di persistere per tutto il ciclo di vita dell'applicazione. Il framework Struts usa lo scope di applicazione per immagazzinare i JavaBeans che devono essere visibili a tutti gli utenti. Normalmente, gli oggetti vengono salvati in questo scope durante l'avvio dell'applicazione e l rimangono finch l'applicazione non si ferma.

2.3.4 Scope di pagina


L'ultimo scope di cui parliamo, lo scope di pagina, ha a che fare esclusivamente con le pagine JSP. Gli oggetti con scope di pagina sono immagazzinati nella javax.servlet.jsp.PageContext per ogni pagina e sono accessibili solo all'interno della pagina JSP in cui sono stati creati. Una volta che la response stata inviata al client o che la pagina effettua un forward a un'altra risorsa, gli oggetti non sono pi disponibili. Ogni pagina JSP ha un riferimento a un oggetto implicito, denominato pageContext, che pu essere utilizzato per immagazzinare e recuperare oggetti a livello di pagina e che include gli stessi metodi getAttribute() e setAttribute() previsti dagli altri scope; tali metodifunzionano in maniera analoga a quelli visti per gli altri scope.

2.4 Usare i parametri URL


I parametri URL sono stringhe inviate al server insieme alla request del client. Tali parametri vengono inseriti nell'oggetto HttpServletRequest dalla stringa di query dell'URI e dai dati che vengono inviati con un metodo POST. I parametri sono formattati come coppie chiave/valore e ad essi le applicazioni possono accedere come parametri di request. I parametri URL svolgono un ruolo importante in tutte le web application, e il framework Struts non la eccezione.
Non confondete i parametri della request con gli attributi della request. Gli attributi sono tipicamente oggetti inseriti nella request in maniera da essere resi disponibili ad altre servlet, come delle pagine JSP.

2.5 Forward a confronto con redirect


Spesso necessario che pi di un componente condivida il controllo su una request. Per esempio, una servlet potrebbe essere responsabile dell'autenticazione e dell'autorizzazione di un client, mentre compito di un'altra servlet recuperare alcuni dati per l'utente. La condivisione di una richiesta pu essere realizzata in diversi modi. Esistono differenze importanti tra il modo in cui un web container elabora un forward di una request rispetto al modo in cui elabora un redirect. Per una tipica richiesta, la servlet controller di Struts, di cui si parlato al Capitolo 1, effettuer sempre l'una o l'altra azione, quindi importante comprendere queste differenze e l'effetto che ciascuno dei due meccanismi avr sulla nostra applicazione.

2.5.1 Utilizzare un'azione di redirect


Quando viene invocato il metodo sendRedirect(), il web container spinto a restituire al browser una response indicante che deve essere richiesto un nuovo URL. Dal momento che il browser produce una richiesta completamente nuova, tutti gli oggetti immagazzinati come attributi della request prima del verificarsi dell'azione di redirect andranno persi: si tratta di una delle maggiori (differenze tra un forward e un redirect. La Figura 2-5 ne illustra le ragioni.

Figura 2-5. Un redirect costringe il browser a produrre una nuova request

A causa dell'ulteriore processo che si verifica, un redirect pi lento di un forward. L'Esempio 2-1 fornisce una servlet di esempio che effettua un'azione di redirect per una pagina JSP denominata result.jsp quando viene prodotta una request per la servlet.
Esempio 2-1 Una servlet java che esegue un'azione di redirect quando riceve una request

package com.oreilly.struts.chapter2Esempios; import import import import import import java.io.IOException; javax.servlet.ServletException; javax.servlet.http.HttpServlet; javax.servlet.http.HttpServletRequest; javax.servlet.http.HttpServletResponse; javax.servlet.RequestDispatcher;

/** * Perform a redirect for the page "redirect.jsp" */ public class RedirectServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ redirect( request, response ); }

public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ redirect( request, response ); } /** * Set a few URL parameters and objects for the request to see what happens * to them during a redirect. */ protected void redirect(HttpServletRequest req,HttpServletResponse resp) throws ServletException, IOException{ log( "A request arrived for " + req.getServletPath( ) ); // Put some objects into request scope req.setAttribute( "firstName", "John" ); req.setAttribute( "lastName", "Doe" ); String contextPath = req.getContextPath( String redirectStr = contextPath + "/result.jsp?username=foo&password=bar"; log( "redirecting to " + redirectStr ); // Always call the encodeRedirectURL( ) method when perfoming a redirect resp.sendRedirect( resp.encodeRedirectURL(redirectStr) ); } } Quando la servlet dell'Esempio 2-1 riceve una request GET o POST, invoca il metodo redirect() e lo passa agli oggetti HttpServletRequest e HttpServletResponse. Il metodo stabilisce due oggetti String all'interno della request, per dimostrare che non saranno disponibili dopo il redirect, e crea uno String che diverr l'URL per il quale viene detto al client di fare una nuova request. Dopo l'invocazione del metodo encodeRedirectURL()e il passaggio della stringa di redirezione, viene chiamato il metodo sendRedirect()sull'oggetto response.
Tutti gli URL passali al metodo sendRedirect() dovrebbero essere eseguiti tramite il metodo encodeRedirectURL(), in maniera che possa essere incluso l'ID di sessione se il browser non supporta i cookie e c' necessit comunque di registrare ci che avviene nella sessione. Il framework Struts effettua questi processi in maniera automatica all'interno di RequestProcessor durante la normale elaborazione delle azioni.
La pagina JSP verso la quale la servlet effettuer il redirect mostrata nell' Esempio 2-2. Esempio 2-2 La pagina result.jsp

);

<html> <head> <title>Struts Redirect/Forward Esempio</title> </head> <body> <img src="images\tomcat-power.gif"> <br> <% String firstName = (String)request.getAttribute( "firstName" ); if ( firstName == null ){ firstName = "Not found in request"; } String lastName = (String)request.getAttribute( "lastName" ); if ( lastName == null ){ lastName = "Not found in request";

} %> <b>First Name:</b> <%=firstName%><br> <b>Last Name:</b> <%=lastName%><br> </body> </html>


Quando si inserisce l'URL per questa servlet : http://localhost:8080/servlet/com.oreilly.struts.chapter2Esempios.RedirectServlet in un browser, esso produrr quanto mostrato nella Figura 2-6.

Figura 2-6. La pagina prodotta quando viene chiamata la RedirectServlet

Va notato che gli argomenti relativi a nome e cognome che sono stati impostati nella servlet, non sono stati trovati nella request. Ci accade perch in effetti stata inviata una seconda request per questa pagina. Fortunatamente, possibile osservare ci che succede effettivamente. I parametri URL username e password sono stati inclusi in questo esempio solo per illustrare che sono ancora presenti dopo che si verificato un redirect. Nella stringa di query non si dovrebbero inserire mai delle informazioni riservate, ad esempio password.

Osserviamo la response HTTP che ritorna al client quando viene richiesta la RedirectServlet. Si pu vedere tale risposta utilizzando una sessione Telnet standard. Una connessione HTTP utilizza una semplice socket di rete per comunicare con il server, quindi possibile simulare in parte l'interazione tra un browser e un server usando l'applicazione Telnet. Usando Telnet, si pu stabilire una connessione a un web server collegandosi alla porta sulla quale il server sta in ascolto (normalmente la porta 80 oppure, nel caso di Tomcat, la 8080):

telnet localhost 8080


Tale operazione si pu fare da riga di comando, sia da shell DOS che da Unix. La sessione Telnet mostrer un avviso nel caso in cui non sia in grado di connettersi a un server con il nome di host e il numero di porta specificati. Se la connessione viene stabilita, la sessione Telnet star in attesa che vengano immesse le informazioni della request HTTP. La sola informazione indispensabile la prima riga della request, che indica al server quale risorsa desiderata dal client. Ciascuna riga della richiesta una riga di testo, separata a un carattere di "a capo". Lo header della request si conclude con una riga vuota, il che, in pratica, significa dover battere due volte il tasto Enter per indicare a Telnet che la richiesta conclusa. La Figura 2-7 mostra il messaggio di response HTTP restituito da Tomcat quando viene effettuata una request per la risorsa RedirectServlet.

Figura 2-7. Telnet pu essere usalo per vedere gli header della response HTTP

Facendo sempre riferimento alla Figura 2-7, la prima riga della request produce un GET e un percorso verso una risorsa. II path la porzione della request HTTP presente dopo lo hostname e include il carattere "/" che lo precede. La stringa "HTTP/1.0" al termine della request GET rappresenta la versione del protocollo HTTP. La versione 1.1 di HTTP presenta una serie di ottimizzazioni rispetto alla versione 1.0. Ci nonostante, ci sono ulteriori header della request che necessario aggiungere usando la versione 1.1: ci avrebbe reso questo esempio pi complicato del necessario ed per questo che abbiamo usato la 1.0.

Tutto quel che segue la prima riga una response proveniente dal server. L'intero output HTML in basso proviene da Tomcat e indica che la richiesta originale ha effettuato un redirect: il client, pertanto dovrebbe richiedere un nuovo URL. Se si guarda lo header Location della response la terza riga in Figura 2-7 si nota come il server abbia informato il client di quale debba essere l'URL per la nuova request. Ogni parametro URL allegato alla request originale sar presente in quella nuova. Quando il browser produce la nuova request, questi parametri verranno inviati insieme a essa. Questa tecnica che prevede l'uso di Telnet fornisce un modo semplice di interazione con un web server e di vedere quali responsc invia al client. probabile che ora il lettore abbia una migliore comprensione del modo in cui funziona una azione di redirect. Dovrebbe essere anche chiaro perch qualsiasi oggetto presente all'interno della request originale non sia disponibile alla risorsa verso cui avviene il redirect. Questo apparir ini tutta la sua importanza pi avanti nella trattazione del framework Struts. Va notato come, nell'Esempio 2 1, la stringa di redirezione non conteneva esplicitamente il nome di host e la porta di cui aveva bisogno il client per la nuova request. compito del servici container tradurre l'URL relativo in un URL pienamente qualificato da trasmettere indietro al client.

2.5.2 Utilizzare un'azione di forward


Un forward si differenzia da un redirect per svariati aspetti. Quando si invoca un forward per una request, questa viene inviata a un'altra risorsa sul server, senza che il client venga informato del fatto che sar un'altra risorsa a processare la request. Questo processo ha luogo tutto all'interno del web container, e il client non ne viene a sapere nulla. A differenza che con un redirect, gli oggetti possono essere immagazzinati nella request e passati direttamente alla risorsa successiva da usare. La Figura 2-8 illustra cosa accade quando avviene il forward di una request.

Figura 2-8. L'azione di forward non viene notificala al client

Dal momento che un forward ha luogo completamente all'interno del server e non c' comunicazione di ci al client, le prestazioni sono migliori rispetto a quelle del redirect. E bene tener presesente che ci sono delle differenze nel modo in cui un forward tratta gli URL relativi. L'Esempio 2-3 dovrebbe rendere pi chiaro il tutto.
Esempio 2-3. Una servlet Java che effettua un forward quando riceve una request package

package com.oreilly.struts.chapter2Esempios; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.RequestDispatcher; /** * Perform a forward to the page "redirect.jsp" */ public class ForwardServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ forward( request, response ); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ forward( request, response ); } /** * Set a few URL parameters and objects for the request to see what happens * to them during a forward. */ protected void forward(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ log( "A request arrived for " + req.getServletPath( ) ); // Put some objects into request scope req.setAttribute( "firstName", "John" ); req.setAttribute( "lastName", "Doe" ); String redirectStr = "/result.jsp?username=foo&password=bar"; this.log( "forwarding to " + redirectStr ); RequestDispatcher dispatcher = req.getRequestDispatcher( redirectStr ); dispatcher.forward( req, resp ); } }
Quando la servlet dell'Esempio 2-3 riceve una request di tipo GET o POST, effettua una chiamata al metodo forward() passando ad esso gli oggetti HttpServletRequest e HttpServletResponse. Come nell'esempio del redirect, vengono impostati all'interno della request due oggetti String. Tuttavia, a differenza con quello che accade nell'esempio di redirect, gli oggetti saranno disponibili dopo il forward. In seguito, la servlet crea un path di redirezione verso il quale sar passata la request. Viene creato un RequestDispatcher e viene invocato il metodo forward() presente su di esso. Impiegando la stessa pagina JSP dell'Esempio 2-2, inseriamo nel browser un URL tipo http://localhost:8080/servlet/com.oreilly.struts.chapter2Esempios.ForwardServlet. Quello che apparir nel browser dovrebbe assomigliare a quanto mostrato in Figura 2-9.

Figura 2-9. La pagina prodotta quando viene chiamata ForwardServlet

Due sono gli aspetti da notare in Figura 2-9. Il primo che i campi di nome e cognome hanno dei valori. Questo accade perch gli oggetti sono stati inseriti nella request prima che si verificasse il forward, e la result.jsp stata in grado di recuperare i valori e utilizzarli nella pagina. Il secondo l'URL nella barra degli indirizzi. Va notato che, mentre in Figura 2-6 veniva mostrato il nuovo URL, esso non cambia in Figura 2-9. Ci mostra ancora una volta come il client non sia al corrente che si sta verificando un forward: al browser non viene notificato che la request stata gestita da una servlet diversa. Il metodo forward() della classe RequestDispatcher pu ricevere invocazioni solo quando nessun output stato invialo al client. Scrivere qualcosa ncll'object della response e poi invocare forward() pu causare da parte del container l'eccezione IllegalStateException.

2.5.3 Che cosa migliore in Struts?


difficile stabilire se le applicazioni Struts debbano usare azioni di redirect o di forward, visto che l'infrastruttura ancora non stata spiegata diffusamente. Ci nonostante, ci sono alcuni punti chiave nell'uso sia dell'uno che dell'altro approccio. Entrambi i meccanismi hanno i loro pro e i loro contro nell'ambito di Struts e delle web application in generale. In molte situazioni, preferibile un forward rispetto a un redirect, poich gli oggetti immagazzinati nello scope della request sono cos facilmente disponibili ai componenti della presentazione; e infatti l'azione di forward l'opzione predefinita per il framework Struts. Un altro vantaggio del forward la sua maggiore efficienza dal momento che il client non deve produrre una nuova request. Ci sono alcune situazioni, per, un cui un redirect diventa necessario ed preferibile rispetto a un forward. Quando vengono utilizzati dei forward, gli URL non sono sempre consistenti con lo stato delle applicazioni client. Se viene premuto il tasto Refresh del browser, si pu verificare una request scorretta. Si approfondir il problema nel Capitolo 5. Se nelle proprie pagine JSP si utilizzano path relativi per immagini e altre risorse, i forward possono risultare problematici. Dal momento che il browser non ha indicazione che si verificata un'azione di forward, i path relativi faranno riferimento alla servlet iniziale e non alla pagina JSP verso la quale avvenuto il forward. Come si vedr nel Capitolo 8, l'infrastruttura Struts fornisce un custom tag JSP che contribuisce ad alleviare i fastidi dovuti a questo comportamento.

CAPITOLO 3

Panoramica sul framework Struts

E giunto finalmente il momento di presentare il framework Struts. I due capitoli precedenti dovrebbero aver fornito una base che facilita l'apprendimento dei concetti qui contenuti. In questo capitolo si illustra una panoramica sull'infrastruttura Struts. Non si trattano tutte le sue caratteristiche, n si approfondisce troppo, ma si mette chiaramente in luce il modo in cui tutte le varie parti rientrino nelle architetture MVC e Model 2 presentate nel Capitolo 1.I successivi capitoli punteranno invece a scoprire i diversi strati, mettendo in luce i dettagli del framework, espandendo i concetti e la terminologia di base qui presentata. molto importante che il lettore comprnda approfonditamente i concetti fondamentali presentati in questo capitolo: anche se i concetti base del framework struts vi sono gi noti, sarebbe comunque meglio leggere questo capitolo prima di proseguire.

3.1 Esempio: un conto corrente bancario


In questa sezione e presentata un'applicazione di banking online che servir a introdurre i concetti di Struts. L'esempio non completo, ma anch'esso una panoramica sui principali componenti presenti in tutte le applicazioni Struts, e mostra il modo in cui questi componenti interagiscono. Nel corso del libro, sar utilizzato un esempio pi completo e approfondito, basato sul "carrello della spesa" di un sito di commercio elettronico. La maggior parte delle persone conosce il concetto di "banca via Internet" e quindi non si star a spiegare nel dettaglio la logica che alla base di una tale applicazione. In breve, l'applicazione di banking online permette a un utente finale di collegarsi al sito di un'istituzione finanziaria, e trasferire fondi da un conto a un altro (a patto che l'utente abbia pi di un conto). L'utente deve presentare una serie di credenziali valide per entrare all'interno del sito: in questo caso, un codice di accesso e un PIN (Personal Identification Number). Se l'utente non completa un campo o entrambi, l'applicazione mostrer un messaggio formattato che informa sulla necessit di riempire entrambi i campi. Se l'utente immette i valori per entrambi i campi, ma l'autenticazione non va a buon fine, verr nuovamente mostrata la schermata di login, accompagnata da un messaggio di errore che informa del fallimento del login precedente. La Figura 3-1 mostra lo schermo di login

Figura 3-l. Schermata di login per l'applicazione di banca via Internet

Se vengono immesse credenziali valide per il conto, l'utente viene condotto alla schermata di informazione sul conto, che mostra tutti i conti correnti posseduti dall'utente presso l'istituto bancario e il loro saldo al momento. Per questo semplice esempio, rinunceremo a fornire un ambito e un servizio di sicurezza robusti e sperimentati. Gestire la sicurezza di una web application pu risultare complicato e al momento tale aspetto pu essere tralasciato, per non confondere le idee. Per gli scopi del capitolo, si user una semplice interfaccia Java che contiene un metodo login() per effettuare l'autenticazione degli utenti. Tale interfaccia di autenticazione viene descritta nell'Esempio 3-1 .
Esempio 3-1. L'interfaccia di autenticazione utilizzata nell'applicazione di banking online

package com.oreilly.struts.banking.service; import com.oreilly.struts.banking.view.UserView; /** * Provides methods that the banking security service should implement. */ public interface IAuthentication { /** * The login method is called when a user wants to log in to * the online banking application. * @param accessNumber- The account access number. * @param pin- The account private id number. * @returns a DTO object representing the user's personal data. * @throws InvalidLoginException if the credentials are invalid. */ public UserView login( String accessNumber, String pin ) throws InvalidLoginException; } L'interfaccia IAuthentication contiene un metodo login() molto semplice, che prelevaaccessNumber e pin dalla pagina di login. Se l'autenticazione ha successo, viene restituito un oggetto com.oreilly.struts.banking.view.UserView. Se il login non va a buon fine, viene lanciata una InvalidLoginException. La UserView un semplice JavaBean che pu essere immagazzinato all'interno della vista presentata all'utente e utilizzato per mostrare contenuto specifico per il cliente nell'applicazione. Sebbene non sia del tutto pertinente alla presente discussione, il codice sorgente di UserView sar mostrato pi in l nel capitolo. La classe com.oreilly.struts.banking.service.SecurityService descritta nell'Esempio 3-2: implementa l'interfaccia IAuthentication dell'Esempio 3-1 e permette all'applicazione di autenticare gli utenti. In questo esempio l'autenticazione sar effettuata senza usare security realm, pertanto la classe SecurityService conterr, codificata stabilmente al suo interno, la logica per autenticare gli utenti.
Esempio 3-2. Il servizio di sicurezza utilizzata dall'applicazione di banking online di esempio

package com.oreilly.struts.banking.service; import com.oreilly.struts.banking.view.UserView; /** * Used by the Esempio banking application to simulate a security service. */ public class SecurityService implements IAuthentication { public UserView login( String accessNumber, String pin ) throws InvalidLoginException { // A real security service would check the login against a security realm. // This Esempio is hardcoded to let in only 123/456. if( "123".equals(accessNumber) && "456".equals(pin) ){ /* Dummy a UserView for this Esempio. * This data/object would typically come from the business layer * after proper authentication/authorization had been done. */ UserView userView = new UserView( "John", "Doe" );

userView.setId( "39017" ); return userView; } else { // If the login method is invalid, throw an InvalidLoginException. // Create a msg that can be inserted into a log file. String msg = "Invalid Login Attempt by " + accessNumber + ":" + pin; throw new InvalidLoginException( msg ); } } } Per questa applicazione di esempio, autenticheremo l'utente solo se l' accessNumber immesso corrisponde a "123" e il pin a "456" Se il SecurityService dovesse essere utilizzato in una applicazione reale, dovrebbe effettuare il controllo delle credenziali tramite un qualche tipo realm di sicurezza, come un di database relazionale o un server LDAP.

Effettuato il login con successo, l'utente pu eseguire una tra queste tre azioni: Vedere i dettagli di un conto Trasferire fondi da un conto a un altro, sempre che l'utente abbia due o pi conti Uscire dall'applicazione (logout) La Figura 3-2 mostra la schermata di informazione su conti alla quale si accede dopo un login effettuato con successo. L'utente pu visualizzare informazioni dettagliate su un conto facendo click su tale conto. La Figura 3-3 mostra la schermata di dettaglio per il controllo del conto elencato nella Figura 3-2 .

Figura 3-2 La schermata di informazione sui conti

Figura 3-3 La schermata con i dettagli del conto

Lutente pu anche trasferire fondi da un conto ad un altro facendo click sul tasto Transfer vicino al conto a partire dal quale si desidera trasferire i fondi. Dal momento che lo scopo di questo capitolo di familiarizzare con i componenti del framework Struts e non di insegnare il funzionamento corretto di unapplicazione di web banking, la funzionalit di trasferimento dei fondi non sar implementata in maniera completa (ma se il lettore lo desidera, pu provare a farlo come esercizio) Alla fine l'utente pu effettuare il logout dell'applicazione semplicemente con un click sul tasto Logout, uscendo cos dall'applicazione e tornando alla schermata di login.

3.2 Il quadro d'insieme !


Vista l'applicazione di esempio alla base di ci che sar spiegato nel capitolo, giunto il momento di cominciare a capire come sia possibile implementarla utilizzando il fra mework Struts. Sebbene nel Capitolo 1 il pattern MVC sia stato trattato seguendo l'ordine model, view e poi controller, non si deve necessariamente seguire quest'ordine per analizzare i componenti Struts. In effetti pi logico trattare i componenti nell'ordine in cui essi sono utilizzati dall'infrastruttura per elaborare una request. Saranno pertanto analizzati per primi i componenti che costituiscono la porzione controller del framework.

3.2.1 I package dei componenti Struts


L'infrastruttura Struts composta approssimativamente di 300 classi Java, divise in 8 package principali ("approssimativamente" un termine appropriato perche il framework subisce di continuo aggiunte e aggiustamenti). In questo capitolo ci concentreremo essenzialmente sui package top-level. La Figura 3-4 mostra questi package e le loro relazioni all'interno del framework.

Figura 3-4 Gli otto package top-level dell'infrastruttura Struts

Il package validator mostrato in Figura 3-4 non rappresenta l'intera serie di classi e interfacce necessarie per il framework Validator. Si tratta solamente delle estensioni specifiche di Struts necessarie per utilizzare il framework Validator con Struts

I componenti del framework non sono organizzati secondo il ruolo che svolgono nel pattern MVC ma, a dire il vero, un po' a casaccio. Probabilmente saranno balzate all'occhio alcune dipendenze circolari mostrate nella Figura 3-4. Questo dovuto pi che altro alla velocit con cui il framework si evoluto, e non a decisioni sbagliate dei progettisti. Per esempio, il package action contiene classi per il controller, alcune classi che sono usate nel dominio della view, e addirittura anche qualcuna che probabilmente sarebbe stata meglio nel package util. Sebbene all'inizio ci possa risultare un po' confuso, non difficile abituarsi a capire dove stanno i diversi componenti. La Tabella 3-1 elenca i package top-level e fornisce una breve descrizione del loro scopo. Alcuni di questi package contengono dei "sottopacchetti", di cui si parler nei successivi capitoli.
Tabella 3-1 Package top-level nel framework Struts

Nome Package

Descrizione Contiene le classi del controller, la ActionForm, la ActionMessages, e diversi altri componenti necessati per il framework. Contiene le classi Action rimanenti, quali la DispatchAction, che pu essere utilizzata o estesa dalla propria applicazione Include le classi di configurazione che sono rappresentazioni presenti in memoria del file di configurazione di Struts. Contiene le classi tag handler per le tag library di Struts. Contiene le classi usati dal framework Tiles. Include classi utilizzate per l'upload e il download dei file dal filesystem locale con un browser Contiene classi di utilit generale utilizzate dall'intero framework. Contiene le classi di estensione, specifiche per Struts, utilizzate dal framework quando si effettua il deploy del validator. Le classi e le interfacce vere e proprie di Validator si trovano nel package commons, che separato da Struts.

action actions config taglib tiles upload util validator

Ora che ci si fatta un'idea dei package contenuti all'interno del framework, il momento di analizzare gli strati dell'architettura di Struts.

3.3 Componenti del controller


Il componente controller di un'applicazione MVC ha varie responsabilit, tra le quali ricevere input da un client, invocare un operazione appartenente alla logica dell'applica zione e coordinare la vista da restituire al client. Ovviamente, il controller pu effettuare molte altre funzioni, ma quelle citate sono alcune delle principali. Per un'applicazione costruita secondo l'approccio Model 2 delle JSP, il controller implementato per mezzo di una servlet; Java, che rappresenta il punto di controllo centralizzato della web application. La servlet controller agisce facendo corrispondere alle azioni dell'utente le opportune operazioni appartenenti alla logica dell'applicazione e poi contribuisce a selezionare ci che deve essere visualizzato al client, sulla base della richiesta e di altre informazioni di stato. La Figura 3-5 mostra nuovamente l'immagine utilizzata al Capitolo 1 per spiegare questo meccanismo.

Figura 3-5. Il framework Struts usa una servlet come controller

In Struts, comunque, le responsabilit di controller sono implementate per mezzo di vari componenti, tra i quali un'istanza della classe org.apache.struts.action.ActionServlet.

3.3.1 La ActionServIet di Struts


La ActionServlet estende la classe javax.servlet.http.HttpServlet ed responsabile della pacchettizzazione e dell'indirizzamento del traffico HTTP al giusto handler del framework. La classe ActionServlet non astratta e pu pertanto essere utilizzata come controller concreto da parte delle applicazioni. Prima della versione 1.1 di Struts, la ActionServlet era responsabile solamente di ricevere ed elaborare la request chiamando il giusto handler. Con la versione 1.1, stata introdotta una nuova classe, org.apache.struts.action.RequestProcessor, al fine di elaborare la request per il controller. La ragione principale per il disaccoppiamento dell'elaborazione della request dalla ActionServlet di fornire la flessibilit di sostituire la sottoclasse RequestProcessor con la propria versione personalizzata e modificare cos il modo in cui viene elaborata la request. Per l'esempio di banking online non complicheremo la situazione e useremo le classi predefinite ActionServlet e RequestProcessor fornite dall'infrastruttura. Per brevit, d'ora in poi in questo capitolo faremo riferimento a questi due componenti semplicemente con il termine "controller". Il Capitolo 5 descrive nei dettagli il modo in cui queste classi possano essere estese per modificarne il comportamento predefinito e spiega i moli e le responsabilit di ciascun componente. Come ogni altra servlet Java, la ActionServlet di Java deve essere configurata nel descrittore di deploy per la web application. Non si entrer comunque nei dettagli per quanto riguarda il deployment descriptor, di cui si parla nel Capitolo 4. Quando il controller riceve una request dal client, delega la gestione della request a una classe helper. Questa helper sa come deve eseguire l'operazione associata all'azione richiesta. In Struts, questa classe helper discende dalla org.apache.struts.action.Action.

Ma Action fa parte del controller o del model?


I diversi articoli e manuali disponibili su Struts non sono d'accordo su un fatto: se la classe Action faccia parte del controller o del model.L'argomentazione che si porta a favore della sua appartenenza al controller che non fa parte della business logic "reale". Se Struts venisse sostituito con un framework alternativo, ci sono molte possibilit che la classe Action venga sostituita da altri componenti. Pertanto, effettivamente non appartiene al dominio del modello, ma risulta invece accoppiata in maniera piuttosto stretta con il controller Struts. Non ha senso inserite la logica dell'applicazione all'interno di Action, poich altri tipi di client non possono riusarla facilmente. Un'altra ragione per considerare la classe Action come appartenente al controller che essa ha accesso alla ActionServlet, e quindi a. tutte le risorse del controller, di cui il dominio del model dovrebbe invece rimanere all'oscuro. Ipoteticamente, sarebbe stato possibile lasciare il comportamento della classe Action nella servlet, e la servlet avrebbe invocato su di s il metodo appropriato. In questo caso, non ci sarebbe stato dubbio sul fatto che tali funzionalit appartengano al controller o al model. Detto ci, la classe Action pu invocare,operazioni sul model, e molti sviluppatori finiscono per cercare di inserire troppa business logic nelle classi Action. Alla fine, la linea di demarcazione diventa confusa. Forse questa la ragione per cui alcuni sviluppatori considerano questa classe come parte del model. In questo libro, comunque, si seguir la corrente di pensiero che considera la classe Action come facente parte del controller.

3.3.2 Le classi Action di Struts


Una classe org.apache.struts.action.Action nell'infrastruttura Struts un'estensione del componente controller e agisce come ponte tra un'azione client-side dell'utente e un'operazione appartenente alla logica business dell'applicazione. La classe Action disaccoppia la request del client dal modello business. Questo disaccoppiamento permette pi di un mapping uno-a-uno tra la request dell'utente e una Action. La classe Action pu svolgere anche altre funzioni, quali l'autorizzazione, il logging e la validazione di sessione, prima di effettuare l'invocazione dell'operazione della logica dell'applicazione. La classe Action contiene diversi metodi, ma il pi importante execute(). Eccone la firma:

public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception;
Il metodo execute() viene invocato dal controller quando viene ricevuta una request proveniente da un client. Il controller crea un'istanza della classe Action se non ne esiste gi una. Il framework Struts creer solo una singola istanza di ciascuna classe Action della nostra applicazione. Dal momento che c' solamente una istanza per tutti gli utenti, occorre garantire che tutte le classi Action operino correttamente in un ambiente multithread, proprio come si fa quando si sviluppa una servlet. La Figura 3-6 illustra il modo in cui il metodo execute() viene invocato dai componenti del controller.

Figura 3-6.Il metodo execute() viene invocato dal controller

Sebbene execute() non sia astratto, l'implementazione predefinita restituisce null, quindi sar necessario crearsi la propria implementazione di Action e ridefinire tale metodo.

C' un dibattito sul modo migliore di implementare le classi Action in Struts. Creare una diversa classe Action per ciascuna operazione o collocare varie operazioni sulla medesima Action soggettivo: ciascun approccio presenta vantaggi e svantaggi. Nel Capitolo 5. si analizzer l'azione org.apache.struts.actions.DispatchAction che permette di creare una singola classe Action e di implementare al suo interno svariate azioni simili, quali Create, Read, Update e Delete (CRUD): si creano cosi un numero minore di classi Action al prezzo di un mantenimento leggermente pi scomodo. Per la nostra applicazione di web banking creeremo una particolare classe Action per ciascuna delle azioni possibili all'utente:

Login Logout GetAccountInformation GetAccountDetail


Ciascuna delle classi Action presenti nell'applicazione estender la classe Action di Struts e ridefinir il metodo execute(), in maniera da svolgere una specifica operazione. Nel Capitolo 5, si imparer che meglio creare una classe di base astratta Action, che poi sar estesa da tutte le altre classi Action. La Action base specifica dell'applicazione estende la classe Action presente nel framework Struts e fornisce ulteriore flessibilit ed estensibilit, consentendo al comportamento comune di Action di essere gestito da un'unica classe genitore. Per esempio, se si desidera verificare per ogni request che la sessione dell'utente non scaduta, si pu collocare tale comportamento nella Action astratta di base, prima di effettuare la chiamata alla sottoclasse. Per l'applicazione di banking di esempio, comunque, tutto sar mantenuto semplice e le azioni saranno dirette discendenti della classe Action di Struts. La classe com.oreilly.struts.banking.action.LoginAction mostrata nell'Esempio 3-3. Estende la classe Action di Struts e viene invocata dal controller quando un utente effettua un tentativo di login all'applicazione di banca online.
Esempio 3-3. La Login Action utilizzata nell'applicazione di banking online

package com.oreilly.struts.banking.action; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.struts.action.Action; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import com.oreilly.struts.banking.IConstants; import com.oreilly.struts.banking.service.IAuthentication; import com.oreilly.struts.banking.service.SecurityService; import com.oreilly.struts.banking.service.InvalidLoginException; import com.oreilly.struts.banking.view.UserView; import com.oreilly.struts.banking.form.LoginForm; /** * This Action is called by the ActionServlet when a login attempt * is made by the user. The ActionForm should be an instance of * a LoginForm and contain the credentials needed by the SecurityService. */ public class LoginAction extends Action { public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response ) throws Exception { // The ActionForward to return when completed ActionForward forward = null; UserView userView = null; // Get the credentials from the LoginForm String accessNbr = ((LoginForm)form).getAccessNumber( String pinNbr = ((LoginForm)form).getPinNumber( ); );

/* * In a real application, you would typically get a reference * to a security service through something like JNDI or a factory. */ IAuthentication service = new SecurityService( ); // Attempt to log in userView = service.login(accessNbr, pinNbr); // Since an exception wasn't thrown, login was successful // Invalidate existing session if it exists HttpSession session = request.getSession(false); if(session != null) { session.invalidate( ); } // Create a new session for this user session = request.getSession(true); // Store the UserView into the session and return session.setAttribute( IConstants.USER_VIEW_KEY, userView ); forward = mapping.findForward(IConstants.SUCCESS_KEY ); return forward; } }
La LoginAction dell'Esempio 3-3 ottiene le credenziali a partire dalla ActionForm passata come argomento nel metodo execute() La classe ActionForm sar trattata in dettaglio pi avanti in "Componenti della view.

Successivamente viene creato un SecurityService e le credenziali di sicurezza vengono pasate al metodo login(). Se il login va a buon fine, viene creata una nuova HttpSession per l'utente, e la UserView restituita dal metodo login() viene inserita nella sessione. Se l'autenticazione fallisce, viene lanciata una InvalidLoginException. Va notato che non c' un blocco try/catch per la InvalidLoginException nel metodo execute(). La ragione che una delle nuove caratteristiche di Struts 1.1 rappresentata dalle sue possibilit di gestione dichiarativa delle eccezioni, che riduce parecchio la fatica a carico dello sviluppatore per quanto attiene alla gestione delle eccezioni. Con la gestione dichiarativa delle eccezioni in Struts, si dichiarano specificamente nel file di configurazione quali eccezioni possono essere lanciate dalle azioni e quale deve essere il comportamento del framework nei loro confronti:

<global-exceptions> <exception key="global.error.invalidlogin" path="/login.jsp" scope="request" type="com.oreilly.struts.banking.service.InvalidLoginException"/> </global-exceptions>


Questa porzione di codice presa dal file di configurazione dell'applicazione d'esempio per il banking online indica al framework che se una qualunque azione lancia una InvalidLoginException, la request deve essere inviata in forward alla risorsa login.jsp e deve essere emesso un errore, usando la chiave global.error.invalidlogin della resource bundle. C' anche la possibilit di ridefinire il comportamento predefinito di gestione delle eccezioni con le funzionalit che si rendono necessarie. La gestione delle eccezioni trattata in dettaglio nel Capitolo 10.

3.3.3 Il mapping delle azioni


A questo punto, il lettore si pu chiedere "Ma come fa il controller a sapere di quale Action deve invocare l'istanza quando riceve una request?". Ci viene determinato attraverso un'analisi delle informazioni contenute nella request e l'utilizzo del mapping delle azioni. Il mapping delle azioni fa parte delle informazioni di configurazione di Struts raccolte all'interno di un particolare file XML. Queste informazioni di configurazione vengono caricate in memoria all'avvio e sono rese disponibili a runtime al framework. Ciascun elemento action rappresentato nella memoria da un'istanza della classe org.apache.struts.action.ActionMapping. L'oggetto ActionMapping contiene un attributo path che corrispondente a parte dell'URI della request in entrata. Gli action mapping e il file di configurazione di Struts saranno trattati con maggiore dettaglio nel Capitolo 4. La seguente porzione di codice XML, presente nel file di configurazione dell'esempio di banking online illustra il mapping dell'azione login:

<action path="/login" type="com.oreilly.struts.banking.action.LoginAction" scope="request" name="loginForm" validate="true" input="/login.jsp"> <forward name="Success" path="/action/getaccountinformation" redirect="true"/> <forward name="Failure" path="/login.jsp" redirect="true"/> </action>
Il mapping dell'azione login qui mostrato mappa il path "/login" alla classe Action com.oreilly.struts.banking.LoginAction. Ogni volta che il controller riceve una request con il path nell'URI che contiene la stringa "/login", sar effettuata un'invocazione al metodo execute()dell'istanza di LoginAction. Il framework Struts utilizza i mapping anche per identificare la risorsa verso cui effettuare il forward dell'utente una volta che l'azione sia stata completata; la configurazione delle mappature delle azioni sar trattata con maggiore dettaglio nel Capitolo 4.

3.3.4 Determinare la vista successiva


Fin qui si e discusso del modo in cui il controller riceve la request e determina corretta istanza di Action da invocare. Quel che non stato trattato ci che determina la visualizzazione da restituire al client e il triodo in cui ci avviene. Se avete guardato attentamente la firma del metodo execute() nella classe Action riportata nella sezione precedente, avrete probabilmente notato che il tipo restituito per il metodo una classe org.apache.struts.action.ActionForward. La classe ActionForward rappresenta una destinazione verso la quale il controller pu inviare controllo una volta che una Action sia stata completata. Invece di specificare nel codice una pagina JSP effettiva, si pu associare in maniera dichiarativa il mapping del forward di un'azione con la JSP e poi utilizzare tale ActionForward in tutta l'applicazione. I forward delle azioni sono specificati nel file di configurazione, in maniera simile ai mapping delle azioni. Possono essere specificati al livello di Action, come questo forward per il mapping dell'azione logout:

<action path="/logout" type="com.oreilly.struts.banking.action.LogoutAction" scope="request"> <forward name="Success" path="/login.jsp" redirect="true"/> </action>


L'azione logout dichiara un elemento forward denominato "Success", che effettua il forward verso la risorsa "/login.jsp". L'attributo redirect viene specificato e impostato a "true". Ora, invece di eseguire un forward utilizzando un RequestDispatcher, la request subir un'azione di redirect. Il mapping dei forward delle azioni possono anche essere specificati in una sezione globale, indipendentemente da qualsiasi specifico mapping delle azioni. Nel caso precedente, solo il mapping dell'azione logout poteva effettuare il

reference al forward dell'azione denominato "Success", ma tutti i mapping delle azioni potevano effettuare reference ai forward dichiarati nella sezione global-forwards. Ecco una sezione global-forwards presa dal file di configurazione dell'applicazione di online banking:

<global-forwards> <forward name="SystemFailure" path="/systemerror.jsp" /> <forward name="SessionTimeOut" path="/sessiontimeout.jsp" /> </global-forwards>
I forward definiti in questa sezione sono pi generali e non si applicano a mapping di azioni specifici. Va notato come ogni forward deve possedere un name e un path, ma l'attributo redirect facoltativo. Se non si specifica un attributo redirect verr utilizzato il suo valore predefinito, uguale a "false", e quindi il framework eseguir un forward. L'attributo path nel forward di un'action pu anche specificare un'altra Action di Struts. Si vedr un esempio a tal proposito nel Capitolo 5. Adesso che si capito come operano ad alto livello i componenti del controller di Struts, giunto il momento di analizzare il successivo tassello del mosaico MVC: il model.

3.4 Componenti del model


Varie sono le angolazioni da cui possibile guardare a ci che costituisce il model di Struts. I confini tra gli oggetti appartenenti alla logica dell'applicazione e quelli della presentazione possono diventare piuttosto confusi quando si tratta di web application: gli oggetti business di un'applicazione diventano i DTO (data transfer object) dell'altra. importante mantenere gli oggetti business della logica dell'applicazione separati dalla presentazione, in maniera che l'applicazione non sia strettamente connessa a un solo tipo di presentazione. E probabile che, con il tempo, il look & feel di un sito web subir dei cambiamenti. Ci sono studi che dimostrano come il rinnovamento dell'aspetto di un sito contribuisca ad attrarre nuovi clienti e a mantenere fedeli i visitatori gi esistenti. E se questo pu non essere vero nel mondo business-to-business (B2B), vale sicuramente per le applicazioni business-to-comumer (B2C), che costituiscono la maggior parte delle web application oggi utilizzate.

Using Data Transfer Objects


I DTO (a cui ci si riferisce anche come oggetti valore) vengono spesso utilizzati per fornire una view "a grana grossa" di dati remoti "a grana fine". Se, per esempio, la nostra applicazione usa degli entity hean, invece di effettuare diverse chiamate in remoto per individuare lo stato individuale dei singoli oggetti, si potrebbe effettuare una sola chiamata che restituirebbe un DTO locale contenente tutti i dati necessari. A volte presente un riepilogo e una versione, dettagliata dell'oggetto remoto per "dosare" il quantitativo di dati restituito. Sebbene il DTO rappresenti l'oggetto business remoto, non necessariamente esso contiene la stessa business logie, anzi, in realt, generalmente non contiene proprio logica business, ma rappresenta invece una "istantanea" dell'oggetto remoto in un dato momento: quello, pi precisamente, in cui il client ha richiesto i dati. I DTO possono essere usati anche per aggiornare l'oggetto business, sebbene questo diventi un po' pi complicato a causa di problemi quali lock e sincronizzazione di tipo ottimistico. Per ragioni di prestazione, l'utilizzo dei DTO in un'applicazione distribuita quasi una necessit: contribuisce a ridurre la larghezza di banda necessaria e a migliorare i tempi di risposta. E inoltre buona idea utilizzare la medesima tecnica anche per applicazioni pi piccole: Usare un DTO in un'applicazione Struts aiuta a disaccoppiare gli oggetti business dalla presentazione, rendendo pi facili la manutenzione e i futuri miglioramenti. La scelta del tipo di componenti model da utilizzare dipende anche da quale applicazione si sta costruendo: una two-tier tradizionale, oppure una multi-tier distribuita. Tipicamente, con una applicazione two-tier, gli oggetti business vengono collocati nella web application. Collocazione significa che il deploy di tali oggetti avviene nella medesima JVM. Ci rende pi facile l'utilizzo di questi oggetti business al fine di recuperare dati per le view. Ci nonostante, il fatto che ci sia pi facile non significa che sia la cosa pi intelligente da fare. Gli oggetti business possono essere costituiti da grafi molto articolati di oggetti e possono contenere riferimenti a molte altre risorse non appartenenti alla rappresentazione. Se non si fa attenzione, gli oggetti business possono facilmente ritrovarsi a essere accoppiati a una specifica presentazione, il che pu avere effetti imprevisti ogni volta che viene modificato l'aspetto grafico e di navigazione del sito Uno dei vantaggi della separazione tra gli oggetti business e la presentazione sta nel fatto che e possibile costruire oggetti "a grana grossa" che le pagine JSP e i custom tag avranno meno difficolt a gestire. Tutta la business logic dovrebbe rimanere separata dalla presentazione, e le viste della presentazione dovrebbero semplicemente limitarsi a recuperare dati dal DTO e visualizzarli. La classe LoginAction illustrata nell'esempio Esempio 3-3 non conteneva la logica di autenticazione vera e propria. Visto (che la classe Action fa parte delle funzionalit del controller, delega la gestione della logica business dell'applicazione a un altro servizio. Nel caso della LoginAction, si fa affidamento su un componente

SecurityService. Il componente SecurityService pu essere un EJB session o semplicemente un wrapper per codice JDBC che esegue l'autenticazione. In (entrambi i casi, la LoginAction non conosce il modo in cui
implementato il servizio, e ci non la riguarda. Questo molto utile, poich anche se cambia l'implementazione iti maniera drastica, non saranno necessari cambiamenti al codice, a patto che l'interfaccia IAuthentication non venga modificata e sia implementata dal servizio. Questo approccio favorisce anche il riuso. Immaginiamo di avere un altro tipo di client, come un'interfaccia grafica Swing, che deve essere autenticata: dal momento che la logica incapsulata in un componente separato, e non nella classe Action, si liberi di riusare questo servizio di sicurezza. Occorre sforzarsi di mantenere la business logie fuori dalle classi Action in previsione dei cambiamenti. Nel caso di LoginAction, il metodo login() restituisce un oggetto della classe com.oreilly.struts.banking.view.UserView. Questo un esempio di DTO. L'Esempio 3-4 mostra la UserView utilizzata nell'applicazione di esempio.
Esempio 3-4. Il value object UserView utilizzalo nello strato di presentazione

package com.oreilly.struts.banking.view; import java.util.Set; import java.util.HashSet; /** * A value object that wraps all of the user's security information */ public class UserView implements java.io.Serializable { private String id; private String lastName; private String firstName; // A unique collection of permission String objects private Set permissions = null; public UserView(String first, String last) { this(first, last, new HashSet( )); } public UserView(String first, String last, Set userPermissions) { super( ); firstName = first; lastName = last; permissions = userPermissions; } public boolean containsPermission(String permissionName) { return permissions.contains(permissionName); } public String getLastName() { return lastName; } public void setLastName(String name) { lastName = name; } public String getFirstName() { return firstName; } public void setFirstName(String name) { firstName = name; } public String getId() { return id; }

public void setId(String id) { this.id = id; } }


La UserView fornisce una view a grana grossa di un oggetto remoto. Potrebbero esserci cinque tabelle per la sicurezza, tutte collegate da chiavi esterne che contengono i dati ma quando lo strato web ottiene una UserView, e gi stata consolidata e resa pi facile da accedere. In effetti, una implementazione di tale applicazione potrebbe procurarsi i dati da un database relazionale e un'altra da un'istanza LDAP. Il bello dell'incapsulamento dell'autenticazione dietro il servizio di sicurezza che lo strato di presentazione non deve essere modificato se si cambia realm di sicurezza. La Action libera di collocare l'oggetto UserView nella request o nella session e poi inviarlo in forward a una JSP, da dove i dati possono essere estratti e presentati all'utente. Il framework Stmts non fornisce rapito supporto per quanto attiene ai componenti del model. Per questo vanno meglio gli EJB, CORBA o qualche altro tipo di infrastnittura a componenti. Si potrebbe fare accesso:a un database direttamente dal framework, ma si dovrebbe comunque sempre separare tale strato dalle altre parti del framework. Ci possibile utilizzando i corretti design pattern per incapsulare il comportamento.

3.5 Componenti della view


Gli ultimi componenti MVC di cui parlare sono quelli della view: probabilmente, si tratta di quelli di comprensione pi.facile. I componenti della view usati tipicamente in una applicazione Struts sono:
HTML Data Transfer Objects ActionForms di Struts JavaServer Pages Custom tags Resource bundles Java

3.5.1 Utilizzare le ActionForm di Struts


Gli oggetti ActionForm di Struts vengono utilizzati nel framework per passare i dati di input del client avanti e indietro tra l'utente e lo strato business. Il framework raccoglie automaticamente l'input dalla request e passa questi dati a un' Action utilizzando un bean "modulo di inserimento" che poi pu essere passato allo strato business. Per mantenere disaccoppiato lo strato di presentazione da quello business, non si dovrebbe passare l' ActionForm stesso allo strato business, quanto piuttosto creare un DTO appropriato utilizzando i dati provenienti da ActionForm. I punti seguenti illustrano il modo in cui il framework elabora la ActionForm per ogni request: 1. 2. 3. 4. 5. 6. 7. 8. Controlla il mapping dell'azione per vedere se stata configurata una ActionForm. Se un' ActionForm configurata per l'azione, utilizza l'attributo name dell'elemento action per cercare le informazioni di configurazione del form bean di immissione dati. Controlla se gi stata creata un'istanza di ActionForm. Se un'istanza di ActionForm presente nel giusto scope ed dello stesso tipo di cui c' bisogno per la nuova request, la riutilizza. Altrimenti, crea una nuova istanza dell' ActionForm necessaria e la pone nel giusto scope (impostato dall'attributo scope e dell'elemento action).

Effettua una chiamata al metodo reset() sull'istanza diActionForm.


Effettua un'iterazione attraverso i parametri della request, e popola con il valore trovato il nome di ogni parametro che ha un corrispondente metodo di set sull' ActionForm. Infine, se l'attributo validate impostato a "true", invoca il metodo validate() sull'istanza della ActionForm e restituisce eventuali errori.

Per ciascuna pagina HTML in cui sono raccolti dati tramite form, si dovrebbe utilizzare una ActionForm. La stessa ActionForm pu essere utilizzata per pagine multiple, se necessario, a patto che i campi HTML e le propriet di ActionForm abbiano corrispondenza. L'Esempio 3-5 mostra la com.oreilly.struts.banking.form.LoginForm utilizzata nell'applicazione di banking online scelta come esempio.
Esempio3-5. La LoginForm utiliziata nell'applicazione di banking online

package com.oreilly.struts.banking.form; import javax.servlet.http.HttpServletRequest; import org.apache.struts.action.Action; import org.apache.struts.action.ActionError; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionMapping; import org.apache.struts.util.MessageResources; /** * This ActionForm is used by the online banking appliation to validate * that the user has entered an accessNumber and a pinNumber. If one or * both of the fields are empty when validate( ) is called by the * ActionServlet, error messages are created. */ public class LoginForm extends ActionForm { // The user's private ID number private String pinNumber; // The user's access number private String accessNumber; public LoginForm( ) { super( ); resetFields( ); } /** * Called by the framework to validate the user has entered values in the * accessNumber and pinNumber fields. */ public ActionErrors validate(ActionMapping mapping, HttpServletRequest req ){ ActionErrors errors = new ActionErrors( ); // Get access to the message resources for this application. // There's no easy way to access the resources from an ActionForm. MessageResources resources = (MessageResources)req.getAttribute( Action.MESSAGES_KEY ); // Check and see if the access number is missing. if(accessNumber == null || accessNumber.length( ) == 0) { String accessNumberLabel = resources.getMessage( "label.accessnumber" ); ActionError newError = new ActionError("global.error.login.requiredfield", accessNumberLabel ); errors.add(ActionErrors.GLOBAL_ERROR, newError); } // Check and see if the pin number is missing. if(pinNumber == null || pinNumber.length( ) == 0) { String pinNumberLabel = resources.getMessage( "label.pinnumber" ); ActionError newError = new ActionError("global.error.login.requiredfield", pinNumberLabel ); errors.add(ActionErrors.GLOBAL_ERROR, newError); } // Return the ActionErrors, if any. return errors; } /** * Called by the framework to reset the fields back to their default values. */ public void reset(ActionMapping mapping, HttpServletRequest request) { // Clear out the accessNumber and pinNumber fields. resetFields( ); }

/** * Reset the fields back to their defaults. */ protected void resetFields( ) { this.accessNumber = ""; this.pinNumber = ""; } public void setAccessNumber(String nbr) { this.accessNumber = nbr; } public String getAccessNumber( return this.accessNumber; } ) {

public String getPinNumber( ) { return this.pinNumber; } public void setPinNumber(String nbr) { this.pinNumber = nbr; } }
La classe ActionForm fornita dal framework Struts implementa diversi metodi, ma finora i pi importanti sono reset() e validate():

public void reset( ActionMapping mapping, HttpServletRequest request ); public ActionErrors validate( ActionMapping mapping, HttpServletRequest request );
L'implementazione predefinita di entrambi i metodi nella classe ActionForm non esegue alcuna logica predefinita. Sar necessario ridefinire i due metodi nelle classi ActionForm che si utilizzeranno concretamente, come nel caso della LoginForm di Esempio 3-5. Il controller chiama il metodo reset() immediatamente prima di popolare l'istanza di ActionForm con i valori provenienti dalla request, dando alla ActionForm la possibilit di effettuare un reset delle sue propriet allo stato predefinito. Ci molto importante, dal momento che l'istanza del form bean pu essere condivisa tra diverse request, o che ad essa possono avere accesso thread differenti. Ci nonostante, se si utilizza, un'istanza di ActionForm tra pagine multiple, sar il caso di non implementare il metodo reset(), in modo che i valori non subiscano un reset prima che tutte le operazioni di immissione relative a quella istanza siano state completate. Un altro approccio potrebbe essere di implementare un opportuno metodo resetFields( ) da invocarsi a partire dalla classe Action dopo un aggiornamento andato a buon fine sullo strato business. Il metodo validate() viene invocato dal controller dopo che nella ActionForm sono stati inseriti i valori della request. La ActionForm dovrebbe eseguire tutte le possibili validazioni dell'input e riportare al controller ogni eventuale errore riscontrato. La validazione della logica di business dovrebbe essere eseguita a livello di business object, non nella ActionForm. La validazione che si verifica nella ActionForm riguarda solo la presentazione, Nei Capitoli 6 e 7 si tratter in dettaglio dove si debbano collocare determinati tipi di logica di validazione. Il metodo validate() nella LoginForm dell'Esempio3-5 controlla se mancano il numero di accesso e/o il PIN e, nel caso, crea messaggi di errore. Se non vengono generati messaggi di errore, il controller passa ActionForm e svariati altri oggetti al metodo execute(). L'istanza di Action pu poi prelevare le informazioni dall' ActionForm. Si probabilmente notato come il metodo execute() della classe Action contenga un argomento che sempre del tipo ActionForm. Sar necessario effettuare il cast di questo argomento alla corretta sottoclasse, per recuperare le propriet necessarie, come possibile vedere nell'Esempio 3-3. Una volta codificate le classi ActionForm, occorre informare l'applicazione Struts che tali classi esistono e comunicare corrispondenza tra mapping delle azioni e determinate ActionForm: lo si specifica nel file di configurazione. Il primo passo di configurare tutte le ActionForm per la propria applicazione nella sezione formbeans di tale file. La seguente porzione di codice tratta dal file di configurazione dell'applicazione di banking online presa come esempio informa Struts dei tre bean ActionForm usati dall'applicazione:

<form-beans> <form-bean name="loginForm" type="com.oreilly.struts.banking.form.LoginForm"/> <form-bean name="accountInformationForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="accounts" type="java.util.ArrayList"/> </form-bean> <form-bean name="accountDetailForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="view" type="com.oreilly.struts.banking.view.AccountDetailView"/> </form-bean> </form-beans>
L'attributo name di ciascun form bean, deve essere unico, e l'attributo type deve definire una classe Java completamente definita, che estenda la ActionForm di Struts. Il passo successivo di usare uno dei nomi form-bean presi dalla sezione form-bean in uno o pi elementi di action. Il codice che segue mostra il mapping per la LoginAction gi vista nel corso del capitolo:

<action path="/login" type="com.oreilly.struts.banking.action.LoginAction" scope="request" name="loginForm" validate="true" input="/login.jsp"> <forward name="Success" path="/action/getaccountinformation" redirect="true"/> <forward name="Failure" path="/login.jsp" redirect="true"/> </action>
Notare come l'attributo name del mapping del login corrisponda a uno dei nomi presenti nella sezione form-beans. Si fa comunemente riferimento alle istanze della classe ActionForm come ai "form bean". Si user questo termine in tutto il libro per indicare un oggetto di tipo ActionForm.

Una delle nuove caratteristiche di Struts 1.1 viene mostrata nella precedente porzione form-beans. Con le versioni precedenti del framework era necessario estendere sempre la classe ActionForm con una propria sottoclasse, anche se la ActionForm eseguiva comportamenti molto generici. In Struts 1.1 stata aggiunta un nuovo tipo di action form, denominata org.apache.struts.action.DynaActionForm. Questa classe pu essere configurata per il mapping di un'azione e gestir automaticamente i dati passati dal form HTML all'Action. La DynaActionForm in grado di gestire dati in maniera generica poich utilizza una Map per immagazzinare internamente i valori. Il Capitolo 7 tratter in maggiore dettaglio la DynaActionForm. Ma qual la differenza tra una ActionForm e i DTO di cui si parlato in precedenza? Una buona domanda su un argomento che pu ingenerare confusione negli sviluppatori che si avvicinano a Struts. I componenti della view possono utilizzare sia le ActionForm che i DTO per popolare contenuti dinamici. Quando nessuna ActionForm configurata per il mapping, per costruire le viste si possono utilizzare i DTO. E quando definito un form bean per il mapping, esistono svariati modi per gestire l'estrazione dei dati dal bean. Una possibilit di effettuare sempre il wrap con un form bean attorno a uno o pi DTO restituiti dallo strato business e di costringere i componenti della view ad accedere ai dati dei DTO attraverso il form bean. Analogamente, quando un clicnt sottopone una pagina HTML, Struts effettuer un'invocazione ai metodi di set del form bean, che possono spingere i dati di nuovo dentro il DTO, una volta che il metodo di validazione ha completato con successo le sue operazioni. Tutto ci fornisce una singola interfaccia coesiva dalla quale le view possono recuperare e verso la quale possono inviare i dati HTML. Nel Capitolo 7 si parler dei vari vantaggi e svantaggi di questo e altri approcci.

3.5.2 Usare le JSP per la presentazione

Le pagine JSP costituiscono la maggior parte di ci che deve essere costruito per i componenti della view di Struts. Insieme alle librerie di tag personalizzati e all'HTML, le JSP rendono semplice fornire una serie di viste per un'applicazione. Sebbene le JSP rappresentino la tecnologia usata pi comunemente da organizzazioni e sviluppatori per mostrare contenuti dinamici, esse non costituiscono l'unica possibilit. Molti sviluppatori ritengono che la tecnologia JSP abbia i seguenti problemi: Gli sviluppatori sono liberi di incorporare la logica dell'applicazione all'interno delle pagine JSP, il che pu comportare difficolt di manutenzione delle applicazioni (con JSP 2.0, si possono configurare le pagine JSP affinch non consentano gli scriptlet). Gli sviluppatori devono imparare la sintassi JSP e come si programmano i tag custom (sebbene ci non sar pi vero quando la specifica JSP 2.0 arriver alla versione finale e sar disponibile il nuovo linguaggio di espressione).

Il container deve ricompilare la pagina JSP quando essa subisce qualche cambiamento.
Alcuni sviluppatori non considerano questi aspetti come un problema importante, e molti sono i siti costruiti usando la tecnologia JSP. Tuttavia, per coloro che desiderano delle alternative, esistono altre tecnologie di presentazione utilizzabili con il framework Struts. Un'alternativa piuttosto diffusa la combinazione di XML/XSLT. Ai fini della presentazione delle viste, questo sistema combina la scrvlet controller del framework Struts con XSLT e bean serializzati dai DTO.

3.5.3 Using Custom Tag Libraries


Il framework Struts fornisce sei principali librerie di tag utilizzabili dalle nostre applicazioni. Ciascuna di esse ha uno scopo diverso e pu essere utilizzata separatamente o insieme alle altre. I tag di Struts possono inoltre essere estesi o se ne possono creare di propri, se c' bisogno di ulteriori funzionalit. Le seguenti sono le librerie di tag personalizzati incluse nel framework: HTML, Bean, Logic, Tcmplate, Nested e Tiles. Per usare queste librerie nelle nostre applicazioni, innanzitutto necessario registrarle nella web application con il file web.xml. E necessario registrare solamente le librerie di tag che si intende effettivamente usare nella propria applicazione. Per esempio, se si pensa si utilizzare le librerie di tag HTML e Logic si deve aggiungere questa porzione di codice al descrittore di deploy della web application:

<web-app> <taglib> <taglib-uri>/WEB-INF/struts-html.tld</taglib-uri> <taglib-location>/WEB-INF/struts-html.tld</taglib-location> </taglib> <taglib> <taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri> <taglib-location>/WEB-INF/struts-logic.tld</taglib-location> </taglib> </web-app>
Maggiori informazioni sull'installazione e sulla configurazione di Struts per le proprie applicazioni vengono fornite nell'Appendice B. Il passo successivo creare le proprie pagine JSP includendo gli elementi taglib necessari, a seconda delle librerie di cui la pagina avr bisogno:

<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
Fatto questo, una volta che i file JAR sono nel CLASSPATH della web application, possibile utilizzare i tag custom nelle proprie pagine JSP. L'Esempio 3-6 mostra l'uso di svariati tag personalizzati di Struts nella pagina login.jsp dell'applicazione di banking online.

Esempio 3-6. La pagina login.jsp usata nell'applicazione di banking online

<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <html:html> <head> <html:base/> <title><bean:message key="title.login"/></title> <link rel="stylesheet" href="stylesheets/login_style_ie.css" type="text/css"> </head> <body topmargin="0" leftmargin="5" marginheight="0" marginwidth="0" bgcolor="#6699FF"> <html:form action="login" focus="accessNumber"> <Tabella border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#6699FF"> <tr><td> <html:img srcKey="image.logo" width="79" height="46" altKey="image.logo.alt" border="0"/> </td></tr> </Tabella> <Tabella border="0" cellpadding="0" cellspacing="0" width="100%"> <tr><td bgcolor="#000000"> <Tabella border="0" cellpadding="0" cellspacing="0" width="1" height="2"></Tabella> </td></tr> </Tabella> <Tabella border="0" cellpadding="0" cellspacing="0" width="1" height="1"> <tr><td></td></tr> </Tabella> <Tabella> <tr><td></td></tr> </Tabella> <Tabella border="0" cellpadding="0" cellspacing="0" width="590"> <tr><td width="15" height="31"></td><td width="12"></td></tr> <tr> <td width="15"></td> <td width="575" bgcolor="#FFFFFF" colspan="2"> <Tabella cellpadding="0" cellspacing="0" border="0" width="575" height="3"> <tr><td></td></tr> </Tabella> </td> </tr> </Tabella> <Tabella border="0" cellpadding="0" cellspacing="0" width="590" bgcolor="#ffffff"> <tr> <td width="15" bgcolor="#6699FF"></td> <td width="15"></td><td width="379"></td> <td width="15"></td> <td width="15"></td> <td width="15"></td> </tr> <tr> <td bgcolor="#6699FF" width="15"></td> <td></td> <td valign="top">

<Tabella border="0" cellpadding="0" cellspacing="0"> <tr class="fieldlabel"> <td><bean:message key="label.accessnumber"/></td> </tr class="fieldlabel"> <tr> <td><html:text property="accessNumber" size="9" maxlength="9"/></td> <td class="error"><html:errors/></td> </tr> <tr class="fieldlabel"><td height="10"></td></tr> <tr class="fieldlabel"><td><bean:message key="label.pinnumber"/></td></tr> <tr class="fieldlabel"> <td><html:password property="pinNumber" size="4" maxlength="4"/></td> </tr> <tr><td height="10"></td></tr> <tr><td><html:submit styleClass="fieldlabel" value="Login"/></td></tr> <tr><td></td></tr> </Tabella> </td> <td width="151" valign="top"> <html:img srcKey="image.strutspower" altKey="image.strutspower.alt"/> </td> </tr> </Tabella> <%@include file="include/footer.jsp"%> <br> </html:form> </body> </html:html>
Un particolare che dovrebbe subito colpire nella pagina di login dell' Esempio 3-6 che essa non contiene codice Java, e si tratta in gran parte di tag di formattazione HTML e di svariati usi delle librerie di tag di Struts. Proprio qui sta il senso di usare le librerie di lag custom. Non serve programmazione Java, quindi i web designer HTML possono lavorare liberamente sul layout della pagina senza doversi accollare gli aspetti di programmazione. L'altra bella caratteristica che i medesimi tag possono essere utilizzati da molte pagine JSP. Maggiori informazioni sulle librerie di tag si trovano al Capitolo 8. Come si pu immaginare analizzandola pagina JSP dell'Esempio 3-6, la manutenzione e la personalizzazione risultano semplici con l'uso di librerie di tag personalizzati.

3.5.4 Usare i resource bundle di messaggi


Uno degli adattamenti pi difficili che gli sviluppatori si trovano a fronteggiare sta nel personalizzare una web application affinch si adatti a molte lingue in maniera rapida e senza troppi problemi. Java incorpora svariate caratteristiche per garantire l'internazionalizzazione; Struts si basa su tali caratteristiche, aggiungendo ulteriori funzioni. La libreria Java include una serie di classi a supporto della lettura di risorse di messaggi da una classe Java o da un file di propriet. La classe di partenza in questa serie la java. java.util.ResourceBundle. Il framework Struts fornisce una serie simile di classi, basate sulla org.apache.struts.util.MessageResources, che garantisce simili funzionalit ma permette maggiore flessibilit, necessaria al framework. Con un'applicazione Struts necessario fornire un resource bundle per ciascuna lingua che si desidera supportare. Il nome della classe o del file di propriet deve attenersi alle indicazioni fornite neiJavaDoc per la classe java.util.ResourceBundle. L'Esempio 3-7 mostra il file di propriet usato nell'applicazione di banking online di riferimento.
Esempio 3-7. Il resource bundle per l'applicazione di banking online

# Labels label.accessnumber=Access Number label.pinnumber=Pin Number label.accounts=Accounts label.balance=Balance

label.totalassets=Total Assets label.account=Account label.balance=Available Balance label.description=Description label.amount=Amount label.deposits=Deposits label.withdrawls=Withdrawls label.openingbalance=Opening Balance # Links link.customeragreement=Customer Agreement link.privacy=Privacy link.security=Security link.viewaccountdetail=View Account Detail # Page Titles title.login=Struts Online Banking - Account Login title.accountinfo=Struts Online Banking - Account Information title.accountdetail=Struts Online Banking - Account Detail # Button Labels label.button.login=Login # Error messages global.error.invalidlogin=<li>Invalid Access Number and/or Pin</li> global.error.login.requiredfield=<li>The {0} is required for login</li> # Images image.logo=images/logo.gif image.logo.alt=Struts Online Banking image.logout=images/logout.gif image.logout.alt=Logout image.strutspower=images/struts-power.gif image.strutspower.alt=Powered By Struts image.transfer=images/transfer.gif image.transfer.alt="Transfer Funds" image.clear=images/clear.gif
Tornando alla pagina login.jsp dell'Esempio 3-6, si pu vedere come sono utilizzati i messaggi provenienti dal bundle. Per esempio, la seguente porzione di codice mostra l'uso della chiave title.login dell'Esempio 3-6, inserita tra i tag HTML title nella pagina.

<title><bean:message key="title.login"/></title> org.apache.struts.taglib.bean.MessageTag uno dei vari tag custom presenti in Struts che pu operare sul resource bundle. Le pagine JSP possono recuperare valori dal resource bundle utilizzando il MessageTag
basato su una chiave, come mostrato nella pagina di login dell'Esempio 3-6. La chiave in questo tag deve corrispondere nel bundle a un valore sul lato sinistro del segno uguale. Maiuscole/minuscole sono importanti e il valore deve corrispondere in maniera esatta. L'utilizzo dei resource bundle di messaggi non limita alla localizzazione, ma pu far risparmiare tempo nella manutenzione dell'applicazione. Dovendo cambiare messaggi di testo o etichette in varie parti del sito o dell'applicazione, sar necessario effettuare un solo cambiamento. bene quindi usare i resource bundle anche senza la necessit dell'internazionalizzazione. Con Struts 1.1, si possono definire per un'applicazione svariate MessageResource, con la possibilit ili isolare certi tipi di risorse in hundle separati. Per esempio, potrebbe darsi il caso in cui si salva la risorsa immagine in un bundle e il resto delle risorse in un'altro. Il modo in cui organizzare le resource della propria applicazione resta competenza dello sviluppatore, ma adesso si ha la flessibilit per separarle con un certo criterio: alcune applicazioni effettuano la

separazione Jungo la linea dei componenti, con le risorse inerenti al catalogo tutte messe in un bundle, le risorse collegate a ordini e "carrello della spesa" inserite in un altro bundle e cos via.

3.6 Supporto per applicazioni multiple


Prima della versione 1.1, ciascuna applicazione di Struts aveva il limite di un solo file di configurazione, di solito denominato struts-config.xml, che veniva specificato nel descrittore di deploy della web application. Si trattava dell'unico "fornitore" di informazioni per la configurazione dell'applicazione. Il fatto che le informazioni di configurazione fossero collocate esclusivamente in un solo file creava difficolt per i progetti pi grandi, perch spesso ci si tramutava in un "collo di bottiglia" e causava conflitti per l'uso e la modifica di tale file. Con l'avvento del supporto multi-applicazione nella versione 1.1 di Struts, questo problema stato risolto. Si possono ora definire file di configurazione multipli, consentendo agli sviluppatori di lavorare meglio in parallelo. I moduli applicativi nome con il qualesono comunemente definiti sono tritati in dettaglio nei Capitoli 4, 5, 6, e 7.

3.7 Riepilogo
Il framework Struts fornisce un'implementazione della struttura MVC, specificamente pensata per le web application. Le classi Struts ActionServlet, RequestProcessor e Action costituiscono in componenti del controller, la Action comunica con i componenti model della nostra applicazione; una combinazione di ActionForm, DTO, pagine JSP e librerie di tag costituiscono la view. Il capitolo si concentrato su Struts a un alto livello di astrazione, tralasciando molti dettagli che rendono il framework ancora migliore: come altre valide infrastrutture software, Struts permette di concentrarsi sullo sviluppo della business logic, facendo risparmiare tempo che altrimenti andrebbe dedicato a funzionalit di pi basso livello, quali la notifica delle request e la validazione a livello di campo. Si spera che questa discussione introduttiva abbia interessato il lettore e lo spinga all'approfondimento dei dettagli del framework nel corso del libro.

CAPITOLO 4

Configurare le applicazioni Struts

Il framework Struts usa due diversi tipi di file di configurazione, comunque tra loro correlati, i quali devono essere configurati in maniera adeguata affinch un'azione funzioni correttamente. Per la popolarit, la flessibilit e la natura autodescrittiva dell'XML, entrambi i tipi di file di configurazione si basano su tale linguaggio. Il descrittore di deploy di una web application denominato web.xml descritto in dettaglio nelle specifiche delle Servlet Java. [1] Questo file di configurazione necessario per tutte le web application, non solo per quelle realizzate con il framework Struts. Esistono per in tale file delle informazioni specifiche per Struts che devono essere configurate quando si costruiscono applicazioni con tale framework.
[1]

Vedi le Java Servlet Specification versione 2.3, capitolo SRV.13.

Sebbene Struts supporti le specifiche Servlet 2.2, molti container per servlet hanno gi il supporto per la versione 2.3. Nel libro si trattano sia le specifiche 2.2 che le 2.3.

Il secondo file di configurazione che esamineremo quello proprio di Struts, di solito denominato struts-config.xml, ma possibile rinominarlo nel modo che si ritiene pi opportuno. Il file di configurazione di Struts rende possibile la configurazione dichiarativa di molte impostazioni della propria applicazione. II file di configurazione di Struts pu essere considerato quello delle "regole" per la web application.

4.1 L'applicazione Storefront


Per tutto il resto del libro, costruiremo un'applicazione di tipo "carrello della spesa" da utilizzare in tutti i nostri esempi. Al termine del libro ci ritroveremo con un'applicazione piuttosto completa che utilizza gran parte delle caratteristiche di Struts 1.1. La Figura 4-1 mostra la pagina principale di questa applicazione di esempio Storefront.

Figura 4-1.La pagina principale dellapplicazione Storefront

L'applicazione Storefront implementer un sito di commercio elettronico che si occupa di autoricambi, ma possibile sostituire tali articoli con il tipo di merce che si preferisce, a patto di avere relative immagini e da usare nell'applicazione. Alla fine, l'applicazione Storefront sar un file web archive (WAR) di cui si pu effettuare il deploy in un qualsiasi web container compatibile e che pu essere usata come esempio per molteplici scopi.

4.2 Che cosa una web application?


Le applicazioni costruite utilizzando il framework Struts sono, in definitiva, delle web application. Una web application una raccolta di componenti individuali che, una volta collegati, formano un'applicazione completa che pu essere installata ed eseguita da un web container. 1 componenti sono strettamente collegati poich risiedono nello stesso contesto web, il che permette loro di referenziarsi vicendevolmente, in maniera diretta o indiretta. Per esempio, se c' un'applicazione che gira su un container web sotto una directory denominata storefront, tutti i file in quella directory e nelle sottodirectory dipendenti vengono considerati come parte della web application Storefront. Qualsiasi riferimento a una risorsa con il prefisso "storefront" viene indirizzato a questa web application; pertanto, se un utente digita http://www.somehost.com/storefront/index.jsp nella barra degli indirizzi del suo browser, la pagina JSP sar servita a partire dalla root dell'applicazione Storefront. Una web application pu essere installata ed eseguita in maniera concorrente su web container multipli. A tal proposito, istanze multiple della stessa web application possono essere installate nel medesimo web container. Tuttavia, a causa del modo in cui gli URL vengono fatti corrispondere alle web application, ciascuna applicazione nel web container deve avere un nome unico, senza "doppioni". La sezione successiva analizzer in maggiore dettaglio i tipi di componenti che possono risiedere in una web application.

4.2.1 Elementi di una web application


Non tutte le web application sono identiche e non tutte avranno gli stessi requisiti funzionali e non funzionali a seconda delle organizzazioni e dei dipartimenti, o anche nello stesso mercato. Pertanto, non tutte le web application conterranno il medesimo tipo di risorse. In generale, comunque, le web application possono essere costituite da uno o pi di uno dei seguenti tipi di componenti: Servlets JSP pages JavaBeans e classi di utilit standard Documenti HTML File multimediali (immagini, audio e video, disegni CAD, etc.) Applet lato client, fogli stile e JavaScript Documenti di testo Meta-informazioni, che tengono insieme tutti i suddetti componenti

4.3 Struttura della directory di una web application


Una web application consiste tipicamente di una gerarchia strutturata di directory. Sebbene le specifiche delle servlet non richiedano espressamente che i container per servlet supportino la struttura gerarchica, viene comunque raccomandato che ci accada e la maggior parte dei servlet container, se non tutti, si conformano a questa raccomandazione. La directory principale della struttura gerarchica funge come document root per la web application. Come detto prima, request effettuate utilizzando il context path root di una web application vengono soddisfatte fuori dalla directory di tale web application. Nella gerarchia delle directory della web application, deve essere creata una directory speciale, denominata WEB-INF, che sar il repository per le meta-intormazioni relative alla web application. E' una directory privata: nessuna risorsa presente in essa accessibile a un client; le risorse della directory WEB-INF, per, sono visibili alle classi Java e alle servlet che risiedono nella web application. Dal momento che le specifiche per le Servlet richiedono che la directory WEB-INF non debba essere visibile a un client web, si tratta della collocazione ideale per i file e le altre risorse che non si desidera esporre al di fuori dell'applicazione: bene collocare qui i file XML di configurazione e altre risorse private della web application. Come si vedr pi avanti, anche il file di configurazione di Struts viene collocato qui. Il descrittore di deploy della web application di cui si parla in dettaglio nella sezione successiva andrebbe posto nella WEB-INF. Ci sono due sottodirectory particolari in WEB-INF che vengono trattate in modo speciale dal web container. La directory WEB-INF/classes utilizzata per le servlet e le classi di utilit che possono essere usate dalla web application. Se le classi Java hanno lo scope entro un package Java, la directory classes deve contenere la giusta sottodirectory corrispondente al nome del package. Supponendo di avere una servlet Java denominata com.oreilly.struts.framework.StorefrontController per una web application chiamata Storefront, il file della classe StorefrontController.class dovrebbe essere collocato nella directory del framework come indicato in Figura 4-2 .

Figura 4-2. Le classi java devono essere collocale nelle directory giuste

L'altra directory speciale la WEB-INF/lib, dove si pu effettuare il deploy di file Java Archive (JAR) che poi potranno essere prelevati dal class loader per la web application. Sulla base delle specifiche Servlet 2.3, il class loader della web application deve caricare le classi prima dalla directory WEB-INF/classes, poi dai JAR presenti nella WEB-INF/lib.

Fatti salvi questi particolari requisiti, la struttura delle directory di una web application lasciata alle decisioni dello sviluppatore e dovrebbe essere basata sulle necessit funzionali e non funzionali dell'applicazione. Con web application pi piccole, i file e le risorse possono essere raccolti in poche directory comuni. Con web application pi grandi, per, potr esserci la necessit che ogni componente abbia la sua sottodirectory separata sotto la root dell'applicazione. Ci faciliter sviluppo e manutenzione. La Figura 4-3 mostra la struttura delle directory per la web application Storefront.

Figura 4-3. La struttura delle directory della web application Storefront

Ecco alcuni aspetti da tenere in considerazione nella scelta delle directory per la nostra applicazione Struts: Mantenere separati i componenti facoltativi da quelli indispensabili, per facilitare il deploy parziale. Prendere in considerazione le dimensioni della squadra che effettuer il deploy e la necessit di evitare "contenziosi" per l'uso e il controllo dei file. Prestare attenzione alle dipendenze tra file e risorse, per sfruttare al meglio il riuso e l'inclusione dei file. Rendere le convenzioni su struttura della directory e denominazione dei package quanto pi intuitive ed esplicative possibile. Per molti container, nei nomi di directory e file c' differenza tra maiuscole e minuscole: quindi molto importante essere coerenti con la convenzione (Maiuscolo/minuscolo) che si decide di adottare

4.3.1 File archive delle web application


Le web application sono spesso confezionate in package usando file di tipo web archive (WAR), che devono avere l'estensione war. Per esempio, se avessimo fatto il package dell'applicazione Storefront come file WAR, sarebbe stato chiamato storefront.war. Quando una web application viene "impacchettata" come file WAR, deve mantenere la sua struttura relativa delle directory come illustrato in Figura 4-3. Seppur con alcune sottili differenze, un file WAR in definitiva uno ZIP con estensione diversa. Infatti, si pu anche usare una utilit ZIP come WinZip per creare e scompattare i file WAR

I web container sono in grado di caricare un file WAR e di scompattare correttamente le risorse nella struttura richiesta dal container. Il formato WAR utile soprattutto quando necessario che l'applicazione sia distribuita. Tale tipo di file pu inoltre fare parte di un file distribuibile, molto pi grande, detto enterprise archive (EAR). Il Capitolo 16 tratta le pratiche migliori per effettuare il package delle proprie applicazioni utilizzando questi diversi formati.

4.4 Il descrittore di deploy della web application


II deployment descriptor della web application trasmette le informazioni di configurazione tra chi sviluppa l'applicazione, chi ne effettua il deploy e chi l'assembla. Inoltre, i web container utilizzano il descrittore per configurare e caricare le web application quando viene avviato il container. Tutti i servlet container che aderiscono alle specifiche Servlet 2.3 devono supportare i seguenti tipi di informazioni di deploy

Parametri di inizializzazione Configurazione delle sessioni Dichiarazioni delle servlet Mapping delle servlet Classi listener per il ciclo di vita dall'applicazione Definizione e mapping di filtri Mapping dei tipi MIME Elenco dei file di benvenuto Pagine di errore Le informazioni di configurazione per la sicurezza non sono obbligatorie tranne quando il servlet container fa parte di un'implementazione J2EE. 1 seguenti elementi non sono richiesti a meno che il container per le servlet usi le pagine JSP o faccia parte di un application server J2EE: Mapping di librerie di tag JNDI References Gran parte delle funzionalit descritte in questo elenco sono state aggiunte nella versione 2.3 delle specifiche per le Servlet e non saranno disponibili per chi usa un container aggiornato alla versione 2.2. Struts versione 1.1 supporta le specifiche Servlet 2.2 e 2.3.

4.4.1 Il DTD del descrittore di deploy per una web application


Sia per il descrittore di deploy della web application, che per il file di configurazione di Struts, il formato si basa su un Document Type Definition (DTD), che definisce i blocchi di costruzione che possono lecitamente essere utilizzati nei file XML. Dal punto di vista del DTD, tutti i documenti XML, compresi il descrittore di deploy della web application e il file di configurazione di Struts, sono composti dei seguenti elementi: Elementi Tags Attributi Entit PCDATA CDATA Utilizzando questi componenti, i DTD aiutano a specificare documenti XML well-formed (ben formati). [2] Il DTD per il descrittore di deploy di applicazioni versione 2.3 pu essere scaricato dall'indirizzo http://java.sun.com/dtd/index.html.
[2]

Un documento XML well-formed (ben formato) quello formattato correttamente con tutti i tag aperti e chiusi adeguatamente, tutti gli attributi correttamente indicati, tutte le entit dichiarate e cos via. Quando un documento XML ben formato, per un programma diventa pi facile effettuarne il parsing e inviarlo in rete. Un documento XML valid (valido) quello che dichiara un DTD e aderisce alle regole stabilite in tale DTD. Per ulteriori infbrmazioni, vedi Java & XML d Brett McLaughlin (O'Reilly).

Le seguenti dichiarazioni del DTD mostrano gli elementi top-level che costituiscono il descrittore di deploy per una web application:

<!ELEMENT web-app (icon?, display-name?, description?, distribuTabella?, context-param*, filter*, filter-mapping*, listener*, servlet*, servlet-mapping*, session-config?, mimemapping*, welcome-file-list?, error-page*, taglib*, resourceenv-ref*, resource-ref*, security-constraint*, login-config?, security-role*, env-entry*, ejb-ref*, ejb-local-ref*) >

L'elemento web-app la root del descrittore di deploy di una web application. Gli altri elementi all'interno delle parentesi sono elementi figli, che devono essere collocati all'interno dell'elemento root web-app nel file XML. I simboli vicino agli elementi figli indicano la molteplicit consentita degli elementi figli nel file XML. La Tabella 4-1 fornisce una breve spiegazione dei simboli. L'ordine degli elementi figli dipende dal loro ordine nell'elemento genitore. Per esempio, nella sintassi di web-app, l'elemento servlet viene prima di servlet-mapping, l'elemento servlet-mapping deve venire prima di taglib, e cos via.
Tabella 4-1. Simboli di molteplicit degli elementi figlio in un DTD

Simbolo Nessun simbolo + * ?

Significato Indica che l'elemento figlio deve ricorrere una e una sola volta nell'elemento genitore. Dichiara che lelemento figlio pu ricorrere una o pi volte entro lelemento genitore. Dichiara che l'elemento figlio pu ricorrere zero o pi volte entro l'elemento genitore. Questo simbolo usato abbastanza spesso. Dichiara che l'elemento figlio pu ricorrere zero o una volta entro l'elemento genitore. In altre parole, l'elemento figlio facoltativo. Questo simbolo usato abbastanza spesso.

4.5 Configurare il file web.xml per Struts


Sebbene il file web.xml sia usato genericamente per configurare ogni web application, in questo file ci sono alcune opzioni di configurazione specifiche per Struts che devono essere configurate quando si utilizza l'infrastruttura Struts. La sezione successiva descrive i passi che necessario seguire per configurare correttamente un'applicazione Struts.

4.5.1 Effettuare il mapping della ActionServIet di Struts


Il primo passo, forse il pi importante, consiste nel configurare la ActionServlet che ricever tutte le request in entrata per l'applicazione E necessario configurare solamente una singola ActionServlet. indipendentemente dal numero di sottoapplicazioni che saranno usate. Alcuni sviluppatori scelgono di importare svariate servlet controller per gestire arce funzionali diverse dell'applicazione. Dal momento che le servlet sono multithread, usando mapping ad ActionServlet multiple, non si ottiene alcun reale vantaggio in legnini di prestazione o scalabilit. Attualmente, il framework Struts consente che funzioni solo una servlet controller singola. Questo problema sar sicuramente risolto in una futura release e potrebbe addirittura giungere a soluzione prima che la versione 1.1 arrivi alla release finale. Sar comunque bene verificare che il problema sia stato risolto prima di tentare l'usai di controller multipli nella propria applicazione. Ci sono due passi da seguire nella configurazione per la servlet controller di Struts nel file web.xml. Il primo passo di usare l'elemento servlet per configurare l'istanza della servici che in seguito pu essere mappata nell'elemento servlet-mapping. Gli elementi figlio utilizzabili nell'elemento servlet sono mostrati di seguito:

<!ELEMENT servlet (icon?, servlet-name, display-name?, description?, (servlet-class|jsp-file), init-param*, load-on-startup?, runas?, security-role-ref*) >
Quelli che ci interessano maggiormente sono servlet-name, servlet-class, e init-param. L'elemento servlet-name specifica il nome utilizzato dal descrittore di deploy per definire un reference alla servlet per tutto il resto del file. Quando si configura l'elemento servlet-class per un'applicazione Struts, questo elemento deve specificare una classe completamente definita, che estende la org.apache.struts.action.ActionServlet.

Dal momento che la classe ActionServlet di Struts non astratta, si liberi di usarla, evitando di dover creare una sottoclasse di ActionServlet per la propria applicazione. Con le precedenti versioni di Struts era pi importante estendere ActionServlet con una delle proprie classi perch gran parte delle elaborazioni si verificavano l, e la creazione di sottoclassi consentiva di ridefnire le sue funzionalit con quelle adatte alla propria applicazione. Ma dalla versione 1.1, gran parte delle funzionalit di elaborazione sono state spostate su un'altra classe, che pu essere configurata in maniera dichiarativa (come si vedr pi avanti nel capitolo). Esistono pochi motivi per crearsi una classe ActionServlet, sebbene ci sia libert di farlo. Ecco una parte di web.xml che mostra l'elemento servlet per configurare la classe servlet:

<web-app> <servlet> <servlet-name>storefront</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> </servlet> </web-app>


Il passo successivo necessario per configurare la servlet controller di Struts nel descrittore di deploy sta nel configurare il mapping delle servlet, utilizzando l'elemento servlet-mapping. La seguente parte di descrittore di deploy illustra il modo in cui combinare l'elemento servlet-mapping con quello servlet, mostrato precedentemente:

<web-app> <servlet> <servlet-name>storefront</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>storefront</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> </web-app>
Va notato che il valore dell'elemento servlet-name dentro l'elemento servlet deve corrispondere al valore dell'elemento servlet-name contenuto nel servlet-mapping. Ci indica al web container che la ActionServlet dovrebbe servire tut te le request aventi estensione .do.

4.5.1.1 Effettuare il mapping delle request alle servlet


giunto il momento di vedere come gli URL digitati nel browser dall'utente vengano fatti corrispondere alla web application e alla servlet giuste. Quando una web application viene installata in un container, quest'ultimo ha la responsabilit di assegnare ad essa un ServletContext. Per ciascuna web application di cui si effettua il deploy in un container, viene creata una singola istanza di un oggetto ServletContext. Se il container distribuito e utilizza pi di una JVM, la web application pu avere una istanza di ServletContext separata per ciascuna JVM.

La classe ServletContext fornisce una vista esterna dell'ambiente del web container per una servlet. Una servlet pu usare l'oggetto ServletContext per accedere a risorse esterne, per registrare gli eventi in opportuni log, e per salvare attributi e oggetti a cui possono avere accesso istanze di altre servici nello stesso contesto. Si tratta essenzialmente di una risorsa condivisa nello scope dell'applicazione. Dal momento che una servlet associata a una specifica web application, tutte le request che cominciano con uno specifico path (detto context path) vengono indirizzate alla web application associata a tale servlet. Le servlet associate con l'applicazione predefinita presentano una stringa vuota ("") come context path.

Quando un web container riceve la request da un client, deve determinare la web application corretta a cui inviarla: ci possibile grazie alla corrispondenza dell'URL con il context path pi lungo corrispondente a una web application installata. Supponiamo per esempio che su un container siano installate due web application: una si chiama Storefront e si trova fuori dalla root del container nella directory /storefront,mentre la seconda si chiama Storefront_demo e si trova fuori dalla root nella directory /storefront _demo. Se al server arriva la request di un client con l'URL http://www.somehost.com/storefront_demo/login.do, il server la far corrispondere alla web application con la corrispondenza pi stretta, che in questo caso sarebbe la Storefront_demo. Una volta che il container ha determinato il context borretto della web application, deve determinare quale servlet della web application dorrebbe processare la request. Il web container utilizza l'URL della request, meno il context path, per determinare il path da usare per effettuare il mapping della request alla servlet corretta. Per individuare la prima corrispondenza utile, il container utilizza le seguenti indicazioni: 1. 2. 3. 4. Il container cerva una corrispondenza esatta tra path della request e della servlet. Il container tenta in maniera ricofsiva di far corrispondere il prefisso di path pi lungo. Viene selezionata, se presente, la servlet con la corrispondenza pi lunga. Se il path dell'URL contiene un'estensione per esempio, do il servlet container cerca corrispondenza con una servlet che gestisce richieste per tale estensione. L'estensione di definisce come la parte del segmento successiva all'ultimo punto (.). Se nessuna delle precedenti regole .sortisce una corrispondenza, il container cerca di usare una servlet predefinita, se ce ne una configurata. Altrimenti la request restituisce una response di errore. Nella ricerca di corrispondenze, il web container utilizza confronti tra stringhe di tipo case-sensitive (differenza tra Maiuscole e minuscole).

Si fatto cenno al concetto di mapping delle estensioni nel punto 3 delle indicazioni di corrispondenza. C' un altro tipo di mapping che pu essere utilizzato, ed detto path mapping. Un servlet-mapping che utilizzi il mapping dei path, permette a un URL che non ritiene estensioni di effettuare la corrispondenza alla servlet. Utilizzando il precedente mapping alle servlet di Storefront, la seguente porzione di file web.xml illustra come viene configurato il mapping dei path:

<servlet> <servlet-name>storefront</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>storefront</servlet-name> <url-pattern>>/action/*</url-pattern> </servlet-mapping> </web-app>


Usando il path mapping, tutte le request fatte corrispondere a questa web application che contengono la stringa "/action" nell'URL della request saranno servite dalla servlet Storefront, indipendentemente da ci che c' al posto del carattere *.

4.5.2 Specificare i moduli per applicazioni multiple


Come brevemente discusso nel Capitolo 3, la release 1.1 di Struts ha apportato la possibilit di definire file multipli di configurazione di Struts, uno per ciascun modulo di applicazione supportato. Nelle versioni precedenti del framework, si specificava un path relativo al singolo file di configurazione di Struts, utilizzando il parametro di inizia lizzazione config. Con la versione 1.1 e l'introduzione del concetto di moduli di applicazione, si possono ora creare molteplici file di configurazione e specificarli nel file web. xml usando parametri di inizializzazione config multipli e il prefisso del modulo dell'applicazione. La sezione che segue tratta dei parametri di inizializzazione che possono essere configurati per una servlet.

4.5.3 Dichiarare i parametri di inizializzazione


I parametri di inizializzazione vengono usati per rendere disponibili alla servlet le opzioni di configurazione, permettendo cos allo sviluppatore di influenzare in maniera dichiarativa l'ambiente di runtime della servlet. I parametri di

inizializzazione sono configurati all'interno dell'elemento servlet utilizzando elementi init-param, come mostrato nel codice seguente del file web.xml:

<web-app> <servlet> <servlet-name>storefront</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> <init-param> <param-name>host</param-name> <param-value>localhost</param-value> </init-param> <init-param> <param-name>port</param-name> <param-value>7001</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>storefront</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> </web-app>
Si pu specificare ogni parametro necessario entro l'elemento init-param, a patto che si tratti di una coppia nome/valore. Per esempio, il precedente descrittore di deploy per la web application comprendeva parametri di inizializzazione per un host e una port. Se si usano gli EJB, questo potrebbe essere il modo per includere le informazioni di connessione del server. Sono consentiti zero o pi elementi init-param. Per la servlet Struts si possono indicare specifici parametri di inizializzazione. Nelle precedenti versioni di Struts, molte opzioni di configurazione che si trovano ora nel file di configurazione di Struts venivano configurate aggiungendo elementi init-param. Sebbene applicazioni costruite e testate con la versione 1.0 continueranno a funzionare anche nella versione 1.1 del framework, potr essere il caso di spostare nella corretta collocazione all'interno del file di configurazione di Struts alcuni dei parametri di inizializzazione ancora specificati nel descrittore di deploy web. Sebbene il framework abbia funzionalit che consentono ai "vecchi" parametri di inizializzazione di funzionare nel file web.xml, qui si tratteranno i parametri della versione 1.1. La Tabella 4-2 identifica i parametri di inizializzazione specificabili in Struts 1.1.
Tabella 4-2 Parametri di inizializzazione per il file web.xml in Struts 1.1

Nome

Scopo/valore predefinito Path relativo al contesto per il file di configurazione predefinito di Struts. Il valore predefinito /WEBINF/struts-config.xml, che funge da applicazione predefinita.

config

Si possono specificare sottoapplicazioni aggiuntive, usando il valore config/ e il prefisso della sottoapplicazione. In questo esempio, il nome di init-param sarebbe config/sub1 e il valore config/sub1 potrebbe essere WEB-INF/struts-sub1-config.xml. Ci indica al controller di caricare la sottoapplicazione sub1 dal file aggiuntivo di configurazione di Struts. Si possono dichiarare tutte le sottoapplicazioni necessarie.

debug

II livello di dettaglio del debug, per la servlet, che controlla quante informazioni vengono registrate in log. un parametro facoltativo, predefinito a 0 se non specificato altrimenti, ovvero al minimo livello di logging possibile. II livello di dettaglio del debug per il Digester, che registra le informazioni a mano a mano che effettua il parsing sui file di configurazione. un parametro facoltativo, predefinito a 0 se non specificato altrimenti, ovvero al minimo livello di logging possibile.

detail

Se questo valore booleano impostato a true, qualsiasi ActionForm che utilizzi classi wrapper Java, come java.lang.Integer, sar impostata a null piuttosto che al valore predefinito per il convertHack tipo della classe. Tale comportamento tipico di Struts 1.0. Il flag facoltativo e il valore predefinito false, che significa che le propriet saranno inizializzate ai loro valori predefiniti Java (p.e.: Integer a 0, Boolean a false, e cos via).

Se si sta supportando un'applicazione Struts 1.0 usando la release 1.1 del framework, il file web.xml potrebbe contenere molti dei parametri di configurazione che vengono attualmente definiti nel file di configurazione di Struts. I parametri si applicano solamente all'applicazione predefinita e, alla line, verranno eliminati nelle release future. E meglio abituarsi a impostarli nel file di configurazione di Struts.

4.5.4 Configurare le librerie di tag


Il framework Struts fornisce diverse librerie di tag JSP che devono essere configurate nel descrittore di deploy della web application se si sceglie di usarle. Si informa il container dell'uso di queste librerie di tag dichiarando uno o pi elementi taglib all'interno del descrittore di deploy della web application. La seguente porzione del file web.xml illustra il modo in cui vengono configurate le librerie di tag all'interno dell'elemento web-app:

<web-app> <servlet> <servlet-name>storefront</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> <init-param> <param-name>host</param-name> <param-value>localhost</param-value> </init-param> <init-param> <param-name>port</param-name> <param-value>7001</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>storefront</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> <taglib> <taglib-uri>/WEB-INF/struts-html.tld</taglib-uri> <taglib-location>/WEB-INF/struts-html.tld</taglib-location> </taglib> <taglib> <taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri> <taglib-location>/WEB-INF/struts-bean.tld</taglib-location> </taglib> <taglib> <taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri> <taglib-location>/WEB-INF/struts-logic.tld</taglib-location> </taglib> </web-app>
L'elemento taglib possiede due sottoelementi: taglib-url e taglib-location. L'elemento taglib-uri specifica un URI che identifica una libreria di tag usata dalla web application; il suo valore pu essere un URI relativo o assoluto e deve trattarsi di un URI valido, ma qui usato come identificatore univoco della libreria di tag. L'elemento taglib-location specifica la collocazione (come risorsa) del file descrittore della libreria di tag. Le librerie di tag di Struts non sono le sole che possono essere dichiarate nel descrittore di deploy della web application. Se si creano le proprie librerie di tag personalizzati, si dovranno creare anche degli elementi taglib per queste.

4.5.5 Impostare l'elenco dei file di benvenuto


L'elemento welcome-file-list consente di configurare le risorse predefinite che dovranno essere usate quando per una web application viene immesso un URI valido ma parziale. Si possono specificare "file di benvenuto" multipli, che saranno usati nell'ordine con cui vengono elencati. Immaginiamo di aver configurato l'elemento welcome-file-list dell'applicazione Storefrontcome nell'Esempio 4-1.

Esempio 4-1. L'elemento welcome-file-list dell'applicazione Storefront

<welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list>


Ci indica che una request al server per http://www.somehost.com/storefront, che la root dell'applicazione Storefront, dovrebbe essere risolta a http://www.somehost.com/storefront/index.jsp. Si capisce subito il vantaggio di tutto ci, poich la maggior parte dei container, invece, ricercherebbe In maniera predefinita index.html o index.htm. All'interno del welcome-file-list si possono specificare elementi welcome-file multipli, il che pu risultare utile, per esempio, se si effettua il deploy della propria applicazione su vari tipi di container e la prima risorsa welcome-file non viene trovata sul server. Il container continuerebbe a cercare corrispondente tra i file di benvenuto e l'URI della request finch non ne trova una sul server per passare la risorsa al client. Nel processo di ricerca delle corrispondenze con i file di benvenuto viene utilizzato l'ordine delle voci specificato nel descrittore di deploy. Nell'elemento welcome-file non dovrebbero esserci caratteri "/" in coda o in testa. Se non sono dichiarati file di benvenuto per la web application o per l'URI immesso dal client, il container pu gestire comunque la richiesta in maniera appropriata, restituendo, per esempio, una pagina di errore 404 (File Not Found) o un elenco di directory. buona idea selezionare un file di benvenuto almeno per la web application principale.

4.5.5.1 Usare un'azione di Struts nell'elenco dei file di benvenuto


Dal momento che i container web non utilizzano il mapping delle servlet per le risorse nel welcome-file-list, non possibile impostare direttamente un elemento welcome-file affinch usi un'azione Struts. Ci nonostante, esiste una maniera alternativa per ottenere il medesimo risultato. Innanzitutto, nel file di configurazione di Struts si crea un forward globale per l'azione che si intende invocare:

<global-forwards> <forward name="welcome" path="viewsignin.do"/> </global-forwards>


Poi si crea una pagina JSP denominata welcome.jsp nella realt il nome pu essere qualunque altro e si usa il tag Struts forward per effettuare un forward al forward globale quando avviene il caricamento della pagina. La pagina welcome.jsp deve contenere solamente:

<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %> <html> <body> <logic:forward name="welcome"/> </body> </html> Poi necessario solo aggiungere un elemento welcome-file per la pagina welcome.jsp: <welcome-file-list> <welcome-file>welcome.jsp</welcome-file> </welcome-file-list>
Quando il container utilizzer la risorsa welcome.jsp, effettuer automaticamente un forward verso il forward denominato welcome, definito nella sezione global-forwards. Il forward welcome, a sua volta, invocher l'azione viewsignin.do ottenendo il risultato desi derato.

4.5.6 Configurare la gestione degli errori nel file web.xml


Sebbene Struts fornisca un meccanismo di gestione degli errori adatto, a volte i problemi si presentano per una non corretta gestione delle eccezioni e agli utenti viene mostrata un'eccezione relativa a servlet o JSP. Per evitare assolutamente che ci si verifichi, si dovrebbe utilizzare l'elemento error-page disponibile per il descrittore di deploy della web application. L'Esempio 4-2 mostra una porzione del file web.xml che usa l'elemento error-page per evitare agli utenti di vedere gli errori 404 o 500 (Internal Server).
Esempio 4-2. Uso dell'elemento error-page

<web-app> <!-- Other elements go here --> <error-page> <error-code>404</error-code> <location>/common/404.jsp</location> </error-page> <error-page> <error-code>500</error-code> <location>/common/500.jsp</location> </error-page> </web-app>
Quando il codice di stato di un errore viene impostato nella response, il container consulta la lista delle dichiarazioni di error-page per la web application. Se viene trovata una corrispondenza, il container restituisce la risorsa indicata dall'elemento location. Il valore dell'elemento location deve cominciare con un carattere "/" e deve fare riferimento a una risorsa presente all'interno della web application. Se necessario fare riferimento a una risorsa esterna alla web application, si pu usare il metatag HTML Refresh. Per compiere tale operazione, si fa riferimento a un documento HTML statico nell'elemento location, il quale contiene solo la seguente riga:

<meta http-equiv="Refresh" content="0;URL=http://www.somehost.com/404.jsp">


Quando si verifica l'errore, il metatag Refresh effettuer immediatamente il reload, ma utilizzer l'URL alternativo che viene fornito. Questa strategia anche un buon modo per consentire agli utenti di fare riferimento a risorse con estensioni statiche, come .htm, ma effettuare poi il reload di una pagina dinamica, come una JSP. Si potrebbe usare questo approccio per mostrare una pagina di tipo "Elaborazione in corso". Sar bene impostare il tempo di reload a un valore superiore allo zero. L'URL pu essere un'azione di Struts, e se l'elaborazione non conclusa, essa continuer semplicemente a richiamare ancora la stessa pagina. Se l'elaborazione stata conclusa, pu effettuare il forward a una pagina completata. Una servlet pu anche generare eccezioni per le quali possibile dichiarare le pagine di errore. Invece di specificare l'elemento error-code, si pu specificare una classe Java pienamente qualificata usando l'elemento exceptiontype. Durante l'elaborazione, le servlet possono lanciare le seguenti eccezioni:

RuntimeException o Error ServletException o sottoclassi IOException o sottoclassi


La classe di eccezione Java dichiarata nell'elemento exception-type deve rientrare tra le suddette. L'Esempio 4-3 illustra il modo in cui si sostituiscono gli elementi exception-type a quelli error-code.

<web-app> <error-page> <exception-type>javax.servlet.ServletException</exception-type> <location>/common/system_error.jsp</location> </error-page> </web-app>

Esempio 4-3. Uso dell'elemento exception-type al posto dell'error-code

Per gran parte del capitolo sono state mostrate solamente porzioni dei descrittori di deploy, fondamentalmente per risparmiare spazio e per concentrarci sui vari elementi supportati. ora giunto il momento di mostrare un esempio completo di descrittore di deploy per una web application: l'Esempio 4-4 mostra quello dell'applicazione Storefront.
Esempio 4-4 Un file web xml completo, configurato per Struts 1.1

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <servlet> <servlet-name>storefront</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> <init-param> <param-name>debug</param-name> <param-value>3</param-value> </init-param> <init-param> <param-name>detail</param-name> <param-value>3</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>storefront</servlet-name> <url-pattern>/action/*</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <error-page> <error-code>404</error-code> <location>/common/404.jsp</location> </error-page> <error-page> <error-code>500</error-code> <location>/common/500.jsp</location> </error-page> <taglib> <taglib-uri>/WEB-INF/struts-html.tld</taglib-uri> <taglib-location>/WEB-INF/struts-html.tld</taglib-location> </taglib> <taglib> <taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri> <taglib-location>/WEB-INF/struts-bean.tld</taglib-location> </taglib> <taglib> <taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri> <taglib-location>/WEB-INF/struts-logic.tld</taglib-location> </taglib> </web-app>

4.6 Il file di configurazione di Struts


Il framework Struts fa affidamento sa uno o pi file di configurazione per riuscire a caricare e creare all'avvio i necessari componenti specifici per l'applicazione. I file di configurazione consentono di specificare in maniera dichiarativa il comportamento dei componenti del framework, evitando; che le informazioni e il comportamento siano inseriti rigidamente nel codice delle applicazioni. Ci garantisce agli sviluppatori la fles sibilit di fornire le loro proprie estensioni, che il framework pu scoprire in maniera dinamica. Il file di configurazione si basa sul formato XML e pu essere validato con il DTD di Struts struts-config_1_1.dtd. Sebbene esistano varie somiglianze tra le versioni 1.0 e 1.1 per quanto attiene al file di configurazione, esistono comunque altrettante differenze. Fortunatamente i progettisti del framework hanno adottato tra gli scopi di Struts 1.1 anche la retrocompatibilit: pertanto le applicazioni versione 1.0 dovrebbero continuare a funzionare adeguatamente con la nuova versione del framework.

4.6.1 Configurare moduli applicativi multipli


Si fatto cenno ai moduli applicativi nel Capitolo 3, ma questa nuova caratteristica non stata ancora presentata in maniera completa. Con i moduli applicativi possibile definire file di configurazione di Struts multipli, uno per ciascun modulo supportato. Ciascun modulo applicativo pu fornire le sue informazioni di configurazione, comprese risorse di messaggi, e pu essere completamente indipendente dagli altri. I moduli applicativi permettono che una singola applicazione Struts sia suddivisa in progetti separati, rendendo pi facile il processo di sviluppo parallelo. Sebbene nel framework esistano tutte le funzionalit necessarie ai moduli applicativi, se ne pu implementare anche solo uno (il modulo applicativo predefinito). Si approfondir il discorso sui moduli applicativi nei Capitoli 5, 6 e 7. Per ora, ci concentreremo sulla configurazione dell'applicazione predefinita e vedremo poi come facile aggiungere ulteriori moduli.

4.7 Il package org.apache.struts.config


A partire dalla versione 1.1 di Struts stato aggiunto il package org.apache.struts.config. Il framework usa i JavaBeans a runtime per trattenere le informazioni di configurazione che legge dai file di configurazione di Struts. La Figura 4-4 mostra le classi principali del package config.

Figura 4-4. Diagramma delle classi del package org.apache struts.config

Ciascuna classe del package config mantiene le informazioni provenienti da una sezione specifica del file di configurazione. Dopo che il file di configurazione ha passato validazione e parsing, il framework Struts usa delle istanze di questi bean per rappresentare versioni "in memoria" delle informazioni che sono state dichiarate nel file di configurazione. Queste classi fungono da container a runtime per le informazioni di configurazione e vengono utilizzate dai componenti del framework come necessario. La classe org.apache.struts.config.ConfigRuleSet mostrata in Figura 4-4 svolge un lavoro leg germente differente, ma collegato al resto: contiene la serie di regole necessarie per effettuare il parsing di un file di configurazione di Struts. Il suo compito sta nel costruire istanze dei JavaBeans di configurazione, cui si fatto cenno nel paragrafo precedente, quando l'applicazione si avvia.

4.7.1 La classe ApplicationConfig


La classeorg.apache.struts.config.ApplicationConfig merita una presentazione speciale, dal momento che svolge un ruolo molto importante. Come indica la Figura 4-4 riveste un ruolo centrale per l'intero package config e , conserva strettamente le informazioni di configurazione che descrivono un'intera applicazione Struts. Se sono in uso molteplici sottoapplicazioni, c' un oggetto ApplicationConfig per ciascuna di queste. La classe ApplicationConfig ritorner in tutto il resto della nostra discussione sul framework.

4.7.2 Il DTD di configurazione di Struts


Cos come il DTD della web application viene utilizzato per validare il file web.xml, il DTD di Struts usato per validare il file di configurazione di Struts. Il codice completo di un file struts-config.xml riportato pi avanti, nell'Esempio 4-5. Ad esso si far riferimento per la discussione dei vari elementi.

La seguente dichiarazione del DTD di Struts indica che struts-config l'elemento root per il file XML e che possiede otto elementi figli:

<!ELEMENT struts-config (data-sources?, form-beans?, global-exceptions?, globalforwards?, action-mappings?, controller?, message-resources*, plug-in*) >

4.7.2.1 L'elemento data-sources


L'elemento data-sources permette di, impostare una rudimentale datasource che pu essere utilizzata dall'interno del framework Struts. Una datasource funge da factory [3] per le connessioni ai database e fornisce un punto di controllo singolo. Molte implementazioni di datasource usano un meccanismo di pool delle connessioni per migliorare prestazioni e scalabilit.
[3]

Si veda la trattazione sul pattern Abstract Factory nel libro Design Pattems; Elementi of Reusable ObjectOriented Software della Gang of Four (Addison Wesley).

Molti produttori forniscono le loro Implementazioni di oggetti datasource. Il linguaggio Java fornisce l'interfaccia javax.sql.DataSource, che deve essere implementata da tutte le altre implementazioni. La maggior parte degli application server e alcuni web container forniscono componenti datasource gi incorporati. Inoltre, tutti i produttori dei pi diffusi database forniscono delle implementazioni di datasource. L'elemento data-sources pu contenere zero o pi elementi data-source:

<!ELEMENT data-sources (data-source*)>


Un elemento data-source permette di specificare molteplici elementi set-property:

<!ELEMENT data-source (set-property*)>


L'elemento set-property consente di configurare propriet specifiche per la propria implementazione di datasource.

Per tutta la discussione riguardante gli elementi di configurazione di Struts per il resto del capitolo, si noter un elemento figlio denominato set-property in molti degli elementi principali del file di configurazione. L'elemento set-property specifica il nome e il valore di un'ulteriore propriet di configurazione dei JavaBeans il cui metodo di set verr invocato nell'oggetto che rappresenta l'elemento inglobante. Questo elemento utile specialmente per passare ulteriori informazioni di propriet a una classe estesa di implementazione. L'elemento set-property facoltativo e lo si user solo se necessario passare informazioni aggiuntive a una classe di configurazione. L'elemento set-property definisce tre attributi, compreso l'attributo id, usato raramente. L'attributo property il nome della propriet JavaBeans il cui metodo di set sar invocato. L'attributo value una stringa che rappresenta il valore che sar passato al metodo di set dopo opportuna conversione. Questa sezione fornisce un esempio dell'uso dell'elemento set-property. Lo stesso formato replicato laddove venga dichiarato l'elemento set-property. Gli attributi per l'elemento data-source sono elencati nella Tabella 4-3. Tabella 4-3. Attribuiti dell'elemento data-source Nome Descrizione Attualmente non usato.

id

La classe di implementazione del bean di configurazione che manterr le informazioni della datasource. Se specificato, deve trattarsi di una classe derivata da className org.apache.struts.config.DataSourceConfig, che rappresenta il valore predefinito quando non specificato nulla. Questo attributo facoltativo.

key

L'attributo del contesto della servlet sotto il quale sar salvato la datasource. Questo attributo facoltativo; il valore predefinito Action.DATA_SOURCE_KEY. II nome pienamente qualificato della classe Java di implementazione della datasource. La classe rappresentata da questo valore deve implementare javax.sql.DataSource ed essere configurabile a partire da propriet JavaBeans. Questo attributo facoltativo; il valore predefinito org.apache.struts.util.GenericDataSource. La classe GenericDataSource inclusa nel framework Struts stata deprecata in favore del progetto DBCP (Database Connection Pool) di Jakarta o dell'implementazione fornita dal proprio container.

type

Ecco come configurare una datasource in un file di configurazione di Struts:

<data-sources> <data-source> <set-property property="autoCommit" value="true"/> <set-property property="description" value="MySql Data Source"/> <set-property property="driverClass" value="com.caucho.jdbc.mysql.Driver"/> <set-property property="maxCount" value="10"/> <set-property property="minCount" value="2"/> <set-property property="user" value="admin"/> <set-property property="password" value="admin"/> <set-property property="url" value="jdbc:mysql-caucho://localhost:3306/storefront"/> </data-source> </data-sources>
Questo codice illustra un elemento data-source configurato per connettersi a un database MySQL tramite un driver JDBC della Caucho Technology, azienda sviluppatrice del container servlet/EJB Resin. Si possono specificare datasource multiple nel file di configurazione, assegnare ciascuna di esse a una chiave unica e accedere a una particolare datasource nel framework tramite la sua chiave. Questo fornisce la possibilit di accedere a database multipli, se necessario. Sebbene le funzionalit di base per le datasource fornite dal framework funzionino piuttosto bene, la nostra applicazione potrebbe aver bisogno di un'implementazione di datasource pi robusta. Esistono svariate implementazioni di datasource piuttosto diffuse, alcune delle quali vengono elencate nella Tabella 4-4.

Tabella 4-4. Implementazioni alternative di datasource Nome Poolman Expresso JDBC Pool DBCP Produttore Open source Jcorporate Open source Jakarta http://www.jcorporate.com http://www.bitmechanic.com/projects/jdbcpool/ http://jakarta.apache.org/commons/index.html URL http://sourceforge.net/projects/poolman/

Il creatore delle librerie open source Poolman non segue pi il progetto. Sebbene funzioni bene e sia sempre disponibile su SourceForge.net, da un po' di tempo che tale implementazione non aggiornala. Ovviamente, visto che open source, sempre possibile apportare i cambiamenti e gli aggiustamenti necessari.

4.7.2.2 L'elemento form-beans


L'elemento form-beans consente di configurare classi ActionForm multiple che vengono usate dalle view. All'interno della sezione form-beans, si possono configurare zero o pi clementi figli form-bean. Ciascun elemento form-bean possiede a sua volta elementi derivati.

<!ELEMENT form-bean (icon?, display-name?, description?, set-property*, formproperty*) > Per ciascun elemento form-bean si possono inoltre specificare quattro attributi, elencati nella Tabella 4-5.
Tabella 4-5. Attributi dell'elemento form-bean

Nome Quando non si vuole

Descrizione

className

org.apache.struts.config.FormBeanConfig, possibile specificare qui la propria classe, che deve estendere la FormBeanConfig. Questo attributo facoltativo e il framework utilizzer un'istanza della classe FormBeanConfig se non specificato.
Se

utilizzare

il

bean

di

configurazione

standard

dynamic

la classe identificata dall'attributo type un'istanza della org.apache.struts.action.DynaActionForm o una sua sottoclasse, questo valore andrebbe impostato a true, altrimenti sar false. Questo attributo facoltativo; il valore predefinito false. L'attributo stato deprecato e il framework adesso lo determina automaticamente. Un identificatore unico per questo bean, che viene usato per effettuarne il reference in tutto il framework. Questo valore obbligatorio e deve essere unico all'interno della singola sottoapplicazione. Nome pienamente qualificato di una classe Java che estende la ActionForm di Struts. Se tale valore specificato in org.apache.struts.action.DynaActionForm, Struts generer dinamicamente un'istanza della DynaActionForm. Attributo obbligatorio. Occorre fare attenzione quando si configura il valore dell'attributo type: deve trattarsi del nome pienamente qualificato della classe di implementazione di ActionForm. Se si sbaglia a digitare il nome potrebbe risultare assai difficile il debug di questo problema.

name type

Come

accennato

nel

Capitolo

3,

un

form

bean

una

classe

JavaBeans

che

estende

la

classe

org.apache.struts.action.ActionForm. Il codice che segue mostra il modo in cui l'elemento form-beans


pu essere configurato nel file di configurazione di Struts:

<struts-config> <form-beans> <form-bean name="loginForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="username" type="java.lang.String"/> <form-property name="password" type="java.lang.String"/> </form-bean> <form-bean name="shoppingCartForm" type="com.oreilly.struts.order.ShoppingCartForm"/> </form-beans>

</struts-config>
Uno degli elementi form-bean presenti nel codice appena visto utilizza una delle nuove caratteristiche di Struts 1.1, detta action-form dinamici. Degli action form dinamici si fatta menzione nel Capitolo 3 e si parler approfonditamente nel Capitolo 7. Una o pi propriet dinamiche possono essere passate a un'istanza di

org.apache.struts.action.DynaActionForm con l'elemento form-property, supportato solo quando l'attributo type dell'elemento form-bean inglobante org.apache.struts.action.DynaActionForm, o una sua classe derivata. inoltre possibile specificare per ciascun elemento form-property quattro attributi, elencati nella Tabella 4-6.
Tabella 4-6 Attributi dell'elemento form-property

Nome Quando non si vuole

Descrizione utilizzare il bean di configurazione standard

className org.apache.struts.config.FormPropertyConfig, si pu specificare qui la propria


classe. Questo attributo non obbligatorio.

initial name type

Una rappresentazione sotto forma di stringa del valore iniziale di questa propriet. Se non specificato altrimenti, le primitive saranno inizializzate a zero e gli oggetti a null. Questo attributo non obbligatorio. II nome della propriet JavaBeans della propriet che questo elemento definisce. Si tratta di un attributo obbligatorio. II nome completamente definito della classe Java di implementazione di questa propriet del bean, seguito eventualmente da "[]" per indicare che si tratta di una propriet indicizzata. Questo attributo obbligatorio.

La seguente porzione di codice del fom-bean illustra l'uso dell'elemento form-property: <form-bean name="checkoutForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="firstName" type="java.lang.String"/> <form-property name="lastName" type="java.lang.String"/> <form-property name="age" type="java.lang.Integer" initial="18"/> </form-bean>

4.7.2.3 L'elemento global-exceptions


La sezione global-exceptions permette di configurare in maniera dichiarativa la gestione delle eccezioni. L'elemento global-exceptions pu contenere zero o pi elementi exceptions:

<!ELEMENT global-exceptions (exception*)>


Pi avanti nel capitolo, quando si parler del mapping delle azioni, si vedr che l'elemento exception pu essere specificato anche nell'elemento action. Se un elemento exception configurato per lo stesso tipo di eccezione sia nell'elemento global-exceptions che in action, avr la precedenza il livello action. Se nel livello action non viene trovato alcun mapping all'elemento exception, il framework cercher i mapping delle eccezioni definiti per la classe genitore dell'eccezione. Alla fine, se non viene trovato alcun handler, sar lanciata una ServletException o una IOException, a seconda del tipo dell'eccezione originale. Il Capitolo 10 tratta in dettaglio la gestione delle eccezioni sia dichiarativa che programmatica. Questa sezione mostra invece come configurare la gestione dichiarativa della eccezioni per le nostre applicazioni. L'elemento exception descrive un mapping tra una eccezione Java che pu verificarsi durante l'elaborazione di una request e un'istanza di org.apache.struts.action.ExceptionHandler responsabile di trattare l'eccezione lanciata. La dichiarazione dell'elemento exception illustra che anche questo ha diversi elementi figli:

<!ELEMENT exception (icon? display-name? description? set-property*)>


Maggiore importanza degli elementi figli rivestono probabilmente i sette attributi elencati nella Tabella 4-7 che possono essere specificati nell'elemento exception.

Tabella 4-7. Attributi dell'elemento exception

Nome

Descrizione

La classe di implementazione del bean di configurazione che manterr le informazioni sull'eccezione. Se specificata, deve essere una classe derivante dalla className org.apache.struts.config.ExceptionConfig. che la classe predefinita quando nessun valore sia specificato.

handler

Il nome completamente definito della classe Java dello handler dell'eccezione che elaborer l'eccezione. Se non specificato alcun valore, sar usata la classe predefinita org.apache.struts.action.ExceptionHandler. Se per questo attributo viene specificata una classe, deve trattarsi di una che derivi dalla ExceptionHandler. Una chiave di messaggio specificata nel resource bundle per questa sottoapplicazione. Questo valore viene usato dall'istanza di ActionError. Il path relativo all'applicazione della risorsa verso la quale effettuare il forward se si verifica l'eccezione. Attributo facoltativo; se non specificato alcun valore, il framework si imposter in maniera predefinita all'attributo input per il mapping dell'azione. L'identificatore del livello di scope nel quale l'istanza di ActionError dovrebbe essere immagazzinato. L'attributo value deve essere request o session. Si tratta di un attributo facoltativo e sar impostato automaticamente a request se non specificato. Il nome completamente definito di classe Java dell'eccezione che deve essere gestita. Questo attributo obbligatorio, perch identifica l'eccezione, dato che il framework non pu conoscerla preventivamente. L'attributo ServletContext che identifica un resource bundle dal quale l'attributo key di questo elemento dovrebbe provenire.

key path

scope type bundle

Di seguito riportato un esempio di un elemento global-exceptions:

<global-exceptions> <exception key="global.error.invalidlogin" path="/security/signin.jsp" scope="request" type="com.oreilly.struts.framework.exceptions.InvalidLoginException"/> </global-exceptions>

4.7.2.4 L'elemento global-forwards


Ciascuna azione che viene eseguita si conclude con un forward o un redirect a una vista, che pu essere una pagina dinamica JSP o statica HTML, ma anche un altro tipo di risorsa. Invece di fare diretto riferimento alla vista, il framework Struts usa il concetto di forward per associare un nome logico con la risorsa. Pertanto, invece di fare diretto riferimento a una login.jsp, l'applicazione Struts, per esempio, pu effettuare il riferimento a questa risorsa come al forward login. La sezione global-forwards consente di configurare dei forward che possano essere utilizzati da tutte le azioni di un'applicazione e si compone di zero o pi elementi forward:

<!ELEMENT global-forwards (forward*)>


L'elemento forward effettua il mapping di un nome logico a un URI relativo a un'applicazione. L'applicazione pu poi effettuare un forward o un redirect utilizzando il nome logico piuttosto che letteralmente l'URI, con un maggior disaccoppiamento della logica del controller e del model dalla view. L'elemento forward pu essere definito sia nell'elemento global-forwards che in quello action. Se in entrambi gli elementi viene definito un forward con lo stesso nome, la precedenza spetta a quello riportato a livello di action. La dichiarazione dell'elemento forward

illustra anche i suoi vari elementi figli: <!ELEMENT forward(icon?, display-name?, description, set-property*)>
Come nel caso di exception, gli attributi dell'elemento forward, mostrati nella Tabella 4-8 sono probabilmente pi interessanti degli elementi figli.

Tabella 4-8. Attributi dell'elemento forward

Nome

Descrizione La classe di estensione del bean di configurazione che manterr le informazioni di forward. La classe predefinita org.apache.struts.action.ActionForward se non specificato altrimenti. Si tratta di un attributo facoltativo. Viene impostato a true per indicare che la risorsa specificata nell'attributo path dovrebbe essere interpretata come relativa all'applicazione se il path comincia con un carattere "/". Ci al fine di permettere alla risorsa specificata dall'attributo path di risiedere in un'altra sottoapplicazione. Attributo facoltativo, dal valore predefinito pari a false. Un valore unico, utilizzato, per effettuare il reference a questo forward nell'applicazione. Si tratta di un valore obbligatorio. Un URI relativo all'applicazione (se l'attributo contextRelative false) o relativo al contesto (se l'attributo contextRelative true) verso il quale dovrebbe avvenire il forward o il redirect del controllo. Questo attributo obbligatorio e deve cominciare con un carattere "/". Un valore booleano che determina se RequestProcessor debba effettuare un forward o un redirect quando utilizza questo mapping di forward. un attributo facoltativo con valore prenefinito false, che indica l'effettuazione di un forward.

className

contextRelative

name

path

redirect

Ecco un esempio di elemento global-forwards dal codice dell'applicazione Storefront: <global-forwards> <forward name="Login" path="/security/signin.jsp" redirect="true"/> <forward name="SystemFailure" path="/common/systemerror.jsp"/> <forward name="SessionTimeOut" path="/common/sessiontimeout.jsp" redirect="true"/> <forward name="Welcome" path="viewsignin"/> </global-forwards> La classe org.apache.struts.action.ActionForward utilizzata per mantenere le informazioni configurate nell'elemento controller (di cui si parla pi avanti). La classe ActionForward estende ora la org.apache.struts.config.ForwardConfig, per ragioni di retrocompatibilit.

4.7.2.5 L'elemento action-mappings


L'elemento action-mapping contiene una serie di zero o pi elementi action:

<!ELEMENT action-mappings (action*)>


L'elemento action descrive un mapping tra il path di una specifica request e la corrispondente classe Action. Il controller seleziona una mapping particolare facendo corrispondere il path dell'URI della request con l'attributo path in uno degli elementi action. L'elemento action contiene i seguenti elementi figli:

<!ELEMENT action (icon?, display-name?, description, set-property*, exception*, forward*)>


Due di questi dovrebbero subito balzare all'occhio, poich li abbiamo gi visti in precedenza in questo capitolo: sono exception e forward. Del primo abbiamo parlato a proposito dell'elemento global-exceptions spiegando come elementi exception possano essere definiti a livello globale o di azione. Gli elementi exception definiti entro l'elemento action hanno la precedenza su tutti gli altri dello stesso tipo definiti a livello globale. Sintassi e attributi sono gli stessi, indipendentemente dalla collocazione in cui vengono definiti. L'elemento forward stato presentato quando si parlato del global-forwards. Analogamente agli elementi exception, quelli forward possono essere definiti a livello globale e di azione. Il livello action ha la precedenza, se il medesimo forward e definito in entrambe le posizioni. I non pochi attributi dell'elemento action sono riportati in Tabella 4-9 .

Tabella Attributi dell'elemento action 4-9


Nome Descrizione II nome dell'attributo con scope di request o di sessione sotto le quali possibile l'accesso al form bean di questa action. permesso un valore solamente quando vi sia un form bean specificato nell'attributo name. Questo attributo facoltativo e non ha un valore predefinito. Se questo attributo e il name contengono un valore, avr la precedenza quello di attribute. La classe di implementazione del bean di configurazione che manterr le informazioni dell'action. La classe

attribute

className predefinita org.apache.struts.action.ActionMapping se non specificato altrimenti.


un attributo facoltativo. II path relativo all'applicazione di una servlet o di una risorsa JSP verso cui verr effettuato il forward, invece di istanziare e invocare la classe Action. Gli attributi forward, include, e type si escludono l'un con l'altro e ne deve essere specificato uno in maniera esatta. Questo attributo facoltativo; per ottenere lo stesso comportamento possibile usare org.apache.struts.actions.ForwardAction di cui si parla nel Capitolo 5. II path relativo all'applicazione di una servlet o di una risorsa JSP che saranno incluse nella response, invece di istanziare e invocare la classe Action. Gli attributi forward, include, e type si escludono l'un con l'altro e ne deve essere specificato uno in maniera esatta. Questo attributo facoltativo; per ottenere lo stesso comportamento possibile usare org.apache.struts.actions.IncludeAction di cui si parla nel Capitolo 5. II path relativo all'applicazione per il modulo di immissione dati verso il quale deve essere reindirizzato il controllo se si verifica un errore di validazione. Attributo obbligatorio se specificato l'attributo name; non consentito se name non specificato. Il nome del form bean associato con l'azione. Questo valore deve corrispondere all'attributo name prelevato da uno degli elementi form-bean definiti precedentemente. Si tratta di un valore facoltativo senza valore predefinito. Il path relativo all'applicazione per la request inviata, che comincia con un carattere "/" e senza l'estensione del nome del file, se si usa il mapping delle estensioni. In altre parole, il nome dell'action: per esempio /addToShoppingCart. un valore obbligatorio. Probabilmente sarebbe stato meglio denominare questo attributo "name" poich in definitiva il nome dell'azione.

forward

include

input

name

path

Un parametro di configurazione multiuso, utilizzabile per passare informazioni extra all'istanza dell'action selezionata da questo mapping delle azioni. Il framework basilare non usa questo valore Se viene fornito un parameter valore qui, lo si pu ottenere nella Action invocando il metodo getParameter() sull' ActionMapping passato al metodo execute().

prefix

Utilizzalo per far corrispondere i nomi di parametro della request ai nomi di propriet dei form bean. Per esempio, se tutte le propriet in un form bean cominciano con "pre_", si pu impostare l'attributo prefix in maniera che i parametri della request corrispondano alle propriet. dell' ActionForm. possibile tornire un valore qui solo se l'attributo name specificato Un elenco, separato da virgole, dei nomi dei ruoli di sicurezza a cui permesso invocare l' Action. Quando viene elaborata una request, la classe RequestProcessor verifica che l'utente possieda tra i suoi attributi almeno uno dei ruoli identificati all'interno di questo attributo. L'attributo facoltativo. Usato per identificare lo scope nel quale viene posto il form bean (se request o session), questo attributo pu essere specificato solo se l'attributo name presente. Il valore predefinito session. Utilizzato per far corrispondere i nomi di parametro della request ai nomi di propriet dei form bean. Per esempio, se tutte le propriet in un form bean finiscono in "_foo", si pu impostare l'attributo suffix in maniera che i parametri della request corrispondano alle propriet dell' ActionForm. possibile fornire un valore qui solo se l'attributo name specificato Un

roles scope

suffix

type

org.apache.struts.action.Action. Questo attributo usato per elaborare la request se gli attributi forward o include non sono specificati. Gli attributi forward, include, e type si
escludono l'un con l'altro e ne deve essere specificato uno in maniera esatta. Un valore booleano che indica se l'azione presente deve essere configurata come predefinita per l'applicazione. Se l'attributo impostato true, questa action gestir ogni request che non trattata da altre azioni. Solamente un mapping delle azioni per applicazione pu avere questo valore impostato a true. Questo attributo facoltativo ed impostato inizialmente a false. Si tratta di una buona collocazione in cui impostare una azione predefinita che catturi ogni URL di azione non valido immesso dall'utente. Probabilmente sarebbe stato meglio chiamare questo attributo "default".

nome

pienamente

qualificato

di

classe

Java

che

estende

la

unknown

Un valore booleano indicante se il metodo validate() del form bean, specificato dall'attributo name, validate debba essere invocato prima dell'invocazione al metodo execute() di questa azione.Questo attributo facoltativo preimpostato a true.

Ecco un esempio dell'elemento action di "signin" dalla applicazione Storefront:

<action path="/signin" type="com.oreilly.struts.storefront.security.LoginAction" scope="request" name="loginForm" validate="true" input="/security/signin.jsp"> <forward name="Success" path="/index.jsp" redirect="true"/> <forward name="Failure" path="/security/signin.jsp" redirect="true"/> </action>
La classe org.apache.struts.action.ActionMapping viene usata per rappresentare le informazioni configurate nell'elemento action. La classe ActionMapping estende org.apache.struts.config.ActionConfig per ragioni di retrocompatibilit.

4.7.2.6 L'elemento controller


L'elemento controller una novit di Struts 1.1. Prima della versione 1.1, le funzioni del controller erano contenute in ActionServlet, ed era necessario estendere tale classe per modificare le funzionalit. Ma, nella versione 1.1, Struts ha spostato gran parte delle funzionalit del controller alla classeRequestProcessor. La ActionServlet continua a ricevere le request, ma delega la loro gestione a un'istanza della classe RequestProcessor, permettendo cos di assegnare la classe che effettua l'elaborazione e di modificarne le funzionalit in maniera dichiarativa. Chi ha dimestichezza con la versione 1.0, noter che molti dei parametri che venivano configurati per la servlet controller nel file web.xml sono ora configurati tramite l'elemento controller. Dal momento che il controller e i suoi attributi sono definiti nel file struts-config.xml, si possono definire elementi controller separati per ciascuna sottoapplicazione. L'elemento controller ha un solo elemento figlio:

<!ELEMENT controller (set-property*)>


L'elemento controller pu contenere zero o pi elementi set-property e molti differenti attributi, mostrati nella Tabella 4-10 . Tabella 4-10 Attributi dell'elemento controller Nome Descrizione La dimensione del buffer di input utilizzato nell'elaborazione degli upload di file. Attributo facoltativo con valore predefinito a 4096. La classe di implementazione del bean di configurazione che manterr le informazioni del controller. Se specificata, deve derivare dalla org.apache.struts.config.ControllerConfig, che la classe predefinita quando non sono specificati altri valori. un attributo facoltativo. II tipo di contenuto e la codifica caratteri opzionale che viene impostata per ciascuna response Questo attributo facoltativo e il suo valore prestabilito text/html. Anche quando specificato, una action o una JSP pu sovrascriverlo. II livello di debug per l'applicazione. Il valore viene utilizzato per tutto il framework per stabilire quanto dettagliate debbano essere le informazioni di logging relative agli eventi che si verificano internamente. Maggiore il valore e pi dettagliato il logging. Questo attributo facoltativo; il valore predefinito 0, che comporta una scrittura delle informazioni in log minima o nulla. Un pattern sostitutivo che definisce il modo in cui l'attributo path di un elemento forward viene mappato a un URL relativo al contesto quando comincia con uno slash (e quando la propriet contextRelative false). Questo valore pu consistere di una qualsiasi combinazione tra quelli seguenti:

bufferSize

className

contentType

debug

forwardPattern

$A Sostituito dal prefisso di applicazione della presente sottoapplicazione $P Sostituito dall'attributo path dell'elemento forward selezionato $$ Fa si che venga mostrato il segno di valuta del dollaro $x (dove x un qualsiasi carattere non definito sopra) Viene accettato senza
conseguenze, poich riservato per usi futuri Se non specificato altrimenti, il valore predefinito di forwardPattern $A$P, che consistente con il precedente comportamento dei forward inserito nel codice.

inputForward

Si imposta a true quando si vuole che i parametri di input degli elementi action siano i nomi degli elementi forward locali o globali usati per calcolare gli URL definitivi. Si imposta a false (valore predefinito) per trattare i parametri di input degli elementi action come path relativi alla sottoapplicazione per le risorse utilizzate per il modulo di input. Valore booleano che indica se il locale preferito dell'utente immagazzinato nella sessione utente, se non gi presente. un attributo facoltativo, il cui valore predefinito impostato a false. La dimensione massima (in byte) accettabile per un file in upload. Il valore pu essere espresso come un numero seguito da "K", "M", o "G" (vale a dire, rispettivamente, kilobyte, megabyte o gigabyte). un attributo facoltativo, il cui valore predefinito pari a 250M. Il nome pienamente qualificato di classe Java per la classe multipari request-handler che deve essere usata. Si usa quando si caricano file dal filesystem locale di un utente verso il server. un attributo

locale maxFileSize

multipartClass facoltativo il cui valore predefinito la classe DiskMultipartRequestHandler del package org.apache.struts.upload. nocache
Valore booleano indicante se il framework debba impostare header HTTP nocache in ogni risposta. Questo attributo facoltativo; il valore predefinito false, va notato come la lettera "c" nella parola nocache sia minuscola anche se, per coerenza con gli altri attributi qui illustrati, essa avrebbe dovuto essere maiuscola (noCache); ma non stato cosi. Un pattern sostitutivo che definisce il modo in cui gli attributi page dei tag custom che lo usano vengono mappati agli URL relativi al contesto delle risorse corrispondenti. Tale valore pu consistere di una qualsiasi combinazione tra quelli seguenti:

pagePattern

$A Sostituito dal prefisso di applicazione della presente sottoapplicazione $P Sostituito dal valore dell'attributo page $$ Fa si che venga, mostrato il segno di valuta del dollaro $x (dove x un qualsiasi carattere non definito sopra) Viene accettato senza
conseguenze, poich riservato per usi futuri Se non specificato altrimenti, il valore predefinito di pagePattern $A$P, che consistente con il precedente comportamento di valutazione degli URL per gli attributi page, inserito nel codice.

Il nome pienamente qualificato di classe Java della classe request-processor utilizzata per elaborare le request. Il valore qui specificato dovrebbe essere una classe derivata dalla processorClass org.apache.struts.action.RequestProcessor, che rappresenta il valore predefinito. un attributo facoltativo.

tempDir

Specifica la directory temporanea di lavoro usata nell'elaborazione degli upload di file. un attributo facoltativo; il servlet container attribuir, di volta in volta, un valore predefinito per ciascuna web application.

La classe org.apache.struts.config.ControllerConfig rappresenta in memoria le informazioni configurate nell'elemento controller. Ecco come configurare l'elemento controller:

<controller contentType="text/html;charset=UTF-8" debug="3" locale="true" nocache="true" processorClass="com.oreilly.struts.framework.CustomRequestProcessor"/>

4.7.2.7 L'elemento message-resources


L'elemento message-resources specifica caratteristiche dei resourec bundle del messaggio che contengono i messaggi localizzati per una data applicazione. Ciascun file di configurazione di Struts pu definire uno o pi resource bundle di messaggi; pertanto, ciascuna sottoapplicazione pu definire i propri bundle. L'elemento messageresources contiene solamente un elemento set-property:

<!ELEMENT message-resources (set-property*)>


La Tabella 4-11 elenca gli attributi supportali dall'elemento message-resources.

Tabella 4-11 Attributi dell'elemento message-resources


Nome Descrizione La classe di implementazione del bean di configurazione che manterr le informazioni del messageresources. Se specificata, deve essere una classe derivata dalla org.apache.struts.config.MessageResourcesConfig, che la classe predefinita quando non specificato alcun valore. un attributo facoltativo. II nome pienamente qualificato di classe Java della classe MessageResourcesFactory che deve essere usata. un attributo facoltativo, il cui valore predefinito la classe PropertyMessageResources del package org.apache.struts.util. L'attributo di servlet context con il quale sar immagazzinato questo resource bundle di messaggio. Questo attributo facoltativo. Il valore predefinito specificato dalla costante di stringa Action.MESSAGES_KEY. Solamente un resource bundle pu essere quello predefinito. Valore booleano indicante come la sottoclasse MessageResources deve comportarsi quando viene utilizzata una chiave di messaggio sconosciuta. Se questo valore impostato a true, sar restituita una stringa vuota; se impostato a false, sar restituito un messaggio del tipo"???global.label.missing???". Il messaggio effettivo conterr la chiave sbagliata. un attributo facoltativo, il cui valore predefinito true.

className

factory

key

null

II nome base del resource bundle. Per esempio, se il nome del proprio resource bundle ApplicationResources.properties, si dovrebbe impostare il valore parameter a parameter ApplicationResources. un attributo obbligatorio. Se il resource bundle si trova dentro un package, occorre fornirne in questo attributo il nome pienamente qualificato. Il seguente esempio mostra come configurare elementi message-resources multipli per una singola applicazione. Da notare come per il secondo elemento si sia dovuto specificare l'attributo key, poich ne pu essere immagazzinato solo uno con la chiave predefinita:

<message-resources null="false" parameter="StorefrontMessageResources"/> <message-resources key="IMAGE_RESOURCE_KEY" null="false" parameter="StorefrontImageResources"/>

4.7.2.8 L'elemento plug-in


Il concetto di plug-in una novit di Struts 1.1. Questa potente caratteristica consente alle applicazioni Struts di scoprire dinamicamente delle risorse all'avvio. Per esempio, se necessario creare all'avvio una connessione a un sistema in remoto, e non si vuole includere tale funzionalit in maniera permanente nel codice, si pu usare un plug-in, e lapplicazione Struts lo scoprir in maniera dinamica. Per creare un plug-in, si crea una classe Java che implementa l'interfaccia org.apache.struts.action.PlugIn e si aggiunge un elemento plug-in al file di configurazione. Il meccanismo di PlugIn sar discusso in dettaglio nel Capitolo 9. L'elemento plug-in specifica un nome di classe completamente definito di un modulo di plug-in di una applicazione adatta che riceve notifica dell'avvio e della conclusione dell'applicazione. Per ciascun elemento viene creata un'istanza della classe specificata; all'avvio dell'applicazione viene invocato il metodo init() e, al termine dell'applicazione, viene invocato destroy(). La classe specificata qui deve implementare l'interfaccia org.apache.struts.action.PlugIn e i metodi init() e destroy(). L'elemento plug-in pu contenere zero o pi clementi set-property in maniera che alla nostra classe PlugIn possano essere passate ulteriori informazioni di configurazione:

<!ELEMENT plug-in (set-property*)>


Gli attributi consentiti per l'elementi plug-in sono mostrati in Tabella 4-12. Tabella 4-12. Attribuiti dell'elemento plug-in Nome Descrizione
II nome pienamente qualificato di classe Java relativo alla classe PlugIn. Deve implementare l'interfaccia className PlugIn.

Il codice seguente mostra l'uso di due elementi plug-in:

<plug-in className="com.oreilly.struts.storefront.service.StorefrontServiceFactory" /> <plug-in className="org.apache.struts.validator.ValidatorPlugIn"> <set-property property="pathnames" value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/> </plug-in> La classe ValidatorPlugIn presente nel secondo elementoplug-in mostra il modo in cui il framework Struts inizializza il Validator.Il framework Validator viene trattato nel Capitolo 11.

4.7.3 Un file struts-config.xml completo


Fin qui non stato ancora mostrato un esempio completo di un file di configurazione di Struts. L'Esempio 4-5 fornisce il codice completo.

Esempio 4-3. Versione completa di un file di configurazione di Struts <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd"> <struts-config> <data-sources> <data-source> <set-property property="autoCommit" value="true"/> <set-property property="description" value="Resin Data Source"/> <set-property property="driverClass" value="com.caucho.jdbc.mysql.Driver"/> <set-property property="maxCount" value="10"/> <set-property property="minCount" value="2"/> <set-property property="user" value="admin"/> <set-property property="password" value="admin"/> <set-property property="url" value="jdbc:mysqlcaucho://localhost:3306/storefront"/> </data-source> </data-sources> <form-beans> <form-bean name="loginForm" type="com.oreilly.struts.storefront.security.LoginForm"/> <form-bean name="itemDetailForm" type="org.apache.struts.action.DynaActionForm"> <form-property name="view" type="com.oreilly.struts.catalog.view.ItemView"/> </form-bean> </form-beans> <global-exceptions> <exception key="global.error.invalidlogin" path="/security/signin.jsp" scope="request" type="com.oreilly.struts.framework.exceptions.InvalidLoginException"/> </global-exceptions>

<global-forwards> <forward name="Login" path="/security/signin.jsp" redirect="true"/> <forward name="SystemFailure" path="/common/systemerror.jsp"/> <forward name="SessionTimeOut" path="/common/sessiontimeout.jsp" redirect="true"/> </global-forwards> <action-mappings> <action path="/viewsignin" parameter="/security/signin.jsp" type="org.apache.struts.actions.ForwardAction" scope="request" name="loginForm" validate="false" input="/index.jsp"> </action> <action path="/signin" type="com.oreilly.struts.storefront.security.LoginAction" scope="request" name="loginForm" validate="true" input="/security/signin.jsp"> <forward name="Success" path="/index.jsp" redirect="true"/> <forward name="Failure" path="/security/signin.jsp" redirect="true"/> </action> <action path="/signoff" type="com.oreilly.struts.storefront.security.LogoutAction" scope="request" validate="false" input="/security/signin.jsp"> <forward name="Success" path="/index.jsp" redirect="true"/> </action> <action path="/home" parameter="/index.jsp" type="org.apache.struts.actions.ForwardAction" scope="request" validate="false"> </action> <action path="/viewcart" parameter="/order/shoppingcart.jsp" type="org.apache.struts.actions.ForwardAction" scope="request" validate="false"> </action> <action path="/cart" type="com.oreilly.struts.storefront.order.ShoppingCartActions" scope="request" input="/order/shoppingcart.jsp" validate="false" parameter="method"> <forward name="Success" path="/action/viewcart" redirect="true"/> </action> <action path="/viewitemdetail" name="itemDetailForm" input="/index.jsp" type="com.oreilly.struts.storefront.catalog.GetItemDetailAction" scope="request" validate="false"> <forward name="Success" path="/catalog/itemdetail.jsp"/>

</action> <action path="/begincheckout" input="/order/shoppingcart.jsp" type="com.oreilly.struts.storefront.order.CheckoutAction" scope="request" validate="false"> <forward name="Success" path="/order/checkout.jsp"/> </action> <action path="/getorderhistory" input="/order/orderhistory.jsp" type="com.oreilly.struts.storefront.order.GetOrderHistoryAction" scope="request" validate="false"> <forward name="Success" path="/order/orderhistory.jsp"/> </action> </action-mappings> <controller contentType="text/html;charset=UTF-8" debug="3" locale="true" nocache="true" processorClass="com.oreilly.struts.framework.CustomRequestProcessor"/> <message-resources parameter="StorefrontMessageResources" null="false"/> <message-resources key="IMAGE_RESOURCE_KEY" parameter="StorefrontImageResources" null="false"/> <plug-in className="com.oreilly.struts.storefront.service.StorefrontServiceFactory" /> <plug-in className="org.apache.struts.validator.ValidatorPlugIn"> <set-property property="pathnames" value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/> </plug-in> </struts-config>

4.7.4 Uso dei moduli applicativi multipli


Ora che si visto come configurare l'applicazione predefinita per Struts, l'ultimo aspetto da discutere il modo in cui includere dei moduli applicativi multipli. Con Struts 1.1, c' la possibilit di impostare file di configurazione multipli. Sebbene i moduli applicativi tacciano parte della medesima web application, agiscono in maniera indipendente l'uno dall'altro. Si pu anche passare da una sottoapplicazione all'altra, se il caso. Usare i moduli applicativi multipli permette una migliore organizzazione dei componenti nell'ambito di un web application. Per esempio, si pu assemblare e configurare un modulo applicativo per tutto ci che ha a che fare con cataloghi e articoli, e organizzare contemporaneamente un altro modulo con le informazioni di configurazione per il carrello della spesa e gli ordini. Separare un'applicazione in componenti secondo questa impostazione facilita lo sviluppo parallelo. Il primo passo consiste nel creare ulteriori file di configurazione di Struts. Supponiamo di aver creato un secondo file di configurazione denominato struts-order-config.xml. Dobbiamo modificare il file web.xml per l'applicazione e aggiungere un ulteriore elemento init-param per il nuovo modulo. Tutto ci gi stato mostrato in precedenza nel capitolo, ma viene qui ripetuto per comodit del lettore. L'Esempio 4-6 mostra il mapping delle istanze della servlet vista precedentemente, con un init-param aggiunto per il secondo file di configurazione di Struts.

Esempio4-6.Parte di un file web.xml che illustra come configurare sottoapplicazioni multiple <servlet> <servlet-name>storefront</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> <init-param> <param-name>config/order</param-name> <param-value>/WEB-INF/struts-order-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
Va notato come il valore param-name del modulo applicativo non predefinito mostrato nell'Esempio 4-6 cominci con config. Gli elementi param-name di tutti i moduli applicativi "aggiunti" devono cominciare con config/; l'elemento dell'applicazione "predefinita", invece, contiene il solo valore; config. La parte che segue config/ detta prefisso del modulo applicativoed utilizzata in tutto il framework per intercettare request e restitu le risorse corrette. ire Con l'attuale versione di Struts, quando si usano moduli applicativi multipli supportato solamente il mapping delle estensioni, il mapping dei path non ancora supportato

Occorre prestare particolare attenzione agli attributi di configurazione disponibili nei vari elementi XML di Struts. Alcuni di questi, come visto nel corso del capitolo, hanno un notevole effetto sulle modalit di funzionamento dell'applicazione in un ambiente a moduli multiapplicativi.

4.7.5 Specificare un elemento DOCTYPE


Per garantire che il file di configurazione di Struts sia valido, esso pu e dovrebbe essere validato con il DTD Struts. Per fare ci, necessario includere l'elemento DOCTYPE all'inizio del file XML di configurazione di Struts:

<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
In versioni precedenti del framework, esistevano alcuni problemi con applicazioni incapaci di avviarsi se non erano in grado di collegarsi al sito Jakarta e di accedere al DTD da l Ci non si verifica pi, visto che Struts ora fornisce copie in locale dei DTD. Alcuni utenti preferiscono specificare un tag SYSTEM DOCTYPE, piuttosto che uno PUBLIC. Ci consente di specificare un path assoluto al posto di uno relativo. Sebbene ci possa risolvere problemi a breve termine, ne crea un numero maggiore a lungo termine. Non sempre possibile garantire la struttura delle directory da un ambiente a un altro e, inoltre, i diversi container agiscono in maniera diversa utilizzando un tag SYSTEM DOCTYPE: forse meglio non usarlo. In ogni caso, se si decide di farlo, dovr trattarsi di qualcosa del genere:

<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE struts-config SYSTEM "file:///c:/dtds/struts-config_1_1.dtd"> <struts-config> <!--The rest of the struts configuration file goes next -->
Come si vede, la collocazione del DTD un path assoluto. Se il path dell'ambiente in cui si va ad operare non il medesimo, sar necessario modificare il file XML. Questa la ragione per cui un tale approccio non raccomandato.

4.8 Il componente Digester


Quando viene inizializzata una applicazione Struts, una delle prime cose che deve fare leggere il file XML di configurazione ed effettuarne il parsing. Il componente Digester un progetto Jakarta separato, costituito da svariate classi che leggono i file XML e creano e inizializzano oggetti Java basandosi su tali file. Il componente Digester utilizza una serie di regole, sotto forma di classi Java, per invocare metodi di callback, i quali istanziano e popolano gli oggetti con i dati letti dal file diconfigurazione di Struts. Struts utilizza il Digester per effettuare il parsing dei file di configurazione e per creare i necessari oggetti di configurazione all'interno del package org.apache.struts.config. Le regole che il Digester utilizza per Struts sono contenute nella classe org.apache.struts.config.ConfigRuleSet. Non esistono molte ragioni per modificare la classe ConfigRuleSet a meno che non sia necessario estendere il file di configurazione: se, per la vostra applicazione, dovesse essere necessario, prima di cominciare bene consultare la documentazione del componente Digester, all'indirizzo http://jakarta.apache.org/commons/digester.html.

4.9 Lo strumento Console di Struts


Quando si sviluppa una piccola applicazione, la configurazione di Struts si gestisce piuttosto bene. Si tratta sempre di XML, e di solito non presenta troppi problemi. Alcuni sviluppatori usano degli editor XML, mentre altri impiegano normali editor di testo: entrambe le opzioni sono buone finch l'applicazione resta relativamente piccola ma, quando si comincia a lavorare su progetti Struts pi ampi, le dimensioni e la complessit del file possono cominciare a incutere un certo timore. L'applicazione Console stata creata da James Holmes per risolvere questi e altri problemi relativi alla gestione di grandi file di configurazione. basata su Swing e fornisce un'interfaccia di facile utilizzo per l'editing dei vari clementi dei file di configurazione. La Console pu essere scaricata gratuitamente dall'indirizzo http://www.jamesholmes.com/struts/console/, sebbene non si tratti di software open source. Al momento non richiesta nessuna licenza, ma sempre bene controllare sul sito eventuali cambiamenti La formattazione del file di configurazione di Struts potrebbe essere modificata quando s salva il file usando Console: questo strumento, infatti, per leggere nel file di configurazione usa un parser XML che non pu mantenere una conoscenza perfetta del modo in cui il file era stato formattato in origine. Console fornisce comunque svariate opzioni di salvataggio utilizzabili per formattare il file XML in base a diversi formati. La Figura 4-5 mostra la schermata della Console relativa alle action.

Figura4-5. Console di Struts: la schermata di configurazione delle action


L'applicazione Console di Struts supporta inoltre l'integrazione in vari IDE (Integrated Development Environments, ambienti di sviluppo integrati) Java tra i pi diffusi, quali: JBuilder (4.0 o versioni successive) NetBeans (3.2 o versioni successive) Oracle JDeveloper (9i o versioni successive) Sun Forte for Java (3.2 o versioni successive) La Figura 4-6 mostra l'aspetto di Console all'interno di JBuilder.

Figura 4-6. La Console di Struts in funzione all'interno dell'IDE JBuilder

4.10 Ricaricare i file di configurazione


Il descrittore di deploy delle web application viene caricato e sottoposto a parsing quando il web container si avvia per la prima volta: di solito si stabilisce che eventuali cambiamenti al descrittore non vengano individuati e ricaricati con il container in funzione; la funzionalit di ricaricamento "in corso", in effetti, non neanche presente in molti web container, per i possibili problemi di sicurezza che potrebbero derivarne. Anche i file di configurazione di Struts vengono caricati e sottoposti a parsing quando viene avviato il container, e anche i cambiamenti a questi file di configurazione non sono automaticamente individuati, per i soliti motivi di sicurezza. Alcune applicazioni potrebbero richiedere per la possibilit che le modifiche ai file di configurazione di Struts siano riconosciute senza che sia necessario il riavvio del web container. In questo caso, esistono diversi modi per ottenerlo. Un primo approccio sta nel creare un'azione di Struts che inizializzi di nuovo ActionServlet (magari con delle restri zioni su quali utenti possono invocare tale action). Una volta che la ActionServlet sia stata inizializzata di nuovo, tutto sar aggiornato e l'applicazione potr tornare ai suoi compiti. Una seconda strategia consiste nel creare un thread che tiene sotto controllo il lastModifiedTime del file di configurazione. A distanza di alcuni secondi tra le rilevazioni, il thread effettua una comparazione tra il lastModifiedTime del file e quello memorizzato in una variabile: se sono diversi, il file cambiato ed quindi il momento di ricaricare l'applicazione. Si tratta di una buona modalit operativa, poich non necessario preoccuparsi che sia un utente indesiderato a far ricaricare l'applicazione. Tuttavia il momento in cui avviene il reload sar totalmente a carico del thread.

CAPITOLO 5

I componenti controller di Struts

Come si visto nel Capitolo 1, i componenti controller hanno la responsabilit di individuare l'input dell'utente, aggiornare eventualmente il modello del dominio e selezionare la vista che sar presentata al client. Il controller contribuisce alla separazione tra presentazione del model e modello stesso, e ci garantisce maggiore libert di sviluppare presentazioni diversificate basato su un singolo modello del dominio. L'utilizzo di un controller fornisce un punto di controllo centralizzato dove tutte le request dei client vengono elaborate inizialmente. Centralizzare il controllo in tale maniera mette in atto due requisiti del design MVC. Innanzitutto il controller agisce come mediatore/traduttore tra l'input del client e il model, con funzionalit correnti quali controllo di sicurezza, logging o altri servizi importanti per conto di ogni request. In secondo luogo, dal momento che tutte le request sono filtrate attraverso il controller, la view viene disaccoppiata sia dalla business logic che dai componenti di altre viste. La vista restituita al client dipende interamente dal controller. Ci rende le applicazioni molto pi flessibili. Per elaborare le request in entrata, il framework Struts usa una servlet, sebbene faccia affidamento anche su molti altri componenti ausiliari che fanno parte del dominio del controller. I componenti controller di Struts sono stati gi citati brevemente nei precedenti capitoli, ma giunto il momento di analizzarli in maniera approfondita, per capire bene le varie funzioni dei diversi componenti nell'ambito controller.

5.1 Il meccanismo controller


Il pattern di progettazione J2EE Front Controller utilizza un singolo controller per reindirizzare tutte le request dei client attraverso un percorso centralizzato. Tra gli svariati vantaggi apportati da questo pattern alle funzionalit delle applicazioni c' che servizi quali sicurezza, internazionalizzazione e registrazione delle attivit in log vengono concentrati nel controller. Ci permette una applicazione consistente di queste funzioni su tutte le request. Quando il comportamento di questi servizi necessita di modifiche, i cambiamenti che potenzialmente dovrebbero influenzare tutta l'intera applicazione, dovranno essere apportati solamente a un'area relativamente piccola e isolata del programma. Come si e visto nel Capitolo 1, il controller di Struts ha svariati compiti, i pi importanti tra i quali sono: Intercettare le request del client. Effettuare un mapping di ciascuna request a una specifica operazione appartenente alla business logic. Raccogliere i risultati dalla operazione e renderli disponibili al client. Determinare la view da mostrare al client, sulla base dello stato corrente e del risultato ottenuto dall'operazione della logica di applicazione. Nel framework Struts, le responsabilit dei compiti del controller ricadono su diversi componenti. La Figura 5-1 un semplice diagramma di classe dei componenti su cui, inStruts, ricade una qualche quota di responsabilit controller.

Figura 5-1. Componenti del controller di Struts

Esistono anche altri componenti ausiliari che forniscono assistenza a quelli appena illustrati, ma per ora ci concentreremo su quelli mostrati in Figura 5-1.

5.1.1 La classe ActionServIet


La classe org.apache.struts.action.ActionServlet intercetta le request provenienti dallo strato client che devono passare attraverso laActionServlet prima di procedere nell'applicazione. Quando un'istanza della ActionServlet riceve una HttpRequest attraverso il metodo doGet() o Post( ), viene invocato il metodo process() affinch elabori la request. Il metodo process() della ActionServlet mostrato nell'Esempio 5-1.
Esempio 5-1. Il metodo process() della classe ActionServlet

protected void process(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException { RequestUtils.selectApplication( request, getServletContext( ) ); getApplicationConfig( request ).getProcessor( ).process( request, response ); }
Il metodo process() probabilmente non appare complicato, ma i metodi invocati al suo interno lo sono. Innanzitutto, viene invocato il metodo statico selectApplication() della classe org.apache.struts.util.RequestUtils al quale passata la request corrente e la ServletContext per la web application. Il compito del metodo selectApplication() di selezionare un modulo applicativo che gestisca la request corrente facendo corrispondere il path restituito da request.getServletPath() al prefisso ci ciascun modulo applicativo configurato. Se si utilizza un unico file di configurazione del framework, ci sar una sola applicazione, conosciuta come applicazione predefinita. Per rendere semplice e consistente l'elaborazione delle request all'applicazione predefinita e ai moduli applicativi, l'applicazione predefinita trattata come se fosse un altro modulo applicativo, e quindi ogni request che non contenga un suffisso di applicazione verr indirizzato e gestito dall'applicazione predefinita.

Il metodo selectApplication() immagazzina gli oggettiApplicationConfig e MessageResources opportuni nella request. Ci rende pi facile per il resto del framework sapere quale applicazione e quali componenti dovranno essere utilizzati per la request.

5.1.1.1 Estendere la classe ActionServlet


Prima della versione 1.1 di Struts, la classe ActionServlet conteneva gran parte del codice per elaborare ciascuna richiesta utente. A partire dalla versione 1.1, per gran parte di tali funzionalit stata spostata nella classe

org.apache.struts.action.RequestProcessor, di cui si parler fra breve. Questo nuovo componente del controller stato aggiunto per alleggerire la classe ActionServlet del pesante carico del controller.
Sebbene il framework consenta sembre di estendere la classe ActionServlet, non detto che da tale operazione si possano trarre gli stessi benefici che si avevano con la versione precedente, poich gran parta delle funzionalit sono state spostate nella nuova classe RequestProcessor. Se comunque si desidera continuare a utilizzare la propria versione personalizzata, si pu creare una classe che estenda la ActionServlet e configurare il framework affinch la utilizzi al posto della ActionServlet vera e propria. L'Esempio 5-2 mostra una servlet Java che estende la ActionServlet di Struts e ridefinisce il metodo init().
Esempio 5-2. possibile estendere la classe ActionServlet di Struts affinch effettui un'inizializzazione personalizzata

package com.oreilly.struts.storefront.framework; import javax.servlet.ServletException; import javax.servlet.UnavailableException; import org.apache.struts.action.ActionServlet; import com.oreilly.struts.storefront.service.IStorefrontService; import com.oreilly.struts.storefront.service.StorefrontServiceImpl; import com.oreilly.struts.storefront.framework.util.IConstants; import com.oreilly.struts.storefront.framework.exceptions.DatastoreException; /** * Extend the Struts ActionServlet to perform your own special * initialization. */ public class ExtendedActionServlet extends ActionServlet { public void init( ) throws ServletException { ) first

// Make sure to always call the super's init( super.init( );

// Initialize the persistence service try{ // Create an instance of the service interface IStorefrontService serviceImpl = new StorefrontServiceImpl(

);

// Store the service into the application scope getServletContext( ).setAttribute( IConstants.SERVICE_INTERFACE_KEY, serviceImpl ); }catch( DatastoreException ex ){ // If there's a problem initializing the service, disable the web app ex.printStackTrace( ); throw new UnavailableException( ex.getMessage( ) ); } } }
Ridefinire il metodo init() solo un esempio; possibile ridefinire ogni metodo, se necessario. Se si ridefinisce init(), prestare attenzione a invocare il metodosuper.init() affinch si verifichi l'inizializzazione predefinita. Per ora non vi preoccupate di quello che fa il codice dell'Esempio 5-2: lo scopo capire il modo in cui estendere la ActionServlet.

Per configurare il framework affinch utilizzi la nostra sottoclasse di ActionServlet invece di quella standard, sar necessario modificare il file web.xml come segue:

<servlet> <servlet-name>storefront</servlet-name> <servlet-class> com.oreilly.struts.storefront.framework.ExtendedActionServlet

</servlet-class> </servlet>

5.1.1.2 Processo di inizializzazione di Struts


In base ai parametri di inizializzazione configurati nel file web.xml, il servlet container caricher la ActionServlet o quando il container viene avviato oppure quando arriva la prima request per la servlet. In entrambi i casi, come con tutte le altre servlet Java, sar sicuramente chiamato il metodo init() che dovr terminare le sue operazioni prima che una qualsiasi request sia elaborata dalla servlet. Il framework Struts effettua tutte le inizializzazioni obbligatorie quando viene chiamato init(). Osserviamo adesso che cosa succede durante il processo di inizializzazione: capirne i dettagli render molto pi semplice il debug e l'estensione delle proprie applicazioni. Quando dal container viene invocato il metodo init() della ActionServlet di Struts, si verificano le seguenti attivit: 1. 2. Inizializzazione del message bundle interno del framework, usato per inviare messaggi di informazione, avvertimento ed errore ai file di log. Per ottenere i messaggi interni viene usato il bundle org.apache.struts.action.ActionResources . Caricamento dal file web.xml dei parametri di inizializzazione che controllano vari comportamenti della classe ActionServlet. Questi parametri comprendono config, debug, detail e convertNull. Per maggiori informazioni sul modo in cui questi e altri parametri delle servlet influenzino il comportamento di un'applicazione, si veda "Dichiarare i parametri di inizializzazione" al Capitolo 4. Caricamento e inizializzazione del nome della servlet e delle informazioni di mapping della servlet desunte dal file web.xml. Questi valori saranno usati in tutto il framework (principalmente dalle librerie di tag) per produrre destinazioni URL corrette quando vengono sottoposti forni HTML. Durante questa inizializzazione, vengono registrati anche i DTD che saranno usati dal framework nel passo successivo per validare il file di configurazione. Caricamento e inizializzazione dei dati di configurazione di Struts per l'applicazione predefinita, specificata dal parametro di inizializzazione config. Viene effettuato il parsing del file di configurazione di Struts viene creato un oggetto ApplicationConfig che sar salvato in ServletContext. L'oggetto ApplicationConfig per l'applicazione predefinita viene immagazzinato nel ServletContext con un valore chiave org.apache.struts.action.APPLICATION. Ciascuna risorsa di messaggi specificata nel file di configurazione di Struts per l'applicazione predefinita viene caricata, inizializzata e salvata nel ServletContext nella collocazione pi opportuna, in base all'attributo chiave specificato in ciascun elemento message-resources. Se nessuna chiave specificata, il message resource viene salvato al valore org.apache.struts.action.MESSAGE. Solo una risorsa di messaggi pu essere immagazzinata come predefinita, poich le chiavi devono essere univoche. Viene caricato e inizializzato ciascun data-sources dichiarato nel file di configurazione di Struts. Se non sono specificati elementi data-sources, questo passo viene sal tato Caricamento e inizializzazione di ciascun plug-in specificato nel file di configurazione di Struts: su ognuno di questi plug-in sar invocato il metodoinit(). Una volta che l'applicazione predefinita stata inizializzata in maniera appropriata, il metodo init() della servlet determiner se sia specificato un qualche modulo applicativo e, in caso affermativo, ripeter i punti da 4 a 7 per ciascuno di essi.

3.

4.

5.

6. 7. 8.

La Figura 5-2 utilizza un diagramma di sequenza per illustrare gli otto punti principali del processo di inizializzazione della classe ActionServlet.

Figura 5-2. Diagramma di sequenza per il metodo init() della ActionServlet

Ci potrebbe essere la tentazione di impostare svariate servlet controller per una singola applicazione, nel tentativo di ottenere prestazioni migliori. Ci, molto probabilmente, non apporter migliori prestazioni o scalabilit, e viene sconsigliato dagli architetti Sruts. Le servlet sono multithread e permettono a molti clicnt di essere eseguite contemporaneamente. Una singola servlet in grado di soddisfare le richieste contemporanee di molti client. In effetti, il framework Struts 1.1 d per scontalo che ci sia un solo mapping a una servlet: meglio non provare a impostare mapping multipli a pi servlet.

5.1.2 La classe RequestProcessor


Il secondo passo nell'Esempio 5-1 di invocare il metodo process() della classe org.apache.struts.action.RequestProcessor che chiamato tramite l'istanza della ActionServlet e passato agli oggetti di request e response correnti. La classe RequestProcessor stata aggiunta al framework per consentire agli sviluppatori di personalizzare in un'applicazione il comportamento di gestione delle request. Sebbene questo tipo di personalizzazione fosse possibile anche nelle precedenti versioni attraverso l'estensione della classe ActionServlet, stato necessario introdurre questa nuova classe per consentire a ciascun modulo applicativo di avere il suo gestore di request personalizzato. La classe RequestProcessor contiene molti metodi che possono essere ridefiniti se c' bisogno di modificare le funzionalit prestabilite. Come e mostrato nell'Esempio 5-1, una volta che sia stato selezionato il modulo applicativo opportuno viene invocato il metodo process() della classe RequestProcessor per gestire la request. Il comportamento del metodo process() della RequestProcessor simile a quello nelle precedenti versioni della classe ActionServIet. L'Esempio 5-3 mostra l'implementazione del metodo process() nella classe RequestProcessor.
Esempio 5-3. II metodo process() elabora tutte le request

public void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Wrap multipart requests with a special wrapper request = processMultipart(request); // Identify the path component we will use to select a mapping String path = processPath(request, response); if (path == null) { return; } if (log.isInfoEnabled( )) { log.info("Processing a '" + request.getMethod( ) + "' for path '" + path + "'"); } // Select a Locale for the current user if requested processLocale(request, response); // Set the content type and no-caching headers if requested processContent(request, response); processNoCache(request, response); // General-purpose preprocessing hook if (!processPreprocess(request, response)) { return; } // Identify the mapping for this request ActionMapping mapping = processMapping(request, response, path); if (mapping == null) { return; } // Check for any role required to perform this action if (!processRoles(request, response, mapping)) { return; } // Process any ActionForm bean related to this request ActionForm form = processActionForm(request, response, mapping); processPopulate(request, response, form, mapping); if (!processValidate(request, response, form, mapping)) { return;

} // Process a forward or include specified by this mapping if (!processForward(request, response, mapping)) { return; } if (!processInclude(request, response, mapping)) { return; } // Create or acquire the Action instance to process this request Action action = processActionCreate(request, response, mapping); if (action == null) { return; } // Call the Action instance itself ActionForward forward = processActionPerform(request, response, action, form, mapping); // Process the returned ActionForward instance processActionForward(request, response, forward); }
Come mostra l'Esempio 5-3 succedono molte cose nel metodo processo della classe RequestProcessor. Seguiamo tali eventi nel metodo passo per passo: 1. Viene invocato il metodo processMultipart(). Se il metodo HttpServletRequest un POST e il contentType della request comincia con multipart/form-data, l'oggetto request standard subisce un wrap all'interno di una sua speciale versione del framework Struts che tratta esclusivamente con request multipari. Se il metodo della request un GET o il contentType non multipart, viene restituita la request originale. A meno che l'applicazione supporti l'upload dei file, non necessario preoccuparsi delle funzionalit multipart di Struts. Viene invocato il metodo processPath() per determinare il componente del path daU'URI per la request. Tra l'altro, questa informazione impiegata per selezionare la corretta Action di Struts da invocare. Viene invocato il metodo processLocale() per determinare il locale dell'utente che effettua la request e per immagazzinare un oggetto Locale all'interno dell'oggetto HttpSession. Il locale non sempre ottenuto nella sessione dell'utente, ma dipende dall'attributo locale nell'elemento di configurazione controller. Si veda il Capitolo 4 per maggiori dettagli sugli attributi dell'elemento

2. 3.

controller.
4. Si determina il tipo di contenuto e la codifica opzionale della request attraverso l'invocazione del metodo processContent(). Il content type pu essere configurato nelle impostazioni e anche ridefinito da parte delle JSP: text/html quello predefinito. 5. Viene invocato il metodo processNoCache() per determinare se l'attributo noCache impostato a true. In caso positivo, aggiungere gli opportuni parametri dell'header nell'oggetto response per impedire che le pagine siano sottoposte a cache nel browser. I parametri dell'header comprendono Pragma, CacheControl ed Expires. 6. Successivamente viene invocato il metodo processPreprocess(). un marcatore generico di preelaborazione che, in maniera predefinita, restituisce true. Tuttavia, le sottoclassi possono ridefinire questo metodo ed utilizzare logica condizionale per decidere se continuare l'elaborazione della rcquest. Dal momento che i metodi vengono invocati prima che sia invocata una Action, si tratta di un buon momento per validare se l'utente contiene una sessione valida. Se questo metodo restituisce true, l'elaborazione della request continuer. Se restituisce false, l'elaborazione si fermer. Sta a voi scegliere, tramite opportuna programmazione, se effettuare un redirect o un forward della request: il controller dar per scontato che siate voi a gestire la request e non invier response al client. 7. Si determina la ActionMapping per la request usando le informazioni sul path chiamando il metodo processMapping(). Se non possibile trovare un mapping tramite l'informazione del path, sar restituito un errore al client. 8. Si controlla che ogni ruolo di sicurezza sia configurato per l' Action, invocando il metodo processRoles(). Se ci sono, dei ruoli configurati, viene invocato il metodo isUserInRole() sulla request. Se l'utente non contiene il ruolo necessario, l'elaborazione si concluder a questo punto e sar restituito al clicnt un opportuno messaggio di errore. 9. Si invoca il metodo processActionForm() per determinare se c' una ActionForm configurata per la ActionMapping. Se per il mapping stata configurata una ActionForm, si effettuer un tentativo di trovare un'istanza gi esistente nello scope appropriato. Una volta che la ActionForm sia stata o trovata o creata, viene salvata nello scope opportuno, utilizzando una chiave configurata nell'attributo name per l'elemento ActionForm. 10. Successivamente viene invocato il metodo processPopulate() e, se c' una ActionForm configurata per il mapping, le sue propriet vengono configurate a partire dai valori dei parametri della request. Prima che

11.

12.

13.

14.

15.

le propriet siano popolate a partire dalla request, comunque, viene invocato il metodo reset() sulla ActionForm. Viene chiamato il metodo processValidate() e, se stata configurata una ActionForm e l'attributo validate stato impostato a true per l'elemento action, viene invocato il metodo validate(). Se il metodo validate() individua degli errori, salver un oggetto ActionErrors nello scope della request, e questa verr immediatamente inviata tramite forward alla risorsa specificata nell'attributo input del mapping dell'azione. Se non sono stati individuati errori dal metodo validate() o non c' ActionForm per il mapping delle azioni, l'elaborazione della request continua. Si pu configurare l'elemento controller perch interpreti gli attributi input come forward definiti. Si veda il Capitolo 4 per maggiori informazioni su questa caratteristica. Si determina se configurato per l'action mapping un attributo forward o include. Se cos, si invoca il metodo forward() o include() sulla RequestDispatcher, a seconda di quale configurato. L'elaborazione della request termina a questo punto se uno di questi configurato, altrimenti si continua l'elaborazione della request. Si invoca il metodo processActionCreate() per creare o acquisire un'istanza di Action per elaborare la request. Sar controllata una cache di Action per vedere se l'istanza di Action sia gi stata creata, nel qual caso essa verr utilizzata per elaborare la request. Altrimenti, verr creata una nuova istanza che sar immagazzinata nella cache. Si invoca il metodo processActionPerform(), il quale, a sua volta, invoca il metodo execute() sull'istanza di Action. La chiamata di execute() viene inglobata in un blocco try/catch in maniera che le eccezioni possano essere gestite dalla RequestProcessor. Si invoca il metodo processActionForward() e lo si passa all'oggetto ActionForward restituito dal metodo execute(). Il metodo processActionForward() determina se debba verificarsi un redirect o un forward controllando l'oggetto ActionForward, il quale a sua volta dipende dall'attributo redirect nell'elemento forward.

5.1.2.1 Estendere la classe RequestProcessor


facile creare la propria classe RequestProcessor personalizzata. Analizziamo un esempio sul come e sul perch ci possa essere fatto. Supponiamo che la nostra applicazione debba consentire all'utente di cambiare il suo locale in un qualsiasi momento durante la sessione. Il comportamento predefinito del metodo processLocale() di RequestProcessor di impostare il Locale dell'utente solo se non gi stato immagazzinato nella sessione, il che tipicamente avviene durante la prima request. Il comportamento predefinito del framework Struts e di non immagazzinare il Locale nella sessione utente. Ci pu essere ridefinito facilmente usando l'attributo locale dell'elemento controller.

L'Esempio 5-4mostra una classe RequestProcessor personalizzata che controlla la richiesta per il locale ogni volta e aggiorna la sessione dell'utente se cambiata rispetto a prima. Ci consente all'utente di cambiare la preferenza per un dato locale in qualsiasi momento durante le operazioni dell'applicazione.
Esempio 5-4. La classe RequestProcessor personalizzata che ridefinisce l'elaborazione predefinita del Locale

package com.oreilly.struts.framework; import import import import javax.servlet.http.*; java.util.Locale; org.apache.struts.action.Action; org.apache.struts.action.RequestProcessor;

/** * A customized RequestProcessor that checks the user's preferred locale * from the request each time. If a Locale is not in the session or * the one in the session doesn't match the request, the Locale in the * request is set in the session. */ public class CustomRequestProcessor extends RequestProcessor { protected void processLocale(HttpServletRequest request, HttpServletResponse response) { // Are we conFigurad to select the Locale automatically?

if (!appConfig.getControllerConfig().getLocale( )){ // The locale is conFigurad not to be stored, so just return return; } // Get the Locale (if any) that is stored in the user's session HttpSession session = request.getSession( ); Locale sessionLocale = (Locale)session.getAttribute(Action.LOCALE_KEY); // Get the user's preferred locale from the request Locale requestLocale = request.getLocale( ); // If the Locale was never added to the session or it has changed, set it if (sessionLocale == null || (sessionLocale != requestLocale) ){ if (log.isDebugEnabled( )) { log.debug(" Setting user locale '" + requestLocale + "'"); } // Set the new Locale into the user's session session.setAttribute( Action.LOCALE_KEY, requestLocale ); } } }
Per configurare la CustomizedRequestProcessor per la nostra applicazione, sar necessario aggiungere un elemento controller al lile di configurazione di Struts e includere l'attributo processorClass come mostrato qui:

<controller contentType="text/html;charset=UTF-8" debug="3" locale="true" nocache="true" processorClass="com.oreilly.struts.framework.CustomRequestProcessor"/>


necessario specificare il nome di classe completo della CustomizedRequestProcessor, come mostrato in questo frammento. Sebbene non tutte le applicazioni presentino reali motivi per la creazione di una classe personalizzata per l'elaborazione delle request, averne una a disposizione pu rappresentare un "segnaposto" per future personalizzazioni. pertanto una buona idea crearne una per la propria applicazione e specificarla nel file di configurazione. Non deve necessariamente ridefinire nulla quanto la si crea: tali modifiche possono essere aggiunte quando si manifester l'esigenza. Per ulteriori informazioni sull'elemento controller si veda "Il DTD di configurazione di Struts" nel Capitolo 4.

5.1.3 La classe Action


La classe org.apache.struts.action.Action il cuore del framework. il ponte tra la request di un client e l'operazione appartenente alla business logic. Ciascuna classe Action in genere progettata per eseguire una singola operazione della logica applicativa per conto di un clicnt. Quando si parla di singola operazione della business logic, non si intende dire che la classe Action pu effettuare un solo compito, quanto piuttosto che il task da essa effettuato dovrebbe essere concentrato e incentrato su una singola unit funzionale. In altre parole, i compiti svolti da Action dovrebbero essere legati a una sola operazione della business logic. Per esempio, non si dovrebbe creare una Action che esegue al contempo funzioni di carrello della spesa e gestione di login e logout. Queste aree dell'applicazione non sono strettamente collegate e non dovrebbero quindi essere associate. Pi avanti nel capitolo, si presenter la DispatchAction fornita dal framework che supporta operazioni multiple in una singola classe, ma anche queste operazioni multiple dovrebbero essere collegate tra loro e gestire una comune unir funzionale dell'applicazione.

Determinata la corretta istanza di Action, viene invocato il metodo processActionPerform(). Il metodo processActionPerform() della classe RequestProcessor mostrato nell'Esempio 5-5 .
Esempio 5-5. Il metodo processActionPerform()

protected ActionForward processActionPerform(HttpServletRequest request, HttpServletResponse response, Action action, ActionForm form, ActionMapping mapping) throws IOException, ServletException { try { return (action.execute(mapping, form, request, response)); }catch (Exception e){ return (processException(request, response, e, form, mapping)); } }
Il metodo processActionPerform() ha la responsabilit di invocare il metodo execute() sull'istanza di Action. Nelle precedenti versioni di Struts, la classe Action conteneva solamente un metodo perform(), che stato deprecato in favore di execute() di Struts 1.1. Questo nuovo metodo si rende necessario poich il metodo perform() dichiara di lanciare solamente eccezioni IOExceptions e ServletExceptions. A causa della nuova funzione di gestione dichiarativa delle eccezioni, il framework ha bisogno di ricevere tutte le istanze di java.lang.Exception dalla classe Action. Invece di modificare la firma del metodo perform( ) a scapito della retrocompatibilit, stato aggiunto il metodo execute(). Il metodo execute() invoca il metodo perform(), ma in futuro il metodo perform() sar definitivamente abbandonato. bene usare il metodo execute() invece di perform() in tutte le classi Action. Analizzando il codice sorgente della classe Action, si nota che ci sono due differenti versioni dei metodi execute() e perform(). Una versione prende request e response non HTTP, mentre la seconda contiene le versioni HTTP. Generalmente sar necessaria solo la versione HTTP, a meno che non si utilizzi una servlet non HTTP. Per ora, le versioni non HTTP si limitano a tentare il cast degli oggetti request e response verso le loro controparti HTTP e invocano le versioni HTTP dei rispettivi metodi. Sar necessario estendere la classe Action e fornire un'implementazione del metodo execute(). L'Esempio 5-6 mostra la LoginAction dell'applicazione Storefront.
Esempio 5-6. La classe LoginAction della applicazione Storefront

package com.oreilly.struts.storefront.security; import import import import import import import import import java.util.Locale; javax.servlet.http.*; org.apache.struts.action.*; com.oreilly.struts.storefront.customer.view.UserView; com.oreilly.struts.storefront.framework.exceptions.BaseException; com.oreilly.struts.storefront.framework.UserContainer; com.oreilly.struts.storefront.framework.StorefrontBaseAction; com.oreilly.struts.storefront.framework.util.IConstants; com.oreilly.struts.storefront.service.IStorefrontService;

/** * Implements the logic to authenticate a user for the Storefront application. */ public class LoginAction extends StorefrontBaseAction { /** * Called by the controller when the user attempts to log in to the * Storefront application. */ public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response ) throws Exception{ // The email and password should have already been validated by the ActionForm String email = ((LoginForm)form).getEmail( );

String password = ((LoginForm)form).getPassword(

);

// Log in through the security service IStorefrontService serviceImpl = getStorefrontService( ); UserView userView = serviceImpl.authenticate(email, password); // Create a single container object to store user data UserContainer existingContainer = null; HttpSession session = request.getSession(false); if ( session != null ){ existingContainer = getUserContainer(request); session.invalidate( ); }else{ existingContainer = new UserContainer( ); } // Create a new session for the user session = request.getSession(true); // Store the UserView in the container and store the container in the session existingContainer.setUserView(userView); session.setAttribute(IConstants.USER_CONTAINER_KEY, existingContainer); // Return a Success forward return mapping.findForward(IConstants.SUCCESS_KEY); } }
Quando viene invocato il metodo execute() nella LoginAction, i valori di e-mail e password vengono recuperati e passati al metodo authenticate(). Se non viene lanciata alcuna eccezione dall'operazione authenticate(), viene creata una HttpSession e un JavaBean contenente informazioni utente viene immagazzinato nella sessione dell'utente. Un bug comune che a volte gli sviluppatori Struts con poca esperienza introducono nelle loro applicazioni di non implementare in maniera corretta il metodo execute(). Se si scrive male o non si implementa esattamente la firma, il metodo non sar mai invocato. Purtroppo non si ricever un errore del compilatore e neanche un errore a runtime che spieghino questo problema, poich la classe Action di Struts, che tutte le classi action devono estendere, ha un metodo execute() predefinito che restituisce null.

La classe UserView contiene semplici propriet quali firstName e lastName che possono essere utilizzate dalla presentazione. A questo tipo di JavaBeans di presentazione si fa comunemente riferimento come value object (VO), ma andrebbero chiamati, in maniera pi formale, data transfer object (DTO) poich si tratta di oggetti utilizzati per trasferire dati da uno strato a un altro. Nella classe UserView mostrata nell'Esempio 5-7i dati vengono trasferiti dal servizio di , sicurezza allo strato di presentazione.
Esempio 5-7. Il DTO della classe UserView

package com.oreilly.struts.storefront.customer.view; import com.oreilly.struts.storefront.framework.view.BaseView; /** * MuTabella data representing a user of the system. */ public class UserView extends BaseView { private String lastName; private String firstName; private String emailAddress; private String creditStatus; public UserView( super( ); } ){

public String getFirstName( return firstName; }

) {

public void setFirstName(String firstName) { this.firstName = firstName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getLastName( return lastName; } ) {

public String getEmailAddress( return emailAddress; }

) {

public void setEmailAddress(String emailAddress) { this.emailAddress = emailAddress; } public void setCreditStatus(String creditStatus) { this.creditStatus = creditStatus; } public String getCreditStatus( ) { return creditStatus; } }
Si parla diffusamente dei DTO nel Capitolo 6. L'Esempio 5-6 usa anche una classe denominata UserContainer, della quale non si danno grandi spiegazioni, quindi non preoccupatevi se vi sfugge qualche cosa. La classe UserContainer un wrapper che ingloba qualsiasi dato che potrebbe normalmente essere inserito direttamente nella sessione utente. Usare tale oggetto, immagazzinando tutto dentro di esso, rende molto pi semplice il recupero e la ripulitura dei dati. buona idea usare un container tipo questo anche per le proprie applicazioni.

5.1.3.1 La cache della classe Action


Dal momento che previsto che istanze di Action siano a prova di thread, per un'applicazione creata solo una singola istanza di ciascuna classe Action. Tutte le request del client condividono la stessa istanza e sono allo stesso tempo in grado di invocare il metodo execute(). La classe RequestProcessor contiene una HashMap, le cui chiavi sono rappresentate dai nomi di tutte le classi Action che sono specificate nel file di configurazione; il valore di ciascuna chiave la singola istanza di tale Action. Durante l'esecuzione del metodo processActionCreate() della classe RequestProcessor, il framework controlla HashMap per vedere se sia gi stata creata un'istanza, nel qual caso l'istanza viene restituita. In caso negativo, invece, viene creata una nuova istanza della classe Action, che salvata nella HashMap e poi viene restituita. La parte di codice che crea una nuova istanza di Action viene sincronizzata per garantire che un solo thread crei un'istanza. Una volta che un thread ha creato un'istanza e l'ha inserita in HashMap, tutti i successivi thread useranno quell'istanza prelevandola dalla cache.

5.1.3.2 La classe ActionForward


Come si visto nella discussione relativa alla Action, il metodo execute() restituisce un oggetto ActionForward. La classe ActionForward rappresenta un'astrazione logica di una risorsa web, tipicamente una pagina JSP o una servlet Java.

ActionForward un wrapper che ingloba la risorsa, in maniera che ci sia minore accoppia mento tra applicazione e risorsa fisica. Quest'ultima specificata solo nel file di configurazione (come attributi name, path, e redirect dell'elemento forward), e non nel codice stesso. La RequestDispatcher pu effettuare o un forward o un redirect per una ActionForward, a seconda del valore dell'attributo redirect.
Per restituire una ActionForward a partire da Action, se ne pu creare una dinamicamente nella classe Action oppure, come accade di solito, possibile utilizzare l'action mapping per individuarne una che sia gi stata preconfigurata nel file di configurazione. Il frammento di codice illustra il modo in cui possibile usare il mapping delle azioni per individuare una ActionForward basandosi sul suo nome logico:

return mapping.findForward( "Success" );


Qui, un argomento di " Success " viene passato al metodo findForward() di un'istanza di ActionMapping. L'argomento del metodo findFoward( ) deve corrispondere o a uno dei nomi specificati nella sezione global-forwards o a uno specifico dell'action a partire dalla quale viene chiamato. La seguente porzione di codice mostra elementi forward definiti per l'action mapping /signin:

<action input="/security/signin.jsp" name="loginForm" path="/signin" scope="request" type="com.oreilly.struts.storefront.security.LoginAction" validate="true"> <forward name="Success" path="/index.jsp" redirect="true"/> <forward name="Failure" path="/security/signin.jsp" redirect="true"/> </action>
Il metodo findForward() della classe ActionMapping innanzitutto effettua una chiamata al metodo findForwardConfig() per vedere se a livello di azione sia specificato un elemento forward con il nome corrispondente. In caso negativo, viene controllata la sezione global-forwards. Quando viene reperita una ActionForward che corrisponde, essa viene restituita alla classe RequestProcessor da parte del metodo execute(). Ecco il metodofindForward() della classe ActionMapping:

public ActionForward findForward(String name) { ForwardConfig config = findForwardConfig(name); if (config == null) { config = getApplicationConfig( ).findForwardConfig(name); } return ((ActionForward) config); }
Se il metodo findForward() non trova un forward che corrisponde all'argomento name, non creer grossi problemi: sar restituito un null e si ricever una pagina vuota, poich non ci sar output da scrivere alla response.

5.1.4 Creare classi Action Multithreaded


Viene creata una singola istanza di Action per ciascuna classe Action presente nel framework. Ogni request del client condivider la stessa istanza, proprio come accade con un istanza di ActionServlet. Pertanto, proprio come accade con le servlet, occorre garan che le classi Action funzionino correttamente in un ambiente multithread. tire Affinch siano thread-safe, importante che le classiAction non usino variabili di istanza per mantenere lo stato specifico di un client. Eventualmente si possono usare variabili di istanza per mantenere informazioni sullo stato, solo che non dovrebbero essere specifiche di un client o di una request. Per esernpio, si pu creare una variabile di istanza del tipo org.apache.commons.logging.Log per mantenere collegamento a un logger, come fa la classe

RequestProcessor. L'istanza del log pu essere utilizzata da tutte le request, perch il logger thread-safe e non
mantiene lo stato di client o request particolari. Per lo stato specifico di un client, ip ogni caso, si dovrebbero dichiarare le variabili all'interno del metodo execute(). Qudste variabili locali sono allocate in uno spazio di memoria diverso rispetto a quello delle variabili d'istanza. Ciascun thread che entra nel metodo execute() ha la sua pila per le Variabili locali, quindi non c' possibilit di sovra scrivere lo stato di altri thread.

5.1.5 Business logic e classe Action


Alcuni sviluppatori non hanno ben chiara quale sia la logica appartenente alla classe Action. La classe Action non rappresenta il luogo ideale in cui collocare la logica operativa della nostra applicazione. Se si torna a guardare la Figura 3-6, si pu notare che la classe Action fa sempre parte del controller; solo stata separata dallaActionServlet e dalla RequestProcessor per comodit. La logica business appartiene al dominio del model. Componenti che implementano tale logica potrebbero essere EJB, oggetti CORBA o anche service basati su di un data-source e un pool di connessione. Il punto importante che il dominio del business dovrebbe essere all'oscuro del tipo di strato di presentazione che sta effettuando l'accesso. Ci consente ai componenti del model di poter essere riusati in maniera pi semplice, da altre applicazioni. L'Esempio 5-8illustra la GetItemDetailAction dell'applicazione Storefront, che richiede al model di recuperare informazioni dettagliate relativo a un articolo nel catalogo.
Esempio 5-8. La classe Action dovrebbe delegare la business logic ad un componente del model

package com.oreilly.struts.storefront.catalog; import import import import import import import import javax.servlet.http.*; org.apache.struts.action.*; com.oreilly.struts.storefront.framework.exceptions.BaseException; com.oreilly.struts.storefront.framework.UserContainer; com.oreilly.struts.storefront.framework.StorefrontBaseAction; com.oreilly.struts.storefront.catalog.view.ItemDetailView; com.oreilly.struts.storefront.framework.util.IConstants; com.oreilly.struts.storefront.service.IStorefrontService;

/** * An action that gets an ItemView based on an id parameter in the request and * then inserts the item into an ActionForm and forwards to whatever * path is defined as Success for this action mapping. */ public class GetItemDetailAction extends StorefrontBaseAction { public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response ) throws Exception { // Get the primary key of the item from the request String itemId = request.getParameter( IConstants.ID_KEY ); // Call the storefront service and ask it for an ItemView for the item IStorefrontService serviceImpl = getStorefrontService( ); ItemDetailView itemDetailView = serviceImpl.getItemDetailView( itemId ); // Set the returned ItemView into the Dynamic Action Form // The parameter name 'view' is what is defined in the struts-config ((DynaActionForm)form).set("view", itemDetailView); // Return the ActionForward that is defined for the success condition return mapping.findForward( IConstants.SUCCESS_KEY ); } }
La classe GetItemDetailAction dell'Esempio 5-8 delega al servizio di Storefront l'effettivo recupero di informazioni sull'articolo. Si tratta si un buon approccio poich la Action non conosce i caratteri intrinseci del servizio

di Storefront o del metodo getItemDetailView(). Pu trattarsi di un oggetto locale che effettua chiamate JDBC, di un session bean che effettua una chiamata remota a un'application server, o di qualche altra implementazione. Se l'implementazione del model cambia (e succeder quando si parler degli EJB nel Capitolo 13), la classe Action sar protetta da tali modifiche. Dato che il servizio di Storefront non a conoscenza del tipo di client che lo usa, pu essere utilizzato anche da client diversi da Struts. Il disaccoppiamento delle classi Action dagli oggetti business verr approfondito nel capitolo successivo.

5.1.6 Usare le Action gi pronte


Struts comprende cinque classi Action pronte per l'uso che possibile integrare facilmente nelle proprie applicazioni, risparmiando molto tempo di sviluppo. Alcune di queste sono pi utili di altre, ma tutte meritano attenzione. Tali classi sono contenute nel package org.apache.struts.actions

5.1.6.1 La classe org.apache.struts.actions.ForwardAction


Ci sono molte situazioni in cui c' semplicemente la necessit di effettuare un forward da una pagina JSP a un'altra, senza che in realt ci sia bisogno di passare per una classe Action. Tuttavia, chiamare direttamente una JSP qualcosa da evitare per svariate ragioni. Il controller responsabile di scegliere il corretto modulo applicativo che gestisca la request e salvi in essa ApplicationConfig e MessageResources di tale modulo applicativo. Se questo passaggio viene saltato, funzionalit quali la scelta dei messaggi corretti per il resource bundle potrebbero non funzionare in maniera adeguata. Un'altra ragione che depone contro la chiamata diretta di una JSP che ci viola le responsabilit dei vari componenti del MVC. stabilito che il controller elabori tutte le request e selezioni una view per il client. Se permettiamo alla nostra applicazione di chiamare direttamente la pagina, il controller non sarebbe in grado di portare a termine i compiti per esso stabiliti nell'architettura MVC. Per risolvere questi problemi e per impedire di dover creare appositamente una classe Action che esegua solamente un semplice forward, si pu usare la ForwardAction compresa in Struts. Questa Action esegue semplicemente un forward verso un URI configurato nell'attributo parameter. Nel file di configurazione di Struts, si specifica un elemento action usando ForwardAction come attributo type:

<action input="/index.jsp" name="loginForm" path="/viewsignin" parameter="/security/signin.jsp" scope="request" type="org.apache.struts.actions.ForwardAction" validate="false"/> </action>


Quando viene selezionata l'action /viewsignin, il metodo perform() della classe ForwardAction riceve una chiamata. Quando si usa la classe ForwardAction in un elemento action, viene usato l'attributo parameter (invece di un elemento forward vero e proprio) per specificare verso dove effettuare il forward. A parte questa differenza, si chiama la ForwardAction nel medesimo modo che qualunque altra Action. La classe ForwardAction si rivela utile quando necessario integrare l'applicazione Struts con altre servlet o pagine JSP mantenendo i vantaggi delle funzionalit del controller. La classe ForwardAction una tra le pi importanti tra le Action pronte per l'uso incluse nel framework.

5.1.6.2 La classe org.apache.struts.actions.IncludeAction


La classe IncludeAction simile per certi versi alla ForwardAction. stata originariamente creata per rendere pi facile l'integrazione di preesistenti componenti basati su servlet con web application basate su Struts. Se la nostra applicazione usa il metodo include() di una RequestDispatcher, si pu implementare lo stesso comportamento usando IncludeAction. Si specifica la classe IncludeAction in un elemento action, nello stesso modo in cui si agisce per ForwardAction, ma specificando IncludeAction nell'attributo type:

<action

input="/subscription.jsp" name="subscriptionForm" path="/saveSubscription" parameter="/path/to/processing/servlet" scope="request" type="org.apache.struts.actions.IncludeAction"/>


Si deve includere l'attributo parameter e specificare un path verso la servlet che si intende includere.

5.1.6.3 La classe org.apache.struts.actions.DispatchAction


Lo scopo della classe DispatchAction di consentire che risiedano su una singola classe operazioni multiple che normalmente sarebbero suddivise attraverso svariate classi Action. Il principio alla base che ci possono essere funzionalit correlate per un servizio e, invece di suddividerle per molteplici classi Action, si possono tenere insieme nella medesima classe. Per esempio, un'applicazione che contiene il tipico servizio di carrello della spesa ha di solito bisogno delle possibilit di aggiungere articoli al carrello, vedere gli articoli e aggiornare gli articoli e le quantit presenti nel carrello. possibile, in questo caso, seguire l'approccio che prevede la creazione di tre distinte classi Action (p.e. AddItemAction, ViewShoppingCartAction, e UpdateShoppingCartAction). Sebbene si tratti di una soluzione valida, tutte e tre le classi Action dovrebbero probabilmente effettuare delle azioni molto simili prima di portare a termine le operazioni specifiche ad esse assegnate. Combinando le operazioni, si render pi semplice la manutenzione dell'applicazione: se si cambia l'implementazione corrente del carrello con una nuova, tutto il codice sar raccolto in una sola classe. Per usare la classe DispatchAction, si crea una classe che la estende e si aggiunge un metodo per ogni funzione necessaria per effettuare il servizio. La nostra classe non dovrebbe contenere il tipico metodo execute(), come invece succede alle altre classi Action. Il metodo execute() implementato dalla classe astratta DispatchAction. Occorre includere nella DispatchAction un metodo per ciascuna Action che si desidera invocare per questa DispatchAction. L' Esempio 5-9 aiuta a illustrare questo procedimento, ma va notato un aspetto importante: invece di estendere la DispatchAction di Struts, in realt qui se ne estende una versione Storefront denominata StorefrontDispatchAction. Ci stato fatto allo scopo di consentire che il comportamento di tipo utility esista come superclasse, senza modificare la versione Struts: pratica abbastanza comune.
Esempio 5-9. Le funzionalit di carrello della spesa in una singola classe DispatchAction package

package com.oreilly.struts.storefront.order; import java.io.IOException; import java.text.Format; import java.text.NumberFormat; import java.util.*; import javax.servlet.ServletException; import javax.servlet.http.*; import org.apache.struts.action.*; import org.apache.struts.actions.DispatchAction; import com.oreilly.struts.storefront.service.IStorefrontService; import com.oreilly.struts.storefront.catalog.view.ItemDetailView; import com.oreilly.struts.storefront.framework.UserContainer; import com.oreilly.struts.storefront.framework.util.IConstants; import com.oreilly.struts.storefront.framework.ShoppingCartItem; import com.oreilly.struts.storefront.framework.ShoppingCart; import com.oreilly.struts.storefront.framework.StorefrontDispatchAction; /** * Implements all of the functionality for the shopping cart. */ public class ShoppingCartActions extends StorefrontDispatchAction { /** * This method just forwards to the success state, which should represent * the shoppingcart.jsp page. */ public ActionForward view(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)

throws Exception { // Call to ensure that the user container has been created UserContainer userContainer = getUserContainer(request); return mapping.findForward(IConstants.SUCCESS_KEY); } /** * This method updates the items and quantities for the shopping cart from the * request. */ public ActionForward update(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { updateItems(request); updateQuantities(request); return mapping.findForward(IConstants.SUCCESS_KEY); } /** * This method adds an item to the shopping cart based on the id and qty * parameters from the request. */ public ActionForward addItem(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { UserContainer userContainer = getUserContainer(request); // Get the id for the product to be added String itemId = request.getParameter( IConstants.ID_KEY ); String qtyParameter = request.getParameter( IConstants.QTY_KEY ); int quantity; if(qtyParameter != null) { Locale userLocale = userContainer.getLocale( ); Format nbrFormat = NumberFormat.getNumberInstance(userLocale); try { Object obj = nbrFormat.parseObject(qtyParameter); quantity = ((Number)obj).intValue( ); } catch(Exception ex) { // Set the default quantity quantity = 1; } } // Call the Storefront service and ask it for an ItemView for the item IStorefrontService serviceImpl = getStorefrontService( ); ItemDetailView itemDetailView = serviceImpl.getItemDetailView( itemId ); // Add the item to the cart and return userContainer.getCart( ).addItem( new ShoppingCartItem(itemDetailView, quantity)); return mapping.findForward(IConstants.SUCCESS_KEY); } /** * Update the items in the shopping cart. Currently, only deletes occur * during this operation. */

private void updateItems(HttpServletRequest request) { // Multiple checkboxes with the name "deleteCartItem" are on the // form. The ones that were checked are passed in the request. String[] deleteIds = request.getParameterValues("deleteCartItem"); // Build a list of item ids to delete if(deleteIds != null && deleteIds.length > 0) { int size = deleteIds.length; List itemIds = new ArrayList( ); for(int i = 0;i < size;i++) { itemIds.add(deleteIds[i]); } // Get the ShoppingCart from the UserContainer and delete the items UserContainer userContainer = getUserContainer(request); userContainer.getCart( ).removeItems(itemIds); } } /** * Update the quantities for the items in the shopping cart. */ private void updateQuantities(HttpServletRequest request) { Enumeration enum = request.getParameterNames( ); // Iterate through the parameters and look for ones that begin with // "qty_". The qty fields in the page were all named "qty_" + itemId. // Strip off the id of each item and the corresponding qty value. while(enum.hasMoreElements( )) { String paramName = (String)enum.nextElement( ); if(paramName.startsWith("qty_")) { String id = paramName.substring(4, paramName.length( )); String qtyStr = request.getParameter(paramName); if(id != null && qtyStr != null) { ShoppingCart cart = getUserContainer(request).getCart( ); cart.updateQuantity(id, Integer.parseInt(qtyStr)); } } } } }
La classe com.oreilly.struts.storefront.order.ShoppingCartActions contiene i metodi addItem(), update(), e view(). Ciascuno di questi metodi, normalmente, dovrebbe essere inserito in una classe Action separata. Con DispatchAction, possono stare insieme nella stessa classe. Ci sono altri due metodi niella classe ShoppingCartActions di cui non si parlato: updateItems() e updateQuantities(). Si tratta di metodi privati di utilit usati dagli altri metodi di azione all'interno della classe. Non vengono invocati al di fuori di questa classe Action, come si evince dal fatto che non hanno la firma del metodo richiesta. Per utilizzare la classe specializzata DispatchAction, necessario configurare ciascun elemento action che la usa in maniera leggermente diversa rispetto agli altri mapping. L'Esempio 5-10mostra come sia dichiarata nel file di configurazione la classe ShoppingCartActions dell'Esempio 5-9 .
Esempio 5-10 Usando una sottoclasse di DispatchAction occorre specificare l'attributo parameter

<action path="/cart" input="/order/shoppingcart.jsp" parameter="method" scope="request" type="com.oreilly.struts.storefront.order.ShoppingCartActions" validate="false"> <forward name="Success" path="/order/shoppingcart.jsp" redirect="true"/> </action>

L'action mapping /cart mostrato nell'Esempio 5-10 specifica l'attributo parameter e imposta il valore alla stringa letterale "method". Il valore specificato qui diventa importante per l'istanza diDispatchAction quando invocata da un client. La DispatchAction usa questo attributo per determinare quale metodo deve essere invocato nella DispatchAction specializzata. Non ci si limita a chiamare il mapping di azione /cart, ma viene passato un parametro della request; la chiave il valore specificato per l'attributo parameter dal mapping. Il valore di questo parametro di request deve essere il nome del metodo da invocare. Per invocare il metodo addItem() sull'applicazione Storetront, si potrebbe effettuare la chiamata nel modo seguente:

http://localhost:8080/storefront/action/cart?method=addItem&id=2
Il parametro della request denominato method ha un valore di "addltem". Questo viene usato dalla DispatchAction per determinare quale metodo debba essere invocato. Nella sottoclasse DispatchAction deve esserci un metodo che corrisponde al valore del parametro. Il nome del metodo deve corrispondere in maniera esatta, e il metodo deve includere i parametri che di solito si trovano nel metodo execute(). Nella seguente porzione di codice viene illustrata la firma del metodo additem() dell'Esempio 5-9:

public ActionForward addItem( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response ) throws Exception; DispatchAction utilizza la reflection per individuare un metodo che corrisponda perfettamente al nome contenuto
nel valore del parametro della request e che contenga lo stesso numero e lo stesso tipo di argomenti. Una volta trovato, il metodo sar invocato e l'oggetto ActionForward sar restituito, proprio come con ogni altra classe Action. Sebbene la DispatchAction utilizzi effettivamente la reflection Java per invocare il metodo corretto, le prestazioni delle API di reflection da Java 1.3 in poi sono talmente migliorate che non si creano problemi in casi come questi. Dal momento che viene usata la reflection, per, il metodo deve essere dichiarato pubblico, o il metodo perform() nella classe astratta DispatchAction non sar in grado di invocarlo. Si possono sempre dichiarare altri metodi privati o pubblici, ma quelli che devono essere invocati dalla DispatchAction devono essere dichiarati pubblici.

5.1.6.4 La classe org.apache.struts.actions.LookupDispatchAction


LookupDispatchAction, come facile intuire, una sottoclasse della DispatchAction. A un'analisi non troppo approfondita, essa svolge compiti simili a quelli di DispatchAction.
Come DispatchAction, la classe LookupDispatchAction permette di specificare una classe con metodi multipli, dove uno dei metodi invocato sulla base del valore di uno speciale parametro di request indicato nel file di configurazione. E qui finiscono le similiarit. Mentre la DispatchAction usa il valore del parametro della request per determinare quale metodo deve essere invocato, la LookupDispatchAction usa il valore del parametro della

request per effettuare un reverse lookup dal resource bundle usando il valore del parametro e per farlo corrispondere a un metodo nella classe. Un esempio aiuter nella comprensione: creiamo innanzitutto una classe che estende LookupDispatchAction e implementa il metodo getKeyMethodMap(). Questo metodo restitui ce un oggetto di tipo java.util.Map s contenente una serie di coppie chiave/valore. Le chiavi di questo Map dovrebbero corrispondere a quelle del resource bundle. Il valore associato a ciascuna chiave in Map dovrebbe essere il nome del metodo della sottoclasse LookupDispatchAction. Questo valore sar invocato quando incluso un parametro della request corrispondente al messaggio del resource bundle per la chiave. Il codice seguente mostra un esempio del metodo ProcessCheckoutAction nell'applicazione Storefront: protected Map getKeyMethodMap( ) { Map map = new HashMap( ); map.put("button.checkout", "checkout" ); map.put("button.saveorder", "saveorder" ); return map; } getKeyMethodMap() per la classe

Per gli scopi di questa trattazione, supponiamo di avere le seguenti risorse nel message resource bundle:

button.checkout=Checkout button.saveorder=Save Order


e di aver specificato il seguente elemento action nel file di configurazione di Struts:

<action path="/processcheckout" input="/checkout.jsp" name="checkoutForm" parameter="action" scope="request" type="com.oreilly.struts.storefront.order.ProcessCheckoutAction"> <forward name="Success" path="/order/ordercomplete.jsp"/> </action>
Poi creiamo una JSP che esegue un POST utilizzando l'azione processcheckout. Con l'header della request sar inviato un parametro di URL action="Checkout". L'Esempio 5-11 mostra la JSP che effettua la chiamata alla action processcheckout. Esempio 5-11 II file checkout.jsp che chiama la ProcessCheckoutAction

<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <html:html> <head> <title>Virtual Shopping with Struts</title> <html:base/> <script language=javascript src="include/scripts.js"></script> <link rel="stylesheet" href="../stylesheets/format_win_nav_main.css" type="text/css"> </head> <body topmargin="0" leftmargin="0" bgcolor="#FFFFFF"> <!-- Header Page Information --> <%@ include file="../include/head.inc"%> <!-- Nav Bar --> <%@ include file="../include/menubar.inc"%> <br> Display order summary and take credit card information here <html:form action="/processcheckout"> <html:submit property="action"> <bean:message key="button.checkout"/> </html:submit> </html:form> <br><br> <%@ include file="../include/copyright.inc"%> </body> </html:html>
La chiave per comprendere il funzionamento di tutto ci che il bottone di invio dell'esempio Esempio 5-11 avr un nome "action" e il suo valore sar quello restituito dal tag <bean:message>. Ci appare ancora pi evidente quando si vede il sorgente HTML generato da questa pagina JSP. La seguente porzione di codice mostra il sorgente generato all'interno del tag <html:form>:

<form

name="checkoutForm" method="POST" action="/storefront/action/processcheckout"> <input type="submit" name="action" value="Checkout" alt="Checkout"> </form>


Si pu notare come nel codice HTML dopo il POST di checkoutForm, sar incluso il parametro URL action="Checkout". ProcessCheckoutAction prender il valore "Checkout" e trover una chiave di un message resource che ha lo stesso valore. Nell'istanza, la chiave sar button.checkout, la quale, in accordo al metodo getKeyMethodMap() mostrato precedentemente, si mappa al metodo checkout(). Si tratta di un percorso piuttosto lungo semplicemente per determinare quale metodo invocare. L'intento di questa classe di rendere tutto pi facile quando si ha un modulo l'HTML con pulsanti di immissione multipli con lo stesso nome. Un pulsante si immissione pu essere una action Checkout e un altro potrebbe essere una action Save Order. Entrambi i bottoni avrebbero lo stesso nome per esempio "action" ma il valore di ciascun pulsante sarebbe differente. Questa classe Action probabilmente tra quelle meno usate, ma in certe situazioni permette realmente di risparmiare tempo in sviluppo.

5.1.6.5 La classe org.apache.struts.actions.SwitchAction


La classe SwitchAction un novit per il framcwork, ed stata introdotta per supportare la commutazione da un modulo applicativo a un altro e inviare successivamente tramite forward il controllo a una risorsa nell'applicazione. Sono richiesti due parametri della request. Il parametro prefix specifica il prefisso di applicazione, che comincia con "/", del modulo applicativo verso cui deve essere inviato il controllo. Se necessario commutare verso l'applicazione predefinita, si usa una stringa vuota (""). L'oggetto ApplicationConfig opportuno sar immagazzinato nella request, proprio come succede quando una nuova request arriva alla ActionServlet. Il secondo parametro della request richiesto il page; dovrebbe specificare l'URI relativo all'applicazione, che comincia con "/", verso il quale il controllo dovrebbe essere indirizzato tramite forward una volta che sia stalo selezionato il modulo applicativo corretto. Ci sar bisogno di questa classe solo se si usa pi di un modulo applicativo di Struts.

5.2 Le classi di utilit


Quando si costruiscono web application, molti dei compiti di recupero ed elaborazione sono piuttosto ripetitivi. Come ogni buon framework, Struts colloca gran parte di queste noiose funzioni in classi di utilit, che possano essere condivise e utilizzate da componenti e applicazioni differenti. Questa separazione tra funzionalit "di routine" e funzioni regolari specifiche dell'applicazione consente un maggiore riuso e riduce la ridondanza per tutto il framework e all'interno delle applicazioni. Le classi di utility usate da Struts sono collocate in vari package. Molti dei componenti delle utility erano cos generici e utili a tante altre applicazioni che sono stati spostati fuori da Struts, nel pi ampio progetto Jakarta Commons. Questi package comprendono BeanUtils, Collections e il componente Digester di cui si parlato nel Capitolo 3. Uno dei package Java che rimane nella gerarchia dei package di Struts org.apache.struts.util. Tutto ci che va dalla classe MessageResources alla StrutsValidatorUtil (parte del nuovo componente Validator aggiunto nella versione 1.1) fa parte di tale package, che contiene molte classi con scopi e responsabilit differenti. Sebbene le classi di Struts mantengano forti relazioni condizionali con le classi di utilit, queste ultime dovrebbero condizionare solo altre classi di utilit e componenti del framework pi in basso nella catena di dipendenza: e ci abbastanza.vero in Struts, pur con qualche eccezione. Le classi di utilit contenute nel package util assistono il resto del framework nella soluzione di problemi usuali cui tutte le web application sono soggette. Non si tratteranno tutte le classi del package ma si illustreranno alcuni dei componenti pi utili.

5.2.1 La classe RequestUtils


La classe org.apache.struts.util.RequestUtils fornisce metodi di utilit generale di uso comune nella elaborazione di request da servlet in Struts. Abbiamo gi visto qualche esempio che utilizzava la classe RequestUtils. Uno dei metodi pi importanti, il primo a essere invocato per una request, selectApplication() che chiamato dalla ActionServlet quando arriva una nuova request. Ogni metodo nella classe RequestUtils progettato per risultare thread-safe e non dichiara alcuna variabile di istanza. Infatti, ogni metodo di questa classe anche statico. Raramente sar necessario modificare un metodo della classe

RequestUtils. E tuttavia importante prendere dimestichezza con i metodi implementati nella RequestUtils, per
evitare di replicare il comportamento di uno di questi all'interno della propria applicazione e per comprendere appieno ci che l'intero framework fa per vostro conto.

5.2.2 La classe ResponseUtils


Lo scopo della classe org.apache.struts.util.ResponseUtils simile a quello della RequestUtils, se non per il fatto che essa serve a realizzare una response e non a gestire una request. Sono solo pochi i metodi della classe, ma le librerie di tag JSP comprese in Struts li usano in maniera estensiva per filtrare e scrivere dati destinati all'oggetto response.

5.2.3 Il package Commons BeanUtils


Il package org.apache.commons.beanutils contiene varie classi usate nel framework. Per quanto riguarda strettamente Struts, le due pi importanti sono BeanUtils e PropertyUtils. La classe BeanUtils viene usata con i JavaBeans. I componenti di Struts utilizzano principalmente solo tre dei metodi della classe BeanUtils: populate( ) Riempie un JavaBean con dati, usando una mappa di coppie chiave/valore. La firma del metodo populate() :

public static void populate( Object bean, Map properties ) throws IllegalAccessException, InvocationTargetException;
getProperty( ) Restituisce una rappresentazione String della propcrty immagazzinata nella variabile con il nome corrispondendente al valore del parametro name. Ecco la firma del metodo getProperty():

public static String getProperty( Object bean, String name ) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException;
Indipendentemente dal tipo di propriet cui fa riferimento l'argomento name, essa sar convertita e restituita sotto forma di String. getArrayProperty( ) Restituisce il valore della propriet dell'array specificato nello specifico bean come array String. Ecco la firma del metodo getArrayProperty():

public static String[] getArrayProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException;
Sebbene il linguaggio Java fornisca reflection e introspection come parte delle sue API fondamentali, la classe BeanUtils fornisce wrapper di comodit per inglobare tali API.

L'altra classe usata dal framework Struts la PropertyUtils. Attualmente, viene usato solo il metodo getProperty() di questa classe. Il metodo getProperty() della classePropertyUtils restituisce il valore della propriet specificata, senza cercare di convertirne il tipo. Ecco la firma del metodo: public static Object getProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException;
Gran parte del codice della classe PropertyUtils era in origine implementato nella BeanUtils. stato collocato in una classe a parte perch BeanUtils diventava troppo grande.

5.2.4 Il package Commons Collection


Sebbene Java 1.3 abbia introdotto le tanto richieste e necessarie classi Collection, qualche lacuna rimaneva comunque. La classi all'interno del package Commons Collection si rivolgono alla soluzione di queste mancanze che ancora rimanevano. Tra le caratteristiche del package Collection si possono elencare:

Implementazione di Lists e Maps progettate per un accesso rapido.


Metodi per utilizzare propriet delle collection inerenti alla teoria degli insiemi, quali unioni, intersezioni e chiusura. Classi adaptor per consentire conversioni tra container Java 1.1 e Java 1.2. Attualmente, il framework Struts usa solo la classeFastHashMap del package Collection. FastHashMap progettata per operare in un ambiente multithread, dove la maggior parte delle chiamate di sola lettura. FastHashMap estende java.util.HashMap e prevede due diverse modalit: slow e fast. In modalit lenta, tutti gli accessi sono sincronizzati, il che ottimo in fase di inizializzazione. Una volta completata l'inizializzazione, si verificano principalmente chiamate di sola lettura; Map pu essere commutata a modalit veloce tramitesetFast(true). In modalit fast, l'accesso in lettura non sincronizzato e le chiamate inscrittura utilizzano tecniche di clonazione degli oggetti per migliorare le prestazioni.

5.2.5 Sicurezza nelle classi Action


Le classi Action, se progettate bene e poste nel giusto scope, possono svolgere funzioni molto importanti per un'applicazione. Per evitare che utenti non autorizzati trovino il modo di eseguire una action, le classi Action dovrebbero essere in grado di autorizzare solo certi utenti a eseguire una determinata azione. Il metodo processRoles() progettato per controllare se nel file di configurazione siano definiti dei ruoli per la Action e, in caso affermativo, per chiamare il metodo isUserInRole() sulla request. Il problema con questo approccio che non tutte le applicazioni possono definire i loro ruoli prima del tempo. In alcune applicazioni, i ruoli possono essere aggiunti e rimossi dinamicamente, e non possono essere elencati prima. In questo caso, occorre trovare un approccio alternativo per gestire utenti, ruoli e permessi: la sicurezza gestita dal container pu non bastare, e sar necessario gestire certi dettagli tramite programmazione.

CAPITOLO 6

I componenti model di Struts

In questo capitolo saranno trattati gli elementi che costituiscono, all'interno dell'applicazione Struts, la componente model. Il model rappresenta i dati di elaborazione di un'applicazione e dovrebbe rispecchiare fedelmente le entit e i processi elaborativi del mondo reale che esso rappresenta. Si esamineranno inoltre ruoli e responsabilit dei componenti del model all'interno di Struts prestando particolare attenzione alla costruzione di un'implementazione dell'applicazione Storefront corretta dal punto di vista dell'architettura. Si porr l'accento sull'utilizzo di un'infrastruttura di persistenza che possa essere integrata facilmente e senza problemi in un'applicazione basata su Struts.

6.1 La "M" in MVC


I componenti del model di un'applicazione sono presumibilmente gli clementi software di maggior valore per un'organizzazione. Il modello include le entit di elaborazione e le regole che governano l'accesso ai dati e la loro modifica. Deve per questo essere mantenuto in una sola collocazione per garantire un'efficace integrit dei dati, ridurre la ridondanza e incrementare la possibilit di riuso. II modello dovrebbe rimanere indipendente rispetto al tipo di client che si utilizza per accedere ai business object e alle regole a questi associate. Di fatto, i componenti all'interno di un model non dovrebbero neanche sapere quali tipi di client o di infrastruttura li stanno utilizzando. Si prenda per assioma "le dipendenze vanno messe sotto, i dati vanno messi sopra". Vale a dire, nel caso si utilizzi un'architettura a strati, che lo strato superiore pu avere dipendenze nello strato inferiore ma gli strati inferiori non devono mai dipendere dagli strati a loro superiori. La Figura 6-1 illustra come tale principio si applichi a una tipica architettura Struts. Se si devono importare package o classi dal framework Struts nel proprio modello, si viola questo principio. Rendere dipendente uno strato inferiore da uno superiore render pi difficile la manutenzione, il riutilizzo e le possibilit di sviluppo successive. Prima di entrare nel dettaglio della progettazione e della costruzione di un model per un'applicazione basata su Struts, daremo uno sguardo ai diversi tipi di modelli e al modo in cui ciascuno di essi sia attinente a un progetto. Figura 6-1. Application layers should depend only on lower layers

If you find yourself importing packages or classes from the Struts framework into your model, you are violating this principle. Coupling a lower layer to an upper one will make maintenance, reuse, and future enhancements more difficult. Before we get into the details of designing and building a model for a Struts application, let's look at the different types of models and how each one is relevant to a project.

6.1.1 Types of Models

The term "model" has many different meanings. In very general terms, a model is a representation of some aspect of reality, such as a shop where products are bought and sold, an auction house where bids are placed, or a way to predict how a weather storm will move. All of these Esempios are based on real concepts. The main purpose of creating a model is to help understand, describe, or simulate how things work in the real world. In software development, the term "model" is used to indicate both the logical representation of real-world entities and the physical creation of classes and interfaces that programs can use. The first step, however, always should be to perform a thorough analysis of the problem domain. Once use-cases are complete, the next step should be to develop a conceptual model. 6.1.1.1 The conceptual model During analysis of the problem domain, a conceptual model should be developed based on the real-life entities within the problem space. The entities in a conceptual model have less to do with the software components of the system and more to do with the physical entities that are fundamental to the business. The conceptual model usually illustrates the concepts, the relationships between them, and the attributes that belong to each concept. The behavior usually is not represented in this type of model. The conceptual model is developed from a set of use-cases for the system. The purpose of building the model is to help identify the entities that most likely will become classes in the design stage and to help better understand the problem domain. Figura 6-2 illustrates a conceptual model for the Storefront application. Notice that only relationships and the attributes for the entities are shown; no methods are specified. Figura 6-2. The Storefront conceptual model

If you are familiar with Entity-Relationship (E-R) diagrams, you shouldn't be too confused by the conceptual model. They are very similar.

The value of a conceptual model is that it clearly shows the entities that are used by the problem domain. Everyone involved in the business, technical or not, should be able to look at the conceptual model and make sense of it. They also should be able to quickly point out problems with the model. For Esempio, maybe an item can't belong to more than one catalog at a time. By examining the conceptual model, someone would be able to point this out, and the analysts could make the change early. The later in the design and development cycle that a change is required, the more costly that change becomes.

6.1.1.2 The design model


The conceptual model is just one artifact of the analysis stage; there can be many others. In smaller development groups or on smaller projects, the conceptual model may be skipped in favor of moving to the design stage sooner. The risk of doing this, however, is that you might leave the analysis stage without a clear and concise understanding of the requirements. The better you understand the requirements for a project, the closer to meeting them the end product will be. Even if you build a conceptual model, you still will need to create the appropriate design documents. This usually includes class diagrams, interaction diagrams, and possibly other artifacts such as state diagrams. At minimum, your design-stage artifacts should include one or more class diagrams. Figura 6-3 illustrates a class diagram based on the conceptual model for the Storefront application illustrated in Figura 6-2. Figura 6-3. The class diagram for the Storefront business objects

Figura 6-3. Il diagramma delle classi per i business object di Storefront

Il diagramma delle classi nella Figura 6-3 mostra i business object usati nell'applicazione Storefront. Per semplificare, sono state evidenziate solo relazioni e attributi. Il modo in cui si arriva a una progettazione accurata delle applicazioni va oltre gli scopi di questo libro. C' gran numero di ottimi libri sull'analisi e la progettazione. Uno di questi Applying UML and Pattems di Craig Larman (Prentice Hall). Tipo e struttura dei business object dipendono ovviamente dal dominio della propria applicazione e non esistono due applicazioni che siano esattamente uguali. Persino all'interno di una singola applicazione si pu capire intuitivamente che le richieste mutano. Tuttavia, un'analisi accurata del dominio del problema complessivo e una progettazione rigorosa mettono al riparo la propria applicazione da cambiamenti inaspettati.

6.2 Che cosa un business object?


Questa domanda potrebbe suonare banale posta all'interno di un libro su Struts. Tuttavia il termine business object ha parecchie connotazioni e alcuni programmatori lo usano a sproposito. Un business objeet (BO) semplicemente un'astrazione software di un'entit appartenente alla realt. Rappresenta una persona, una cosa o un concetto

appartenente al dominio dell'elaborazione. Concetti quali articoli, ordini e clienti sono tutti business objeet del dominio dell'applicazione Storefront. I business object sono costituiti da stato e comportamento. Il business object OrderBO, ad esempio, contiene le informazioni relative a un ordine di acquisto di un singolo cliente, incluso prezzo, tasse e stato dell'ordine. Un OrderBO deve anche sapere chi il cliente ed essere in grado di trasmettere queste informazioni. Per poter essere considerata un business object importante che una classe possegga le informazioni di stato e comportamento. Esaminiamo altre caratteristiche dei business object.

6.2.1 Caratteristiche dei business object


Una classe, per poter essere considerata un business object, deve sottostare a diverse condizioni: Possedere stato e comportamento Rappresentare una persona, un luogo, un oggetto o un concetto appartenente al dominio dell'applicazione Essere riutilizzabile Anche i business object possono essere raggruppati in divere categorie, riassumibili di solito in tre tipi: Business object riferiti alle entit Business object riferiti ai processi Business object riferiti agli eventi II pi familiare probabilmente il BO riferito alle entit, che rappresenta una persona, un luogo, una cosa o un concetto. Di solito sono estrapolati dal dominio dell'applicazione e possono essere considerati, trasposti nella sintassi grammaticale, i "sostantivi" nel processo di elaborazione. Di questa categoria fanno parte anche concetti come clienti, ordini, articoli e cos via. In un'applicazione EJB, questi sono modellati come entity bean (da cui il nome). In un'applicazione web di stampo pi tradizionale possono essere normali JavaBeans che contengono stato e comportamento del dominio dell'applicazione. I BO riferiti ai processi rappresentano processi elaborativi o compiti all'interno di una applicazione, di solito dipendono da BO riferiti alle entit e rappresentano i "verbi" dell'applicazione. In un'applicazione EJB sono di solito codificati come session bean o, in alcuni casi, come bean diretti da messaggi (message driven beans). In un'applicazione che non faccia uso di EJB possono essere JavaBeans normali cui assegnato un compito di gestione o controllo per l'applicazione. Anche se questi tipi di business object sono utilizzati per l'elaborazione del flusso di dati, possono pur sempre mantenere lo stato di un'applicazione, Con gli EJB, per esempio, esistono session bean che mantengono lo stato e che non lo mantengono (detti rispettivamente stateful e stateless). E ultima categoria di business object quella dei BO riferiti agli eventi che rappresentano alcuni eventi nell'applicazione (eccezioni, allarmi, eventi temporizzati) che possono essi stessi causare o che sono generati da una qualche azione compiuta nel sistema. In un'applicazione Java Swing, per esempio, quando si preme un pulsante, si attiva un evento che informa l'infrastruttura sottostante in modo che tale azione possa essere gestita da un appropriato gestore degli eventi.

6.2.2 L'importanza dei business object


L'utilizzo dei business object in un'applicazione apporta notevoli vantaggi. Il pi evidente che i business object offrono una terminologia e delle idee comuni che possono essere trasmesse all'interno di un'organizzazione e comprese sia da personale tecnico che da altri. Dal momento che a essere rappresentati sono concetti e idee appartenenti alla realt ordinaria, i business object sono facilmente comprensibili anche intuitivamente, a qualsiasi livello dell'organizzazione. Se esistono molte applicazioni che dipendono dallo stesso ambito operativo, si presume che esistano gli stessi business object nei limiti dell'applicazione. Tale riusabilic di informazioni e comportamento permette di velocizzare il processo di sviluppo delle applicazioni e di ridurre la ridondanza. I business object possiedono anche la capacit di evolversi di pari passo con l'organizzazione per mezzo di modifiche all'oggetto originario o attraverso un'appropriata specializzazione. Ci importantissimo dal momento che, se un'organizzazione soggetta a mutamenti, anche le informazioni e i comportamenti devono adattarsi e cambiare. Infine, i business object possiedono interfacce ben definite; non interfacce nel senso comune del linguaggio Java, ma un insieme chiaro e completo di funzionalit. L'implementazione interna dovrebbe essere nascosta al client per proteggere gli oggetti invocanti da cambiamenti ai dettagli implementativi. Ad esempio, poniamo di avere un business object che utilizzi java.util.ArrayList. Invece di esporre il tipo ArrayList, si dovrebbe esporre java.util.List. Se l'implementazione cambia internamente passando da ArrayList a LinkedList,

l'oggetto invocante non sar coinvolto, dal momento che il client programma utilizzando l'interfaccia List e non la classe di implementazione. A questo punto si dovrebbe avere cosipevolezza dell'importanza che i business object rivestono all'interno di una organizzazione. Sono infatti presenti in tutte le applicazioni (eccetto le pi banali). Dovrebbe anche essere chiaro che contengono stato e comportamento e che, nella maggior parte dei casi, questi ultimi agiscono sui dati. Ci si potrebbe chiedere dunque dove ha origine lo stato e dove viene conservato quando l'applicazione viene terminata: il momento di parlare di persistenza degli oggetti.

6.3 Persistenza
Generalmente con il termine persistenza ci si riferisce al fatto che i dati inseriti in un'applicazione, sia da un utente umano che da altri mezzi, avranno un'esistenza che va oltre il ciclo di vita dell'applicazione. L'informazione sopravviver agli arresti dell'applicazione o allo spegnimento della macchina. Questo importante per un'organizzazione dal momento che vi sempre la necessit di dati persistenti qualsiasi sia la grandezza dell'organizzazione stessa.

6.3.1 Business object persistenti


Quando gli oggetti sono creati in memoria per un'applicazione non possono risiedervi per sempre. A seconda del caso, devono essere cancellati oppure conservati in un mezzo di memorizzazione dei dati. La memoria volatile e un'applicazione potrebbe andare incontro a un crash o potrebbe essere necessario fermarla per operazioni di manutenzione. Senza la persistenza dei dati non esisterebbe alcuna registrazione di quello che, ad esempio, potrebbe essere stato ordinato oppure di chi deve pagare. I business object rappresentano informazioni che devono essere conservate. Un'applicazione come Storefront per essere di effettiva utilit deve mantenere informazioni come ordini, articoli e dati del cliente. Perdere l'ordine di un cliente significa probabilmente perdere anche il cliente in breve tempo. Una volta che i dati siano stati conservati, possono essere recuperati e utilizzati per ricostruire i business object.

6.3.2 Immagazzinare oggetti in un modello relazionale


Anche se esistono molti tipi diversi di immagazzinamento dei dati, i database relazionali sono quelli utilizzati pi di frequente per conservare i dati di un'applicazione, specialmente nel caso di applicazioni come Storefront. I database relazionali rappresentano una scelta obbligata e il loro utilizzo molto diffuso. Ci sono tuttavia alcuni ostacoli che devono essere superati per poterli utilizzare con profitto. Una delle maggiori difficolt da superare consiste nella soluzione del cosiddetto impedance mismatch.

6.3.2.1 L'impedance mismatch


Gli oggetti mantengono stato e comportamento e possono essere incrociati con altri oggetti attraverso le loro relazioni. Il paradigma relazionale, d'altro canto, basato sull'immagazzinamento e la congiunzione di insiemi di dati mediante il riconoscimento di campi susseguenti. In sostanza un database relazionale permette una visibilit "piatta" dei dati. Questa differenza comporta una discrepanza importante tra i due mondi: gli oggetti devono essere normalizzati prima di poter essere conservati in un database relazionale ma le relazioni che interconnettono gli oggetti devono pure poter essere conservate per poter permettere una corretta ricostruzione della gerarchia degli oggetti. I database a oggetti non richiedono la normalizzazione degli oggetti. Tuttavia non sono cos diffusi come i database relazionali.

Non possibile in questa sede discutere il mapping degli oggetti in un database relazionale per motivi di spazio. Basti al momento sapere che vi sono diversi approcci. Fortunatamente sono disponibili numerose risorse e punti di riferimento per la soluzione di questi problemi. Il documento di Scott Ambler "Mapping Objects to Relational Databases" costituisce la pi importante fonte di informazioni sull'argomento. (Si veda http://www. ambysoft. com/mappingObjects.pdf). Come si vedr in seguito, vi sono molte infrastrutture per il mapping tra database a oggetti e relazionali (object-torelational mapping, ORM) che rendono questo lavoro molto pi semplice per lo sviluppatore Java. Queste infrastrutture

non eliminano completamente la necessit di una buona comprensione del problema, ma possono evitare molti dei compiti sgraditi agli sviluppatori.

6.4 Che cosa offre Struts per il model?


Per essere onesti Struts non offre grafiche per costruire componenti model, ma forse giusto cos. Sono gi disponibili numerose infrastrutture per trattare con l'ambito elaborativo di un'applicazione, inclusi gli Enterprise JavaBeans e i Java Data Objects (JDO), oppure si possono utilizzare comuni JavaBeans e un ORM. Struts, fortunatamente, non costringe gli sviluppatori all'implementazione particolare di un modello. In questo capitolo sar presentato un certo approccio e nel Capitolo 13 un approccio totalmente diverso e si vedr il grado di coinvolgimento della struttura in questi cambiamenti.

6.5 La costruzione del modello Storefront


Dopo questa digressione sulle parti che costituiscono il model per un'applicazione Struts venuto il momento di applicare i concetti precedentemente discussi utilizzando come ambito l'applicazione Storefront. Ovviamente Storefront un esempio speculativo e non rappresenta un modello completo per quello che una "vera" applicazione di commercio elettronico dovrebbe supportare. In ogni caso offre sufficienti elementi di un object model per la comprensione della semantica di questo capitolo.

6.5.1 Come accedere a un database relazionale


Lo stato dell'applicazione Storefront sar mantenuto per mezzo di un database relazionale. Questo di fatto sarebbe il metodo da utilizzare se Storefront fosse un'applicazione reale. Spesso utilizzato un sistema ERP in congiunzione al database relazionale ma molte applicazioni di commercio elettronico si appoggiano direttamente al database per migliorare le prestazioni e per la facilit di sviluppo. Nel caso che siano entrambi impiegati in un'azienda, c' solitamente un servizio middleware per mantenere tra i due la sincronizzazione tra i due che pu avvenire o in tempo reale o in modalit batch. Come probabilmente gi noto c' una vasta scelta di database relazionali. Si pu scegliere uno dei grandi produttori di database oppure, se le proprie necessit non richiedono un tale impegno di costi e caratteristiche, si pu scegliere uno dei prodotti pi economici sul mercato o anche liberamente scaricabili. Dal momento che, ai fini dell'esempio, non si costruiranno tutti gli elementi che compongono l'applicazione e che il bacino d'utenza cui Storefront si rivolge minimo, non esistono caratteristiche particolari che restringano la scelta del database. Detto ci, gli esempi specifici per i database in questo capitolo dovrebbero essere adatti per la maggior parte delle piattaforme, Se si conosce il Data Definition Language (DDL) di SQL, si pu adattare il DDL per il database disponibile. C' ancora un po' di lavoro da fare prima di poter utilizzare il modello Storefront. Devono essere completati i seguenti passaggi prima di coinvolgere l'infrastruttura definita da Struts: Occorre creare i business objects per l'applicazione Storefront. Occorre creare il database per l'applicazione Storefront. Occorre effettuare il mapping dei business object nel database. Occorre poi verificare che i business object possano essere conservati nel database. Come evidente, nessuno di questi compiti coinvolge Struts. Si dovrebbe affrontare questa parte del piano di sviluppo senza avere in mente un particolare client. La web application Storefront solo un tipo di client per i business object. Se tale applicazione stata progettata e scritta in modo adeguato, possono essere utilizzati diversi tipi di infrastrutture. I business object sono usati per richiedere e conservare informazioni che riguardano l'ambito di lavoro di Storefront. Non dovrebbero essere accoppiati al client a livello di presentazione. Per consentire l'isolamento di Struts dai cambiamenti che possono essere apportati nei business object, si utilizzer il pattern di progettazione Business Delegate all'interno dell'applicazione Storefront. Il Business Delegate opera come astrazione lato client dell'elaborazione. Nasconde l'implementazione del servizio effettivo, ottenendo una riduzione dell'accoppiamento tra client e business object.

6.5.2 Creazione dei business object di Storefront


I business object consistono di dati e comportamento. Sono una rappresentazione virtuale di uno o pi record all'interno di un database. Nell'applicazione Storefront ad esempio, un oggetto OrderBO rappresenta fisicamente un ordine di un cliente. Pu anche contenere la business logic che aiuta a garantire e mantenere la validit dei dati.

Dove va la business validation?


La decisione circa la posizione della logica di validazione in un'applicazione Struts potrebbe essere frustrante. Da una parte, sembrerebbe far parte della struttura stessa, dal momento che questo il primo luogo da cui ottenere e convalidare i dati dell'utente. Il problema insito nel porre la business logie per la validazione all'interno delle classi Action o ActionForm che la validazione rimane poi associata all'infrastruttura Struts, cosa che non permette la successiva riusabilit da parte di altri client. C' tuttavia un diverso tipo di validazione chiamato presentation validation che pu (e deve) manifestarsi all'interno dell'infrastruttura. La validazione della presentazione, o input validation, come a volte detta, pu essere raggruppata in tre categorie distinte: Lessicale Sintattica Semantica La validazione lessicale opera un controllo sulla consistenza dei dati. Ad esempio, il valore che corrisponde a una quantit un intero? La validazione sintattica procede oltre controllando che i valori che formano un composto siano validi e accettabili nella forma. Ad esempio, i campi per le date in un browser sono di solito accettali nella forma di valori mese/giorno/anno. La validazione sintattica controlla che i dati inseriti siano nel formato corretto. Tuttavia, non controlla che la data riportata sia una data valida. Per questo entra in gioco la validazione semantica che garantisce che i valori inseriti siano significativi per l'utilizzo che l'applicazione ne fa. Per esempio l'immettere un valore di -3 nel campo della quantit dell'ordine lessicalmente e sintatticamente corretto ma non valido dal punto di vista della semantica. La validazione della presentazione, ma non la business validation, appartiene all'infrastruttura Struts. I business object hanno responsabilit di assicurare la validit dei dati inseriti nel database, per cui devono avere le necessarie regole per poter ottemperare a questo compito. Il primo passo consiste nel creare i business object con i quali si dovr interagire. In questa implementazione saranno normali oggetti JavaBeans. Molti componenti di modello sono specifici per una singola implementazione. Ad esempio, gli entity bean funzioneranno solo all'interno di un EJB container. Per questo esempio i business object di Storefront non seguiranno le specifiche di una particolare implementazione. Se pi in l si vorranno utilizzare questi stessi business object con un container EJB, si potranno incapsulare in entity bean oppure si potr delegare la chiamata da un metodo di session bean a uno di questi oggetti. Nel Capitolo 13, si vedr come fare senza coinvolgere l'applicazione Storefront. Dal momento che tutti i business object condividono propriet comuni, si creer una superclasse astratta per i business object. Ogni business object sar una sottoclasse della classe BaseBusinessObject mostrata nell'Esempio 6-1 .
Esempio 6-1. BaseBusinessObject la superclasse per tutti i business object

package com.oreilly.struts.storefront.businessobjects; /** * An abstract superclass that many business objects will extend. */ abstract public class BaseBusinessObject implements java.io.Serializable { private Integer id; private String displayLabel; private String description; public Integer getId( return id; } ) {

public void setId(Integer id) { this.id = id; } public void setDescription(String description) { this.description = description; } public String getDescription( return description; } ) {

public void setDisplayLabel(String displayLabel) { this.displayLabel = displayLabel; } public String getDisplayLabel( return displayLabel; } }
La BaseBusinessObject evita che ogni business object debba dichiarare queste propriet comuni. Se esiste business logic comune, possibile inserire anche essa in questa sede. L'Esempio 6-2 mostra il business object OrderBO che rappresenta un ordine d'acquisto di un cliente nell'applicazione Storefront. Non c' niente di particolare circa la classe OrderBO; solo un normale oggetto JavaBeans. Oltre al metodo recalculatePrice(), la classe offre solo i metodi di get e di set relativamente alle propriet dell'ordine.
Esempio 6-2. L'oggetto OrderBO rappresentante un ordine di un cliente

) {

package com.oreilly.struts.storefront.businessobjects; import import import import import java.math.BigDecimal; java.sql.Timestamp; java.util.Iterator; java.util.List; java.util.LinkedList;

/** * The OrderBO, which represents a purchase order that a customer * has placed or is about to place. */ public class OrderBO extends BaseBusinessObject{ // A list of line items for the order private List lineItems = new LinkedList( ); // The customer who placed the order private CustomerBO customer; // The current price of the order private double totalPrice; // The id of the customer private Integer customerId; // Whether the order is in process, shipped, canceled, etc. private String orderStatus; // The date and time that the order was received private Timestamp submittedDate; public OrderBO( Integer id, Integer custId, String orderStatus, Timestamp submittedDate, double totalPrice ){ this.setId(id); this.setCustomerId(custId); this.setOrderStatus(orderStatus); this.setSubmittedDate(submittedDate); this.setTotalPrice(totalPrice); } public void setCustomer( CustomerBO owner ){ customer = owner; } public CustomerBO getCustomer( return customer; } public double getTotalPrice( return this.totalPrice; } ){ ){

private void setTotalPrice( double price ){ this.totalPrice = price; } public void setLineItems( List lineItems ){ this.lineItems = lineItems; } public List getLineItems( return lineItems; } ){

public void addLineItem( LineItemBO lineItem ){ lineItems.add( lineItem ); } public void removeLineItem( LineItemBO lineItem ){ lineItems.remove( lineItem ); } public void setCustomerId(Integer customerId) { this.customerId = customerId; } public Integer getCustomerId( return customerId; } ) {

public void setOrderStatus(String orderStatus) { this.orderStatus = orderStatus; } public String getOrderStatus( return orderStatus; } ) {

public void setSubmittedDate(Timestamp submittedDate) { this.submittedDate = submittedDate; } public Timestamp getSubmittedDate( return submittedDate; } private void recalculatePrice( double totalPrice = 0.0; ){ ) {

if ( getLineItems( ) != null ){ Iterator iter = getLineItems().iterator( ); while( iter.hasNext( ) ){ // Get the price for the next line item and make sure it's not null Double lineItemPrice = ((LineItemBO)iter.next()).getUnitPrice( ); // Check for an invalid lineItem. If found, return null right here. if (lineItemPrice != null){ totalPrice += lineItemPrice.doubleValue( ); } } // Set the price for the order from the calcualted value setTotalPrice( totalPrice ); } } }
Non si riporteranno qui tutti i business object, in quanto la loro implementazione analoga alla classe OrderBO.

Quando si progettano i propri business object, si pu trascurare il modo in cui sono mappati nel database. Ci sar tempo a sufficienza per farlo. Non ci si deve preoccupare di usare tecniche orientate agli oggetti come ereditariet e polimorfismo, proprio come si farebbe con qualsiasi altro modello di oggetti. BaseBusinessObject nell'Esempio 6-1 non sar effettivamente mappata in una tabella di database ma lo saranno le sue propriet con le rispettive sottoclassi. Anche se la maggior parte delle infrastrutture che operano il mapping della persistenza supportano diversi approcci al mapping dell'ereditariet all'interno di un database, se si aggiungono le propriet a ogni tabella possono essere eseguite meno operazioni di join di SQL, migliorando la performance.

6.5.3 Il modello di dati di Storefront


Creati tutti i business object per l'applicazione Storefront, bisogna creare un modello di database e uno schema. I dettagli che riguardano la creazione di uno schema di database per l'applicazione Storefront vanno oltre gli scopi di questo libro. Pu sembrare semplice creare qualche tabella in un database e aggiungervi le colonne. Tuttavia difficile comprendere i passaggi tra la normalizzazione del database e i problemi che emergono dalla discrepanza tra "a oggetti" e "relazionale" di cui si detto precedentemente. Se l'applicazione sufficientemente piccola, chiunque potrebbe creare lo schema di un database, in special modo con i mezzi messi a disposizione dai vari produttori di database e da terze parti. Ma se il proprio schema un po' pi corposo di qualche tabella oppure se la complessit di chiavi esterne, trigger e indici alta, meglio lasciare creare lo schema ad un esperto. Lo schema di Storefront abbastanza piccolo, soprattutto perch si scelto di implementare solo una parte di quello che di solito sarebbe richiesto. La Figura 6-4 mostra il modello di dati che sar implementato in Storefront.

Figura 6-4. Il modello dati di Storefront

Le definizioni della tabella nella Figura 6-4 si commentano da s. Ci sono per parecchi elementi che meritano il nostro interesse e che devono essere evidenziati. Il primo che ad ogni tabella, eccetto che a CATALOGITEM_LNK, stato assegnato un identificatore di oggetto (OID). Gli OID semplificano la navigazione tra gli oggetti e non dovrebbero avere alcun significato per l'applicazione. I valori che si basano sulla semantica di elaborazione sono soggetti a cambiamenti e basare le proprie chiavi su valori che cambiano molto problematico. Nel mondo dei database, l'utilizzo di una strategia basata su OID chiamato utilizzo di chiavi surrogate.

Per generare lo schema per il modello di dati illustrato nella figura Figura 6-4, necessario creare il DDL. Il DDL SQL usato per creare le entit fisiche nel database; quello di Storefront usato per creare le tabelle nella Figura 6-4 illustrato nell' Esempio 6-3.
Esempio 6-3. Il DDL SQL di Storefront

# The SQL DDL for the Storefront Application # (Programming Jakarta Struts by O'Reilly) # Chuck Cavaness # Execute the next line if you need to clear the storefront database DROP DATABASE storefront; # Creates the initial database CREATE DATABASE storefront; # Make sure you are creating the Tabellas in the storefront Tabellaspace use storefront; CREATE TABELLA CATALOG( id int NOT NULL, displaylabel varchar(50) NOT NULL, featuredcatalog char(1) NULL, description varchar(255) NULL ); ALTER TABELLA CATALOG ADD CONSTRAINT PK_CATALOG PRIMARY KEY(id); CREATE TABELLA CUSTOMER ( id int NOT NULL, firstname varchar(50) NOT NULL, lastname varchar(50) NOT NULL, email varchar(50) NOT NULL, password varchar(15) NOT NULL, description varchar(255) NULL, creditStatus char(1) NULL, accountstatus char(1) NULL, accountnumber varchar(15) NOT NULL ); ALTER TABELLA CUSTOMER ADD CONSTRAINT PK_CUSTOMER PRIMARY KEY(id); CREATE TABELLA ITEM ( id int NOT NULL, itemnumber varchar (255) NOT NULL, displaylabel varchar(50) NOT NULL, description varchar (255) NULL, baseprice decimal(9,2) NOT NULL, manufacturer varchar (255) NOT NULL, sku varchar (255) NOT NULL, upc varchar (255) NOT NULL, minsellingunits int NOT NULL, sellinguom varchar (255) NOT NULL, onhandquantity int NOT NULL, featuredesc1 varchar (255) NULL, featuredesc2 varchar (255) NULL, featuredesc3 varchar (255) NULL, smallimageurl varchar (255) NULL, largeimageurl varchar (255) NULL ) ALTER TABELLA ITEM ADD CONSTRAINT PK_ITEM PRIMARY KEY(id);

CREATE TABELLA CATALOGITEM_LNK( catalogid int NOT NULL, itemid int NOT NULL ) ALTER TABELLA CATALOGITEM_LNK ADD CONSTRAINT PK_CATALOGITEM_LNK PRIMARY KEY(catalogid, itemid); ALTER TABELLA CATALOGITEM_LNK ADD CONSTRAINT FK_CATALOGITEM_LNK_CATALOG FOREIGN KEY (catalogid) REFERENCES CATALOG(id); ALTER TABELLA CATALOGITEM_LNK ADD CONSTRAINT FK_CATALOGITEM_LNK_ITEM FOREIGN KEY (itemid) REFERENCES ITEM(id); CREATE TABELLA PURCHASEORDER ( id int NOT NULL, customerid int NOT NULL, submitdttm timestamp NOT NULL, status varchar (15) NOT NULL, totalprice decimal(9,2) NOT NULL, ) ALTER TABELLA PURCHASEORDER ADD CONSTRAINT PK_PURCHASEORDER PRIMARY KEY(id); ALTER TABELLA PURCHASEORDER ADD CONSTRAINT FK_PURCHASEORDER_CUSTOMER FOREIGN KEY (customerid) REFERENCES CUSTOMER(id); CREATE TABELLA LINEITEM ( id int NOT NULL, orderid int NOT NULL, itemid int NOT NULL, lineitemnumber int NULL, extendedprice decimal(9, 2) NOT NULL, baseprice decimal(9, 2) NOT NULL, quantity int NOT NULL ) ALTER TABELLA LINEITEM ADD CONSTRAINT PK_LINEITEM PRIMARY KEY(id); ALTER TABELLA LINEITEM ADD CONSTRAINT FK_LINEITEM_ORDER FOREIGN KEY (orderid) REFERENCES PURCHASEORDER(id); ALTER TABELLA LINEITEM ADD CONSTRAINT FK_LINEITEM_ITEM FOREIGN KEY (itemid) REFERENCES ITEM(id);
Il DDL dell'Esempio 6-3 stato testato sotto Oracle 8.1.7 e Microsoft SQL Server 2000. Se si desidera usarlo con altri database, potrebbe essere necessario modificare gli statement ALTER. Per esempio, a causa delle limitazioni di chiavi di tipo esterno con MySQL, potrebbe essere necessario eliminare interamente gli statement FOREIGN KEY. Le uniche parti assolutamente necessarie per far funzionare l'esempio sono le sezioni CREATE TABELLA e le chiavi primarie, che dovrebbero essere supportate da qualunque database. Una volta eseguito il DDL dell'Esempio 6-3, si dovranno inserire alcuni dati nelle tabelle del database per far s che l'applicazione Storefront funzioni correttamente.

6.5.4 Mapping dei business object in un database


Quando giunge il momento di connettersi o di effettuare il mapping ai business object nel database, si pu scegliere tra una variet di approcci. La scelta dipende da una moltitudine di fattori che possono cambiare da un'applicazione a un'altra e da una situazione a un'altra ancora. Eccone alcuni: Usare chiamate dirette JDBC Usare un approccio ORM "fatto in casa" Usare un'infrastruttura ORM proprietaria Usare un'infrastruttura ORM non intrusiva e non proprietaria Usare un database a oggetti Pur tenendo presente che alcune cose vengono meglio se "fatte in casa" e altre vanno lasciate agli esperti, la costruzione di un meccanismo che garantisca la persistenza in Java uno di quei compiti che si dovrebbe evitare di affrontare. Si ricordi che l'obiettivo dello sviluppo di un'applicazione consiste nel risolvere un problema di elaborazione. Di solito, la cosa migliore acquistare da terze parti una soluzione al problema della persistenza. Ci sono parecchi problemi con cui ci si deve misurare, pi complessi di una semplice select SQL attraverso JDBC, come le transazioni, il supporto per le varie associazioni, i proxy virtuali, le strategie di lock, gli incrementi delle chiavi primarie, il caching e il pooling delle connessioni, tanto per menzionarne alcuni. La costruzione di un'infrastruttura di persistenza di per s un progetto intero. Non si dovrebbe buttar via tempo e risorse per qualcosa che non sia attinente l'obiettivo centrale. La sezione successiva elenca diverse soluzioni disponibili.

6.5.5 Infrastrutture di mapping object-to-relational


Esistono numerosi prodotti ORM tra i quali possibile scegliere. Alcuni di questi sono commerciali e hanno un costo che raggiunge o supera quello di molti application server. Altri invece sono liberi e open source. La Tabella 6-1 presenta alcune soluzioni commerciali e non tra cui operare una scelta.
Tabella 6-1. infrastrutture di mapping object-to relational

Prodotto TopLink CocoBase Torque ObJectRelationalBridge FrontierSuite Castor FreeFORM Expresso JRelationalFramework VBSF JGrinder http://www.cocobase.com

URL http://otn.oracle.com/products/ias/toplink/content.html http://jakarta.apache.org/turbine/torque/index.html http://jakarta.apache.org/ojb/ http://www.objectfrontier.com http://castor.exolab.org http://www.chimu.com/projects/form/ http://www.jcorporate.com http://jrf.sourceforge.net http://www.objectmatter.com http://sourceforge.net/projects/jgrinder/

Anche se la Tabella 6-1 non costituisce una lista completa dei prodotti disponibili, presenta svariate soluzioni tra cui scegliere. Sia che si scelga un prodotto commerciale o meno, ci si dovrebbe preoccupare che l'implementazione dell'infrastruttura di mapping non "collida" con la propria applicazione. Si ricordi che in Figura 6-1 le dipendenze dovrebbero sempre percorrere i vari strati verso il basso e che non ci dovrebbe mai essere uno strato superiore con dipendenze dall'infrastruttura di persistenza. Si rivela vantaggioso persino mantenere i business object all'oscuro del modo in cui si garantisce la loro persistenza. Alcune infrastrutti re costringono a importare le loro classi e interfacce, ma questo rappresenta un problema se si avr necessit di cambiare il proprio meccanismo di persistenza. Pi oltre nel corso del capitolo si vedr come usare i pattern Business Delegate e Data Access Object (DAO) per limitare l'intrusione dell'infrastruttura di persistenza. Una tabella comparativa di questi prodotti disponibile all'URL http://www.objectrelational.com/object-relational.html. Queste informazioni non sono gratis, ma se la propria applicazione grande, potrebbe valere la pena di spendere.

Un'ultima cosa di cui avere cura consiste nel fatto che alcune infrastrutture di persistenza devono alterare il bytecode Java dei business object dopo che sono stati compilati. A seconda della reale importanza rivestita da questa operazione, potrebbero nascere dei problemi. Ci si assicuri solo di avere capito completamente come l'infrastruttura di persistenza interagisca con,ia propria applicazione prima di investire tempo e risorse nell'usarla.

6.5.6 L'infrastruttura di persistenza di Storefront


Scegliendo una qualsiasi delle soluzioni indicate in Tabella 6-1 potremo effettuare con successo il mapping dei business object di Storefront nel database. Le caratteristiche richieste non sono particolarmente stringenti e il modello non particolarmente complesso. Si sono valutate parecchie opzioni ma il nostro processo di selezione stato veloce e informale, cosa che non si dovrebbe fare mai per nessun progetto serio. I criteri attraverso i quali le infrastrutture sono state valutate sono stati: Il costo della soluzione Quanto grande l'intrusione del meccanismo di persistenza sulla nostra applicazione La bont della documentazione disponibile

Il costo stato un fattore primario. Si aveva necessit di una soluzione che si potesse utilizzare per seguire gli esempi di questo libro senza far spendere soldi. Tutte le soluzioni valutate per questo esempio hanno funzionato piuttosto bene ed erano abbastanza facili da usare ma alla fine si optato per un prodotto open source ObJectRelationalBridge (OJB).
Non si assuma che solo per il fatto che tale implementazione stata scelta Per questo libro, sia la migliore soluzione per la propria applicazione. Si prenda tempo e si valutino i prodotti seguendo i propri specifici criteri. La documentazione di OJB discreta, pur considerando che, per i progetti open source, la documentazione uno degli ultimi compiti cui si assolve. In sostanza, l'intero mapping dei business object in tabelle di un database ha luogo in un solo file XML, repository_user.xml. Il file interpretato dall'infrastruttura di mapping a runtime ed usato per eseguire SQL in un database. L'Esempio 6-4 mostra la porzione del file di mapping che effettua il mapping dei business object. Al momento della stesura di questo codice la versione pi recente di OJB era la 0.8.375. Quando questo capitolo stato aggiornato per l'ultima volta si era giunti alla versione 1.0 RC3 che era stata aggiunta alla lista dei progetti Jakarta. Il codice qui mostrato per la versione precedente. Per gli ultimi sviluppi di OJB, si veda http://jakarta.apache.org/ojb/.
Esempio 6-4. Il codice XML per il mapping della classe CustomerBO

<ClassDescriptor id="120"> <class.name>com.oreilly.struts.storefront.businessobjects.CustomerBO</clas s.name> <Tabella.name>CUSTOMER</Tabella.name> <FieldDescriptor id="1"> <field.name>id</field.name> <column.name>id</column.name> <jdbc_type>INTEGER</jdbc_type> <PrimaryKey>true</PrimaryKey> <autoincrement>true</autoincrement> </FieldDescriptor> <FieldDescriptor id="2"> <field.name>firstName</field.name> <column.name>firstname</column.name> <jdbc_type>VARCHAR</jdbc_type> </FieldDescriptor> <FieldDescriptor id="3"> <field.name>lastName</field.name> <column.name>lastname</column.name> <jdbc_type>VARCHAR</jdbc_type>

</FieldDescriptor> <FieldDescriptor id="4"> <field.name>email</field.name> <column.name>email</column.name> <jdbc_type>VARCHAR</jdbc_type> </FieldDescriptor> <FieldDescriptor id="5"> <field.name>password</field.name> <column.name>password</column.name> <jdbc_type>VARCHAR</jdbc_type> </FieldDescriptor> <FieldDescriptor id="6"> <field.name>accountStatus</field.name> <column.name>accountstatus</column.name> <jdbc_type>CHAR</jdbc_type> </FieldDescriptor> <FieldDescriptor id="7"> <field.name>creditStatus</field.name> <column.name>creditstatus</column.name> <jdbc_type>CHAR</jdbc_type> </FieldDescriptor> <CollectionDescriptor id="1"> <cdfield.name>submittedOrders</cdfield.name> <items.class>com.oreilly.struts.storefront.businessobjects.OrderBO</items .class> <inverse_fk_descriptor_ids>2</inverse_fk_descriptor_ids> </CollectionDescriptor> </ClassDescriptor>
I mapping rimanenti sono operati in un modo analogo. Una volta che sono specificati nel file XML, si deve configurare la connessione al database per permettere al driver JDBC di connettersi al database corretto. Con OJB le informazioni per la connessione si configurano tramite il file repository.xml, come da Esempio 6-5.
Esempio 6-5. Il file repository.xml contiene le informazioni per la connessione al database

<?xml version="1.0" encoding="UTF-8"?> <!-- defining entities for include-files --> <!DOCTYPE MappingRepository SYSTEM "repository.dtd" [ <!ENTITY user SYSTEM "repository_user.xml"> <!ENTITY junit SYSTEM "repository_junit.xml"> <!ENTITY internal SYSTEM "repository_internal.xml"> ]> <MappingRepository> <JdbcConnectionDescriptor id="default"> <dbms.name>MsSQLServer2000</dbms.name> <jdbc.level>1.0</jdbc.level> <driver.name>com.microsoft.jdbc.sqlserver.SQLServerDriver</driver.name> <url.protocol>jdbc</url.protocol> <url.subprotocol>microsoft:sqlserver</url.subprotocol> <url.dbalias> //localhost:1433;DatabaseName=storefront;user=sa;SelectMethod=cursor </url.dbalias> <user.name>sa</user.name> <user.passwd></user.passwd> </JdbcConnectionDescriptor> <!-- include user-defined mappings here --> &user; <!-- include ojb internal mappings here --> &internal;

</MappingRepository>
Si devono ovviamente configurare i parametri presenti in questo file per il proprio specifico ambiente. Tutto ci serve per configurare l'infrastruttura di persistenza per la propria applicazione sta tutto in questo file. Per inizializzare questa infrastruttura all'interno della propria applicazione si devono chiamare tre metodi, come viene mostrato pi oltre in questa sezione. OJB offre due diverse API che possono essere utilizzate: il Persistence-Broker e l'implementazione di ODMG. Si pu leggere la documentazione per una comprensione approfondita delle differenze, ma in questa sede sufficiente dire che ODMG molto pi potente e possiede un proprio Object Query Language (OQL). Tuttavia alla funzionalit extra si accompagna un aumento della complessit. L'applicazione Storefront utilizzer l'API ODMG perch possiede caratteristiche migliori. L'ODMG permette inoltre al framework di poter essere utilizzato in modalit standalone oppure client/server. Quest'ultima modalit utilissima qualora vi siano numerosi server nello stesso ambiente. Si scelto di usarla per in modalit standalone perch Storefront non necessita di scalabilit. L'infrastruttura di persistenza gira perci all'interno della stessa JVM come pure l'applicazione Storefront.

Non c' spazio a sufficienza in questo capitolo per una spiegazione pi accurata del framework degli OJB. Per informazioni maggiormente dettagliate si consulti la documentazione disponibile su http://jakarta.apache.org/ojb/. Non si dimentichi che vi sar la necessit di avere un database supportato e un driver JDBC all'interno del classpath della propria web application.

6.5.7 I pattern Business Delegate e DAO in azione


Il pezzo finale del mosaico consiste nella creazione di un'interfaccia di servizio che le classi Action di Storefront possano utilizzare al posto dell'interazione diretta con il framework di persistenza. L'idea consiste sempre nel maggior disaccoppiamento possibile tra persistenza e applicazione. Prima di mostrare come questo avviene per l'applicazione Storefront, introdurremo brevemente il pattern Data Access Object (DAO). Lo scopo di un pattern DAO di disaccoppiare la business logic di un'applicazione dalla logica che si occupa dell'accesso ai dati. Quando si utilizza un'infrastruttura di persistenza, il pattern dovrebbe essere d'aiuto per separare i business object dal framework. Un altro scopo consiste nel permettere una semplice intercambiabilit tra implementazioni della persistenza senza effetti negativi sui business object. Esistono due pattern di progettazione indipendenti contenuti nel DAO Bridge e Adaptor che sono entrambi pattern di strutturazione spiegati in Design Patterns: Elements of Reusable Object-Oriented Software (Addison Wesley) della Gang of Four. Per l'applicazione Storefront, si combineranno i pattern DAO e Business Delegate per isolare le classi Action e quelle dei business object dall'implementazione della persistenza. La Figura 6-5 mostra un'astrazione di tale approccio.

Figura 6-5.I1 pattern Business Delegate e DAO associati

L'oggetto client della Figura 6-5 rappresenta le classi Action di Struts. Queste otterranno un riferimento per un'interfaccia service cui ci si riferisce nel diagramma come Business Delegate Interface. L'interfaccia business di Storefront illustrata nell'Esempio 6-6. Esempio 6-6. The Storefront business interface

package com.oreilly.struts.storefront.service; import java.util.List; import com.oreilly.struts.storefront.catalog.view.ItemDetailView; import com.oreilly.struts.storefront.catalog.view.ItemSummaryView; import com.oreilly.struts.storefront.framework.exceptions.DatastoreException; import com.oreilly.struts.storefront.framework.security.IAuthentication; /** * The business interface for the Storefront application. It defines all * of the methods that a client may call on the Storefront application. * This interface extends the IAuthentication interface to provide a * single cohesive interface for the Storefront application. */ public interface IStorefrontService extends IAuthentication { public List getFeaturedItems( ) throws DatastoreException;

public ItemDetailView getItemDetailView( String itemId ) throws DatastoreException; }


L'interfaccia IStorefrontService nell' Esempio 6-6 definisce tutti i metodi che un client pu invocare per l'applicazione Storefront. Nel caso in esame, il client sar l'insieme delle classi Action di Storefront. IStorefrontService progettata in modo che non vi siano dipen denze dal web. Sarebbe auspicabile che altri.tipi di client possano usare lo stesso servizio.

IStorefrontService estende la classe IAuthentication per incapsulare i metodi relativi alla sicurezza. La classe IAuthentication dell'Esempio 6-7, contiene solo due metodi per questosemplice esempio.

Esempio 6-7. L'interfaccia Authentication

package com.oreilly.struts.storefront.framework.security; import com.oreilly.struts.storefront.customer.view.UserView; import com.oreilly.struts.storefront.framework.exceptions.InvalidLoginException; import com.oreilly.struts.storefront.framework.exceptions.ExpiredPasswordException; import com.oreilly.struts.storefront.framework.exceptions.AccountLockedException; import com.oreilly.struts.storefront.framework.exceptions.DatastoreException; /** * Defines the security methods for the system. */ public interface IAuthentication { /** * Log the user out of the system. */ public void logout(String email); /** * Authenticate the user's credentials and either return a UserView for the * user or throw one of the security exceptions. */ public UserView authenticate(String email, String password) throws InvalidLoginException, ExpiredPasswordException, AccountLockedException, DatastoreException; }
Nell'Esempio 6-8 si pu analizzare, un'implementazione per l'interfaccia service IStorefrontService. L'implementazione potrebbe essere sostituita senza problemi da altre purch le nuove implementazioni contengano l'interfaccia IStorefrontService. Nessun client avrebbe alcun problema perch la programmazione per loro fatta a fronte dell'interfaccia e non dell'implementazione. Si potr vedere un esempio di sostituzione dell'implementazione dell'interfaccia IStorefrontService nel corso del Capitolo 13, quando sostituiremo uno strato EJB nell'applicazione Storefront.

Esempio 6-8. La classe di implementazione del servizio Storefront

package com.oreilly.struts.storefront.service; import java.sql.Timestamp; import java.util.List; import java.util.ArrayList; import com.oreilly.struts.storefront.catalog.view.ItemDetailView; import com.oreilly.struts.storefront.catalog.view.ItemSummaryView; import com.oreilly.struts.storefront.framework.security.IAuthentication; import com.oreilly.struts.storefront.customer.view.UserView; import com.oreilly.struts.storefront.businessobjects.*; // Import the exceptions used import com.oreilly.struts.storefront.framework.exceptions.DatastoreException; import com.oreilly.struts.storefront.framework.exceptions.InvalidLoginException; import com.oreilly.struts.storefront.framework.exceptions.ExpiredPasswordException;

import com.oreilly.struts.storefront.framework.exceptions.AccountLockedException; // Import the implementation-specific packages import org.odmg.*; import ojb.odmg.*; public class StorefrontServiceImpl implements IStorefrontService{ // Implementation-specific references Implementation odmg = null; Database db = null; /** * Create the service, which includes initializing the persistence * framework. */ public StorefrontServiceImpl( ) throws DatastoreException { super( ); init( ); } /** * Return a list of items that are featured. */ public List getFeaturedItems( ) throws DatastoreException { // Start a transaction Transaction tx = odmg.newTransaction( ); tx.begin( ); List results = null; try{ OQLQuery query = odmg.newOQLQuery( ); // Set the OQL select statement query.create( "select featuredItems from " + ItemBO.class.getName( ) ); results = (List)query.execute( ); tx.commit( ); }catch( Exception ex ){ // Roll back the transaction tx.abort( ); ex.printStackTrace( ); throw DatastoreException.datastoreError(ex); } int size = results.size( ); List items = new ArrayList( ); for( int i = 0; i < size; i++ ){ ItemBO itemBO = (ItemBO)results.get(i); ItemSummaryView newView = new ItemSummaryView( ); newView.setId( itemBO.getId().toString( ) ); newView.setName( itemBO.getDisplayLabel( ) ); newView.setUnitPrice( itemBO.getBasePrice( ) ); newView.setSmallImageURL( itemBO.getSmallImageURL( ) ); newView.setProductFeature( itemBO.getFeature1( ) ); items.add( newView ); } return items; } /** * Return a detailed view of an item based on the itemId argument. */ public ItemDetailView getItemDetailView( String itemId ) throws DatastoreException{ // Start a transaction Transaction tx = odmg.newTransaction( ); tx.begin( ); List results = null; try{

OQLQuery query = odmg.newOQLQuery(

); );

// Set the OQL select statement String queryStr = "select item from " + ItemBO.class.getName( queryStr += " where id = $1"; query.create(queryStr); query.bind(itemId); // Execute the transaction results = (List)query.execute( ); tx.commit( ); }catch( Exception ex ){ // Roll back the transaction tx.abort( ); ex.printStackTrace( ); throw DatastoreException.datastoreError(ex); } if (results.isEmpty( ) ){ throw DatastoreException.objectNotFound( } ItemBO itemBO = (ItemBO)results.get(0); // Build a ValueObject for the Item ItemDetailView view = new ItemDetailView( ); view.setId( itemBO.getId().toString( ) ); view.setDescription( itemBO.getDescription( ) ); view.setLargeImageURL( itemBO.getLargeImageURL( ) ); view.setName( itemBO.getDisplayLabel( ) ); view.setProductFeature( itemBO.getFeature1( ) ); view.setUnitPrice( itemBO.getBasePrice( ) ); view.setTimeCreated( new Timestamp(System.currentTimeMillis( view.setModelNumber( itemBO.getModelNumber( ) ); return view; } );

) ));

/** * Authenticate the user's credentials and either return a UserView for the * user or throw one of the security exceptions. */ public UserView authenticate(String email, String password) throws InvalidLoginException,ExpiredPasswordException,AccountLockedException, DatastoreException { // Start a transaction Transaction tx = odmg.newTransaction( tx.begin( ); );

// Query the database for a user that matches the credentials List results = null; try{ OQLQuery query = odmg.newOQLQuery( ); // Set the OQL select statement String queryStr = "select customer from " + CustomerBO.class.getName( ); queryStr += " where email = $1 and password = $2"; query.create(queryStr); // Bind the input parameters query.bind( email ); query.bind( password ); // Retrieve the results and commit the transaction results = (List)query.execute( ); tx.commit( ); }catch( Exception ex ){

// Roll back the transaction tx.abort( ); ex.printStackTrace( ); throw DatastoreException.datastoreError(ex); } // If no results were found, must be an invalid login attempt if ( results.isEmpty( ) ){ throw new InvalidLoginException( ); } // Should only be a single customer that matches the parameters CustomerBO customer = (CustomerBO)results.get(0); // Make sure the account is not locked String accountStatusCode = customer.getAccountStatus( ); if ( accountStatusCode != null && accountStatusCode.equals( "L" ) ){ throw new AccountLockedException( ); } // Populate the value object from the Customer business object UserView userView = new UserView( ); userView.setId( customer.getId().toString( ) ); userView.setFirstName( customer.getFirstName( ) ); userView.setLastName( customer.getLastName( ) ); userView.setEmailAddress( customer.getEmail( ) ); userView.setCreditStatus( customer.getCreditStatus( ) ); return userView; } /** * Log the user out of the system. */ public void logout(String email){ // Do nothing with it right now, but might want to log it for auditing reasons } /** * Opens the database and prepares it for transactions */ private void init( ) throws DatastoreException { // Get odmg facade instance odmg = OJB.getInstance( ); db = odmg.newDatabase( ); // Open database try{ db.open("repository.xml", Database.OPEN_READ_WRITE); }catch( Exception ex ){ throw DatastoreException.datastoreError(ex); } } }
L'implementazione del servizio presenta tutti i metodi richiesti dell'interfaccia IStorefrontService. Dal momento che IStorefrontService estende l'interfaccia IAuthentication, anche la classe StorefrontServiceImpl deve implementare i metodi per la sicurezza. Si noti che di nuovo l'implementazione non ha alcuna consapevolezza del framework Struts o dei web container in generale. Questo permette la riusabilit per numerosi tipi di applicazioni differenti, cosa che all'inizio del capitolo ci eravamo posti come obiettivo. Si detto prima che si dovranno invocare alcuni metodi del framework OJB cos che l'XML che provvede alla mappatura possa essere letto e le connessioni al database possano essere effettuate con prontezza. Questa inizializzazione mostrata nel metodo init() nell'Esempio 6-8. Quando chiamato il costruttore di questa implementazione, il file XML caricato in memoria e letto. Il framework di persistenza pronto alle chiamate in seguito a un completamento corretto delle operazioni del costruttore. Il costruttore deve essere invocato dal client nel caso di Storefront si utilizzer una classe factory, che sar anche un PlugIn per Struts, per la determinazione del servizio Storefront da inizializzare. La factory illustrata nell' Esempio 6-9.

Esempio 6-9. La classe StorefrontServiceFactory

package com.oreilly.struts.storefront.service; import import import import import import javax.servlet.ServletContext; javax.servlet.ServletException; org.apache.struts.action.PlugIn; org.apache.struts.action.ActionServlet; org.apache.struts.config.ApplicationConfig; com.oreilly.struts.storefront.framework.util.IConstants;

/** * A factory for creating Storefront service implementations. The specific * service to instantiate is determined from the initialization parameter * of the ServiceContext. Otherwise, a default implementation is used. * */ public class StorefrontServiceFactory implements IStorefrontServiceFactory,PlugIn{ // Hold on to the servlet for the destroy method private ActionServlet servlet = null; // The default is to use the debug implementation String serviceClassname = "com.oreilly.struts.storefront.service.StorefrontDebugServiceImpl"; public IStorefrontService createService( ) throws ClassNotFoundException, IllegalAccessException, InstantiationException { String className = servlet.getInitParameter( IConstants.SERVICE_CLASS_KEY ); if (className != null ){ serviceClassname = className; } return (IStorefrontService)Class.forName(className).newInstance( } public void init(ActionServlet servlet, ApplicationConfig config) throws ServletException{ // Store the servlet for later this.servlet = servlet; /* Store the factory for the application. Any Storefront service factory * must either store itself in the ServletContext at this key or extend * this class and don't override this method. The Storefront application * assumes that a factory class that implements the IStorefrontServiceFactory * is stored at the proper key in the ServletContext. */ servlet.getServletContext( ).setAttribute( IConstants.SERVICE_FACTORY_KEY, this ); } public void destroy( ){ // Do nothing for now } }
La classe StorefrontServiceFactory dell'Esempio 6-9 legge un parametro di inizializzazione dal file web.xml, che riporta il nome della classe che implementa IStorefrontService da istanziare. Se non possiede in initparam per questo valore, viene creata una classe di implementazione predefinita (in questo caso, l'implementazione di debug). Dal momento che la classe factory implementa l'interfaccia PlugIn, sar istanziata all'avvio e sar invocato il metodo init(). Il metodo init() conserva un'istanza della factoryall'interno dello scope di applicazione, dove in seguito pu essere recuperato. Per creare un'istanza del servizio Storefront, un client deve recuperare la factory dal

);

ServletContext e invocare il metodocreateService(). Il metodo createService() invoca a suavolta il


costruttore senza argomento su qualunque classe di implementazione sia stata configurata. Il passo finale consiste nell'invocazione dell'interfaccia service di Storefront da una classe Action. L'Esempio 6-10 riporta i metodi di maggior rilievo.
Esempio 6-10. La LoginAction dell'applicazione Storefront

package com.oreilly.struts.storefront.security; import import import import import import import import import java.util.Locale; javax.servlet.http.*; org.apache.struts.action.*; com.oreilly.struts.storefront.customer.view.UserView; com.oreilly.struts.storefront.framework.exceptions.BaseException; com.oreilly.struts.storefront.framework.UserContainer; com.oreilly.struts.storefront.framework.StorefrontBaseAction; com.oreilly.struts.storefront.framework.util.IConstants; com.oreilly.struts.storefront.service.IStorefrontService;

/** * Implements the logic to authenticate a user for the Storefront application. */ public class LoginAction extends StorefrontBaseAction{ /** * Called by the controller when a user attempts to log in to the * Storefront application. */ public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response ) throws Exception{ // Get the user's login name and password. They should have already // been validated by the ActionForm. String email = ((LoginForm)form).getEmail( ); String password = ((LoginForm)form).getPassword( ); // Log in through the security service IStorefrontService serviceImpl = getStorefrontService( ); UserView userView = serviceImpl.authenticate(email, password); UserContainer existingContainer = null; HttpSession session = request.getSession(false); if ( session != null ){ existingContainer = getUserContainer(request); session.invalidate( ); }else{ existingContainer = new UserContainer( ); } // Create a new session for the user session = request.getSession(true); existingContainer.setUserView(userView); session.setAttribute(IConstants.USER_CONTAINER_KEY, existingContainer); return mapping.findForward(IConstants.SUCCESS_KEY); } }
La prima linea evidenziata invoca il metodo getStorefrontService(). Questo metodo collocato nella superclasse chiamata StorefrontBaseAction perch ogni classe Action dovr avere la possibilit di invocarlo. L'implementazione del metodo getStorefrontService() ritira la factory e invoca a sua volta il metodo createService(). Nell'Esempio 6-11 mostrata la classe StorefrontBaseAction, che include il metodo getStorefrontService().

Esempio 6 11. La classe StorefrontBaseAction

package com.oreilly.struts.storefront.framework; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Iterator; import javax.servlet.http.*; import org.apache.struts.action.*; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.oreilly.struts.storefront.framework.util.IConstants; import com.oreilly.struts.storefront.framework.exceptions.*; import com.oreilly.struts.storefront.service.IStorefrontService; import com.oreilly.struts.storefront.service.IStorefrontServiceFactory; /** * An abstract Action class that all Storefront action classes should * extend. */ abstract public class StorefrontBaseAction extends Action{ Log log = LogFactory.getLog( this.getClass( ) ); protected IStorefrontService getStorefrontService( ){ IStorefrontServiceFactory factory = (IStorefrontServiceFactory) getApplicationObject( IConstants.SERVICE_FACTORY_KEY ); IStorefrontService service = null; try{ service = factory.createService( ); }catch( Exception ex ){ log.error( "Problem creating the Storefront Service", ex ); } return service; } /** * Retrieve a session object based on the request and the attribute name. */ protected Object getSessionObject(HttpServletRequest req, String attrName) { Object sessionObj = null; // Don't create a session if one isn't already present HttpSession session = req.getSession(true); sessionObj = session.getAttribute(attrName); return sessionObj; } /** * Return the instance of the ApplicationContainer object. */ protected ApplicationContainer getApplicationContainer( ) { return (ApplicationContainer) getApplicationObject(IConstants.APPLICATION_CONTAINER_KEY); } /** * Retrieve the UserContainer for the user tier to the request. */ protected UserContainer getUserContainer(HttpServletRequest request) { UserContainer userContainer = (UserContainer)getSessionObject(request, IConstants.USER_CONTAINER_KEY); // Create a UserContainer for the user if it doesn't exist already

if(userContainer == null) { userContainer = new UserContainer( ); userContainer.setLocale(request.getLocale( )); HttpSession session = request.getSession( ); session.setAttribute(IConstants.USER_CONTAINER_KEY, userContainer); } return userContainer; } /** * Retrieve an object from the application scope by its name. This is * a convenience method. */ protected Object getApplicationObject(String attrName) { return servlet.getServletContext( ).getAttribute(attrName); } }
Il metodo getApplicationObject() soltanto un metodo utile alle classi Action di Storefront;invoca il metodo getAttribute() sull'oggetto ServletContext. Infine la seconda linea evidenziata nell'Esempio 6-10 invoca un metodo di servizio sull'implementazione. Viene invocato un metodo authenticate() ed restituito un oggetto valore chiamato UserView per Action:

UserView userView = serviceImpl.authenticate(email, password);


Quest'oggetto posto all'interno di un oggetto di sessione riportato da Action. Se non si trova nessun utente con un corrispondente insieme di credenziali il metodo authenticate() lancer un'eccezione InvalidLoginException. Si noti che la classe Action usa l'interfaccia IStorefrontService, non l'oggetto dell'implementazione. Come si detto, importante impedire che implementazioni alternative abbiano un effetto turbativo sulle classi Action.

6.5.8 Conclusioni
n questo capitolo si sono trattati molti argomenti importanti e potrebbe addirittura disorientare se si nuovi ai meccanismi della persistenza. Alcuni dei prossimi capitoli partiranno dal presupposto che questi argomenti siano ormai acquisiti, per cui importante aver compreso bene quanto detto fin qui prima di procedere oltre.

CAPITOLO 7

I componenti view di Struts

In questo capitolo saranno introdotti i componenti che costituiscono lo strato view di Struts. L'infrastruttura utilizza i componenti di view per la presentazione del contenuto dinamico al client. Basati soprattutto sulle Java Server Pages, i componenti offrono un supporto per l'internazionalizzazione, l'accettazione dell'input dell'utente, la validazione e la gestione degli errori, rendendo possibile agli sviluppatori di concentrarsi sui requisiti alla base dell'applicazione. Questo capitolo conclude la discussione sul modo in cui Struts implementa le lince guida del Model-View-Controller (MVC). Verr anche trattata brevemente la specifica delle future JavaServer Faces. Anche se queste specifiche sono in una fase iniziale di scrittura, probabile che in futuro abbiano un certo impatto sul framework Struts.

7.1 Che cosa una view?


In generale si dice view (vista) la rappresentazione di un modello di dominio tramite un'interfaccia utente. Come precedentemente riportato nel Capitolo 5, il modello di dominio contiene le entit business che conservano lo stato di un'applicazione. Metaforicamente, una view una finestra che i client possono usare per vedere lo stato del modello e la prospettiva pu essere diversa a seconda della finestra da cui il client sta guardando. Per esempio, nell'applicazione Storefront, la pagina principale mostra un set di articoli rappresentati nel catalogo. Non mostra tutte le informazioni su qualsiasi articolo ma solo una piccola quantit di queste. Questa vista ridotta utilizzata in Storefront laddove le informazioni sulla merce non devono essere complete, come nella pagina principale (Figura 7-1).

Figura 7-1. La pagina principale di Storefront rappresenta una prospettiva del modello.

Quando un utente seleziona uno degli oggetti in vendita vengono mostrati anche i dettagli. L'utente sta guardando ancora lo stesso modello di business, ma la view diversa. La Figura 7-1 illustra questa vista alternativa del modello.

Figura 7-2. Una vista alternativa del modello Storefront.

Per questa pagina necessaria una visione maggiormente dettagliata del modello di business: potrebbero infatti esserci pagine JSP differenti, immagini, contenuti multimediali e altri componenti. Queste due diverse prospettive sono di fatto due punti di vista diversi dello stesso modello. Dal momento che i business object non possiedono un modo naturale di rappresentazione visiva, sta ai componenti di rappresentazione (view) il compito di presentare le informazioni del modello di dominio ai client. Questa presentazione potrebbe essere nella forma di XML e XSLT, messaggi SOAP destinati a un servizio web client oppure, come in Storefront, HTML interpretato da un browser. Un tipo diverso di client, come un apparato wireless, potrebbe considerare un insieme completamente diverso di view e continuare a usare lo stesso modello. Il modello utilizzato per rappresentare lo stato di un'applicazione, mentre le view servono a presentarlo al client, sia totalmente che parzialmente. Sebbene l'applicazione Storefront sia un'applicazione B2C, possibile avere delle applicazioni B2B che usano lo stesso modello per rendere disponibili ai partner le funzionalit che riguardano gli ordini degli articoli. Finch si mantiene una adeguata separazione tra modello e livello di presentazione si pu creare qualsiasi numero di view per tutti i client che si desidera sulla base dello stesso modello di dominio.

7.1.1 Utilizzo di view nel framework Struts


Generalmente le View in Struts sono assemblate usando le pagine JSP. Altri approcci e infrastrutture possono essere disponibili per compiere le stesse azioni ma le JSP sono la tecnologia maggiormente diffusa nella comunit Struts. Vi sono anche altri componenti che possono essere utilizzati per la presentazione delle view, ad esempio: documenti HTML librerie di tag personalizzati JSP JavaScript e fogli di stile file multimediali resource bundle di messaggi classi ActionForm

7.1.1.1 Documenti HTML


Anche se i documenti HTML generano solo contenuto statico, possono comunque essere usati in un'applicazione Struts. Ovviamente, non potranno interpretare dati dinamici, ma possono essere un valido aiuto quando la view statica e non necessita delle capacit dinamiche delle JSP. Per esempio, una pagina di login potrebbe essere costituita soltanto da un documento che invoca un'azione login di Struts. Tuttavia, per trarre vantaggio dalla maggior parte degli automatismi di Struts, in special modo quelli relativi ai tag personalizzati, si dovranno usare le JSP e non il semplice HTML.

7.1.1.2 I tag personalizzati JSP


I tag personalizzati delle JSP possono rivestire un ruolo importante all'interno di un'applicazione Struts. Anche se non esplicitamente richiesto che le applicazioni li usino, in certi casi particolari, senza il loro impiego, la programmazione sarebbe molto complessa. I custom tag manterranno la loro importanza anche se versioni future di Struts renderanno pi semplice l'utilizzo di tecnologie alternative.

7.1.1.3 JavaScript e fogli di stile


Struts non impedisce l'impiego di JavaScript in un'applicazione. Al contrario, offre all'interno delle librerie di tag alcune funzionalit per facilitare l'uso di JavaScript. Il supporto dato a questo linguaggio all'interno dei tag personalizzati discusso nei dettagli al Capitolo 8. Struts non impedisce neanche l'uso di fogli di stile. Si possono includere in una JSP degli stylesheet e saranno interpretati dai browser esattamente come una pagina HTML. Si usano i fogli di stile per permettere ai web designer un controllo migliore sull'aspetto del sito web. Caratteristiche quali la dimensione e il colore del carattere e altre componenti dell'aspetto esteriore possono essere cambiate in una determinata parte centrale ed essere diffusi nell'intero sito.

7.1.1.4 File multimediali


I file multimediali si usano in tutte le applicazioni web; pu trattarsi dei tipi di file sottoelencati ma anche di altre tipologie:

Immagini (.gif, .jpg, etc.) Audio (.wav, .mp3, etc.) Video (.avi, .mpg, etc.) Le immagini sono con buona probabilit i file multimediali maggiormente usati. Per applicazioni B2B sono invece prevalenti file audio e anche video (ad esempio per illustrare disegni CAD e specifiche). Struts supporta l'utilizzo di file multimediali all'interno di un'applicazione. Questo supporto si ottiene soprattutto attraverso l'impiego di tag personalizzati ma possibile usare anche l'HTML per questo tipo di risorse. Ci sono alcuni problemi relativi alle immagini con i tag personalizzati. Le differenze tra patti relativi e assoluti potrebbero rendere impossibile la visualizzazione di alcune immagini. Il miglior approccio consiste nel deci ere se usare path relativi o assoluti ed essere poi il pi d possibile coerenti con la propria decisione.

7.1.1.5 Resource bundle di messaggi


I resource bundle di messaggi sono un componente che nelle applicazioni di Struts ha una grande importanza. Infatti offrono mezzi per il supporto della localizzazione e contribuiscono anche a ridurre i tempi di manutenzione e la ridondanza in alcune applicazioni. Ad esempio, si supponga che la propria web application utilizzi certe etichette di testo o messaggi in pi locazioni. Invece di inserire stabilmente nel codice direttamente la stringa in tutte le pagine, si pu specificarla nel bundle e recuperarla utilizzando uno dei tag personalizzati, cos, se il messaggio o il testo successivo cambiano, la modifica pu essere effettuata in una sola parte del codice, riducendo il tempo dell'intervento. Alcuni sviluppatori considerano i resorurce bundle come appartenenti al modello piuttosto che allo strato view. Questo accade perch i dati dell'applicazione (nella forma di messaggi) sono conservati all'interno dei resource bundle. Tuttavia dal momento che anche i bundle contengono stringhe ed etichette per i campi di testo, le etichette per le checkbox, i titoli delle pagine e cos via, entrambe le argomentazioni sono valide.

7.1.2 Utilizzo dei JavaBeans nei componenti dello strato view


Potrebbe sembrare strano parlare dei JavaBeans in un capitolo dedicato allo strato view. Tuttavia, dal momento che i JavaBeans costituiscono una parte importante del modo di utilizzo del modello dei dati all'interno dell'infrastruttura, vale la pena di dare una rapida occhiata anche a questo argomento.

7.1.2.1 Un ripasso veloce


JavaBeans un modello a componenti portabile e indipendente dalla piattaforma, scritto in Java. L'architettura dei JavaBeans permette agli sviluppatori di creare componenti riutilizzabili in qualsiasi piattaforma supporti una JVM. Il modello dei JavaBeans supporta propriet, eventi, metodi e persistenza. Struts, e in generale le web application scritte in Java, impiegano solo una minima parte delle capacit definite nelle specifiche dei JavaBeans. Questi, in applicazioni basate su Struts, sono utilizzati come comuni oggetti Java ma necessario che seguano alcune linee guida: Devono fornire un costruttore di default. Devono offrire sia un metodo get<PropertyName> sia un set<PropertyName> per qualsiasi propriet definita all'interno del bean. Per quanto concerne le propriet booleane, se presente, il metodo is<PropertyName> sar usato per restituire il valore della propriet. Per quanto riguarda invece le propriet indicizzate, dove definita una propriet come <PropertyElement>[], dovrebbero essere presenti anche i metodi get<PropertyName>(int a) e set<PropertyName>(int a, PropertyElement b). Queste linee guida sono necessarie perch i JavaBeans siano presi in considerazione dal framework durante l'esecuzione.

Uno dei trabocchetti in cui gli sviluppatori cadono pi frequentemente quando impiegano i JavaBeans consiste nell'impiego di un valore tipo di ritorno diverso dal tipo del parametro. Se si crea in un JavaBean un metodo che passa come argomento una String:

public void setDescription( String newDescription );


si deve avere un metodo corrispondente get che restituisca lo stesso valore:

public String getDescription(

Se il tipo restituito noi uguale al tipo del parametro, Struts potrebbe non riconoscerlo come bean, e con buone probabilit si otterrebbe un errore del tipo: "No getter method could be found" oppure "No setter method could be found" per il nome della propriet.

);

7.1.2.2 Come le applicazioni Struts usano i JavaBean


In un'architettura rigorosamente MVC, come mostrato dalla Figura 7-3, lo strato view ottiene gli aggiornamenti direttamente dal modello quando riceve la notifica che qualcosa all'interno del modello cambiato. Con le web application, questa notifica non possibile (o almeno difficile da implementare). Solitamente sta al client inviare una richiesta al controller per un aggiornamento dello strato view; in altri termini, il client che "tira a s" lo strato view, piuttosto che il model che "spinge verso" la view i cambiamenti.

Figura 7-3. La vista interroga il modello circa le informazioni sullo stato

7.1.2.3 Il pattern Data Transfer Object


Il Capitolo 6 ha trattato un approccio alla costruzione dei componenti del modello di un'applicazione Struts. L'unico aspetto che stato intenzionalmente trascurato il modo in cui lo strato view effettivamente acceda ai dati del modello. Per comprendere come ci avvenga, sar utile comprendere il pattern Data Transfer Object (DTO), cui talvolta ci si riferisce come pattern Value Object o Replicate Object. Il pattern DTO utilizzato con una certa frequenza nelle applicazioni J2EE, dove i componenti distribuiti che operano chiamate remote possono risentire negativamente dal punto di vista delle prestazioni per le troppe invocazioni remote. Viene utilizzato anche in altre tecnologie, sotto nomi e forme diversi. Un DTO una visione a grana grossa di informazioni normalmente disponibili a grana fine. Pu mettere insieme parecchi attributi da una o pi entit e conservarli all'interno di una istanza JavaBeans. Questa istanza pu poi essere passata all'applicazione locale oppure serializzata e inviata sulla rete. I client potranno in tal modo recuperare le informazioni pertinenti al modello da un oggetto locale senza impatti negativi sulle prestazioni. Il DTO di solito non offre n business logic, n validazione; fornisce solo un accesso alle propriet del bean. Certa documentazione a riguardo di questo pattern suggerisce che il bean dovrebbe essere immutabile, per ribadire che l'oggetto locale e che i cambiamenti non si rifletteranno nel sistema. Tuttavia questa affermazione potrebbe causare dei problemi, dato che le specifiche relative ai JavaBeans richiedono l'implementazione dei metodi get<PropertyName> e set<PropertyName> per tutte le propriet private. Sta allo sviluppatore determinare la maniera migliore di gestire i cambiamenti del DTO sulla base delle proprie esigenze. I DTO fanno effettivamente parte del modello: sono in fondo solo copie locali ed eventualmente immutabili dei business object. All'interno di Struts sono utilizzati dallo strato view per trasportare a destinazione i dati di modello che sono elaborati insieme alle informazioni statiche della pagina.

7.2 Che cosa sono le ActionForm?


Quasi tutte le web application devono poter accettare input da parte degli utenti. Nome utente e password, informazioni sulla carta di credito, dati di fatturazione e per la spedizione, e via dicendo, costituiscono alcuni esempi. L'HTML mette a disposizione i componenti necessari per presentare i campi di input all'interno di un browser, ivi incluse le gabbie di testo,

i pulsanti, le checkbox e altro. Quando si costruiscono questi tipi di pagine, si devono annidare i componenti dell'input all'interno di elementi di un form HTML. L'Esempio 7-1 illustra una pagina di sign-in molto semplice, simile a quella utilizzata nell'applicazione Storefront.
Esempio 7 1. Una semplice pagina di sign-in

<html> <head> <title>Esempio 7-1. OReilly Struts Book</title> <link rel="stylesheet" href="stylesheets/main.css" type="text/css"> </head> <body> <form method="post" action="/action/signin"> <!-- The Tabella layout for the email and password fields --> <Tabella BORDER="0" cellspacing="0" cellpadding="0"> <tr> <td>Email: </td> <td>&nbsp; </td> <td> <input type="text" name="email" size="20" maxlength="20"/> </td> </tr> <tr> <td>Password:</td> <td>&nbsp; </td> <td class="alignformslist"> <input type="text" name="password" size="20" maxlength="25"/> </td> </tr> <!-- The Tabella layout for the signin button --> <Tabella width="250" border="0"> <tr> <td> <input type="submit" name="Submit" value="Signin" class="Buttons"> </td> </tr> </Tabella> </form> </body> </html>
Quando l'utente preme il pulsante Signin nel form HTML dell'Esempio 7-1, i valori all'interno del campo sono trasmessi con la richiesta HTTP. L'applicazione server pu recuperare i dati inseriti, validarli in seguito trasmetterli a un altro componente dell'applicazione dove avviene l'autenticazione vera e propria. Se i dati in ingresso non vengono validati, l'applicazione li dovrebbe trasmettere indietro insieme a un messaggio d'errore che spieghi il fallimento del login. Sarebbe un compito arduo gestire manualmente tutte queste funzionalit, dal recupero dei valori all'esecuzione della validazione, alla trasmissione di messaggi d'errore in caso di fallimento. Questo tipo di azioni sono compiute in molte parti di una web application e sarebbe bello che a prendersene carico fosse il framework, che poi si occupi anche della possibilit di riutilizzarle in altre applicazioni. Per fortuna Struts offre questa funzionalit e gestir questi compiti per conto dell'applicazione. Il framework Struts si basa sulla classe org.apache.struts.action.ActionForm come componente chiave per gestire questi compiti. La classe ActionForm impiegata per catturare i dati in ingresso da un form HTML e trasferirli alla classe Action. Dal momento che gli utenti spesso inseriscono dati non validi, le web application necessitano di un modo per conservare i dati temporaneamente per poterli poi inviare di nuovo quando si verifica un errore. In questo senso la classe ActionForm agisce da buffer per mantenere lo stato dei dati inviati dall'utente mentre sono validati. La classe ActionForm opera anche come "firewall" per la propria applicazione dal momento che contribuisce a tenere dati in ingresso sospetti o non validi fuori dallo strato applicativo fino alla loro effettiva validazione ad opera delle regole definite. In ultimo, quando i dati sono restituiti dal livello applicativo, pu essere riempita una particolare ActionForm che pu essere impiegata da una pagina JSP per effettuare la visualizzazione dei campi di input di un modulo HTML.

Questo permette ai form HTML di utilizzare una coerenza maggiore, dal momento che i dati sono sempre estratti da ActionForm e non da JavaBeans differenti. Quando i dati inseriti dall'utente passano la validazione dell'input, ActionForm trasmesso nel metodo execute() della classe Action. Da quel punto i dati possono poi essere recuperati a partire da ActionForm ed essere trasmessi a livello applicativo. Dal momento che ActionForm importa i propri package dall'API Servlet, non si dovrebbe passare l'ActionForm allo strato applicativo, dal momento che si assocerebbero i metodi dell'applicazione allo strato dell'applicazione stessa e la riusabilit dei componenti dello strato applicativo sarebbe pi difficoltosa. I dati all'interno dell'ActionForm dovrebbero essere trasferiti a un oggetto all'interno del modello di dominio. Un approccio comunemente impiegato consiste nel creare un DTO e nel popolarlo con i dati dell'ActionForm. Non si dovr dichiarare una ActionForm per ogni form HTML presente nella propria applicazione. La stessa ActionForm pu essere associata con uno o pi mapping di azioni. Questo significa che possono essere distribuite in pi form HTML. Per esempio, se si avesse un interfaccia di tipo wizard dove alcuni dati sono inseriti e copiati su pi pagine, si potrebbe impiegare una sola ActionForm per raccogliere tutti questi dati.

7.2.1 ActionForm e scope


Le ActionForms possono avere due differenti livelli di scope: request e session. Se si usa lo scope di request, ActionForm disponibile solo fino alla fine del ciclo request/response. Una volta che la risposta stata trasmessa al client, ActionForm e i dati al suo interno non sono pi accessibili. Se si devono mantenere i dati del form per un periodo maggiore di quello di una singola richiesta, si pu configurare ActionForms in modo da avere uno scope di sessione. Questo potrebbe essere necessario se la propria applicazione cattura dati da pi pagine, come ad esempio una serie di finestre di dialogo di un'applicazione wizard. Una ActionForms che sia stata configurata con uno scope di sessione rimarr in sessione finch non sia rimossa o sostituita con un'altro oggetto oppure fino a che la sessione non sia scaduta. Il framework non contiene una caratteristica interna per l'eliminazione di oggetti ActionForms con scope di sessione. Come ogni altro oggetto posto nella classe HttpSession, sta all'applicazione il compito di effettuare una "ripulitura" periodica delle risorse qui conservate. Questo sottilmente diverso da quello che succede con gli oggetti posti nello scope della request, dal momento che una volta che la request terminata non possono essere referenziati oltre e per questo motivo possono essere recuperati dal garbage collector. A meno che non sia necessario mantenere i dati del form per pi richieste, si dovrebbe usare lo scope di request per i propri oggetti ActionForms. Se non si specifica l'attributo scope per la mappatura di action, ActionForm assumer come predefinito lo scope di sessione. Per cautelarsi, si dovrebbe sempre esplicitamente specificare lo scope di ActionForm. Per sapere come specificare lo scope di un'elemento action si veda "Il DTD di configurazione di Struts" nel Capitolo 4. Quando il controller riceve una request, tenta di riciclare un'istanza di tipo ActionForm o dalla richiesta o dalla sessione, a seconda del tipo di scope che ActionForm possiede nell'elemento action. Se non sono travate istanze, ne viene creata una nuova.

7.2.2 Il ciclo di vita di una ActionForm


La sezione "Utilizzare le ActionForm. di Struts" nel Capitolo 3 descrive le mosse intraprese dal framework quando ActionForm viene utilizzata da un'applicazione. Da queste mosse, diventa facile raffigurarsi il ciclo di vita di una ActionForm. La Figura 7-4 illustra i passi principali, intrapresi dal framework, che hanno un qualche effetto su ActionForm.

Figura 7-4. Il ciclo di vita di una ActionForm

La Figura 7-4 mostra soltanto i passi attinenti a una ActionForm, non tutti quelli cui una request va incontro durante la sua l'elaborazione. Si noti che, qualora una ActionForm rilevi uno o pi errori di validazione, compie un forward verso la risorsa identificata nell'attributo input. I dati inviati insieme alla richiesta sono lasciati in ActionForm perch questa possa poi usarli per ripopolare i campi HTML.

7.2.3 Creazione di una ActionForm


La classe ActionForm fornita da Struts astratta e si devono creare sottoclassi per raccogliere i dati dai form della propria specifica applicazione. All'interno delle proprie sottoclassi si deve definire una propriet per ciascun campo che si vuole catturare dal form HTML. Ad esempio, supponiamo di voler catturare i campi email e password da un form come quello dell'Esempio 7-1. L'Esempio 7-2 mostra la classe LoginForm dell'applicazione Storefront, che pu essere utilizzata per conservare e validare i campi email e password.
Esempio 7 2. La classe LoginForm conserva i campi email e password

package com.oreilly.struts.storefront.security; import javax.servlet.http.HttpServletRequest; import org.apache.struts.action.*; /** * Form bean for the user signin page. */ public class LoginForm extends ActionForm { private String email = null; private String password = null; public void setEmail(String email) { this.email = email; } public String getEmail( return (this.email); } ) {

public String getPassword( return (this.password); }

) {

public void setPassword(String password) { this.password = password; } /** * Validate the properties that have been sent from the HTTP request, * and return an ActionErrors object that encapsulates any * validation errors that have been found. If no errors are found, return * an empty ActionErrors object. */ public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) { ActionErrors errors = new ActionErrors( ); if( getEmail() == null || getEmail().length( ) < 1 ) { errors.add("email", new ActionError("security.error.email.required")); } if( getPassword() == null || getPassword().length( ) < 1 ){ errors.add("password", new ActionError("security.error.password.required")); } return errors; } public void reset(ActionMapping mapping, HttpServletRequest request) { /** Because this ActionForm should be request-scoped, do nothing here. * The fields will be reset when a new instance is created. We could * have just not overriden the parent reset( ) method, but we did so * to provide an Esempio of the reset( ) method signature. */ } }
Quando si invia il form, viene creata;un'istanza di LoginForm che popolata dai parametri di request. Il framework compie questa azione comparando ogni parametro di richiesta con il corrispondente nome della propriet nella classe ActionForm. ActionForm viene riempita da parametri di request, non da attributi di request. Se si compie un forward da un'azione a un'altra non si pu poi aggiungere un attributo request ed aspettarsi che ActionForm sia da questa popolata. I parametri di request e gli attributi di request sono due risorse separate.

7.2.3.1 Il metodo validate()


RequestProcessor pu invocare il metodo validate() per tutte le richieste. Due sono le condizioni da cui dipende l'invocazione. In primo luogo deve essere configurata una ActionForm per la mappatura delle azioni. Per questo l'attributo name per un elemento action deve corrispondere all'attributo name di uno degli elementi formbean nel file di configurazione. La seconda condizione che deve essere rispettata perch RequestProcessor possa invocare il metodo validate() consiste nel fatto che l'attributo validate nella mappatura delle azioni debba avere un attributo true. La porzione di codice che segue mostra un elemento action che impiega LoginForm dell'Esempio 72 e che ottempera a entrambe le condizioni:

<action path="/signin" type="com.oreilly.struts.storefront.security.LoginAction" scope="request" name="loginForm" validate="true" input="/security/signin.jsp"> <forward name="Success" path="/index.jsp" redirect="true"/> <forward name="Failure" path="/security/signin.jsp" redirect="true"/> </action> Quando invocata l'azione signin, il framework completa un'istanza di una LoginForm per mezzo dei valori contenuti nella richiesta. Dal momento che l'attributo validate ha come valore true, sar invocato il metodo validate() in LoginForm. Anche se l'attributo validate impostato come false, ActionForm sar comunque popolata dalla
richiesta.

Il metodo validate() nella classe ActionForm restituisce semplicemente una stringavuota. Se si desidera validare i dati sottoposti con la richiesta si dovr evitare il metodo validate() nelle proprie sottoclassi di ActionForm, cos come riportato dall'Esempio 72. Il metodo validate() potrebbe restituire un oggetto ActionErrors, a seconda se siano stati rilevati errori di validazione. Potrebbe anche restituire un valore vuoto se non ci sono errori. Il framework controller la sussistenza di entrambe le condizioni, sia valori vuoti che oggetti vuoti del tipo ActionErrors. Questo evita allo sviluppatore di creare un'istanza di ActionErrors quando non ci sono errori. La classe ActionError e la sua classe padre, ActionMessage, saranno esaminate in dettaglio pi oltre in questo capitolo.

7.2.3.2 Il metodo reset( )


Il metodo reset() stato sempre la disperazione per molti degli utenti della comunit di Struts. Infatti quando e perch si deve invocare rimane una questione che ha dato adito a fraintendimenti. Questo non significa che una scelta sia pi corretta di un'altra, ma che molti sviluppatori che cominciano a lavorare con Struts hanno dei preconcetti sbagliati su reset() che riescono a fatica a scrollarsi di dosso. Come illustra la Figura 7-4, il metodo reset() invocato per ciascuna nuova request, senza tener conto dello scope di ActionForm e prima che questo venga popolato dalla richiesta. II metodo stato in origine aggiunto alla classe ActionForm per facilitare l'azzeramento delle propriet booleane, riportandole ai valori predefiniti. Per capire perch queste debbano essere reimpostate ai valori predefiniti ci sar d'aiuto conoscere il il modo in cui il browser elabora le check-box presenti nei forni HTML di immissione dati. Quando un form HTML contiene delle checkbox, soltanto i valori delle checkbox selezionate sono inviati insieme alla request. Quelli che non sono selezionati non sono inclusi nei parametri della request. Il metodo reset() stato aggiunto per consentire alle applicazioni di reimpostare le propriet booleane in ActionForm a false: dal momento che false non incluso nella richiesta, per i valori booleani possibile rimanere ancora allo stato true. Il metodo reset() nell'ActionForm di base non contiene un comportamento predefinito, dal momento che nessuna propriet definita in questa classe astratta. Le applicazioni che estendono la classe ActionForm possono ignorare questo metodo e reimpostare le propriet di ActionForm a qualsiasi stato desiderino. Ci potrebbe comprendere anche l'impostazione delle propriet booleane a true o false, dei valori String a null o a un qualche altro valore di inizializzazione o perfino a creare istanze di altri oggetti su cui una ActionForm pu agire. Il framework creer una nuova istanza a ogni nuova richiesta per una ActionForm che stato configurata con uno scope relativo alle request; per cui non c' una gran necessit di reimpostare i valori a un qualsiasi stato predefinito. Le ActionForm configurate con uno scope di sessione sono tuttavia diverse e questo il caso in cui torna utile il metodo reset().

7.2.4 Dichiarare ActionForm nel file di configurazione di Struts


Una volta creata una classe che estenda ActionForm, necessario configurare la classe nel file di configurazione di Struts. Il primo passo consiste nell'aggiunta di un nuovo elemento form-bean alla sezione del fileform-bean:

<form-beans> <form-bean name="loginForm" type="com.oreilly.struts.storefront.security.LoginForm"/> </form-beans>


Il valore per l'attributo type deve essere un nome completo di classe Java che discende da ActionForm. Una volta definito il proprio form-bean, possibile utilizzarlo in uno o pi ele menti action. Di solito si condivide una ActionForm con parecchie azioni. Per esempio, sisupponga che ci sia un'applicazione di amministrazione che gestisce gli articoli in Storefront. Ci sar la necessit di un form HTML per aggiungere nuovi articoli al sistema. Ci potrebbe essere un'azione createltem. Ci sar anche necessit di un'azione getltemDetail per mostrare i dettagli di un oggetto esistente. Questi form HTML potrebbero sembrare analoghi ma potrebbero essere utilizzati per azioni diverse. Tuttavia, dal momento che contengono le stesse propriet, potrebbero impiegare la stessa ActionForm. Per usare una ActionForm in un'elemento action, si devono specificare alcuni attributi perciascun mapping delle azioni che lo impiega. Questi attributi sono name, scope e validate:

<action path="/signin" input="/security/signin.jsp" name="loginForm" scope="request"

type="com.oreilly.struts.storefront.security.LoginAction" validate="true"> <forward name="Success" path="/index.jsp" redirect="true"/> <forward name="Failure" path="/security/signin.jsp" redirect="true"/> </action> Per maggiori informazioni circa gli attributi dell'elemento action, si veda "Il DTD di configurazione di Struts" nel
Capitolo 4.

7.2.5 Usare una ActionForm in una Action


Una volta configurata una ActionForm per una particolare Action, si possono inserire dei valori al suo interno e riceverli all'interno del metodo execute() come dall'Esempio 7-3.
Esempio 7 3. ActionForm disponibile dall'interno del metodo execute()

public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response ) throws Exception{ // Get the user's login name and password. They should already have // been validated by the ActionForm. String email = ((LoginForm)form).getEmail( ); String password = ((LoginForm)form).getPassword( ); // Log in through the security service. IStorefrontService serviceImpl = getStorefrontService( ); UserView userView = serviceImpl.authenticate(email, password); UserContainer existingContainer = null; HttpSession session = request.getSession(false); if ( session != null ){ existingContainer = getUserContainer(request); session.invalidate( ); }else{ existingContainer = new UserContainer( ); } // Create a new session for the user. session = request.getSession(true); existingContainer.setUserView(userView); session.setAttribute(IConstants.USER_CONTAINER_KEY, existingContainer); return mapping.findForward(IConstants.SUCCESS_KEY); }
Non obbligatorio impiegare una ActionForm per recuperare i dati da un HTML. Anche se se form non si dichiara unaActionForm per un form i dati possono essere sempre ricavati a partire dalla request. Tuttavia l'applicazione dovr essere in grado di gestire il processo di validazione eerrori gli dalla classe Action.

7.2.6 Dichiarare come stringhe le propriet di ActionForm


Tutti i parametri di request inviati dal browser sono stringhe, qualunque sia il tipo in cui il valore in seguito sar mappato in Java. Per esempio, date, ore, valori booleani e altri valori sono tutte stringhe quando sono estratte dalle request e anche quando saranno inviate come risposta all'interno di una pagina HTML dovranno essere riconvertite ancora in stringhe. Per questo, tutte le propriet di ActionForm dove esiste la possibilit che l'input non sia valido dovrebbero essere del tipo String, in modo che i dati possano essere rinviati all'utente nella loro forma originale quando capitano degli errori. Per esempio, poniamo che un utente digiti "12Z" per una propriet che richiede un int oppure Integer; in questo caso non c' la possibilit di conservare "12Z" in una propriet Integer ma si pu conservare in una propriet String fino alla validazione. Questo valore pu anche essere impiegato per visualizzare il campo di input con il valore sbagliato, cos l'utente pu vedere il proprio errore. Questa funzionalit molto importante tanto da essere richiesta spesso anche da programmatori meno esperti.

All request parameters that are sent by the browser are strings. This is true regardless of the type that the value will eventually map to in Java. For Esempio, dates, times, Booleans, and other values all are strings when they are pulled out of the request, and they will be converted into strings when they are written back out to the HTML page. Therefore, all of the ActionForm properties where the input may be invalid should be of type String, so that the data can be displayed back to the user in its original form when an error occurs. For Esempio, say a user types in "12Z" for a property expecting an Integer. There's no way to store "12Z" into an int or Integer property, but you can store it into a String until it can be validated. This value can be used to render the input field with the value, so the user can see his mistake. Even the most inexperienced users have come to expect and look for this functionality.

Le ActionForm non sono il model


Molti programmatori sono disorientati quando apprendono le caratteristiche della classe ActionForm. Anche se questa pu mantenere lo stato di un'applicazione, lo stato che mantiene dovrebbe essere limitato all'input dei dati dell'utente ricevuto dal client e l' ActionForm dovrebbe mantenerlo solo fino al momento della validazione e perci trasferito al livello dell'applicazione. Si gi visto perch importante separare il modello dal livello di presentazione in un'applicazione. I business object possono essere conservati nel loro stato e dovrebbero contenere la business logic dell'applicazione. Dovrebbero essere anche riutilizzabili. Questo insieme di criteri non pu essere rispettato nel caso delle ActionForm. La classe ActionForm legata a un web container e a Struts, dal momento cio che importa i package javax.servlet. Potrebbe essere molto difficile altrimenti portare le classi ActionForm in un tipo diverso di framework, come ad esempio un'applicazione Swing. Le ActionForm sono progettate esclusivamente per catturare i dati HTML immessi da un client, far eseguire una validazione di presentazione e offrire infine un metodo di trasporto per i dati verso il livello applicativo pi persistente. Trasportano inoltre anche i dati dal lato applicativo al livello view. Se si eccettuano questi utilizzi, si dovrebbero mantenere le ActionForm divise dai propri componenti dell'elaborazione.

7.2.7 Utilizzare ActionForm attraverso pi pagine


Molte applicazioni richiedono funzionalit di wizard in cui i dati sono raccolti nel corso di pi pagine. Si pu usare ActionForm anche in questo caso, ma lo scope deve essere impostato a session per assicurare che i dati raccolti da una pagina precedente possano essere presenti attraverso l'intero l'insieme di pagine del wizard. Quando si inseriscono questi tipi di funzionalit nella propria applicazione, si deve prestare attenzione alla validazione dei soli campi gi pronti alla validazione. Per esempio, se dalla pagina 1 si procede alla pagina 2, solo le propriet inserite nella pagina 1 devono essere validate. Particolare attenzione in questa circostanza deve essere posta nell'utilizzo del metodo reset(): ovviamente non il caso di resettare campi di dati gi popolati ma si potrebbe voler resettare campi di dati per la pagina successiva. Seguire queste indicazioni potrebbe complicare non poco il lavoro del programmatore, ma tenere a mente questi princpi pu far risparmiare molto tempo.

7.3 Utilizzo di ActionErrors


Poco fa si visto che il metodo validate() restituisce un oggetto ActionErrors. La classe ActionErrors incapsula uno o pi errori che vengono rilevati dall'applicazione. Ogni problema scoperto rappresentato da un'istanza di org.apache.struts.action.ActionError. Un oggetto ActionErrors ha il proprio scope di richiesta. Dopo che un'istanza stata creata e popolata dal metodo validate(), conservata nella richiesta. In seguito la JSP pu recuperare l'oggetto dalla richiesta ed utilizzare gli oggetti ActionError contenuti al proprio interno per mostrare messaggi di errore all'utente. Il framework Struts include un tag personalizzato JSP che rende molto facile recuperare e mostrare i messaggi di errore. Questo tag, chiamato ErrorsTag, sar analizzato nel Capitolo 8.

Un'istanza di ActionErrors pu essere istanziata nel metodo validate() e pu essere popolata aggiungendovi istanze della classe ActionError. Nel LoginForm dell'Esempio 7-2 avviene proprio questo e si riprende qui lo stesso procedimento per comodit:

public ActionErrors validate(ActionMapping mapping, HttpServletRequest request){ ActionErrors errors = new ActionErrors( ); if( getEmail() == null || getEmail().length( ) < 1 ){ errors.add("email", new ActionError("security.error.email.required"));

} if( getPassword() == null || getPassword().length( errors.add("password", new ActionError("security.error.password.required")); } return errors; } ) < 1 ){

Il metodo validate() in questa porzione di codice compie i controlli necessari sui campi email e password. Se la stringa risultante vuota, si aggiungono degli oggetti ActionError all'istanza ActionError. La classe ActionError contiene parecchi costruttori utili: alcuni di questi sono elencati di seguito.

public public public public

ActionError(String ActionError(String ActionError(String ActionError(String

key); key, Object value0); key, Object value0, Object value1); key, Object[] values);

L'argomento chiave un valore String che corrisponde a una chiave dei resource bundle di un'applicazione. Il tag personalizzato ErrorsTag impiega questo valore per ricercare il messaggio da mostrare all'utente. Gli argomenti restanti sono usati come valori di sostituzione dei parametri per il messaggio. Se si ha un bundle di messaggi cos definito:

global.error.login.requiredfield=The {0} field is required for login


si pu creare un'istanza di un ActionError nel seguente modo:

ActionError error = new ActionError("global.error.login.requiredfield", "Email");


Il messaggio visualizzato all'utente dopo la sostituzione nella stringa "Email" sarebbe:

The Email field is required for login


Se si necessita di strutturare applicazioni secondo I18N, si deve prestare attenzione all'utilizzo di stringhe inserite nel codice come nell'esempio precedente. La stringa "Email" non pu essere facilmente localizzata dal momento che inserita nel codice In questo caso si dovrebbe prendere il valore localizzato dal bundle dell'applicazione e passarlo come argomento del costruttore ActionError. Quando si aggiungono delle istanze della classe ActionError all'oggetto ActionErrors, il primo argomento del metodo add() una propriet che pu essere usata per recuperare una specifica istanza ActionError. Per esempio, se si possiedono dei campi login e password e si desidera visualizzare un certo tipo di messaggio a fianco di ciascun campo corrispondente, si potrebbe scrivere:

errors.add("login", new ActionError("security.error.login.required")); errors.add("password", new ActionError("security.error.password.required"));


Dall'associazione di un nome specifico con ciascun errore si pu recuperare l'errore corrispondente nella pagina JSP con l'uso del tag ErrorsTag discusso nel capitolo successivo. Se invece si desidera mostrare tutti gli errori in cima alla pagina, si pu usare la costante ActionErrors.GLOBAL_ERROR, in questo modo:

errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("security.error.password.required"));

7.3.1 La classe ActionMessage


In Struts 1.1 stata aggiunta una nuova classe per inviare messaggi a un utente. La classe

org.apache.struts.action.ActionMessage funziona nello stesso modo della classe ActionError: di fatto stata aggiunta in qualit di superclasse per la classe ActionError.
Il motivo principale per cui la classe ActionMessage stata aggiunta al framework consiste nel fatto che molti programmatori la utilizzano come mezzo per veicolare messaggi generici o di avvertimento, mentre, come implica il nome stesso, dovrebbe essere ristretta al solo ambito dei messaggi di errore. Per questo era auspicabile l'introduzione di una nuova classe appositamente pensata per utilizzi generici. ActionMessage viene impiegata esattamente allo stesso modo della classe ActionError, fatta eccezione per il fatto che rappresenta un messaggio corrispondente a un evento meno grave di un errore da inviare a un utente. Le istanze di questa classe vengono create allo stesso modo ma sono aggiunte a un oggetto ActionMessages anzich ad un oggetto ActionErrors. Dal momento che ActionError solo un messaggio particolare, estende la classe ActionMessage. Nella Figura 7-5 illustrata la relazione tra queste due classi.

Figura 7-5 ActionErrors specializzazione di ActionMessages

7.3.2 Creazione di ActionError nella classe Action


Una ActionForm non l'unico luogo in cui creare ActionMessages o ActionErrors. Si pu anche crearle in altre parti dell'infrastruttura. Se, ad esempio, una operazione chiamata da una Action genera un'eccezione e si desidera creare un messaggio d'errore che informi l'utente, si pu creare un ActionError a partire dalla classe Action stessa. La classe di Struts Action include le funzionalit di supporto. Quando l'operazione genera un'eccezione, la classe Action la intercetta e compie le azioni appropriate:generalmente torna alla pagina precedente e visualizza per l'utente un messaggio d'errore. Il ritorno allo stato precedente pu essere conseguito mediante la restituzione dell' ActionForward appropriata a patto che la ActionError sia stata inserita nella request prima del forward. Nel Capitolo 10, si apprender come trarre vantaggio della gestione dichiarativa delle eccezioni in Strurs 1.1 e per questo a evitare completamente di maneggiare eccezioni nella classe Action

L'Esempio 7-4 illustra come inserire ActionError nelle richieste impiegando la classe LoginAction.
Esempio 7 4. Creazione di ActionErrors a partire dal metodo execute()

public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response ) throws Exception{

/** * Get the user's email and password, which should already have been * validated by the ActionForm. */ String email = ((LoginForm)form).getEmail( ); String password = ((LoginForm)form).getPassword( ); // Log in through the security service IStorefrontService serviceImpl = getStorefrontService( );

UserView userView = null; try{ userView = serviceImpl.authenticate(email, password); }catch( InvalidLoginException ex ){ ActionErrors errors = new ActionErrors( ); ActionError newError = new ActionError( "security.login.failed" ); errors.add( ActionErrors.GLOBAL_ERROR, newError ); saveErrors( request, errors ); // Return back to the previous state return mapping.findForward( mapping.getInput( ) ); } // Authenticate was successful UserContainer existingContainer = null; HttpSession session = request.getSession(false); if ( session != null ){ existingContainer = getUserContainer(request); session.invalidate( ); }else{ existingContainer = new UserContainer( ); } // Create a new session for the user session = request.getSession(true); existingContainer.setUserView(userView); session.setAttribute(IConstants.USER_CONTAINER_KEY, existingContainer); return mapping.findForward(IConstants.SUCCESS_KEY); } }
Nell'Esempio 7-4, quando generata un'eccezione InvalidLoginException dal metodo authenticate(), l'eccezione viene intercettata ed creato un ActionError. Il metodo saveErrors() esiste nelle classi base di Struts e conserva l'oggetto ActionErrors nella richiesta. Esiste anche un corrispondente metodo saveMessages() per usi analoghi. Una volta che una ActionError o una ActionMessage sono immagazzinate nelle request e il controllo passato a una pagina JSP, pu essere impiegato uno dei lag personalizzati del framework per la visualizzazione del messaggio per l'utente. Questi tag sono introdotti nel capitolo successivo.

7.4 Come implementare la presentation validation


Questo capitolo ha introdotto i metodi di validazione dell'input dei dati introdotti dall'utente nella propria applicazione tramite il metodo validate() della classe ActionForm. Si pu in seguito creare qualsiasi regola si desideri all'interno di questo metodo. Ad esempio, il LoginForm dell'Esempio 7-2 verifica che i campi email e password non siano stringhe vuote. Anche se quest'esempio molto semplice, si noti che qualsiasi cosa pu essere validata. Una regola generale consiste nell'accertarsi che un valore stringa che dovrebbe essere un numero sia di fatto la rappresentazione di un numero valido. La routine validate() per questa regola potrebbe essere simile a quella rappresentata nell' Esempio 7-5.
Esempio 7 5. Regola di validazione di un numero

public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) { ActionErrors errors = new ActionErrors( ); String orderQtyStr = getQuantity( );

if( orderQtyStr == null || orderQtyStr.length( ) < 1 ){ errors.add( ActionErrors.GLOBAL_ERROR, new ActionError( "order.quantity.required" )); } // Validate that the qty entered was in fact a number try{ // Integer.parse was not used because it's not really I18N-safe java.text.Format format = java.text.NumberFormat.getNumberInstance( Number orderQty = (Number)format.parseObject( orderQtyStr ); }catch( Exception ex ){ // The quantity entered by the user was not a valid qty errors.add( ActionErrors.GLOBAL_ERROR, new ActionError( "order.quantity.invalid" )); } return errors; }
Come si pu arguire, le web application spesso necessitano di un controllo sui valori richiesti o di accertare che i dati inseriti siano aderenti a un certo formato oppure siano di un certo tipo. Dal momento che tutti i dati recuperati da una richiesta sono del tipo String, ci si deve assicurare che i dati non vadano a introdurre errori nei componenti dell'elaborazione. Anche se si pu effettuare la validazione tramite programmazione, l'infrastruttura Struts offre un'alternativa esterna ad ActionForm e al metodo validate(). Di fatto la maggior parte delle regole di validazione sono gi delinite, per cui non si deve scrivere alcun codice per queste ultime. Tutto quello che si deve fare configurare in maniera dichiarativa le regole di cui si necessita, in un file XML aggiuntivo. Il Validator di Struts sar affrontato nel Capitolo 11.

);

7.5 Uso della classe DynaActionForm


Con l'uso della classe ActionForm si hanno molti vantaggi rispetto a scrivere da soli o con qualche set di classi di utilit le funzionalit nella classe Action. Dal momento che la classe ActionForm necessaria in quasi tutte le web application e spesso anche pi volte all'interno di una sola di queste, l'impiego del framework riduce di molto il tempo di sviluppo e il proprio livello di frustrazione. Tuttavia esistono alcuni importanti problemi legati all'uso di ActionForm. Il problema maggiore consiste nell'imponente numero di classi che l'uso di ActionForm pu aggiungere a un progetto. Anche se si condividono le definizioni di ActionForm attraverso pi pagine, le classi aggiuntive rendono pi difficoltoso mantenere e gestire un progetto. Si potrebbe pensare infatti di creare una sola ActionForm che contenga al suo interno le propriet relative a t u t t i - i form HTML. Il problema con questo approccio risiede nel fatto che combinare i campi in una classe lo rende un punto critico in un progetto con un certo numero di sviluppatori. Un'altro problema sta nella caratteristica che richiede di definire nell' ActionForm le propriet che devono essere catturate dal form HTML, per cui la classe ActionForm potrebbe essere modificata e ricompilata. Per questi motivi stato aggiunto al framework un nuovo tipo di ActionForm, dinamico per natura e che permette di evitare la creazione di classi concrete ActionForm per la propria applicazione. L' ActionForm dinamica implementata dalla classe di base org.apache.struts.action.DynaActionForm, che estende la classe ActionForm. Ci sono solo tre differenze reali tra le ActionForm per ciascuna applicazione: Le propriet definite da ActionForm Il metodo validate() Il metodo reset() Le propriet per una DynaActionForm sono configurate nel file di configurazione di Struts e saranno illustrate nella sezione successiva. Il metodo reset() viene chiamato esattamente nello stesso istante dell'elaborazione delle richieste, come per una ActionForm standard. L'unica differenza che si ha un controllo minore su quello che pu essere fatto durante l'invocazione del metodo. Tuttavia possibile inserirlo in una sottoclasse DynaActionForm per evitare le azioni di reset o di validazione.

7.5.1 Configurazione di ActionForm dinamiche


Per usare DynaActionForm nella propria applicazione Struts prima necessario aggiungere un elemento formbean al file di configurazione, come con le ActionForms normali. Nelle prime versioni beta di Struts 1.1, la sezione form-bean richiedeva l'impostazione dell'attributo dynamic a true quando si usavano ActionForms dinamiche, ma non pi necessario, perch il framework determiner se la classe specificata nell'attributo type discende dalla classeDynaActionForm. Passando al file di configurazione, la differenza tra una ActionForm normale e una DynaActionForm consiste nell'includere uno o pi elementi form-property affinch il form dinamico abbia le sue peculiari propriet. DynaActionForm impiega internamente una java.util.Map per conservare le coppie di propriet chiave/valore. Gli elementi form-property sono caricati nella Map e divengono le propriet popolate dal framework. Gli attributi per form-bean e form-property sono stati discussi nel Capitolo 4.

L'Esempio 7-6 mostra la configurazione di DynaActionForm nel file di configurazione di Struts.


Esempio 7 6. DynaActionForm deve essere specificata nel file di configurazione di Struts

<form-beans> <form-bean name="loginForm" type="org.apache.struts.action.DynaActionForm"> <!-- Specify the dynamic properties of the form --> <form-property name="email" type="java.lang.String "/> <form-property name="password" type="java.lang.String "/> <!-- You can also set the initial value of a property --> <form-property initial="false" name="rememberMe" type="java.lang.Boolean "/> </form-bean> <form-beans>
Sono le propriet dichiarative a rendere dinamica ActionForm. Durante l'esecuzione il framework crea un'istanza della classe DynaActionForm che rende possibile impostare e recuperare i valori configurati delle propriet; non c' bisogno di effettuare cambiamenti nel codice sorgente, cosa che aumenta potenza e flessibilit. Come delineato nel Capitolo 4 l'elemento form-property permette anche di specificare il valore iniziale per ciascuna propriet. Il framework imposta la propriet a quel valore all'avvio dell'applicazione. Il valore iniziale usato anche quando invocato il metodo reset()per riportare i valori allo stato iniziale. Il metodo reset() diversamente dalla classe ActionForm in cui, in maniera predefinita, reset() non fa nulla in DynaActionForm riporta tutti i valori allo stato originale. Se non si include l'attributo initial per una propriet, a questo verr assegnato un valore predefinito basato sulle convenzioni di programmazione di Java: i numeri verranno reimpostati a zero (0) e le propriet del tipo Object a null.

L'attributo type si aspetta un nome completo di classe Java. Perci si dovranno sempre usare i wrapper Java dei tipi primitivi (ad esempio, java.lang.Boolean per un tipo di propriet booleana, java.lang.Integer per una propriet int e cos via). Tuttavia, come gi spiegato in questo capitolo, si dovrebbero usare solo propriet di tipo String anche con DynaActionForm.

7.5.2 La validazione con DynaActionForm


DynaActionForm non offre alcun comportamento predefinito per il metodo validate(). A meno di non porla come sottoclasse, evitando anche il metodo validate(), non esiste un metodo semplice per la validazione con l'uso di DynaActionForm. Per fortuna il framework fornisce ancora una volta una caratteristica adatta, chiamata Validator. Struts Validator stato creato da David Winterfeldt ed ora disponibile nella distribuzione principale di Struts. Il Validator un'infrastruttura volta fin dall'inizio a interoperare con Struts. Supporta regole di validazione fondamentali come il controllo dei campi richiesti, email, data e ora e altri ancora. Uno dei benefici maggiori sta nell'offrir; molte delle regole di validazione che un'applicazione web deve implementare. Si possono inoltre creare con facilit le proprie regole di validazione. Struts Validator sar affrontato nel Capitolo 11.

7.6 Verso le JavaServer Faces


JavaServer Faces (JSF) una specifica studiata per offrire un insieme standard di tag JSP e classi Java che render pi facile scrivere GUI per applicazioni server Java. Uno dei problemi che JSF sta cercando di risolvere che le tecnologie che Servlet e JSP rappresentano non offrono API abbastanza specifiche per la creazione di GUI per i client; quindi gli sviluppatori devono appoggiarsi massicciamente all'HTML e ad altre alternative per facilitare i cambiamenti di stile:c comportamento per tutte le view. Il meccanismo per la gestione di tutto ci prende moltissimo tempo, pi di quanto serva per sviluppare l'applicazione stessa. Invece di concentrarsi su operazioni e logica di elaborazione, ci vuole un sacco di tempo per la gestione dei controlli di presentazione. JSF si propone di rimediare a questa situazione creando uno standard per definire form HTML complessi e altri elementi delle GUI. In questo modo gli sviluppatori potranno concentrarsi su un singolo componente'del framework. La prima versione di questa infrastruttura include i seguenti obiettivi progettuali: 1. 2. 3. 4. 5. 6. 7. 8. Creare una infrastruttura standard per GUI, che pu essere migliorata da strumenti di sviluppo per rendere pi facile per gli utenti dello strumento la creazione di GUI di alta qualit e la gestione delle connessioni della GUI con l'applicazione e le sue risposte. Definire un insieme di classi Java semplici e leggere per componenti di GUI, componenti di stato ed eventi di input. Queste classi risolveranno i problemi relativi al ciclo vitale delle GUI, in special modo gestendo Io stato della persistenza di un componente per tutto il ciclo vitale della pagina. Offrire un insieme di componenti della GUI inclusi gli elementi di input standard della GUI. Questi componenti saranno derivati da un semplice set di classi di base (delineate nel #2) che possono essere impiegate per definire nuovi componenti. Offrire un modello JavaBean per trasmettere eventi dai controlli della GUI lato client alle applicazioni lato server. Definire API per la validazione dell'input, ivi incluso il supporto per la validazione lato client. Specificare un modello per l'internazionalizzazione e la localizzazione della GUI. Automatizzare la generazione di un output appropriato per il client, mettendo in conto tutti i dati di configurazione del client (versione del browser etc.). Automatizzare la generazione di output che contenga i necessari agganci per il supporto dell'accessibilit.

7.6.1 Cosa implica JSF per Struts?


JSF e Struts potranno interagire abbastanza bene e in seguito gli sviluppatori potranno aggiungere o sostituire le librerie dei tag personalizzati con i componenti delle JSF. Il resto dell'infrastruttura di Struts (sia dei componenti il modello e il controller) non sar toccato dall'architettura JSF. JSF includer anche un modello per gli eventi ma l'architettura sar progettata in modo che gli sviluppatori possano decidere se usare i componenti della GUI con o senza il meccanismo di gestione degli eventi. Dal momento che attualmente non vi alcuna specifica pubblica per le JSF, difficile entrare nei particolari su come entrambe potranno interagire. Tuttavia Craig McClanahan, il creatore del framework Struts, attualmente anche alla guida dei JSR per la definizione delle specifiche delle JavaServer Faces. I commenti di Craig su JSF e Struts danno idea che egli abbia ben presente la materia di discussione. I JSR di JavaServer Faces sono consultabili all'URL http://jcp.org/jsr/detail/127.jsp.

CAPITOLO 8

Librerie di tag JSP personalizzati

Il supporto per le custom action (dette anche tag personalizzati) nelle JSP stato introdotto a partire dalle specifiche JSP 1.1. Questa funzionalit permette agli sviluppatori di estendere i tag disponibili ben oltre le possibilit delle sole JSP. I tag personalizzati sono raggruppati in librerie di tag riutilizzabili. Struts sfrutta la caratteristica delle librerie di tag JSP con l'inclusione di svariate categorie di tag che contribuiscono a rendere lo strato di presentazione pi facilmente gestibile e riutilizzabile. Usando le librerie di tag personalizzati di Struts, gli sviluppatori possono interagire con il resto dell'infrastruttura senza includere codice Java nelle pagine JSP. Questo capitolo presenta una panoramica sulle diverse categorie di tag disponibili all'interno di Struts e spiega come possano rendere lo sviluppo delle applicazioni ancora pi facile. Questo capitolo non vuole rappresentare un riferimento esaustivo per tutti i tag che fanno parte delle librerie di tag in Struts: questo tipo di informazioni pu essere reperito nella guida utente di Struts oppure nei JavaDocs. L'obiettivo di questo capitolo invece quello di esporre i vantaggi dell'impiego delle librerie di tag in Struts e di proporre alcune strategie che possano rendere meno doloroso il passaggio ai tag. Per finire, questo capitolo toccher una nuova iniziativa che circola attraverso il Java Community Process (JCP), intesa a coadiuvare la standardizzazione di alcuni tag custom usati pi comunemente. Questo, a sua volta, render le applicazioni realizzate tramite questi tag ancora pi portabili su diversi container e infrastrutture.

8.1 Una panoramica sui tag personalizzati


Questa sezione propone una breve introduzione sui tag personalizzati delle JSP e sul modo in cui possano aggiungere valore a un'applicazione. I tag personalizzati sono esplicitamente collegati alla tecnologia JavaServer Pages per cui sono usati solo nella realizzazione di web application basate sulle JSP, come quelle realizzate tramite Struts.

8.1.1 Che cosa un tag?


Prima di addentrarci nei particolari dei tag delle JSP importante aver compreso cosa sia un tag in termini generici senza fare subito riferimento ai tag personalizzati JSP o ai tag di Struts. Tra breve anche questi saranno affrontati. Se si possiede familiarit con l'HTML si dovrebbe gi avere una discreta comprensione del concetto di tag. Ne esistono di due tipi base:

Tag senza body Tag con body


I tag senza body sono tag che specificano degli attributi senza apportare contenuti. Hanno la seguente sintassi:

<tagName attributeName="someValue" attributeName2="someValue2"/>


I tag senza body sono spesso impiegati per semplici azioni, come la presentazione di campi HTML o la visualizzazione di immagini. Un esempio di tag senza body potrebbe essere:

<img src="images/struts-power.gif"/>

I tag possono definire determinati attributi predefiniti che offrono al tag stesso informazioni e che possono influenzare il modo in cui il tag compie le proprie azioni. Nei tag img HTML, ad esempio, l'attributo src definisce il path di un'immagine che sar presentata poi dal tag.

I tag con un body hanno un tag iniziale e un corrispondente tag finale. Tra i due tag vi del contenuto (body).Ecco un esempio di sintassi:

<tagName attributeName="someValue" attributeName2="someValue2"> <!-- The Tag body is between the start and end tags --> </tagName>
I tag con body sono usati per le operazioni sul contenuto del body come formattare output HTML e altro ancora. Ecco un altro esempio di HTML:

<html> <!-- The HTML body inside the start and end HTML tags --> </html>
II tag finale deve sempre cominciare con un carattere "/".

8.1.2 Che cosa un tag personalizzato JSP?


Quando il browser legge un file HTML determina come elaborare e gestire i tag contenuti dal file sulla base di un set standard di tag. Il proposito dei tag personalizzati JSP consiste nel dare allo sviluppatore la possibilit di estendere il set di tag che possono essere usati all'interno di una pagina JSP. Con i semplici tag HTML, il browser che contiene la logica per l'elaborazione dei tag e per la presentazione dell'output. Con i tag personalizzati JSP la funzionalit collocata in una classe speciale Java detta tag handler. Il tag handler una classe Java che esegue il comportamento specifico del tag personalizzato. Implementa una interfaccia dei diversi tag personalizzati, a seconda del tipo di tag di cui si necessita. La classe handler ha un accesso a tutte le risorse JSP come ad esempio l'oggetto PageContext e gli oggetti request, response e session. Il tag anche integrato con le informazioni dell'attributo, cos da adeguare il proprio comportamento sulla base dei valori dell'attributo.

8.1.2.1 I vantaggi dei tag personalizzati


I tag personalizzali offrono molti vantaggi rispetto a scrivere scriptlet o codice Java nelle proprie pagine JSP: I tag sono riutilizzabili, e permettono di risparmiare tempo prezioso nello sviluppo e nei test I tag possono essere personalizzati con attributi sia statici che dinamici. I tag hanno accesso a tutti gli oggetti, presenti in una pagina JSP, incluse le variabili request, response e output. I tag possono essere annidati, dando luogo a interazioni pi complesse all'interno di una pagina JSP. I tag semplificano la leggibilit di unq pagina JSP. In generale l'uso dei tag JSP porta avanti i concetti di riusabilit, dal momento che il comportamento implementato in un solo luogo, il tag handler, e non replicato in tutte le pagine JSP.

8.1.3 Che cosa una libreria di tag?


Una libreria di tag un insieme di tag personalizzati JSP raggruppati secondo una logica di impacchettamento. Anche se non un requisito, i tag all'interno di una libreria dovrebbero risolvere problemi di tipo simile. Dal momento che le web application possono includere diverse librerie di tag, non c' la necessit di mettere tutti i tag in una sola libreria. All'URL http://jakarta.apache.org/taglibs/ nel progetto Jakarta Taglibs sono presenti esempi di come si possano raggruppare logicamente i tag. Le librerie di tag possono rendere possibile la visualizzazione di date e orari, la manipolazione di stringhe e diverse altre azioni. Ciascuna Jakarta Taglibs sar discussa pi avanti.

8.1.3.1 I componenti delle librerie di tag


Una libreria di tag consta dei seguenti componenti: Una classe tag handler Un file descrittore della libreria di tag Il descrittore di deploy dell'applicazione (web.xml) La dichiarazione della libreria di tag nella pagina JSP

8.1.3.2 Tag handler


La classe tag handler rappresenta il luogo di implementazione del tag. La tag handler una classe Java caricata durante l'esecuzione, che compie azioni prestabilite.

8.1.3.3 II file TLD


Il file tag library descriptor (TLD) un file XML che contiene metainformazioni sui tag all'interno di una libreria. Informazioni quali nome del tag, attributi richiesti e nome della classe tag handler sono tutte racchiuse in questo file e sono lette dal container JSP.

8.1.3.4 II file web.xml


I descrittori di deploy delle web application sono stati trattati precedentemente nel Capitolo 3. All'interno di questi descrittori si deve definire quali librerie di tag sono impiegate nella web application e quali file TLD dovrebbero essere adoperati per la descrizione di ogni libreria di tag.

8.1.3.5 La pagina JSP


Ovviamente, la pagina JSP un componente importante. Contiene le direttive di inclusione per una o pi librerie di tag e le chiamate necessarie a tali librerie all'interno della stessa pagina JSP. Non esiste un limite a quante librerie di tag o riferimenti si possono avere in una pagina JSP. Quando il numero di tag in una JSP si avvicina ai 40-50 stato fatto presente che possono esserci problemi di eccessiva lentezza. I produttori hanno la massima libert circa il modo in cui implementano l'inizializzazione e l'elaborazione di tag personalizzati: alcuni lo fanno meglio di altri. Se questo costituisce un problema per la propria applicazione, si provi a ridisegnare la pagina per ridurre il numero di tag personalizzati oppure si accorpino alcuni tag in un solo handler. Se si continuano ad avere problemi si provi a far girare i propri tag nell'ambito di un container diverso e si misuri conseguentemente la performance. Esula dagli scopi di questo libro il tentativo di coprire ogni aspetto dei tag personalizzati delle JSP, dato che ci sono libri interi su questo argomento. Una buona.fonte di informazioni sui tag personalizzati e il loro utilizzo nelle applicazioni JavaServer Pages di Hans Bergsten (O'Reilly).

8.2 Le librerie di tag incluse in Struts


Struts propone un insieme di componenti di infrastruttura abbastanza consistente. Include anche un set di librerie di tag progettati per una stretta interazione con il resto dell'infrastruttura. I tag personalizzati inclusi in Struts sono raggruppati in quattro librerie distinte: HTML Bean Logic

Template C' anche una quinta libreria, la Nested, di cui si parler pi oltre nel capitolo. Le prime versioni di Struts contenevano una libreria di tag chiamata Form. stata poi sostituita dalla libreria HTML, ma in alcuni esempi o nella documentazione si possono trovare ancora riferimenti a tag di Form.

C' un tag per generare una checkbox, uno per un pulsante e un'altro ancora per generare un collegamento (hyperlink).

8.2.1 Usare le librerie di tag nelle applicazioni di Struts


L'uso dei tag di Struts non richiede nessuna spiegazione particolare: sono esattamente come qualsiasi altro tag personalizzato delle JSP. Si devono includere le dichiarazioni TLD per ciascuna libreria di tag che si impiega in ogni pagina in cui si inseriscano tag. Se si devono usare le librerie HTML e Logic in una pagina JSP, per esempio, si dovranno inserire le seguenti due linee in cima alla pagina JSP:

<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
Una volta che siano state incluse queste due lince e che il proprio descrittore di deploy della web application contenga gli indispensabili elementi taglib si possono adoperare tag HTML e Logic all'interno della pagiha JSP. Riprendendo l'esempio precedente, si devono aggiungere le seguenti linee al proprio file web.xml:

<taglib> <taglib-uri>/WEB-INF/struts-html.tld</taglib-uri> <taglib-location>/WEB-INF/struts-html.tld</taglib-location> </taglib> <taglib> <taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri> <taglib-location>/WEB-INF/struts-logic.tld</taglib-location> </taglib>


Per informazioni pi complete sulla configurazione del descrittore di deploy per le web application, si veda "Configurare il file web.xml per Struts" nel Capitolo 4.

8.3 Usare i JavaBeans con i tag di Struts


In molti casi si impiegheranno i tag delle varie librerie in associazione con i JavaBeans. I JavaBeans possono essere ActionForms, le cui propriet corrispondono ai campi di input dei form HTML. In altri casi, tuttavia, i bean saranno oggetti dai valori comuni appartenenti al livello del modello. Questi bean possono avere qualsiasi scope: di pagina, di request, di sessione o di applicazione. Ci sono tre modalit di accesso alle propriet di un JavaBean: Accesso alle propriet semplici Accesso alle propriet annidate Accesso alle propriet indicizzate

8.3.1 Accesso alle propriet semplici


L'accesso alle propriet semplici di un bean funziona in modo analogo all'azione JSP <jsp:getProperty>. Un riferimento a una propriet chiamata "firstName" convertito in una chiamata di un metodo getFirstName() o setFirstName(value), nel rispetto delle specifiche delle convenzioni sui nomi dei JavaBeans per le propriet dei bean. Struts impiega l'introspezione delle API Java per identificare i nomi delle propriet dei metodi di get e di set, per cui i propri bean possono fornire nomi di metodo personalizzati grazie all'uso di una classe chiamata Beanlnfo. Le specifiche per i JavaBeans possono essere consultate all'URL http://Java.sun.com/products/javabeans/.

8.3.2 Accesso alle propriet annidate


I riferimenti alle propriet annidate possono essere adoperati per l'accesso a una propriet attraverso una gerarchia di nomi di propriet separati da punti (.), in modo analogo a quanto avviene per le propriet annidate in JavaScript. Ad esempio:

property="user.address.city"
viene tradotto nell'equivalente Java:

getUser().getAddress().getCity(

Se si impiega metodo di set su una propriet annidata (come ad esempio quando elaborato un form di input) il metodo di set della propriet invocato nell'ultima propriet della catena. Per il riferimento alla propriet precedente l'espressione equivalente in Java sarebbe:

getUser().getAddress(

).setCity(value)

Le propriet annidate sono molto comode da usare con i tag personalizzati. Sono quasi sempre adoperate unitamente ad attributi di tipo property dei tag supportati

8.3.3 Accesso alle propriet indicizzate


Per poter accedere a elementi individuali di propriet i cui valori siano davvero stringhe o i cui JavaBeans offrano metodi di set e get indicizzati, possono essere utilizzati dei subscript:

property="address[2]"
in Java:

getAddress(2);
mentre il riferimento alla stessa propriet in un metodo di set sarebbe:

setAddress(2, address)

Come si evince dagli esempi, i subscript dei riferimenti indicizzati sono "relativi a zero" (ovvero, il primo elemento in un array address[0]), proprio come in Java.

8.4 Tag della libreria HTML di Struts


La libreria di tag HTML di Struts contiene i tag usati per creare form di input HTML, come pure altri tag utili solitamente per la creazione di interfacce utente basate su HTML. Per esempio, invece di usare un campo di input di testo in HTML normale, si pu impiegare il tag text di questa libreria. Questi tag sono studiati per operare in stretta connessione con gli altri componenti del framework Struts, ivi incluso ActionForm. Per tale motivo il loro utilizzo sarebbe sempre da preferire all'impiego di HTML puro. Vale la pena di usarli per la loro integrazione con il resto dell'infrastruttura. Molti dei tag contenuti in questa libreria devono essere annidati all'interno di un tag form. Ci sono anche alcuni altri tag qui contenuti che risolvono problemi analoghi non necessariamente correlati a form o widget. Anche questi tag saranno brevemente considerati in questa sezione. Anche se non.tutti i tag HTML saranno trattati in dettaglio, nella Tabella 8-1 sono elencati tutti i tag.inclusi nella libreria di tag HTML.
Tabella 8-1. I tag personalizzati all'interno della libreria di tag HTML di Struts

Nome tag

Descrizione Visualizza un elemento di base HTML Visualizza un campo-di input (pulsante) Visualizza un pulsante di cancellazione Visualizza un campo di input (checkbox) Visualizza un insieme di messaggi d'errore Visualizza un campo di input per la selezione di un file Definisce un elemento HTML form Visualizza un elemento HTML frame

base button cancel checkbox errors file form frame

hidden html image img javascript link messages multibox option options password radio reset rewrite select submit text textarea

Genera un campo nascosto Visualizza un elemento HTML html Visualizza un tag input del tipo "image"Renders an input tag of type "image" Visualizza un tag HTML img Provvede alla validazione di JavaScript basandosi sulle regole di validazione caricate da

ValidatorPlugIn
Visualizza un elemento HTML anchor oppure hyperlink Mostra in modo condizionale un insieme di messaggi cumulativi Visualizza campi di input (checkbox) multipli Visualizza un'opzione select Visualizza una raccolta di opzioni select Visualizza un campo di input per le password Visualizza un campo di input radio button Visualizza un campo di input con pulsante reset Visualizza un URI Visualizza un elemento select Visualizza un pulsante submit Visualizza un campo di input del tipo "text" Visualizza un campo di input textarea

optionsCollection Visualizza una raccolta di opzioni select

Come precedentemente detto questo capitolo non affronter in dettaglio tutti i tag di Struts; una simile operazione sarebbe inutile dal momento che questa materia trattata in dettaglio nei JavaDoc di Struts. Saranno per selezionati e discussi alcuni tag pi importanti, che possono essere fraintesi dagli sviluppatori e che consentono l'uso di particolari strategie per il loro impiego. Se si necessita dei riferimenti a proposito di un set completo di tag, si possono reperire informazioni nella documentazione delle API del framework Struts all'URL http://jakarta.apache.org/struts/api/.

8.4.1 Il tag HTML


Il tag html usato per la visualizzazione di un elemento html. Permette di includere un attributo locale che scriver le impostazioni locali dell'utente, sempre che uno di questi attibuti sia stato conservato nella sessione:

<html:html locale="true"> <!-- Body of the JSP page --> </html:html>

8.4.2 Il tag base


Questo tag usato per la visualizzazione di un elemento HTML base con un attributo href che punta alla collocazione assoluta della pagina JSP che lo include. Questo tag molto utile dal momento che permette di usare riferimenti nella forma di URL relativi calcolati sulla base dell'URL della pagina stessa e non sulla base dell'URL in cui hanno avuto luogo i pi recenti submit (ovvero il punto in base al quale i browser risolvono i riferimenti relativi). Questi tag devono essere posti all'interno di un elemento HTML head. Segue un esempio d'uso del tag base:

<html:html locale="true"> <head> <html:base/> <title><bean:message key="title.login"/></title> </head> </html:html> Il tag base di quest'esempio darebbe origine al seguente risultato se eseguito nella pagina principale dell'applicazione
Storefront:

<html lang="en"> <head> <base href="http://localhost:8080/storefront/index.jsp"> <title>Virtual Shopping with Struts</title> </head>

<html>
Questo tag della pi grande importanza quando si adoperano URL relativi per le immagini in una pagina JSP. Non ha associato alcun attributo.

8.4.3 Il tag form


Il tag form di Struts uno dei pi importanti tag della libreria HTML. Il suo scopo consiste nel permettere la visualizzazione di un tag HTML form e di collegare il form HTML con una ActionForm configurata per l'applicazione. Tutti i campi nel form HTML avrebbero dovuto corrispondere a una propriet dell' ActionForm. Quando il nome di un campo HTML e il nome di una propriet coincidono, la propriet dell' ActionForm viene usata per riempire i campi HTML. Quando si invia il forni HTML, l'infrastruttura conserver l'input dell'utente in ActionForm, confrontando ancora i nomi dei campi HTML con i nomi delle propriet. Tutti i tag personalizzati HTML che visualizzano i controlli HTML devono essere annidati all'interno del tag html. DynaActionForm e le sue sottocl funzionano allo stesso modo con assi il tag form.

II tag form controlla diversi aspetti importanti della pagina. I suoi attributi sono elencati nella Tabella 8-2.
Tabella 8-2. Gli attributi del tag form

Nome

Descrizione L'URL cui questo form sar sottoposto La codifica del contenuto da usare quando questo form viene passato all'elaborazione. II nome del campo cui sar assegnata la priorit nell'esame della pagina. II metodo HTTP usato per sottoporre la richiesta II nome dell' ActionForm le cui propriet saranno impiegate per il riempimento dei campi di input con i valori appropriati. L'event handler JavaScript che eseguito in seguito a un reset del form. L'event handler JavaScript che eseguito in seguito all'invio del form. Lo scope dell' ActionForm per questo form Gli stili CSS da applicare a questo elemento HTML.

action enctype focus method name onreset onsubmit scope style

styleClass La classe stylesheet CSS da applicare a questo elemento HTML. styleId L'identificatore da assegnare a questo elemento HTML. target type
II frame target cui questo form inviato. II nome completo della classe dell' ActionForm per questa pagina.

8.4.3.1 L'attributo action


Il valore per l'attributo action usato per selezionare l'ActionMapping che si presume la pagina stia elaborando. Da questo si pu identificare l'ActionForm e lo scope appropriati. Se si adopera il mapping delle estensioni (*.do), il valore action dovrebbe essere uguale a quello dell'attributo path del corrispondente elemento action seguito opzionalmente dal suffisso dell'estensione. Ad esempio:

<html:form action="login.do" focus="accessNumber">


Se si impiega il mapping del path il valore dell'attributo action dovrebbe essere esattamente uguale al valore dell'attributo path del corrispondente elementoaction:

<html:form action="login" focus="accessNumber">

8.4.3.2 L'attributo enctype


Di solito non si deve impostare l'attributo enctype. Tuttavia se il proprio form compie degli upload di file si dovrebbe impostare l'attributo enctype a multipart/form-data. Ci si deve anche assicurare che l'attributo method sia impostato a POST, che rappresenta il metodo di default se non altrimenti specificato.

8.4.3.3 L'attributo name


L'attributo name specifica il nome dell'ActionForm con scope di request o di sessione le cui propriet saranno impiegate per popolare i valori dei campi di input. Se non si trova questo bean, sar creato un nuovo bean e sar aggiunto allo scope appropriato usando il nome della classe Java specificato dall'attributo type. Se non si specifica alcun valore per l'attributo, name sar calcolato tramite l'impiego dell'attributo action per ricercare il corrispondente elementoActionMapping, dal quale sar determinato il nome del bean del form. In altri termini se non specificato un attributo name per il tag form, il tag user il valore preso dall'attributo name nell'elemento action dal file di configurazione. Questo un importante punto che confonde molti sviluppatori che iniziano a programmare con Struts. Ad esempio, si consideri un elemento action configurato nel seguente modo:

<action path="/signin" type="com.oreilly.struts.storefront.security.LoginAction" scope="request" name="loginForm" validate="true" input="/security/signin.jsp"> <forward name="Success" path="/index.jsp" redirect="true"/> <forward name="Failure" path="/security/signin.jsp" redirect="true"/> </action>
Poniamo ora di avere un tag form come questo dichiarato in una pagina JSP:

<html:form action="signin">
Dal momento che l'attributo name non specificato nel tag form, il tag cercher l'azione signin nel file di configurazione. Recuperato questo valore dall'elemento action lo user per cercare una ActionForm nello scope di sessione o della request. In questo caso sar selezionato loginForm a motivo del proprio valore per l'attributo name nell'elemento action.

8.4.3.4 L'attributo scope


L'attributo scope definisce dove il tag dovrebbe cercare l'ActionForm. Il suo valore dovrebbe essere o request o session. Se l'attributo scope non viene specificato, sar calcolato usando il valore dell'attributo action per cercare il corrispondente elemento ActionMapping, dal quale sar selezionato lo scope dello specifico bean del form (in modo analogo a come si determina l'attributo name se non specificato nel tag form).

8.4.3.5 L'attributo type


L'attributo type specifica il nome completo di classe dell'ActionFotm da creare se non si trova un tale bean nello scope specificato. Se non specificato alcun attributo, sar calcolato impiegando il valore dell'attributo action per ricercare il corrispondente elemento ActionMapping in base al quale si potr selezionare il tipo di form bean specificato.

8.4.4 Uso di tag form multipli


Proprio come per l'HTML standard, si pu includere pi di un tag form all'interno di una pagina JSP. Ovviamente, pu essere inviato solo un form per volta, ma questo non impedisce di dichiarare tag form multipli per un'area di ricerca della pagina. Quando un utente preme il pulsante di ricerca, quel form inviato con i campi che definiscono i criteri di ricerca. In quella stessa pagina ci potrebbe benissimo essere anche un tag form diverso che ha un'altra funzione. Quando premuto un altro pulsante nello stesso form, sono inviati quel particolare form e i corrispondenti campi, come nell'Esempio 8-1.

Esempio 8-1. Usare tag form multipli

<html:html locale="true"> <head> <html:base/> </head> <body> <!<html:form action="searchAction"> <!-- The search fields --> <html:submit styleClass="button" value="Go"/> </html:form> <html:form action="anotherAction"> <!-- The other form fields --> <html:submit styleClass="button"/> </html:form> </body> </html:html>

8.4.5 I tag button e cancel


Questi due tag servono alla visualizzazione di elementi HTML input del tipo button, riempiti da valori specificati oppure dal contenuto del tag body. Questi tag sono validi solo se annidati all'interno del body di un tag form. Anche gli stylesheet possono essere usati dando un valore all'attributo styleClass. Come valore predefinito l'etichetta button impostata a "Click" e quella cancel al valore "Cancel ". Si possono ridefinire queste impostazioni usando l'attributo value. Il pulsante creato dal tag cancel consente di evitare l'escuzione del metodo validate() quando premuto. Il RequestProcessor invoca il metodo execute(), senza attraversare la routine di validazione.

8.4.6 Il tag checkbox


Questo tag visualizza un elemento HTML input del tipo checkbox, riempito dai valori specifici del bean associato con il form presente. Questo tag valido solo quando annidato all'interno del body di un tag form. Il valore della propriet associata con questo campo dovrebbe essere di tipo booleano e qualsiasi valore specificato dovrebbe corrispondere a una delle stringhe che indicano un valore "attivo" (true, yes o on). Il browser invier i valori nella richiesta solo per le checkbox che sono selezionate. Per un corretto riconoscimento delle checkbox non selezionate, il bean ActionForm associato a questo form deve includere uno statement che imposti la corrispondente propriet boolean a false nel metodo reset().

8.4.7 I tag messages ed errors


Questi due tag hanno il compito di visualizzare una serie di messaggi o errori per l'utente. Il tag messages corrisponde ad ActionMessage mentre errors ad ActionError. I tag messages/errors sono creati nel metodo validate() oppure tramite l'infrastruttura di gestione delle eccezioni. Se non sono presenti messaggi e/o errori, non sar mostrato nulla. Quando si usano i tag errors, il complesso del messaggio deve includere chiavi per i seguenti valori: errors.header Testo che deve essere mostrato prima della lista dei messaggi d'errore. Di solito il testo di questo messaggio termina con uno statement <ul> per avviare la lista dei messaggi d'errore. errors.footer Testo che deve essere mostrato dopo la lista dei messaggi d'errore. Di solito il testo di questo messaggio termina con uno statement </ul> per terminare la lista dei messaggi d'errore. Ad esempio si potrebbero impostare i valori header e footer a:

errors.header=<h3><font color="red">Validation Error</font></h3>You must correct the following error(s) before proceeding:<ul> errors.footer=</ul><hr>

all'interno di una lista. Ora che Struts supporta MessageResources multiple all'interno del file di configurazione, si pu specificare quale deve essere impiegata usando l'attributo bundle.

8.4.8 Gestori degli eventi JavaScript


Molti dei tag HTML supportano i gestori degli eventi JavaScript (event handler) tramite i loro attributi. Per esempio, per configurare un gestore onClick per un tag supportato si deve includere il nome della funzione nell'attributo onClick per il tag. La Tabella 8-3 elenca gli attributi supportati per i gestori degli eventi.
Tabella 8-3. Attributi supportati per i gestori degli eventi Javascript

Attributo

Descrizione Eseguito quando questo elemento perde il focus di input. Eseguito quando questo elemento perde il focus di input ed il suo valore cambiato. Eseguito quando questo elemento riceve un click del mouse.

onblur onchange onclick

ondblclick Eseguito quando questo elemento riceve un doppio click del mouse. onfocus Eseguito quando questo elemento riceve un focus di input.onkeydown
Eseguito quando questo elemento ha un focus e il tasto premuto.

onkeypress Eseguito quando questo elemento ha un focus e un tasto premuto e rilasciato. onkeyup Eseguito quando questo elemento ha un focus e un tasto rilasciato. onmousedown Eseguito quando questo elemento sotto il puntatore del mouse ed premuto un tasto. onmousemove Eseguito quando questo elemento sotto il puntatore del mouse e il puntatore spostato. onmouseout Eseguito quando questo elemento sotto il puntatore del mouse ma il puntatore spostato fuori
dall'elemento. l'elemento.

onmouseover Eseguito quando questo elemento non sotto il puntatore del mouse ma il puntatore spostato sopra onmouseup onreset onsubmit
Eseguito quando questo elemento sotto il puntatore del mouse ed rilasciato un tasto del mouse. Eseguito in caso di reset del form che lo contiene. Eseguito in caso di invio del form.

8.4.9 Attributi per la navigazione HTML


Molti dei tag supportano anche la navigazione tramite il solo uso della tastiera. Gli attributi elencati nella Tabella 8-4 hanno questo scopo: Tabella 8-4. Attributi per la navigazione mediante l'uso della tastiera Attributo Descrizione Il carattere della tastiera usato per muovere il focus immediatamente su questo elemento. L'ordine dei tab (interi positivi in ordine crescente) per questo argomento.

acesskey tabindex

8.5 Tag della libreria Logic


La libreria Logic include tag utili per la gestione della generazione condizionale di output di testo e della gestione del flusso dell'applicazione. La Tabella 8-5 elenca i tag disponibili nella libreria Logic.
Tabella 8-5. Tag personalizzati nella libreria di tag Logic

Nome del tag

Descrizione Valuta il contenuto annidato di questo tag se la variabile richiesta null o una stringa vuota. Valuta il contenuto annidato di questo tag se la variabile richiesta uguale al valore specificato. Inoltra il controllo alla pagina specificata dall'entry ActionForward. specificato.

empty equal forward

greaterEqual Valuta il contenuto annidato di questo tag se la variabile richiesta maggiore o uguale al valore greaterThan Valuta il contenuto annidato di questo tag se la variabile richiesta maggiore del valore specificato.

iterate lessEqual lessThan match

Reitera il contenuto annidato di questo tag su un insieme specificato. Valuta il contenuto annidato di questo tag se la variabile richiesta minore o uguale al valore specificato. Valuta il contenuto annidato di questo tag se la variabile richiesta minore del valore specificato. Valuta il contenuto annidato di questo tag se il valore specificato una sottostringa specificata della variabile richiesta. richiesta.

messagesNotPresent Genera il contenuto annidato di questo tag se il messaggio specificato non presente nella messagesPresent notEmpty notEqual notMatch notPresent present redirect
Genera il contenuto annidato di questo tag se il messaggio specificato presente nella richiesta. Valuta il contenuto annidato di questo tag se la variabile richiesta non null n una stringa vuota. Valuta il contenuto annidato di questo tag se la variabile richiesta non uguale al valore specificato. Valuta il contenuto annidato di questo tag se il valore specificato non una sottostringa appropriata della variabile richiesta. Genera il contenuto annidato di questo tag se il valore specificato non presente nella richiesta. Genera il contenuto annidato di questo tag se il valore specificato presente nella richiesta. Esegue una redirect HTTP.

I tag all'interno della libreria Logic possono essere divisi in quattro categorie separate sulla base del loro impiego: Comparazione di valori Riconoscimento di sottostringhe Redirect e forward Utilit per la raccolta Questa divisione in categorie effettuata a scopo esplicativo. I tag non sono impacchettati o riuniti in queste categorie; appartengono tutti al package Logic.

8.5.1 Comparazione di valori


I tag per la comparazione dei valori scrivono il corpo del tag se e solo se la comparazione d come risultato true. Ci sono"svariati tipi di tag per la comparazione che si possono usare a seconda delle necessit specifiche. Ciascun tag di comparazione dei valori prende un valore e lo paragona al valore di un attributo di confronto. Se il valore puoi essere convertito con successo in un numero, viene effettuata una comparazione di valori; altrimenti viene effettuata una comparazione di stringhe. I tag di comparazione condividono gli attributi della Tabella 8-6.
Tabella 8-6. Attributi comuni dei tag di comparazione

Nome

Descrizione II nome di un bean da usare per la comparazione con l'attributo value. Se viene impiegato l'attributo property, il valore confrontato con le propriet del bean invece che con il bean stesso. La variabile da comparare la propriet (del bean specificato dall'attributo name) specificata da quest'attributo, il riferimento alla propriet pu essere semplice, annidato e/o indicizzato. Lo scope all'interno del quale ricercato il bean nominato dall'attributo name. Verr effettuata una ricerca su tutti dli scope se non viene specificato. II valore costante rispetto al quale la variabile comparata, specificata da un'altro attributo di questo tag.

name

parameter II nome di un parametro di richiesta con cui comparare l'attributo value. property scope value

Alcuni esempi saranno d'aiuto a consolidare la conoscenza del modo in cui possano essere usati questi tag di comparazione. Per controllare se un particolare parametro di richiesta presente si pu adoperare il tag di Logic present:

<logic:present parameter="id">

<!-- Print out the request parameter id value --> </logic:present>


Per controllare se un insieme vuoto prima di compiervi delle iterazioni si pu usare il tag notEmpty:

<logic:notEmpty name="userSummary" property="addresses"> <!-- Iterate and print out the user's addresses --> </logic:notEmpty>
Ecco come comparare un valore numerico e una propriet all'interno di una ActionForm:

<logic:lessThan property="age" value="21"> <!-- Display a message about the user's age --> </logic:lessThan>

8.5.2 Ricerca di corrispondenza delle sottostringhe


I tag di ricerca della corrispondenza delle sottostringhe assumono tutti gli stessi argomenti dei tag di comparazione dei valori. Si compara la stringa specificata dall'attributo value con qualsiasi valore di comparazione si voglia, specificato da cookie, header, parameter. property o name. I tag corrispondenti hanno anche un ulteriore attributo location che informa il tag su dove iniziare la ricerca di corrispondenze ("start" o "end"). In questo esempio viene impiegato il tag matchTag per determinare se il parametro di richiesta action inizia con la stringa "processLogin":

<logic:matchTag parameter="action" value="processLogin" location="start"> Processing Login.... </logic:matchTag> Se l'attributo location non viene specificato, potrebbe verificarsi una corrispondenza tra la variabile e il valore in una qualsiasi posizione all'interno della variabile stringa.

8.5.3 Redirect e forward


I tag forward e redirect all'interno della libreria di tag Logic potrebbero essere maggiormente adatti alla libreria dei tag HTML. Tuttavia, il fatto che siano nella libreria Logic non sminuisce il loro valore. Di fatto, combinati con uno degli altri tag di Logic, questi due tag diventano molto utili. Il tag redirect invia un redirect al browser del client, completo di riscrittura dell'URL se il container supporta questa funzione. I suoi attributi sono associati al taglink di HTML. L'URL di base calcolato tramite la selezione dei seguenti attributi (se ne deve specificare almeno uno): forward href page Usa il valore di questo attributo come il nome di una ActionForward globale da ricercare e usa gli URI relativi al contesto trovate. Usa il valore di questo attributo lasciandolo immutato.

Usa il valore di questo attributo come un URI relativo a! contesto e genera un URI relativo al server includendo il path del contesto. II tag forward responsabile della redirezione o dell'inoltro a una ActionForward globale specificata. Il tag forward ha un attributo, name, che anche il nome logico dell' ActionForward presa dal file di configurazione.

8.5.4 Utilit per la gestione di insiemi


Uno dei tag pi comuni e utili di Struts il tag iterate. Il tag iterate esegue il contenuto del proprio body una volta per ogni elemento all'interno dell'insieme specificato. Deve contenere un attributo: id

II nome di un bean JSP con scope di pagina che conterr l'elemento corrente nel corso di un'iterazione.
Un esempio potr agevolare la comprensione dell'uso del tag iterate:

<logic:iterate id="address" name="userSummary" property="addresses">

<!-- Print out the address obejct in a Tabella --> </logic:iterate>


In questo caso il tag iterate con il metodo getAddresses() prender l'insieme di indirizzi chiamando il metodo getAddresses( ) sul bean userSummary. Nel corso di ogni iterazione un indirizzo individuale sar assegnato alla variabile address. Questa variabile potr essere usata all'interno del body del tag iterate come se si fosse assegnata direttamente. Nel corso della successiva iterazione, il successivo oggetto indirizzo sar assegnato alla variaile address. b Ci continua finch l'intero insieme di indirizzi stato consultato. Il tag iterate molto flessibile in termini'di dove prendere l'insieme su cui effettuare iterazioni. Gli attributi che controllano,il,modo in cui il tag iterate svolge il proprio compito sono elencati nella Tabella 8-7.
Tabella 8-7. Attributi del tag iterate

Nome

Descrizione

collection Un'espressione a runtime che valuta un insieme (conforme alle richieste elencate sopra) su cui compiere le iterazioni. id indexed length
II nome di un bean JSP con scope di pagina che contiene l'elemento corrente dell'insieme per ogni iterazione, se non null. II nome di un bean con scope di pagina che contiene l'indice corrente dell'insieme per ciascuna iterazione. II massimo numero di entry (dall'insieme sottostante) su cui compiere le iterazioni nella pagina. Questo potrebbe essere sia un intero che esprime direttamente il valore desiderato o il nome di un bean JSP (in qualunque scope) del tipo java.lang.Integer che definisce il valore desiderato. Se non presente, non esiste limite al numero di iterazioni svolte. II nome del bean che contiene l'insieme su cui compiere iterazioni (se property non specificato), o il bean JSP le cui propriet di get restituiscono l'insieme su cui compiere le iterazioni (se specificato property). L'indice relativo del punto di partenza dell'insieme su cui saranno svolte le iterazioni. Pu essere un intero che esprime direttamente il valore voluto o il nome di un bean JSP (in qualsiasi scope) del tipo java.lang.Integer che definisce il valore desiderato. Se non presente, sar posto a zero (le iterazioni saranno svolte dall'inizio dell'insieme). II nome della propriet del bean JSP specificato da name il cui metodo getter restituisce l'insieme su cui svolgere le iterazioni. Lo scope del bean all'interno del quale cercare il bean citato dalla propriet name o "qualsiasi scope" se non specificato. II nome completo della classe dell'elemento da esporre attraverso il bean JSP citato dall'attributo id. Se non presente, non sar effettuata nessuna conversione di tipi. Gli elementi dell'insieme devono poter essere assegnabili a questa classe, altrimenti si verificher un'eccezione ClassCastException.

name

offset property scope type

8.5.5 Messaggi ed errori


I tag messagesPresent e messagesNotPresent valutano il contenuto del body, a seconda che ActionMessages o ActionErrors siano presenti nello scope di request. La Tabella 8-8 elenca gli attributi di questi due tag.
Tabella 8-8. Attributi dei tag messagesPresent e messagesNotPresent

Nome

Descrizione Il parametro chiave per recuperare il messaggio dallo scope di request. recuperati tutti i messaggi (ignorando la propriet).

name

property II nome della propriet per la quale i messaggi dovrebbero essere recuperati. Se non specificato, saranno
Per default, il tag prender il bean con scope di richiesta che iterer sulla stringa costante Action.ERROR_KEY, ma se quest'attributo impostato a true, il bean con scope di richiesta sar preso message dalla stringa costante Action.MESSAGE_KEY Inoltre, se impostato a true, qualsiasi valore assegnato all'attributo name sar ignorato.

8.6 Tag della libreria Bean


I tag che fanno parte della libreria di tag Bean vengono usati per l'accesso ai JavaBeans e alle propriet loro associate ma anche per definire nuovi bean che siano accessibili al resto della pagina attraverso variabili di script e attributi con scope di pagina. Sono anche resi disponibili alcuni meccanismi per creare nuovi bean sulla base dei valori di cookie, header e parametri. La Tabella 8-9 elenca i tag contenuti nella libreria Bean.

Tabella 8-9. Tag personalizzati all'interno della libreria di tag Bean

Nome del tag

Descrizione Definisce una variabile di scripting sulla base del valore di uno specifico request cookie. Definisce una variabile di scripting sulla base del valore di uno specifico bean property. Definisce una variabile di scripting sulla base del valore di uno specifico request header. Carica la risposta da una richiesta dinamica di un'applicazione e la rende disponibile come bean. Nella risposta mostra un messaggio internazionalizzato. Espone come bean un oggetto dal contesto della pagina specificata. Carica una risorsa di una web application e la rende disponibile come bean. Definisce un bean che contiene il numero degli elementi in una Collection o in una Map. Espone un oggetto della configurazione interna di Struts come bean. Scrive il valore della propriet specificata del bean. Molti tag di questa libreria lanciano eccezioni del tipo JspException quando sono usate non correttamente (p.e., quando si specifica una combinazione non valida degli attributi dei tag). Le JSP permettono di dichiarare una "pagina di errore" nella direttiva <%@ page %> . Se si desidera elaborare l'eccezione che ha causato il problema, questa passata alla pagina di errore come un'attributo della richiesta con la dicitura org.apache.struts.action.EXCEPTION.

cookie define header include message page resource size struts write

parameter Definisce una variabile di scripting basata sul/i valore/i del parametro di richiesta specificato.

8.6.1 Il tag define


Questo tag recupera una propriet specificata di un bean e la definisce come attributo accessibile al resto della pagina. Non viene svolta alcuna conversione sul valore restituito dalla propriet a meno che vi sia un'tipo primitivo Java nel qual caso incapsulato nella classe wrapper appropriata (p.e., int incapsulato da java.lang.Integer). Il valore della propriet conservato nello scope definito dalla variabile toScope.

8.6.2 l tag header


Questo tag prende il valore dal request header specificato e definisce il risultato come attributo con scope di pagina del tipo String. Se questo non pu essere identificato e non specificato alcun valore nell'attributo value, sar lanciata un'eccezione del tipo request-time. Se l'attributo multiple impostato a un qualsiasi valore non null, l'attributo id conterr il risultato della chiamata a HttpServletRequest.getHeaders() e non alla chiamata a HttpServletRequest.getHeader().

8.6.3 Il tag include


Questo tag esegue un invio interno perj il componente specificato dell'applicazione (o un URL esterno) e rende disponibili i dati di questa richiesta come bean del tipo String. Il valore conservato nell'attributo id. Questo tag ha una funzione simile a quella del tag standard <jsp:include> se non che i dati sono conservati in un attributo con scope di pagina piuttosto che essere scritti nell'output. In questo modo si permette di posizionare l'output dove si desidera. Se la richiesta corrente fa parte di una sessione, la richiesta generata per l'include conterr anche l'ID di sessione. Gli URL usati per l'accesso ai componenti specificati dell'applicazione sono calcolati sulla base dei seguenti attributi (se ne deve specificare almeno uno): forward Usa il valore di questo attributo come nome di un ActionForward globale da considerare e usa l'URI relativo all'applicazione o relativo al contesto qui trovato. href page Usa il valore immutato di questo attributo (dal momento che potrebbe contenere un collegamento a una risorsa esterna all'applicazione, non incluso un identificatore di sessione). Usa il valore di questo attributo come URI per la risorsa richiesta. Questo valore deve iniziare con una "/".

8.6.4 Il tag message


Il tag message uno dei tag pi diffusi nell'uso comune della libreria dei tag di Struts. Prende un messaggio internazionalizzato per l'area locale specificata impiegando la chiave d' messaggio specificata e lo scrive nell'output. Si possono inserire fino a cinque sostituzioni parametriche (utilizzando una metasintassi del tipo "{0}"). Lii chiave pu essere specificata direttamente usando l'attributo key oppure indirettamente con gli attributi name e property per recuperarla da un bean. L'attributo bundle permette di specificare il nome di un bean con scope sull'applicazione sotto il quale pu essere trovato l'oggetto MessageResources. Se viene specificato l'attributo locale, allora dalla sessione sar ripreso Locale per mezzo della chiave Action.LOCALE_KEY. Ecco un esempio del lag message:

<td><bean:message key="global.user.firstName"/>:</td>

8.6.5 Il tag parameter


Questo tag prende il valore del parametro di richiesta specificato e definisce il risultato come un attributo con scope di pagina del tipo String. Se viene specificato un qualsiasi valore non null per l'attributo multiple, il risultato sar una String[] ottenuta dalla chiamata a getParameters() al posto di quella a getParameter().

8.6.6 Il tag resource


Questo tag prende il valore della risorsa della web application specificata e lo mette a disposizione o come InputStream o come String, a seconda del valore dell'attributo input. Se l'attributo input contiene un valore non null viene creato un InputStream. Altrimenti la risorsa caricata come String. Questo tag invoca il metodo getResourceAsStream() usando l'attributo name. L'attributo name deve iniziare con una"/".

8.6.7 Il tag write


Il tag write un'altro importante tag della libreria di tag Bean, usato diffusamente. Recupera il valore della propriet specificata del bean e lo visualizza nella pagina come String. Il tag write usa le seguenti regole: Se si specifica l'attributo format il valore sar formattato sulla base del formato della stringa e delle impostazioni locali di default. Pu essere impiegato anche l'attributo formatKey. Dovrebbe specificare il nome di una stringa di formato dall'insieme delle risorse dei messaggi. L'attributo formatKey si adopera per specificare una stringa di formato dall'insieme delle risorse. Possono essere specificati anche gli specifici insiemi di risorse e le impostazioni locali. Se non sono specificati, si fa uso dell insieme predefinito e delle impostazioni locali correnti dell'utente. Altrimenti saranno adoperate le conversioni toString(). Ecco un esempio d'uso del tag write:

<td>Hello <bean:write name="user" property="firstName"/></td>


Se l'attributo ignore impostato a true ed il bean specificato dagli attributi name e scope non rinvenuto, il tag non lancer un'eccezione. Se invece non ha impostazioni o impostato a false, verr lanciata un'eccezione

8.7 Tag della libreria Template


The concept of templates has not been discussed yet in this book. The Template library supplies tags that are useful for creating dynamic JSP templates for pages that share a common format. These tags provide capabilities similar to stylesheets or the standard include directive in JSP technology, but they can be dynamic.

Il concetto di template non stato ancora, approfondito in questo libro. La libreria Template mette a disposizione tag utili per la creazione di template dinamici JSP per pagine che condividono un formato simile. Questi tag offrono capacit simili agli stylesheet o alla direttiva standard include nella tecnologia JSP ma possono essere anche dinamici. Con l'avvento dell'infrastruttura Tiles che sar affrontata nel Capitolo 14, i tag di Template non sono pi molto usati. Per questo si elencheranno i tag della libreria ma non si entrer nel dettaglio. Se si desideranti maggiori informazioni, possibile trovarle all'URL http://jakarta.apache.org/struts/api/. Ecco l'elenco dei tag della libreria dei tag Template nella Tabella 8-10.
Tabella 8-10. Tag personalizzati nella libreria Template

Nome del tag

Descrizione Recupera (o include) il file template speciticato e inserisce il contenuto scelto nel template. Modificando l'aspetto definito nel file template, qualsiasi altro file inserito nel body del template user il nuovo aspetto. Crea un bean con scope di request che specifica il contenuto da usare per il tag get. Il contenuto pu essere direttamente scritto o incluso da una JSP o da un file HTML. Recupera il contenuto da un bean con scope di request per usarlo nel layout di un template

insert put get

Comunque, i componenti di Tiles offrono maggiori funzionalit rispetto a questi tag. C' chi ancora li usa, ma la maggior parte degli sviluppatori sta migrando verso Tiles.

8.8 La libreria Nested


Le librerie dei tag personalizzati di Struts offrono potenza e flessibilit allo sviluppatore. La libert di accedere alle propriet di un bean a qualsiasi livello mediante l'impiego delle propriet annidate offre ancora pi potenza. Ci sono per alcune restrizioni all'utilizzo di questa tecnica. Quando si tenta di annidare i tag uno dentro l'altro, il tag all'interno ha alcune dipendenze dal tag all'esterno che possono rendere ingombrante o anche impossibile la visualizzazione dei dati dinamici. La maniera per aggirare il problema esiste e di solito implica l'uso di scriptlet con Java all'interno della pagina JSP. I tag "annidati" sono stati ideati per risolvere questo tipo di problema per arricchire le librerie di tag di Struts. La libreria di tag Nested stata creata da Arron Bates per rendere pi facile l'annidamento dei tag di Struts e per adeguarsi alla capacit del livello Model di annidare i bean. I tag annidati sono diventati tanto diffusi da essere aggiunti alle librerie fondamentali. I tag annidati vanno in parallelo con i tag attualmente supportati da Struts. Ad esempio ci sono tag annidati HTML, tag annidati Logic e tag annidati Bean. Non c' bisogno di lag annidati Tmplate, ed per questo che non esistono. La differenza consiste nel fatto che l'operazione di annidmento meglio supportata dalle versioni dei tag annidati. I tag all'interno della libreria Nested sono usati nello stesso modo delle versioni normali. Esistono poche differenze che consentono ai tag di sapere quando sono annidati l'uno nell'altro e per questo possono accedere alle propriet del tag superiore. A parte queste differenze trascurabili, essi sono molto simili a quelli non annidati. Si possono recuperare informazioni sui tag annidati nella documentazione sulle API di Struts. Invece all'URL http://www.keyboardmonkey.com/struts/index.html sono disponibili tutorial e documentazione integrativa.

8.9 Altre utili librerie di tag


I tag personalizzati non sono le uniche librerie di tag a funzionare con le applicazioni di Struts. Anche se i tag di Struts rendono molto facile l'utilizzo di certi aspetti del framework, possono tornare utili anche altri tag per lo sviluppo di certe applicazioni e per una riduzione del tempo di sviluppo di un progetto. Si parler solo di due delle pi comuni risorse presso cui reperire librerie di tag aggiuntive, ma anche altri siti o produttori offrono librerie, gratis o a basso costo. Si dovrebbe sempre controllare se esiste gi il tag di cui si necessita o se ce n' uno che si pu adattare o estendere per le proprie necessit.

8.9.1 Il progetto Jakarta Taglibs


Una delle risorse migliori costituita da un altro progetto Jakarta dal nome Taglibs. L'obiettivo di Taglibs consiste nell'offrire un archivio open source per librerie di tag personalizzati delle JSP e le estensioni ai tool per la pubblicazione sul web. Esistono circa 25 librerie di tag diverse nel progetto Taglibs, ciascuna delle quali offre alcune funzionalit uniche di cui la maggior parte delle web application prima o poi necessita. Le librerie di tag sono Ubere e mettono a disposizione il

codice sorgente, il che permette non solo una migliore comprensione di come si sviluppano i tag JSP ma anche di modificare o estendere i tag a seconda delle esigenze della propria organizzazione. Non ha senso elencare i tag disponibili, dal momento che se ne aggiungono di nuovi con regolarit. La miglior cosa dare un'occhiata al sito all'indirizzo http://jakarta.apache.org/taglibs/.

8.9.2 JSPTags.com
Un'altro utile silo web che contiene molti tag delle JSP http://jsptags.com. Questo sito in piedi da parecchi anni e contiene molte librerie di tag a disposizione gratuitamente ma anche una fonte autorevole di informazioni sulle web application. Ci si accerti effettuare un controllo su questo sito prima di intraprendere la creazione di un proprio tag. Qualcuno potrebbe aver gi creato il tag di cui si ha bisogno, risparmiandoci il tempo di sviluppo e di test.

8.10 La JSP Standard Tag Library (JSTL)


I primi membri del gruppo che defin le specifiche delle JSP avevano come obiettivo quello di definire un set di tag standard delle JSP all'interno di tali specifiche. In questo modo i produttori avrebbero potuto generare le loro versioni dei tag standard ma avrebbero permesso agli sviluppatori di cbntare sul fatto che questo insieme standard di tag fosse disponibile in tutti i container a norma. Tuttavia, a causa di limiti temporali, questa caratteristica non fu inclusa nelle prime specifiche delle JSP. Da allora molti produttori hanno creato le proprie versioni delle librerie di tag pi comunemente usate, ma queste versioni;sono tanto diverse da non permettere che gli sviluppatori possano trasferire le loro plagine JSP da un container ad un'altro senza modificare le pagine stesse. La JSR 52, ovvero la JSP Standard Tag Library (JSTL), punta a risolvere questo problema. La JSR 52 definisce un insieme di tag standard che dovrebbero essere presenti in qualsiasi container che segua le specifiche delle JSP. La prima versione di queste specifiche stata completata e approvata e il gruppo ha rilasciato un'implementazione di riferimento delle librerie dei tag. I tag contenuti nella prima versione possono essere raggruppati in quattro aree distinte: Tag fondamentali Tag di internazionalizzazione Tag XML Tag SQL

8.10.1 Tag fondamentali della JSTL


I tag fondamentali includono quelli relativi a espressioni, controllo del flusso e un modo generico di accedere a risorse basato sugli URL il cui contenuto potrebbe essere incluso o elaborato all'interno della pagina JSP. Questi includono tag come if, forEach, import, redirect e molti altri.

8.10.2 Tag di internazionalizzazione delle JSTL


I tag di internazionalizzazione sono divisi in due gruppi: messaggistica e formattazione I tag di messaggistica coadiuvano gli autori di pagine con la creazione di messaggi che possono essere adattati a qualsiasi impostazione locale disponibile nel container JSP. Annoverano tag come bundle e message. I tag di formattazione consentono la formattazione e l'interpretazione in base a criteri di localizzazione di vari elementi di dati come numeri, valute, date e orari Vi sono inclusi tag come formatDate, parseNumber, e timeZone.

8.10.3 Tag XML di JSTL


La libreria di tag XML di JSTL include tag che permettono l'accesso a documenti XML. I tag sono basati su XPath. I tag XML usano XPath come linguaggio di espressione locale.

8.10.4 Tag SQL di JSTL


I tag SQL di JSTL permettono l'accesso diretto a risorse JDBC Sono progettati per una veloce prototipizzazione e per applicazioni semplici. Per applicazioni pi avanzate e di maggiori dimensioni, le operazioni sui database non dovrebbero trovarsi al livello di presentazione: di solito sono collocate e svolte al livello del modello. Questo aiuta ad assicurare la separazione delle responsabilit. I tag all'interno della libreria SQL di JSTL includono tag per la configurazione delle risorse JDBC e anche per le query e gli aggiornamenti a un database.

8.10.5 Un nuovo linguaggio di espressione


La libreria JSTL introdurr anche un nuovo linguaggio di espressione (expression language, EL) per rendere pi facile agli autori di pagine web scrivere codice che acceda ai dati delle applicazioni senza costringerli ad apprendere un linguaggio di programmazione completo come Java. L'EL diverr parte con ogni probabilit delle JSR 152 (le specifiche per le JSP 1.3), ma stato incluso in JSTL per assicurare che tale specifica sia disponibile per JSTL. I due gruppi lavorano a stretto contatto per cui l'EL soddisfa le necessit di entrambi. Le librerie dei tag di JSTL sono disponibili in due versioni. Una conosciuta col nome di JSTL-RT, continuer a supportare le espressioni nel modo in cui attualmente sono usate. La seconda versione, conosciuta come JSTL-EL, supporter il nuovo expression language. Entrambe le versioni saranno supportate. Per informazioni pi approfondite su EL, si faccia riferimento alle specifiche JSTL 1.0 che possono essere scaricate dall'URL http://jcp.org/jsr/detail/52.jsp.

8.10.6 Prerequisiti della JSTL


La libreria JSTL richiede un servlet container che supporti le specifiche per le servlet 2,3 e la versione 1.2 delle specifiche per le JSP. Se si usa un container che non supporta tali specifiche, non si potr usare la JSTL. Tomcat le supporta entrambe ma ce ne sono molti altri. Ci si assicuri che il proprio produttore di container supporti la JSTL prima di usarla.

8.10.7 JSTL e Struts


Le librerie di tag Bean e Logic possono essere anche eliminate in favore dei tag JSTL. Nelle prossime versioni di Struts si controllino i cambiamenti in queste due librerie per facilitare la migrazione. Dovrebbe essere ovvio da uno sguardo ai tag inclusi in JSTL che non tutti i tag di Struts saranno sostituiti. I tag all'interno della libreria HTML, in particolare, saranno mantenuti ancora per un po' di tempo.

CAPITOLO 9

Estendere il framework Struts

Uno dei maggiori vantaggi dell'impiego di un framework sta nella possibilit di estenderlo e personalizzarlo sulla base delle necessit dell'applicazione. Il framework Struts non fa eccezione: offre molteplici e importanti punti di estensione per gli sviluppatori. Questo capitolo getta luce su alcuni di questi punti di estensione e discute i pro e i contro dell'operazione di estensione del framework.

9.1 Che cosa sono i punti di estensione?


Si pensi a un framework come a una casa con parte della struttura gi completa ma con la possibilit di modificare certe caratteristiche quali tappezzeria e pitture murali. Se le caratteristiche predefinite sono gi adeguate alle proprie necessit, non si deve cambiare alcunch. Se, ad esempio, le imbiancature sono di proprio gradimento, c' una cosa in meno di cui preoccuparsi. Questa solo un'analogia del funzionamento del framework ed un gran vantaggio quando si scrivono applicazioni. Se la funzionalit che soddisfa i requisiti della propria applicazione gi presente nel framework, non ci si deve pi preoccupare di questo aspetto dell'applicazione. Gli sviluppatori sono pertanto liberi di concentrarsi sugli aspetti fondamentali dell'applicazione piuttosto che sull'infrastruttura. Questa analogia non precisissima, ma il punto che un buon framework dovrebbe offrire la maggior parte delle infrastrutture: fondamenta e tubature, ad esempio. L'aspetto di maggior importanza di un framework comunque quello di fornire nelle sue parti costituenti dei punti in cui sia possibile effettuare delle estensioni. Il punti di estensione del framework, detti anche hooks (ganci), permettono di estendere in determinati luoghi il framework per adattarlo ai requisiti dell'applicazione. Di estrema importanza sono il dove e il come il framework offre questi agganci. Se sono offerti in maniera non corretta o nei punti sbagliati, diventa molto difficile per un'applicazione adattarsi al framework, che diventa cos meno utilizzabile. Il resto del capitolo si focalizza sulla collocazione di questi punti in e sul modo in cui se ne pu trarre vantaggio per scrivere funzionalit specifiche per la propria applicazione.

9.2 Punti di estensione generali


Questa sezione affronter alcuni punti di estensione che interessano l'intero framework, non necessariamente un particolare strato. Come si pu intuire, il pi importante di questi il meccanismo PlugIn.

9.2.1 Usare il meccanismo Plugln


Struts offre un meccanismo che permette ai componenti di essere collegati e caricati dinamicamente. Questa caratteristica stata aggiunta nella versione 1.1 ed supportata tramite l'uso dell'interfaccia org.apache.struts.action.PlugIn. Qualsiasi classe Java pu funzionare come plug-in se implementa l'interfaccia PlugIn. Un plug-in quindi una classe Java che necessario inizializzare all'avvio dell'applicazione Struts e distruggere quando si spegne l'applicazione. L'interfaccia PlugIn contiene due metodi come mostra l' Esempio 9-1

Esempio 9-1. L'interfaccia org.apache.struts.action.PlugIn

public interface PlugIn { /** * Notification that the specified application module is being started. */ public void init(ActionServlet servlet, ApplicationConfig config) throws ServletException; /** * Notification that the application module is being shut down. */ public void destroy( ); }
Durante l'avvio di un'applicazione Struts, ActionServlet invoca il metodo init() per ogni PlugIn configurata; il framework supporto la configurazione di una o pi PlugIn per applicazione. Le routine di inizializzazione necessarie al proprio plug-in dovrebbero essere svolte nel corso del metodo init(): un buon momento per inizializzare una connessione a un database o, ad esempio, per stabilire una connessione a un sistema remoto.[1]
[1]

Si potrebbe anche inizializzare una connessione da un database attraverso l'uso di datasource

Il secondo metodo che il proprio plug-in deve implementare destroy(). Il framework invoca questo metodo quando l'applicazione viene arrestata. La necessaria pulizia andrebbe effettuata in questo frangente: il momento adatto per chiudere le connessioni al database, le socket remote e qualsiasi altra risorsa che il plug-in stia usando. Ecco un esempio concreto dell'uso di PlugIn in Struts. Si supponga che l'applicazione debba comunicare con uno strato EJB. Una delle prime cose da fare fare prima che possa avvenire questa comunicazione sta nell'ottenere un riferimento al servizio Java Naming and Directory Interface (JNDI). JNDI permette ai client di accedere a svariati servizi di nomi e directory come datasource, sessioni JavaMail e factory di EJB home. L'Esempio 9-2 mostra l'acquisizione conPlugIn di un InitialContext per un servizio JNDI.
Esempio 9-2. Un esempio del meccanismo PlugIn di Struts

package com.oreilly.struts.storefront.framework.ejb; import import import import import import import java.util.HashTabella; javax.naming.InitialContext; javax.naming.Context; org.apache.struts.action.ActionServlet; org.apache.struts.config.ApplicationConfig; org.apache.struts.action.PlugIn; javax.servlet.ServletException;

public class JNDIConnectorPlugin implements PlugIn { private String jndiFactoryClass; private String jndiURL; private Context initCtx = null; public JNDIConnectorPlugin( super( ); } ) {

public void init(ActionServlet servlet, ApplicationConfig config) throws ServletException{ // Get the host and port where the JNDI service is running jndiFactoryClass = servlet.getInitParameter("jndi-factory-class"); jndiURL = servlet.getInitParameter("jndi-url"); try{ HashTabella props = new HashTabella( ); // The EJB spec also allows these to be read from the jndi.properties file props.put( Context.INITIAL_CONTEXT_FACTORY, jndiFactoryClass );

props.put( Context.PROVIDER_URL, jndiURL ); initCtx = new InitialContext(props); }catch( Exception ex ){ throw new ServletException( ex ); } // Store the JNDI Context into the ServletContext servlet.getServletContext( ).setAttribute( "Storefront.InitCtx", initCtx ); } public void destroy( ){ try{ if ( initCtx != null ){ initCtx.close( ); initCtx = null; // No need to remove from ServletContext because app is being shut down } }catch( Exception ex ){ ex.printStackTrace( ); } } }
Quando il framework invoca il metodo init() della classe JNDIConnectorPlugin, il plug-in crea un oggetto InitialContext e lo conserva all'interno del ServletContext. Questo permette all' InitialContext di JNDI di essere usato dall'intera applicazione quando ve ne bisogno. Si tratta solo di un semplice esempio; ci sono molti possibili impieghi del meccanismo PlugIn. Per esempio, il framework Validator, che sar trattato nel Capitolo 11, adopera il meccanismo PlugIn per inizializzare le regole di validazione di un'applicazione.

9.2.1.1 Aggiungere il plug-in al file di configurazione


Il plug-in deve essere dichiarato nel file dj configurazione di Struts perch il framework lo riconosca e possa inizializzarlo alla partenza. Nel file di configurazione viene specificato per mezzo dell'elemento plug-in:

<plug-in className="com.oreilly.struts.storefront.framework.ejb.JNDIConnectorPlugi n"/>


possibile passare le propriet alla propria classe PlugIn anche con l'elemento set-property.

Se si specifica pi di un elemento plug-in, questi saranno inizializzati nell'ordine in cui sono elencati nel file di configurazione

Per maggiori dettagli sulla configurazione dell'elemento plug-in o per passare delle propriet ad un'istanza si veda "Il DTD di configurazione di Struts" nel Capitolo 4.

9.2.2 Estensione delle classi di configurazione di Struts


Una delle maggiori novit della versione 1.1 di Struts consiste nel package org.apache.struts.config. Questo package comprende tutte le classi che sono impiegate per la rappresentazione in memoria delle informazioni conservate nel file di configurazione di Struts. Vi rappresentato tutto, dalle configurazioni di Action alle configurazioni di PlugIn. Se si torna al Capitolo 4, si vedr che la maggior parte degli elementi di configurazione nel file di configurazione di Struts permettono di dare un nome completo di classe Java alla classe di configurazione per mezzo dell'attributo className. Questo d la possibilit di personalizzare gli elementi di configurazione e di inserire ulteriori informazioni.

Per esempio si supponga di voler passare un parametro aggiuntivo alle proprie classi Action. Per default si impiega la classe ActionMapping che estende ActionConfig dal package config. Per trasmettere un parametro aggiuntivo come ssl-required che controlla se usato HTTP o HTTPS, si pu estendere la classe ActionMapping e configurare questa estensione attraverso l'attributo className. La possibilit di estendere gli elementi di configurazione di Struts attraverso questo meccanismo rende il framework estremamente duttile e flessibile, tanto da poter venire incontro alle necessit di qualsiasi applicazione.

9.3 Punti di estensione del controller


La serie successiva di possibili punti di estensione sta nello strato controller, e a qualcuno di questi si gi fatto cenno nei capitoli precedenti. Nel corso dei paragrafi successivi, tali punti di estensione saranno approfonditi.

9.3.1 Estendere la classe ActionServIet


Nelle prime versioni di Struts era un dato di fatto che si dovesse estendere la classe ActionServlet dal momento che la maggior parte della funzionalit del controller, fatta eccezione per la classe Action, si trovava in tale classe. Con Struts 1.1 le cose cambiano, tuttavia ci sono ancora valide ragioni per estendere la classe ActionServlet. Come stato evidenziato dal Capitolo 5, le routine di validazione invocate quando viene lanciata un'applicazione Struts stanno nella classe ActionServlet. Se necessario modificare il modo in cui il framework si inizializza, questo il luogo dove operare le modifiche. Per estendere la classe ActionServlet, si crei una sottoclasse di org.apache.struts.action.ActionServlet. Si potranno poi ridefinire il metodo o i metodi per i quali si vuole un diverso funzionamento. Una volta fatto questo, si deve modificare il descrittore di deploy cos che l'applicazione usi l' ActionServlet personalizzata:

<servlet> <servlet-name>storefront</servlet-name> <servlet-class> com.oreilly.struts.storefront.framework.ExtendedActionServlet </servlet-class> </servlet>


La maggior parte del funzionamento di elaborazione delle richieste in Struts 1.1 stato spostato nella classe RequestProcessor. Se necessario personalizzare il modo in cui Struts elabora una richiesta, si leggano i paragrafi seguenti.

9.3.2 Estendere la classe RequestProcessor


Se occorre ridefinire le funzionalit della classe RequestProcessor, si deve informare il framework della volont di impiegare la propria classe personalizzata al posto della versione predefinita. Ci si ottiene con la modifica del file di configurazione di Struts. Se questo file non contiene gi un elemento controller al proprio interno, occorre aggiungerne uno (ci sono molti altri attributi che possono essere configurati all'interno dell'elemento controller; per maggiori dettagli si veda il Capitolo 4):

<controller contentType="text/html;charset=UTF-8" debug="3" locale="true" nocache="true" processorClass="com.oreilly.struts.framework.CustomRequestProcessor"/>


L'attributo processorClass permette di specificare il nome completo di classe Java del proprio RequestProcessor. Il framework Struts creer un'istanza del RequestProcessor personalizzato all'avvio e lo user per elaborare tutte le richieste per l'applicazione. Dal momento che ciascun modulo applicativo pu avere il proprio file di configurazione di Struts, si pu specificare un RequestProcessor per ogni modulo.

9.3.2.1 Usare il metodo processPreprocess()


Ci sono molti metodi che possono essere ridefiniti con la classe RequestProcessor. Uno dei metodi progettati tenendo presente l'estensione il metodo processPreprocess(). Questo metodo invocato per qualsiasi rcquest.

In maniera predefinita non compie alcuna azione nelle request ma possibile usarlo in vari modi per modificare il funzionamento della normale elaborazione delle richieste. Il metodo processPreprocess() appare come:

protected boolean processPreprocess( HttpServletRequest request, HttpServletResponse response ){ return (true); }


Questo metodo invocato all'inizio dello stadio di elaborazione delle richieste, prima dell'invocazione del metodo ActionForm e prima che il metodo execute() sia invocato sull'attributo Action. Come risultato predefinito restituisce il valore true, che informa la RequestProcessor del fatto che pu continuare a elaborare la request. Tuttavia possibile ridefinire il metodo per operare un certo tipo di operazioni logiche e se la request non deve essere elaborata ulteriormente, si. pu fare restituire il valore false. Quando processPreprocess() restituisce false, la .RequestProcessor cessa di elaborare la richiesta e semplicemente riparte dalla chiamata doPost() o doGet(). Di conseguenza, sta allo sviluppatore inoltrare o redirigere la request all'interno del metodo processPreprocess(). L'Esempio 9-3 illustra un approccio del genere. Questo esempio controlla se la request proviene dalla macchina locale. Se questa condizione vera, il valore restituito true e la richiesta pu aver luogo. Se la request proviene da un altro host la richiesta rediretta alla pagina di "unauthorized access" e il metodo restituisce il valore false
Esempio 9-3. Uso del metodo processPreprocess()

protected boolean processPreprocess( HttpServletRequest request, HttpServletResponse response ){ boolean continueProcessing = true; // Get the name of the remote host and log it String remoteHost = request.getRemoteHost( ); log.info( "Request from host: " + remoteHost ); // Make sure the host is from one that you expect if ( remoteHost == null || !remoteHost.startsWith( "127.") ){ // Not the localhost, so don't allow the host to access the site continueProcessing = false; ForwardConfig config = appConfig.findForwardConfig("Unauthorized"); try{ response.sendRedirect( config.getPath( ) ); }catch( Exception ex ){ log.error( "Problem sending redirect from processPreprocess( )" ); } } return continueProcessing; }
La cosa pi importante da notare in questo esempio che anche se il metodo processPreprocess() restituisce false, sta ancora a questo metodo prendersi carico della redirect della richiesta. La restituzione di false informa il RequestProcessor solo del fatto che non deve continuare. Il controller non compie alcuna azione con la request; la responsabilit passa al metodo processPreprocess().

Il modo in cui l'Esempio 9-3 specifica il path nel metodo sendRedirect() ci evita di inserire direttamente nel codici, l'URI della risorsa unauthorized_access.jsp In questo modo, se la pagina per effettuare il forward cambia, questo metodo non dovr essere modificato.

Ci sono altri due modi in cui si pu operare la stessa logica senza impiegare il metodo processPreprocess(). Un'alternativa consiste nell'usare una servlet filtro (che fa parte delle API Servlet 2.3). I filtri permettono di ispezionare la richiesta persino prima che raggiunga il controller di Struts. Tuttavia ci sono due problemi di cui essere consapevoli. In primo luogo, dal momento che i filtri sono parte delle API 2.3 non potranno essere usate se si usa un servlet container che supporta soltanto la versione 2.2. In secondo luogo, dal momento che il filtro ispeziona la request al principio dello stadio di elaborazione, i filtri non hanno un accesso molto facile alle API di Struts. Questo rende difficoltoso ricercate ActionForward o qualunque altro sistema che che normalmente si usa nel metodo processPreprocess().

Questo avviene perch, dal momento che il controller di Struts non ha ancora ricevuto la richiesta nel momento in cui il filtro la ispeziona, esso non ha avuto la possibilit di selezionare un modulo applicativo appropriato.

9.3.3 Estendere la classe Action di base


Ci sono stati diversi punti nei capitoli precedenti in cui si citata una tecnica per la creazione di una Action base che estenda la classe di Struts Action e che la usi come superclasse per altre azioni. Una delle ragioni per fare questo che in molte applicazioni c' una logica comune che deve essere implementata dalla maggior parte delle classi Action. Se si lascia che questa superclasse Action contenga la maggior parte di questo codice, si elimina la ridondanza. L'Esempio 9-4 offre una versione molto semplice della classe base Action che ha questo tipo di funzionamento.
Esempio 9-4. Una classe Action di base

import import import import import import import import import import import

java.util.Collection; java.util.LinkedList; java.util.List; java.util.Locale; java.util.Iterator; javax.servlet.http.*; org.apache.struts.action.*; com.oreilly.struts.storefront.framework.util.IConstants; com.oreilly.struts.storefront.framework.exceptions.*; com.oreilly.struts.storefront.framework.UserContainer; com.oreilly.struts.storefront.service.IStorefrontService;

/** * An abstract Action class that all Storefront Action classes can extend. */ abstract public class StorefrontBaseAction extends Action { /** * The default execute( ) method that all actions must implement. */ public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response ) throws Exception{ // It just calls a worker method that contains the real execute logic return executeAction( mapping,form,request,response,getUserContainer(request)); } /** * The actual do work method that must be overridden by the subclasses. */ abstract public ActionForward executeAction( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response, UserContainer userContainer ) throws Exception; // This super Action is also a good place to put common utility methods public boolean isLoggedIn( HttpServletRequest request ){ UserContainer container = getUserContainer(request); return ( container != null && container.getUserView( ) != null ); } /** * Retrieve the UserContainer for the user tied to the request. */ protected UserContainer getUserContainer(HttpServletRequest request) { HttpSession session = request.getSession( );

UserContainer userContainer = (UserContainer)session.getAttribute(IConstants.USER_CONTAINER_KEY); // Create a UserContainer if one doesn't exist already if(userContainer == null) { userContainer = new UserContainer( ); userContainer.setLocale(request.getLocale( )); session.setAttribute(IConstants.USER_CONTAINER_KEY, userContainer); } return userContainer; } }
La classe StorefrontBaseAction dell'Esempio 9-4 mostra come si possa impiegare Action per svolgere un funzionamento ripetitivo di modo che tutte le sottoclassi non debbano svolgerlo per conto loro. Per esempio, si supponga che tutte le proprie classi Action debbano avere UserContainer per l'utente corrente e debbano poter usare delle informazioni al suo interno (ad esempio, ID dell'utente o permessi di sicurezza). Da una parte si potrebbero forzare tutte le classi Action a ottenere UserContainer per loro conto, gestire la situazione in caso non ve ne sia uno e cos via. Un modo alternativo, maggiormente gestibile, consiste nel porre quella funzionalit in una superclasse Action e nel passare UserContainer alle sottoclassi. Come riportato dall' Esempio 9-4StorefrontBaseAction implementa il metodoexecute() ma all'interno di quel metodo prende un'istanza di UserContainer e la passa come argomento al metodo executeAction(). Ogni sottoclasse implementa il metodo astratto executeAction() e fa passare, istanzia e garantisce che non sia "null" UserContainer. Questo solo un banale esempio di quello che si pu fare. Qualsiasi funzionamento che le classi debbano svolgere un candidato per la propria implementazione nella superclasse Action, cos, quando giunge l'occasione di modificare l'implementazione, deve essere modificato solo il funzionamento nella superclasse.

9.4 Estendere i componenti della view


Di solito ci sono meno ragioni o necessit per estendere i componenti posti all'interno del livello view rispetto a quelli nel livello controller. Di solito, i livelli view sono scritti per una e una sola applicazione; ad esempio, improbabile che una pagina JSP scritta per un'applicazione possa essere impiegata all'interno di una applicazione diversa. Questo non sempre il caso, ma le differenze tra look-and-feel e contenuto rendono improbabile un simile tipo di riutilizzo. L'unico spazio in cui si creano spesso delle estensioni all'interno dello strato view di Struts nelle librerie di tag JSP.

9.4.1 Estensione dei tag personalizzati di Struts


I tag personalizzati messi a disposizione da Struts possono essere riutilizzati tra le applicazioni e tra i domini delle applicazioni. Per questo motivo, diventa comprensibile che personalizzazione ed estensione siano pi probabili all'interno di questi componenti piuttosto che all'interno delle pagine JSP. Dal momento che i gestori dei tag sono normali classi Java, la specializzazione si ottiene mediante l'operazione di subclassing. Anche se si potrebbe estendere qualsiasi tag, la libreria HTML l'unica che probabilmente sar necessario personalizzare (di solito perch i tag personalizzati all'interno di questa libreria hanno un grandissimo impatto sul contenuto dello strato view). Ad ogni modo, qualunque sia il tag che si desidera estendere, si dovranno creare le proprie librerie di tag per mantenere le proprie estensioni dei tag. Si possono modificare le proprie librerie di tag Struts e includervi le proprie nuove classi, ma un'operazione del genere renderebbe il passaggio a una nuova versione di Struts molto pi difficoltoso.Sar quindi meglio creare proprie librerie di tag che contengano solo i tag della propria applicazione. Una volta creato un file .tld per le proprie estensioni e registrato con il descrittore di deploy della web application si possono adoperare i propri tag come tutti gli altri.

9.5 Estensione dei componenti del model


Dal momento che il framework Struts non offre una gran quantit di componenti per il livello model, le estensioni sono meglio trattate in altri libri sulla programmazione Java.

Tuttavia ci sono due classi che potrebbero appartenere alla categoria dei componenti del model che possibile estendere. Non sono certo rappresentative della sostanza di un model ma sono responsabili del mantenimento dello stato del model.

9.5.1 Le classi UserContainer e ApplicationContainer


In precedenza si sono citate le classi UserContainer e ApplicationContainer senza definireesattamente cosa siano. Queste due classi non fanno parte di Struts ma sono state create come parte dell'applicazione di esgkipio Storefront. Lo scopo di queste classi consiste nell'immagazzinare informazioni sull'utente e sull'applicazione nelle proprie istanze e non piuttosto negli oggettiHttpSession e ServletContext rispettivamente. Uno dei problemi dell'immagazzinamento dati nelle sessioni HttpSession consiste nel fatto che l'interfaccia per conservare e recuperare dati dagli oggetti di sessione non fortemente tipizzata. In altre parole, l'interfaccia per qualsiasi dato :

public void setAttribute( "permissionsKey", permissions ); public Object getAttribute( "permissionsKey" );


Il client deve conoscere la chiave in cui conservato il dato per poter inserire un oggetto dal luogo dove conservato o recuperarlo. Alcuni programmatori preferiscono un'interfaccia pi tipizzata, come ad esempio:

userContainer.setPermissions( permissions ); userContainer.getPermissions( );


Il client, in questo caso, non si deve predecupare di dove o come siano conservati i dati. Potrebbe essere un oggetto HttpSession o qualsiasi altro mezzo di immagazzinamento di dati; il client non obbligato a svolgere queste operazioni perch non e obbligato a usare direttamente i metodi della sessione HttpSession. Non c' niente di particolarmente complesso nella classe UserContainer. Non altro che un ordinario JavaBean contenente variabili di istanza e metodi di get e set per le propriet. L'Esempio 9-5 illustra una classe UserContainer.
Esempio 9-5. Una semplice classe UserContainer

package com.oreilly.struts.storefront.framework; import java.util.Locale; import javax.servlet.http.HttpSessionBindingListener; import javax.servlet.http.HttpSessionBindingEvent; import com.oreilly.struts.storefront.customer.view.UserView; /** * Used to store information about a specific user. This class is used * so that the information is not scattered throughout the HttpSession. * Only this object is stored in the session for the user. This class * implements the HttpSessionBindingListener interface so that it can * be notified of session timeout and perform the proper cleanup. */ public class UserContainer implements HttpSessionBindingListener { // The user's shopping cart private ShoppingCart cart = null; // Data about the user that is cached private UserView userView = null; /** * The Locale object for the user. Although Struts stores a Locale for * each user in the session, the locale is also maintained here. */ private Locale locale; public UserContainer( super( ); initialize( ); } ) {

public ShoppingCart getCart( return cart; }

) {

public void setCart(ShoppingCart newCart) { cart = newCart; } public void setLocale(Locale aLocale) { locale = aLocale; } public Locale getLocale( return locale; } ) {

/** * The container calls this method when it is being unbound from the * session. */ public void valueUnbound(HttpSessionBindingEvent event) { // Perform resource cleanup cleanUp( ); } /** * The container calls this method when it is being bound to the * session. */ public void valueBound(HttpSessionBindingEvent event) { // Don't need to do anything, but still have to implement the // interface method. } public UserView getUserView( return userView; } ) {

public void setUserView(UserView newView) { userView = newView; } /** * Initialize all of the required resources */ private void initialize( ) { // Create a new shopping cart for this user cart = new ShoppingCart( ); } /** * Clean up any open resources. The shopping cart is left intact * intentionally. */ public void cleanUp( ) { setUserView( null ); } }

Una

cosa da notare che la classe UserContainer dell'Esempio 9-5 implementa l'interfaccia HttpSessionBindingListener, I metodi di quest'interfaccia permettono all' UserContainer di essere avvisati quando esso legato o meno alla sessione. Questo gli permette di compiere qualsiasi ripulitura dell'oggetto necessaria. Viene creata un'istanza di UserContainer per ciascun nuovo utente nel momento in cui esso entra nell'applicazione. L'oggetto UserContainer di per s stesso conservato nella sessione e deve avere la durata di questa. Se un qualsiasi utente esce e rientra nel sito, di solito viene creato un nuovo UserContainer. La ApplicationContainer ha pressappoco un utilizzo analogo ma a livello applicazione e nondi sessione. La sua utilit consiste nel poter conservare o fare il caching di informazioni dell'applicazione necessarie a tutti gli utenti. La classe ApplicationContainer opera con elementi come elenchi di selezione, propriet di configurazione e altri dati non

specifici che una volta ricevuti devono essere solo conservati. Questa classe viene creata quando l'applicazione avviata la prima volta e distrutta quando l'applicazione viene arrestata.

9.6 Inconvenienti dell'estensione di Struts


Esistono alcuni risvolti negativi legati a personalizzazione o estensione del framework. Anche se si suggerito che la personalizzazione un obiettivo previsto dell'utilizzo di un framework, come con altri elementi nello sviluppo di software vi sono dei compromessi da rispettare. Quando si estende un framework, uno dei pi grandi problemi che si possano incontrare che cosa fare quando sono rilasciate nuove versioni del framework stesso. A meno che gli sviluppatori del framework non abbiano posto attenzioni particolari alla compatibilit con le versioni precedenti, l'applicazione potrebbe non funzionare pi correttamente con nuove versioni del framework. Ad esempio, Struts ha ricevuto dei cambiamenti significativi alle proprie API tra le versioni 1.0 e 1.1. In particolare, il metodo perform() non pi il metodo preferito per invocare Action; al suo posto usa il metodo execute(). Per fortuna gli sviluppatori che lavorano su Struts sono stati molto attenti e hanno assicurato che la nuova funzionalit fosse compatibile con le applicazioni scritte con versioni precedenti. Si dovrebbe avere la stessa cura quando si scrivono le proprie applicazioni. Se per esempio si ridefiniscono i metodi di Struts per ottenere una particolare funzionalit, possibile che il metodo usato possa essere deprecato o rimosso nelle versioni successive del framework. Di fatto, parecchi commenti nel codice sorgente del framework indicano che alcune parti del framework potrebbero essere ritirate. Anche se quasi impossibile proteggere la propria applicazione da tutti i cambiamenti che potranno avvenire, meglio intraprendere lo sviluppo con la necessaria consapevolezza. L'uso di un framework, anche se cos completo e utile come Struts, non esime dal mantenere attiva l'attenzione. Si potrebbero avere gli stessi problemi di aggiornamento sia se ci si scrive il proprio framework sia nel caso in cui se ne usa uno di terze parti.

CAPITOLO 10

Gestione delle eccezioni


Java usa le eccezioni per informare i client che successo qualcosa di anormale durante l'elaborazione di un metodo. Il client viene avvertito tramite un'istanza della specifica eccezione lanciata e la scelta dell'azione da intraprendere quando si verifica un'eccezione completamente a carico del client. In alcuni casi il client potrebbe anche scegliere di non prendere alcuna iniziarvi e la JVM continuer a cercare un gestore per l'eccezione. La gestione delle eccezioni all'interno delle proprie applicazioni Struts non molto diversa. Quando si verifica una condizione anormale, viene lanciata un'eccezione al client chiamante, per avvisarlo del problema. La differenza per le web application, e in special modo Struts, consiste nella scelta dell'azione da intraprendere per conto del client e nel modo in cui queste eccezioni siano restituite all'utente finale. Questo capitolo prende in considerazione il modo in cui si pu usare correttamente il meccanismo di gestione delle eccezioni di Java all'interno delle proprie applicazioni Struts per irrobustirle e farle rispondere in modo appropriato quando le cose non vanno come ci si aspetta. Verr evidenziata la differenza tra lo svolgere la gestione delle eccezioni a livello di programmazione e l'uso della nuova caratteristica dichiarativa aggiunta alla versione 1.1 di Struts.

10.1 Gestione delle eccezioni Java


Prima di addentrarsi nell'argomento della gestione delle eccezioni in Struts, dovrebbe essere ben chiaro ci che succede quando un metodo lancia un'eccezione. La comprensione del processo che ha luogo nella JVM quando viene generata un'eccezione pu chiarire l'importanza sia di lanciare eccezioni per i corretti motivi, che di lanciare quelle di tipo adeguato. Dal momento che la gestione delle eccezioni causa un sovraccarico ulteriore per la JVM, importante sempre curare un impiego corretto delle eccezioni.

10.1.1 Eccezioni Java


In Java, le eccezioni sono oggetti creati quando si verifica una condizione anormale (cui spesso ci si riferisce come a una exception condition, condizione di eccezione) durante l'esecuzione di un'applicazione. Quando un'applicazione Java lancia un'eccezione, crea oggetto che discende da java.lang.Throwable. La classe Throwable ha due sottoclassi dirette: java.lang.Error e java.lang.Exception. La Figura 10-1 mostra un albero gerarchico parziale per la classe Throwable.

Figura 10-1. Gerarchia parziale per la classe Throwable

Lo spazio non permette di mostrare tutti i discendenti della classe Throwable che solamente nella libreria fondamentale Java sono pi di cento fra sottoclassi dirette e indirette. Di solito le eccezioni discendenti da Exception sono lanciate per indicare condizioni anormali che possono di solito essere gestite dall'applicazione. Tutte le eccezioni create e lanciate dalla propria applicazione Struts dovrebbero essere sottoclassi della classe Exception, Le eccezioni che discendono dall'altro ramo di Throwable, ovvero che derivano dalla classe Error, sono riservate a problemi pi seri che capitano durante il ciclo di vita dell'applicazione. Per esempio, se non c' pi memoria disponibile per un'applicazione, sar generato un messaggio OutOfMemoryError e di solito nessun client pu far niente per questo. Per questo i client di solito non si preoccupano della gestione delle sottoclassi di Error. Nella maggior parte dei casi la stessa JVM che lancia delle istanze di Error o delle sue sottoclassi.

10.1.2 Lo stack di invocazione del metodo


La JVM usa una "pila" di invocazione del metodo, detta anche call stack, per tener traccia della successione delle invocazioni dei metodi di ogni thread. Lo stack conserva informazioni locali riguardo ai metodi chiamati risalendo al metodo originale main() dell'applicazione. Quando viene invocato un nuovo metodo, un nuovo elemento viene inserito in cima alla pila corrente e il nuovo metodo diventa il metodo che viene eseguito. Con ogni porzione della pila viene salvato lo stato locale di ciascun metodo. La Figura 10-2 illustra un esempio della pila Java.

Figura 10-2. Un esempio di pila Java dell'invocazione dei metodi

Quando un metodo Java termina correttamente, la JVM elimina la porzione della pila relativa a tale metodo prosegue con l'elaborazione del metodo precedente dal punto in cui era stata lasciata. Quando si verifica la condizione per un'eccezione, tuttavia, la JVM deve trovare un appropriato gestore delle eccezioni. Dapprima controlla se il metodo corrente "cattura" l'eccezione. Se la cattura, l'esecuzione continua nell'ambito definito dalla clausola di catch. Se il metodo corrente non offre la possibilit di catturare l'eccezione, la JVM inizier a gettare fuori dalla pila di chiamata i frame dei metodi finch non trovi un gestore per 1' eccezione o per una delle eccezioni gerarchicamente superiori che hanno generato l'eccezione in oggetto. Alla fine, se non trova un gestore per le eccezioni neanche se risale al metodo main(), il thread terminer. Se il thread quello principale e non ci sono altri thread in background, terminer l'applicazione stessa. Se la JVM trova un gestore delle eccezioni, l'elaborazione riprender da quel metodo, che viene posto in cima allo stack. L'importanza di sapere come la JVM gestisce le eccezioni consiste nel fatto che molte di esse, quando si verificano, discendono molto in profondit. Per la JVM potrebbe essere un lavoro durissimo localizzare un gestore delle eccezioni per una particolare eccezione specie se il gestore posto a un basso livello dello stack. Diventa molto importante perci inserire a livelli appropriati degli exception handler. Se ci si lasciano scappare le eccezioni, facile che finiscano per bloccare l'applicazione.

10.1.3 Qualcosa a proposito della clausola throws


Quando si determina la firma di un metodo per le classi che fanno parte di un'applicazione, molto importante stabilire correttamente le eccezioni che saranno lanciate dal metodo, con i loro parametri e il loro tipo restituito. Probabilmente avrete sentito parlare di "design by contract". L'idea che sta alla base di questo principio che l'insieme di metodi pubblici che una classe espone rappresenta un contratto virtuale tra il client e la classe stessa. Il client ha determinati obblighi nel modo in cui invoca il metodo, ma ci possono essere anche requisiti della stessa classe sempre nel contratto. Quando accade qualcosa di anormale e viene lanciata un'eccezione da un metodo della classe, il contratto in un certo senso viene rotto. La classe informa il client che non pu adempiere ai propri termini contrattuali. Sta interamente al chiamante decidere come gestire l'eccezione. Ecco perch la clausola throws di un metodo cos importante: forza un

client a decidere cosa far quando accadono delle condizioni anormali. Tuttavia, come si capir dalla prossima sezione, nessuna eccezione Java uguale ad un'altra.

10.1.4 Eccezioni checked e unchecked


Le eccezioni Java possono essere separate in due gruppi distinti: checked e unchecked ("controllate" e "non controllate"). Un'eccezione checked segnala una condizione anormale che deve essere gestita dal client. Tutte le eccezioni controllate devono essere catturate e gestite all'interno del metodo corrente o essere dichiarate nella clausola throws che segue la firma del metodo. Ecco perch sono dette "controllate". Il compilatore e la JVM verificheranno che tutte le eccezioni checked che possono verificarsi in un metodo siano gestite. Il compilatore e la JVM non si curano se sono ignorate delle eccezioni unchecked perch queste sono eccezioni che il client usualmente non potrebbe comunque gestire. Le eccezioni unchecked come java.lang.ClassCastException sono di solito il risultato di una logica non corretta o di errori di programmazione. La determinazione del fatto che un'eccezione sia controllata o meno semplicemente basata sulla sua posizione nella gerarchia delle eccezioni. Tutte le classi che discendono da java.lang.Exception eccezion fatta per le sottoclassi di RuntimeException, sono eccezionichecked; il compilatore garantir che siano gestite dal metodo o elencate nella clausola throws. RuntimeException e le sottoclassi che discendono da essa sono eccezioni del tipo unchecked e il compilatore non si preoccuper se queste non sono elencate nella clausola throws di un metodo o non sono gestite nell'ambito di un blocco try/catch. Per questo sono dette "non controllate".

10.1.5 L'impatto sulle prestazioni della gestione delle eccezioni


In generale, l'impatto sulle prestazioni dell'incapsulamento del proprio codice Java all'interno di blocchi try/catch limitato. Solo quando si verificano delle vere eccezioni, vi un reale impatto dovuto alla ricerca che la JVM deve fare per localizzare il corretto gestore per l'eccezione. Se il blocco catch per l'eccezione posto nello stesso metodo, l'impatto non poi tanto negativo. Tuttavia, quanto pi viene discesa la pila dalla JVM, tanto maggiore risulta l'impatto sulle prestazioni complessive. Ecco perch si dovrebbe usare un blocco try/catch solo per condizioni di errore. Non si dovrebbero mai usare eccezioni per operazioni come il controllo del flusso del programma. Il seguente uso di un blocco try/catch probabilmente corretto, ma al limite della correttezza per l'uso improprio della gestione delle eccezioni:

Double basePrice = null; String basePriceStr = request.getParameter( "BASE_PRICE_AMOUNT" ); // Use a try/catch to make sure the value is a number try{ basePrice = Double.valueOf( basePriceStr ); }catch( NumberFormatException ex ){ // The value could not be converted to a valid Double; set the default basePrice = ApplicationDefaults.DEFAULT_BASE_PRICE; }
Il codice precedente mostra un blocco try/catch che determina una condizione d'errore e prende dei provvedimenti correttivi. La condizione un valore non valido di prezzo e l'azione correttiva consiste nell'assegnargli un valore prestabilito. Ci sono altri modi per determinare se una stringa sia un valore Double valido ma un approccio del genere molto sfruttato. Fortunatamente il gestore delle eccezioni nello stesso metodo e la JVM non viene messa in difficolt se ci si verifica. Certo, le regole sono sempre in qualche modo soggettive e quella che una ragione valida per uno sviluppatore potrebbe, non esserla per un'altro. Occorre essere consapevoli dei problemi ed evitare blocchi try/catch se non per vere condizioni di errore.

10.2 Eccezioni di sistema e di applicazione


Le eccezioni possono essere classificate in eccezioni di sistema ed eccezioni di applicazione. Le eccezioni di sistema sono pi serie per la loro stessa natura. Questi sono tipici problemi low-level non relativi alla logica dell'applicazione e dai quali gli utenti finali non possono ripararsi. In molti casi le eccezioni di sistema sono unchecked e la propria applicazione non pu raccoglierle perch sono errori relativi al sistema e non di programmazione o sono cos critici che non pu essere fatto niente per ripararvi. Le eccezioni di applicazione sono errori che capitano a causa della violazione di una business rule o di qualche altra condizione nella logica dell'applicazione. Per esempio, si potrebbe lanciare un'eccezione per lin'applicazione quando un

utente cerca di fare login in un'applicazione ma l'account stato bloccato. Questo non rappresenta un errore catastrofico ma comunque un problema che deve essere registrato e gestito. All'interno di applicazioni Struts e all'interno delle web application in generale ci sono essenzialmente due approcci da adottare ogniqualvolta si verifichi un'eccezione. Se si tratta di un'eccezione di applicazione che l'utente pu in qualche modo gestire, di solito si restituir il controllo alla pagina di input e si visualizzer una descrizione di facile comprensione e un'azione alternativa per risolverla. Continuando con l'esempio dell'account bloccato, si potrebbe lanciare un'eccezione AccountLockedException agganciata alla classe action che poi restituisce il controllo alla pagina di login e informa che l'account bloccato. Se l'eccezione lanciata un'eccezione di basso livello come RemoteException, la sola azione significativa che l'applicazione pu svolgere mostrare una pagina di errore di sistema. Non c' niente che l'utente possa fare per risolvere il problema. Potrebbe trattarsi di un errore di programmazione o qualche problema di rete ma il punto che non si desidera che l'utente veda la traccia della pila dell'eccezione. Piuttosto potrebbe essere il caso di indirizzare verso una pagina di errore di sistema pi comprensibile ed eventualmente si informi l'utente di darne avviso all'amministratore. L'eccezione dovrebbe anche essere registrata per determinare la causa del problema. Pi oltre nel capitolo si vedranno esempi su come restituire il controllo alla pagina di input e mostrare un errore localizzato all'utente. Si vedr anche come si dirigono i messaggi d'errore a una pagina di errore di sistema, tutte cose che aggiungono valore all'applicazione e arricchiscono l'esperienza dell'utente.

10.3 Impiego di eccezioni concatenate


Spesso meglio catturare un tipo particolare di eccezione e lanciarne uno diverso. Questo talvolta necessario dal momento che un client potrebbe non sapere o non conoscere oppure ancora non curarsi di gestire l'eccezione originale. Ad esempio, supponiamo che un client invochi un'azione su un'applicazione di Struts per caricare un file di immagini in un database. Supponiamo anche che la classe Action chiami un metodo update la cui firma simile a:

public void updateImageFile( String imagePath ) throws UploadException;


Quando il metodo invocato per il caricamento di un'immagine e si verifica un problema sar lanciata una UploadException. Tuttavia il problema alla base sar pi specifico: ad esempio, il filesystem pieno o l'immagine gi contenuta nel database. L'eccezione originale lanciata sar IOException oppure SQLException, ma l'utente non necessita di ottenere un tale livello di dettaglio. Tutto quello che deve sapere che la funzione di aggiornamento fallita. Anche se l'utente finale non si cura dell'eccezione specifica lanciata, questa comunque dovr essere visibile all'amministratore di sistema o a chi si occupa del debug. Ecco perch non si deve scartare la causa primaria del problema quando si rilancia una eccezione diversa. Prima della versione 1.4, Java non conteneva un meccanismo per incapsulare l'eccezione originale con una nuova. Gli sviluppatori erano costretti a scrivere i loro propri sistemi per aggirare il problema. La maggior parte delle soluzioni create avevano pressappoco 1' aspetto descritto dall'Esempio 10-1.
Esempio10-1. Una classe di eccezione che supporta il concatenamento delle eccezioni

import java.io.PrintStream; import java.io.PrintWriter; /** * This is the common superclass for all application exceptions. This * class and its subclasses support the chained exception facility that allows * a root cause Throwable to be wrapped by this class or one of its * descendants. */ public class BaseException extends Exception { protected Throwable rootCause = null; protected BaseException( Throwable rootCause ) { this.rootCause = rootCause; } public void setRootCause(Throwable anException) { rootCause = anException; } public Throwable getRootCause( return rootCause; ) {

} public void printStackTrace( ) { printStackTrace(System.err); } public void printStackTrace(PrintStream outStream) { printStackTrace(new PrintWriter(outStream)); } public void printStackTrace(PrintWriter writer) { super.printStackTrace(writer); if ( getRootCause( ) != null ) { getRootCause( ).printStackTrace(writer); } writer.flush( ); } }
La classe dell'Esempio 10-1 permette di effettuare il wrapping della Throwable originale con un'istanza di questa eccezione o di tutti i suoi derivati. Il bello di questa caratteristica sta nel permettere di astrarre i dettagli di basso livello delle applicazioni e allo stesso tempo di mantenere questi dettagli disponibili in modo che possano essere scritti in un log e usati dagli sviluppatori. Dal momento che queste eccezioni possono essere raggruppate indefinitamente, ci si riferisce a questo concetto come a quello di concatenamento delle eccezioni. Il concatenamento delle eccezioni un modo eccellente per evitare che astrazioni di livello basso, come l'accesso JDBC, si propaghino all'utente finale. L'utente finale non si cura del problema di basso livello e astrarre, il problema a un livello pi alto gli impedir di vedere buona parte dei dettagli. Un'altro beneficio dell'impiego di una classe di eccezioni a livello alto sta nel fatto che le API dello strato superiore non sono legate o accoppiate ai dettagli della loro implementazione. Sulla stessa falsariga dell'esempio precedente si supponga che un filesystem sia usato inizialmente per conservare le immagini per cui la clausola throws del metodo updateImageFile() dichiara di lanciare una IOException. Se in seguito l'implementa zione cambia, e viene usato JDBC, la clausola di throws e il client che invoca il metodo devono essere cambiati per dichiarare o raccogliere una SQLException. Con un maggior livello di astrazione il client necessita solo di curarsi della UploadException, qualunque sia l'implementazione sottostante.

10.3.1 Trattare eccezioni multiple


Una sottile variazione dell'idea del concatenamento delle eccezioni consiste nel concetto di lanciare pi eccezioni da un metodo. Per esempio, diciamo che un utente stia riempiendo un modulo che contiene molti campi di prezzi che devono cadere tra valori massimi e minimi. Si ponga anche che i valori dei prezzi possano essere validati dal backend dell'applicazione e non dall' ActionForm. A meno che la propria applicazione non possa lanciare eccezioni multiple da un metodo, l'utente vedr sempre una eccezione alla volta. Quest'approccio funzioner ma probabilmente diverr molto seccante per l'utente finale che dovr correggere un campo e inviare il modulo solo per ricevere l'errore successivo. Sar tutto pi facile per gli utenti se tutti gli errori verranno visualizzati ed potranno essere corretti in uno stesso momento. Sfortunatamente un metodo Java pu lanciare solo un'istanza di Throwable. Per trattare con eccezioni multiple esiste una soluzione che consiste nel permettere ad una classe di eccezione di avere una eccezione primaria che supporta un'insieme di altre eccezioni. Ogni eccezione pu essere trattata allo stesso modo ma l'eccezione primaria viene usata quando si verifica un'eccezione singola ed il client pu controllare l'insieme di eccezioni per vedere se ce ne sono di maggiori. L'Esempio 10-2 illustra cosa sarebbe la classe BaseException dell'Esempio 10-1 se vi fosse aggiunta questa caratteristica.
Esempio 10-2. Una classe Exception che supporta eccezioni multiple annidate

package com.oreilly.struts.framework.exceptions; import java.util.List; import java.util.ArrayList; import java.io.PrintStream; import java.io.PrintWriter; /** * This is the common superclass for all application exceptions. This

* class and its subclasses support the chained exception facility that allows * a root cause Throwable to be wrapped by this class or one of its * descendants. This class also supports multiple exceptions via the * exceptionList field. */ public class BaseException extends Exception{ protected Throwable rootCause = null; private List exceptions = new ArrayList( public BaseException( super( ); } ){ );

public BaseException( Throwable rootCause ) { this.rootCause = rootCause; } public List getExceptions( return exceptions; } ) {

public void addException( BaseException ex ){ exceptions.add( ex ); } public void setRootCause(Throwable anException) { rootCause = anException; } public Throwable getRootCause( return rootCause; } ) {

public void printStackTrace( ) { printStackTrace(System.err); } public void printStackTrace(PrintStream outStream) { printStackTrace(new PrintWriter(outStream)); } public void printStackTrace(PrintWriter writer) { super.printStackTrace(writer); if ( getRootCause( ) != null ) { getRootCause( ).printStackTrace(writer); } writer.flush( ); } }
Si noti nell'Esempio 10-2 che stata aggiunta alla classe una java.util.List. Se si verifica pi di una eccezione durante l'elaborazione di un metodo, le eccezioni in pi possono essere aggiunte e restituite al client. Se Se il client vuole trattare solo una eccezione non dovr raccogliere tutte quelle addizionali

10.4 Gestione delle eccezioni ad opera di Struts


Prima della versione 1.1, Struts offriva un sistema di gestione delle eccezioni molto ridotto: era lasciato al programmatore il compito di estendere il framework con le proprie capacit di gestione delle eccezioni. Ci spinse ciascun gruppo di sviluppo ad accostarsi ad una soluzione da direzioni diverse e rese difficile l'ideazione di una soluzione comune. Nella Versione 1.1, Struts ha incorporato un framework piccolo ma efficace di gestione delle eccezioni per le applicazioni. La via intrapresa dagli sviluppatori di Struts ha seguito le specifiche delle EJB Servlet API riguardo la gestione della sicurezza, permettendo agli sviluppatori di impiegare un approccio dichiarativo e/o programmatico.

Uso della classe AppException


La classe org.apache.struts.util.AppException incorporata in Struts. Questa classe un wrapper di ActionError che estende java.lang.Exception. Offre la possibilit di lanciare eccezioni in un'applicazione Struts come ad esempio:

throw new AppException("error.password.mismatch");


Dove l'argomento del costruttore AppException una chiave di resource bundle. Il framework creer automaticamente oggetti ActionError da queste eccezioni e li conserver all'interno dello scope appropriato. Si pu estendere poi la classe AppException con eccezioni specifiche per la propria applicazione. Un problema derivante dall'uso di questa classe sta nel fatto che lega strettamente la propria applicazione, e nello specifico il proprio sistema di gestione delle eccezioni, a Struts. Si basa infatti su classi specifiche di Struts, il che diviene problematico se si usa un vero strato applicativo basato, per esempio, sugli EJB. Non si dovr legare l'intera applicazione a Struts se si pu evitarlo. Se si scrive una piccola web application dove non si dovr mai rimpiazzare Struts con altro e non importante associare i propri componenti a Struts, la classe AppException pu essere un buon approccio. Altrimenti meglio non farne uso

10.4.1 Gestione delle eccezioni programmatica e dichiarativa: pro e contro


La gestione delle eccezioni dichiarativa viene implementata tramite l'espressione di una policy di gestione delle eccezioni, ivi incluse quali debbano essere lanciate in concomitanza di un particolare evento e come debbano essere trattate; il tutto racchiuso in un file di testo (di solito scritto in XML) che completamente esterno al codice dell'applicazione. Quest'approccio rende facile modificare la logica di gestione delle eccezioni senza dover ricompilare il codice. La gestione programmatica delle eccezioni esattamente l'opposto. Consiste nel metodo tradizionale di scrivere codice per la gestione delle eccezioni specifico per le applicazioni e all'interno del codice, piuttosto che la semplice modifica di un file di configurazione esterno. Tuttavia all'interno di un'applicazione di Struts la questione diventa alquanto pi complicata. Come per altre opzioni di configurazione di Struts, anche in questo caso le mappature dichiarative sono scritte nel file di configurazione di Struts. Come gi visto nel Capitolo 4, si possono specificare le eccezioni che possono verificarsi e le azioni da intraprendere sia a livello globale che per una specifica mappatura delle eccezioni. Per i parametri disponibili per gli elementi della gestione delle eccezioni si veda il Capitolo 4. L'Esempio 10-3 mostra un file di configurazione parziale di Struts che dichiara tre eccezioni diverse che possono essere lanciate dall'azione login
Esempio 10-3. Un file di configurazione di Struts: gestione dichiarativa delle eccezioni

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd"> <struts-config> <action-mappings> <action path="/login" type="com.oreilly.struts.storefront.security.LoginAction" name="loginForm" scope="request" input="/login.jsp"> <!--The following exceptions can be thrown during the login action --> <exception key="security.error.changepassword" path="/changePassword.jsp" type="com.oreilly.struts.framework.exceptions.ExpiredPasswordException "/>

<exception key=" security.error.loginfailed" type="com.oreilly.struts.framework.exceptions.InvalidLoginException" path="/login.jsp"/> <exception key="security.error.accountlocked" type="com.oreilly.struts.framework.exceptions.AccountLockedException" path="/accountLocked.jsp"/> </action> </action-mappings> </struts-config>

L'elemento exception definito nel mapplng delle azioni o anche nella sezione delle eccezioni globali specifica il path verso cui indirizzarsi quando si verifica una delle eccezioni durante la corrispondente invocazione idi azione. Per esempio, se viene lanciata l'eccezione ExpiredPasswordException durante l'azione login, il controller passer il controllo alla pagina changePassword.jsp.Presumibilmente se viene lanciata AccountLockedException, il controllo verr passato alla pagina accountLocked.jsp. Quando un'eccezione non gestita a livello di programmazione nella classe Action, la RequestProcessor prova a vedere se esiste un elementoexception configurato per quellospecifico tipo di eccezione. Se tale elemento esiste, il controllo passato alla risorsa specificata nell'attributo path di exception. L'Esempio 10-4 mostra il metodo processException() della classe RequestProcessor.
Esempio 10-4. Il metodo processException() della RequestProcessor di Struts

protected ActionForward processException(HttpServletRequest request, HttpServletResponse response, Exception exception, ActionForm form, ActionMapping mapping) throws IOException, ServletException { // Is there a defined handler for this exception? ExceptionConfig config = mapping.findException(exception.getClass( ));

if (config == null){ if (log.isDebugEnabled( )){ log.debug(getInternal().getMessage("unhandledException",exception.getClas s( ))); } if (exception instanceof IOException){ throw (IOException) exception; }else if (exception instanceof ServletException){ throw (ServletException) exception; }else{ throw new ServletException(exception); } } // Use the conFigurad exception handling try { Class handlerClass = Class.forName(config.getHandler( )); ExceptionHandler handler = (ExceptionHandler)handlerClass.newInstance( ); return (handler.execute(exception, config, mapping, form,request, response)); }catch (Exception e){ throw new ServletException(e); } }
Si noter che il metodo findException() potrebbe restituire un oggettoExceptionConfig all'inizio del metodo processException(). L'oggetto ExceptionConfig una rappresentazione in memoria dell'elemento exception specificato all'interno del file di configurazione. Se il metodo findException() non trova un elemento exception per lo specifico tipo di eccezione verificatasi, l'eccezione rinviata al client senza passare

attraverso il gestore delle eccezioni di Struts. A meno che l'eccezione sia una IOException oppure una delle sue sottoclassi, questa eccezione dunque sar incapsulata da una istanza di ServletException e rilanciata. Se c' un elemento exception specificato nel mapping delle azioni per lo specifico tipo di eccezione verr restituito un oggetto ExceptionConfig dal metodo findException(). Il metodo getHandler() poi invocato sull'oggetto ExceptionConfig e viene usato il gestore assegnato l'elaborazione dell'eccezione. per Struts possiede una classe predefinita per l'elaborazione delle eccezioni, usata a meno che non se ne configuri una propria. La classe predefinita per la gestione delle eccezioni org.apache.struts.action.ExceptionHandler. Il metodo execute() di questo handler crea un ActionError, lo conserva all'interno di uno scope appropriato e restituisce un oggetto ActionForward associato con l'attributo path specificato nell'elemento exception. Per riassumere, se si dichiara un elemento exception all'interno di un elemento action, il gestore delle eccezioni predefinito creer e conserver un ActionError all'interno dello scope specificato passando il controllo alla risorsa specificata dall'attributo path. Come si notato nel Capitolo 4, l'elemento exception permette anche di ridefinire il funzionamento standard del gestore delle eccezioni se si desidera una risposta diversa quando si verifica una certa eccezione. Specificando un nome completo di classe Java che estenda la classe org.apache.struts.action.ExceptionHandler nell'attributo dell'eccezione handler, il metodo execute() di ExceptionHandler verr ridefinito affinch svolga il compito specificato. Per esempio, le eccezioni di un'applicazione potrebbero estendere la classe BaseException come illustrato dall'Esempio 10-5.
Esempio 10-5. Classe di eccezione che supporta una chiave di messaggio e suoi argomenti

package com.oreilly.struts.framework.exceptions; import java.util.List; import java.util.ArrayList; import java.io.PrintStream; import java.io.PrintWriter; /** * This is the common superclass for all application exceptions. This * class and its subclasses support the chained exception facility that allows * a root cause Throwable to be wrapped by this class or one of its * descendants. This class also supports multiple exceptions via the * exceptionList field. */ public class BaseException extends Exception{ protected Throwable rootCause = null; private List exceptions = new ArrayList( private String messageKey = null; private Object[] messageArgs = null; public BaseException( super( ); } ){ );

public BaseException( Throwable rootCause ) { this.rootCause = rootCause; } public List getExceptions( return exceptions; } ) {

public void addException( BaseException ex ){ exceptions.add( ex ); } public void setMessageKey( String key ){ this.messageKey = key; } public String getMessageKey( return messageKey; } ){

public void setMessageArgs( Object[] args ){ this.messageArgs = args; } public Object[] getMessageArgs( return messageArgs; } ){

public void setRootCause(Throwable anException) { rootCause = anException; } public Throwable getRootCause( return rootCause; } ) {

public void printStackTrace( ) { printStackTrace(System.err); } public void printStackTrace(PrintStream outStream) { printStackTrace(new PrintWriter(outStream)); } public void printStackTrace(PrintWriter writer) { super.printStackTrace(writer); if ( getRootCause( ) != null ) { getRootCause( ).printStackTrace(writer); } writer.flush( ); } }
La classe BaseException dell'Esempio 10-5 contiene un messageKey utilizzabile come chiave nel resource bundle di Struts. Questa chiave pu essere passata al costruttore della classe ActionError e il framework la far corrispondere a un messaggio nel resource bundle di Struts. Questa classe contiene anche un array di oggetti che pu essere popolato dal creatore dell'eccezione. Questi oggetti possono poi essere utilizzati per la sostituzione all'interno di un messaggio da parte del bundle che contiene parametri di sostituzione basati sulla classe MessageFormat. Ecco un esempio di messaggio nel bundle:

global.error.invalid.price=The price must be between {0} and {1}.


Quando si crea un oggetto ActionError si pu passare un array di oggetti come secondo parametro e ciascun oggetto sar sostituito nei parametri racchiusi tra parentesi graffe. L'elemento 0 nell'array sar inserito nella posizione {0} l'oggetto 1 sar inserito nella posizione {1} e cos via. Il Capitolo 12 tratter in maggior dettaglio questo argomento. L'Esempio 10-6 illustra come estendere la classe predefinita del gestore delle eccezioni e permettere un funzionamento specializzato per la sostituzione degli argomenti dall'eccezione nel costruttore ActionError.
Esempio 10-6. Una classe excepton handler specializzata

package com.oreilly.struts.chapter10Esempios; import import import import import import import import import import javax.servlet.ServletException; javax.servlet.http.HttpServletRequest; javax.servlet.http.HttpServletResponse; org.apache.struts.action.ExceptionHandler; org.apache.struts.action.ActionForm; org.apache.struts.action.ActionError; org.apache.struts.util.AppException; org.apache.struts.action.ActionForward; org.apache.struts.action.ActionMapping; org.apache.struts.config.ExceptionConfig;

import com.oreilly.struts.framework.exceptions.BaseException; public class SpecialExceptionHandler extends ExceptionHandler {

protected ActionForward execute(Exception ex, ExceptionConfig config, ActionMapping mapping, ActionForm formInstance, HttpServletRequest request, HttpServletResponse response) throws ServletException { ActionForward forward = null; ActionError error = null; String property = null; /* Get the path for the forward either from the exception element * or from the input attribute. */ String path = null; if (config.getPath( ) != null) { path = config.getPath( ); }else{ path = mapping.getInput( ); } // Construct the forward object forward = new ActionForward(path); /* Figura out what type of exception has been thrown. The Struts * AppException is not being used in this Esempio. */ if( ex instanceof BaseException) { // This is the specialized behavior BaseException baseException = (BaseException)ex; String messageKey = baseException.getMessageKey( ); Object[] exArgs = baseException.getMessageArgs( ); if ( exArgs != null && exArgs.length > 0 ){ // If there were args provided, use them in the ActionError error = new ActionError( messageKey, exArgs ); }else{ // Create an ActionError without any arguments error = new ActionError( messageKey ); } }else{ error = new ActionError(config.getKey( )); property = error.getKey( ); } // Store the ActionError into the proper scope // The storeException method is defined in the parent class storeException(request, property, error, forward, config.getScope( )); return forward; } }
Sta allo sviluppatore definire il funzionamento specializzato che si pone in opera nella propria classe di gestione delle eccezioni. Nell'Esempio 10-6, inserendo degli argomenti in ActionError, il messaggio viene reso maggiormente comprensibile. Per informazioni pi dettagliate sull'installazione di un gestore delle eccezionisi consulti il paragrafo "Il DTD di configurazione di Struts" nel Capitolo 4.

Ci sono molti altri casi in cui sar necessario ignorare il funzionamento predefinito. Questo non prevede infatti un oggetto di eccezione che supporti eccezioni multiple. Se la propria applicazione necessita del supporto per questa funzionalit si dovranno creare le proprie classi ExceptionHandler.

Nella maggior parte dei casi il gestore delle eccezioni di Struts sufficiente a soddisfare le esigenze. Solo quando vi necessit di una gestione specializzata delle eccezioni che non pu essere ottenuta mediante il gestore delle eccezioni di Struts ci si dovrebbe impegnare a crearne una propria. La Figura 10-3 illustra un diagramma di sequenza della gestione delle eccezioni standard di Struts.

Figura 10-3. Diagramma di sequenza della gestione delle eccezioni di Struts

L'uso del meccanismo dichiarativo di gestione delle eccezioni di Struts non preclude l'uso di un approccio programmatico. Di fatto, i due sistemi possono funzionare abbastanza bene in combinazione. Le classi Action proveranno dapprima a gestire una certa eccezione e solo se essa non raccolta e gestita dall'istanza di Action sar raccolta dal metodo processActionPerform() nella classe RequestProcessor. La RequestProcessor impiegher allora il meccanismo dichiarativo di gestione delle eccezioni per elaborare l'errore. Di seguito si tratta appunto il modo di gestire le eccezioni mediante approccio programmatico.

10.4.2 L'uso della gestione programmativa delle eccezioni


Lapproccio alternativo alla gestione dichiarativa delle eccezioni di Struts consiste nello scrivere nel codice stesso dell'applicazione la gestione delle eccezioni specifica per l'applicazione. Questo significa che si dovr estendere il framework con un funzionamento specifico per la propria applicazione Come gi stato riportato precedentemente in questo capitolo, ci sono due modalit di base per trattare un'eccezione lanciate da una classe Action. Se l'eccezione un'eccezione dell'applicazione, si registrer l'eccezione stessa in un file di log; ad esempio, si proweder a creare un ActionError nello scope appropriato, quindi si passer il controllo a un appropriato ActionForward. Si ricordi dalla discussione delle eccezioni dichiarative che questo lo stesso comportamento di Struts fatta eccezione per il logging. Nel caso dell'applicazione Storefront le eccezioni di applicazione sarebbero tutte discendenti di BaseException; diventa facile venire a conoscenza del verificarsi di un'eccezione perch si pu sempre fare uso di un blocco catch per BaseException. Se l'eccezione non un'istanza di BaseException, si pu assumere che si sia verificata un'eccezione di sistema che dovr essere elaborata come tale. Il percorso dell'azione per le eccezioni di sistema consiste normalmente nel registrare l'eccezione e nel restituire unActionForward per la pagina di errore di sistema. Sulle prime si potrebbe essere tentati di usare dei blocchi try/catch nelle proprie classi Action e di svolgere la gestione delle eccezioni in questo modo:

try{ // Peform some work that may cause an application or system exception }catch( BaseException ex ){ // Log the exception // Create and store the action error ActionErrors errors = new ActionErrors( ); ActionError newError = new ActionError( ex.getErrorCode(), ex.getArgs( ) ); errors.add( ActionErrors.GLOBAL_ERROR, newError ); saveErrors( request, errors ); // Return an ActionForward for the Failure resource return mapping.findForward( "Failure" ) }catch( Throwable ex ){ // Log the exception // Create and store the action error ActionError newError = new ActionError( "error.systemfailure" ); ActionErrors errors = new ActionErrors( ); errors.add( ActionErrors.GLOBAL_ERROR, newError ); saveErrors( request, errors ); // Return an ActionForward for the system error resource return mapping.findForward( IConstants.SYSTEM_FAILURE_PAGE ); }
Il problema che all'interno di ogni classe Action si finirebbe per avere una sovrabbondanza di codice.L'uso dell'approccio dichiarativo permette di eliminare questa ridondanza. Tuttavia, se non si desidera impiegare l'approccio dichiarativo o se non si pu perch si usa una versione di Struts che non lo supporta, esiste un metodo alternativo che non genera una cos grande ridondanza. Nel Capitolo 5, si visto come l'impiego di una classe di base Action astratta quale StorefrontBaseAction possa ridurre la ridondanza all'interno delle classi Action per altri motivi. Anche in questo caso si pu spingere la funzionalit della gestione programmatica delle eccezioni fino alla classe abstract cosi che non si debba farlo in tutte le proprie classi Action. A tal fine si dovr implementare il metodoexecuteAction(), introdotto dalla classe StorefrontBaseAction che un'implementazione del design pattern Template. L'Esempio 10-7 mostra il metodo execute() della classe StorefrontBaseAction.

Esempio 10-7. Il metodo execute() della classe StorefrontBaseAction

public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { ActionForward forwardPage = null; try{ UserContainer userContainer = getUserContainer( request ); // Inform the specific action instance to do its thing forwardPage = executeAction(mapping, form, request, response, userContainer); }catch (BaseException ex){ // Log the application exception using your logging framework // Call the generic exception handler routine forwardPage = processExceptions( request, mapping, ex ); }catch (Throwable ex){ // Log the system exception using your logging framework // Make the exception available to the system error page request.setAttribute( Action.EXCEPTION_KEY, ex ); // Treat all other exceptions as system errors forwardPage = mapping.findForward( IConstants.SYSTEM_FAILURE_KEY ); } return forwardPage; }
Il metodo execute() invoca il metodo executeAction() che deve essere ignorato da tutte le sottoclassi di Action e incapsula l'invocazione negli appositi blocchi try/catch. La classe StorefrontBaseAction astratta e offre una versione astratta del metodo executeAction() come mostrato di seguito:

abstract public ActionForward executeAction( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response, UserContainer userContainer ) throws BaseException;
Quando si verifica una qualsiasi eccef.idne dell'applicazione, essa sar raccolta nei blocchi try/catch all'interno del metodo exetute(), a patto che estenda la StorefrontBaseAction. Le sottoclassi non devono fornire un blocco catch a meno che non vogliano ulteriormente specializzare il comportamento di tale eccezione. Il metodo execute() passa l'eccezione insieme alla richiesta e agli oggetti del mapping al metodo processExceptions(), come illustrato dall'Esempio 10-8.
Esempio 10-8. The processExceptions( ) method in the StorefrontBaseAction

protected ActionForward processExceptions( HttpServletRequest request, ActionMapping mapping, BaseException ex ){ ActionErrors errors = new ActionErrors( ); ActionForward forward = null; // Get the locale for the user Locale locale = getUserContainer( request ).getLocale( ); if (locale == null){ // If it hasn't been conFigurad, get the default for the environment locale = Locale.getDefault( ); } processBaseException(errors, (FieldException) ex, locale);

// Either return to the input resource or a conFigurad failure forward String inputStr = mapping.getInput( ); String failureForward = mapping.findForward(IConstants.FAILURE_KEY); if ( inputStr != null) { forward = new ActionForward( inputStr ); }else if (failureForward != null){ forward = failureForward; } // See if this exception contains a list of subexceptions List exceptions = ex.getExceptions( ); if (exceptions != null && !exceptions.isEmpty( ) ){ int size = exceptions.size( ); Iterator iter = exceptions.iterator( ); while( iter.hasNext( ) ){ // All subexceptions must be BaseExceptions BaseException subException = (BaseException)iter.next( processBaseException(errors, subException, locale); } } // Tell the Struts framework to save the errors into the request saveErrors( request, errors ); // Return the ActionForward return forward; }
Il metodo processExceptions() sembra alquanto complesso ma non poi cos terribile. Ecco la sequenza di operazioni svolte al suo interno: 1. 2. 3. 4. 5. Ottiene le impostazioni locali per l'utente. Invoca il metodo processBaseException() per elaborare le eccezioni di livello alto. Se ci sono sottoeccezioni, le elabora una per una Salva tutti gli ActionError creati. Restituisce il controllo alla risorsa identificata nell'attributo input dell'azione ActionForward "Failure" che sia stata precedentemente configurata per l'azione.

);

oppure

una

Il metodo processBaseException() il luogo in cui sono creati gli oggetti ActionError: mostrato nellEsempio 10-9.
Esempio 10-9. Il metodo processBaseException() della classe StorefrontBaseAction

protected void processBaseException( ActionErrors errors, BaseException ex, Locale locale) { // Holds the reference to the ActionError to be added ActionError newActionError = null; // The errorCode is the key to the resource bundle String errorCode = ex.getMessageKey( ); /** * If there are extra arguments to be used by the MessageFormat object, * insert them into the argList. The arguments are context sensitive * arguments for the exception; there may be 0 or more. */ Object[] args = ex.getMessageArgs( ); /** * In an application that had to support I18N, you might want to * format each value in the argument array based on its type and the * user locale. For Esempio, if there is a Date object in the array, it

* would need to be formatted for each locale. */ // Now construct an instance of the ActionError class if ( args != null && args.length > 0 ){ // Use the arguments that were provided in the exception newActionError = new ActionError( errorCode, args ); }else{ newActionError = new ActionError( errorCode ); } errors.add( ActionErrors.GLOBAL_ERROR, newActionError ); }
II metodo processBaseException() ha la responsabilit di creare l'oggettoActionError. Fa uso del campo messageKey per ricercare un messaggio di tipo bundle e se sono inclusi degli argomenti, li include nel costruttore ActionError. Come si pu vedere, aggiungere alle proprie applicazioni la gestione programmatica delle eccezioni richiede molto pi lavoro rispetto all'uso del gestore del framework Struts. Diventa anche difficile la manutenzione se si compiono cambiamenti drastici nella gerarchia delle eccezioni o si cambia la modalit di gestione di determinate eccezioni. Tuttavia, se si usa una vecchia versione di Struts, questo approccio potrebbe costituire una scelta obbligata. Forse questi esempi andranno estesi appositamente per le proprie applicazioni ma l'approccio utilizzato gi ben progettato da permettere di utilizzarlo e inserirlo nelle proprie applicazioni. All'interno delle specifiche per EJB e servlet, la sicurezza programmatica non viene molto considerata perch troppo facile associare strettamente la propria applicazione all'ambiente di sicurezza fisica. Con la gestione delle eccezioni difficile che si debbano cambiare le eccezioni lanciate sulla base dell'ambiente di destinazione. Per questo motivo, non c' lo stesso rigore associato alla gestione programmatica delle eccezioni come vi per la sicurezza programmatica. Ma anche vero per che, se si pu trarre vantaggio dalla gestione delle eccezioni dichiarativa, la propria applicazione sar pi semplice da aggiornare rispetto al caso in cui le stesse funzionalit sono inglobate nel codice sorgente. Un'applicazione dovr essere sempre modificata nel corso del tempo e nuove eccezioni dovranno essere lanciate e catturate. Pi se ne specificano in maniera dichiarativa, pi facile sar la manutenzione.

10.5 Aspetti particolari


Prima di chiudere l'argomento gestione delle eccezioni, saranno presi in considerazione alcuni casi particolari che, nella loro peculiarit, potrebbero risultare, in certe condizioni, utili alle applicazioni del lettore.

10.5.1 Gestione di eccezioni remote


Gli oggetti remoti Java possono lanciare istanze di java.rmi.RemoteException. Di fatto, ogni metodo EJB che viene esposto verso un client remoto deve dichiarare di lanciare RemoteException. Trattare con delle RemoteException non differisce granch dal trattare con delle eccezioni di sistema, se non per il fatto che non discendono da java.lang.Error o java.lang.RuntimeException. Spesso l'applicazione non riuscir a sopravvivere dopo il verificarsi di una RemoteException e verr cos visualizzata la pagina di errore di sistema. Se si usano gli EJB e si ottiene una RemoteException, si pu tentare di recuperare tramite l'acquisizione di un nuovo riferimento remoto, ma ci sar con tutta probabilit un qualche tipo di errore programmatico o di ambiente che impedir all'utente finale di continuare. Che si stia usando un approccio programmatico o dichiarativo, si desiderer comunque registrare l'eccezione, creare e conservare un oggetto ActionError, quindi dirigerlo verso la pagina di errore di sistema. Si potrebbe anche definire il funzionamento della gestione delle eccezioni in modo che l'utente sia diretto alla pagina precedente e gli venga data l'opportunit di provare ancora. Se un qualche problema di rete stato la causa dell'eccezione remota, potrebbe essere possibile per l'utente riprendere a usare l'applicazione.

10.5.2 Eccezioni nei tag personalizzati


I tag custom delle JSP di solito lanciano oggetti JSPException oppure uno dei loro discen denti. Prima delle specifiche delle JSP 1.2, la classe JSPException non supportava la concatenazione delle eccezioni e, dal momento che Struts fu introdotto prima delle specifiche 1.2, diversi aspetti dei tag personalizzati di Struts ignorano tuttora l'eccezione originale quando viene lanciata JSPException.

Tuttavia i tag di Struts di solito conservano l'eccezione nello scope di request sotto la chiave:

Action.EXCEPTION_KEY
che fa riferimento a una stringa di org.apache.struts.action.Action.EXCEPTION_KEY. Se si necessita di avere accesso alla causa scatenante, si pu usare chiave per risalire all'oggetto. questa La versione 1.2 delle specifiche JSP ha modificato JSPException per il supporto della concatenazione di eccezioni, tuttavia gli sviluppatori di Struts probabilmente preferiranno lasciare i tag attualmente presenti per compatibilit con le versioni precedenti e trarranno vantaggio da questa nuova funzionalit solo per i futuri tag. Tuttavia, nei tag personalizzati che si creano, si dovrebbe sempre usare il campo rootCause nella classe JSPException quando si rilanciano eccezioni come tipi diversi.

10.5.2.1 L'interfaccia TryCatchFinally


Le specifiche per le JSP 1.2 hanno anche introdotto una nuova interfaccia chiamata TryCatchFinally. Quest'interfaccia, cui si fa riferimento come interfaccia "mista", pu essere implementata tramite un tag handler in aggiunta a una delle altre interfacce del tag. L'interfacciaTryCatchFinally presenta due metodi:

public void doCatch(Throwable); public void doFinally( );


II container invoca il metodo doCatch() se il body del tag o uno dei metodi doStartEnd() doEndTag(), dolnitBody(), doAfierBody() lancia una Throwable. Il metodo doCatch() pu rilanciare la stessa eccezione o una di tipo diverso una volta gestito l'errore. Il container invoca quindi il metodo doFinally() dopo il metodo doEndTag() o dopo doCatch() quando si verificano le condizioni per un'eccezione. L'interfaccia TryCatchFinally permette una migliore gestione delle eccezioni nei tag personalizzati. Diventa pertanto importante permettere alle limitata risorse usate dai tag personalizzati di essere rilasciate. Senza quest'interfaccia non c' garanzia che il container offra a tag la possibilit di rilasciare le risorse usate dai tag.

10.5.3 Gestione delle eccezioni internazionalizzata


Il Capitolo 12 affronta l'internazionalizzazione in dettaglio ma si deve spendere anche in questa sede qualche riga sull'internazionalizzazione della gestione delle eccezioni. Quando gli sviluppatori Java lanciano eccezioni, sovente scrivono codice come questo:

// Detect some problem and throw an exception throw new InvalidLoginException( "An exception has occurred." );
Il problema insito nell'inserimento di una stringa nel codice sta nel fatto che utile solo per gli sviluppatori della stessa area di localizzazione. Potrebbe essere difficile per gli sviluppatori o per gli amministratori eli sistema di aree diverse l'uso dei file di log dove sono registrate queste eccezioni. Al posto di inserire stringhe nel codice direttamente potrebbe essere una scelta migliore prendere il messaggio dal resource bundle. Ovviamente non sono sotto il proprio controllo le eccezioni lanciate da package di terze parti, come pure sono difficili da individuare le immagini della "pila". Molte organizzazioni non si curano di localizzare i messaggi di eccezioni, cosa che va anche bene fino a quando nessuno di un'area locale diversa dalla propria deve cercare di interpretarle.

10.6 Conclusione
Il nuovo meccanismo di gestione dichiarativa delle eccezioni una grande integrazione al framework Struts e sicuramente permetter un maggior risparmio di tempo per gli sviluppatori sia durante lo sviluppo iniziale che per l'aggiornamento. Quando possibile si dovrebbe fare uno sforzo anche considerevole per trarre vantaggio da questa caratteristica piuttosto che scrivere programmaticamente la propria gestione delle eccezioni. Ma se si desidera o si ha la necessit di personalizzare la gestione delle eccezioni all'interno del framework questa possibilit non affatto preclusa.

CAPITOLO 11

Il framework Validator

Struts permette che la validazione dell'input avvenga all'interno dell' ActionForm. Per svolgere correttamente il compito della validazione di dati passati a un'applicazione Struts, gli sviluppatori devono scrivere una logica di validazione speciale all'interno di ogni classe ActionForm, Anche se quest'approccio funziona, ha tuttavia alcune limitazioni piuttosto serie. Questo capitolo costituisce un'introduzione al framework Validator di David Winterfeldt, che stato creato appositamente per l'interazione con i componenti di Struts e per poter dare una risposta a queste limitazioni. Il Validator permette la configurazione dichiarativa di routine di validazione per un'applicazione basata us Struts senza bisogno di scrivere alcuna logica speciale di validazione. Il Validator diventato tanto popolare e diffuso tra chi sviluppa con Struts che stato aggiunto all'elenco dei progetti Jakarta e alla distribuzione principale di Struts.

11.1 Perch un framework di validazione?


Nel Capitolo 7 si discusso su come eseguire logica di validazione all'interno della classe ActionForm. La soluzione presentata richiede la scrittura di una parte separata di logica di validazione per ciascuna propriet che si deve convalidare. Se viene rilevato un errore si dovr creare manualmente un oggetto ActionError e aggiungerlo all'insieme ActionErrors. Anche se questa soluzione funziona, ci sono alcuni problemi legati a questo approccio. Il primo problema sta nel fatto che scrivere logica di validazione all'interno di ogni classe ActionForm immette logica di validazione ridondante per tutta l'applicazione. All'interno di una singola web application, il tipo di validazione che deve verificarsi nei form HTML molto simile. La necessit di validare i campi richiesti, le date, gli orari e i numeri, per esempio, tipicamente ricorrente in molti punti di un'applicazione. La maggior parte delle applicazioni non banali contengono form HTML multipli che devono essere validati. Anche se si usa un singolo ActionForm per l'intera applicazione si potrebbe ancora finire per duplicare la logica per la validazione per le varie propriet. Il secondo problema riguarda invece gli aggiornamenti o le modifiche. Se si devono effettuare queste operazioni sulla validazione che viene svolta per una ActionForm, il codice sorgente deve essere ricompilato. Questo rende difficoltosa la configurazione di un'applicazione. Il framework Validator permette di spostare tutta la logica di validazione completamente al di fuori dell' ActionForm e di configurarla in maniera dichiarativa per un'applicazione attraverso l'uso di file di testo esterni scritti in XML. Non pi quindi necessaria alcuna logica di validazione all'interno dell'ActionForm, cosa che rende la propria applicazione pi facile da sviluppare e da aggiornare. Un altro grande beneficio del Validator consiste nel fatto che facile da estendere. Offre molte routine standard di validazione pronte, ma se c' bisogno di ulteriori regole di validazione, il framework pu essere facilmente esteso offrendo la possibilit di collegare le proprie regole, ancora una volta senza la necessit di modificare la propria applicazione.

11.2 Installazione e configurazione del Validator


Il framework Validator attualmente parte integrante dei progetti Jakarta Commons ed incluso nella distribuzione principale di Struts, ma anche possibile scaricare l'ultima versione da http://jakarta.apache.org/commons/. A meno di non avere la necessit del codice sorgente oppure dell'ultimissima versione, si troveranno tutti i file necessari inclusi con la distribuzione di Struts 1.1.

11.2.1 Package richiesti


Il Validator dipende da diversi altri package per un funzionamento corretto e il pi importante di questi il package Jakarta ORO. Il package ORO contiene funzionalit per la manipolazione di espressioni regolari, per lo svolgimento di sostituzioni e altro ancora. Le librerie furono in origine sviluppate da ORO Inc. e donate all'Apache Software Foundation. Le prime versioni del Validator dipendevano da un diverso package di espressioni regolari chiamato Regexp che pure un progetto Jakarta. Tuttavia, ORO stato considerato il pi completo dei due ed il validator incluso in Struts 1.1 ora dipende dal package ORO. Altri package richiesti dal validator sono Commons BeansUtils, Commons Logging, Commons Collections e Digester. Tutti i package da cui dipende il validator sono inclusi in Struts 1.1. I file commons-validator.jar e jakarta-oro.jar devono essere posti nella directory WEB-INF/lib per la propria web application. Gli altri file JAR necessari devono anche essi essere presenti in tale directory, cosa che dovrebbe essere garantita dal fatto che sono necessari per altre dipendenze di Struts.

11.2.2 Configurazione delle regole di validazione


Come gi accennato, il Validator permette alle regole di validazione per un'applicazione di essere configurate in maniera dichiarativa. Questo significa che possono essere specificate esternamente al codice sorgente dell'applicazione. Ci sono due importanti file di configurazione per il Validator: validation-rules.xml e validation.xml.

11.2.2.1 Il file validation-rules.xml


Il file di configurazione validation-rules.xml contiene un insieme globale di regole di validazione pronto per l'uso all'interno della propria applicazione. Questo file non legato a un caso specifico e pu essere usato da una qualsiasi applicazione basata su Struts. Si dovrebbe modificare questo file solo se si desidera modificare o estendere la serie di regole di default.

Se occorre estendere le regole predefinite, si potrebbero mettere le proprie regole personalizzate in un apposito file XML separato per mantenerle distinte da quelle prestabilite. Questa operazione verr utile al momento di aggiornare la propria versione del framework Validator. Il file validator-rules_l_1.dtd descrive la sintassi del file validation-rules.xml. L'elemento centrale l'elemento formvalidation che richiede uno o pi elementi global.

<!ELEMENT form-validation (global+)> <!ELEMENT global (validator+)>


Ogni elemento validator descrive un'unica regola di validazione. Il seguente frammento dal file validation-rules.xml la definizione della regola di validazione required:

<validator name="required" classname="org.apache.struts.util.StrutsValidator" method="validateRequired" methodParams="java.lang.Object, org.apache.commons.validator.ValidatorAction, org.apache.commons.validator.Field, org.apache.struts.action.ActionErrors, javax.servlet.http.HttpServletRequest" msg="errors.required"> </validator>

L'elemento validator permette anche l'uso di un sottoelemento javascript ma per amor di sintesi non verr qui riportato. Il supporto per JavaScript all'interno del Validator discusso pi avanti nel capitolo. L'elemento validator supporta sette attributi come qui elencato:

<!ATTLIST validator name classname method methodParams msg depends jsFunctionName

CDATA CDATA CDATA CDATA CDATA CDATA CDATA

#REQUIRED #REQUIRED #REQUIRED #REQUIRED #REQUIRED #IMPLIED #IMPLIED>

L'attributo name assegna un nome logico alla regola di validazione. Viene usato per effettuare il reference della regola ad altre regole all'interno di questo file e del file specifico per le applicazioni che sar esaminato pi avanti. Il nome deve essere univoco. Gli attributi classname e method definiscono classe e metodo che contengono la logica per la regola di validazione. Per esempio, come stato visto nel codice sopra, il metodo validateRequired() nella classe StrutsValidator sar invocato per la regola di validazionerequired. L'attributo methodParams un elenco di parmetri separati da virgola per il metodo definito nell'attributo method. L'attributo msg una chiave proveniente dal resource bundle. Il framework Validator usa questo valore per ricercare un messaggio nel resource bundle di Struts quando si verifica un errore. Come predefiniti; Validator impiega i seguenti valori:

errors.required={0} is required. errors.minlength={0} cannot be less than {1} characters. errors.maxlength={0} cannot be greater than {1} characters. errors.invalid={0} is invalid. errors.byte={0} must be a byte. errors.short={0} must be a short. errors.integer={0} must be an integer. errors.long={0} must be a long. errors.float={0} must be a float. errors.double={0} must be a double. errors.date={0} is not a date. errors.range={0} is not in the range {1} through {2}. errors.creditcard={0} is not a valid credit card number. errors.email={0} is an invalid email address
Si dovrebbero aggiungere questi valori al resource bundle della propria applicazione, o in alternativa cambiare i valori della chiave in validation-rules.xml se si desiderano usare messaggi alternativi. L'attributo depends viene usato per specificare altre regole di validazione che dovrebbero essere chiamate prima della regola che lo specifica. L'attributo depends illustrato nella regola di validazione minLength:

<validator name="minLength" classname="org.apache.struts.util.StrutsValidator" method="validateMinLength" methodParams="java.lang.Object, org.apache.commons.validator.ValidatorAction, org.apache.commons.validator.Field, org.apache.struts.action.ActionErrors, javax.servlet.http.HttpServletRequest" depends="required" msg="errors.minlength"> </validator>
Prima che la regola di validazione minLength sia chiamata, verr invocata la regola required. Si pu anche impostare una regola che dipenda da regole multiple separando le regole nell'attributo depends con una virgola: depends="required,integer" Se una regola che specificata nell'attributo depends non riesce a compiere la validazione, non verr invocata la regola successiva. Per esempio, nella regola di validazione minLength mostrata precedentemente, il metodo

validateMinLength() non sar invocato se la regola di validazione required non va a buon fine. Questo perch
non ha senso controllare la lunghezza di un valore se non presente alcun valore. L'attributo finale supportato dall'elemento validator l'attributo jsFunctionName. Questo attributo facoltativo permette di specificare il nome della funzione JavaScript. Come predefinito verr usato il nome dell'azione di Validator. Il framework Validator piuttosto generico. Contiene regole molto semplici e a grana grossa che possono essere impiegate per qualsiasi applicazione. Come si vedr in seguito proprio questa genericit che permette al Validator di essere impiegato in applicazioni non basate su Struts. La classe org.apache.commons.Validator.GenericValidator implementa le regole generiche come fossero una serie di metodi pubblici statici. La Tabella 11-1 elenca le regole di validazione disponibili nella classe GenericValidator.
Tabella 11-1. Regole di validazione nella classe GenericValidator

Nome del metodo esclusi gli spazi.

Descrizione

isBlankOrNull Controlla che il campo non contenga il valore null e che la lunghezza del campo sia maggiore d zero isByte isDate isDouble isEmail isFloat isInRange isInt isLong isShort matchRegexp maxLength minLength
Controlla che il valore possa essere convertito in un byte primitivo. Controlla che il campo sia una data valida. Controlla che il valore possa essere convertito in una primitiva a doppia precisione. Controlla che il campo sia un indirizzo email valido. Controlla che il valore possa essere convertito in una primitiva a virgola mobile. Controlla che il valore stia in un determinato intervallo. Controlla che il valore possa essere convertito in una primitiva int. Controlla che il valore possa essere convertito in una primitiva long. Controlla che il valore possa essere convertito in una primitiva short. Controlla se il valore uguale all'espressione regolare. Controlla se il valore minore o uguale al massimo. Controlla se il valore maggiore o uguale al minimo.

isCreditCard Controlla se il campo un numero di carta di credito valido.

Dal momento che le regole di validazione in GenericValidator sono cos granulari, gli sviluppatori di Struts hanno aggiunto una classe di utilit chiamata org.apache.struts.util.StrutsValidator, che definisce un insieme di metodi ad alto livello associati a Struts ma che ne rendono pi semplice l'interazione con Struts. Sono elencati qui di seguito senza descrizione, comunque molto simile a quella dei metodi di Tabella 11-1:

validateByte validateCreditCard validateDate validateDouble validateEmail validateFloat validateInteger validateLong validateMask validateMinLength validateMaxLength validateRange validateRequired validateShort
La classe StrutsValidator contiene la logica di validazione concreta usata da Struts. Questa classe e i metodi appena citali sono configurati in maniera dichiarativa nel file validation-rules.xml. Quando uno di questi metodi viene invocato e la validazione non ha successo, viene creato automaticamente un ActionError e aggiunto all'oggetto ActionErrors. Questi errori sono conservati nella richiesta e resi disponibili ai componenti dello strato view.

11.2.2.2 Il file validation.xml

Il secondo file di configurazione richiesto da Validator validation.xml. Questo file specifico per una certa applicazione;descrive infatti quali regole di validazione del file validation-rules.xml debbano essere adoperate da uno specifico ActionForm. Questo significa che sono configurate in modo dichiarativo: non si deve aggiungere codice alla classe ActionForm. La logica di validazione associata con una o pi classi ActionForm attraversoquesto file esterno. Il file validation.xml gestito mediante il validation_1_1.dtd. L'elemento pi esterno l'elemento form-validation che pu contenere due oggetti figli, global e formset. L'elementoglobal pu essere presente nessuna o pi volte, mentre l'elemento formset deve essere presente una o pi volte

<!ELEMENT form-validation (global*, formset+)>


L'elemento global permette di configurare elementi constant che possono essere usati attraverso il resto del file:

<!ELEMENT global (constant*)>


Vi un'analogia tra questa situazione e definire una costante in un file Java e usarla per tutta la classe. Ecco un frammento di global che definisce due costanti:

<global> <constant> <constant-name>phone</constant-name> <constant-value>^\(?(\d{3})\)?[-| ]?(\d{3})[-| ]?(\d{4})$</constant-value> </constant> <constant> <constant-name>zip</constant-name> <constant-value>^\d{5}(-\d{4})?$</constant-value> </constant> </global> Questo frammento include due costanti, phone e zip, anche se possono esserne incluse quante si desidera. Queste costanti sono disponibili per gli elementi all'interno della sezione formset. Si possono anche riutilizzare all'interno della sezione formset semplicemente riferendosi a loro per nome.
Un esempio contribuir alla migliore comprensione di questo punto. L'Esempio 11-1 mostra un semplice file validation.xml.
Esempio 11-1. Un semplice file Validation.xml

<form-validation> <global> <constant> <constant-name>phone</constant-name> <constant-value>^\(?(\d{3})\)?[-| ]?(\d{3})[-| ]?(\d{4})$</constantvalue> </constant> </global> <formset> <form name="checkoutForm"> <field property="phone" depends="required,mask"> <arg0 key="registrationForm.firstname.displayname"/> <var> <var-name>mask</var-name> <var-value>${phone}</var-value> </var> </field> </form> </formset> </form-validation>
Nell'Esempio 11-1, la costante phone dichiarata nella sezione global viene impiegata nell'elemento var per contribuire a validare la propriet phone. L'elemento formset pu contenere due elementi figli constant e form. L'elemento constant ha lo stesso formato di quello nella sezione global. Pu essere presente nessuna o pi volte, mentre l'elemento form pu essere presente una o pi volte all'interno dell'elemento formset:

<!ELEMENT formset (constant*, form+)>


L'elemento formset supporta due attributi per l'internazionalizzazione, language e country:

<!ATTLIST formset language CDATA #IMPLIED country CDATA #IMPLIED>

Se non si ha alcuna necessit di internazionalizzare le proprie routine di validazione e si desidera impiegare l'impostazione predefinita si possono lasciare senza modifiche questi attributi o eliminarli. La sezione "Validazione e internazionalizzazione" pi avanti nel capitolo affronter nei particolari tale argomento. L'elemento form definisce un insieme di campi da validare mentre name corrisponde all'identificatore che l'applicazione assegna al form. Nel framework Struts, questo l'attributo name dalla sezione form-beans. L'elemento form definisce una serie di campi da validare. Contiene un singolo attributo name, che dovrebbe essere uguale a quello di uno degli attributi name dalla sezione form-beans del file di configurazione di Struts. L'elemento form pu contenere uno o pi elementi field:

<!ELEMENT form (field+)>


L'elemento field corrisponde a una propriet specifica di un JavaBean che deve essere validato. In un'applicazione Struts, questo JavaBean una ActionForm. Nell'Esempio 11-1, l'unico elemento field per checkoutForm corrisponde alla propriet phone in una ActionForm detta checkoutForm nella sezione form-beans del file di configurazione di Struts. L'elemento field supporta vari attributi elencati nella Tabella 11-2.
Tabella 11-2. Gli attributi dell'elemento field

Attributo

Descrizione Il nome della propriet del JavaBean (oActionForm) da validare. Elenco delimitato da virgole di regole di validazione da applicare a questo campo. Perch field abbia successo, tutti i validatori devono essere soddi fatti. s Il JavaBean corrispondente a questo form potrebbe includere una propriet page. Solo i campi coni un valore dell'attributopage uguale o minore al valore della propriet page nel form JavaBean sono elaborati. Questa caratteristica risulta utile quando si lisa un approccio di tipo "wizard" per completare un form molto grande per assicurarsi che la pagina non sia saltata.

property depends

page

Il nome del metodo che restituir un array o una Collection impiegata per recuperare indexedListProperty la lista e poi effettuare un ciclo attraverso quest'ultima svolgendo le validazioni per questo campo.

Sia ValidatorActionForm che DynaValidatorActionForm coincidono con la mappatura delle azioni piuttosto che con il nome del form. Vale a dire che, invece di coincidere per il nome del form nell'attributo name dell'elemento form, si pu usare l'attributo path dell'elemento action. In questo modo si permette allo stesso form di essere riutilizzato per diversi mapping di azioni, dove ciascun mapping pu dipendere da certi campi per essere validato mentre gli altri possono essere ignorati. L'elemento field contiene diversi elementi figli:

<!ELEMENT field (msg?, arg0?, arg1?, arg2?, arg3?, var*)>


L'elemento figlio msg permette di specificare un messaggio alternativo per un elemento field. La regola di validazione pu usare questo valore anzich il messaggio di default dichiarato dalla regola. Il valore per l'elemento msg deve essere una chiave del resource bundle dell'applicazione. Ad esempio:

<field property="phone" depends="required,mask"> <msg name="mask" key="phone.invalidformat"/> <arg0 key="registrationForm.firstname.displayname"/> <var> <var-name>mask</var-name> <var-value>${phone}</var-value> </var> </field>
L'elemento msg supporta tre attributi:

<!ATTLIST msg name CDATA #IMPLIED key CDATA #IMPLIED resource CDATA #IMPLIED >
L'attributo name specifica la regola con la quale dovrebbe essere usato msg. Il valore dovrebbe essere quello di una delle regole specificate nel file validation-rules.xml o nella sezione global. L'attributo key specifica una chiave del resource bundle che dovr essere aggiunta ad ActionError se la validazione fallisce. Se si desidera specificare un messaggio scritto, anzich utilizzare il resource bundle si pu impostare a false l'attributo resource. In questo caso l'attributo key preso come stringa di testo.

L'elemento field permette di includere fino a quattro elementi aggiuntivi. Questi elementi, cio arg0, argl, arg2 e arg3, sono impiegati per passare valori addizionali al messaggio sia tramite il resource bundle sia tramite gli elementi var o constant. L'elemento arg0 definisce il primo valore sostitutivo, argl il secondo e cosi via. Ogni elemento arg supporta tre attributi, name, key e resource, che sono omologhi degli attributi dell'elemento msg prima descritto. L'Esempio 11-1 include gli elementi per arg0 e argl:

<field property="phone" depends="required,mask,minLength"> <arg0 key="registrationForm.firstname.displayname"/> <arg1 name="minlength" key="${var:minLength}" resource="false"/> <var> <var-name>mask</var-name> <var-value>${phone}</var-value> </var> <var> <var-name>minLength</var-name> <var-value>5</var-value> </var> </field>
L'ultimo degli elementi figli di field l'elemento var, come si nota nell'Esempio 11-1 e nel frammento precedente. Questo elemento pu impostare parametri che un elemento field pu passare a una delle sue regole di validazione come un valore massimo e minimo in un intervallo di validazione. Questi parametri possono anche essere referenziati da uno degli elementi arg con una sintassi di shell: $(var:var-name}. Nell'Esempio 11-1, il valore che viene sostituito per la costante phone passato attraverso la maschera della regola di validazione per cui pu essere adoperato per controllare se il valore della propriet conforme alla maschera phone appropriata. L'elemento field pu avere zero o pi elementi var. Una volta che si sono configurati i due file di risorse XML per la propria applicazione essi vanno posti nella directory WEB-INF. Saranno quindi referenziati all'interno del file di configurazione di Struts come descritto nella sezione successiva.

11.2.3 Inserimento del Validator


Ogni applicazione Struts deve sapere se si impiega l'infrastruttura Validator. Come gi accennato nel Capitolo 9, si pu usare il meccanismo PlugIn per "agganciare" Validator a un'applicazione Struts. Le prime versioni del Vahoator usavano una servlet aggiuntiva per informare l'applicazione Struts clic i componenti del validator erano presenti. La ValidatorServlet ora deprecata e non dovrebbe essere adoperata.

Il seguente frammento mostra il modo in cui impostare il Validator come plug-in:

<plug-in className="org.apache.struts.validator.ValidatorPlugIn"> <set-property property="pathnames" value="/WEB-INF/validator-rules.xml,/WEB-INF/validator.xml"/> </plug-in>


C' stata una certa confusione in una delle recenti versioni beta per il Validator che impiegava elementi multipli set-property che ora non sono pi supportati; si dovrebbe pertanto usare un singolo elemento set-property che specifica file di risorse di Validator multipli, separati da virgole. Si noti pure che il valore della propriet il plurale pathnames. Struts invocher il metodo init() nella classe ValidatorPlugIn all'avvio dell'applicazione. Nel corso dell'esecuzione di questo metodo sono caricate le risorse di Validator in memoria cos che possano essere disponibili all'applicazione. Prima dell'invocazione del metodo init() tuttavia, il valore della propriet pathnames viene passato all'istanza di ValidatorPlugIn. Questo il modo in cui ValidatorPlugIn trova quali risorse di Validator caricare. Per maggiori ragguagli sul funzionamento del meccanismo PlugIn si torni a "Estendere il framework Struts" nel Capitolo 9.

11.3 Usare una ActionForm con Validator


Non si pu usare la classe ActionForm con il Validator. Al suo posto si deve usare una sottoclasse di ActionForm progettata per l'interazione con Validator. Ci sono due sottoclassi root tra cui scegliere nel caso si desiderino usare ActionForms dinamici. La Figura 11-1 mostra l'ActionForm e i suoi discendenti per una migliore visualizzazione della gerarchia.

Figura 11-1. La gerarchia di classi di ActionForm.

Se si usano ActionForm dinamiche, si dovrebbe usare la branca della gerarchia che fa capo a DynaValidatorForm Se si usano invece ActionForm standard, si pu usare ValidatorForm o una delle sue sottoclassi. Che si usino ActionForm dinamiche o regolari, il Validator si configura allo stesso modo. Occorre solo assicurarsi che qualsiasi sottoclasse di ActionForm si scelga la sezione form-bean del file di configurazione di Struts sia configurata usando il nome completo di classe Java. Si veda "Configurare le applicazioni Struts" nel Capitolo 4 per dettagli maggiori. La prima decisione da prendere quando si deve scegliere una sottoclasse ActionForm appropriata : dinamica o standard? Si noti che in entrambe le sottoderivazioni della classe ActionForm, sia dinamica che standard, ci sono due versioni del form ValidatorForm. La classe genitore chiamata ValidatorForm, o DynaValidatorForm per la branca dinamica. Ciascuna comprende una sottoclasse che contiene il nome Action nel suo titolo. La sottoclasse di ValidatorForm ValidatorActionForm, mentre DynaValidatorActionForm la sottoclasse di DynaValidatorForm. Lo scopo delle due versioni diverse di permettere l'associazione della validazione con la definizione di form-bean oppure la definizione dell'azione. Le classi ValidatorActionForm e DynaValidatorActionForm passano in seguito l'attributo path dall'elemento action nel Validator e il Validator usa il nome dell'azione per cercare tra le regole di validazione. Se si adopera ValidatorForm o DynaValidatorForm, viene usato il nome di ActionForm per cercare la regola di validazione da usare. La sola ragione per usare una o l'altra classe un controllo pi fine sulle regole di validazione da usare. Per esempio, si supponga che una ActionForm contenga tre diverse regole di validazione ma solo due di esse debbano venir svolte per una particolare azione. Si potrebbero configurare le regole per svolgere solo il sottoinsieme di regole di validazione quando quella certa azione viene invocata. Altrimenti tutte le regole sarebbero invocate. In generale l'uso di ValidatorForm o di DynaValidatorForm dovrebbe essere sufficiente per i propri scopi Si prenda in esame un esempio pi completo dell'utilizzo del framework Validator Come gi successo nei capitoli precedenti si prender ad esempio l'applicazione Storefront per migliorare la comprensione del Validator. In particolare si esaminer il form HTML usato per catturare le informazioni di spedizione durante il checkout dell'applicazione Storefront come da Figura 1-2.

Figura 11-2. Raccolta delle informazioni di spedizione

Per questo esempio si user un form dinamico e quindi utilizzeremo la classe DynaValidatorForm per raccogliere i dettagli dell'indirizzo di spedizione. Dal momento che il processo di checkout si protrarr per diverse pagine e si dovranno raccogliere dati per pi pagine, si dovr configurare il bean del form affinch possieda uno scope di sessione. Si raccoglieranno anche tutte le informazioni di checkout in una sola classe ActionForm. Invece di avere uno ShippingForm e un CreditCardForm, si creer un solo form, chiamato CheckoutForm che cattura tutte quante le informazioni. Nel nostro file di configurazione di Struts si dovr configurare il checkoutForm come indicato di seguito:

<form-bean name="checkoutForm" type="org.apache.struts.validator.DynaValidatorForm"> <form-property name="firstName" type="java.lang.String"/> <form-property name="lastName" type="java.lang.String"/> <form-property name="address" type="java.lang.String"/> <form-property name="city" type="java.lang.String"/> <form-property name="state" type="java.lang.String"/> <form-property name="postalCode" type="java.lang.String"/> <form-property name="country" type="java.lang.String"/> <form-property name="phone" type="java.lang.String"/> </form-bean>
L'attributo type specifica l'esatta sottoclasse ActionForm. Nelle prime versioni beta di Struts 1.1, la sezione form-bean richiedeva che si impostasse l'attributo dynamic come true quando si usavano ActionForm, dinamiche. Questo non pi necessario dal momento che il framework determiner se la classe specificata nell'attributo type discende dalla classe DynaActionForm.

Il passo successivo consiste nella modifica della logica di validazione specifica per l'applicazione. Tale modifica va effettuata nel file validation.xml. Va dichiarata una regola di validazione per ciascuna propriet nel form che si deve validare. In alcuni casi si dovranno specificare regole multiple. Nella Figura 11-2, per esempio, il campo phone deve essere inserito e deve rispettare un formato specifico. Queste sono due regole che devono essere entrambe rispettate, altrimenti l'invio del form non avr successo. Non verr mostrato tutto il file validation.xml dal momento che molto esteso e la maggior parte di questo ridondante. La sezione mostrata nell'Esempio 11-2 permetter di capire meglio la connessione dei vari elementi.
Esempio11-2. Un file validation.xml d'esempio per il form checkout

<formset> <constant> <constant-name>phone</constant-name> <constant-value>^\(?(\d{3})\)?[-| ]?(\d{3})[-| ]?(\d{4})$</constantvalue> </constant> <constant> <constant-name>zip</constant-name> <constant-value>^\d{5}(-\d{4})?$</constant-value> </constant> <form name="checkoutForm"> <field property="firstName" depends="required,mask"> <arg0 key="label.firstName"/> <var> <var-name>mask</var-name> <var-value>^[a-zA-Z]*$</var-value> </var> </field> <field property="postalCode" depends="required,mask"> <arg0 key="registrationForm.zip"/> <var> <var-name>mask</var-name> <var-value>${zip}</var-value> </var> </field> <field property="phone" depends="required,mask"> <arg0 key="registrationForm.phone"/> <var> <var-name>mask</var-name> <var-value>${phone}</var-value> </var> </field> </form> </formset> </form-validation>
Ora che per Storefront stato configurato tutto, si pu provare a lanciare l'esempio. Il vantaggio di usare un approccio dichiarativo rispetto a quello programmativo che, una volta che si configurato tutto, si pu partire. L'assenza di programmazione rende l'approccio dichiarativo molto pi semplice. Questo specialmente vero per il framework validator. Non c' codice da scrivere finch ci sono necessit speciali. Quando si invia la pagina con le informazioni sull'indirizzo di spedizione con certi campi non compilati, entrano in gioco le regole di validazione. Il risultato illustrato dalla Figura 11-3.

Figura 11-3. La pagina con l'indirizzo di spezione usando il framework Validator

11.4 Creare regole di validazione personalizzate


Il Validator preconfigurato con molte delle regole pi comuni per le applicazioni basate su Struts. Se la propria applicazione contiene delle richieste di validazione non incluse nelle regole di default si possono creare le proprie liberamente. Ci sono diversi passi da seguire per creare le proprie regole personalizzate: 1. 2. 3. Si crei una classe Java che contiene metodi di validazione. Si modifichi il file validation-rules.xml oppure se ne crei la propria versione. Se si crea un nuovo file di risorse di validazione, ci si accerti di averlo aggiunto alla lista di file di risorsa nel plug-in Validator. Nel file validation.xml si devono usare poi le nuove regole per l'applicazione.

Ogni metodo di validazione creato deve inoltre avere la seguente firma:

public static boolean validateXXX( java.lang.Object, org.apache.commons.validator.ValidatorAct ion, org.apache.commons.validator.Field, org.apache.struts.action.ActionErrors, javax.servlet.http.HttpServletRequest, javax.servlet.ServletContext );
dove validateXXX pu essere qualsiasi cosa, tranne un nome duplicato di regola. La Tabella 11-3 espone gli argomenti per il metodo validateXXX()

Tabella 11-3. Gli argomenti per il metodo validateXXX()

Parametro

Descrizione II JavaBean sul quale viene svolta la validazione. La ValidatorAction corrente. L'oggetto field che viene validato Gli oggetti errors da aggiungere ad ActionError se la validazione fallisce. L'oggetto di richiesta corrente II ServletContext dell'applicazione.

Object ValidatorAction Field ActionErrors HttpServletRequest ServletContext

Nella maggior parte dei casi, il metodo dovrebbe essere statico. Tuttavia si possono anche definire metodi a livello di istanza. In ogni caso ci si deve assicurare che i propri metodi siano thread-safe. L'Esempio 11-3 illustra una nuova regola di validazione che valuta se un valore di tipo String sia un valore boolean valido.
Esempio 11-3. Una regola di validazione che valida un valore booleano

import import import import import import import import import import import

java.io.Serializable; java.util.Locale; javax.servlet.ServletContext; javax.servlet.http.HttpServletRequest; org.apache.commons.validator.Field; org.apache.commons.validator.GenericTypeValidator; org.apache.commons.validator.GenericValidator; org.apache.commons.validator.ValidatorAction; org.apache.commons.validator.ValidatorUtil; org.apache.struts.action.ActionErrors; org.apache.struts.util.StrutsValidatorUtil;

public class NewValidator implements Serializable { /** * A validate routine that ensures the value is either true or false. */ public static boolean validateBoolean( Object bean, ValidatorAction va, Field field, ActionErrors errors, HttpServletRequest request ) { String value = null; // The boolean value is stored as a String if (field.getProperty() != null && field.getProperty().length( ) > 0){ value = ValidatorUtil.getValueAsString(bean, field.getProperty( ) ); } Boolean result = Boolean.valueOf(value); if ( result == null ){ errors.add( field.getKey( ), StrutsValidatorUtil.getActionError(request, va, field)); } // Return true if the value was successfully converted, false otherwise return (errors.empty( )); } }
Il passo successivo consiste nell'aggiunta di questa nuova regola al file validation-rules.xml o, per mantenere le cose distinte, a un nuovo file. L'elemento validator per la regola validateBoolean dovrebbe avere questo aspetto:

<validator name="boolean" classname="NewValidator" method="validateBoolean" methodParams="java.lang.Object, org.apache.commons.validator.ValidatorAction, org.apache.commons.validator.Field, org.apache.struts.action.ActionErrors, javax.servlet.http.HttpServletRequest"

msg="errors.boolean">
Infine si usi la nuova regola nel file validation.xml. Questo comprende anche la creazione di un elemento field che coincida con la propriet boolean su una ActionForm:

<field property="sendEmailConfirmation" depends="boolean"> <arg0 key="label.emailconfirmation"/> </field>

11.5 Validator e i tag personalizzati JSP


Parecchi tag personalizzati JSP inclusi nelle librerie di tag di Struts possono essere impiegati con il Validator. Uno dei tag si utilizza per generare JavaScript dinamico basato sulle regole di validazione. Gli altri tag fanno parte delle caratteristiche peculiari di Struts e sono impiegati con o senza Validator. I tag elencati nella Tabella 11-4 sono generici e possono essere usati a prescindere dal Validator ma, se si dovesse usarlo, diventano utilissimi.
Tabella 11-4. Tag personalizzati JSP che possono essere usati con il Validator

Nome tag

Descrizione Mostra tutti gli errori di validazione avvenuti durante l'elaborazione Determina se ci sono stati errori di validazione Mostra tutti i messaggi rinvenuti durante l'elaborazione Determina se ci sono stati messaggi durante l'elaborazione

Errors ErrorsExist Messages MessagesExist

I tag nella Tabella 11-4 consentono alle pagine JSP di riconoscere e di ottenere acceso a messaggi o errori riconosciuti nell'applicazione Struts. Questi tag sono stati gi trattati in dettaglio nel Capitolo 8.

11.5.1 Uso di JavaScript con il Validator


II framework Validator pu anche generare JavaScript per le proprie applicazioni Struts usando la stessa infrastruttura della validazione lato server. Per questo scopo si impiegano dei tag personalizzati JSP appositamente progettati.

11.5.1.1 Configurazione del file validation-rules.xml per JavaScript


Il tag personalizzato del Validator chiamato JavascriptValidator viene usato per generare una validazione lato client basata su un'attributo javascript presente all'interno dell'elemento validator. Prima che possano essere usati i tag personalizzati JSP, deve essere presente un elemento javascript per la regola di validazione. Il seguente frammento di codice illustra la regola di validazione required che include un elemento javascript:

<validator name="required" classname="org.apache.struts.util.StrutsValidator" method="validateRequired" methodParams="java.lang.Object, org.apache.commons.validator.ValidatorAction, org.apache.commons.validator.Field, org.apache.struts.action.ActionErrors, javax.servlet.http.HttpServletRequest" msg="errors.required"> <javascript><![CDATA[ function validateRequired(form) { var bValid = true; var focusField = null; var i = 0; var fields = new Array( ); oRequired = new required( ); for (x in oRequired) { if ((form[oRequired[x][0]].type == 'text' ||

form[oRequired[x][0]].type == 'textarea' || form[oRequired[x][0]].type == 'select-one' || form[oRequired[x][0]].type == 'radio' || form[oRequired[x][0]].type == 'password') && form[oRequired[x][0]].value == '') { if (i == 0) focusField = form[oRequired[x][0]]; fields[i++] = oRequired[x][1]; bValid = false; } } if (fields.length > 0) { focusField.focus( ); alert(fields.join('\n')); } return bValid; }]]> </javascript> </validator>
Quando il tag JavascriptValidator viene incluso nella pagina JSP, il testo dell'elemento javascript viene scritto nella pagina JSP per permettere alla pagina di offrire una validazione lato client. Quando l'utente invia il form, viene svolta la validazione lato client e per ogni validazione fallita sono mostrati messaggi all'utente. Si dovr includere il tag javascript con il nome dell'ActionForm che sta per validare:

<html:javascript formName="checkoutForm"/>
L'attributo formName viene impiegato per ricercare la serie di regole di validazione da impiegare come JavaScript nella pagina. Si dovr aggiungere manualmente un gestore degli eventi onsubmit per il form:

<html:form action="getPaymentInfo" onsubmit="return validateCheckoutForm(this);"> La funzione JavaScript validateCheckoutForm() viene invocata all'atto dell'invio del form. Verrano eseguite le regole di validazione e, se una o pi regole non trovano riscontro positivo, il form non sar inviato. Il tag javascript genera una funzione con il nome di validateXXX(), dove XXX il nome dell' ActionForm . Per esempio, se la propria ActionForm si chiama checkoutForm, il tag javascript creer una funzione JavaScript validateCheckoutForm() che esegue la logica di validazione. E questo il motivo per cui il gestore degli eventi onsubmit() chiama la funzione validateCheckoutForm().
Come impostazione predefinita, il tag JavascriptValidator genera funzioni JavaScript sia dinamiche che statiche. Se si desidera includere un file separato che contiene funzioni JavaScript statiche per trarre vantaggio dalla possibilit di caching del browser o per meglio organizzare la propria applicazione, si possono usare gli attributi dynamicJavascript e staticJavascript, entrambi con il flag impostato a true, come valore predefinito. Si pu impostare l'attributo staticJavascript a false nel proprio form e includere una pagina JavaScript separata con l'attributo dynamicJavascript impostato a false e l'attributo staticJavascript impostato a true. Si veda la documentazione del tag JavascriptValidator per ulteriore approfondimento..

11.6 Validazione e internazionalizzazione


Il framework Validator impiega il resource bundle dell'applicazione per la generazione di messaggi d'errore sia per la validazione lato server che per quella lato client. Per questo, dal punto di vista dell'internazionalizzazione, la maggior parte di questo lavoro per la visualizzazione di messaggi specifici per la lingua locale dell'utente demandato all'infrastruttura. Nelle pagine precedenti stato accennato il fatto che l'elemento formset nel file validation.xml supporta attributi relativi all'internazionalizzazione, che sono language, country, e variant. Come gi si sa, questi attributi corrispondono alla classe java.util.Locale. Se non si specificano questi attributi, impiegata l'impostazione Locale predefinita. Se la propria applicazione ha come prerequisito la possibilit di effettuare la validazione secondo le modalit di I18N, si potranno creare elementi formset separati, uno per ciascun Locale che si deve supportare per ciascun form che si deve validare. Per esempio, se la propria applicazione deve poter supportare la validazione per un form chiamato registrationForm sia per l'impostazione locale predefinita sia per le impostazioni per la localizzazione del Francese, il file validation.xml dovrebbe contenere due elementi formset: uno per l'impostazione predefinita e l'altro per quelle relative al Francese, come nella seguente porzione di codice:

<formset> <form name="registrationForm"> <field property="firstName" depends="required,mask,minLength"> <arg0 key="registrationForm.firstname.displayname"/> <var> <var-name>mask</var-name> <var-value>^\w+$</var-value> </var> <var> <var-name>minLength</var-name> <var-value>5</var-value> </var> </field> </form> </formset> <formset language="fr"> <form name="registrationForm"> <field property="firstName" depends="required,mask,minLength"> <arg0 key="registrationForm.firstname.displayname"/> <var> <var-name>mask</var-name> <var-value>^\w+$</var-value> </var> </field> </form> </formset>

11.7 Uso di Validator al di fuori di Struts


Anche se Validator stato progettato in origine per interoperare con Struts, nulla vieta di impiegarlo per la validazione di un qualsiasi JavaBean. Ci sono diversi passi che devono essere seguiti prima che questo framework funzioni al di fuori di Struts. Anche se non dipende da Struts, Validator stato integrato con molto lavoro per incrementarne la facilit di utilizzo all'interno di Struts. Questo funzionamento dovr essere replicato per la propria applicazione se si desidera usare il Validator senza Struts. Le dipendenze dai package sono le stesse tanto per Struts che per altre applicazioni. Sono necessari sia ORO che i package Commons Logging, Commons BeanUtils, Commons Collections e Digester. Ci sar anchebisogno di un parser XML conforme alle specifiche SAX 2.0. Non si sarobbligati a per usare Struts. Il primo funzionamento da replicare sono le funzioni per caricare e inizializzare le risorse XML del Validator. Questi sono due file XML usati per configurare le regole per il Validator. Quando il framework Validator viene usato in congiunzione con Struts, la classe org.apache.struts.Validator.ValidatorPlugIn svolge questo compito. Tuttavia, a causa della dipendenza della ValidatorPlugIn da Struts, si dovr creare un approccio alternativo per inizializzare le risorse appropriate del Validator. Per questo si pu creare una semplice classe Java che svolge le stesse funzioni di ValidatorPlugIn ma che non ha una dipendenza su Struts. Nell'Esempio 11-4 mostrata una semplice applicazione dei concetti esposti.
Esempio 11-4. Uso del Validator al di fuori di Struts

import import import import

java.util.*; java.io.*; org.apache.commons.validator.ValidatorResources; org.apache.commons.validator.ValidatorResourcesInitializer;

public class ValidatorLoader{ private final static String RESOURCE_DELIM = ",";

protected ValidatorResources resources = null; private String pathnames = null; public ValidatorLoader( loadPathnames( ); initResources( ); } ) throws IOException {

public ValidatorResources getResources( return resources; } public String getPathnames( return pathnames; } ) {

){

public void setPathnames(String pathnames) { this.pathnames = pathnames; } protected void loadPathnames( ){ // Set a default just in case String paths = "validation-rules.xml,validation.xml"; InputStream stream = null; try{ // Load some properties file stream = this.getClass( ).getResourceAsStream( "validator.properties" ); if ( stream != null ){ Properties props = new Properties( ); props.load( stream ); // Get the pathnames string from the properties file paths = props.getProperty( "validator-pathnames" ); } }catch( IOException ex ){ ex.printStackTrace( ); } setPathnames( paths ); } protected void initResources( ) throws IOException { resources = new ValidatorResources( ); if (getPathnames() != null && getPathnames().length( ) > 0) { StringTokenizer st = new StringTokenizer(getPathnames( ), RESOURCE_DELIM); while (st.hasMoreTokens( )) { String validatorRules = st.nextToken( ); validatorRules = validatorRules.trim( ); InputStream input = null; BufferedInputStream bis = null; input = getClass( ).getResourceAsStream(validatorRules); if (input != null){ bis = new BufferedInputStream(input); try { // pass in false so resources aren't processed // until last file is loaded ValidatorResourcesInitializer.initialize(resources, bis, false); }catch (Exception ex){ ex.printStackTrace( ); } } } // process resources

resources.process( } } }

);

Il lavoro svolto in ValidatorLoader dell'Esempio 11-4 molto simile a quello di ValidatorPlugIn: carica e inizializza un'istanza della classeValidatorResources. L'oggetto una rappresentazione in memoria delle regole di validazione per un'applicazione. Questo esempio impiega il metodogetResourceAsStream() per trovare e caricare un file di propriet che contiene la lista dei file delle risorse del Validator. Una volta creata e inizializzata un'istanza della classe ValidatorResources, andr conservata in un qualche luogo. In un'applicazione Struts conservata in ServletContext: la nostra applicazione pu fare affidamento su questo oggetto o si pu inglobare la richiesta in un Singleton.

11.7.1 Modifica del file validation-rules.xml


Nella precedente sezione "Creare regole di validazione personalizzate" si visto il modo in cui possibile estendere il framework Validator con le nostre regole personalizzate. Anche in questa sede si dovr fare la stessa cosa ma saranno diverse le firme dei metodi. Nell'Esempio 11-3 erano inclusi parametri che sono parte delle API Servlet e Struts. Si dovranno usare argomenti diversi per evitare conflitti associativi tra API Servlet e Struts. In primo luogo l'attributo methodParams deve essere modificato per poter supportare argomenti diversi. Quello che segue parte di una regola chiamata currency:

<global> <validator name="currency" classname="com.oreilly.struts.storefront.Validator" methodParams="java.lang.Object,org.apache.commons.validator.Field,java.uti l.List" method="isCurrency" msg="Value is not a valid Currency Amount."/> </global>
Una volta scritta la regola di validazione, essa va aggiunta al file di validazione specifico per l'applicazione:

<form-validation> <global> </global> <formset> <form name="checkoutForm"> <field property="paymentAmount" depends="required,currency"> <arg0 key="registrationForm.paymentamount.invalid"/> </field> </formset> </form-validation>
Da qualche parte nell'applicazione si deve aver accesso all'istanza dell'oggetto ValidatorResources inizializzato nell'Esempio 11-4 da usare per validarc il JavaBean:

ValidatorResources resources = // Get instance of the ValidatorResources Validator validator = new Validator(resources, "checkoutForm"); validator.addResource(Validator.BEAN_KEY, bean); validator.addResource("java.util.List", lErrors); try { // Execute the validation rules validator.validate( ); } catch ( ValidatorException ex ) { // Log the validation exception log.warn( "A validation exception occured", ex ); }
Anche se il framework Validator progettato per l'utilizzo indipendente da Struts, necessario un po' di lavoro prima di poterlo impiegare in questo secondo modo. Tuttavia vale la pena di spendere un po' di fatica per poi risparmiare un mucchio di tempo nel ciclo di sviluppo.

CAPITOLO 12

Internazionalizzazione e Struts
Le aziende non possono pi permettersi di pensare solo ai mercati della propria regione geolinguistica. A partire dalla seconda met degli anni Novanta, il mondo dell'economia stato tempestato di idee che riguardano un'economia di stampo mondiale: si pensi a quello che successo in Europa con l'introduzione della moneta unica. L'economia e le nazioni comprendono di non poter pi pensare solo ai loro mercati tradizionali se vogliono continuare a crescere in maniera significativa; occorre pensare in modo globale e tentare di vendere i propri prodotti e servizi a utenti di tale mondo globalizzato. Con l'esplosione del World Wide Web. alla met degli anni Novanta, le aziende che proponevano i loro prodotti su Internet iniziarono a pensare che offrire un accesso a prodotti e servizi tramite un sito web fosse un modo ideale di attrarre nuovi utenti da tutto il mondo. Una delle ragioni chiave sta nella possibilit di accedervi 24 ore su 24 e 7 giorni su 7. Il web permette a un consumatore di poter fare acquisti, senza le limitazioni dovute al tempo e alle fasce orarie, in qualsiasi momento del giorno o della notte. I tradizionali orari di esercizio sono irrilevanti sul Web. Il significato dell'accesso libero per le aziende di straordinaria importanza e per il supporto di clienti internazionali pu diventare davvero problematico per gli sviluppatori che devono scrivere e aggiornare applicazioni. Questo capitolo si focalizza su ci che serve per rendere le applicazioni basate su Struts adatte per qualsiasi utente mondiale senza limitazioni di lingua o di area geografica. Come spesso si verifica nel caso dello sviluppo di software, la pianificazione l'aspetto pi importante per assicurarsi il successo. Dalla lettura di questo capitolo si potr comprendere come strutturare applicazioni basate su Struts che rendono possibile il supporto di un vasto bacino di utenza.

12.1 Due parole su I18N


Tradizionalmente gli sviluppatori di programmi si concentrano su applicazioni che devono risolvere un problema immediato connesso all'attivit che sta alla base dell'applicazione stessa. Cos operando facile e talvolta necessario assumere determinate decisioni sul linguaggio dell'utente o sul Paese di residenza. Nella maggior parte dei casi dare per scontati questi aspetti va bene, senza possi un interrogativo reale su chi

sar veramente il pubblico cui ci si riferisce. Tuttavia, se vi capitalo mai di reingegnerizzare un'applicazione perch si cambiato parere riguardo alle cose date per scontate, saprete quanto possa essere difficile tornare indietro e correggere il progetto una volta realizzata l'applicazione. L'internazionalizzazione (I18N, sigla composta dalla prima e dall'ultima lettera della parola InternationalizatioN, entro le quali e riportato il numero di lettere comprese tra I e N) , in parole povere il processo di progettazione del proprio software in previsione del supporto linguistico per pi aree onde evitare di tornare a riscrivere l'applicazione tutte le volte che devono essere supportate nazioni e/o lingue nuove. Per dire che un'applicazione supporta l'internazionalizzazione, essa deve avere le seguenti caratteristiche: Possono essere supportati linguaggi aggiuntivi senza necessit di cambiamenti a livello del codice. Elementi di testo, messaggi e immagini hanno una collocazione esterna al codice sorgente. Dati dipendenti dalla cultura locale come date e orari, valori decimali e valuta sono correttamente formattati a seconda della collocazione geolinguistica dell'utente. Sono supportati set di caratteri non standard. L'applicazione pu essere adattata rapidamente a nuove lingue e/o regioni. Quando si internazionalizza un'applicazione, non ci si pu permettere di scegliere quali opzioni devono essere supportate. Vanno implementate tutte, altrimenti il processo si interrompe. Se un utente visita un sito web e tutti i testi, le immagini e i bottoni sono nella lingua corretta ma numeri e valute non sono visualizzati propriamente, l'utente non potr essere a proprio agio. Assicurarsi che l'applicazione possa supportare lingue e aree multiple solo un primo passo. Si deve anche creare una versione localizzata dell'applicazione per ciascuna area geolinguistica che si vuole supportare. Per fortuna i vantaggi di Java e delI'I18N vengono in aiuto proprio per questo. Per le applicazioni che siano state internazionalizzate in modo corretto tutto il lavoro da fare per supportare una nuova lingua o un nuovo Paese rimane esterno al codice sorgente. Si dice locale una regione (di solito geografica ma non necessariamente) che condivide costumi, cultura ed espressione linguistica. Le applicazioni scritte per un solo locale sono di solito definite miopi. La localizzazione (L10N) il processo mediante il quale si adatta la propria applicazione, che stata precedentemente internazionalizzata, alle impostazioni specifiche di un locale. Per applicazioni in cui il supporto per l'I18N non stato pianificato o reso disponibile, di solito questa operazione implica cambiamenti nel codice in cui sono incorporati testo, immagini e messaggi. Dopo che si sono applicati i cambiamenti, il codice sorgente deve essere ricompilato. Si immagini cosa diventerebbe un simile processo ripetuto per tutte le nuove impostazioni locali da supportare! A sentire Richard Gillam dell'Unicode Technology Group, che ha progettato la maggior parte del supporto per l'I18N nelle librerie Java, "L'internazionalizzazione non una caratteristica facoltativa". Gli utenti si aspettano che i prodotti funzionino per loro nelle loro lingue. Quando quello che si d per scontato non corretto, le cose vanno male e gli utenti potrebbero anche rimanere infastiditi. Bisogna iniziare a progettare con un certo anticipo il supporto per l'I18N nella propria applicazione. Anche se sembra che non se ne avr mai bisogno, implementandola comunque si sar sempre un passo avanti e ci non ritarder lo sviluppo della propria applicazione. Non tutte le applicazioni necessitano del supporto di I18N e alcuni programmatori o organizzazioni ,di sviluppatori aggrottano le sopracciglia se sentono parlare di aggiunta di funzionalit al di fuori dei requisiti. Tuttavia, anche se la propria applicazione non deve supportare pi di una impostazione di localizzazione, ci sono altri benefici derivanti dall'inclusione di alcuni aspetti della I18N. Per esempio, usando il resource bundle per tutto il testo statico, s pu risparmiare tempo sia per lo sviluppo che, pi importante ancora, per l'aggiornamento. Nel corso del capitolo si vedr come tutto ci sia vero.

12.2 Supporto per l'I18N in Java


Java offre un ricco insieme di funzionalit per l'I18N nella libreria di base, che saranno trattate brevemente nei prossimi paragrafi. Il supporto per l'I18N all'interno del framework Struts si basa su questi componenti e una buona comprensione del modo in cui essi interagiscono sar di valido aiuto pei internazionalizzare le proprie applicazioni. L'argomento dell'internazionalizzazione; davvero imponente e non possibile trattarlo nei dettagli in questo libro. Una trattazione completa dell'argomento si trova nel libro Java Internationalization di Andy Dbitsch e David Czarnecki (O'Reilly).

12.2.1 La classe Locale


La classe java.util.Locale sicuramente la classe di maggior rilevanza ai fini dell'I18N nella libreria di Java. Quasi tutto il supporto per l'internazionalizzazione e la localizzazione nel linguaggio Java si basa su questa classe.

La classe Locale dota Java delle istanze del concetto di locale poco sopra illustrato. Una particolare istanza di Locale rappresenta un'unica lingua e regione. Quando una classe all'interno della libreria Java modifica la sua funzionalit mentre svolge il suo compito sulla base di un oggetto Locale si dice che locale-sensitive. Per esempio, java.text.DateFormat locale-sensitive dal momento che formatter una data a seconda di un oggetto Locale particolare. Gli oggetti Locale non svolgono alcun lavoro di interpretazione o formattazione del tipo I18N, ma sono usati come identificatori dalle classi locale-sensitive. Quando si ottiene un'istanza della classe DateFormat possibile passarla in un'oggetto Locale per gli Stati Uniti. la classe DateFormat a compiere tutte le operazioni locale-sensitive di interpretazione e formattazione, basandosi sulla Locale solamente per identificare il formato adatto. Occorre prestare molta attenzione quando si impiega la classe java.text.Format o una qualsiasi classe da questa discendente, incluse DateFormat, NumberFormat e SimpleDateFormat, dal momento che non sono thread-safe. Il problema della sicurezza dei thread sussiste dal momento che un'istanza della classe Calendar viene conservata come variabile e si accede ad essa durante le invocazioni dei metodi parse()e format(). Si dovr impiegare un'istanza separata per ciascun thread o sincronizzare esternamente l'accesso. Non si deve conservare un'istanza singola ad esempio nello scope dell'applicazione e permettere a thread multipli di accedervi. Tuttavia possibile conservare le istanze all'interno della sessione utente usando differenti istanze per ciascun utente. Il problema della sicurezza dei thread coinvolge tutte le versioni di Java, inclusa la 1.4. La documentazione delle API per le classi Format stata aggiornata includendo la descrizione di questo problema di progettazione. Quando si crea un oggetto Locale, di solito si specifica la lingua e il codice della nazione. Il seguente frammento di codice illustra la creazione di due oggetti Locale uno per gli Stati Uniti e l'altro per la Gran Bretagna:

Locale usLocale = new Locale("en", "US"); Locale gbLocale = new Locale("en", "GB");
Il primo argomento nel costruttore il codice della lingua. Il codice della lingua consta di due lettere minuscole e deve essere conforme alle specifiche ISO-6.39. Si pu trovare un elenco completo dei codici linguistici all'URL http://www.unicode.org/unicode/onlinedat/languages.html. Il secondo argomento il codice nazionale. Consiste di due lettere maiuscole che devono essere conformi alle specifiche ISO-3166. La lista dei codici nazionali disponibile all'URL http://www.unicode.org/unicode/onlinedat/countries.html. La classe Locale offre molte costanti statiche che permettono di ottenere un'istanza delle impostazioni locali pi diffuse. Per esempio per ottenere un'istanza delle impostazioni locali giapponesi se ne pu impiegare una tra le due seguenti:

Locale locale1 = Locale.JAPAN; Locale locale2 = new Locale("ja", "JP");

12.2.1.1 Le impostazioni predefinite di locale


La JVM interrogher il sistema operativo al suo avvio e imposter il locale predefinito per l'ambiente. Si potranno ottenere le informazioni per queste impostazioni con una chiamata al metodo getDefault() sulla classe Locale:

Locale defaultLocale = Locale.getDefault(

);

Il web container normalmente impiegher le impostazioni predefinite di locale per il proprio ambiente ma user un locale in base alla richiesta del client in HttpServletRequest per visualizzare a uso dell'utente finale informazioni localizzate.

12.2.1.2 Determinazione del locale dell'utente


Nella sezione precedente si appreso come creare oggetti di tipo Locale in Java tramite la trasmissione dei codici linguistico e nazionale al costruttore di Locale. All'interno delle web application incluse quelle che sono state scritte tramite Struts si dovranno raramente creare le proprie istanze dal momento che il container le crea in automatico. L'interfaccia ServletRequest contiene due metodi che possono essere invocati per recuperare le preferenze locali di un client:

public java.util.Locale getLocale( ); public java.util.Enumeration getLocales( ); Entrambi questi metodi impiegano l'header Accept-Language che parte costituente di ciascuna richiesta client
inviata al container delle servlet.

Dal momento che il web server non mantiene aperta a lungo la comunicazione con il browser, le preferenze locali del client sono inviate al servlet container unitamente a ogni richiesta. Struts, come comportamento predefinito, conserver queste informazioni nella sessione dell'utente permettendo di inviarle una sola volta. L'oggetto Locale, se conservato all'interno della sessione, vi conservato con una chiave Action.LOCALE_KEY, che lo trasmette alla stringa org.apache.struts.action.LOCALE. La configurazione della possibilit di conservare le impostazioni locali dell'utente nella sessione avviene nel file di configurazione di Struts, impostando l'attributo locale nell'elemento controller. Se non si imposta alcun valore, questo sar automaticamente impostato a false. Si veda il Capitolo 4 per maggiori informazioni sulle impostazioni del locale. L'invocazione del metodo getLocale() sull'oggetto HttpServletRequest restituisce le impostazioni locali del client mentre il metodo; getLocales() restituisce un elenco (Enumeration) delle localizzazioni preferite in ordine decrescente. Se il client non ha configurato le preferenze di localizzazione, il container delle servlet impiegher le proprie impostazioni locali predefinite. L'Esempio 12-1 illustra come determinare questa informazione con l'impiego di una servlet.
Esempio 12-1. Determinazione delle impostazioni locali dell'utente in una servlet

import import import import import import import import import

java.io.IOException; java.io.PrintWriter; java.util.Enumeration; java.util.Locale; javax.servlet.ServletConfig; javax.servlet.ServletException; javax.servlet.http.HttpServlet; javax.servlet.http.HttpServletRequest; javax.servlet.http.HttpServletResponse;

/** * Prints out information about a user's preferred locales */ public class LocaleServlet extends HttpServlet { private static final String CONTENT_TYPE = "text/html"; /** * Initialize the servlet */ public void init(ServletConfig config) throws ServletException { super.init(config); } /** * Process the HTTP Get request */ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType(CONTENT_TYPE); PrintWriter out = response.getWriter( ); out.println("<html>"); out.println("<head><title>The Esempio Locale Servlet</title></head>"); out.println("<body>"); // Retrieve and print out the user's preferred locale Locale preferredLocale = request.getLocale( ); out.println("<p>The user's preffered Locale is " + preferredLocale + "</p>"); // Retrieve all of the supported locales of the user out.println("<p>A list of preferred Locales in descreasing order</p>"); Enumeration allUserSupportedLocales = request.getLocales( ); out.println("<ul>"); while( allUserSupportedLocales.hasMoreElements( ) ){

Locale supportedLocale = (Locale)allUserSupportedLocales.nextElement( ); StringBuffer buf = new StringBuffer( ); buf.append("<li>"); buf.append("Locale: "); buf.append( supportedLocale ); buf.append( " - " ); buf.append( supportedLocale.getDisplayName( buf.append("</li>"); // Print out the line for a single Locale out.println( buf.toString( ) ); } out.println("</ul>");

) );

// Get the container's default locale Locale servletContainerLocale = Locale.getDefault( ); out.println("<p>The container's Locale " + servletContainerLocale + "</p>"); out.println("</body></html>"); } }
Quando si eseguir la servlet dell'Esempio 12-1, si dovrebbe vedere un risultato simile a quello illustrato nella Figura 12-1.

Figura 12-1. Visualizzazione del browser derivante dall'Esempio 12-1

L'output potrebbe essere anche diverso se si hanno impostazioni locali differenti nel proprio sistema. La maggior parte dei browser web permette di configurare le impostazioni di localizzazione che si desidera supportare. Con Internet Explorer, per esempio si possono modificare le impostazioni linguistiche nel menu a tendina Tools Internet Options. L'impostazione della localizzazione per l'utente facile con Struts. Ci sono di fatto parecchi modi di recuperare la classe Locale per l'utente a seconda di dove si desidera accedere. Se si opera all'interno di una classe Action, per esempio, si pu semplicemente chiamare il metodo getLocale() definito nella classe di base di Struts Action. Il seguente frammento mostra il metodo getLocale():

protected Locale getLocale(HttpServletRequest request) { HttpSession session = request.getSession( ); Locale locale = (Locale) session.getAttribute(LOCALE_KEY); if (locale == null) locale = defaultLocale; return (locale); } Si dovr passare l'oggetto request a questo metodo perch avr bisogno di usare HttpSession per ottenere le
impostazioni locali. Il metodo getLocale() restituir sempre un'istanza di Locale, anche se non ne conservata nessuna nella sessione utente. Il metodo restituir la propriet defaultLocale se necessario. La propriet defaultLocale conservata come una variabile membro statica cui ha accesso ogni sottoclasse di Action:

protected static Locale defaultLocale = Locale.getDefault(

);

L'ottenimento delle impostazioni locali dell'utente da qualsiasi altra parte ugualmente diretto. Si possono recuperare direttamente dalla sessione come fa il metodo getLocale() impiegando Action.LOCALE_KEY:

Locale userLocale = (Locale)session.getAttribute( Action.LOCALE_KEY); // With this approach, always check the Locale to see if it's null if ( userLocale != null ){ // Access the Locale }
Dal momento che possibile che non sia conservata alcuna informazione riguardo a Locale nella sessione utente, si dovrebbe confrontare con null la propriet Locale prima di tentare di usarla. Se la propria applicazione permette all'utente di cambiare le impostazioni locali al volo potrebbe essere necessario chiamare il metodo getLocale() per ciascuna nuova request per vedere se l'utente ha cambiato queste impostazioni. Un esempio di questa operazione stato delineato nella classe CustomRequestProcessor dell'Esempio 5-4.

12.2.2 Resource bundle Java


La classe java.util.ResourceBundle permette di raggruppare un insieme di risorse per un dato set di impostazioni locali. Le risorse sono di solito elementi testuali come campi e pulsanti o messaggi di stato ma possono anche essere oggetti come nomi di immagini, messaggi d'errore e titoli di pagine. Struts non usa la classe ResourceBundle messa a disposizione dal linguaggio Java. Al suo posto offre funzionalit simili tramite classi proprie. La classe org.apache.struts.util.MessageResources e la sua sottoclasse org.apache.struts.util.PropertyMessageResources sono impiegate per svolgere funzionalit parallele a quelle della gerarchia ResourceBundle. Se si comprendono i fondamenti del ResourceBundle nella libreria del codice, si potranno comprendere come operi la versione all'interno di Struts Nella prospettiva attuale, la classe MessageResources avrebbe dovuto essere stata considerata almeno come sottoclasse di Java ResourceBundle.

Si vedr un esempio della creazione di un resource bundle per un'applicazione Struts pi avanti nel capitolo, sotto "Il resource bundle di Struts"

12.2.3 La classe MessageFormat


Le classi Java ResourceBundle e quella Struts MessageResources permettono di usare sia testo dinamico che statico. II testo statico viene impiegato per elementi come etichette di campi e pulsanti in cui le stringhe di localizzazione sono usate esattamente come sono definite nel bundle: in altre parole, quando il testo del messaggio gi prefissato. Con il testo dinamico, parte del messaggio potrebbe non essere conosciuta fino all'esecuzione. Per migliorare la comprensione della descrizione si consideri un esempio. Poniamo che si debba inviare un messaggio all'utente informandolo che i campi nome e telefono devono essere inseriti per poter operare un salvataggio. Il nostro approccio sar di aggiungere delle definizioni come le seguenti al resource bundle:

error.requiredfield.name=The Name field is required to save. error.requiredfield.phone=The Phone field is required to save. // other resource messages
Quest'approccio funziona bene, ma cosa succederebbe se ci fossero centinaia di campi da riempire? Ci sar allora bisogno di un messaggio delle risorse per ciascun campo richiesto e il resource bundle potrebbe diventare smisurato e difficile da gestire. Si noti tuttavia che l'unica differenza tra i due messaggi il nome del campo richiesto. Un metodo pi facile e gestibile quello di usare la funzionalit della classe java.text.MessageFormat. Questo permette di operare nel modo seguente:

error.requiredfield=The {0} field is required to save.label.phone=Phone label.name=Name


I valori che sono ignoti fino all'esecuzione sono sostituiti nel messaggio da un valore intero racchiuso in parentesi graffe. L'intero all'interno delle parentesi graffe impie gato come indice in un Object[] che vieneitrasmesso con il messaggio format() della classe MessageFormat. L'Esempio 12-2 illustra quanto detto.

Esempio 12-2. Uso della classe MessageFormat per formattare messaggi con variabili testuali

import java.util.ResourceBundle; import java.util.Locale; import java.text.MessageFormat; public class FormatEsempio { public static void main(String[] args) { // Load the resource bundle ResourceBundle bundle = ResourceBundle.getBundle( "ApplicationResources" ); // Get the message template String requiredFieldMessage = bundle.getString( "error.requiredfield" ); // Create a String array of size one to hold the arguments String[] messageArgs = new String[1]; // Get the "Name" field from the bundle and load it in as an argument messageArgs[0] = bundle.getString( "label.name" ); // Format the message using the message and the arguments String formattedNameMessage = MessageFormat.format( requiredFieldMessage, messageArgs ); System.out.println( formattedNameMessage ); // Get the "Phone" field from the bundle and load it in as an argument messageArgs[0] = bundle.getString( "label.phone" ); // Format the message using the message and the arguments String formattedPhoneMessage = MessageFormat.format( requiredFieldMessage, messageArgs ); System.out.println( formattedPhoneMessage ); } }
I messaggi che contengono dati variabili sono conosciuti come messaggi compositi (compound messages). L'utilizzo di messaggi compositi permette di sostituire i dati specifici dell'applicazione con messaggi da resource bundle durante l'esecuzione. Pu anche ridurre il numero di messaggi che la propria applicazione richiede nel resource bundle e ci pu ridurre i tempi di traduzione in altre impostazioni di localizzazione.

L'utilizzo di messaggi compositi rende la traduzione appena pi complessa dal momento che il testo contiene valori che non saranno conosciuti se non al momento dell'esecuzione. Inoltre il personale che si occupa della traduzione deve tenere conto di dove va posta la variabile di testo nel messaggio localizzato dal momento che i valori di sostituzione potrebbero trovarsi in posizioni diverse nel messaggio per lngue diverse. Struts include le capacit della classe MessageFormat ma incapsula la funzionalit dietro i componenti all'interno del framework.

12.2.4 Supporto multilingua


Molti sviluppatori rabbrividiscono al solo pensiero di dover supportare gruppi di utenti presenti in una delle molte possibili impostazioni di localizzazione. Nella maggior parte dei casi, tuttavia, una volta che un'applicazione sia stata installata e localizzata come qualsiasi altra applicazione con impostazioni singole di localizzazione. L'utente che accede all'applicazione o appartiene alla stessa area locale oppure di un'area abbastanza simile da poter considerare insignificanti le differenze culturali o di lingua. Le applicazioni multilingua d'altro canto, portano l'internazionalizzazione al livello successivo permettendo agli utenti di aree diverse di accedere alla stessa applicazione. Questo significa che l'applicazione deve essere abbastanza flessibile da riconoscere le impostazioni di localizzazione dell'utente e formattare tutto sulla base di queste. Non necessario sottolineare quanto questa operazione sia maggiormente difficile rispetto alla semplice localizzazione di un'applicazione. La discussione a riguardo della scrittura di applicazioni multilingua talmente vasta che non pu essere contenuta nei termini di questo libro. Nel resto del capitolo ci si concentrer sui problemi relativi all'internazionalizzazione che si incontrano pi comunemente, tralasciando il supporto multilingua.

12.3 L'internazionalizzazione delle applicazioni basate su Struts


II supporto per l'internazionalizzazione offerto da Struts si focalizza quasi soltanto sulla presentazione di testo e immagini dell'applicazione. Non sono incluse funzionalit come ad esempio l'accettazione di input da lingue non tradizionali. Come si gi potuto notare, a seconda della propria configurazione di Struts, il framework pu determinare l'impostazione di localizzazione per un utente e conservarla nella sessione utente. Una volta che le impostazioni dell'utente siano state rilevate Struts pu usare queste impostazioni per ricercare testo e altre risorse dai resource bundle. Questi ultimi sono componenti essenziali all'interno di Struts.

12.3.1 Il resource bundle di Struts


Come gi visto nel Capitolo 4, ciascuno dei moduli della propria applicazione pu essere configurato con uno o pi resource bundle. L'informazione all'interno di ogni bundle viene resa disponibile ad action, form, JSP e tag personalizzati nello stesso modo.

12.3.1.1 Creazione di un resource bundle di Struts


I resource bundle che si creano devono seguire le convenzioni della classe PropertyResourceBundle della libreria di base Java, vale a dire che, se si necessita di creare un file di testo per il proprio resource-bundle, esso ha un'estensione .properties e deve essere conforme alle linee guida discusse nel JavaDoc per la classe java.util.Properties. La pi importante di queste linee guida consiste nel formato del messaggio all'interno di questo file, ovvero:

key=value

L'Esempio 12-3 mostra un file di propriet chiamato StorefrontMessageResources. properties che pu essere caricato da Struts.
Esempio 12-3. Un semplice resource bundle per Struts

global.title=Virtual Shopping with Struts global.error.invalidlogin=The login was unsuccessful! Please try again. global.required={0} is a required value. label.featuredproducts=This Weeks Featured Products label.email=Email Address label.password=Password label.returning label.firstName=First Name label.lastName=Last Name label.address=Address label.city=City label.state=State label.postalCode=Postal Code

label.zip=Zip label.country=Country label.phone=Phone button.add=Add Me button.delete=Delete button.checkout=Checkout button.saveorder=Save Order errors.required={0} is required. errors.minlength={0} can not be less than {1} characters. errors.maxlength={0} can not be greater than {1} characters. errors.invalid={0} is invalid. errors.byte={0} must be a byte. errors.short={0} must be a short. errors.integer={0} must be an integer. errors.long={0} must be a long. errors.float={0} must be a float. errors.double={0} must be a double. errors.date={0} is not a date. errors.range={0} is not in the range {1} through {2}. errors.creditcard={0} is not a valid credit card number. errors.email={0} is not a valid e-mail address.
Ci si deve assicurare di scrivere il nome del file con l'estensione .properties. altrimenti Struts non potr essere in grado di riconoscerlo. Si noti che le chiavi impiegate nell'Esempio 12-3 sono separate da un punto (.). Si potevano usare anche altri caratteri nelle chiavi o in alternativa una chiave a parola singola come labelPhone=Phone. L'utilizzo della spaziatura nelle proprie chiavi un ottimo modo per organizzare il testo localizzato per rendere poi facili gli aggiornamenti e le modifiche ed evitare le collisioni di nomi, in maniera simile a quanto accade con i nomi dei package per le classi Java. Si deve per fare attenzione quando si adoperano come separatori caratteri diversi dal punto. I due punti (:), per esempio, possono essere utilizzati al posto del segno uguale (=) per separare chiavi e valori, quindi causeranno problemi se inseriti all'interno delle proprie chiavi. Se non si vuole usare il punto nelle proprie chiavi si pu adottare l'uso del tratto sottosegnato o underscore (_) o del semplice trattino (-). Gli spazi dovrebbero essere evitati dal momento che possono essere fonte di problemi.

12.3.1.2 Linee guida per il naming del resource bundle


L'operazione di naming del resource bundle ha un'importanza critica per il suo funzionamento. Tutti i resource bundle contengono un nome di base che pu essere selezionato. Nell' Esempio 12-3, il nome StorefrontMessageResources stato impiegato come nome di base. Se si deve mettere a disposizione un resource bundle aggiuntivo per la lingua Francese e la nazione Canada, andr creato un file di propriet chiamato StorefrontResouces_fr_CA.properties con le risorse appropriatamente localizzate. Quando Struts ricerca un messaggio in uno dei bundle cerca quello che coincide il pi possibile con il nome di base e le impostazioni locali. Se non disponibile il nome delle impostazioni locali verranno impiegate quelle predefinite. Solo quando non stato possibile rinvenire un resource bundle con una specifica lingua e relativo codice nazionale come parte del nome si far riferimento al resource bundlc di base. Il resource bundle di base quello senza alcuna lingua o codice nazionale nel proprio nome. Si dovrebbe sempre rendere disponibile un resource bundle di base. Se si collega un utente con impostazioni locali diverse da quelle che si supportano nel proprio sito, l'applicazione selezioner per questo il resource bundle di base.You should always provide a base resource bundle. If a user with a locale that you don't support visits your site, the application will select the base bundle for that user.

12.3.1.3 Il resource bundle e il classpath


Il resource bundle deve essere posizionato in una collocazione in cui possa essere trovato e caricato. Inoltre, lo stesso class loader che carica la propria applicazione web deve essere in grado di trovare e caricare il resource bundle. Per le web application dovrebbe essere posto nella directory WEB-INF/classes. Se si mette a disposizione per il resource bundle un nome di package, deve essere posto nel package appropriato. Per esempio, se si d il nome al proprio resource bundle com.oreilly.struts.StorefrontResources.properti s, va inserito nella e directory WEB-INF/classes/com/oreilly/struts.

12.3.2 L'accesso al resource bundle


I resource bundle per un'applicazione basata su Struts devono essere caricati all'avvio e ciascun bundle viene rappresentato in memoria da un'istanza della classe org.apache.struts.util.MessageResources (in effetti dalla, sua sottoclasse, PropertyMessageResources). Ogni istanza di MessageResources consenta in ServletContext: essa viene creata quando l'applicazione inizializzata e dal quel momento potr essere acceduta da un qualsiasi componente del servlet container. Tuttavia si dovrebbe utilizzare una combinazione di tag personalizzati e classi ActionMessage o ActionError per l'accesso alle risorse piuttosto che invocare direttamente metodi della classe MessageResources. Di fatto, se si usa una combinazione di gestione delle eccezioni dichiarativa di cui si detto nel Capitolo 10 e framework Validator del Capitolo 11, e possibile che non si debbano creare istanze di ActionMessage o ActionError; il framework compir queste azioni automaticamente. Se c' bisogno di creare una ActionMessage o ActionError manualmente, tuttavia, vi anche questa possibilit. Ecco un esempio tratto dal metodo validate() di una ActionForm:

public ActionErrors validate(ActionMapping mapping, HttpServletRequest request){ ActionErrors errors = new ActionErrors( );

if(getEmail() == null || getEmail().length( ) < 1) { errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("security.error.email.required")); } if(getPassword() == null || getPassword().length( ) < 1) { errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("security.error.password.required")); } return errors; }
L'argomento String passato nel costruttore della classe ActionError deve essere una chiave all'interno del resource bundle. Le classi ActionMessage e ActionError hanno parecchi costruttori, la maggior parte dei quali permette di passare degli argomenti di sostituzione per le occasioni in cui i propri messaggi sono messaggi compositi, come spiegato all'inizio di questo capitolo. Si possono anche creare ActionMessage e ActionError da una classe Action. Segue un piccolo esempio dalla classe Srorefront LoginAction:

// Log in through the security service IStorefrontService serviceImpl = getStorefrontService(

);

String errorMsg = null; UserView userView = null; try{ // Attempt to authenticate the user userView = serviceImpl.authenticate(email, password); }catch( InvalidLoginException ex ){ ActionErrors errors = new ActionErrors( ); ActionError newError = new ActionError("error.security.invalidlogin"); errors.add( ActionErrors.GLOBAL_ERROR, newError ); saveErrors( request, errors );

return mapping.findForward( IConstants.FAILURE_KEY ); }catch( ExpiredPasswordException ex ){ ActionErrors errors = new ActionErrors( ); ActionError newError = new ActionError("error.security.passwordexpired"); errors.add( ActionErrors.GLOBAL_ERROR, newError ); saveErrors( request, errors ); return mapping.findForward( IConstants.FAILURE_KEY ); }catch (AccountLockedException ex){ ActionErrors errors = new ActionErrors( ); ActionError newError = new ActionError("error.security.accountlocked" ); errors.add( ActionErrors.GLOBAL_ERROR, newError ); saveErrors( request, errors ); return mapping.findForward( IConstants.FAILURE_KEY ); }
Questo frammento costituisce un'altra motivazione a usare la caratteristica di gestione dichiarativa delle eccezioni. Si noti la quantit di codice scritto: tutto poteva essere evitato con la semplice definizione all'interno del file di configurazione di Struts.

12.3.2.1 La classe MessageTag della libreria di tag Bean


Struts contiene molti tag personalizzati che possono essere impiegati untiamente a classi MessageResources per un'applicazione. Uno dei pi importanti tuttavia il tag Message che parte integrante della libreria di tag Bean. Questo tag personalizzato recupera una stringa di messaggio da uno dei bundle per una certa applicazione. Supporta anche delle sostituzioni parametriche opzionali se la JSP lo richiede. Tutto quello che si deve fare rendere disponibile una chiave dal bundle e specificare quale bundle delle applicazioni impiegare, quindi il tag scriver le informazioni nella pagina JSP. Per esempio, il seguente frammento JSP adopera il MessageTag per scrivere il titolo della pagina HTML:

<head> <title><bean:message key="global.title"/></title> </head>


Questo un tag che si riveler molto utile all'interno delle applicazioni Struts.

12.3.3 Impostazione del set di caratteri


Il supporto di set di caratteri diversi da quello standard USA (ISO-8859-1) un po' complicato. Ci sono diverse operazioni da compiere prima di essere pronti per il supporto. Per prima cosa si deve configurare il proprio application server e/o il servlet container per " supporto dello schema di codifica dei caratteri che si vuole impiegare. Per esempio, per Unicode si dovr impostare il container per l'interpretazione dell'input come UTF-8. Si controlli anche la documentazione del produttore, dal momento che i metodi di impostazione potrebbero essere diversi. Si potrebbe anche impostare una servlet filtro a tale scopo, ma si richiede in questo caso un servlet container che aderisca alle specifiche 2.3 della Servlet API appunto. Proseguendo, la propriet contentType all'interno dell'elemento controller nel file di configurazione di Struts necessita di essere correttamente impostata. La si imposti come text/html;charset=UTF-8 per il supporto Unicode HTML. Questo potrebbe anche essere specificato all'interno di una JSP inserendo la riga seguente in cima a ogni pagina: e questa linea nella sezione head dell'HTML:

<%@ page contentType="text/html; charset=utf-8" %>

<head> <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8"> </head>


Un'altra possibilit consiste nell'imppsrazione del tipo di contenuto e nello schema di codifica nell'oggetto di risposta in questo modo:

response.setContentType("text/html; charset=UTF-8");
Tuttavia si dovrebbe compiere questa operazione in molte posizioni differenti, rendendo l'aggiornamento e la personalizzazione pi difficoltosi.

Si potrebbe anche impostare ii browser perch invii gli URL sempre come UTF-8; di solito esiste un riquadro di selezione o un'opzione: in IE 5.5 nelle opzioni avanzate. Ci sono ancora due operazioni per assicurarsi che la propria applicazione supporti pienamente Unicode. La prima consiste nel garantire che il proprio database sia configurato per Unicode. Ci predefinito, ma sempre meglio fare una verifica. In secondo luogo, se si usa il JRE anzich SDK, si usi la versione I18N e non quella americana.

12.4 Gestione delle eccezioni e I18N


La gestione delle eccezioni stata trattata nei dettagli all'interno del Capitolo 10 e, come si potuto vedere, ci sono problemi relativi a I18N che devono essere tenuti presenti all'atto di scrivere un'infrastruttura per la gestione delle eccezioni. A meno che non si vogliano localizzare anche i messaggi delle eccezioni lanciate, questi vanno isolati per essere ben sicuri che non saranno mai mostrati all'utente finale. La cosa pi frustrante per un utente ricevere una pagina che, oltre a essere piena di informazioni sull'eccezione o o contenente l'Exception Stacktrace, perfino codificata con impostazioni locali diverse da quelle dell'utente stesso. Come stato sottolineato nel Capitolo 10, le eccezioni dovrebbero essere catturate e all'utente finale dovrebbero essere mostrati messaggi localizzati. Si possono usare a questo scopo il resource bundle e la classe ActionError. Non si dovrebbe mai visualizzare un'eccezione Java per l'utente finale. Persino in caso di un'errore di sistema da cui non possibile riprendersi si dovrebbe preparare una pagina di errore localizzata per l'utente.

CAPITOLO 13

Struts ed Enterprise JavaBeans


Come si visto fin qui, si pu usare Struts sia per assemblare i componenti del controller sia per assemblare quelli dello strato view di una applicazione basata sul modello MVC. Struts non un'infrastruttura per la logica di elaborazione o per l'accesso ai dati, per cui non gioca un ruolo nel componente model. La logica di elaborazione (diversa dalla validazione della presentazione) sarebbe quindi fuori posto all'interno di una classe Action o Form. Per questo la scelta di Struts per un'applicazione non dovrebbe inserire alcuna costrizione sulla progettazione del model. La separazione delle responsabilit in un'architettura a strati sta a significare che le classi Struts non dovrebbero occuparsi di come sia implementato il proprio model e, a sua volta, il model non dovrebbe occuparsi addirittura non dovbrebbe avere consapevolezza di quali controller e view siano implementati tramite Struts. Fino a questo punto, le applicazioni d'esempio presentate in questo libro hanno impiegato lo strato web per offrire i servizi, inclusa la logica di elaborazione e l'accesso al database. Con quest'approccio, il modello dell'applicazione consisteva semplicemente di comuni classi Java installate all'interno del web container. Queste classi avevano la responsabilit completa di rispondere alle richieste da parte delle classi action che costituiscono lo strato model dell'applicazione. Quest'architettura, comune tra le web application, funziona bene, ma i requisiti di sicurezza, scalabilit e complessit delle transazioni restano confinate nei limiti delle possibilit offerte da un web container. Potrebbe essere molto difficile provare a soddisfare requisiti pi stringenti che non sono certo una priorit nella progettazione di un web container. L'alternativa consiste nell'impiego di un vero strato applicativo come quello offerto da un application server J2EE. Con tale approccio il web container mette a disposizione controller e view mentre lo strato application fornisce i dati di elaborazione e le regole ad essi associate. Un design di questo tipo diventa indispensabile quando scalabilit, sicurezza e supporto per le transazioni richiedono un container pi robusto. In questo capitolo sar presa in esame questa situazione. Se vero che lo sviluppo delle proprie classi Struts pu essere indipendente dall'implementazione d un model, ci richiede alcuni sforzi di comprensione. Questo capitolo riassume anche alcuni dei problemi da considerare quando si sviluppa un'interfaccia tra le proprie azioni Struts e un livello applicativo. In particolare si concentrer l'attenzione sul metodo di interfacciamento verso un model che impiega Enterprise JavaBeans (EJB).

EJB: si o no?
Da quando l'architettura J2EE ha cominciato ad assumere grande importanza per lo sviluppo di applicazioni di livello enterprise, si spesso dato per scontato che se un'applicazione non includeva EJB non era una applicazione di tipo J2EE. Vi stata allora una gran corsa all'implementazione di sistemi di livello enterprise tramite i container EJB e tutto quello che potevano offrire. Per applicazioni che erano state progettate con cura e che richiedevano il tipo di infrastruttura inerente gli EJB, la tecnologia metteva a disposizione un approccio basato su standard, e ci ha condotto a molti successi. Purtroppo il peso dato alla porzione EJB di J2EE ha condotto anche all'uso di questi in applicazioni che sarebbero state meglio eseguite con mezzi pi leggeri. Tutto ci, insieme al fatto che alcuni sviluppatori non erano contenti della lentezza dell'evoluzione degli EJB, ha portato a una reazione di rifiuto totale degli EJB. Gli sviluppatori si sono schierati su uno dei due fronti di questa discussione e alcuni sviluppatori di Struts hanno fatto sentire la propria voce nella critica agli EJB. L'opinione pi moderata consiste nell'ammettere che gli EJB offrono un gran numero di servizi quando vi necessit d'elle loro potenzialit peculiari, ma possono anche essere "costosi", in termini sia di complessit che di tempo di sviluppo, qualora queste funzionalit non siano espressamente richiesto. Questo capitolo non prover neanche a valutare i pro e i contro degli EJB in quanto questo non rientra assolutamente negli obiettivi del libro. Si far conto invece che sia stato richiesto di scrivere uno strato web basato su Struts che deve interfacciarsi con un livello applicativo EJB. Partendo da questo presupposto, la preoccupazione principale consister nell'identificare i problemi chiave e trovare un approccio utile per risolverli.

13.1 Implementazione del servizio Storefront con utilizzo di EJB


Anche se questo capitolo dedicato agli EJB, l'intento rimane quello di concentrare l'attenzione su Struts. Per questo motivo non si parler molto di dettagli implementativi degli EJB. Gli EJB costituiscono un'argomento complesso, ma la natura di molti modelli progettuali diretti all'interazione fra EJB e loro client lo rende un compito pi facile di quello che si potrebbe pensare. Dopotutto, uno degli obiettivi di questo capitolo dimostrare come scrivere un'applicazione Struts in modo che le proprie classi Struts non subiscano l'impatto dalla scelta di usare un'implementazione EJB del model. Qualcosa il lettore dovrebbe gi sapere, cio che il componente model di una web application pu essere nascosto dietro

un'interfaccia di servizio. In particolare si visto nel corso dell'esempio Storefront quanto possa essere facile effettuare lo scambio da un modello di debug a una implementazione completa che accede a un database, se si segue questo approccio di progettazione. Attraverso questo capitolo si user l'esempio di Storefront per illustrare come uno strato applicativo EJB possa essere usato con un'applicazione basata su Struts. Se non fosse nella natura originaria degli EJB la possibilit che una applicazione client faccia accesso ad essi, questa implementazione non avrebbe alcuna differenza rispetto all'altra. Tuttavia gli aspetti connessi all'architettura distribuita degli EJB devono essere tenuti presenti quando il proprio client dello strato web accede a questo tipo di model. Nel resto del capitolo si troveranno diverse raccomandazioni e suggerimenti riguardanti la scrittura di codice per l'interfacciamento con lo strato applicativo. Un approccio che isoli il codice che gestisce le caratteristiche peculiari degli EJB basilare al fine di non coinvolgere le proprie classi action.

13.1.1 Panoramica veloce sugli EJB


Le specifiche degli EJB definiscono tre tipi di bean: entity, session e message-driven. Ogni tipo di bean ha un uso diverso all'interno di un'applicazione EJB. Gli entity bean offrono la possibilit di effettuare un accesso transazionale a dati persistenti e sono spesso impiegati per rappresentare colonne in una o pi tabelle tra loro in relazione in un database. Per esempio, si potrebbero implementare le classi entity CustomerBean, ItemBean, e OrderBean tra le altre per il supporto dell'applicazione Storefront. Questi entity bean incorporerebbero la funzionalit offerta dalle corrispondenti classi di business object nell'applicazione d'esempio. Quando si usano gli entity bean questi assumono il ruolo primario di mettere a disposizione il modello dell'applicazione. Ci si aspetta che essi offrano sia le operazioni richieste di persistenza dei dati sia la logica di elaborazione che governa i dati che essi rappresentano. Dal momento che in un'azienda il modello dovrebbe essere riutilizzabile per pi applicazioni, gli entity bean devono essere indipendenti dai tipi di client che accedono al livello applicativo. I session bean sono spesso descritti come estensioni delle applicazioni client che essi servono. Possono implementare logica di elaborazione, ma pi sovente il loro ruolo consiste nella coordinazione del funzionamento di altre classi (in particolare degli entity bean). Sono legati molto pi strettamente ai loro client, per cui ai session bean non si applica la stessa precauzione degli entity bean di mantenere la possibilit di essere riciclati. Lo strato applicativo considerato fondamentalmente il modello dell'applicazione, ma ci si riferisce ai session bean anche come a quelli "controller" dal momento che operano un certo tipo di coordinamento. Questo vero specialmente quando i metodi dei session bean vengono impiegati per implementare transazioni che fanno uso di business object multipli. I session bean possono essere implementati sia "senza stato" che "con stato". Un session bean stateless non conserva uno stato specifico per il proprio client, per cui una singola istanza potrebbe essere condivisa in modo efficiente tra molti client nel container EJB. Un'istanza di bean stateful, invece, viene assegnata a un client specifico e pertanto pu essere conservata per molte chiamate differenti. Mantenere lo stato nell'ambito dello strato applicativo pu semplificare la logica applicativa del client ma rende diffcile l'impiego di molti client. Tipicamente in una web application lo stato viene mantenuto nella sessione utente e a volte nel database, se possibile, al posto di usare session bean stateful. Per tale motivo ci si concentrer pi che altro sui bean di tipo stateless. Le specifiche per gli EJB 2.0 hanno aggiunto i message-driven bean come terzo tipo di bean per poter integrare gli EJB con Java Message Service (JMS). I bean di tipo message-driven differiscono dagli altri due tipi in quanto rispondono in modo asincrono alle request, anzich essere chiamati direttamente da un'applicazione client. Il container invoca un bean message-driven qualora venga ricevuto un messaggio JMS che coincida con i criteri di selezione del bean. Per esempio, in una versione pi complessa dell'applicazione Storefront si potrebbe irhpiegare un bean message-driven per rispondere a una notifica che l'oggetto stato nuovamente ordinato. Il bean potrebbe inviare agli utenti un messaggio e-mail con su scritto che l'ordine gi stato fatto per l'oggetto in questione. I message-driven bean non hanno un'interazione diretta con le applicazioni client per cui sono ancora meno dipendenti dal client rispetto agli entity bean.

13.1.2 La Session Faade


Il primo passo per progettare un'interfaccia per lo strato applicativo sta nell'identificare i punti di aggancio che si espongono per un'applicazione client. I message-driven bean non vengono chiamati direttamente dal client e quindi non entrano in gioco in questa fase. Tuttavia una tipica applicazione EJB pu includere un certo numero di session ed entity bean. Come gi stato sottolineato, prassi comune isolare gli entity bean dai dettagli del client per poterli riutilizzare in altre applicazioni. Se le proprie action di Struts dovessero interagire con gli entity bean in modo diretto, lo strato web

diverrebbe rapidamente associato in modo stretto all'object model implementato dal livello applicativo. Questa stretta associazione, combinata con la natura distribuita degli EJB, porterebbe a un gran numero di problemi: I cambiamenti nell'object model EJB richiederebbero cambiamenti corrispondenti nello strato web. Le classi action dovrebbero spesso eseguire molteplici chiamate remote all'application server per soddisfare le necessit tli logica di elaborazione di una request. La logica di elaborazione e il codice per la gestione delle transazioni necessarie per manovrare chiamate multiple dovrebbero essere posti all'interno dello strato web. Per evitare questi problemi, l'interfaccia che viene esposta ai client del livello applicativo quasi sempre limitata ai session bean. Quest'approccio definito comunemente come "sessione che ingloba l'entity" o "session faade". Questo stile di progettazione offre alcuni vantaggi. Quando un'applicazione client rivolge una chiamata singola a un metodo di un session bean per svolgere le operazioni richieste, il session bean pu eseguire la richiesta come se fosse una singola transazione e i dettagli dell'implementazione possono essere nascosti. Questo metodo che usa i session bean potrebbe necessitare di un accesso ad altri entity bean, oppure anche ad altri session bean per poter soddisfare la request. Non ha importanza quale sia il flusso di controllo su un'application server, tanto la complessit viene comunque nascosta al client. Dal momento che i session bean divengono gli unici client degli entity bean, quando si usa una session faade ci sono poche probabilit che gli entity siano associati a qualsiasi tipo particolare di client esterno.

Anche se la discussione sta dando per scontato che i business object siano implementati come entity bean, questo non deve essere il caso in un'applicazione EJB. Gli stessi problemi e vantaggi che si ottengono tramite l'uso della session facade si incontrano anche con l'impiego di altre implementazioni. Proprio come alcuni sviluppatori Java non gradiscono gli EJB, non tutti gli sviluppatori di EJB amano gli entity bean. Dal momento che la session facade nasconde il modello dell'oggetto al client, gli entity bean possono essere sostituiti con altri approcci, come Java Data Objects (JDO) o normali data access objects (DAO), senza alcun impatto sull'interfaccia rivolta al client.

13.1.3 L'interfaccia business


Quando si impiega la session faade si devono per prima cosa definire i dettagli dell'interfaccia tra gli strati web e application. Ma quale lato dovrebbe essere quello primario in questo "contratto" tra i due strati? Agli inizi dello sviluppo dell'applicazione Storefront, si introdotta l'interfaccia IStoreFrontService per definire la funzionalit del lato application richiesta per svolgere i compiti di presentazione dell'applicazione stessa. In particolare, lo strato di presentazione si basa sul servizio a supporto per autenticare gli utenti e per mettere a disposizione le descrizioni dei prodotti necessarie ad assemblare il catalogo in linea. Da un punto di vista focalizzato sull'utente facile notare come le richieste dello strato service possano essere pilotate dallo strato web. D'altra parte, se deve essere messa insieme una determinata view, deve esistere anche la funzionalit del modello che deve supportarla. Tuttavia, una delle ragioni principali che porta a implementare una logica d elaborazione di livello enterprise all'interno degli EJB sta proprio nella possibilit di permettere di usare quelle regole di elaborazione e i dati a corredo e anche di poterli usare entrambi per pi applicazioni. Questo include il supporto per client diversi da quelli associati alle web application, ma non costituisce un problema dal momento che la distinzione dei ruoli tra session bean ed entity bean agevola la protezione della riusabilit della logica di elaborazione. Dal momento che i session bean sono considerati come estensioni dei client per i quali effettuano un servizio, non c' niente di sbagliato nel definire una session faade specifica per una certa applicazione client. Finch i propri bean di tipo entity e message-driven rimangono indipendenti dal client, si pu pensare di implementare un'interfaccia con session bean in risposta alle richieste di uno specifico client. Se sono richieste view multiple del modello da parte del client, devono essere implementate facade multiple che le supportino. La facade presentata da un session bean pu supportare sia client remoti che locali. I client locali di un session bean o di un entity bean possono essere solo altri EJB all'interno dello stesso container. Questi client sono strettamente associati ai bean cui accedono ma offrono anche dei vantaggi in termini di prestazioni quando devono essere effettuate chiamate tra EJB. Questo avviene dal momento che queste chiamate ai metodi impiegano una semantica a "trasferimento tramite riferimento" (pass-by-reference) invece di essere elaborate come chiamate remote tra componenti distribuiti. I session bean sono spesso client locali di entity bean ma non altrettanto comune che abbiano propri client locali da supportare. I nostri componenti dello strato web ovviamente non sono EJB funzionanti all'interno dello strato applicativo per cui ci si deve preoccupare solo di client remoti per questi scopi. Bisogner per definire l'interfaccia remota per la session faade. Ogni session bean che supporti client remoti deve possedere una corrispondente interfaccia remota che estenda l'interfaccia javax.ejb.EJBObject. Questa interfaccia determina le modalit di elaborazione che sono esposte dal

session bean. Potrebbe suonare strano ma non si vedr mai (o quasi mai) un metodo dichiarato esplicitamente in un'interfaccia remota. Ci a causa di un pattern di progettazione EJB detto business interface. Oltre ad avere un'interfaccia remota, un session bean che supporta client remoti deve avere un'interfaccia home che estenda javax.ejb.EJBHome, una classe di implementazione, e uno o pi descrittori di deploy XML. Diversamente dall'interfaccia remota che dichiara i metodi di elaborazione del bean. l'interfaccia home definisce metodi per creare riferimenti a session bean. Per definizione, i'metodi del bean sono implementati dalla classe di implementazione. I descrittori di deploy identificano e configurano un bean perch possa esseifc irnpiegato all'interno di un certo container EJB. Per ogni elemento verr definito un adeguato esempio nel capitolo.

Quando si considera una classe e un'interfaccia con cui associata, ci si aspetter che la classe implicitamente implementi quell'interfaccia. Questo non per vero per le interfacce locali o remote per quanto riguarda gli EJB. Al loro posto, il container crea un oggetto intermedio (cui spesso ci si riferisce come EJBObject) per implementare l'interfaccia remota. Quest'oggetto intercetta tutte le chiamate dei client del bean e in seguito le passa alla classe di implementazione dopo aver svolto tutte le operazioni richieste (ad esempio, gestione della sicurezza o delle transazioni). La responsabilit del controllo che la classe del bean implementi tutti i metodi business dichiarati dall'interfaccia remota passa dal compilatore Java agli strumenti del container per il deploy. Anche se la classe di un bean viene compilata senza problemi non se ne potr effettuare il deploy se ci sono discrepanze tra questa e la sua interfaccia remota. Se si dichiarato un session bean per implementare la sua interfaccia remota si pu stare sicuri che il compilatore riconosca qualsiasi problema con le sue dichiarazioni di metodi business. Il problema sorge dalla necessit di offrire implementazioni "inerti" dei metodi non-business dichiarati da javax.ejb.EJBObject. Questi metodi non dovrebbero essere mai invocati (sono invocati solo sull'oggetto intermedio creato dal client) ma devono essere presenti per poter compilare l'applicazione. Invece di quest'approccio, una gran parte di sviluppatori di EJB crea un'interfaccia secondaria, conosciuta con il nome di business interface, che dichiara i metodi di elaborazione che devono essere esposti. La dichiarazione dell'interfaccia remota per estendere quest'interfaccia e la classe bean che la implementa espone i metodi richiesti e il compilatore pu verificare che il bean li implementi. Questo modo di procedere offre un buon punto di partenza per la definizione del meccanismo di accesso dei client. L'uso di un'interfaccia business previene anche il passaggio accidentale per cui istituito un riferimento a this a un'istanza di una classe bean stata dichiarata come implementazione dell'interfaccia remota. Questo argomento non rientra nell'area di interesse di questo libro ma in breve si pu dire che un container EJB pu gestire in maniera appropriata le istanze solo quando ci si riferisce a queste utilizzando la loro interfaccia remota o locale. Il riferimento a un bean non pu essere restituito al posto della sua interfaccia remota se la classe del bean implementa solo l'interfaccia business. Ritornando all'interfaccia IStorefrontService che dovrebbe essere soddisfatta dall'implementazione che si deve eseguire, si ricordi che contiene metodi relativi sia all'autenticazione dell'utente sia al catalogo dei prodotti. Anche se si impiega una session facade, si desiderer probabilmente separare queste responsabilit in session bean separati. Questo riduce da un lato la complessit dei session bean coinvolti e dall'altro semplificher la manutenzione e l'aggiornamento. Tuttavia, dato che in questa sede non ci si vuole concentrare troppo sulla progettazione degli EJB, per semplificare si immaginer che la facade consista in un solo session bean Storefront. Probabilmente non si far la stessa cosa in un'applicazione reale, ma una volta appreso come interfacciali con un singolo session bean diventa immediato applicare la stessa tecnica a session bean multipli. Nell'Esempio 13-1 riportata un'interfaccia business adatta al session bean Storefront.
Esempio 13-1. L'interfaccia business del session bean per l'applicazione Storefront

package com.oreilly.struts.storefront.service; import import import import import java.rmi.RemoteException; java.util.List; com.oreilly.struts.storefront.catalog.view.ItemDetailView; com.oreilly.struts.storefront.customer.view.UserView; com.oreilly.struts.storefront.framework.exceptions.*;

/** * The business interface for the Storefront session bean */

public interface IStorefront { public UserView authenticate( String email, String password ) throws InvalidLoginException, ExpiredPasswordException, AccountLockedException, DatastoreException, RemoteException; public List getFeaturedItems( RemoteException; ) throws DatastoreException,

public ItemDetailView getItemDetailView( String itemId ) throws DatastoreException, RemoteException; }


La prima cosa da notare sull'interfaccia IStorefront che i suoi metodi non coincidono esattamente con quelli dichiarati da IStorefrontService. Anzitutto, l'interfaccia business non include i metodi logout() e destroy() che si trovano nell'interfaccia service, perch i metodi di questa rappresentano funzionalit dello strato web, non reale logica di elaborazione che deve spostarsi nello strato applicativo. Inoltre, ogni metodo in IStorefront pu lanciare eccezioni di tipo RemoteException, che non parte delle dichiarazioni in IStorefrontSetvice. Tutti i metodi di elaborazione rivolti a client remoti di un EJB devono essere dichiarati permettendo il lancio di RemoteException, impiegata per riportare fallimenti nella comunicazione specifici per l'esecuzione di un metodo remoto. Questo un aspetto dell'interfaccia remota che non pu essere nascosto dall'interfaccia business. Senza questa restrizione, l'interfaccia potrebbe sembrare simile all'interfaccia service. Una volta compreso come realizzare, il session bean dell'esempio, si discuter del trattamento delle discrepanze tra le interfacce. Va anche notato che l'interfaccia business creata fa riferimento alle classi view gi create per supportare l'interfaccia service. Lo stesso Data Transfer Object (DTO) di cui si parlato nel Capitolo 7 si conforma a un modello basato sugli EJB. Invece di esporre le classi di implementazione dei veri business object o di molti metodi a granularit fine per accedere alle loro propriet, si possono impiegare semplici classi JavaBeans per comunicare lo stato del model al client. Si dichiarata la superclasse BaseView in modo che sia Serializable per cui le classi di view potrebbero essere referenziate in un'interfaccia remota. Le classi DTO consistono primariamente di tipi di dati semplici, quindi tale restrizione dovrebbe essere una di quelle di minor peso. Tramite l'interfaccia business definita, l'Esempio 1.3-2 mostra la dichiarazione (banale) dell'interfaccia remota di cui si avr bisogno per effettuare il deploy del session bean.
Esempio 13-2. L'interfaccia remota per il session bean Storefront

package com.oreilly.struts.storefront.service; import javax.ejb.EJBObject; public interface Storefront extends EJBObject, IStorefront { /** * The remote interface for the Storefront session bean. All methods are * declared in the IStorefront business interface. */ }

13.1.4 Implementazione dei session bean stateless


Senza entrare troppo nei particolari, si cercher ora di implementare la nostra session facade. Si prenderanno alcune decisioni per semplificare le cose, ma il risultato sar tutto ci di cui si necessita per illustrare come si interfaccia lo strato web e quello applicativo. Sar anche possibile effettuarne il deploy all'interno di un container EJB e usarlo per provare la propria interfaccia Struts. Se si dovesse implementare il livello applicativo di Storefront usando gli EJB, probabilmente si produrrebbe un progetto costituito sia da entity bean che da session bean. Si potrebbero rappresentare i componenti del modello usando gli entity bean e probabilmente si avrebbe un certo numero di session bean che offrono funzionalit di sicurezza, catalogazione e operazioni di ordinamento. I session bean fornirebbero le funzionalit di controllo del flusso del lavoro richiesto dai business object del processo e gli entity bean svolgerebbero servizi in qualit di entity business object corrispondenti. Si gi deciso di usare solo un session bean per quest'esempio. La session iacade rende immediata la nostra semplificazione. Dal momento che si isolata l'interfaccia tra i due strati in una facade, qualsiasi distinzione dei ruoli tra session bean ed entity bean non ha importanza per gli sviluppatori che usano Struts in quanto tali. Lo strato web vede solo metodi di session bean e classi DTO, per cui i componenti web non saranno influenzati da nient'altro per quanto riguarda l'implementazione. Premesso ci, si implementer la fac.ade impiegando un solo session bean stateless che non richiede alcun altro EJB.

Se si parte con l'implementazione EJB che include gli entity bean, si potrebbe usare XDoclet (disponibile presso http://www.sourceforge.net/projects/xdoclet/) per generare in automatico ActionForms di Struts da questi bean. Per implementazioni EJB pi complesse rispetto a quella presa in esame, XDoclet dispone di mezzi automatici per la generazione di varie interfacce e descrittori di deploy richiesti da un bean. Questa generazione di codice viene svolta sulla base di tag JavaDoc speciali che si includono nelle proprie classi di implementazione degli EJB. Dal momento che non si impiegheranno gli entity bean, si potr fare uso dello stesso approccio ORM e delle stesse classi di entity business object gi impiegate nella classe StorefrontServiceImpl. Di fatto questa particolare nostra implementazione sembrer molto simile a quella classe, eccetto che per i metodi di callback richiesti dall'interfaccia javax.ejb.SessionBean. Si consideri quindi l'Esempio 13-3.
Esempio 13-3. Il session bean Storefront

package com.oreilly.struts.storefront.service; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.ejb.CreateException; import javax.ejb.EJBException; import javax.ejb.SessionBean; import javax.ejb.SessionContext; import org.odmg.*; import ojb.odmg.*; import com.oreilly.struts.storefront.businessobjects.CustomerBO; import com.oreilly.struts.storefront.businessobjects.ItemBO; import com.oreilly.struts.storefront.catalog.view.ItemDetailView; import com.oreilly.struts.storefront.catalog.view.ItemSummaryView; import com.oreilly.struts.storefront.customer.view.UserView; import com.oreilly.struts.storefront.framework.exceptions.AccountLockedException; import com.oreilly.struts.storefront.framework.exceptions.DatastoreException; import com.oreilly.struts.storefront.framework.exceptions.ExpiredPasswordException; import com.oreilly.struts.storefront.framework.exceptions.InvalidLoginException; /** * This is a simple Session Bean implementation of the Storefront service */ public class StorefrontBean implements SessionBean, IStorefront { private SessionContext ctx; private Implementation odmg = null; private Database db = null; public UserView authenticate( String email, String password ) throws InvalidLoginException, ExpiredPasswordException, AccountLockedException, DatastoreException { // Query the database for a user that matches the credentials List results = null; try{ OQLQuery query = odmg.newOQLQuery( ); // Set the OQL select statement String queryStr = "select customer from " + CustomerBO.class.getName( ); queryStr += " where email = $1 and password = $2"; query.create(queryStr); // Bind the input parameters query.bind( email ); query.bind( password );

// Retrieve the results results = (List)query.execute( ); }catch( Exception ex ){ ex.printStackTrace( ); throw DatastoreException.datastoreError(ex); } // If no results were found, must be an invalid login attempt if ( results.isEmpty( ) ){ throw new InvalidLoginException( ); } // Should only be a single customer that matches the parameters CustomerBO customer = (CustomerBO)results.get(0); // Make sure the account is not locked String accountStatusCode = customer.getAccountStatus( ); if ( accountStatusCode != null && accountStatusCode.equals( "L" ) ){ throw new AccountLockedException( ); } // Populate the value object from the Customer business object UserView userView = new UserView( ); userView.setId( customer.getId().toString( ) ); userView.setFirstName( customer.getFirstName( ) ); userView.setLastName( customer.getLastName( ) ); userView.setEmailAddress( customer.getEmail( ) ); userView.setCreditStatus( customer.getCreditStatus( ) ); return userView; } public List getFeaturedItems( ) throws DatastoreException { List results = null; try{ OQLQuery query = odmg.newOQLQuery( ); // Set the OQL select statement query.create( "select featuredItems from " + ItemBO.class.getName( ) ); results = (List)query.execute( ); }catch( Exception ex ){ ex.printStackTrace( ); throw DatastoreException.datastoreError(ex); } List items = new ArrayList( ); Iterator iter = results.iterator( ); while (iter.hasNext( )){ ItemBO itemBO = (ItemBO)iter.next( ); ItemSummaryView newView = new ItemSummaryView( ); newView.setId( itemBO.getId().toString( ) ); newView.setName( itemBO.getDisplayLabel( ) ); newView.setUnitPrice( itemBO.getBasePrice( ) ); newView.setSmallImageURL( itemBO.getSmallImageURL( ) ); newView.setProductFeature( itemBO.getFeature1( ) ); items.add( newView ); } return items; } public ItemDetailView getItemDetailView( String itemId ) throws DatastoreException { List results = null; try{ OQLQuery query = odmg.newOQLQuery( ); // Set the OQL select statement String queryStr = "select item from " + ItemBO.class.getName( queryStr += " where id = $1"; );

query.create(queryStr); query.bind(itemId); // Execute the query results = (List)query.execute( ); }catch( Exception ex ){ ex.printStackTrace( ); throw DatastoreException.datastoreError(ex); } // if (results.isEmpty( ) ){ throw DatastoreException.objectNotFound( } ItemBO itemBO = (ItemBO)results.get(0); // Build a ValueObject for the Item ItemDetailView view = new ItemDetailView( ); view.setId( itemBO.getId().toString( ) ); view.setDescription( itemBO.getDescription( ) ); view.setLargeImageURL( itemBO.getLargeImageURL( ) ); view.setName( itemBO.getDisplayLabel( ) ); view.setProductFeature( itemBO.getFeature1( ) ); view.setUnitPrice( itemBO.getBasePrice( ) ); view.setTimeCreated( new Timestamp(System.currentTimeMillis( view.setModelNumber( itemBO.getModelNumber( ) ); return view; } /** * Opens the database and prepares it for transactions. */ private void init( ) throws DatastoreException { // Get odmg facade instance odmg = OJB.getInstance( ); db = odmg.newDatabase( ); // Open database try{ db.open("repository.xml", Database.OPEN_READ_WRITE); }catch( ODMGException ex ){ throw DatastoreException.datastoreError(ex); } } public void ejbCreate( ) throws CreateException { try { init( ); }catch ( DatastoreException e ) { throw new CreateException(e.getMessage( )); } } public void ejbRemove( ) { try { if (db != null) { db.close( ); } }catch ( ODMGException e ) {} } public void setSessionContext( SessionContext assignedContext ) { ctx = assignedContext; } public void ejbActivate( ) { // Nothing to do for a stateless bean }

);

) ));

public void ejbPassivate( ) { // Nothing to do for a stateless bean } }


Nella nostra classe StorefrontBean, le implementazioni dei metodi di elaborazione non hanno subito delle variazioni dalle versioni precedenti di StorefrontServicelmpl. Solo la gestione della connessione alla base dati deve essere modificata. Qualora il container degli EJB crei una nuova istanza di questo bean viene invocato il metodo ejbCreate() ed stabilita la connessione ad una base dati. Questa connessione viene chiusa nel metodo corrispondente ejbRemove() chiamato prima che l'istanza venga distrutta. Il container non disattiva session bean stateless, per cui vengono messi a disposizione implementazioni dei metodi ejbPassivate() e ejbActivate() dell'interfaccia SessionBean che non fanno nulla. Se si avesse bisogno di pi di un session bean nel proprio esempio, si sposterebbero questi due metodi in una adapter class e si estenderebbero tutte le implementazioni concrete da questa. Un approccio pi ortodosso consisterebbe nell'aprire la connessione al database utilizzando javax.sql.DataSource recuperata da una ricerca tramite JNDI. Sarebbe cos permesso al container di gestire il pooling delle connessioni e le transazioni in automatico. Ma questo non impatta sull'interfaccia dell'esempio, quindi si pu continuare ad usare questa semplice implementazione. Il session bean creato ha ora un'interfaccia remota e una classe di implementazione. Questo allevia l'interfaccia home che sempre semplice nel caso di un session bean stateless. Tutto quello di cui si ha bisogno un metodo create() come mostrato nell'esempio Esempio 13-4 .
Esempio 13-4. L'interfaccia home per il session bean di Storefront

package com.oreilly.struts.storefront.service; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.EJBHome; /** * The home interface for the Storefront session bean. */ public interface StorefrontHome extends EJBHome { public Storefront create( ) throws CreateException, RemoteException; }

13.1.5 Effettuare il deploy con JBoss


Si deve poi scegliere un container EJB e creare i descrittori XML richiesti per il deploy prima di poterlo effettuare e provare il session bean appena creato. L'application server open source JBoss perfetto per le comuni esigenze: ha caratteristiche complete per J2EE con il supporto per gli EJB 2.0 ed ormai un classico per gli sviluppatori di software open source; pu essere scaricato liberamente da http://www.jboss.org. Data l'implementazione minimale non serve nulla di complicato se non delle informazioni di deploy per il session bean. L'Esempio 13-5 mostra il descrittore standard ejb-jar.xml per il bean in questione. Per la maggior parte, questo file identifica semplicemente le interfacce home e remote e la classe di implementazione. Dichiara anche che tutti i metodi di elaborazione sono non transazionali, dal momento che sono di sola lettura.
Esempio 13-5. Il descrittore di deploy ejb-jar.xml per il session bean Storefront

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd"> <ejb-jar > <description>

Generic deployment information for the Storefront session bean </description> <display-name>Storefront Session Bean</display-name> <enterprise-beans> <session > <ejb-name>Storefront</ejb-name> <home>com.oreilly.struts.storefront.service.StorefrontHome</home> <remote>com.oreilly.struts.storefront.service.Storefront</remote> <ejbclass>com.oreilly.struts.storefront.service.StorefrontBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> <assembly-descriptor > <container-transaction > <method > <ejb-name>Storefront</ejb-name> <method-name>*</method-name> </method> <trans-attribute>NotSupported</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
In aggiunta al file ejb-jar.xml, la maggior parte dei container richiede uno o pi descrittori specifici per il produttore come integrazione delle informazioni di deploy di un bean. In questo caso ci che serve l'associazione di un nome JNDI con il bean Storefront. L'Esempio 13-6 mostra come operare con JBoss. Il nome completo dell'interfaccia remota del bean stato scelto come nome JNDI. Prassi comune usare il nome dell'interfaccia home.
Esempio 13-6. Il descrittore di deploy di JBoss per il session bean Storefront

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE jboss PUBLIC "-//JBoss//DTD JBOSS//EN" "http://www.jboss.org/j2ee/dtd/jboss.dtd"> <jboss> <enterprise-beans> <session> <ejb-name>Storefront</ejb-name> <jndi-name>com.oreilly.struts.storefront.service.Storefront</jndiname> </session> </enterprise-beans> </jboss>
Il deploy di un EJB richiede di effettuare il package di questo in un file Java ARchive (JAR). Il file di deploy JAR per il nostro session bean deve includere i seguenti file: I file delle classi delle interfacce home e remote Il file della classe di implementazione del bean I due descrittori di deploy (questi file devono essere posti in una directory META-INF) Il file OJB.properties e i vari file XML usati dall'infrastruttura ORM I file delle classi di business object e DTO a cui viene effettuato il refererence dal bean Storefront Una volta creato questo file JAR, si pu effettuare il deploy all'interno della directory server/default/deploy nella propria installazione di JBoss. Si possono mettere invece in server/default/lib i file JAR per il proprio driver JDBC e le classi OJB. A questo punto si pu far partire JBoss e verificare che tutto sia a posto per eseguire l'applicazione.

13.2 Interfacciamento tra Struts e gli EJB


Riportando nuovamente l'attenzione al lato client della nostra session facade si discuter ora il modo in cui soddisfare i requisiti dell'interfaccia service con l'implementazione del session bean. Quindi si passer a capire come gestire al meglio le interrogazioni JNDI e la gestione delle interfacce home e remote nei loro rapporti con un EJB in quanto client remoti.

13.2.1 Uso di Business Delegate


Come precedentemente si fatto notare quando stata definita l'interfaccia business per il session bean Storefront, si deve ancora compiere un po' di lavoro per collegarla all'interfaccia di servizio Storefront. L'interfaccia business non include tutti i metodi di IStorefrontService e i metodi dichiarati includono RemoteException nelle loro clausole di throw. Si spiegheranno queste differenze tornando al pattern Business Delegate introdotto nel Capitolo 6. Si tenga presente che lo scopo di questo pattern sta nel nascondere l'implementazione business-service all'applicazione client. Si partir con un'implementazione piuttosto semplice del Business Delegate per poi spiegare alcuni modi specifici per migliorarla. L'Esempio 13-7 illustra un'implementazione iniziale:
Esempio 13-7. Un Business Delegate per il session bean Storefront

package com.oreilly.struts.storefront.service; import import import import import import import import import import import java.rmi.RemoteException; java.util.HashTabella; java.util.List; javax.ejb.CreateException; javax.naming.Context; javax.naming.InitialContext; javax.naming.NamingException; javax.rmi.PorTabellaRemoteObject; com.oreilly.struts.storefront.catalog.view.ItemDetailView; com.oreilly.struts.storefront.customer.view.UserView; com.oreilly.struts.storefront.framework.exceptions.*;

/** * This class is a business delegate that supports the implementation of the * IStorefrontService interface using the Storefront session bean. */ public class StorefrontEJBDelegate implements IStorefrontService { private IStorefront storefront; public StorefrontEJBDelegate( init( ); } ) {

private void init( ) { try { HashTabella props = new HashTabella( ); props.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory"); props.put(Context.PROVIDER_URL, "localhost"); InitialContext ic = new InitialContext(props); Object home = ic.lookup("com.oreilly.struts.storefront.service.Storefront"); StorefrontHome sfHome = (StorefrontHome) PorTabellaRemoteObject.narrow(home, StorefrontHome.class); storefront = sfHome.create( ); } catch (NamingException e) { throw new RuntimeException(e.getMessage( } ));

catch (CreateException e) { throw new RuntimeException(e.getMessage( } catch (RemoteException e) { throw new RuntimeException(e.getMessage( } }

)); ));

public UserView authenticate( String email, String password ) throws InvalidLoginException, ExpiredPasswordException, AccountLockedException, DatastoreException { try { return storefront.authenticate(email, password); } catch (RemoteException e) { throw DatastoreException.datastoreError(e); } } public List getFeaturedItems( ) throws DatastoreException { try { return storefront.getFeaturedItems( ); } catch (RemoteException e) { throw DatastoreException.datastoreError(e); } } public ItemDetailView getItemDetailView( String itemId ) throws DatastoreException { try { return storefront.getItemDetailView(itemId); } catch (RemoteException e) { throw DatastoreException.datastoreError(e); } } public void logout( String email ) { // Do nothing for this Esempio } public void destroy( ) { // Do nothing for this Esempio } }
Quando viene creata un'istanza della classe StorefrontEJBDelegate, sono invocati il suo metodo init() per ottenere un reference remoto al session bean Storefront. Tale metodo opera le interrogazioni JNDI richieste usando il servizio di naming offerto da JBoss. Come gi detto, il BO delegate d per assunto che il servizio di naming sia in funzione sulla macchina locale. Pi avanti vedremo come rendere esterni i dettagli delle interrogazioni JNDI che devono essere svolte da un business object delegate. Ottenuto un reference remoto, il BO delegate lo trattiene come parte integrante del proprio stato. Dal momento va usato solo per l'accesso ai metodi di elaborazione, si dichiara questo campo come tipo dell'interfaccia business. Anche se il metodo storefront non viene dichiarato come tipo dell'interfaccia remota del session bean, il fatto che gestisca obbligatoriamente RemoteException rende evidente che il delegato sta accedendo a un oggetto remoto. Oltre a quello che richiesto per ottenere un reference remoto, la maggior parte del codice del business object delegate non fa altro che inviare chiamate dei metodi business all'implementazione del session bean. I metodi logout() e destroy() non hanno una controparte a livello applicativo, per cui tali implementazioni non includono chiamate ai session bean. Se ci fosse bisogno di una qualche operazione in questi metodi, quel codice dovrebbe essere implementato direttamente nei metodi di StorefrontEJBDelegate oppure in un'altro componente dello strato web che potrebbe essere chiamato dal BO delegate.

13.2.1.1 Gestione delle eccezioni

La gestione delle eccezioni che si trova in questa implementazione della classe StorefrontEJBDelegate merita particolare attenzione. Un business delegate usato con un session bean oltre a nascondere i dettagli delle interrogazioni JNDI dovrebbe anche nascondere le eccezioni specifiche per gli EJB non propagandole allo strato del client remoto. Nei business method del delegate, qualsiasi eccezione remota lanciata da una chiamata di sessione raccolta e al client viene mostrata solo una DatastoreException. In questo modo si pone rimedio al problema di non coerenza nelle eccezioni dichiarate tra l'interfaccia business e le dichiarazioni IStorefrontService. Se l'inclusione di RemoteException nella nostra interfaccia business stata sola differenza la rispetto a quella service, potrebbe nascere la tentazione di aggiungere quest'eccezione a IStorefrontService e proseguire avanti. Tuttavia questo potrebbe inutilmente compremettere il contratto per altre implementazioni di service.

L'unica ragione per cui il delegato usa una DatastoreException per rispondere a una RemoteException consiste nel lasciare l'interfaccia service non coinvolta dall'approccio implementativo. Se questa restrizione autoimposta fosse abbandonata e i cambiamenti a IStorefrontService fossero accettabili, un 'migliore approccio potrebbe essere quello di dichiarare una classe di eccezione il cui unico scopo sia quello di propagare eccezioni da un BO delegate in modo generico. Per esempio, se si dovesse dichiarare un'eccezione chiamata SetviceDelegateException, potrebbe essere lanciata quando capita una RemoteException. Invece di lanciare una RuntimeException per riportare un insuccesso circa l'ottenimento di un riferimento remoto, il metodo init() potrebbe essere anche caricato di questa ulteriore responsabilit e fare uso di ServiceDelegateException. Questa nuova eccezione darebbe una pi accurata indicazione del tipo di problema verificatosi rispetto a una DatastoreException. Inoltre l'aggiunta di questa nuova eccezione alle dichiarazioni di IStorefrontService continuerebbe a non rivelare che l'implementazione basata sugli EJB.

13.2.1.2 Scambiare l'implementazione


Tutto quello che rimane da fare consiste nello scambiare l'attuale implementazione di Storefront con il delegate appena creato. Il framework messo in opera con StorefrontServiceFactory nel Capitolo 6 rende questo compito molto semplice. Occorre solo cambiare la classe specificata dalla nostra implementazione di servizio nel file web.xml nei seguenti termini:

<init-param> <param-name>storefront-service-class</param-name> <param-value> com.oreilly.struts.storefront.service.StorefrontEJBDelegate </param-value> </init-param>


Con questi cambiamenti un'action creer un'istanza di delegate qualora invochi il metodo

getStorefrontService() implementato in StorefrontBaseAction, il quale dovrebbe essere chiamato


solo una volta per evitare un ulteriore sovraccarico per la creazione di riferimenti aggiuntivi opzionali. Tuttavia, anche se si ha cura di impiegare lo stesso delegato attraverso una richiesta, l'implementazione non molto efficiente. La sezione successiva tratta alcuni modi per migliorare il proprio uso di JNDI e interfacce home Non si dimentichi che sar necessario copiare i file JAR del client di nella directory lib per la JBoss propria web application prima di usare il delegate. Ci sar bisogno dei file delle classi per le interfacce home e remote il session bean Storefront nella directory classes. per

13.2.2 Gestione dei reference home e remote degli EJB


L'implementazione di un business delegate isola nettamente e minimizza le dipendenze tra gli strati web e applicativo. Si potuto implementare il session bean Storefront usando un'interfaccia business che non legata ad alcun tipo di client. Si sono potute lasciare immodificate le classi action di Struts quando si passa a questa implementazione del proprio modello. Tuttavia va risolto un paio di problemi per poter impiegare questa soluzione in un'applicazione reale. La cosa pi importante consiste nel migliorare la modalit in cui si ottengono i reference per l'interfaccia home. Dovremo anche rimuovere i parametri inseriti nel codice usati dalla lookup JNDI. Una lookup JNDI effettuata per ottenere un reference a un'interfaccia home un'operazione molto lenta. Non si pu fare granch per evitare questo se si deve ottenere un nuovo riferimento home per ogni richiesta, ma non questo il caso. Un

EJB home un oggetto di tipo factory valido per tutto il ciclo vitale dell'applicazione client. Non esiste uno stato in questo oggetto che impedisca di usarlo per tutte le richieste o i thread del client. Il delegate migliorerebbe in modo significativo se il reference home di cui necessita fosse salvato all'interno dello strato web dopo la prima richiesta. Come per ogni problema di progettazione c' pi di una tecnica da considerare per quest'operazione. Praticamente si sta parlando di dati che stanno nello scope dell'applicazione all'interno dello strato web, per cui una soluzione potrebbe essere quella di conservare i riferimenti all'interno del ServletContext dopo aver fatto le necessarie lookup JNDI. Questo renderebbe inutile qualsiasi interrogazione aggiuntiva ma richiederebbe di rendere il ServletContext disponibile al delegato attraverso il suo costruttore. Questo solo cambiamento si ripercuote sulla nostra service factory, dal momento che istanzia un'implementazione di IStorefrontService usando il suo costruttore senza argomento. Sarebbe meglio scegliere una soluzione senza uno stretto legame a costrutti HTTP. Un approccio pi flessibile per costituito dall'applicazione del pattern EJBHomeFactory come modo di conservare in una cache i reference di cui si necessita.

13.2.2.1 Implementazione di una EJBHomeFactory


Il pattern EJBHomeFactory viene definito in EJB Design Patterns di Floyd Marinescu (Wiley & Sons). L'implementazione di questo pattern permette di creare e di conservare nella cache qualsiasi riferimento all'EJB home di cui la propria applicazione ha bisogno. Dal momento che non dipendente dal ServletContext, si pu riutilizzare questa tecnica in applicazioni diverse dalle web application. L'Esempio 13-8 mostra l'implementazione di questo pattern per l'applicazione Storefront.
Esempio 13-8. L'implementazione di EJBHomeFactory

package com.oreilly.struts.storefront.framework.ejb; import import import import import import java.io.InputStream; java.io.IOException; java.util.*; javax.ejb.*; javax.naming.*; javax.rmi.PorTabellaRemoteObject;

/** * This class implements the EJBHomeFactory pattern. It performs JNDI * lookups to locate EJB homes and caches the results for subsequent calls. */ public class EJBHomeFactory { private Map homes; private static EJBHomeFactory singleton; private Context ctx; private EJBHomeFactory( ) throws NamingException { homes = Collections.synchronizedMap(new HashMap( )); try { // Load the properties file from the classpath root InputStream inputStream = getClass( ).getResourceAsStream( "/jndi.properties" ); if ( inputStream != null) { Properties jndiParams = new Properties( ); jndiParams.load( inputStream ); HashTabella props = new HashTabella( ); props.put(Context.INITIAL_CONTEXT_FACTORY, jndiParams.get(Context.INITIAL_CONTEXT_FACTORY)); props.put(Context.PROVIDER_URL, jndiParams.get(Context.PROVIDER_URL)); ctx = new InitialContext(props); } else { // Use default provider ctx = new InitialContext( ); } } catch( IOException ex ){ // Use default provider ctx = new InitialContext( );

} } /** * Get the Singleton instance of the class. */ public static EJBHomeFactory getInstance( ) throws NamingException { if (singleton == null) { singleton = new EJBHomeFactory( ); } return singleton; } /** * Specify the JNDI name and class for the desired home interface. */ public EJBHome lookupHome(String jndiName, Class homeClass) throws NamingException { EJBHome home = (EJBHome)homes.get(homeClass); if (home == null) { home = (EJBHome)PorTabellaRemoteObject.narrow(ctx.lookup( jndiName), homeClass); // Cache the home for repeated use homes.put(homeClass, home); } return home; } }
Il metodo getInstance di EJBHomeFactory si distingue da molte implementazioni Singleton per il fatto che non dichiarato come sincronizzato. Come gi discusso in Design Patterns, EJB l'uso di un metodo sincroniz degraderebbe le prestazioni zato senza alcun beneficio. Se si istanziano diverse istanze di EJBHomeFactory, date le chiamate simultanee getInstance nel corso dell'inizializzazione, potrebbero essere create alcun referece a a home ridondanti, anche se non ci saranno danni Si noter che la classe EJBHomeFactory accetta il nome e la classe JNDI per l'interfaccia home che deve localizzare. Se si deve accedere a pi di un session bean all'interno dello strato applicativo, si implementer semplicemente una classe delegata per ciascun session bean e si user la sua factory per cercare l'interfaccia home corrispondente. Oltre ai miglioramenti nelle prestazioni permesse dal caching delle home, tutti i "colli di bottiglia" e la gestione delle eccezioni che accompagnano la ricerca di questi reference vengono mantenuti in un'unica collocazione. Il costruttore per la EJBHomeFactory s occupa anche di esporre il provider e i parametri di factory di cui si necessita per accedere al servizio di naming. Per questa operazione si pu usare un approccio standard tramite file jndi.properties con le seguenti inclusioni:

java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory java.naming.provider.url=localhost Se viene invocato un costruttore senza argomenti per la classe InitialContext, viene condotta una ricerca

attraverso il classpath per trovare un file jndi.properties Se viene tro vato, le inclusioni vengono impiegate per inizializzare il contesto di naming Nell'esempio, la classe factory carica esplicitamente il file dal classpath. Altrimenti potrebbe succedere che delle priorit di classloader possano anche impedire a questi valori di essere scelti con priorit rispetto ai valori predefiniti all'interno del server. L'esempio JNDIConnectorPlugin (nel Capitolo 9 ha dimostrato come i parametri del servizio di naming possano essere letti dal file web.xml e conservati all'interno di ServletContext. Qui stato evitato tale approccio allo scopo di mantenere distinti factory e strato web.

13.2.2.2 Uso di una EJBHomeFactory in un business delegate


Il Business Delegate pu ora essere semplificato dato che si ha un approccio standard per la localizzazione dell'interfaccia home. Si pu cambiare nel seguente modo l'implementazione del metodo init():

private void init( ) { try { StorefrontHome sfHome = (StorefrontHome)EJBHomeFactory.getInstance( lookupHome("com.oreilly.struts.storefront.service.Storefront", StorefrontHome.class); storefront = sfHome.create( ); } catch (NamingException e) { throw new RuntimeException(e.getMessage( )); } catch (CreateException e) { throw new RuntimeException(e.getMessage( )); } catch (RemoteException e) { throw new RuntimeException(e.getMessage( )); } }

).

13.2.2.3 Due parole sui reference remoti


Nella nostra implementazione viene creata un reference remoto al session bean per ogni richiesta. Ci non rappresenta un problema da un punto di vista delle prestazioni, visto che il sovraccarico per la creazione di un reference remoto trascurabile rispetto a quello associato agli home. Tuttavia questo non significa che non si possa conservare in una cache un reference remoto se lo si desidera. Di fatto, se ci si sta interfacciando con un session bean stateful, si devono creare nuovi reference remoti per ogni richiesta, dal momento che non il caso di chiamare ogni volta la stessa istanza del bean. Si pu evitare di creare un nuovo reference remoto per ciascuna request mantenendo nella cache un'istanza di business delegate nella sessione. A differenza di home, non si pu conservare nella cache un reference remoto come se fosse un dato all'interno dello scope di un'applicazione. Anche per i session bean stateless un reference remoto mantiene delle informazioni relative al thread del client che l'ha creato, per cui non possibile condividerlo tra pi sessioni utente. Se si opera il caching del business delegate, ci sono alcuni importanti cambiamenti da fare: la sessione utente potrebbe essere serializ zata e ripristinata dal web container e non necessario che un reference remoto sia serializzabile. Per un session bean stateless, si deve poter creare un nuovo reference remoto in caso di errore o di riavvio del container EJB. Per un session bean stateful si deve conservare una handle ("maniglia") per gli EJB nel proprio delegato al posto di un reference remoto per cui si pu sempre mantenere la possibilit di accedere allo stesso bean. Oltre alla trattazione in Design Patterns, possono trovare informazioni esaustiveCore J2EE EJB si in Patternsdi Deepak Alur, John Crupi e Dan Malks (Prentice Hall).

13.2.3 Uso di proxy dinamici


In questa sezione si considerer uno degli ultimi esempi per le modifiche da implementare nel business delegate. L'implementazione corrente funziona bene per Storefront ma i delegati tendono ad avere troppi metodi ridondanti se si deve supportare un'interfaccia con pi metodi. Si sono dichiarati solo tre metodi per il model di Storefront, ma anch'essi seguono un pattern alquanto monotono. Ogni business method nel delegate implementato chiamando il metodo con lo stesso nome che ha nel session bean, tranne logout() e destroy(). raccogliendo un'eccezione RemoteException per sostituirla con unaDatastoteException. Un proxy dinamico permette di eliminare questa ridondanza. Se ci si trovati a eseguire le stesse operazioni in maniera ripetitiva come parte del delegare un insieme di chiamate di metodo a un altro oggetto, si dovrebbe prendere in considerazione l'introduzione di un proxy dinamico: concetto alquanto complesso se in precedenza non si gi lavorato con oggetti di questo genere, ma il cui uso permette di eliminare molto codice ripetitivo. In buona sostanza, un proxy dinamico un oggetto creato durante l'esecuzione usando la reflection, che implementa una o pi interfacce che si specificano. L'implementazione dei metodi dell'interfaccia consiste nella chiamata al metodo invoke() di un oggetto che va anch'esso specificato. Questo metodo invoke() viene dichiarato dall'interfaccia java.lang.reflect.InvocationHandler, che deve essere esplicitamente implementata dall'oggetto usato per assemblare il proxy. Il proxy trasmette parametri al metodo invoke() che identifica il metodo di interfaccia chiamato e gli argomenti a questo trasmessi. Tale idea risulta molto pi comprensibile nell'Esempio 13-9 che mostra la sostituzione del business delegate che pu essere impiegata con un proxy dinamico.
Esempio 13-9. Implementazione di un proxy dinamico per il servizio Storefront

package com.oreilly.struts.storefront.service;

import import import import import import import import import import

java.lang.reflect.*; java.rmi.RemoteException; java.util.*; javax.ejb.CreateException; javax.naming.*; javax.rmi.PorTabellaRemoteObject; com.oreilly.struts.storefront.catalog.view.ItemDetailView; com.oreilly.struts.storefront.customer.view.UserView; com.oreilly.struts.storefront.framework.ejb.EJBHomeFactory; com.oreilly.struts.storefront.framework.exceptions.*;

/** * This class is a dynamic proxy implementation of the IStorefrontService * interface. It implements two of the IStorefrontService methods itself and * delegates the others to the methods declared by the IStorefront business * interface with the same name. */ public class DynamicStorefrontEJBDelegate implements InvocationHandler { private IStorefront storefront; private Map storefrontMethodMap; public DynamicStorefrontEJBDelegate( init( ); } ) {

private void init( ) { try { // Get the remote reference to the session bean StorefrontHome sfHome = (StorefrontHome)EJBHomeFactory.getInstance( ). lookupHome("com.oreilly.struts.storefront.service.Storefront", StorefrontHome.class); storefront = sfHome.create( ); // Store the business interface methods for later lookups storefrontMethodMap = new HashMap( ); Method[] storefrontMethods = IStorefront.class.getMethods( ); for (int i=0; i<storefrontMethods.length; i++) { storefrontMethodMap.put(storefrontMethods[i].getName( ), storefrontMethods[i]); } } catch (NamingException e) { throw new RuntimeException(e.getMessage( } catch (CreateException e) { throw new RuntimeException(e.getMessage( } catch (RemoteException e) { throw new RuntimeException(e.getMessage( } } public void logout(String email) { // Do nothing for this Esempio } public void destroy( ) { // Do nothing for this Esempio } public Object invoke(Object proxy, Method method, Object[] args ) throws Throwable{ try { // Check for the two methods implemented by this class if (method.getName( ).equals("logout")) { logout((String)args[0]); )); )); ));

return null; } else if (method.getName( ).equals("destroy")) { destroy( ); return null; } else { // This method should match a method implemented by the // session bean that has the same name and argument list Method storefrontMethod = (Method)storefrontMethodMap.get( method.getName( )); if (storefrontMethod != null) { // Call the method on the remote interface return storefrontMethod.invoke( storefront, args ); } else { throw new NoSuchMethodException("The Storefront does not implement " + method.getName( } } } catch( InvocationTargetException ex ) { if (ex.getTargetException( ) instanceof RemoteException) { // RemoteException isn't declared by the IStorefront method that was // called, so we have to catch it and throw something that is throw DatastoreException.datastoreError(ex.getTargetException( )); } else { throw ex.getTargetException( ); } } } }
L'idea che sta dietro la creazione di DynamicStorefrontEJBDelegate di implementare un proxy dinamico creato come implementazione dell'interfaccia IStorefrontService per delegare tutte quelle invocazioni al metodo invoke() qui dichiarate. Si noti che questa classe delegata non viene dichiarata come se implementasse IStorefrontService. Di fatto i soli metodi di elaborazione da IStorefrontService che compaiono in questa classe sono il metodo logout() e il metodo destroy() che non sono implementati dal session bean. Per usare il BO delegate basato sul proxy dinamico si deve modificare la sua modalit per l'ottenimento dell'interfaccia service dalla factory. Invece di presentare un approccio elegante alla questione, si inseriranno direttamente nel codice le nuove modalit, come mostrato nella seguente versione del metodo createService() della StorefrontServiceFactory:

));

public IStorefrontService createService( ){ Class[] serviceInterface = new Class[] { IStorefrontService.class }; IStorefrontService proxy = (IStorefrontService)Proxy.newProxyInstance( Thread.currentThread().getContextClassLoader( ), serviceInterface, new DynamicStorefrontEJBDelegate( ) ); return proxy; }
Quando una classe action richiede un'implementazione dell'interfaccia service, la factory crea un proxy dinamico che implementa quest'interfaccia impiegando un'istanza di DynamicStorefrontEJBDelegate. Quando l'action compie un'invocazione sull'interfaccia service la chiamata viene diretta al proxy che la trasforma in una chiamata al metodo del delegato invoke(). Il metodo invoke() controlla i nomi del metodo che stato chiamato e di conseguenza o chiama i metodi logout() oppure destroy() implementati nella classe o li delega a un session bean con lo stesso nome. Questa sequenza di chiamate illustrata dalla Figura 13-1. La cattura e la sostituzione di RemoteException sono ora gestite in un solo punto quando il business delegare chiama un metodo che un session bean. Il solo metodo invoke() pu gestire tutti i metodi esposti tramite l'interfaccia business dei session bean senza alcuna modifica.

Figura 13-1. Diagramma di sequenza sul recupero dei dettagli di un articolo in un proxy dinamico

Un proxy dinamico quindi una attraente tecnica per minimizzare la ridondanza dei metodi all'interno di un business delegate, ma si deve pagare un prezzo: dato che un approccio del genere si basa pesantemente sulla reflection, si deve tener conto della maggiore lentezza di esecuzione che risulter a fronte della migliorata gestibilit del codice.

13.3 Conclusioni
Non tutte le web application richiedono la sofisticazione e la complessit di un container EJB. I benefici pure ben documentati relativi all'impiego di un container EJB devono essere soppesati con seriet data l'accresciuta complessit sia dello sviluppo che della gestione. Non si deve dare per scontato di usare gli EJB per la porzione del model di un'applicazione Struts. Tuttavia, se si decide di usare gli EJB, si sappia che non avranno un impatto disastroso sul resto del codice. Esistono delle modalit da seguire per limitare l'accoppiamento tra la propria applicazione e gli EJB, le quali rendono il tutto molto pi facili da usare.

CAPITOLO 14

Usare Tiles

Fino a questo punto non si detto granch a proposito di organizzazione, montaggio di contenuti e aspetto delle pagine JSP per un'applicazione Struts; ci non rientra per vari motivi tra gli obiettivi di un libro su Struts ed esistono in commercio molti ottimi libri che offrono strategie per l'organizzazione di contenuti web e disposizione delle pagine. Nell'applicazione Storefront per la composizione delle pagine, si sono impiegati due approcci diversi. Il primo approccio, basato direttamente su JSP, probabilmente il pi familiare ai web designer. Le pagine JSP contengono logica di presentazione e tag di composizione HTML; non esiste una separazione tra i due elementi. Questo approccio impiegato di solito per web application piccole e non particolarmente complesse. Il secondo approccio fa uso della direttiva JSP include. Viene usato dagli sviluppatori per applicazioni web di grandi dimensioni o dopo aver compreso quanto possa essere ripetitivo l'approccio precedente. Se si speso molto tempo per la manutenzione delle web application, si sapr quanto possa essere noioso e difficile aggiornare l'aspetto di un sito. L'impiego della direttiva include delle JSP permette una maggiore riusabilit, riducendo i costi totali di sviluppo e manutenzione. Un terzo approccio, introdotto in questo capitolo, consiste nella riduzione della ridondanza del codice di una web application e, allo stesso tempo, nella separazione del contenuto dall'aspetto della pagina in modo pi netto rispetto agli altri due approcci.

14.1 Che cosa sono i template?


Gli strumenti visuali pi diffusi come VisualWorks Smalltalk o Java Swing mettono tutti a disposizione un qualche tipo di gestore dell'aspetto che indica come il contenuto dovrebbe essere disposto all'interno del frame o della finestra. Nel caso di siti web normali, nel corso del loro ciclo di vita, l'aspetto pu subire diversi cambiamenti, sia piccoli che radicali. L'impiego di modelli e gestori di aspetto pu essere d'aiuto per incapsulare le aree fisiche delle pagine all'interno di un'applicazione in modo da poterle modificare con influssi minimi sul resto dell'applicazione. Sfortunatamente le JSP non consentono di base alcun supporto per gestori di composizione e simili. Questo il motivo per cui stato ideato l'approccio basato sui template. Il concetto di template non nuovo, dal momento che se ne parla da tempo in un modo o nell'altro. Per comprendere come i template possano rendere pi semplice l'aspetto di un sito weh paragoniamoli con un approccio che impiega il meccanismo delle JSP include. Ecco nell'Esempio 14-1 la pagina index.jsp di Storefront.
Esempio 14-1. La pagina index.jsp di Storefront

<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> <html:html> <head> <title><bean:message key="global.title"/></title> <html:base/> <script language=javascript src="include/scripts.js"></script> <link rel="stylesheet" href="stylesheets/format_win_nav_main.css" type="text/css"> </head> <body topmargin="0" leftmargin="0" bgcolor="#FFFFFF">

<!-- Header information --> <%@ include file="include/head.inc"%> <!-- Menu bar --> <%@ include file="include/menubar.inc"%> <!--- Include the special offer --> <%@ include file="include/mainoffer.inc"%> <!-- Featured items header row --> <Tabella width="645" cellpadding="0" cellspacing="0" border="0"> <tr> <td width="21"> <html:img height="1" alt="" page="/images/spacer.gif" width="1" border="0"/> </td> <td width="534"> <html:img page="/images/week_picks.gif" altKey="label.featuredproducts"/> </td> <td width="1" bgcolor="#9E9EFF"> <html:img height="1" alt="" page="/images/spacer.gif" width="1" border="0"/> </td> <td width="1" bgcolor="#9E9EFF"> <html:img height="1" alt="" page="/images/spacer.gif" width="1" border="0"/> </td> <td width="90" bgcolor="#9E9EFF" align="right"> <html:img height="1" alt="" page="/images/spacer.gif" width="90" border="0"/> </td> </tr> <tr> <td> <html:img height="1" alt="" page="/images/spacer.gif" width="21" border="0"/> </td> <td colspan="4" bgcolor="#9E9EFF"> <html:img height="1" alt="" page="/images/spacer.gif" width="1" border="0"/> </td> </tr> </Tabella> <html:img height="10" alt="" page="/images/spacer.gif" width="1" border="0"/><br> <!--- Include the featured items --> <%@ include file="include/featureditems.inc"%> <!--- Include the copyright statement --> <%@ include file="include/copyright.inc"%> </body> </html:html>
Anche se nella pagina principale viene usata la direttiva JSP include, nella pagina gli aspetti relativi al layout sono frammisti ai contenuti. Per esempio, si noti come questa specifichi esplicitamente che il file di inclusione head.inc viene per primo, poi il file menubar.inc, poi il file mainoffer.inc e cos via fino al file copyright.inc a fondo pagina. Si devono porre le stesse dichiarazioni nello stesso ordine per ciascuna pagina che deve conservare questo particolare aspetto. Se un committente volesse far spostare il menu sul lato sinistro anzich destro, tutte le pagine andrebbero modificate di conseguenza. L'applicazione Storefront applica il meccanismo delle JSP include anzich basarsi sulle sole JSP. Anche se l'uso del meccanismo include un passo nella giusta direzione dal momento che riduce la ridondanza (si pu immaginare cosa succederebbe a dover includere dentro ogni pagina il contenuto del copyright), pur sempre meno efficiente di un approccio basato su template.

Contenuto: statico o dinamico?

Con le JSP ci sono due diversi tipi di contenuto che possibile includere: statico e dinamico. La direttiva include di seguito mostrata:

<%@ include file="include/copyright.inc" %>

include il sorgente della pagina cui si riferisce al momento della traduzione/compilazione. Perci, non possibile includere contenuti a runtime impiegando la direttiva include. Questa considera una risorsa come fosse un oggetto statico e il contesto della risorsa incluso letteralmente nella pagina. Al contrario, l'action include qui di seguito:

<jsp:include page="include/copyright.inc"/>

gestisce la risorsa come fosse un oggetto dinamico. La richiesta inviata alla risorsa e vi incluso il risultato dell'elaborazione. I template impiegano un approccio dinamico per cui alcune espressioni possono essere valutate e incluse a runtime.

14.1.1 Che cosa un template?


Un template una pagina JSP che usa una libreria di tag personalizzati JSP per descrivere l'aspetto della pagina. Il template agisce come definizione dell'aspetto delle pagine di un'applicazione senza che se ne specifichi il contenuto. Il contenuto viene inserito a runtime nella pagina. Inoltre una o pi pagine possono impiegare lo stesso template. Lo scopo di un template sta nel permettere alle pagine di avere un aspetto coerente all'interno di un'applicazione senza dover inserire direttamente il codice in ogni pagina. Gran parte delle pagine impiegher lo stesso template, ma non infrequente che l'aspetto delle pagine debba variare all'interno di un'applicazione e sia quindi richiesto pi di un template. L'Esempio 14-2 illustra un template per l'applicazione Storefront.
Esempio 14-2. Un template di base per l'applicazione Storefront

<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html"%> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%> <%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles"%> <html:html> <head> <title><bean:message key="global.title"/></title> <html:base/> </head> <body topmargin="0" leftmargin="0" bgcolor="#FFFFFF"> <!-- Header page information --> <tiles:insert attribute="header"/> <!-- Menu bar --> <tiles:insert attribute="menubar"/> <!-- Main body information --> <tiles:insert attribute="body-content"/> <!-- Copyright information --> <tiles:insert attribute="copyright"/> </body> </html:html>
L'Esempio 14-2 non introduce nuovi concetti. La prima cosa da notare che si usano librerie di lag personalizzati di Struts. Il fatto che si stia usando la libreria di tag Tiles come pure la HTML e la Bean non dovrebbe risultare strano. La libreria Tiles una delle tante e verr discussa in dettaglio pi oltre nel capitolo. Il resto della pagina una commistione di tag di formattazione HTML. Si noter che non vi incluso alcun contenuto, ma solo tag insert dove deve essere inserito il contenuto a runtime. L'uso dei tag di Struts qui impiegati dovrebbe essere gi noto, per cui non se ne parler oltre. Il tag insert svolge un ruolo simile a quello della direttiva include. In buona sostanza questo tag indica che esiste una variabile header, per esempio, e che il valore dell'attributo "header" deve essere passato al tag insert e infine il contenuto generato deve essere posto in questo punto. Lo stesso dicasi per menubar, body-content, e copyright. Tra breve si spiegher come viene sostituito il contenuto "reale" al

posto di questi attributi durante l'esecuzione. Si noti che questo layout molto simile a quello descritto nell'Esempio 14-1. L'unica differenza che invece di includere esplicitamente gli include mainoffer e featureditem come da Esempio 14-1, il file del template include una sezione body-content. Questo permette di riusare il template per qualsiasi pagina che abbia questo formato generico. Una volta capito come inserire il contenuto del body specifico per la pagina, si pu riutilizzare questo template pi volte. Questo unico file pu allora controllare il layout di molte pagine. Se c' necessit di modificare il layout del sito, questo l'unico file da cambiare: ed il vero vantaggio di usare un approccio basato sui template. L'ultimo pezzo del puzzle consiste nel capire come sono assemblate le sezioni header, menubar, bodycontent e copyright per formare la risposta che viene visualizzata. Il punto importante da ricordare che la pagina JSP dell'Esempio 14-2 il template. C' sempre bisogno di pagine JSP che supportino contenuti specifici per la pagina usati dal template. Per esempio, se si riscrive index.jsp dell'Esempio 14-1 usando il template dell'Esempio 14-2, essa apparir come quella dell'Esempio 14-3 .
Esempio 14-3. La pagina index.jsp per l'applicazione Storefront facendo uso di un template

<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %> <tiles:insert page="/layouts/storefrontDefaultLayout.jsp" flush="true"> <tiles:put name="header" value="/common/header.jsp" /> <tiles:put name="menubar" value="/common/menubar.jsp" /> <tiles:put name="body-content" value="/index-body.jsp" /> <tiles:put name="copyright" value="/common/copyright.jsp" /> </tiles:insert>
La prima cosa da notare nell'Esempio 14-3 che la libreria di tag Tiles viene inclusa all'inizio del file. Ogni pagina (o tile) che necessita di usare la libreria di tag Tiles deve includerla con questa riga:

<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %>


Nell'Esempio 14-3 sono inclusi due tag della libreria Tiles: insert e put. (L'elenco completo di tag di Tiles e dei loro attributi sar discusso nel prosieguo del capitolo). Si gi visto il tag insert nell'Esempio 14-2, ma questo nell'Esempio 14-3 svolge un compito alquanto diverso. Sono aggiunti due attributi al tag insert: page e flush. L'attributo page informa il tag che questa pagina JSP sta impiegando un template particolare (o layout per seguire la terminologia di Tiles; anche dei layout si parler in seguito nel capitolo). Nel template dell'Esempio 14-2 storefrontDefaultLayout.jsp, l'attributo flush informa il controller di emettere l'output della pagina prima di inserire il contenuto nella pagina risultante. Il tag put dell'Esempio 14-3 risponde alla domanda che ci si era posti nella sezione precedente: come viene inviato il contenuto specifico al template? Gli attributi per il tag put in questo esempio sono name e value. Se si paragonano i valori dei diversi attributi name si vedr che questi sono uguali a quelli che il template dell' Esempio 14-2 si aspetta. Quando la pagina index.jsp dell'Esempio 14-3 viene eseguita, il file template viene elaborato e i file header.jsp, menubar.jsp, index-tile.jsp e copyright.jsp sono passati ai tag put:

<tiles:insert page="/layouts/storefrontDefaultLayout.jsp" flush="true"> <tiles:put name="header" value="/common/header.jsp" /> <tiles:put name="menubar" value="/common/menubar.jsp" /> <tiles:put name="body-content" value="/index-body.jsp" /> <tiles:put name="copyright" value="/common/copyright.jsp" /> </tiles:insert>
A runtime, i valori dei tag put sono sostituiti dinamicamente nel file template e quindi elaborati. Il risultato quello che poi viene mostrato al client. Per riassumere la discussione sui tempiale, ecco un'altra pagina che impiega lo stesso template dell'Esempio 14-2 ma che fornisce un diverso body-content. L'Esempio 14-4 mostra la pagina itemdetail.jsp.
Esempio 14-4. La pagina itemdetail.jsp per l'applicazione Storefront

<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %> <tiles:insert page="../layouts/storefrontDefaultLayout.jsp" flush="true"> <tiles:put name="header" value="../common/header.jsp"/> <tiles:put name="menubar" value="../common/menubar.jsp"/> <tiles:put name="body-content" value="../catalog/itemdetail-body.jsp"/> <tiles:put name="copyright" value="../common/copyright.jsp"/> </tiles:insert>

L'unica differenza tra la pagina index.jsp nell'Esempio 14-3 e la pagina itemdetail.jsp dell'Esempio 14-4 consiste nel contenuto dell'attributo body-content. Se ancora non si convinti dei vantaggi dell'uso dei template si noti che index.jsp e Itemdetail.jsp negli Esempi 14-3 e 14-4 non specificano nulla a riguardo del contenuto che deve essere visualizzato. Entrambi fanno riferimento al file storefrontDefaultLayout.jsp, che ha il compito di mostrare a video il contenuto in un eferto formato. Se si vuole cambiare il layout del sito si deve solo cambiare il file storefrontDefaultLayout.jsp.

14.2 Installazione e configurazione di Tiles


Prima di poter usare il framework Tiles, ci si deve assicurare che sia installato e configurato correttamente all'interno del proprio web container. Tiles non dipende da alcun container specifico. Si dovranno recuperare i file richiesti e assicurarsi che siano posti nelle directory appropriate all'interno della web application.

14.2.1 Download di Tiles


Tiles incluso in Struts. In precedenza era incluso nella cartella contrib ma ora fa parte della distribuzione principale; l'ultima versione del codice sorgente e dei binari insieme a informazioni reperibile presso l'URL http://www.lifl.fr/~dumoulin/tiles/index.html.

14.2.2 Installazione dei file richiesti JARs e Misc


Il primo passo consiste nella installazione all'interno della cartella WEB-INF/lib dei file necessari: tiles.jar commons-digester.jar commons-beanutils.jar commons-collections.jar commons-logging.jar Tutti questi binari dovrebbero essere gi present se si sta utilizzando Struts. Si dovranno anche installare i file TLD di Struts, tiles.tld nella directory WEB-INF dell'applicazione. Non si aggiunga il file tiles.jar al dasspath del proprio servlet container nel tentativo di evitare di porlo nella WEB-INF/lib di ciascuna web application in quanto si verificheranno eccezioni ClassNotFoundException. Anche il file tiles-config.dtd dovrebbe essere posto nella directory WEB-INF. Questo DTD viene impiegato per validare i file di definizione di Tiles di cui si dir in seguito.

14.2.3 Aggiungere la libreria di tag Tiles a web.xml


Come per qualsiasi altra libreria di tag delle JSP, si deve aggiungere la libreria Tiles al descrittore del deploy della web application prima di poterla usare. Si inserisca nel file web.xml l'elemento taglib:

<taglib> <taglib-uri>/WEB-INF/tiles.tld</taglib-uri> <taglib-location>/WEB-INF/tiles.tld</taglib-location> </taglib> Dovrebbero gi esistere elementi taglib se si impiega una delle librerie dei tag standard di Struts. Ogni pagina che
deve usare la libreria dei tag di Tiles deve includere la seguente riga di intestazione:

<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %>

14.2.4 Configurare Tiles per il funzionamento con Struts


Tiles pu essere usato sia con che senza Struts. A seconda di come lo si usa, ci sono diverse opzioni per configurarlo per un'applicazione web. Dal momento che questo libro su Struts, si cercher di capire come inserirlo in un'applicazione Struts. Con le prime versioni di Tiles si doveva configurare una speciale ActionServlet chiamata ActionComponentServlet nel file web.xml. Era anche obbligatorio configurare una RequestProcessor speciale nell'elemento controller di Struts. Tutto questo non pi necessario dal momento che disponibile un plug-in Tiles che si occupa di tutta l'inizializzazione. Il plug-in Tiles necessario se si desidera usare le definizioni di Tiles, anche se possibile usare le librerie Tiles con Struts senza configurarlo. Tuttavia sar bene imparare a configurarlo, dal momento che si potr risparmiare tempo nel caso si decidesse in seguito di usare le definizioni di Struts. Per aggiungere il plug-in a un'applicazione Struts, si aggiunga il seguente elemento plug-in al file di configurazione di Struts:

<plug-in className="org.apache.struts.tiles.TilesPlugin" > <set-property property="definitions-config" value="/WEB-INF/tilesdefs.xml" /> <set-property property="definitions-debug" value="2" /> <set-property property="definitions-parser-details" value="2" /> <set-property property="definitions-parser-validate" value="true" /> </plug-in>
All' interno dell'elementoplug-in si possono specificare uno o pi elementiset-property per trasmettere ulteriori parametri alla classe Plugin. Il parametro di inizializzazione definitions-config specifica il file XML o.i file che contengono definizioni di Tiles. sono usati nomi multipli, devono essere separati da virgole. Se Il parametro definitions-debug specifica illivello di debug. I valori predefiniti sono: 0 1 2 Nessuna informazione di debug. Informazioni di debug minime. Informazioni di debug complete.

Il valore preimpostato 0. Il parametro definitions-parser-details indica il livello di informazioni di debug richiesto quando vengono interpretati i file di definizione. Questo valore viene passato al Commons Digester. I valori permessi sono gli stessi del parametro definitions-debug. Il valore preimpostato 0. Il parametro definitions-parser-validate specifica se il parser debba validare il file di configurazione di Tiles. I valori permessi sono true e false. Il valore preimpostato true. Esiste un parametro aggiuntivo chiamato definitions-factory-class. Si pu creare una definizione personalizzata di factory e inserire qui il nome della classe. Il nome preimpostato org.apache.struts.tiles.xmlDefinition.I18NfactorySet.

14.3 Panoramica su Tiles


Tiles mette a disposizione un meccanismo basato su template che permette di separare le responsabilit del layout da quelle del contenuto. Come per i template descritti poco fa nel capitolo, si ha la possibilit di impostare un layout e inserirvi dinamicamente i contenuti durante l'esecuzione dell'applicazione. Se si deve personalizzare il proprio sito sulla base di elementi come l'internazionalizzazione, le preferenze dell'utente o anche solo cambiare l'aspetto e l'ergonomia, questo diventa uno strumento potentissimo. Tiles offre queste caratteristiche: Capacit di utilizzare template Composizione e caricamento dinamici di pagine Definizioni di schermo Supporto per il riutilizzo di tile e layout

Supporto per l'internazionalizzazione Supporto di canali multipli Per un certo periodo, all'interno di Struts esistita una libreria di tag Template. Questi tag consentono un approccio molto limitato per la costruzione delle proprie pagine JSP in un'applicazione web. Anche se questi tag sono utilissimi per la separazione del contenuto di una applicazione web dal suo layout prescritto, Tiles va molto oltre e offre un insieme di funzionalit che include quello della libreria di tag Template integrandola ed espandendola con molte altre caratteristiche.

Tiles era precedentemente noto come Components ma il nome stato cambiato, dal momento che tale termine era troppo comune. La documentazione e il codice sorgente di Tiles fanno ancora riferimento al vec nome in alcune parti. chio

Cedric Dumoulin ha creato Tiles per estendere il concetto di template e offrire agli sviluppatori maggiore flessibilit e libert quando si creano applicazioni con la tecnologia JSP. Il contenuto per le web application ancora appannaggio delle pagine JSP e dei JavaBeans. Tuttavia il layout specificato all'interno di una pagina separata JSP o come si vedr in seguito, in un file XML.

14.3.1 Che cosa una tile?


Una tile (letteralmente: "tessera", "piastrella", "tegola") un'area o una regione all'interno di una pagina web. Una pagina pu consistere di un'unica regione o essere suddivisa in diverse regioni. La Figura 14-1 illustra un esempio tratto dall'applicazione Storefront.

Figura 14-1. Le regioni dell'applicazione Storefront

Una pagina JSP di solito composta da diverse regioni, altrimenti dette tile. Non c' niente di particolare a riguardo, a parte il fatto che si tratta di una pagina progettata per essere usata con il framework Tiles che fa uso della libreria di tag Tiles. L'aspetto di maggiore importanza di Tiles la riusabilit. Questo vero sia per i layout che per l'area del contenuto. Diversamente dalla maggior parte delle pagine JSP, i componenti di Tiles sono riusati all'interno di una applicazione o anche di applicazioni diverse. Oltre a questo fatto, non c' niente di complicato in una tile. Di fatto, la maggior parte degli esempi sin qui seguiti possono essere classificati come tile, inclusi gli esempi dal 14-2 al 14-4.

14.3.2 Uso di tile di layout


Nella terminologia di Tiles, un layout quello che in precedenza abbiamo chiamato template. Un layout serve esattamente,allo stesso scopo di un template: mette insieme un gruppo di tile per specificare il formato di una pagina. L'Esempio 14-2 di fatto un layout di Tiles. La sintassi di Tiles e di una libreria di template come quella inclusa in Struts pressoch la stessa.

Tiles mette a disposizione un superinsieme delle caratteristiche incluse dalla libreria standard di Struts Template definita da David Geary, ma porta il concetto di template ancora oltre offrendo ulteriori funzionalit.

Anche i layout sono considerati tile. Le pagine JSP e perfino le applicazioni intere possono riutilizzare i layout ed prassi comune scrivere una libreria di layout che poi sono impiegati in molti diversi progetti. Tiles offre una serie di layout standard che si possono modificare o riutilizzare a piacimento. I layout inclusi sono: Classic layout Columns layout Center layout Menu layout Tabs layout Vertical box layout Visualizza un header, un menu sinistro, un body e un footer HTML Visualizza una lista di tile in colonne multiple ciascuna delle quali visualizza i propri tile in un'ordine verticale Visualizza un header, un tile destro, un body e un footer Visualizza un menu con collegamenti Visualizza un insieme di tile in un ordine tabulato Visualizza una lista di tile in un ordine verticale

Dal momento che uno dei maggiori obiettivi di Tiles la riusabilit, si possono riutilizzare questi layout all'interno delle proprie applicazioni quasi senza modifiche. Si possono anche personalizzare i layout in qualsiasi modo si crede opportuno.

14.3.3 Pianificare il proprio layout


Pianificare i propri requisiti di layout con il dovuto anticipo molto importante. Non invece un buon approccio decidere l'aspetto del proprio sito dopo che lo si assemblato. Questa decisione viene di norma presa da un team, o di gestione del prodotto o, se possibile, dagli sviluppatori stessi. Ad ogni modo si dovr sviluppare il layout (o i layout) prima di qualsiasi operazione di sviluppo. Il layout preimpostato per Storefront stato presentato nell'Esempio 14-2.

14.3.4 Passaggio di parametri al layout


Il layout mostrato nell'Esempio 14-2 generico. Non sa nulla del contenuto di itemdetail.jsp o di quello delle altre pagine. Questo voluto, dal momento che permette di riusare il layout per altre pagine. Invece di essere inserito all'interno della pagina di layout, il contenuto fornito o "passato" come parametri alla pagina di layout durante l'esecuzione. Si esamini signin.jsp per Storefront nell'Esempio 14-5 .
Esempio 14-5. La tile signin.jsp per Storefront

<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %> <tiles:insert page="../layouts/storefrontDefaultLayout.jsp" flush="true"> <tiles:put name="header" value="../common/header.jsp"/> <tiles:put name="menubar" value="../common/menubar.jsp"/> <tiles:put name="body-content" value="../security/signin-body.jsp"/> <tiles:put name="copyright" value="../common/copyright.jsp"/> </tiles:insert>
Lo scopo dei tag put nell'Esempio 14-5 di fornire i contenuti alla tile di layout specificata nel tag insert di inclusione. I valori degli attributi name nell'Esempio 14-5 (come anche nei 14-3 e 14-4) devono essere uguali a quelli che il tile di layout si aspetta. Il tag insert pu includere opzionalmente un attributo ignore che avr l'effetto di istruire il tag a non scrivere niente se non trova un valore per l'attributo atteso. Come comportamento predefinito, viene lanciata un'eccezione quando non fornito un attributo.

14.4 La libreria di tag Tiles


Questa sezione affronta i tag personalizzati JSP usati in Tiles. La Tabella 14-1 elenca i tag disponibili per Tiles che sono molto simili a quelli presenti in un qualsiasi framework basato sui template: Tiles, tuttavia, aggiunge altre funzionalit.
Tabella 14-1. Tag all'interno della libreria di tag di Tiles

Nome tag

Descrizione Aggiunge un elemento alla lista,. Crea una definizione di un componente tile. Prende il contenuto dallo scope di request che stato messo l da untag put. Visualizza il valore dell'attributo specifico per tile/componente/template allo JspWriter corrente Importa l'attributo di una tile nello specifico contesto.

add definition get getAsString importAttribute

initComponentDefinitions Inizializza una factory di definizione di tile insert Inserisce un componente tile. put putList useAttribute
Mette un attributo in un componente tile. Dichiara una lista che verr passata come attributo Usa un valore attributo all'interno di una pagina

14.4.1 Il tag insert


Il tag insert responsabile dell'inserimento del contenuto all'interno di una pagina. In una tile di layout il tag insert indica il punto di inserimento del contenuto impiegando valori di un attributo. In una tile normale, non di layout, il tag insert viene usato per recuperare un layout e permettere di passarne il contenuto impiegando un tag put. La Tabella 14-2 elenca gli attributi per il tag insert.
Tabella 14-2. Attributi per il tag insert

Nome attributo

Descrizione II nome di un'attributo nel contesto corrente di tile/componente. Il valore di quest'attributo passato all'attributo name. Il nome del bean impiegato come valore. Il bean viene recuperato dal contesto specifico se esiste. Altrimenti viene usato il metodo pageContext.findAttribute(). Se l'attributo beanProperty specificato, il valore viene recuperato dalla corrispondente propriet del bean. Se il bean (o il valore della propriet del bean) un'istanza di una delle classi Attribute (Direct, Instance, ecc.), l'inserimento effettuato sulla base del tipo della classe. Altrimenti viene invocato sul bean il metodo toString() e la String restituita viene passata all'attributo name. II nome della propriet del bean. Se specificato il valore recuperato da questa propriet. Lo scope del contesto in cui pu essere trovato il bean. Se non specificato, viene usato il metodo pageContext.findAttribute(). Lo scope potrebbe essere un qualsiasi scope di JSP, componente o template. Negli ultimi due casi il bean viene ricercato nel contesto tile/componente/template. Una stringa che rappresenta l'URI di un tile o template. Gli attributi template, page, e component hanno esattamente gli stessi comportamenti. L'URL di un controller chiamato subito prima che la pagina sia inserita. L'URL di solito connota una action di Struts. Il controller (action) viene impiegato per preparare dati da visualizzare tramite il tile inserito. Va scelta una delle due possibilit controllerUrl o controllerClass.

attribute

beanName

beanProperty beanScope

component

controllerUrl

Il tipo della classe di un controller invocato subito prima dell'inserimento della pagina. Il controller viene usato per preparare dati che devono essere trattati dal tile inserito. Solo uno tra controllerUrl o controllerClass dovrebbe essere usato. La classe deve controllerClass implementare o estendere una delle seguenti: org.apache.struts.tiles.Controller, org.apache.struts.tiles.ControllerSupport, o org.apache.struts.action.Action.

definition

Il nome della definizione da inserire. Le definizioni sono inserite in un file centralizzato. Per ora solo le definizioni di una factory possono essere inserite all'interno di questo attributo. Per inserire

una detinizione tramile il tag definition si usa beanName="".

flush ignore name page role template

true o false. Se true, la pagina corrente viene inviata prima dell'inserimento dellatile.
Se quest'attributo true e l'attributo specificato da name non esiste, esce senza scrivere nulla. Il valore predefinito false, che provocher il lancio di un'eccezione. Il nome di un'entity da inserire. La ricerca avviene in quest'ordine: definition, attribute, e in seguito page. Una stringa che rappresenta l'URI di un tile o template. Gli attributi template, page e component si comportano allo stesso modo. Se l'utente ha il ruolo specificato, il tag viene tenuto presente; altrimenti il tag viene saltato e il contenuto non viene scritto. Una stringa che rappresenta l'URI di un tile o template. Gli attributi template, page e component si comportano allo stesso modo

Molti esempi del tag insert sono gi stati mostrati nelle pagine precedenti del capitolo.

14.4.2 Il tag definition


Il tag definition viene usato per creare una definizione di tile (o template) in quanto bean. Il bean appena creato sar salvato sotto l' id specificato all'interno dello scope richiesto. Il tag definition ha la stessa sintassi del tag insert. La nuova definizione pu estendere una definizione descritta nella factory di definizione (file XML) e pu forzare qualsiasi parametro precedentemente definito. La Tabella 14-3 elenca gli attributi del tag definition.
Tabella 14-3. Attributi per il tag definition

Nome attributo

Descrizione II nome di una definizione genitore usata per inizializzare questa nuova definizione. La definizione parent viene ricercata nella factory di definizioni. II nome sotto il quale la definizione appena creata sar salvata. Attributo obbligatorio. URL del template/componente da inserire. Lo stesso di template. II ruolo da controllare prima di inserire questa definizione. Se il ruolo non definito per l'attuale utente, la definizione non viene inserita. Viene effettuato un controllo al momento dell'inserimento e non durante il processo di definizione. La variabile scope in cui verr creata la nuova definizione. Se non specificato il bean sar trattato nello scope di pagina. Stringa che rappresenta l'URL di una tile/componente/template (una pagina JSP)

extends id page role scope template

Il seguente frammento illustra come usare un tag definition in una pagina JSP:

<tiles:definition id="storefront.default" page="/layouts/storefrontDefaultLayout.jsp" scope="request"> <tiles:put name="header" value="/common/header.jsp" /> <tiles:put name="menubar" value="/common/menubar.jsp" /> <tiles:put name="copyright" value="/common/copyright.jsp" /> </tiles:definition>
In seguito sar mostrato un esempio completo, nella sezione "Dichiarare definizioni in una pagina JSP" di questo capitolo.

14.4.3 Il tag put


Il tag put viene usato per passare attributi a un componente tile. Tale tag pu essere usato solo all'interno dei tag insert o definition. Il valore (contenuto) del tag put viene specificato con l'attributo value o il tag body; anche possibile specificare il tipo del valore:

string page o template

Il contenuto tradotto letteralmente. Il contenuto incluso da un URL specificata. Name viene usato come URL.

definition

Il contenuto viene da una definizione specificata (factory). Name viene usato come nome della definizione.

Se si usa l'attributo type, dato per scontato che i tag get o insert siano all'interno della tile inserita. Se l'attributo type non specificato, il contenuto untyped, a meno che non provenga da un bean tipizzato. Impostare direct="true" equivalente a impostare type="string"

La Tabella 14-4 elenca gli attributi per il tag put. Tabella 14-4. Attributi per il tag put Nome attributo Descrizione II nome del bean impiegato per recuperare il valore. Il bean viene recuperato dal contesto specifico se esistente, altrimenti viene usato il metodo pageContext.findAttribute(). Se si specifica beanProperty, recupera il valore dalla corrispondente propriet del bean. Lo scope impiegato per ricercare il bean. Se non specificato viene usato il metodo pageContext.findAttribute(). Lo scope pu essere qualsiasi scope di JSP, "component", o "template". Nei due ultimi casi, viene ricercato il bean all'interno del contesto della tile/component/template. Il valore lo stesso dell'attributo value. Quest'attributo stato aggiunto per compatibilit con i tag template delle JSP. Come gestito il contenuto: true significa che il contenuto direttamente stampato, mentre false significa che incluso, false il valore predefinito. Questo un altro metodo di specificare il tipo di contenuto. Se direct="true", content "string"; se direct="false", content "page". Questo attributo era aggiunto per compatibilit con i tag template delle JSP. Il nome dellattributo. Questo tag viene tenuto in considerazione se l'utente riveste un certo ruolo, altrimenti viene ignorato e il contenuto non mostrato. Il role non considerato se si usa in una definizione il tag add. Tipo del contenuto: "string", "page", "template", o "definition". Se l'attributo type non viene specificato, il contenuto untyped, a meno che non provenga da un bean tipizzato. II valore dell'attributo pu essere String o Object. Il valore potrebbe essere direttamente assegnato (value="aValue") o derivare da un bean. Deve essere presente almeno un value, content o beanName.

beanName

beanProperty Il nome della propriet del bean. Se specificato, il valore viene recuperato da questa propriet. beanScope

content

direct name role type value

14.4.4 Il tag putList


Il tag putList crea un elenco che sar poi passato come attributo a una tile. Gli elementi dell'elenco sono aggiunti con l'utilizzo del tag add. Questo tag pu essere impiegato solo all'interno del tag insert o definition. La Tabella 14-5 elenca l'attributo per il tag putList. Tabella 14-5. Attributi per il tag putList Nome attributo Descrizione II nome della lista. Questo attributo obbligatorio.

name

14.4.5 Il tag add


Il tag add aggiunge un elemento alla lista. Questo tag pu essere solo impiegato all'interno del tag putList, Il valore potrebbe derivare da un'assegnazione diretta (value="aValue") o da un bean. Deve essere specificato obbligatoriamente value o beanName. La Tabella 14-6 elenca gli attributi per il tag add. Tabella 14-6. Attributi per il tag add Nome attributo Descrizione II nome del bean usato per recuperare il valore. Il bean viene recuperato dal contesto specificato se presente. Altrimenti viene usato il metodo pageContext.findAttribute(). Se specificato beanProperty il valore recuperato dalla corrispondente propriet del bean

beanName

beanProperty Il nome della propriet del bean. Se specificato, il valore viene recuperato da questa propriet.

beanScope

Lo scope impiegato per ricercare il bean. Se non specificato viene usato il metodo pageContext.findAttribute(). Lo scope pu essere qualsiasi scope di JSP, "component", o "template". Nei due ultimi casi, viene ricercato il bean all'interno del contesto della tile/component/template. Il valore lo stesso dell'attributo value. Quest'attributo stato aggiunto per compatibilit con i tag template delle JSP. Come gestito il contenuto: true significa che il contenuto direttamente stampato, mentre false significa che incluso, false il valore prestabilito. Questo tag viene tenuto in considerazione se l'utente riveste un certo ruolo, altrimenti viene ignorato e il contenuto non mostrato. Il role non considerato se si usa in una definizione il tag add Tipo del contenuto: "string", "page", "template", o "definition". Se l'attributo type non viene specificato, il contenuto untyped, a meno che non provenga da un bean tipizzato. Il valore che deve essere aggiunto. Pu essere String o Object.

content direct role type value

14.4.6 Il tag get


Il tag get recupera il contenuto dal contesto della tile e lo include nella pagina. La Tabella 14-7 elenca gli attributi per il tag get. Tabella 14-7. Attributi per il tag get Nome attributo Descrizione

flush ignore name role

true o false. Se true, la pagina corrente visualizzata prima dell'inserimento del tile.
Se questo attributo impostato a true e l'attributo specificato dal nome non esiste, non scrive nulla. Il valore predefinito false, che provoca il lancio di un'eccezione a runtime. II nome del contenuto da recuperare dallo scope del tile. Questo attributo obbligatorio. Questo tag viene tenuto in considerazione se l'utente riveste un certo ruolo, altrimenti viene ignorato.

14.4.7 Il tag getAsString


Il tag getAsString recupera il valore della propriet del valore del tile specificato e lo riporta come String al JspWriter corrente. Al valore applicata ia conversione toString(). Se il valore non trovato, sar lanciata una JSPException. La Tabella 14-8 elenca gli attributi per il tag getAsString.
Tabella 14-8. Attributi per il tag getAsString

Nome attributo

Descrizione Se impostato a true e l'attributo specificato dal nome non esiste, non scrive nulla. Il valore predefinito false, che provocher il lancio di un'eccezione a runtime. II nome dell'attributo. Questo attributo obbligatorio. Questo tag viene tenuto in considerazione se l'utente riveste un certo ruolo, altrimenti viene ignorato.

ignore name role

14.4.8 Il tag useAttribute


Il tag useAttribute dichiara una variabile Java e un attributo nello scope specificato, usando il valore dell'attributo della tile. La variabile e l'attributo saranno specificati tramite l'id o tramite il nome originale se non altrimenti specificato. La Tabella 14-9 elenca gli attributi per il tag useAttribute.
Tabella 14-9. Attributi per il tag useAttribute

Nome attributo

Descrizione La classe della variabile dichiarata. II valore e il nome di variabile dichiarati. Se quest'attributo impostato a true e l'attributo specificato dal nome non esiste, semplicemente esce senza errori. Il valore di default false, che causer il lancio di un'eccezione a runtime. II nome dell'attributo del tile. Questo attributo obbligatorio, Lo scope dell'attributo dichiarato. Di default "page".

classname id ignore name scope

14.4.9 Il tag importAttribute


II tag importAttribute importa l'attributo dalla tile allo scope richiesto. Gli attributi name e scope sono opzionali. Se non diversamente specificato, tutti gli attributi sono importati nello scope di pagina. Una volta importato, un attributo pu essere impiegato come qualsiasi altro bean dal contesto della JSP. La Tabella 14-10 elenca gli attributi per il tag importAttribute.
Tabella 14-10. Attributi per il tag importAttribute

Nome attributo

Descrizione Se quest'attributo impostato a true e l'attributo specificato dal nome non esiste esce senza errori. Il valore predefinito false, che causer il lancio di un'eccezione a runtime. II nome dell'attributo del tile. Se non specificato, tutti gli attributi sono importati. Lo scope dell'attributo importato. Valore predefinito "page".

ignore name scope

14.4.10 Il tag initComponentDefinitions


Il tag initComponentDefinitions s inizializza la factory delle definizioni. La Tabella 14-11 elenca gli attributi per questo tag.
Tabella 14-11. Attributi per il tag initComponentDefinitions

Nome attributo

Descrizione Se specificato, l'attributo classname della factory da creare e inizializzare. II nome del file di definizione. Questo attributo obbligatorio.

classname file

14.5 Uso delle definizioni


Quanto fin qui mostrato aggiunge valore ad un'applicazione dal momento che Tiles organizza in una singola risorsa l'aspetto della pagina JSP. Questo potrebbe far risparmiare tempo di sviluppo e soprattutto il tempo occorrente a modificare l'aspetto di un'applicazione. Tuttavia vi un problema relativo all'approccio impiegato nell'applicazione Storefront poco fa illustrato. la ogni tile non relativa all'aspetto c' comunque codice ridondante che specifica quale contenuto usare per header, menubar e copyright: gli stessi attributi che sono passati in ogni pagina. Non sempre accade, ma in generale questi valori saranno costanti per tutta l'applicazione. Per esempio in ogni pagina di solito mostrato lo stesso copyright. Specificare questi valori in ogni tile ridondante. Idealmente si potrebbero dichiarare questi attributi in un solo luogo e i tile potrebbero includere solo gli attributi specifici per la pagina in un luogo dove necessario. Le definizioni Tiles offrono questa funzionalit. Una definizione permette di specificare staticamente gli attributi usati da un template che a sua volta permette di specifiche solo gli attributi specifici della pagina nelle proprie tile. Le definizioni consentono: Definizione della schermata Centralizzazione della descrizione della pagina Di evitare dichiarazioni ripetitive di pagine quasi uguali (tramite l'uso dell'ereditariet delle definizioni) Di evitare la creazione di componenti intermedi usati per passare parametri Di specificare il nome di una definizione come forward nel file di configurazione di Struts. Di specificare il nome di una definizione come parametri del componente Di ridefinire gli attributi delle definizioni Uso di una copia differente di un componente, a seconda della localizzazione (I18N) Uso di una copia differente di un componente, a seconda di una chiave (Questo potrebbe essere usato per mostrare diversi aspetti a seconda del tipo di client) Le definizioni possono essere dichiarate in una pagina JSP oppure in un file XML. Ad ogni modo, si dovrebbe provare a tenere le definizioni in una collocazione centralizzata. Se si pensa di usare una pagina JSP per specificare le proprie definizioni, si mettano tutte le definizioni per la propria applicazione in una pagina sola. Non si devono sparpagliare le proprie definizioni per tutto il sito, dal momento che questo render la manutenzione pi difficile.

14.5.1 Dichiarare definizioni in una pagina JSP

Come gi detto, ci sono due luoghi in cui si possono specificare le definizioni: una pagina JSP o un file XML. Per primo sar discusso l'approccio tramite pagine JSP. Per seguire questo approccio, si crei una pagina JSP e si dichiarino tutte le definizioni al suo interno. Per l'applicazione Storefront, si creato un file storefront-defs.jsp e si sono poste le definizioni di default al suo interno, come mostrato nell'Esempio 14-6.
Esempio 14-6. Dichiarazione di definizioni di tile in una pagina JSP

<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %> <tiles:definition id="storefront.default" page="/layouts/storefrontDefaultLayout.jsp" scope="request"> <tiles:put name="header" value="/common/header.jsp" /> <tiles:put name="menubar" value="/common/menubar.jsp" /> <tiles:put name="copyright" value="/common/copyright.jsp" /> </tiles:definition>
La definizione nell'Esempio 14-6 usa lo stesso aspetto della tile prima illustrata. I file comuni prima sparsi attraverso le varie tile sono ora accentrati nel file di definizione, rendendo molto pi facile la loro modifica. Per esempio, se si volesse definire una differente pagina per il copyright, il file di definizione sarebbe l'unico luogo dove operare tale modifica. Non si dovrebbero perci modificare tutte le pagine JSP. La sintassi del tag definition somiglia a quella dei tag insert prima discussi. Si deve solo provvedere un attributo id e modificare l'attributo path come attributo page. Inoltre lo scope predefinito per il tag definition page. Si stabilito di impostare lo scope a page per offrire uno scope un po' pi vasto. Per trarre vantaggio dalle definizioni, i componenti devono potervi accedere. Dal momento che si impostato lo scope di definizione della richiesta ed esister solo per il tempo necessario a questa, occorre un modo per includere la definizione nelle diverse pagine JSP. Per fortuna gi noto il modo di includere una pagina JSP in un'altra pagina usando la direttiva JSP include. L'Esempio 14-7 illustra la pagina signin.jsp con l'impiego del file di definizione delle JSP.
Esempio 14-7. La pagina signin.jsp che impiega una definizione di tile

<%@ taglib uri="/WEB-INF/tiles.tld" prefix="tiles" %> <%@include file="../common/storefront-defs.jsp" %> <tiles:insert beanName="storefront.default" beanScope="request"> <tiles:put name="body-content" value="../security/signin-body.jsp"/> </tiles:insert>
Con quest'approccio i componenti di tile devono solo inserire il contenuto specifico per la pagina. Si paragoni l'Esempio 14-7 all'Esempio 14-5. Si noter allora che il file signin.jsp che usa le definizioni deve solo mettere a disposizione contenuti specifici per la pagina, il file sign-body.jsp.

14.5.2 Dichiarazione di definizioni in un file di configurazione


Si possono anche dichiarare le definizioni in un file centralizzato XML. Se sia preferibile usare le JSP o l'XML dipende dalle proprie esigenze e requisiti. Con l'approccio basato sull'XML non si dovr usare la direttiva include prima mostrata.

14.5.2.1 Creazione di un file di configurazione per le definizioni


Per usare l'approccio basato su XML, si crei un file XML che segua la sintassi del file tiles-config.dtd. Il file XML delle definizioni dovrebbe essere posto nella directoryWEB-INF assieme alle altre metainformazioni delle applicazioni. Anche il DTD dovrebbe essere posto nella directory WEB-INF. L'Esempio 14-8 ne mostra un esempio.
Esempio 14-8. Il file XML di definizioni per Storefront

<!DOCTYPE tiles-definitions PUBLIC

"-//Apache Software Foundation//DTD Tiles Configuration//EN" "http://jakarta.apache.org/struts/dtds/tiles-config.dtd"> <tiles-definitions> <definition name="storefront.default" path="/layouts/storefrontDefaultLayout.jsp"> <put name="header" value="/common/header.jsp" /> <put name="menubar" value="/common/menubar.jsp" /> <put name="copyright" value="/common/copyright.jsp" /> </definition> </tiles-definitions>
Non esiste molta differenza tra il formato delle definizioni nel file JSP dell'Esempio 14-6 e nel file XML dell'Esempio 14-8. Il file XML impiega una sintassi appena differente. I due formati sono comunque abbastanza simili da causare problemi.noti che nelle definizioni Si JSP si usa il tag put:

<tiles:put name="body-content" value="../security/signin-body.jsp"/> ma nelle definizioni XML si usa l'elemento : put <put name="header" value="/common/header.jsp" />
Ci si assicuri di non confondere i due dal momento che potrebbe essere difficile da scoprire un bug a posteriori. Ogni definizione dovrebbe avere un nome unico. I tag e le pagine JSP adoperano il nome per recuperare la definizione. Tuttavia l'uso differisce da quello degli URL dal momento che solo un nome logico per la definizione.

14.5.3 Estensione delle definizioni di file


Uno dei maggiori vantaggi dell'usare le definizioni sta nella possibilit di creare nuove definizioni estendendo quelle gi esistenti. Tutti gli attributi e le propriet delle definizioni madri vengono ereditati e per questo si pu ridefinire qualsiasi attributo o propriet. Per estendere una definizione si aggiunge l'attributo extends. L'Esempio 14-9 mostra un esempio di una definizione chiamata storefront.custom che estende la definizione storefront.default.
Esempio 14-9. Le definizioni possono estendere altre definizioni

<tiles-definitions> <definition name="storefront.default" path="/layouts/storefrontDefaultLayout.jsp"> <put name="header" value="/common/header.jsp" /> <put name="menubar" value="/common/menubar.jsp" /> <put name="copyright" value="/common/copyright.jsp" /> </definition> </tiles-definitions> <tiles-definitions> <definition name="storefront.custom" extends="storefront.default"> <put name="copyright" value="/common/new-copyright.jsp" /> </definition> </tiles-definitions>
Nell'Esempio 14-9, sono ereditati tutti gli attributi nella definizione storefront.default. Tuttavia la definizione storefront.customer ignora il valore dell'attributocopyright con una pagina alternativa di copyright. Questa una caratteristica molto potente. Se si hanno definizioni figlie multiple che estendono tutte una definizione centrale, il cambiamento della definizione centrale si riflette su tutte le definizioni figlie. Vale a dire, cambiando il layout nella definizione centrale, il cambiamento si riflette in tutte le altre.

14.5.4 Uso delle definizioni come forward in Struts

Le definizioni di Tiles possono essere usate anche come forward di Struts, invece dei normali URL. Per poter usare le definizioni in tal modo, si creino prima le definizioni:

<tiles-definitions> <definition name="storefront.default" path="/layouts/storefrontDefaultLayout.jsp"> <put name="header" value="/common/header.jsp" /> <put name="menubar" value="/common/menubar.jsp" /> <put name="copyright" value="/common/copyright.jsp" /> </definition> <definition name="storefront.superuser.main" extends="storefront.default"> <put name="header" value="/common/super_header.jsp" /> <put name="menubar" value="/common/super_menubar.jsp" /> <put name="copyright" value="/common/copyright.jsp" /> </definition> </tiles-definitions>
Questo frammento illustra due definizioni, la definizione standard di default e una seconda che definisce il layout per un eventuale "superutente". Un superutente potrebbe essere un cliente abituale che compie molte ordinazioni per cui deve avere la possibilit di poter usare pi opzioni per una maggior facilit d'uso. Nel file di configurazione di Struts vanno definiti poi i forward che usano tali definizioni:

<global-forwards> <forward name="Super_Success" path="storefront.superuser.main" /> </global-forwards>


Si potrebbe poi usare il forward Super_Success per inoltrare storefront.superuser.main proprio come per qualsiasi altro forward. l'utente alla definizione

14.6 Supporto di Tiles per l'internazionalizzazione


Anche se Struts offre determinate funzionalit di I18N che possono essere impiegate con Tiles, quest'ultimo mette a disposiziorie anche la possibilit di usare una particolare tile basata sulle impostazioni locali dell'utente. Per il supporto di tale caratteristica nella propria applicazione necessario creare una diversa definizione di Tiles per ogni impostazione locale che si desidera supportare. Per esempio, se occorre supportare una serie di definizioni per le impostazioni locali statunitensi e una serie separata per quelle tedesche, si devono creare due file di definizione separati: tiles-tutorial-defs_en.xml tiles-tutorial-defs_de.xml Le regole di denominazione del suffisso seguono quelle di java.util.ResourceBundle, che anche impiegato dal resource bundle di Struts. Quando viene effettuata una richiesta per una definizione, la definizione correit determinata dalle impostazioni locali incluse. Si deve sempre usare una definizione di base quando non sono fornite impostazioni locali o non supportata l'impostazione locale in uso. Non viene aggiunto alcun suffisso di lingua o paese al nome del file di base delle definizioni Tiles.

Una volta che si creata la definizione specifica per la localizzazione e la si posta nella directory WEB-INF, l'unica altra cosa da fare assicurarsi che una Locale venga immagazzinata nella HttpSession dell'utente. Tiles dipende dalla stessa istanza di Locale che Struts usa per determinare quale sia il file di definizioni da impiegare. Ci si deve assicurare che Struts conservi le impostazioni locali dell'utente nella sessione. Questo obiettivo raggiunto impostando l'attributo locale a true nell'elemento controller. Si veda "11 DTD di configurazione di Struts" nel Capitolo 4 per maggiori ragguagli sull'elemento controller. Questo quanto c'era da dire su Tiles. Si noti che si deve continuare a fare affidamento sul resource bundle di Struts per quanto riguarda le risorse locale-sensitive, quali testo, messaggi e immagini. Il supporto in Tiles di I18N dvrebbe essere sempre usato per differenze nel layout basate sulla localizzazione. Struts e Tiles funzionano molto bene insieme al fine di produrre un completo supporto di I18N.

CAPITOLO 15

Il logging nelle applicazioni Struts

In quanto programmatori Java diligenti e appassionati, vorremmo illuderci che il nostro software potr superare anche l'esame pi impegnativo. Tuttavia, dato che tutti possono sbagliare, non mai buona abitudine credere che il proprio software sia immune da difetti. Diventa quindi importante usare qualche strumento per eliminare i difetti o almeno per ridurli al minimo accettabile. La generazione di messaggi di log pu aiutare a individuare i difetti presenti nei propri programmi, ma anche importante per altri motivi. Ad esempio, la sicurezza e il controllo possono dipendere dal logging per raccogliere informazioni per gli amministratori di sistema riguardo a cosa stanno facendo gli utenti autorizzati e soprattutto quelli non autorizzati. Con informazioni in tempo reale circa potenziali attacchi alla sicurezza dell'applicazione, il logging pu dare un margine molto utile agli amministratori di sistema e permettere reazioni pi rapide. Questo capitolo prende in esame da vicino l'impiego del logging nelle applicazioni Struts e come possa essere utile per identificare difetti prima che l'applicazione entri in produzione oppure, se il software gi in produzione, come possa identificare velocemente i problemi per giungere a una soluzione

15.1 Il logging in una web application


L'importanza del logging nota agli sviluppatori da molti anni. Pertanto, il logging potrebbe essere una parte importante del proprio framework come la gestione delle eccezioni o anche la sicurezza, entrambe le quali possono dipendere dalla funzionalit di logging per esercitare la propria funzione. Senza il logging, la gestione di un'applicazione pu diventare un dramma per i programmatori. Tutti sappiamo che le applicazioni reali vengono periodicamente revisionate. Tuttavia ci si potrebbe chiedere se il logging delle web application sia cos importante e necessario come il logging in altri tipi di applicazioni. Dal momento che le web application possono essere a volte piccole e meno complesse della loro controparte enterprise, si potrebbe essere indotti a pensare che il logging sia meno importante in queste applicazioni. Tuttavia con web application complesse, e non questo il caso, il logging importante come quello per un'applicazione enterprise.

Logging di sistema e di applicazione a confronto


I messaggi di log possono essere separati arbitrariamente in due categorie: messaggi di sistema e messaggi di applicazione. I messaggi di sistema hanno a che fare con le operazioni interne dell'applicazione, piuttosto che con qualcosa di specifico per un certo utente o dato: per esempio, un messaggio di sistema potrebbe indicare che l'applicazione impossibilitata a inviare un messaggio di posta elettronica perch il server SMTP non risponde. D'altro canto, un messaggio di applicazione potrebbe indicare che l'utente "Jane Doe" ha provato a sottoporre un ordine di acquisto che era oltre il limite di credito propria azienda. della Il messaggio di sistema nel primo caso potrebbe essere registrato con una priorit "error", mentre il messaggio di applicazione potrebbe solo avere la priorit di "info. " Si pu allora impostare l'ambiente di logging in modo che messaggi con priorit "error" generino e-mail o messaggi al cercapersone degli amministratori di sistema con la richiesta di inter ento, mentre i messaggi "info" vengano immagazzinati fileun un esame successivo. v in per Differenti tipi e categorie di messaggi di log sono di solito impiegati per usi differenti. Anche se molte applicazioni possono registrare eventi con la priorit di "error", quello errore per un'organizzazione potrebbe essere solo un che avviso per un'altra. Non esiste grande omogeneit tra organizzazioni e non ci sar probabilmente mai: le aziende una hanno priorit diverse e ci che critico per una potrebbe essere trascurabile per un'altra. In questo capitolo si generalizzer la discussione sul confronto tra messaggi di sistema e di applicazione. Dal momento che non c' uniformit riguardo a quello che deve essere considerato errore, avviso o solo informazione generica per la propria applicazione, que una decisione che si dovr prendere a tutti i livelli della propria struttura sta produttiva.La discussione sar mantenuta a un livello di astrazione senza entrare in questi dettagli.

15.2 Uso del servlet container per il logging


Le specifiche delle Servlet richiedono che ogni servlet container permetta agli sviluppatori di effettuare il logging di eventi nel file di log del container. Anche se il nome e la posizione del file di log dipendono dal container, il modo in cui questi eventi sono registrati diretto dalle specifiche ed portabile tra diversi web container. La classe javax.servlet.ServletContext contiene due metodi che possono essere adoperati per la registrazione di messaggi nel log del container:

public void log( String msg ); public void log( String msg, Throwable throwable ); Si possono usare questi metodi tramite ServletContext passando i corretti argomenti. L'Esempio 15-1 illustra come farlo tramite una Action di Struts
Esempio 15-1. LoginAction: uso di ServletContext per il logging

public class LoginAction extends StorefrontBaseAction { public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response ) throws Exception{ // Get the user's login name and password. They should have already been // validated by the ActionForm. String email = ((LoginForm)form).getEmail( ); String password = ((LoginForm)form).getPassword( ); // Obtain the ServletContext ServletContext context = getServlet().getServletContext( // Log which user is trying to enter the site context.log( "Login email: " + email ); // Log in through the security service IStorefrontService serviceImpl = getStorefrontService( ); UserView userView = serviceImpl.authenticate(email, password); // Log the UserView for auditing purposes context.log( userView.toString( ) ); UserContainer existingContainer = null; HttpSession session = request.getSession(false); if ( session != null ){ existingContainer = getUserContainer(request); session.invalidate( ); }else{ existingContainer = new UserContainer( ); } // Create a new session for the user session = request.getSession(true); existingContainer.setUserView(userView); session.setAttribute(IConstants.USER_CONTAINER_KEY, existingContainer); return mapping.findForward(IConstants.SUCCESS_KEY); } } LoginAction dell'Esempio 15-1 mostra una semplice applicazione per l'invio di messaggi di log al file di log del container. Invoca il metodo log() e passa un messaggio come stringa di lettere che viene scritto nel log. Come
precedentemente affermato, nome e posizione del log dipendono dal web container in uso. Alcuni di questi possono assegnare un file di log separato per ciascuna applicazione mentre altri possono usare un singolo file di log. Se viene usato un file di log singolo i messaggi di web application diverse finiranno nello stesso file, in genere preceduti dal nome dell'applicazione.

);

15.2.1 Uso dei filtri


I filtri sono una caratteristica nuova delle specifiche 2.3 delle servlet. I filtri delle servlet permettono di controllare e/o trasformare il contenuto degli oggetti di request e response HTTP. Il loro funzionamento permette di operare operare sia su contenuti statici che dinamici. Struts 1.1 supporta sia le specifiche per le servlet 2.2 che le 2.3. Se non si usa un container che aderisce alle specifiche Servlet 2.3 non si potr trarre vantaggio dai filtri delle servlet.

Tramite i filtri, gli sviluppatori di servlet possono compiere le seguenti azioni: Accedere a una risorsa web prima che sia invocata una request a questa. Elaborare una request per una risorsa prima che sia invocata. Modificare header e dati inglobandola request in una versione personalizzata dell'oggetto della request. Modificare gli header e i dati di response inglobando la response in una versione personalizzata dell'oggetto della response. Intercettare una chiamata a un metodo su una risorsa prima che sia compiuta. Compiere azioni su una servlet o sii gruppi di servlet tramite uno o pi filtri attivi in un certo ordine. Sulla base dei compiti richiesti da sviluppatori di servlet, le specifiche delle servlet descrivono parecchi tipi possibili di filtri: Filtri per autenticazione Filtri per controllo e logging Filtri per conversione delle immagini Filtri per compressione dei dati Filtri per cifratura Filtri per tokenizing Filtri che innescano eventi basati su accesso a una risorsa Filtri XSLT per trasformare contenuti XML Filtri MIME a catena Filtri per mantenere in cache URL e altre informazioni Tutti questi tipi di filtri sono interessanti, ma in questo capitolo ci si occuper di quelli per il logging e il controllo. Con i filtri possibile registrare messaggi con qualsiasi dato contenuto negli oggetti di response e request. Dal momento che il filtro associato strettamente al container delle servlet, sar probabilmente necessaria la funzionalit di logging da altre parti nella propria applicazione. L'uso di filtri di solito non sufficiente per la totalit dell'applicazione. Tuttavia i filtri sono perfetti per controllare o seguire le azioni di un utente nel sistema. Se si vuole avere un'idea di quali parti del proprio sito siano le pi frequentate o di dove vanno alcuni gruppi di utenti, i filtri costituiscono la scelta ideale. Possono anche fornire informazioni a riguardo di dati specifici cui gli utenti accedono pi di frequente. Per esempio, poniamo di avere un'applicazione tipo catalogo in linea e di voler sapere quali articoli gli utenti visionano pi spesso. Dal momento che gli oggetti di richiesta e risposta contengono questa informazione, si pu facilmente seguire questa informazione e immetterla in un database per successive analisi.

15.2.2 Creazione di un filtro


Per creare un filtro occorre seguire tre passi: 1. 2. 3. Creare una classe Java che implementi l'interfaccia javax.servlet.Filter e che contenga un costruttore di default. Dichiarare il filtro nel descrittore di deploy della web application tramite l'elemento filter. Mettere insieme un package con il resto delle risorse della web application.

15.2.2.1 Creazione della classe filtro


Il primo passo per la creazione di una classe filtro sta nella realizzazione di una nuova classe Java lo nell'uso di una gi esistente) facendo s che implementi l'interfaccia javax.servlet.Filter. Le classi Java possono implementare interfacce multiple per cui non si deve necessariamente creare una nuova classe. Tuttavia, la classe dovr essere caricata dal web container, per cui non dovrebbe essere installata solo in un sistema di supporto come un container di EJB. L'interfaccia Filter contiene tre metodi che devono essere implementati nella classe:

public void init(FilterConfig filterConfig) throws ServletException; public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; public void destroy( );

Il web container invoca il metodo init() quando pronto a porre in opera il filtro. In questo metodo si pu inizializzare qualsivoglia risorsa. Il metodo destroy() l'opposto di init(). Il web container invoca questo metodo quando chiama fuori servizio il filtro. A questo punto si dovrebbero ripulire le risorse aperte che il filtro potrebbe avere utilizzato, ad esempio le connessioni ai database. Infine, il web container chiama il metodo doFilter() ogni volta che una richiesta viene recepita e il container determina che dovrebbe essere avvisata l'istanza filtro. Qui il luogo dove porre qualsivoglia funzionalit il filtro debba svolgere. L'Esempio 15-2 illustra un filtro che potrebbe essere usato per effettuare il logging di eventi nel file di log delle servlet o per inizializzare un servizio di log terze parti.
Esempio 15-2. Una classe servlet filtro

import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletRequest; import javax.servlet.ServletContext; import javax.servlet.ServletResponse; import javax.servlet.ServletException; /** * An Esempio servlet filter class. */ public class LoggingFilter implements Filter{ public final static String LOG_FILE_PARAM = "log_file_name"; private FilterConfig filterConfig = null; private ServletContext servletContext = null; public void init( FilterConfig config ) throws ServletException { // Initialize any neccessary resources here this.filterConfig = config; this.servletContext = config.getServletContext( ); // You can get access to initialization parameters from web.xml // although this Esempio doesn't really use it String logFileName = config.getInitParameter( LOG_FILE_PARAM ); // You can log messages to the servlet log like this log( "Logging to file " + logFileName ); // Maybe initialize a third-party logging framework like log4j } public void doFilter( ServletRequest request, ServletResponse response, FilterChain filterChain )

throws IOException, ServletException { // Log a message here using the request data log( "doFilter called on LoggingFilter" ); // All request and response headers are available to the filter log( "Request received from " + request.getRemoteHost( ) ); // Call the next filter in the chain filterChain.doFilter( request, response ); } public void destroy( ){ // Remove any resources to the logging framework here log( "LoggingFilter destroyed" ); } protected void log( String message ) { getServletContext( ).log("LoggingFilter: " + message ); } protected ServletContext getServletContext( return this.servletContext; } }
Proprio come si deve stare attenti all'uso di thread multipli nelle servlet Java, si deve porre attenzione a non violare con i filtri qualsiasi pratica di salvaguardia dei thread. Il servlet container potrebbe inviare thread concorrenti a una sola istanza di una classe filtro e ci si deve assicurare di non creare niente che causi problemi tra i thread. In altri termini, nessun dato specifico per il client dovrebbe essere conservato in variabili di istanza. Le variabili locali vanno bene, proprio come le servici Java, dal momento che sono conservate all'interno dello stack invece che nellheap.

){

15.2.2.2 Dichiarare il filtro nel descrittore di deploy


Il secondo passo nella creazione di un filtro delle servlet sta nella configurazione degli elementi appropriati nel descrittore di deploy per l'applicazione web. Come appreso dal Capitolo 4, il nome del descrittore di deploy per una web application web.xml. Il primo passo nell'impostazione della dichiarazione del filtro nel descrittore di deploy della web application sta nella creazione dei vari elementi filter. Il Capitolo 4 descrive l'elemento filter in maggior dettaglio. La seguente porzione di descrittore di deploy illustra l'uso della classe LoggingFilter della sezione precedente:

<filter> <filter-name>MyLoggingFilter</filter-name> <filter-class>LoggingFilter</filter-class> <init-param> <param-name>log_file_name</param-name> <param-value>log.out</param-value> </init-param> </filter>


Si potrebbero anche specificare dei parametri di inizializzazione o icone o ancora una descrizione e un'etichetta. Si veda il Capitolo 4 per maggiori dettagli sugli attributi di un elemento filter. Una volta aggiunto l'elemento filter, si deve aggiungere un elemento filter-mapping che associer o collegher il filtro specifico a una servlet o a una risorsa statica nella web application. I filtri possono essere applicati a una servlet singola, a gruppi di servlet e a contenuto statico, impiegando due diversi approcci di mapping. La seguente porzione di descrittore di deploy illustra il mapping di un filtro sulla singola servlet MyEsempioServlet:

<filter-mapping> <filter-name>MyLoggingFilter</filter-name> <servlet-name>MyEsempioServlet</servlet-name> </filter-mapping>

Ogni volta che il web container riceve una richiesta per la risorsa MyEsempioServlet, verr invocato il metodo doFilter() nella classe LoggingFilter. La seguente porzione XML mostra come il filtro possa essere mappato su tutte le request inviate alla web application:

<filter-mapping> <filter-name>MyLoggingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>


Il mapping del filtro, in questo caso, far corrispondere tutte le richieste a MyLoggingFilter dal momento che ogni URI comprender comunque il pattern dell'URL "/*".

15.2.2.3 Packaging del filtro


Il passo finale sta nel realizzare il package della classe filtro con il resto delle risorse di una web application. Come per qualsiasi altra risorsa Java che fa parte di una web application, la classe filtro deve essere inclusa nel file WAR e deve poter essere caricata dal class loader della web application. Nella maggioranza dei casi la classe filtro dovrebbe essere posta all'interno della directory WEB-INF/classes della web application. Le classi filtro possono essere anche inserite in un file JAR e poste nella directory WEB-INF/lib

15.2.3 Uso dei listener di eventi


I listener di eventi delle web application sono classi Java che implementano una o pi interfacce delle servlet per i listener degli feventi. I listener degli eventi supportano le notifiche per gli eventi riguardanti i cambiamenti dello stato negli oggetti ServletContext e HttpSession. I listener degli eventi legati a ServletContext supportano modifiche a livello applicativo, mentre quelli legati a HttpSession vengono informati di cambiamenti di stato a livello di sessione. Per ogni tipo di evento, possono essere impostati listener multipli e lo sviluppatore di servlet pu offrire la possibilit di scegliere l'ordine delle notifiche sulla base del tipo di evento. Le Tabelle 15-1 e 15-2 elencano i tipi di eventi e le interfacce event-listener a disposizione dello sviluppatore di servlet.
Tabella 15-1. ServletContext: eventi dell'applicazione e interfacce listener

Tipo Evento Lifecycle Attributes

Descrizione

Interfaccia listener

ServletContext sta per gestire la prima

richiesta oppure sta per essere spento dal container ServletContextListener delle servlet Sono stati aggiunti, rimossi o modificati attributi di

ServletContext

ServletContextAttributesListener

Tabella 15-2. HttpSession: eventi dell'applicazione e interfacce listener

Tipo Evento Lifecycle Attributes

Descrizione

Interfaccia listener

Un oggetto HttpSession stato creato, invalidato o il HttpSessionListener tempo scaduto. Sono stati aggiunti, rimossi o modificati attributi di

HttpSession.

HttpSessionAttributesListener

15.2.4 Creazione di un listener di eventi


I passi per la creazione di un listener di eventi ricalcano quelli per l'impostazione dei filtri. Ci sono tre fasi fondamentali da seguire: 1. 2. 3. Creare una classe Java che implementi l'interfaccia event-listener per la quale si interessati a ricevere eventi. Questa classe deve contenere un costruttore senza argomenti Dichiarare il listener degli eventi all'interno del descrittore di deploy della web application usando l'elemento listener. Effettuare il packaging della classe del listener degli eventi con il resto delle risorse della web application.

15.2.4.1 Creazione della classe listener


Proprio come nella creazione di filtri il primo passo sta nel creare una classe Java che implementi l'interfaccia appropriata per il listener. Nell' Esempio 15-3 proveremo a creare una classe Java che implementi javax.servlet.ServletContextListener e che sia responsabile dell'inizializzazione del servizio di logging all'avvio della web application.
Esempio 15-3.Interfaccia ServletContextListener.

import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; /** * An Esempio event listener class that * initializes a logging service. */ public class LoggingListener implements ServletContextListener{ private ServletContext context = null; /** * Called by the container before the first request is * processed. This is a good time to initialize * the logging service. */ public void contextInitialized( ServletContextEvent event ){ this.context = event.getServletContext( ); // Initialize the logging service here // Log a message that the listener has started log( "LoggingListener initialized" ); } /** * Called by the container when the ServletContext is about * ready to be removed. This is a good time to clean up * any open resources. */ public void contextDestroyed( ServletContextEvent event ){ // Clean up the logging service here // Log a message that the LoggingListener has been stopped log( "LoggingListener destroyed" ); } /** * Log a message to the servlet context application log or * system out if the ServletContext is unavailable. */ protected void log( String message ) { if (context != null){ context.log("LoggingListener: " + message ); }else{ System.out.println("LoggingListener: " + message); } } }
La classe event-listener dell'Esempio 15-3 ha due metodi invocati dal web container:

public void contextInitialized( ServletContextEvent event );

public void contextDestroyed( ServletContextEvent event );


Il web container chiama il metodo contextInitialized() prima dell'elaborazione della prima request. In questo metodo si dovrebbero quindi inizializzare tutte le risorse necessarie. Ad esempio, potrebbe essere il momento ideale per inizializzare un servizio di logging. Il metodo contextDestroyed() viene invocato Quando la web application posta fuori servizio. Quindi il momento migliore per chiudere qualsiasi risorsa lasciata apena e in uso da parte del listener. Dal momento che ci possono essere classii listener degli eventi multiple per lo stesso evento, si user la classe eventlistener ServletContext per rendere l'esempio maggiormente realistico. L'Esempio 15-4 mostra la classe DBConnectionPoolListener. Sia LoggingListener che DBConnectionPoolListener riceveranno notifiche degli eventi qualora ServletContext sia inizializzata o distrutta.
Esempio 15-4. La classe DBConnectionPoolListener

import javax.servlet.ServletContextListener; import javax.servlet.ServletContextEvent; /** * An Esempio event listener class that * initializes the database connection pooling. */ public class DBConnectionPoolListener implements ServletContextListener{ /** * Called by the container before the first request is * processed. This is a good time to initialize * the connection pooling service. */ public void contextInitialized( ServletContextEvent event ){ // Initialize the connection pooling here } /** * Called by the container when the ServletContext is about * ready to be removed. This is a good time to clean up * any open resources. */ public void contextDestroyed( ServletContextEvent event ){ // Shut down the connection pooling and open database connections } }
Dal momento che sia LoggingListener che DBConnectionPoolListener sono in ascolto dello stesso tipo di eventi dell'applicazione il servlet container le informer nell'ordine in cui sono elencate nel descrittore di deploy sulla base del tipo dell'evento.

15.2.4.2 Dichiarazione del listener di eventi nel descrittore di deploy


La seguente porzione di descrittore di deploy per una web application mostra come impostare i listener degli eventi:

<web-app> <listener> <listener-class>LoggingListener</listener-class> </listener> <listener> <listener-class>DBConnectionPoolListener</listener-class> </listener> <servlet> <servlet-name>EsempioServlet</servlet-name> <servlet-class>EsempioServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>EsempioServlet</servlet-name>

<url-pattern>/Esempio</url-pattern> </servlet-mapping> </web-app> LoggingListener sar avvertito per prima, subito dopo verr informata l'istanza di DBConnectionPoolListener. Quando verr terminata la web application, i listener saranno informati in ordine opposto. I listener degli eventi di HttpSession vengono informati prima dei listener relativi al contesto
dell'applicazione.

15.2.4.3 Packaging dei listener di eventi


Il packaging delle classi listener degli eventi segue le linee guida descritte nella sezione precedente a proposito dei filtri. Vale a dire, le classi devono essere poste in WEB-INF/classes oppure in un file JAR posto nella directory WEB-INF/lib della web application.

15.3 Jakarta Commons Logging


Commons Logging una libreria open source per il logging che permette agli sviluppatori di impiegare una API comune per il logging pur conservando la libert di mantenere molte implementazioni di logging di terze parti. L'API Commons Logging isola l'applicazione e la protegge da un eccessivo;collegamento a una implementazione specifica di logging. Questa API mette a disposizione un piccolo insieme di classi e interfacce Java che un'applicazione pu importare e su cui pu basarsi ma che non ha alcuna implicita dipendenza su un certo prodotto per il logging. La libreria Logging permette agli sviluppatori di poter configurare in maniera dichiarativa l'implementazione del logging; la libreria scoprir dinamicamente quale implementazione in uso. Un'applicazione che usa la API Commons Logging non deve essere modificata quando l'implementazione di logging viene cambiata. Questo il maggior beneficio di tale package. Il package Commons Logging gi predisposto per supportare diverse implementazioni di logging: log4j (http://jakarta.apache.org/log4j) JDK 1.4 Logging LogKit (http://jakarta.apache.org/avalon/logkit) SimpleLog (scrive messaggi di log stdout e stderr) NoOpLog (vengono ignorati i messaggi di log) II package Commons Logging include soltanto le implementazioni di SimpleLog e NoOpLog; non contiene altri programmi di logging di terze parti che andranno perci scaricati separatamente.

L'estensibilit solo un'altra delle potenti caratteristiche di questa libreria. Se si impiega un package di logging non ancora supportato, si pu creare un adattatore per tale implementazione estendendo i componenti appropriati, e la nostra applicazione potr usarla.

15.3.1 Installazione del package Commons Logging


E possibile scaricare II codice sorgente e i file binari pi aggiornati dall'URL http://jakarta.apache.org/commons/logging.html. Struts 1.1 include gi commons-logging.jar, che il solo file binario richiesto. A meno non sia necessario utilizzare l'ultimissima versione, quella inclusa in Struts dovrebbe essere sufficiente. Si dovrebbe poi collocare il file commons-logging.jar nella directory WEB-INF/lib della web application. Si dovr anche decidere l'implementazione di logging. Il package Commons Logging ne include una chiamata SimpleLog che scrive i messaggi di log in stdout e stderr. Se non si vuole usare log4j e non si usa Java 1.4, SimpleLog una buona scelta per iniziare. Decisa l'implementazione si deve quindi configurare la classe di implementazione di modo che i componenti della factory di Commons Logging possano trovarla all'avvio

dell'applicazione. Ci sono molti modi per farlo, ma il pi semplice consiste nella creazione di un file di propriet chiamato commons-logging.properties che comprende i nomi delle classi di implementazione di logging presenti. L'elemento chiave di maggiore importanza in questo file la chiave org.apache.commons.logging.Log. impiegata per definire la classe di implementazione. Quanto segue illustra come impostare il package Commons Logging affinch utilizzi l'implementazione SimpleLog:

org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog
Durante il funzionamento i componenti di logging cercheranno il file commons-logging. properties e tenteranno di istanziare il nome completo di classe qui trovato. Il nome della classe specificata deve essere disponibile al class loader della web application. Il file di propriet deve essere inserito nella directory WEB-INF/classes. Per il passaggio a log4j, si deve soltanto cambiare il nome della classe nel filecommons-logging.properties:

org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JCategory Log
Si noti che si deve ancora configurare log4j per il proprio ambiente, inclusa la creazione di un file log4j.properties. Ogni implementazione di logging potrebbe avere differenti requisiti di configurazione che devono essere soddisfatti.

15.3.2 Uso della API Commons Logging


Terminata la configurazione, l'applicazione pronta a usare la API Commons Logging. Si devono includere le seguenti definizioni import in ogni classe o componente in cui si desideri usare questa API:

import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory;


Per ottenere un'istanza di un componente di logging cui inviare messaggi si devono usare i metodi della factory getLog() sulla classe org.apache.commons.logging.LogFactory:

public static Log getLog(Class class); public static Log getLog(String name) Entrambi i metodi getLog() restituiscono un oggetto che implementa l'interfaccia org.apache.commons.logging.Log. Per creare un'istanza di Log da usare all'interno della classe LoginAction, per esempio, si potrebbe passare il nome della classe al metodo getLog(): Log log = LogFactory.getLog( LoginAction.class );
L'istanza di Log sarebbe allora disponibile per la classe LoginAction:

if (log.isInfoEnabled( )){ // Log which user is trying to enter the site log.info( "Login email: " + email ); }
L'interfaccia Log implementa i metodi di logging che si possono usare per inviare messaggi di log a una certa destinazione. I seguenti sono i pi importanti:

debug( ) error( ) fatal( ) info( ) trace( ) warn( )


Ciascuno di questi metodi di log ha una versione ridefinita che prende una Throwable. Ci sono anche metodi che permettono di determinare se sia abilitato il debug, l'errore e cos via. Le prestazioni della propria applicazione possono essere migliorate se si controlla che sia stato abilitato un certo livello di logging. Per esempio, nel codice:

StringBuffer buf = new StringBuffer( ); buf.append( "Login Successful - " ); buf.append( "Name: " ); buf.append( userView.getFirstName( ) ); buf.append( " " ); buf.append( userView.getLastName( ) ); buf.append( " - " ); buf.append( "Email: " );

buf.append( userView.getEmailAddress(

) );

// Log the information for auditing purposes log.debug( buf.toString( ) );


le prestazioni sarebbero migliorate se le definizioni di append non fossero eseguite completamente quando il livello di logging non impostato per registrare i messaggi di debug. Per questo si pu usare il metodo isDebugEnabled():

if ( log.isDebugEnabled( ) ){ StringBuffer buf = new StringBuffer( ); buf.append( "Login Successful - " ); buf.append( "Name: " ); buf.append( userView.getFirstName( ) ); buf.append( " " ); buf.append( userView.getLastName( ) ); buf.append( " - " ); buf.append( "Email: " ); buf.append( userView.getEmailAddress( ) ); // Log the UserView for auditing purposes log.debug( buf.toString( ) ); }
In questo caso, l'applicazione non perde tempo a creare StringBuffer che poi non user.

15.3.3 Struts e il package Commons Logging


Struts ha un meccanismo interno di logging limitato e impiega anche la API Commons Logging. Per questo, il framework impiegher qualsiasi meccanismo di logging che sia configurato per la propria applicazione. I log di Struts sono ottimi per comprendere cosa succede all'interno di Struts quando questo elabora una richiesta ma, oltre le finalit di debug, non c' bisogno di preoccuparsene molto. Nella maggior parte degli ambienti di produzione, i messaggi generati da Struts dovrebbero essere disabilitati, secondo modalit specifiche dell'implementazione di logging scelta. IL rimanente contenuto di questo capitolo dedicato a una delle pi popolari implementazioni di logging in uso, log4j. Dal momento che anche supportata dal package Commons Logging, log4j un'ottima scelta per le necessit di logging di un'applicazione basata su Struts. The Struts framework does perform some limited internal logging, and also uses the Commons Logging API. Thus, the Struts framework will use whichever logging implementation you conFigura for your application.

15.4 Uso del package log4j


Probabilmente avrete gi sentito parlare delia libreria log4j da altre fonti ma, nel caso siate completamente digiuni della materia, di seguito si discuter un po' di storia di questa libreria. Come Struts, log4j un progetto open source che fa parte dell'insieme di progetti Jakarta. Essenzialmente una raccolta di classi e interfacce Java che offrono funzionalit di logging per svariati tipi di destinazione. Sono gi diversi anni che esiste, durante i quali stato continuamente corretto e migliorato, e viene applicato nello sviluppo di progetti Java di ogni tipo. Di fatto, log4j stato cosi ben accolto da essere stato poi portato in altri linguaggi come C, C++, Python e perfino .NET. Al momento di questa stesura, log4j giunto alla versione 1.2.5, che la sua ventiduesima release. La prossima versione, la 1.3, in lavorazione ma per un po' ancora non sar rilasciata. La versione 1.2 compatibile con le versioni precedenti per cui, se si usa la versione 1.1.3, questo materiale sar sempre utilizzabile.

Secondo i suoi creatori, log4j stato creato con due concetti in mente: velocit e flessibilit. Una delle caratteristiche principali dell'infrastruttura di logging costituita dalla propria nozione di ereditariet per le categorie o logger, come ora vengono chiamate. log4j supporta una relazione genitore/figlio tra i logger configurati nell'ambiente. Per esempio, se si configurato un logger nel package com.oreilly.struts e un'altro logger per tutte le classi in com.oreilly.struts.storefront, il primo logger sar il padre del secondo. Questa struttura gerarchica permette la flessibilit di controllo sui messaggi di log scritti sulla base di elementi come la struttura del package.

Non si deve andare molto pi in l se i requisiti non lo richiedono. Se lo si desidera, si potr configurare un singolo logger per l'intero ambiente. Si pu configurare log4j per i propri specifici fini e cambiarlo quando si desidera, semplicemente modificando un file di configurazione esterno, senza la necessit di cambiare il codice dell'applicazione. Ci sarebbe da riempire un libro discutendo di log4j. Si dar per scontato che si abbia familiarit con i concetti base e si affronteranno solo gli aspetti essenziali a riguardo dell'integrazione di log4j con Struts. Se non si ha ancora familiarit con log4j, un buon momento per acquisirla. Per dettagli pi approfonditi si pu scaricare o leggere la documentazione presso lURL http://jakarta.apache.org/log4j.

15.4.1 Integrazione di log4j con Struts


Per garantire che le librerie di log4j siano raggiungibili dalle proprie applicazioni si deve mettere il file JAR log4j nella directory WEB-INF/lib per ogni applicazione di cui si effettua il deploy. Il file non va messo all'interno della directory lib del container neanche se molteplici web application usano log4j. Se si installa a livello di container prima o poi si verificheranno numerosi problemi di ClassNotFoundException. Il web container, sulla base delle specifiche delle Servlet 2.3, carica tutti i file JAR nella directory WEB-INF/lib inclusa la libreria log4j. Completato questo passo iniziale, si liberi di usare log4j come implementazione di logging per il package Commons Logging. Si ricordi che la configurazione:di log4j totalmente indipendente dalla configurazione dell'implementazione di logging del package Commons Logging. Dovete ancora imparare come configurare log4j (se l'implementazione che avete scelto) e seguire i passi richiesti dal package log4j.

15.4.2 Che cosa sono i logger?


In log4j la classe principale org.apache.log4j.Logger. Escluso il procedimento di configurazione, la maggior parte delle funzionalit viene svolta da questa classe. Nelle prime versioni di log4j, era la classe org.apache.log4j.Category che implementava le stesse funzionalit. Ai fini della retrocompatibiljt, la classe Logger estende la classe Category. Anche se i metodi nella classe Category non sono stati ancora deprecati, si dovrebbe sempre usare la classe Logger. In futuro, la classe Category verr rimossa dalla libreria. Quando viene usato con Struts, la maggior parte delle classi e delle interfacce di log4j (diverse dagli aspetti di configurazione di log4j) inglobata nella API Commons Logging.

15.4.3 Configurare gli appender di log4j


Con log4j, si possono mandare messaggi di log verso varie destinazioni, indicate con il termine appender. log4j gi predisposto per i seguenti appender: Console Appender File Appender Socket Appender Java Message Service (JMS) Appender NT Event Logger Appender Unix Syslog Appender Null Appender SMTP Appender Telnet Appender Asynchronous Appender log4j permette di stabilire uno o pi appender per un ambiente di logging. Si possono addirittura inviare messaggi a determinati appender a seconda di diverse condizioni. L'altra utile caratteristica dell'architettura degli appender sta nel fatto che, se serve un particolare appender diverso da quelli predefiniti, possibile crearne uno estendendo la classe org.apache.log4j.AppenderSkeleton.

15.4.4 Livelli di log in log4j


A un messaggio di log di log4j pu essere assegnato un livello di priorit tra cinque diversi livelli. I livelli permettono di impostare un limite per un logger particolare scartando lutti i messaggi di log che non lo raggiungono. I cinque livelli sono: DEBUG INFO WARN ERROR FATAL Una delle prime versioni di log4j definiva anche i livelli OFF e ALL; tuttavia sembra siano stati deprecati e dovrebbero essere evitati. Se si imposta il livello a DEBUG, si otterranno gli stessi risultati di ALL, e OFF non proprio necessario dal momento che si pu sempre scegliere semplicemente di non configurare un appender per l'ambito in questione, disabilitando di conseguenza il logging. Esiste un effetto a cascata che rende possibile il logging di messaggi con livello uguale o superiore al limite impostato. Per esempio, se viene configurato un limite WARN saranno registrati solo messaggi relativi ai livelli WARN, ERROR o FATAL.

15.4.5 Inizializzazione di log4j


Molte sono le propriet configurabili in log4j. Di fatto, log4j flessibile al punto che non possibile discutere di tutte le opzioni di configurazione in questa sede. La migliore fonte di informazioni il manuale di log4j, disponibile all'URL http://jakarta.apache.org/log4j/docs/documentation.html e compreso nella distribuzione di log4j. Dal momento che log4j non d niente per scontato, necessaria una configurazione personalizzata. In altre parole, non ci sono appender predefiniti pronti per l'uso. Le propriet di configurazione per l'ambiente di log4j possono essere inizializzate in diversi modi. Ci si concentrer su due di questi, distinti ma entrambi praticabili.

15.4.5.1 Inizializzazione tramite il file di propriet di log4j


Il primo modo riguarda la creazione di un file log4j.properties che contenga i necessari elementi di configurazione per le nostre necessit di logging. Questo file deve seguire le linee guida del formato java.util.Properties. Una di queste linee guida stabilisce che il for mato debba essere key=value. L'Esempio 15-5 illustra un file di configurazione di log4j molto semplice che invia messaggi di livello INFO alla console tramite org.apache.log4j.ConsoleAppender.
Esempio 15-5. Un file log4j.properties

# A basic log4j configuration file that creates a single console appender # Create a single console appender that logs INFO and higher log4j.rootLogger=INFO, stdout # ConFigura the stdout appender to go to the console log4j.appender.stdout=org.apache.log4j.ConsoleAppender # ConFigura the stdout appender to use the PatternLayout log4j.appender.stdout.layout=org.apache.log4j.PatternLayout # Pattern to output the caller's filename and line number. log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n

Il semplicissimo file di configurazione dell'Esempio 15-5 illustra l'impostazione di un solo appender, in questo caso ConsoleAppender, che dirige i messaggi di log aSystem.out. Questo file log4j.properties deve essere installato nella directory WEB-INF/classes in modo tale che log4j possa trovarlo e utilizzarlo per configurare l'ambiente di logging della web application. Se si hanno diversq web application, e possibile configurare un file log4j.properties per ciascuna di esse. II file di configurazione di log4j mostrato dell'Esempio 15-5 invia messaggi di log a una destinazione singola, la console. Tuttavia, si possono configurare diverse destinazioni per i file di log e perfino inviare messaggi ai certe destinazioni sulla base del livello di log prescelto. L'Esempio 15-6 illustra un semplice esempio con due appender.
Esempio 15-6. Un file di configurazione di log4j che usa due appender

# A sample log4j configuration file # Create two appenders, one called stdout and the other called rolling log4j.rootLogger=DEBUG, stdout, rolling # ConFigura the stdout appender to go to the console log4j.appender.stdout=org.apache.log4j.ConsoleAppender # ConFigura the stdout appender to use the PatternLayout log4j.appender.stdout.layout=org.apache.log4j.PatternLayout # Pattern to output the caller's filename and line number log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n # ConFigura the rolling appender to be a RollingFileAppender log4j.appender.rolling=org.apache.log4j.RollingFileAppender # ConFigura the name of the logout for the rolling appender log4j.appender.rolling.File=output.log # Set up the maximum size of the rolling log file log4j.appender.rolling.MaxFileSize=100KB # Keep one backup file of the rolling appender log4j.appender.rolling.MaxBackupIndex=1 # ConFigura the layout pattern and conversion pattern for the rolling appender log4j.appender.rolling.layout=org.apache.log4j.PatternLayout log4j.appender.rolling.layout.ConversionPattern=%d{ABSOLUTE} - %p %c - %m%n
II file di configurazione di log4j dell'Esempio 15-6 crea un appender che invia messaggi alla console, come gi nell'Esempio 15-5e un altro appender che li invia al file output.log. Di nuovo ripetiamo che non si vogliono trattare tutte le possibilit di logging di log4j maggiori informazioni possono essere reperite sul sito di log4j.

15.4.5.2 Inizializzazione tramite un file XML


Nel secondo approccio, il problema viene risolto tramite la creazione di un file XML. L'Esempio 15-7 illustra un file XML che svolge lo stesso compito dell'Esempio 15-5.
Esempio 15-7. Un file di configurazione di log4j che usa il formato XML

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'> <appender name="stdout" class="org.apache.log4j.ConsoleAppender"> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%5p [%t] (%F:%L) - %m%n"/> </layout> </appender>

<root> <priority value ="INFO" /> <appender-ref ref="stdout" /> </root> </log4j:configuration>
Al solito, si porr il file XML nella directory WEB-INF/classes come gi per il file log4j. properties. Tuttavia si dovr impostare la propriet di sistema log4j.configuration in conformit con il nome del file, in maniera che log4j possa sapere quale file caricare. Non ci sono nomi di file predefiniti per il formato XML. Ci non stato necessario con il file di propriet dal momento che il nome log4j.properties parte dell'inizializzazione prestabilita di log4j. Se tale file viene localizzato in qualsiasi punto del classpath, esso verr usato per la configurazione dell'ambiente di log4j. Ci sono molti modi per impostare le propriet di configurazione di log4j.confguration e i vari container possono provvedere metodi alternativi. In Tomcat 4.0, ad esempio, si pu

In Tomcat Version 4.0, for Esempio, you can set a variable called CATALINA_OPTS in the catalina.bat file to provide this information to the logging environment. For Esempio:

set CATALINA_OPTS=-Dlog4j.configuration=log4j.xml
When you start up Tomcat, the log4j environment will then be able to locate the XML configuration file. Other containers may provide alternate methods for setting the value, but you can always set the value on the Java command line as a system property. You will probably need to modify the container's startup script using this approach, however:

java -Dlog4j.configuration=log4j.xml
If the log4j environment is unable to find a valid configuration file, either properties-based or XML-based, you will see something similar to the following message when you first attempt to initialize the logging environment:

log4j:WARN No appenders could be found for logger XXX. log4j:WARN Please initialize the log4j system properly. The XXX in the first message will actually show the name of the logger for which no appenders were conFigurad.

15.4.5.3 Specifying a relative versus an absolute path


When you use a system property to conFigura the log4j configuration file within a web application, the file is relative to the web application by default. The following Esempio tells log4j to search for a file called log4j.xml in the WEBINF/classes directory for the web application:

java -Dlog4j.configuration=log4j.xml

Most containers use a separate class loader for each web application, and some containers may not allow the web applications to know about classes or JARs loaded by the container itself. However, if you need to use an absolute path, you can specify one like this:

java -Dlog4j.configuration=file:/c:/dev/env/log4j.xml

Be careful when using an absolute pathbecause the configuration file is not relative to a web application, all web applications will share the same one. Generally speaking, specifying a relative path is much more flexible than using an absolute path because you can't always guarantee the directory structure of all your target environments.

15.4.5.4 Synchronization issues


There's one more issue that you should be aware of when logging to resources such as filesystems. Even though log4j is able to handle multiple client threads using the same appender (because all threads are synchronized), if you have multiple appenders writing to the same resource or file, you will have unpredicTabella results. In other words, there is no synchronization between appenders, even within the same JVM. This really has nothing to do with a deficiency in the log4j design; it's just a case of not being able to easily synchronize multiple writers to a resource. The easiest way to solve this problem is to ensure that if you have multiple appenders or web applications logging to the filesystem, you don't allow them to log to the same file. If you do, you will probably experience synchronization-related issues.

15.4.6 Log File Rollover


In a normal production environment, log files can grow quite large if not managed properly. If the logging threshold is set to DEBUG or INFO or if the files are not purged from time to time, the files can grow without bounds.

It's a good idea to periodically back up the log files and start again with an empty log file. For some production environments, this "rollover" period may be nightly; others may only need to perform this routine weekly. Unless you can shut down the application while you back up the log files, it's very cumbersome to back them up manually. Fortunately, log4j provides a type of appender that automatically swaps the log file out with a new one while at the same time maintaining a backup of the old log file. The org.apache.log4j.DailyRollingFileAppender class provides the ability to log to a file and roll over or back up the log file while the application is still running. You can also specify the rollover frequency and the date pattern that will be used for the backup names. Having this functionality available out of the box makes log4j invaluable to any application that needs to roll over log files at user-defined intervals.

15.5 Using Commons Logging in JSP Pages


We've covered how to use the Commons Logging API within Java components, but we haven't yet discussed how to use them in JSP pages. There are a number of ways to use the library within JSP pages, but we'll just cover the two easiest approaches here. The first approach is to use the same three steps defined earlier, this time performing them in the JSP page itself: 1. 2. 3. Import the Commons Log and LogFactory classes. Define and initialize a logger variable for the page. Start logging.

Esempio 15-8 illustrates this approach in a basic JSP page.


Esempio 15-8. Uso di Commons Logging in una pagina JSP

<%@ page import="org.apache.commons.logging.Log" %> <%@ page import="org.apache.commons.logging.LogFactory" %> <%-- Get a reference to the logger for this class --%> <% Log logger = LogFactory.getLog( this.getClass( ) ); %> <% logger.debug( "This is a debug message from a jsp" ); %> <html> <head> <title>Using Commons Logging in a JSP page</title> </head> <body> <% logger.info( "This is another log message in the jsp" ); %> There should be two log messages in the log file. </body> </html>
L'ambiente Commons Logging deve essere configurato bene perch questo funzioni proprio come quando lo si impiega per le classi Java. Qualsiasi pagina JSP che sia parte della web application potr usare le utilit di logging. Dal momento che molti container usano un differente class loader per ciascuna web application, qualsiasi pagina JSP che non faccia parte della web application configurata per log4j potrebbe non essere in grado di usare le utility di logging. Anche se l'Esempio 15-8 mostra come possa essere facile l'impiego dell'APl Commons Logging nelle pagine JSP, ci sono alcuni problemi legati a questo approccio. Il pi evidente che le proprie pagine JSP conferanno codice Java. Come gi detto pi volte. alcuni sviluppatori la considerano una prassi da evitare. Per fortuna c' un altro modo. La libreria di tag Jakarta Taglibs contiene una libreria di tag personalizzati JSP progettata per log4j e chiamata Log. Per informazioni e per scaricare questa libreria si veda l'URL http://jakarta.apache.org/taglibs/doc/log-doc/intro.html. Proprio come una comune libreria di tale, anche questa va configurata per la propria web application, mettendo il file log.tld nella directory WEB-INF e il file JAR Log nella directory WEB-INF/lib. Si dovr anche aggiungere il corretto elemento taglib al descrittore di deploy della propria web application, come per le librerie di tag di Struts:

<taglib> <taglib-uri>/WEB-INF/log.tld</taglib-uri> <taglib-location>/WEB-INF/log.tld</taglib-location> </taglib>

Una volta che il tag personalizzato Log installato e configurato, pu essere impiegato nelle proprie pagine JSP come da Esempio 15-9.
Esempio 15-9. Una pagina JSP di esempio con l'uso del tag Log

<%@ taglib uri="/WEB-INF/log.tld" prefix="logger" %> <logger:debug message="This is a debug message from a jsp using the Log tag" /> <html> <head> <title>Using the Log Tag in a JSP page</title> </head> <body> <logger:info message="This is another message using the log4j tag" /> There should be two log messages in the log4j log file. </body> </html>
Si noti che non c' traccia di codice Java nell'Esempio 15-9. Tale approccio, anche se non troppo differente dall'Esempio 15-8, risulta molto pi pulito con pagine JSP grandi pi e complesse. Ancora, il tag Log offre la funzionalit di dumping degli oggetti conservati nello scope della pagina, della sessione o dell'applicazione. Ci utilissimo quando si svolge il debug della propria web application. L' Esempio 15-10 illustra come sia facile.
Esempio 15-10. Uso della libreria di tag Log per il dumping di informazioni

<%@ taglib uri="/WEB-INF/log.tld" prefix="logger" %> <html> <head> <title>Using the Log Tag in a JSP page</title> </head> <body> <logger:dump <logger:dump <logger:dump <logger:dump scope="page"/> scope="request"/> scope="session"/> scope="application"/>

The page, request, session, and application dumps should be in the log file. </body> </html>
Sfortunatamente il tag Log non funziona ancora con il package Commons Logging. Dipende dall'implementazione di log4j. Tuttavia sempre un tag prezioso se si vuole ottenere un'informazione pi accurata per il debug delle JSP.

15.6 L'impatto di log4j sulle prestazioni


L'impatto potenziale sulle prestazioni del logging di un'applicazione significativo. Per un tipico ambiente di produzione possono essere registrati migliaia di messaggi al giorno per ogni applicazione a seconda del livello prescelto. Le conseguenze possono essere ancora peggiori se sono registrate le impronte della pila per errori nell'applicazione. La documentazione di log4j indica che stata data molta attenzione alla riduzione dell'impatto sulla performance di log4j. Niente tuttavia gratis e vi un costo da pagare per la generazione di messaggi di log. Il problema quindi: tale costo supera i benefici dati dalle informazioni aggiuntive di logging? Il costo in termini di tempo per la creazione di un messaggio di log tramite log4j dipende da diversi fattori: Il tipo di appender usato Il formato configurato per i messaggi di log4j Il tempo di costruzione del parametro per creare il messaggio di log La profondit della gerarchia del logger e la soglia di logging Il tipo di appender ha una relazione con il tempo speso a creare un messaggio di log. Il logging di un appender che usa SMTP ria una durata diversa dal logging su un filesystem. Su una macchina tipo con Java -1.3, il logging di un messaggio su filesystem prende da 20 a 300 microsecondi in base al livello di logging, che pu essere incrementato a seconda delle condizioni. Probabilmente l'impatto pi significativo del logging sta nelle informazioni di cui si tenta di effettuare il logging e nel formato di tali informazioni. log4j usa una sottoclasse di org.apache.log4j.Layout per la determinazione del formato del messaggio nella destinazione. La classe SimpleLayout la pi rapida, dato che registra solo il livello di log e il messaggio. D'altro canto, la classe PatternLayout permette una maggiore flessibilit per quanto riguarda il formato, dato che si pu registrare qualsiasi tipo di informazioni, inclusa la classe che crea il messaggio. Tuttavia tutta questa flessibilit incide in termini di prestazioni. La documentazione di log4j contiene parecchie indicazioni in JavaDocs per la classe PatternLayout che indicano come le prestazioni possano degradarsi gravemente se sono scritte determinate informazioni con il messaggio di log. Si deve essere molto particolareggiati a;riguardo delle informazioni di cui si necessita nel messaggio di log. Per i risultati voluti potrebbero bastare classe, livello e messaggio. Le restanti informazioni possono anche essere piacevoli da leggere ma superflue. La stessa creazione del messaggio che viene inserito nella definizione di log ha un costo in termini di tempo e prestazioni. Se si crea un messaggio in questo modo:

logger.debug("Session user.getFullName());

id

is:

"

sessId

"

for

user

"

si perde molto tempo per il logging. log4j non ha a che fare con questo; piuttosto si tratta del costo associato all'invocazione, di metodi Java e alla concatenazione di stringhe prima della generazione della reale definizione di log. Infine, come gi riportato, i logger possono essere connessi in relazioni tipo padre/figlio. Dal momento che il livello di logging pu essere assegnato a qualsiasi livello, l'ambiente di log4j dovrebbe risalire la gerarchia per determinare se il messaggio di log pu essere scritto. Se questa gerarchia molto profonda, il tempo speso va a detrimento della creazione del messaggio di log. Solitamente, in fase di sviluppo della propria applicazione i costi di tempo del logging non sono tanto importanti. La prestazione del meccanismo di logging non dovrebbe avere troppa importanza durante lo sviluppo e il debug dell'applicazione; di fatto si desidera in questi stadi quanto pi logging possibile. In test o produzione per necessario abbassare notevolmente il livello di logging. Con altri linguaggi, il codice che si occupa del logging potrebbe non essere neanche presente nei file binari, ma venire rimosso per rendere i file binari pi "leggeri" e per non fare visualizzare i messaggi di log. Questo non necessario con log4j, dal momento che, con opportuni cambiamenti al file di configurazione, si pu controllare quanto logging effettuare (ma nei file binari ci saranno comunque le indicazioni per il logging)

15.7 Estensioni di terze parti per log4j


Ci sono molte utili estensioni di terze parti per log4j. La maggior parte sono gratis e/o open source. Per esempio, esistono molte applicazioni grafiche basate su Swing che permettono la visualizzazione e il filtraggio dinamico dei log, l'ideale per amministratori di sistema in un ambiente di produzione.

Ci sono anche diversi altri tipi di appender creati da altri sviluppatori che potrebbero tornare utili. Si possono trovare queste estensioni di terze parti in http://jakarta.apache.org/log4j/docs/download.html. Vale davvero la pena di dare un'occhiata a quello che si trova in questo URL. La lista di estensioni disponibili in continua crescita.

15.8 L'API Logging di Java 1.4


Anche se non usate Java 1.4, probabilmente ne avete sentito parlare. Una delle nuove caratteristiche lAPI di logging ora inclusa nelle librerie di base. Ci si pu chiedere quale sia la differenza tra log4je questa nuova libreria e perch usarla. Molte sono le analogie tra log4j e l'implementazione di logging in Java 1.4. Ma ci sono anche alcune grosse differenze. Esamineremo per prime le somiglianze. Entrambe le implementazioni usano un namespace gerarchico per i logger, che permette di configurarli in maniera allineata nella struttura dei package anche se non l'unico modo di strutturare i logger. Entrambe, poi, prevedono livelli o priorit multiple. L'implementazione 1.4 contiene per alcuni livelli in pi di log4j, anche se non si usano spesso perch a granularit finissima. Le differenze tra le due implementazioni non sono di solito cos grandi da far perdere delle funzionalit importanti. Tuttavia sembra che log4j offra pi funzionalit per chi ne ha davvero bisogno. Pi importante ancora, log4j funziona con Java dalla versione 1.1 in su, mentre l'implementazione di logging 1.4 funziona solo con la Java 1.4. Si parlato di renderla compatibile con le versioni precedenti, ma ci non si ancora verificato e probabilmente non accadr mai. Inoltre ci sono un numero di appender migliori per log4j rispetto a Java 1.4, anche se quest'ultima include i pi importanti. Sia che si usi log4j o Java 1.4, ci si deve comunque basare sulla API Commons Logging per proteggere la propria applicazione dagli inevitabili cambiamenti. Accoppiare la propria applicazione a qualsiasi altra implementazione di terze parti non raccomandabile in termini di logging o altri aspetti.

CAPITOLO 16

Packaging delle applicazioni Struts

Contrariamente a quello che pensano molti sviluppatori, progettare e compilare un'applicazione sono solo la met dell'opera. Terminata la sua realizzazione, l'applicazione deve ancora essere confezionata in package e ne deve essere effettuato il deploy nell'ambiente di base. Molte applicazioni sono scritte per essere inserite nell'ambiente di produzione di un cliente. Per altre, l'ambiente di destinazione quello interno. Fortunatamente, per le web application il lavoro da compiere in entrambi i casi molto simile. I deploy interni possono essere meno formali e meno stressanti, ma in ogni caso vanno affrontati con seriet, in maniera efficiente, e professionale. Un cattivo deploy lascia una cattiva impressione sia al cliente vero e proprio sia a un altro dipartimento della nostra organizzazione. Formalizzare il processo ali packaging e deploy permette agli sviluppatori di concentrarsi sulla scrittura di un'applicazione di qualit e di risparmiare tempo per capire se l'applicazione si installer e funzioner correttamente una volta realizzata. Questo capitolo tratta i metodi migliori per il packaging e il deploy di un'applicazione basata su Struts, includendo l'automazione del processo di compilazione. Sar dato speciale rilievo ad Ant, il tool di compilazione basato su Java disponibile in Jakarta.

16.1 Package: s o no?


Le applicazioni necessitano di un deploy per funzionare. Non ha senso un'applicazione che venga sviluppata e di cui non venga mai fatto il deploy, anche se questo capita pi volte di quanto si pensi. Se ovvia la necessit del deploy, che dire del packaging? Ogni applicazione di Struts deve davvero essere "impacchettata" prima del deploy? La risposta, in breve, affermativa; nel resto del capitolo si dar una risposta pi lunga. Prima di entrare nei dettagli, si definir con esattezza cosa significano packaging e deploy nel contesto di web application basate su Struts. Anche se i due concetti sono strettamente associati, non sono la stessa cosa.

16.1.1 Packaging
L'operazione di packaging di una applicazione basata su Struts include la raccolta di tutti i file e delle risorse che fanno parte dell'applicazione e la loro collocazione in una struttura logica. In un'applicazione Struts sono di solito inclusi molti tipi differenti di risorse necessarie all'applicazione, quali ad esempio:
files HTML e/o XML Immagini e file audio/video Stylesheets JavaServer Pages File di propriet Classi di utilit Java File di configurazione Classi Action e ActionForm JAR di terze parti Durante la fase di progettazione si deve pensare bene a come strutturare l'applicazione. Non si deve certo eccedere nell'immaginare ogni dettaglio, ma si devono comprendere bene i requisiti dell'ambiente di destinazione e quanto questo possa influenzare la strategia di packaging e deploy. Si dovrebbero prendere decisioni sul package principale e sulla struttura della directory dell'applicazione prima della scrittura: ci pu ovviare ai normali fraintendimenti tra sviluppatori e ridurre il numero di file di risorse ridondanti.

16.1.2 Deploy
Come gi detto, il packaging e il deploy sono strettamente associati ma prevedono compiti diversi: il packaging determina dove risiederanno e come verranno ordinati i file delle risorse all'interno della struttura del package; il deploy si occupa del modo in cui le web application vengono installate e configurate all'interno di un web container. Esistono due modalit per effettuare il deploy di una web application all'interno di un container. Il primo modo prevede il deploy in un file di tipo web archive (WAR). La maggior parte dei web container installano un file WAR e lo mettono a disposizione degli utenti, spesso senza richiedere un riavvio del container. Questo un modo preferibile, dal momento che una volta che il file WAR composto nel modo corretto il resto del lavoro effettuato dal container. Uno degli svantaggi sta nel fatto che, per qualsiasi cambiamento, il file .war deve essere ricreato e ne deve essere rieseguito il deploy. In seguito si discuter di come effettuare il deploy delle applicazioni Struts come file WAR. La seconda modalit richiede pi lavoro da parte dello sviluppatore. Rimane la necessit di generare un file WAR con la propria web application ma occorre anche lo "spacchettamento" manuale del file WAR in una directory del container. L'esatta collocazione di una directory dipende dal container. In Tomcat e Resin, per esempio, la directory predefinita per le web application webapps. In WebLogic si devono decomprimere i file WAR sotto la directory applications. Tutti questi container permettono di specificare directory di installazione alternative. Quando si decomprime un file WAR, si deve creare una directory nella quale effettuare "l'apertura del pacchetto" contenente i file. Il nome della directory di solito quello della web application. Per fare un esempio, se la web application fosse Storefront e volessimo installarla sotto Tomcat, si potrebbe creare una directory chiamata storefront nella directory webapps e l estrarre i file dall'archivio WAR. Il file WAR in questione non dovrebbe contenere la directory storefront come parte della propria struttura. Questo approccio di deploy si definire come exploded directory format (formato di directory esplosa), dal momento che il file WAR aperto nella sua struttura originale all'interno del container. I vantaggi di quest'approccio stanno nel fatto che, se vi sono dei cambiamenti, devono essere sostituiti solo i file necessari e non occorre un nuovo deploy: modalit comoda in fase di sviluppo o debug ma sconsigliata per la produzione. Quando si effettua il deploy di una web application in produzione consigliabile lasciare il file WAR "impacchettato", perch cos si evita pi facilmente di creare confusione nei file. In questo modo, infatti, deve essere sostituito l'intero file WAR, e quindi non c' possibilit di perdita di sincronizzazione tra la versione di un singolo file e il resto dell'applicazione.

16.2 Decidere il packaging dell'applicazione


Le applicazioni basate su Struts sono web application, per cui la maggior parte delle risposte sul modo in cui sistemarle in package stanno nelle specifiche delle Servlet e delle JSP. Una web application deve seguire un insieme di linee guida e convenzioni mollo stringenti che ne rendano possibile, la compatibilit con pi web container. Fortunatamente, il packaging della propria applicazione nel formato WAR risolve molti problemi, sebbene ne rimangano alcuni da risolvere. Per esempio, si tratter di decidere dove andranno messe le pagine JSP e se inserire insieme classi Action e ActionForm oppure creare package indipendenti per action e form. Anche se ci sono modalit migliori di altre, la soluzione di questi problemi dipende dalle esigenze e dalla prassi della propria organizzazione, per cui non esistono regole assolute.

16.2.1 Gestione del namespace


Un namespace semplicemente un insieme di nomi che possono essere associati oppure no. Un namespace di solito viene impiegato per prevenire conflitti o collisioni con entit simili e per permettere ai client di effettuare riferimenti a tali entit tramite nomi logici. Nello sviluppo del software, un namespace una modalit di ordinamento di classi, interfacce e altri tipi di informazioni in una struttura gerarchica. Ci sono molti esempi di namespace nell'industria dei computer. Le modalit di funzionamento del Domain Name System (DNS) di Internet un esempio di namespace. Nel dominio oreilly.com, per esempio, gli indirizzi IP sono tra loro legati gerarchicamente. Tutto questo allo scopo di evitare conflitti tra IP. Un altro esempio, pi strettamente legato allo sviluppo del software, il namespace usato in JNDI. Ma il modo di utilizzo pi familiare agli sviluppatori Java quello per creare classi e interfacce che risiedono in un package. Come si sapr le applicazioni Java sono organizzate in una serie di package. Perfino quando non si specifica un package esplicitamente, si fa sempre riferimento a uno di questi.[1] Lo scopo di un package quello di prevenire conflitti tra nomi e

di contribuire all'identificazione di entit come le classi Java. Quando estendiamo Struts con le nostre classi e interfacce, occorre decidere come effettuare al meglio il packaging di questi componenti per la nostra applicazione. Non tutte le applicazioni conterranno le stesse classi e interfacce.
[1]

Qualsiasi classe Java che non abbia un package dichiarato nel file sorgente viene considerata come appartenente al package predefinito.

16.2.2 Collocazione delle JSP


Per molti sviluppatori pu sembrare una domanda banale dove posizionare le JSP. Anche se si deve decidere a quale directory appartiene una particolare pagina JSP, la questione spesso si esaurisce qui. Tuttavia in certe situazioni necessario un maggior controllo su chi e come debba aver accesso alle JSP. Si suggerisce di inserire le JSP all'interno di una sottodirectory della directory WEB-INF. Vi un triplice scopo: Costringere tutte le request ad attraversare la classe ActionServlet. Impedire che gli utenti possano usare un segnalibro con la pagina in oggetto. Proteggere la directory WEB-INF ed evitare che le pagine JSP possano essere chiamate direttamente. Questo approccio alternativo ha una certa popolarit anche se al momento non tutte le servlet lo supportano. Le specifiche Servlet 2.3 sembrano indicarne la fattibilit ma alcuni produttori non concordano sull'interpretazione delle specifiche; per esempio, nel passato gli sviluppatori WebLogic interpretarono in modo differente la sezione SRV.6.5 delle specifiche Servlet 2.3. WebLogic restituir un errore 404 o 500 se si tenta di accedere a una pagina JSP sotto la directory WEB-INF (anche se si dice che prossimamente WebLogic render quest'opzione possibile nelle future versioni del loro container). Anche se alcuni container non supportano l'approccio precedentemente descritto, potrebbe non essere necessario inserire le JSP all'interno della directory WEB-INF. Se si invocano action Struts solo dalla propria web application e non si collegano a pagine JSP direttamente (come richiesto da Struts 1.1), quest'approccio non dar vantaggi alle proprie applicazioni. Ci sono altre alternative: ad esempio, si potrebbe usare l'elemento security-constraint nel file web.xml. Si creino solo le directory richieste per le pagine JSP che si intendono proteggere. In Storefront poniamo, ad esempio, di non volere che gli utenti si colleghino direttamente alle pagine sotto la directory order. Potrebbe essere allora aggiunto un elemento security-constraint:

<security-constraint> <web-resource-collection> <web-resource-name>SecureOrderJSP</web-resource-name> <description>Protect the Order JSP Pages </description> <url-pattern>/order/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name></role-name> </auth-constraint> </security-constraint>
La Figura 16-1 mostra cosa succede se un client cerca di accedere direttamente alla pagina JSP nella directory order.

Figura 16-1. L'errore 500 che si verifica tentando l'accesso a una JSP protetta

Quando si aggiunge l'elemento security-constraint al file web.xml per l'applicazione Storefront, nessun utente non autenticato pu accedere alla risorsa specificata. In questo esempio, l'elemento role-name stato intenzionalmente lasciato vuoto per cui non incontrer mai un vero role. Potremmo anche aver specificato che solo gli utenti con un role di tipo "admin" possono accedere alle pagine, senza poi mai concedere tale ruolo a un utente reale. Il container potrebbe ancora accedere a queste pagine tramite forward e include. Occorre fare attenzione a ci che si mette nell'elemento url-pattern. Se si hanno risorse come immagini in sottodirectory, esse non saranno disponibili quando il browser prova a scaricarle. L'uso del pattern /order/* significa che non pu essere richiesto direttamente dal client niente che stia sotto la directory order, incluse le immagini nelle sottodirectory, e il browser avr difficolt a ricevere le immagini da un messaggio di response HTTP.

16.2.3 Precompilazione di JavaServer Pages


Se si usano le JavaServer Pages come tecnologia di presentazione delle applicazioni Struts si conoscer bene il processo di compilazione che il container opera per conto dell'applicazione. Quando il web container riceve una request per una risorsa con l'estensione jsp, viene selezionato al fine di elaborare la richiesta il container JSP. Se questa la prima request di una pagina JSP o se il timestamp della JSP pi recente di quello della classe servlet esistente, la pagina sar normalmente compilata. [2]
La maggior parte dei container ha la possibilit di disabilitare il riconoscimento di cambiamenti per cui le pagine che hanno subito cambiamenti non saranno ricompilate. Quest'opzione di solito utilizzata in ambiti di produzione dal momento che non dovrebbero esservi introdotti cambiamenti incrementali che potrebbero anche costituire un rischio per la sicurezza.
[2]

Il container JSP usa un processo in due tempi. Per prima cosa la pagina JSP tradotta dal codice sorgente JSP in un file sorgente Java. Ogni container potrebbe avere una propria implementazione per svolgere questo compito. Il secondo passo prende il file sorgente Java e lo compila in un file classe di servlet usando qualsiasi compilatore Java trovi installato per il JSP container. Anche se la "compilazione sul momento" una buona caratteristica mentre si opera il debug o lo sviluppo di un'applicazione, potrebbe esserci l'intenzione di non rendere disponibile il sorgente JSP in un package di deploy. Questo un problema di sicurezza e licenze: anche se le pagine JSP contengono solo logica di presentazione, si tratta pur sempre di propriet intellettuale. Se il caso questo, si possono precompilare le pagine JSP e non dare il sorgente delle JSP ai clienti. Si dovr distribuire solo il bytecode, rendendo difficile la scoperta del codice sorgente. Una volta che le pagine JSP siano state compilate in file di classi Java si potrebbe anche "offuscarle" rendendo ancora pi difficile la lettura attraverso utilit di decompilazione. La precompilazione delle pagine JSP mette a disposizione pi opzioni per trattare simili problemi.

dome sempre, ci sono alcuni svantaggi nella precompilazione di pagine JSP. Da un lato si perde la possibilit di aggiornamento o di rimediare velocemente a un problema. Invece di porre semplicemente un nuovo file JSP nel server lasciando che il container lo compili al momento dell'accesso, si deve effettuare manualmente la ricompilazione e effettuare il deploy della classe servlet. Un altro svantaggio consiste nel fatto che, quando alcuni container rilevano un problema con una sola JSP, non ne elaborano pi altre. Quando ci accade, si deve accertare che ogni pagina JSP si compili correttamente prima che il container effettui il deploy della web application. Gli sviluppatori potrebbero considerarlo un vantaggio, ma in ambienti di produzione potrebbe rendere difficoltosa una correzione veloce di un bug. Sfortunatamente non esiste un modo standard di precompilare le pagine JSP. Ogni container ha un suo modo per svolgere quest'operazione. Di seguito si descrive brevemente la precompilazione effettuata da tre container: Tomcat 4.0. Resin 2.0 e WebLogic 7.0.

16.2.3.1 Precompilazione di pagine JSP con Tomcat


Jasper, il motore delle JSP in Tomcat, offre un'implementazione di riferimento per le ultime specifiche. Viene inclusa assieme a Catalina, il servlet engine di riferimento per le ultime specifiche delle Servlet all'interno di Tomcat. Il compilatore JSP-to-Java disponibile come programma separalo che pu essere eseguito da linea di comando. Il suo compito sta nel tradurre una pagina JSP in un file sorgente Java. Da quel punto in poi un compilatore standard Java pu convertire il codice sorgente in bytecode. Il programma, jspc.bat (jspc.sh, a seconda della piattaforma) si trova nella directory bin. di installazione di Tomcat. Possono essere impostate molte opzioni per configurare qualsiasi cosa, dalla directory di output al nome del package del package Java. C' anche un'opzione XML che pu essere aggiunta al file web.xml per ogni pagina JSP che viene precompilata. Si pu elaborare una pagina alla volta o specificare l'intera web application.

16.2.3.2 Precompilazione di pagine JSP con Resin


Per precompilare pagine JSP tramite Resin, si pu usare il comando httpd.exe dalla directory bin, in questo modo:

resin/bin/httpd -conf conf/resin.conf -compile <URL>


Come si vede, I'URL una risorsa JSP all'interno di una web application installata in Resin. Si pu anche usare la linea di comando in questo modo:

resin/bin/httpd -e <URL>
Con tale approccio, si pu compilare solo una JSP per volta, anche se non difficile creare uno script che compili l'intera web application. Nel prosieguo del capitolo verr discusso un altro modo molto pi facile che utilizza Ant.

16.2.3.3 Precompilazione di pagine JSP con WebLogic


Con WebLogic 6.0, si deve includere un; parametro context-param nel descrittore di deploy per ogni web application. In questo modo WebLogic compiler ogni JSP che sia parte della web application all'avvio dell'application server. Il seguente elemento context-param deve essere aggiunto al file web.xml:

<context-param> <param-name>weblogic.jsp.precompile</param-name> <param-value>true</param-value> </context-param>


WebLogic 6.0 non effettuer il deploy della web application se anche una sola JSP non riesce a essere compilata. Se viene rilevato un errore, la compilazione cesser e non avverr il deploy dell'applicazione. Si dovr correggere il problema e riavviare WebLogic per far ripartire il processo.

16.2.4 Packaging di risorse EJB con Struts


Se si comunica con gli EJB nello strato intermedio, potrebbe essere necessario inserire alcune delle risorse EJB nel package della web application. Dal momento che Io strato web agisce come client nei confronti del server EJB, sono richieste determinate risorse per connettersi e comunicare con i bean. Le interfacce home e remote dei bean, per esempio, devono essere incluse nel package nella directory classes oppure come file JAR nella directory lib della web application. Anche alcune classi JNDI devono essere incluse affinch i client possano raggiungere oggetti home e remoti. Le reali classi EJB non sono tuttavia richieste. Non sempre stato cos, ma

le recenti specifiche descrivono ora un meccanismo che permette a queste classi di essere scaricate automaticamente ogni volta che venga fatta una request usando una interfaccia remota EJB. Nella maggior parte dei casi sufficiente porre il file JAR del container EJB nella directory WEB-lNF/lib. Per esempio, nel caso si stia usando WebLogic 6.0 o successivo, si pu mettere il file weblogic.jar nello strato web dal momento che contiene tutte le necessarie risorse lato client.

16.3 Packaging di applicazioni come file WAR


Risulta molto comodo il packaging delle web application con il formato WAR. La struttura precisa e, dal momento che viene specificata con tanta attenzione, la portabilit attraverso web container differenti molto pi facile. I paragrafi successivi descriveranno come creare un package nel formato WAR.

16.3.1 Creazione del file WAR


Il primo passo per creare un package come file WAR sta nella creazione di una directory root. Generalmente la directory porter lo stesso nome della web application. Per l'esempio corrente, sar chiamata storefront. Dopo aver deciso come disporre le proprie pagine JSP e HTML nella propria applicazione, andranno disposte nella directory root appena creata, nelle giuste sottodirectory. Per l'esempio Storefront, la struttura delle directory potrebbe essere quella illustrata in Figura 16-2.

Figura 16-2. Struttura delle directory dell'applicazione Storefront

La Figura 16-2 mostra 11 sottodirectory, 8 delle quali contengono le JSP dell'applicazione. La directory images contiene immagini che sono utilizzate globalmente nell'applicazione, la directory stylesheets ne contiene i fogli stile e la directory include contiene file inclusi mediante include statici o dinamici. Il passo seguente sta nell'assicurarsi di avere una directory WEB-INF all'interno della directory root della propria web application. La directory WEB-INF contiene tutte le risorse usate internamente da un'applicazione. Per esempio, risiedono in questa directory i file TLD per le librerie dei tag personalizzati, come pure il descrittore di deploy o il file di configurazione di Strts. Nessuna risorsa all'interno di questa directory- pu essere resa pubblica esternamente all'applicazione. All'interno della directory WEB-INF si creitoo due sottodirectory: una chiamata classes e l'altra lib. La directory classes dovrebbe includere tutte le classi di utilit e servlet. La directory lib dovrebbe contenere invece i file JAR da cui dipende l'applicazione. Una volta che tutte le risorse per la web application sono state messe ciascuna nel posto giusto, si deve usare jar, il tool Java per l'archiviazione, per comprimere directory e file. Da linea di comando si entri nella directory- root della propria web application e si usi l'utilit jar. Si deve dare un'estensione .war il file nel seguente modo:

jar cvf storefront.war .

Dal momento che si passati alla directory root, la directory storefront non verr inclusa nel file WAR. Ci corretto, dal momento che una volta installata si vorr chiamare la directory root in un'altro modo. Quando si installa la web application, se si desidera decomprimere il file WAR, tutto quel che si deve fare creare una directory ed estrarre i file, in questo modo:

C:\tomcat\webapps>mkdir storefront C:\tomcat\webapps>cp storefront.war storefront C:\tomcat\webapps>cd storefront C:\tomcat\webapps\storefront>jar xf storefront.war


La collocazione in cui si deve espandere il file WAR dipende dal container in uso. Se si desidera distribuire il file WAR senza scompattarlo, si deve soltanto disporlo nel luogo appropriato del container. Non si dovr ricreare la directory storefront anche se magari si vuole cancellare la directory esistente quando si effettua il deploy di una nuova versione. Alcuni container non hanno buone funzionalit di rimozione per i file vecchi prima di effettuare il deploy di un file WAR aggiornato. Anche se la sezione 9.8 delle specifiche delle servlet 2.3 alquanto ambigua al proposito, i container pi recenti permettono di inserire un file WAR nuovo o di sostituire certi file senza far ripartire il server. In produzione esiste un pericolo nel lasciare questa caratteristica attiva; tuttavia molto apprezzabile in fase di sviluppo o di debug. Nei container che supportano questa caratteristica, c' di solito un modo per disabilitarla quando si effettua il deploy d una web application in produzione.

16.4 Compilazione di applicazioni con Ant


Ant il pi flessibile dei meccanismi per compilare ed effettuare il deploy delle proprie applicazioni Struts.[3] Questa sezione tratter dell'utilizzo di Ant per compilare e ed effettuare il packaging della propria web application.
[3]

Ant acronimo di Another Neat Tool, "un altro valido strumento".

16.4.1 Che cosa Ant?


Ant un tool indipendente dalla piattaforma che pu essere configurato per compilare i file sorgenti Java, realizzare i file JAR e WAR per il deploy, sottoporre a test il codice e creare la documentazione JavaDoc per un progetto. Ha anche altre funzioni e pu essere esteso per svolgere compiti definiti dallo sviluppatore. Ant simile all'utilit Unix make (o gmake in Linux e nmake in DOS/Windows). Queste utilit di tipo make sono state usate per molto tempo per gestire progetti nei linguaggi C e C++, ma t u t te dipendono dalla piattaforma, dal momento che utilizzano in sostanza comandi shell eseguiti dal sistema operativo sottostante. Diversamente da make, le regole di Ant (o task, per usare la terminologia di Ant) sono classi Java e funzionano su qualsiasi piattaforma supporti una JVM.

Brevissima storia di Ant


Ant fa parte del progetto Jakarta dal 1998 ed stato creato da James Duncan Davidson, che ha anche scritto il servlet container Tomcat originale. All'inizio Ant era solo un'utilit per la compilazione di Tomcat. Altri sviluppatori intuirono vantaggi rispetto a make. Ant fu quindi spostato nel suo progetto CVS e divent nel 2000 un progetto ufficiale Jakarta.

16.4.2 Installazione e configurazione di Ant


Ant pu essere scaricato dal sito http://jakarta.apache.org/ant/. Gli esempi presenti in questo volume sono stati collaudati con Ant 1.4, ma dovrebbero essere compatibili a partire da Ant 1.3. Scaricato il file binario in formato .zip (per Windows) o .tar.gz (per Unix), si scompatti il file nella directory prescelta. Si dovrebbe anche scaricare il file optional.jar 1.4.1 e installarlo nella directory ANT_HOME/lib. Anche se non usato per questi esempi, il file optional.jar ha molte caratteristiche che potranno risultare molto utili in futuro. Occorre sincerarsi che la directory ANT_HOME/bin sia presente nel proprio PATH di sistema. Potrebbe anche essere necessario aggiungere la variabile d'ambiente ANT_HOME che dovrebbe puntare alla directory di installazione di Ant. I binari di Ant possono determinare di solito quale sia ANT_HOME ma, se si dovesse incappare in un errore quando si lancia Ant, si provi a inserire Questa variabile d'ambiente. C' anche da stare attenti se si usa Ant sotto Windows 95/93:

non va installato in una directory con un nome di path lungo, dal momento che il file batch adoperato per eseguire l'installazione potrebbe non gestirlo correttamente. Si consulti, per maggiori ragguagli, la documentazione di Ant.

16.4.3 Come iniziare


Ant reads its build commands from an XML file. By default, it looks for a file called build.xml, but you can give the file any name by using the -buildfile <file> option when running Ant. From a command prompt, change directories to the base project directory, which in our Esempio is called storefront. In here, you should see the build.xml file. The Ant build file consists of a project that has zero or more targets, each of which consists of zero or more tasks. The project element is defined at the top of the build file:

<project name="storefront" default="war" basedir="."> The project is named storefront and the default target to execute is the war target. The default target is what gets
executed if you type ant at the command line without specifying a target. Because the project root directory is the same directory that the build.xml file is located in, "." is used to indicate the base directory property. The build directory structure for the Storefront application is shown in Figura 16-3.

Figura 16-3. La struttura di compilazione di Storefront

Un target ha la seguente forma:

<target name="dostuff"> <task1 param1="value1" param2="value2"/> <task2 param="value"/> ... </target>


Un target deve avere un nome e pu avere diversi altri attributi che determinano come e se il target viene eseguito. Il target dovrebbe contenere un numero di task maggiore o uguale a zero. Nel mondo di Ant, un task un'unit atomica. Ogni task legato a una classe Java che Ant esegue passando a questa qualsiasi argomento o sottoelemento definito nell'ambito del task. Ant pu essere esteso e permette di creare i propri task. I task inclusi con Ant sono sufficienti per gli esempi di questo libro e per Storefront. Se tuttavia si desidera creare un nuovo task, si pu creare una nuova definizione di task nel file build.xml usando il task taskdef per legare il nome del task ad una classe Java. La classe Java deve estendere il file org.apache.tools.ant.Task ed essere collocata nel classpath di Ant. Ci sono molte altre cose da fare la cui descrizione va oltre gli intenti di questo libro e che possono essere approfondite tramite la documentazione di Ant. Prima di avviare Ant, si devono cambiare alcune propriet nel file build.xml di Storefront perch si adattino al proprio ambiente di sviluppo:

<property name="webserver.home" value="c:/tomcat"/> <property name="webserver.deploy" value="${webserver.home}/webapps"/> <property name="servlet.jar" value="${webserver.home}/common/lib/servlet.jar"/>

Queste tre propriet definiscono dove sono situati il container delle servlet, la directory di deploy e le classi API delle servlet. Per prima cosa, la propriet webserver.home deve puntare alla directory root del container delle servlet. In questo caso, si usa Tomcat 4.0 come web server e servlet container. Il file build.xml di Storefront supporta molti altri container ma sono tutti commentati; si deve solo togliere il segno di commento a quello che si intende usare. La directory di deploy di Tomcat la directory webapps posta all'interno della directory root di Tomcat. Tomcat conserva le classi delle API delle servlet nel file common/lib/servlet.jar relativo alla directory root di Tomcat. In seguito, si deve definire il classpath che sar usato durante la compilazione della propria applicazione. Ant permette di associare un insieme di file con il nome di una propriet. Nel seguente frammento di build.xml, la lista di tutti i file JAR necessaria alla compilazione di Storefront e collegata alla proprietbuild.classpath:

<path id="build.classpath"> <pathelement location="${servlet.jar}"/> <pathelement location="${lib.dir}/commons-beanutils.jar"/> <pathelement location="${lib.dir}/commons-collections.jar"/> <pathelement location="${lib.dir}/commons-dbcp.jar"/> <pathelement location="${lib.dir}/commons-digester.jar"/> <pathelement location="${lib.dir}/commons-logging.jar"/> <pathelement location="${lib.dir}/commons-pool.jar"/> <pathelement location="${lib.dir}/commons-services.jar"/> <pathelement location="${lib.dir}/commons-validator.jar"/> <pathelement location="${lib.dir}/jdbc2_0-stdext.jar"/> <pathelement location="${lib.dir}/log4j.jar"/> <pathelement location="${lib.dir}/poolman.jar"/> <pathelement location="${lib.dir}/struts.jar"/> <pathelement location="${lib.dir}/tiles.jar"/> <pathelement path="${build.dir}"/> </path>
Potevano essere usate meno righe di elementi include per definire la propriet build.classpath; tuttavia molto pi chiaro elencare esplicitamente ogni file JAR usato durante il processo di compilazione per non aggiungere o omettere nulla che possa impedire un corretto svolgimento dell'operazione. La dereferenziazione del nome della propriet tramite la sintassi di Ant ${property} permette al task di impiegare la propriet build.classpath.

16.4.4 Compilazione di sorgenti Java


I sorgenti Java per Storefront vengono compilati tramite il task di Ant javac. Il target di compilazione, compile, dipende dal target prepare:

<target name="compile" depends="prepare"> <javac destdir="${build.dir}" deprecation="on"> <classpath refid="build.classpath"/> <src path="${src.dir}"/> </javac> </target>
Un target potrebbe dipendere da zero o pi target secondo la seguente sintassi:

<target name="final-jar" depends="jars, wars">


L'attributo depends permette di controllare, se specificato, l'ordine in cui verranno eseguiti i target di Ant. In questo caso il target compile non viene eseguito finch non terminato il target prepare:

<target name="prepare"> <tstamp/> <mkdir dir="${build.dir}"/> <mkdir dir="${dist.dir}/lib"/> </target> II target prepare genera valori di timestamp che possono essere convertiti in propriet e aggiunti ai prodotti della
compilazione come i file JAR e WAR. Per questo piccolo progetto, tuttavia, non si utilizzano timestamp. Il target prepare crea anche le sottodirectory necessarie per le classi Java e per i file WAR. Il target compile istruisce Ant affinch esegua il compilatore javac su tutti i file all'interno della directory con i sorgenti e a inviare tutti i file delle classi alla directory di compilazione. L'opzione deprecation attiva, per cui si otterr un messaggio dettagliato se si include un metodo deprecato in un file sorgente:

<target name="compile" depends="prepare"> <javac destdir="${build.dir}" deprecation="on"> <classpath refid="build.classpath"/>

<src path="${src.dir}"/> </javac> </target> Il task javac impiega la propriet build.classpath illustrata nella sezione precedente.

16.4.5 Uso di Ant per la creazione di un file WAR


Il task di Ant war crea il web archive. Di seguito illustrato il target war usato per creare il file:

<target name="war" depends="compile"> <echo>building war...</echo> <war warfile="${dist.dir}/lib/storefront.war" webxml="${web.dir}/WEB-INF/web.xml"> <fileset dir="${web.dir}"/> <classes dir="${build.dir}"/> <classes dir="${lib.dir}"> <include name="*.properties"/> <include name="poolman.xml"/> </classes> <lib dir="${lib.dir}"> <include name="*.jar"/> </lib> </war> </target>
Come visto in precedenza, il target war quello predefinito per il progetto. Questo significa che all'esecuzione di Ant da linea di comando senza un argomento target, viene eseguito il target war. Il target war viene eseguito solo se il target compile stato prima terminato con successo. Il task war richiede la definizione di un nome per il file WAR e la posizione del file web.xml. Tutti gli altri attributi sono facoltativi. Se si interessati a conoscerli, vi un elenco nella documentazione in linea di Ant (http://jakarta.apache.org/ant/manual/CoreTasks/war.html). Un'altra importante risorsa su Ant Ant: The Definitive Guide di Jesse Tilly e Eric Burke (O'Reilly). Gli elementi annidati informano il task war circa la collocazione dei contenuti del file WAR. L'elemento fileset definisce il contenuto web di base del file WAR. Questo elemento viene utilizzato per dichiarare dove sono collocati i file HTML, le JSP, le immagini e cos via. L'elemento classes punta ai file di classi Java che dovrebbero essere inclusi nella directory WEB-INF/classes del file WAR e l'elemento lib dichiara quali file debbano essere inclusi nella cartella WEBINF/lib. In Storefront, viene incluso tutto il contenuto della sottodirectory web. Le varie sottodirectory contengono tutte le risorse necessarie (HTML, JSP, immagini etc). Tutte le classi compilate nella sottodirectory build sono copiate nella directory del file WAR WEB-INF/classes insieme ai file di propriet. Tutti i JAR di terze parti nella sottodirectory lib sono copiati nella directory WEB-INF/lib del file WAR. Se la sottodirectorv lib contiene JAR che non si desidera includere, possibile usare il seguente codice:

<lib dir="${lib.dir}"> <include name="*.jar"/> <exclude name="dont_need.jar"/> </lib>


Cos verranno copiati tutti i JAR dalla directory lib alla directory del file WAR WEB-INF/lib eccetto dont_need.jar. L'ultima opzione quella che permette di includere qualsiasi JAR esplicitamente. Anche se appena pi ridondante, questo metodo non opera cambiamenti alla cartella lib se altri sviluppatori cominciano ad aggiungere file JAR in maniera indiscriminata. Risulta inoltre molto pi semplice controllare cosa sia effettivamente incluso nel file WAR.

16.4.6 Rimozione dei sottoprodotti della compilazione


I successivi due passi sono banali ma importanti. Il target clean elimina la directory build cancellando tutti i file di classi Java:

<target name="clean"> <delete dir="${build.dir}"/> </target> <target name="distclean">

<antcall target="clean"/> <delete dir="${dist.dir}"/> </target>


II target distclean ripristina lo stato originario della distribuzione, vale a dire che sono rimossi tutti i prodotti e sottoprodotti della compilazione, cos che tutto riportato allo stato in cui la distribuzione stata appena installata. Si noti che il target distclean invoca il target clean. Anche se non qui necessario, viene comunque dimostrata la potenza di Ant tramite l'invocazione del task antcall. Il task antcall potrebbe chiamare anche un target con argomenti, ma la spiegazione di ci esula dagli intenti di questo libro. Ci sono diversi plug-in che permettono di usare Ant all'interno del proprio specifico IDE. Per esempio, il plug-in AntRunner permette di usare Ant con JBuilder. Per questi e altri plug-in. si veda all'URL http://jakarta.apache. org/ant/external.html..

16.5 Ambienti automatizzati di compilazione


Una volta che si sia messo insieme un soddisfacente ambiente di compilazione, si potrebbe proseguire e cercare di renderlo automatico. Questo sta a significare che non necessario l'intervento umano per creare nuove distribuzioni. Di sovente la frequenza di successive nuove build di package aumenta verso la fine del periodo di sviluppo. Si possono sempre effettuare a mano le operazioni necessarie, ma un'automatizzazione costituisce un approccio molto efficiente e comodo. Esistono due meccanismi temporizzati che lo permettono, a seconda del proprio ambiente di esecuzione. In Unix si pu usare il daemon cron e sotto Windows si pu usare il Task Scheduler.

16.5.1 Uso di cron per l'invocazione di Ant


cron un programma che permette l'automazione di compiti tramite l'esecuzione di programmi definiti dall'utente a intervalli regolari, cron permette di definire sia il programma da eseguire sia il momento esatto in cui eseguirlo. Il programma cron viene eseguito in background fino a quando necessario. A ogni minuto controlla se vi siano task da svolgere e dopo che li ha svolti torna in attesa del task successivo. La lista di task per cron scritta in un file detto tabella di cron, cui si fa riferimento crontabs. La crontabs un "calendario" di compiti che riporta i compiti stessi e il momento in cui svolgerli.

Alcuni amministratori di sistema potrebbero disabilitare cron per risparmiare potenza di calcolo. In questo caso si dovranno ottenere i permessi per poter eseguire programmi cron. Si pu verificare che cron sia in esecuzione e che si abbiano i permessi di accesso, digitando crontab -l sulla linea di comando.

Si deve soltanto modificare crontabs e aggiungervi una riga che invochi ant che a sua volta far partire il processo di build. Si consultino le pagine del manuale Unix per maggiori informazioni circa l'uso di cron.

16.5.2 Uso di Microsoft Task Scheduler


Windows contiene un'applicazione chiamata Task Scheduler (o Scheduled Tasks nelle versioni pi recenti della piattaforma). Questo programma opera le stesse funzioni di cron, ma pensato per Windows. Consiste di un wizard che permette di impostare compiti da eseguire a intervalli regolari. La Figura 16-4 mostra la schermata principale dell'applicazione. Il task creato invocher un file batch che, a sua volta, far effettuare ad Ant la creazione di una nuova compilazione.

Figura 16-4. L'applicazione Scheduled Tasks sono Windows

Non si ha un controllo cos granulare degli intervalli di tempo come nella piattaforma Unix. Tuttavia, uno strumento sufficiente per produrre distribuzioni giornaliere a un certo orario. Si veda la documentazione di Windows per dettagli pi approfonditi. Lo scaricamento e la compilazione dei sorgenti fanno parte dell'automazione del proprio ambiente. Molti sistemi di controllo dei sorgenti come Clear Case, CVS e PVCS hanno una API a linea di comando per estrarre file sorgenti in modalit di sola lettura per compilarli. Con Java e Ant, questa parte del processo di automazione resa piuttosto rapida.

16.6 Riavvio remoto del server


Se il proprio server di deploy localizzato in un server diverso da quello di sviluppo (come dovrebbe essere) si dovranno spostare i file di deploy e riavviare il server. Il modo pi rapido consiste nell'uso dei task li Ant ftp e telnet. Si potrebbe perfino ottenere un report via e-mail dei risultati dell'ultima compilazione con il task email. Si veda la documentazione di Ant per maggiori informazioni su questo e altri utili task.

CAPITOLO 17

Attenzione alle prestazioni

Ottenere prestazioni adeguate per un'applicazione un fattore della massima importanza per gli utenti. Non molti di questi infatti potranno apprezzare un bel design e uno standard di programmazione all'avanguardia se l'applicazione lenta. Persino la migliore e pi utile delle applicazioni verr rifiutala dalla comunit degli utenti se risulta lenta nella risposta alle loro richieste. Nel caso delle applicazioni basate sul web, le aziende devono testare e misurare la velocit dei vari componenti web, quante richieste simultanee il sito possa gestire e quale sia la scalabilit del software e dell'hardware. Devono anche definire di quanto le prestazioni verranno degradate nei momenti di picco. Questo capitolo esplora le implicazioni legate alle prestazioni di Struts e delle tecnologie che lo affiancano per la costruzione di web application, e del modo in cui alcune decisioni legate alla progettazione e alla programmazione influiscano sulle prestazioni totali. Si distinguer tra test di prestazioni, test di carico e test di stress, esaminando i passi necessari a condurli.

17.1 Che cosa si intende per buone prestazioni?


Molti sviluppatori hanno avuto la desolante esperienza di scrivere un'applicazione lenta. Ovviamente non che desiderino scrivere applicazioni di questo tipo e non esiste alcun gruppo o azienda che le richieda. Ma molto spesso una cattiva prestazione non viene scoperta fino al momento in cui l'applicazione finita e installata in produzione. Ma perch succede questo? La verit che ci si verifica perch in fase di progettazione e programmazione non si dato abbastanza peso ai problemi di performance. Non diremo che la prestazione deve essere l'obiettivo primario di un'applicazione: se ci si concentra troppo o troppo presto su quest'aspetto, ne risentir negativamente l'intera struttura del progetto e del codice. D'altro canto, se si aspetta troppo, ci si potrebbe trovare con utenti arrabbiati a causa della lentezza del programma, e non resterebbe che capire dove abbiamo sbagliato. Probabilmente avrete gi sentito l'adagio "Test presto, test spesso". Questo sempre un buon principio da osservare per cautelarsi da cattive sorprese al termine dello sviluppo di un'applicazione. Prima si riscontra un problema di prestazioni, prima si pu porre rimedio. Un altro assioma recita: "Non lasciare finestre rotte." Vale a dire, se si riscontra un problema, esso va corretto subito senza lasciarlo perdurare. Proviamo a immaginare una casa con una finestra rotta. Se la gente viene abituata a pensare che sia normale avere una finestra rotta, in seguito si sentir legittimata a lasciarne molte altre. Dopo un po' di tempo, l'edificio sar in rovina e gli abitanti se ne andranno. Se si riscontrano problemi di prestazione durante i primi test; vanno risolti subito. Ma come misurare le prestazioni di una web application? Cosa considerare accettabile e cosa troppo lento? Le risposte a queste domande sono strettamente collegate ai requisiti non funzionali dell'applicazione.[1] Esistono misurazioni tangibili e quantitative che possono essere effettuate per determinare se l'applicazione raggiunge i requisiti minimi delineati nei requisiti non funzionali.
[1] I requisiti non funzionali fanno parte del lavoro di analisi che dovrebbe essere svolto per qualsiasi applicazione non banale. Questi requisiti descrivono i problemi in senso lato di un'applicazione come disponibilit, possibilit di recupero da problemi di funzionamento, dipendenze dei package, configurazione hardware e, quasi sempre, criteri di prestazione.

Il problema che ogni applicazione divrsa e di conseguenza ha differenti requisiti non funzionali. Un'applicazione potrebbe necessitare di un tempo medio di risposta di 3.0 secondi e supportare 50 utenti contemporaneamente, mentre un'altra potrebbe dover supportare 500 utenti simultaneamente. Il test delle prestazioni un po' pi oscuro del test funzionale, per il quale facile capire quando l'applicazione svolge il proprio compito.

Secondo Alberto Savoia, il direttore del dipartimento Ricerca sul Software presso la Sun Microsystems, ci sono quattro leggi comportamentali che rendono fondamentali per il successo di un'azienda le prestazioni delle pagine web: Legge dell'abitudine Questa legge indica che gli utenti web sono abitudinari ma non fedeli. Se trovano un sito web che risponde ai propri bisogni, essi tendono a continuare a utilizzarlo. Se il sito inizia a rispondere con lentezza e l'utente deve aspettare, quest'ultimo cercher un'altro sito che corrisponda agli.stessi bisogni. Il punto che necessaria un forte impegno sulle prestazioni per far tornare ogni volta gli utenti. Legge della prospettiva dell'utente Questa legge stabilisce che si deve misurare la prestazione della propria applicazione dal punto di vista dell'utente e non dal proprio. Cio necessario considerare che magari noi operiamo all'interno di una rete a 100 MB non trafficata, ma l'utente potrebbe invece usare un modem e molta meno banda. Si tenga ben presente perci quali sono le capacit dell'ambiente e della rete dell'utente e si compiano test appropriati. Legge della responsabilit Questa legge stabilisce che a un utente non interessa la causa di una magra prestazione del sito; l'imputata sar sempre l'applicazione. Il problema potrebbe essere il suo provider o un'altra ragione che niente ha a che fare con la nostra applicazione, ma la maggior parte degli utenti non sar capace di isolare il problema a quel livello e a essere biasimate saranno applicazione e/o sito. Occorre essere consapevoli di tutti i fattori che potrebbero influenzare le prestazioni della nostra applicazione. Legge delle aspettative Questa legge indica che la soddisfazione dell'utente basata sulle proprie aspetta tive che sono in relazione con le personali esperienze legate ad altri siti web simili. Quando si misurano le prestazioni non ci si deve basare solo su numeri arbitrari e astratti ma si devono fare confronti con la concorrenza. Queste leggi semplici e basate sul buon senso spiegano il comportamento umano nei confronti delle prestazioni dei siti web. In generale, tuttavia, lento lento e veloce veloce, quindi si possono fare delle generalizzazioni tra applicazioni e domini di elaborazione. Ma prima di discutere come riconoscere se esiste un problema di prestazione in un'applicazione, deve essere fatta una distinzione tra i tipi di test da eseguire.

17.2 Test di prestazione e test di carico


Esistono svariate metodologie di test del software: delle funzioni, di unit, di integrazione, a scatola bianca, a scatola nera, di regressione e via dicendo. I test di prestazione e carico sono tra i pi importanti, ma di solito ricevono poche attenzioni, per due motivi: in primo luogo, gli sviluppatori di solito aspettano che l'applicazione sia terminata per fare questi test e ci coincide con il momento in cui il tempo agli sgoccioli; per vero che non sempre pratico effettuare test durante tutte le fasi dello sviluppo. Le fasi iniziali tendono a focalizzare l'attenzione su porzioni significative dal punto di vista architetturale e potrebbe non esserci abbastanza applicazione gi costruita per condurre test di prestazione. Tuttavia sarebbe bene iniziare a raccogliere dati il pi presto possibile. Un'altro motivo sta nel fatto che questo tipo di test difficile da condurre. Anche se ci sono molti strumenti sul mercato, sia liberi che commerciali, non sempre sono facili da usare per il riconoscimento di certi problemi. Gli strumenti dovrebbero essere in grado di simulare molti utenti simultanei di un sistema, ma questo implica la comprensione di quello che un utente virtuale,[2] che cosa sono i diversi modelli di thread e come influenzano carico e prestazioni. Inoltre si dovrebbe guardare ai risultati e determinare se sono accettabili o no. Tutto questo potrebbe essere troppo per uno sviluppatore medio. Per questi due motivi gli sviluppatori sono riluttanti a condurre test; o non capiscono i passi necessari o non sanno come e dove iniziare. Molte aziende hanno un team separato che si occupa solo dei test delle prestazioni.
[2]

Gli utenti virtuali sono utenti simulati che le applicazioni di test impiegano per emulare l'impatto di molti utenti su di un sistema senza richiedere l'intervento di utenti reali. Per ciascun utente virtuale viene registrala una sessione utente reale che pu essere eseguita come se un utente reale stesse usando l'applicazione.

Anche se i test di stress, di prestazione e di carico sono correlati, differiscono tra loro sia sostanzialmente che operativamente. Il test delle prestazioni prevede l'esecuzione degli aspetti funzionali dell'applicazione e la misurazione del tempo necessario a ottenere un risultato completo. Il tempo impiegato da un solo task viene detto tempo di risposta. Se il metodo eseguito pi volte e si opera una media, il risultato conseguito il tempo medio di risposta. Il tempo medio di risposta per l'action signin di Storefront di circa 250 millisecondi.[3] Questo risultato vale per un utente singolo. Si dovrebbe sempre condurre il test iniziale di performance con un solo utente per ottenere una linea di base. Se esistono dei colli di bottiglia per un utente singolo si pu scommettere che gli stessi problemi avranno un impatto devastante quando cominciano a collegarsi decine di utenti. In generale, si pu dire che pi veloce il tempo di risposta pi veloce l'applicazione. Questo tempo si potrebbe anche definire come il tempo di transazione per la funzione in test.

[3]

I test sono stati condotti su di un Pentium III 750 MHz con I GB di memoria, che conteneva tutti i livelli.

Sulla base del tempo di risposta si pu arguire un tempo di throughput. Il throughput (o portata) definisce il numero di transazioni che avvengono in un certo lasso di tempo. Il throughput teorico che viene calcolato sulla base di un solo utente probabilmente varier con carichi reali. A seconda del rhultiprocessing e di altre particolarit hardware e software, le applicazioni potranno avere un throughput maggiore aggiungendo risorse hardware o software. In questo modo l'applicazione elaborer un numero maggiore di transazioni permettendo un aumento dei valori di throughput. Il test di carico analogo al test di volume. Questo tipo di test viene svolto per capire il modo in cui l'applicazione reagir a un carico molto pesante di utenti del sistema. Nel corso di questo tipo di test si potranno modificare le configurazioni hardware e software per determinare quale configurazione offre il miglior throughput per un certo carico di utenti. Il test di carico di solito difficoltoso da svolgere perch implica un costante impegno per trovare la miglior combinazione per il miglior throughput. Nessuna applicazione pu sostenere un carico infinito. L'idea consiste nel trovare un buon accordo tra tempo di risposta e numero massimo di utenti. Il throughput di solito misurato in transazioni per secondo (tps), ma pu essere anche misurato in transazioni per minuto, o ora e cos via. Tramite valori di risposta e valori di throughput si possono prendere le decisioni pi opportune circa la migliore configurazione della propria applicazione allo scopai di assicurare prestazioni elevate e scalabilit per gli utenti. Questi valori andrebbero trasmessi ai tecnici di rete per capire quanta larghezza di banda richiede l'applicazione. Il test di stress la successiva logica estensione. Il test di stress essenzialmente un test di carico tramite l'utilizzo di carichi di picco. Se si svolge questo test, l'intento di mettere alla prova l'applicazione ai suoi limiti, per osservarne le reazioni, l'uso della memoria e qualsiasi altro problema si possa delineare. Spingere la propria applicazione ai limiti sotto un carico simulato pesantissimo offre molti benefici, tra i quali: Permette di identificare colli di bottiglia nell'applicazione sotto un forte carico di utenti prima dell'introduzione nell'ambiente di produzione. Permette il controllo di costi e rischi prevedendo limiti di scalabilit e prestazioni. Incrementa disponibilit e tempi di caricamento tramite una corretta pianificazione delle risorse. Evita dei ritardi dovuti a problemi inaspettati di performance o scalabilit. I test di performance, carico e stress dovrebbero essere svolti tutti per ogni applicazione per poter avere un quadro completo della situazione. Possono essere diretti a parti dell'applicazione che potrebbero rivelarsi colli di bottiglia sia sotto carichi normali sia all'aumentare del numero di utenti.

17.3 Strumenti per i test di prestazione e stress


Sul mercato esiste davvero un gran numero di strumenti per condurre test di prestazione e stress. Alcuni sono poco costosi e altri hanno costi astronomici. I prodotti commerciali tendono a offrire caratteristiche pi complete, ma non esiste una vera e propria correlazione tra costi e qualit quando si tratta di strumenti come questi. Per la maggior parte delle appplicazioni non necessario spendere un'occhio della testa, dal momento che alcuni strumenti sono gratis e compiono efficacemente la funzione richiesta. Si veda la tabella Tabella 17-1 che elenca anche diversi strumenti commerciali per la misura della performance e dello stress.
Tabella 17-1. Un elenco di tool per i test di prestazione e stress

Organizzazione Apache Group Mercury Interactive Rational RadView Empirix Segue Software, Inc. Microsoft Apache Group SourceForge

Prodotto JMeter LoadRunner SiteLoad WebLoad e-Test Suite SilkPerformer WAS Flood The Grinder

URL http://jakarta.apache.org/jmeter/ http://www-svca.mercuryinteractive.com http://www.rational.com http://www.radview.com http://www.empirix.com http://www.segue.com http://webtool.rte.microsoft.com http://httpd.apache.org/test/flood/ http://grinder.sourceforge.net

17.4 Test su Storefront

Storefront rappresenta un'applicazione tipica di commercio elettronico che potreste avere gi visto su Internet o anche avere gi sviluppato. Una normale applicazione di questo tipo si connette a un database con decine o centinaia di migliaia di campi. Nel nostro caso. Storefront usa un'implementazione di debug e, volutamente, non si connette a un database, in modo che non sia necessario un database installato solo per eseguire l'applicazione di esempio. Non c' motivo di effettuare un test completo di Storefront: i numeri non avrebbero troppo significato. utile per mostrare come iniziare e i passi da seguire per avere delle misurazioni di prestazione su un'applicazione. Ecco i passi fondamentali: 1. 2. 3. 4. 5. 6. Identificare gli obiettivi di prestazione ricercati. Stabilire le basi per la misurazione delle prestazioni. Eseguire test per raccogliere dati. Analizzare i dati per scoprire dove sono i problemi. Operare le necessarie modifiche al software e all'hardware per incrementare la prestazione. Ripetere i punti da 3 a 5 secondo necessit per raggiungere gli obiettivi preposti.

Si tratteranno di seguito tutti questi punti. Per questo esercizio si user una versione in scala ridotta di Mercury Interactive LoadRurmer, chiamato Astra LoadTest. Si tratta di un prodotto commerciale ricco di caratteristiche. Si pu scaricare una versione dimostrativa che supporta fino a 10 utenti Contemporanei dal sito http://wwwsvca.mercuryinteractive.com/products/downloads.html.

17.4.1 Identificare gli obiettivi di prestazione ricercati


Prima di iniziare il test, e importante definire gli obiettivi per le prestazioni dell'applicazione. Questi sono di solito specificati nei requisiti non funzionali tramite le seguenti unit di misura: Tempo medio di risposta per transazione Transazioni per secondo (tps) Contatti per secondo Non assolutamente necessario che si conoscano le misurazioni delle prestazioni prima di fare i test, ma utile averli per verificare che siano nei limiti delle aspettative. Prima o poi qualcuno chieder quanto sia veloce l'applicazione. Per poter dire "velocissima!" o "fa dormire" si dovranno valutare le prestazioni relative ad alcuni obiettivi prefissati.

17.4.2 Stabilire le basi per la misurazione


Una volta che si sta per cominciare i test, la prima cosa da fare stabilire un riferimento, ovvero un test dell'applicazione prima di qualsiasi modifica. Si dovrebbe sempre stabilire un punto di riferimento prima di effettuare qualsiasi cambiamento, altrimenti come si pu stabilire se la modifica ha migliorato o peggiorato la situazione?

17.4.2.1 Determinazione di un riferimento


Gli strumenti per i test delle prestazioni permettono di solito di registrare la sequenza interattiva tra un browser e una web application. Anche se molti strumenti permettono di creare manualmente gli script di test, l'uso degli aspetti di registrazione automatica degli strumenti molto comodo. La Figura 17-1 illustra la schermata di registrazione dei risultati nel programma Astra LoadTest.

Figura 17-1. La schermata di registrazione dei risultali nel programma Astra LoadTest

Con Astra, e con altri strumenti analoghi, ogni interazione con la web application pu essere registrata come transazione separata. Nella Figura 17-1, ogni elemento nell'albero sulla sinistra dello schermo rappresenta una transazione separata che pu essere ripetuta e di cui possono essere misurati i parametri di prestazione. Una volta iniziata la registrazione dei risultati, tutte le interazioni tra client e server sono salvate, inclusi i parametri di richiesta e gli header. Si pu poi ripetere questa registrazione modificando diversi parametri quali, ad esempio, il numero di utenti che partecipa all'operazione. Quindi, ottenuti gli script appropriati, si pu stabilire il riferimento, che di solito dato dalla misurazione su un singolo utente che usa l'applicazione. Il numero di utenti virtuali pu variare a seconda che si stia misurando la prestazione o il carico. Solitamente meglio partire con un singolo utente e in seguito incrementarne il numero. Se l'applicazione lenta con un solo utente, sar presumibilmente lenta anche con pi utenti. La Figura 17-2 mostra gli script di test della Figura 17-1 in azione sull'applicazione Storefront con un singolo utente.

Figura 17-2. Test dell'applicazione Storefront con un singolo utente

Concluso l'insieme di test, il programma presenta un sommario dei risultati del test per la propria applicazione. La Figura 17-3 mostra i punti di riferimento per Storefront con un singolo utente.

Figura 17-3. Il sommario con i risultati per Storefront

Ottenuti i punti di riferimento, se si deduce che le prestazioni sono da aumentare si pu modificare la propria applicazione. Sfortunatamente non quasi mai un'operazione semplice. Si devono determinare i punti deboli su cui concentrarsi. Non c' motivo di velocizzare l'applicazione in punti dove gi veloce. Si devono utilizzare gli strumenti adatti a determinare le posizioni dei colli di bottiglia.

17.4.3 Trovare le aree probiematiche


A volte si fortunati e si individuano velocemente i problemi di prestazione. Altre volte si devono provare tool diversi per localizzare e isolare le aree che causano questi problemi. In questo caso possono essere di valido aiuto gli strumenti di ottimizzazione. La messa a punto della nostra applicazione un po' diversa dallo svolgimento dei test di prestazione di cui si finora trattato. Anche se gli strumenti per la misurazione delle prestazioni possono rivelare quale richiesta web ha necessitato di pi tempo per essere completata, non possono dire quale metodo Java ha richiesto il tempo pi lungo. Questo il campo degli strumenti di ottimizzazione (o profiler). La Tabella 17-2 elenca diversi strumenti ili profiling utili per l'identificazione di aree problematiche nelle applicazioni.
Tabella 17-2. Strumenti di ottimizzazione in commercio

Organizzazione Rational Inprise Sitraka Software

Prodotto Quantify OptimizeIt JProbe http://www.rational.com

URL http://www.borland.com/optimizeit/ http://www.sitraka.com

L'ottimizzazione di un'applicazione un'operazione simile al debug. Si vede il punto in cui un'applicazione impiega pi tempo per l'elaborazione, il modo in cui sono effettuate le chiamate a una singola funzione, quanti oggetti vengono creati, e via dicendo. Si parte da un livello alto per discendere fino al livello dei metodi che causano problemi di prestazioni o scalabilit. Una volta posto rimedio, si eseguono nuovamente i test. Questo processo viene ripetuto finch i problemi non sono tutti risolti o finch non si deve rendere disponibile il prodotto.

Gli strumenti per misurare le prestazioni possono dare comunque un aiuto per determinare le aree pi problematiche. La Figura 17-4, ad esempio, illustra come il tempo medio di risposta per transazione di una certa action sembri maggiore delle altre.

Figura 17-4. Tempi medi di risposta maggiori possono indicare un problema

Le misurazioni qui riportate sono straordinariamente buone. Il tempo peggiore di risposta per Storefront di 0.25 secondi. La maggior pane degli sviluppatori sarebbe disposta a uccidere per avere problemi come questo! Ma in questa applicazione non viene svolto nessuna elaborazione reale, perch solo un esempio in cui non ci si connette a un database con centinaia di migliaia di record tra cui cercare. Ci, tuttavia ci fa notare un fatto: solo perch una transazione pi lenta delle altre non vuol dire che sia lenta tout court. Potrebbe solo essere lenta relativamente ad altre transazioni davvero velocissime. Non si sprechi tempo per velocizzare qualcosa che gi veloce. Ci si deve concentrare piuttosto sulle parti che sono davvero lente. 0.25 secondi vuol dire molto veloce e, se l'applicazione fosse reale, potremmo subito distribuirla. L'operazione che mostra il peggior tempo di risposta nella Figura 17-4 l'action "view item detail". Con Astra, si pu osservare la transazione ancora pi nei particolari. La Figura 17-5 analizza la pagina di visione dei dettagli articolo, suddividendola nelle parti che la compongono.

Figura 17-5. La pagina di visione dei dati di un articolo scomposta nelle sue parti

Cos, si pu spostare l'attenzione sui tempi di download delle varie pagine per capire come si comporta al confronto la pagina "view item detail" (Figura 17-6).

Figura 17-6. Tempi di scaricamento delle pagine per Storefront

Come si potuto notare, riconoscere problemi di prestazione necessita di un po' di lavoro da detective. Si deve sempre guardare dietro ogni angolo e sotto ogni pietra.

17.4.4 Effettuare le modifiche necessarie a software e hardware


Identificato il problema e apportate le necessarie modifiche, si provi ancora a condurre test sull'applicazione con gli script. Questi passaggi devono essere reiterati finch tutti i problemi saranno stati risolti o il prodotto deve essere distribuito. Dal momento che il tempo scorre velocemente, assolutamente importante pianificare le proprie attivit di test per coprire le aree pi importanti dell'applicazione concentrandosi su componenti specifici che si presume possano causare problemi relativi a prestazioni o scalabilit. Di solito non c' un solo problema da risolvere ma un processo reiterativo che potrebbe andare avanti all'infinito, se non si dovesse prima o poi distribuire il prodotto. Le aree pi critiche sono perci quelle su cui imperativo concentrarsi.

17.5 Problemi di prestazioni e scalabilit


Questa sezione introduce diversi problemi ben noti che possono influire sulla performance e la scalabilit delle proprie applicazioni Struts; non vuole essere esauriente ma puntualizzare alcuni importanti elementi.

17.5.1 Scope di richiesta e di sessione


La memoria limitata. Se ne pu anche comprare molta per una macchina ma a un certo punto non si potr pi progredire per questa via. Una prassi comune sta nell'immagazzinare oggetti e dati nella HttpSession dell'utente. In alcuni casi potrebbe essere l'unica via praticabile per ottemperare a un particolare requisito. Tuttavia, si deve considerare l'effetto che l'immagazzinamento di dati e oggetti nella sessione ha nei confronti dell'applicazione. Pi informazioni e oggetti sono conservati nella sessione, pi ci si deve curare della scalabilit. Poniamo di dover immagazzinare 0.5 MB di dati per utente singolo. Se l'applicazione ha 1000 utenti contemporanei dovr utilizzare 500 MB (0,5GB) di memoria.

Non si deve dimenticare che, oltre alle sessioni utente, ci sono altre risorse che richiedono memoria. Esistono i dati con scope di applicazione, il resto del framework Strus, i componenti propri dell'applica ione, il container stesso, la JVM.e via dicendo. Sono tutti fattori da tenere in z considerazione.

Come si evince facilmente, l'utilizzo della sessione per l'immagazzinamento di dati pu fare esaurire rapidamente la memoria. Un'alternativa da considerare potrebbe essere HttpServletReques per conservare temporaneamente dei dati che possano essere impiegati da altri componenti e quindi richiamati dal garbage collector al completamento della richiesta. La responsabilit della distruzione dei dati, con dati che hanno lo scope di richiesta, suddivisa tra container e JVM ma non dell'applicazione.

17.5.2 Uso della parola chiave synchronized


Per controllare l'accesso di thread multipli a lina risorsa singola viene usata la sincronizzazione. Esistono molte situazioni in cui la sincronizzazione consigliabile ed assolutamente necessario evitare che thread multipli (interferiscano tra loro, cosa che potrebbe causare errori significativi dell'applicazione. In Struts, le applicazioni sono tutte a thread multipli e si potrebbe essere portati a pensare che certe parti della web application debbano essere sincronizzate. Tuttavia, l'uso della parola chiave synchronized nelle applicazioni Struts potrebbe essere la causa di problemi di prestazione e scalabilit. Che le servlet non dovrebbero contenere delle variabili di istanza un fatto noto a tutti, dal momento che ci potrebbe essere solo una istanza di una servlet attiva con molti client che eseguono la stessa istanza contemporaneamente. Se si conserva lo stato del thread di un client in una variabile di istanza e un thread di un client differente viene eseguito nello stesso momento, potrebbe sovrascrivere le informazioni di stato del thread precedente. Questo vale per le classi di Struts Action e anche per ActionForm con scope di sessione. Ci si deve accertare di programmare con attenzione la propria applicazione perch sia thread-safe, cio si deve progettare e scrivere la propria applicazione per permettere a thread di client multipli di essere eseguiti contemporaneamente senza interferire tra di loro. Se c' bisogno di un controllo degli accessi a una risorsa condivisa si provi ad usare un pool di risorse invece che la,sincronizzazione su di un singolo oggetto. Inoltre si ricordi che HttpSession non sincronizzata. Se si hanno thread multipli che leggono e scrivono negli oggetti della sessione utente, si possono verificare problemi difficili da identificare. Sta al programmatore proteggere le risorse condivise immagazzinate nella sessione utente.

17.5.2.1 Uso di java.util.Vector e java.util.Hashtable


Si deve anche fare attenzione a quali classi Java si impiegano nella propria applicazione Struts specialmente quando si tratta di classi collection. Le classi java.util.Vector e java.util.Hashtable, per esempio, possiedono una sincronizzazione interna. Se si usano Vector o Hashtable nelle applicazioni Struts, l'effetto potrebbe essere simile all'utilizzo esplicito della parola chiave synchronized. Si dovrebbe evitare di usare queste classi, a meno che non si sia assolutamente sicuri di averne bisogno. Invece di usare Vector, per esempio, si potrebbe usare java.util.ArrayList e, al posto di Hashtable, la classe java.util.HashMap. Entrambe offrono funzionalit analoghe senza imporre la sincronizzazione.

17.5.3 Uso eccessivo di tag personalizzati


I tag personalizzati JSP compiono con grande efficienza il loro lavoro. Il loro impiego nelle JSP, al posto dell'inserimento di codice Java, consigliato da chiunque abbia provato entrambi i metodi. Ma non si deve esagerare inserendo troppi tag personalizzati nella stessa pagina JSP. Alcuni container non sono troppo efficienti nella gestione dei tag handler, e alcuni potrebbero generare codice Java mal formato. Se le nostre JSP hanno un'esecuzione lenta, una soluzione possibile sta nello spostare parte del codice in un'altra pagina JSP e usare poi il meccanismo include. Un'altra soluzione, anche se meno praticabile, sta nel ridurre il numero di tag della pagina. Se entrambe le soluzioni non recano i risultati voluti, si provi un container diverso. Un container potrebbe avere comportamenti differenti con i tag, vale a dire che uno risulta lento con la nostra applicazione, ma un altro potrebbe essere veloce.

17.5.4 Impostazione scorretta della JVM

La JVM supporta molte diverse opzioni per l'impostazione e la configurazione dei propri parametri di esecuzione. A volte potrebbe essere utile modificare queste informazioni per ottenere una migliore prestazione. Le due opzioni pi importanti ai fini di prestazioni e scalabilit sono Xms(size)e -Xmx(size). L'opzione -Xms permette di impostare la misura iniziale dell'heap dell'applicazione mentre -Xmx permette di impostare la dimensione massima dell'heap. L'heap l'area di immagazzinamento della memoria per l'applicazione. Pi grande l'area di immagazzinamento, pi la memoria disponibile per l'applicazione. Ci si potrebbe chiedere, "e perch non impostarla allora alla dimensione massima consentita dalla memoria fisica?". Il problema che si tratta comunque di un'area che il garbage collector deve ripulire. Il garbage collector della JVM va periodicamente in esecuzione e tenta di recuperare tutta la memoria inutilizzata. Il garbage collector deve passare attraverso tutta la memoria assegnata a un'applicazione. Ogni volta che il garbage collector si attiva, l'applicazione viene momentaneamente bloccata. Pi tempo questo impiega a svolgere la propria funzione, pi gli utenti staranno in attesa. L'impostazione corretta della dimensione dell'heap quindi molto importante e non facile determinarla in generale per una qualsiasi applicazione. Ogni applicazione diversa e crea e distrugge oggetti con frequenza diversa. Si possono per impostare valori standard di riferimento ed effettuare cambiamenti progressivi. Si raggiunger prima o poi un punto in cui scalabilit e prestazioni peggiorano all'aumentare dei valori. Si ritorner allora ai valori immediatamente pi bassi che saranno quelli ottimali. In generale si dovrebbero utilizzare i seguenti valori di partenza:

-Xms 256M -Xmx 256M


Molte fonti raccomandano di impostare i valori iniziale e massimo allo stesso livello, perch la JVM non debba interrompere l'applicazione quando deve richiedere pi memoria. Anche questa misura dovrebbe incrementare le prestazioni. Il valore minimo preimpostato di 2 MB; il valore massimo preimpostato di 64 MB. Nell'opzione, la lettera dopo il numero pu essere:

k o K per kilobytes m o M per megabytes


Per una elenco di altre opzioni supportate dalla JVM si scriva java -X sulla linea di comando. Se il proprio path impostato correttamente per l'eseguibile Java, si dovrebbe vedere quanto illustrato in Figura 17-7.

Figura 17-7. Scrivendo java -X compaiono le opzioni per la JVM

17.5.5 Utilizzo di troppe chiamate remote


Quando si accede a componenti remoti come gli EJB dalla propria applicazione, si pu notare come il sovraccarico delle comunicazioni di rete possa dare problemi di velocit. La "granularit" delle proprie chiamate remote un fattore da tenere d'occhio. Se si scoprisse che per poche informazioni sono necessarie molteplici invocazioni remote per una stessa pagina, si provi ad accorpare un insieme di chiamate in relazione tra loro all'interno di un numero minore di invocazioni remote. Per esempio, se si usano invocazioni remote separate per visualizzare un elenco di prodotti, effettuare qui delle ricerche e in seguito richiedere i dettagli degli articoli, sarebbe meglio effettuare invece una chiamata remota comprensiva di tutti questi elementi che restituisca tutte le informazioni sui prodotti e i dettagli in una sola volta. Si

possono anche migliorare le prestazioni delle applicazioni che usano riferimenti remoti per mezzo di un meccanismo di caching dei riferimenti remoti. Per maggiori dettagli, si veda il Capitolo 13.

17.5.6 Utilizzo eccessivo di immagini/grafica


Quando una pagina web contiene grafica, essa viene caricata separatamente dal contenuto HTML. Ogni immagine potrebbe richiedere e usare connessioni separate. Anche quando i problemi legati alla performance sono da imputarsi alle sole immagini, un utente potrebbe pensare che l'applicazione sia lenta. Si eviti perci l'utilizzo eccessivo di immagini e in special modo di quelle di grandi dimensioni. Questo un metodo sicuro di migliorare le prestazioni di un'applicazione.

Appendici
Appendice A. Cambiamenti rispetto a Struts 1.0
Con il passaggio dalla versione 1.0 alla 1.1, al framework Struts sono stati apportati numerosi cambiamenti: di essi si parlato nel corso di tutto il libro, ma in questa appendice si riassumono tutte le variazioni, illustrando inoltre le caratteristiche pi importanti tra quelle nuove.

A.1 ActionServlet e RequestProcessor


In Struts 1.1 stata aggiunta una nuova classe la RequestProcessor. Questa classe si inca di gran parte del lavoro rica che, nelle precedenti versioni, era svolto dalla ActionServlet. Con la versione 1.1, sempre possibile configurare nel file web.xml il nome di classe della ActionServlet che si desidera utilizzare per un'applicazione, ma adesso la classe RequestProcessor che svolge gran parte del vero lavoro di elaborazione delle request. Per gestire tutte le request in entrata, le applicazioni utilizzano come classe predefinita la

org.apache.struts.action.RequestProcessor e, se il comportamento predefinito si adatta ai nostri


requisiti, non necessario fare nulla di particolare. Si pu comunque scegliere di ridefinire l'implementazione predefinita con una personalizzata. Si pu passare da un'implementazione all'altra configurando opportunamente l'elemento controller nel file di configurazione di Struts, come si visto in dettaglio al Capitolo 4.

A.2 Modifiche alla classe Action di Struts


La classe base Action stata modificata con un nuovo metodo, execute(), il quale deve essere invocato al posto di perform(). La differenza principale tra i due metodi che execute() dichiara di lanciare l'eccezione java.lang.Exception, mentre il "vecchio" perform() dichiarava le eccezioni IOException e ServletException. Tale cambiamento si reso necessario per facilitare la nuova caratteristica di gestione dichiarativa delle eccezioni di Struts 1.1. II metodo perform() stato deprecato e non andrebbe utilizzato.

A.3 Modifiche a web.xml e struts-config.xml


Ci sono stati molti cambiamenti relativi ai due file di configurazione necessari per le applicazioni Struts. Nel file web.xml, diversi parametri di inizializzazione sono stati rimossi e sono adesso supportati grazie a elementi presenti all'interno del file struts-config.xml. Il file web.xml supporta inoltre alcuni nuovi parametri. Gli elementi di entrambi i file sono trattati nel capitolo Capitolo 4 e si eviter di ripeterli qui. Si pu fare riferimento al DTD di Struts per vedere esattamente quali clementi siano supportati in un file di configurazione di Struts. Sar necessario aggiornare il proprio lile di configurazione di Struts affinch utilizzi il DTD pi recente, che si chiama struts-config_l_l.dtd. Se ci si dimentica di questo aggiornamento, saranno prodotti molti errori di parsing.

A.4 Nuove caratteristiche di Struts 1.1


Le nuove caratteristiche della versione 1.1 del framework Struts sono state trattate diffusamente nel corso di tutto il libro: si fornisce qui la descrizione delle pi importanti.

A.4.1 Gestione dichiarativa delle eccezioni


Prima della versione 1.1, la gestione delle eccezioni era lasciata esclusivamente all'applicazione. La gestione non era inserita nel framework ed era compito degli sviluppatori cercare di comprendere, con i loro mezzi, il modo in cui dovevano essere trattate le eccezioni all'interno di un'applicazione. A cominciare dalla versione 1.1, la gestione delle eccezioni fa parte delle funzionalit del framework, ed possibile configurare in maniera dichiarativa le eccezioni che le varie azioni possono lanciare e cosa deve succedere quando ci si verifichi. La gestione delle eccezioni trattata nel Capitolo 10.

A.4.2 ActionForm dinamiche


In Struts 1.1 stato introdotto un nuovo tipo di classe ActionForm, alla quale stato garantito anche un maggiore supporto per tutto il framework. La classe DynaActionForm e le sue sottoclassi, consentono di configurare istanze di form-bean nel file di configurazione di Struts, con conseguente notevole risparmio di tempo nello sviluppo, visto che non pi necessario creare classi ActionForm. E un po' troppo semplicistico affermare che non sar mai pi necessario creare altre classi ActionForm, ma il numero di quelle da creare dovrebbe essere drasticamente ridotto.

Maggiori informazioni su questa nuova caratteristica sono riportate nel Capitolo 7.

A.4.3 Plug-ins
La caratteristica PlugIn stata aggiunta a Struts affinch disponga di un meccanismo che notifichi e inizializzi i servizi all'avvio e alla conclusione dell applicazione Struts. C' un'ampia gamma di possibilit per questa caratteristica e, infatti, il Validator e la libreria Tiles traggono entrambi dei vantaggi dal meccanismo PlugIn. Si veda il Capitolo 9 per maggiori informazioni sull'uso dei plug-in nelle applicazioni.

A.4.4 Moduli applicativi multipli


L'aggiunta di moduli applicativi al framework consente che una singola applicazione supporti pi di un file di configurazione. Ci facilita la suddivisione di un programma in svariati sottoprogrammi, ciascuno con il sifo file di configurazione. Sebbene ci possa sembrare pi complicato, consente un miglior supporto per lo sviluppo parallelo. Il supporto per moduli applicativi multipli continua a subire ancora cambiamenti e modifiche. Nel momento in cui il libro stato scritto, c'era la possibilit di usare il mapping delle estensioni solo per la servlet controller e potrebbe essere necessario un po' di tempo ancora prima che i restanti componenti abbiano il pieno supporto di questa nuova funzionalit.

A.4.5 Tag annidati


I tag annidati sono una serie di tag custom JSP aggiunti a Struts per dare agli sviluppatori un maggior controllo sull'accesso alle propriet da un grafo di oggetti JavaBeans. Molti oggetti JavaBeans contengono oggetti JavaBeans annidati a cui possibile effettuare il reference per mezzo del bean genitore. Per esempio, un oggetto Customer pu mantenere il reference a un oggetto Address. Per supportare meglio la natura annidata di questi JavaBeans nelle viste di Struts, sono stati creati i tag annidati. I tag contenuti all'interno la libreria di tag Nested sono t r a tta t i nel Capitolo 7

A.5 Il Validator di Struts


II Validator di Struts un framework di validazione che supporta applicazioni sia Struts che non Struts. stato progettato comunque tenendo ben presente Struts e ha molte buone caratteristiche desiderabili per le applicazioni tipicamente costruite con Struts. Il Capitolo 11 tratta in dettaglio il Validator di Struts.

A.5.1 Cambiamenti al Package ORO


C' un aspetto importante da citare a proposito degli aggiornamenti. Le precedenti versioni del framework Validator si basava sul package RegExp di Jakarta, che per stato cambiato ora nel package ORO. Si veda il Capitolo 11 per maggiori dettagli sulle ragioni alla base di questa modifica e sui suoi effetti sulla configurazione del Validator.

A.6 Cambiamenti alla Commons Logging


L'intero framework Struts fa adesso affidamento sulla libreria Commons Logging, una API open source di Jakarta per il logging. Fornisce una API per il logging semplice e consistente, utilizzabile entro tutte le applicazioni e che permette il plug-in dinamico di differenti implementazioni. Saranno necessari gli archivi JAR della Commons Logging, che dovrebbero essere gi inclusi nel framework Struts. Questo cambiamento richiede inoltre l'aggiunta di alcune impostazioni di configurazione. Il Capitolo 15 tratta la libreria Commons Logging e il logging in generale in una applicazione di Struts.

A.7 Rimozione di action amministrative


Nelle precedenti versioni di Struts erano presenti diverse action "amministrative", utilizzabili per funzioni quale il ricaricamento di un'applicazione Struts (comprese le impostazioni di configurazione) senza il bisogno di terminare l'applicazione. Era inoltre presente la possibilit di aggiungere nuove action mapping in maniera dinamica. Queste azioni sono state rimosse dal framework e non sono pi disponibili. Se un'applicazione necessita di tale funzionalit, dovr essere aggiunta tramite programmazione.

A.8 Deprecazione di GenericDataSource


La classe org.apache.struts.util.GenericDataSource stata deprecata. Questa classe non era progettata per essere pensata come implementazione completa di datasource adatta a grandi applicazioni con alto grado di disponibilit, ma era intesa senza le caratteristiche avanzate presenti in prodotti datasource pi maturi, adatta a piccole applicazioni. Se c' bisogno di un'implementazione di datasource la scelta raccomandata ricade o su quella disponibile nel container che si utilizza o su quella disponibile nel progetto Commons DBCP all'indirizzo http://cvs.apache.org/viewcvs/jakartacommons/dbcp/.

A.9 Dipendenza con progetti Commons


Il framework Struts ha adesso stretta relazione di dependency con i seguenti package del progetto Jakarta Commons: Commons BeanUtils Commons Digester Commons Collections Commons Logging

Appendice B. Download e installazione di Struts


Questa appendice fornisce istruzioni e utili consigli sull'installazione di Struts. Sono stati compiuti tutti gli sforzi aftinch queste istruzioni funzionino con le pi recenti versioni dei container ma, nel caso si incontrino dei problemi, utile consultare la mailing list STRUTS-USER.

B.1 Le distribuzioni: binaria e sorgente


Il framework Struts disponibile sotto due forme di distribuzione: codice sorgente e file binari. La distribuzione in forma di codice sorgente, garantisce un maggior controllo sull'ambiente in cui si effettua la compilazione, ma non certo adatto ai principianti. Se non si hanno particolari esigenze (come l'inclusione di una classe modificata nella propria installazione), si pu tranquillamente usare la distribuzione binaria. In questa sezione sono comunque esaminate entrambe le modalit.

B.1.1 Compilare Struts dalla distribuzione sorgente


E possibile procurarsi i pacchetti con il codice sorgente di Struts e tutti i restanti progetti Jakarta dall'indirizzo http://iakarta.apache.org/site/sourceindex.hlml. Sono in genere disponibili svariate versioni, quindi occorre prestare molta attenzione a scegliere quella giusta per le proprie esigenze. C' sempre anche l'ultimissima versione che include i pi recenti cambiamenti e le correzioni ai problemi. Nella maggior parte dei casi, proprio quest'ultima sar la pi adatta rrva bene essere consapevoli del fatto che potrebbe contenere anche nuovi bug. Nella scelta del download, si pu optare tra un file ZIP o un TAR compresso occorre scegliere quello adatto al proprio ambiente operativo.

Una volta scaricata la versione pi adatta, si estrae il contenuto dell'archivio all'interno di una directory, che non deve essere necessariamente vuota, anche se molto meglio che lo sia: il file di distribuzione di Struts sotto forma di codice sorgente scompatta tutto il suo contenuto all'interno di una sottodirectory denominata jakarta-struts. Prima di poter effettuare la compilazione dei file sorgenti, sar necessario anche scaricare alcuni pacchetti software necessari al buon funzionamento di tutto (sempre che gi non siano stati installati precedentemente). L'elenco seguente descrive il software richiesto: Java SDK Versione 1.2 o pi recente. Si pu scaricare da http://java.sun.com/j2se/. Servlet container Un container per servlet che supporti le specifiche Servlet 2.2 o successive e le specifiche JavaServer Pages 1.1 o successive. Pool di compilazione Ant Versione 1.3 o successiva. Si pu scaricare da http://jakarta.apache.org/ant/. Classi Servlet API Tutti i servlet container dovrebbero contenere i file necessari; occorre solo garantire che i JAR necessari si trovino nel classpath per il tool di compilazione. Parser XML Deve supportare JAXP versione 1.0 o successive. Il parser Xerces compatibile e pu essere scaricato da http://xml.apache.org/xerces-j/. Xalan XSLT Processor Versione 1.2 o successiva, sebbene occorra notare che sono stati riferiti alcuni problemi con la versione 2.0. Questo programma di elaborazione utilizzato per convertire i file XML di documentazione in file HTML. Xalan include anche il parser Xerces, ed disponibile all'indirizzo http://xml.apache.org/xalan-j/index.html. Classi del package Optional JDBC 2.0 Il framework Struts supporta un'implementazione di javax.sql.DataSource e richiede quindi la presenza delle classi JDBC 2.0 Optional, le quali possono essere scaricate dall'indirizzo http://java.sun.com/products/jdbc/download.html. Sar anche necessario scaricare i package di terze parti elencati in Tabella B-l ed estrarre i file JAR in essi inclusi. Ciascun package disponibile in distribuzione sia sorgente che binaria e, a meno di non essere veri masochisti, meglio scaricare quella binaria.

Tabella B-1. Package di terze parti necessari per la compilazione di Struts

Nome package Commons BeanUtils Commons Collections Commons Digester Commons Logging Commons Pool Commons Services Commons DBCP Commons Validator Jakarta ORO

URL http://jakarta.apache.org/commons/beanutils.html http://jakarta.apache.org/commons/collections.html http://jakarta.apache.org/commons/digester.html http://jakarta.apache.org/commons/logging.html http://jakarta.apache.org/commons/pool/index.html http://jakarta.apache.org/builds/jakarta-commons/nightly/ http://cvs.apache.org/viewcvs/jakarta-commons/dbcp/ http://cvs.apache.org/viewcvs/jakarta-commons/validator/ http://jakarta.apache.org/oro/

L'indirizzo http://jakarta.apache.org/commom/components.html fornisce un ottimo quadro riassuntivo sulle date a partire dalle quali saranno disponibili le varie release dei componenti Commons.

I file JAR estratti dalle distribuzioni binarie di questi package vanno collocati in maniera che risulti facile effettuare i riferimenti ad essi, dal momento che serviranno per il passo successivo. Ciascun package viene estratto all'interno della sua sottodirectory ed possibile lasciarli in tale posizione per avere riferimenti pi facili. Il passo successivo sta nel creare un file build.properties che specifichi le relazioni di ambiente, quali la collocazione dei JAR terze parti estratti nella fase precedente. La distribuzione sorgente di Struts fornisce un file build.properties.sample nella directory top-level della distribuzione: basta aprire questo file e rinominarlo build.properties. Sar necessario personalizzare questo file per le peculiarit del proprio ambiente. I patii del file sono relativi alla collocazione del file di propriet: per esempio, al fine effettuare il riferimento al file JAR Commons BeanUtils che si trova in una directory lib a un livello immediatamente superiore, sar necessario modificare in questo modo il file build.properties:

# The JAR file containing version 1.0 (or later) of the Beanutils package # from the Jakarta Commons project. commons-beanutils.jar=../lib/commons-beanutils-1.3/commons-beanutils.jar
Una volta messi a posto tutti i path per i file nel build.properties, arrivato il momento di trasformare i file sorgenti di Struts in file binari. Occorre che Ant sia installato e configurato in maniera corretta e che il file xalan.jar sia stato scaricato e posto nella directory <ANT_HOME>/lib, affinche un task di Ant possa elaborare i file XML di documentazione. Sar poi necessario essere in grado di eseguire Ant da linea di comando nella directory top-level della distribuzione sorgente. Completati tutti questi punti, si va alla directory in cui collocato il file build.properties e si digita:

ant dist
Nella console saranno prodotti dei messaggi. Quando, alla fine, termineranno, dovrebbero indicare che la compilazione andata a buon fine, come mostrato in Figura B-l.

Figura B-1. Quando l'operazione di build completata, viene presentato un messaggio di successo

Se la build non va a buon fine, saranno prodotti dei messaggi di errore, di solito con informazioni sufficienti a determinare la causa dei problemi. Per esempio, se ci si dimentica di collocare il file xalan.jar nella directory <ANT_HOME>/lib, sar prodotto qualcosa di simile a quanto mostrato in Figura B-2.

Figura B-2. Quando ci sono errori di build, viene in genere fornito un registro dei problemi

Nel caso in cui la compilazione sia andata a buon fine, i file binari e dipendenti di Struts vengono copiati nella directory dist sotto quella top-level.

B.1.2 Installare la distribuzione binaria


Effettuare il download della distribuzione binaria e scompattarla in una directory. I file si trovano? in una sottodirectory denominata jakarta-struts all'interno della distribuzione. Come nel caso della distribuzione sorgente, per quella binaria possibile scaricare il file ZIP o un TAR compresso, andando all'indirizzo http://jakarta.apache.org/site/binindex.html. Se si costruisce Struts a partire dalla distribuzione sorgente, i file binari saranno gi scompattati nella directory dist/lib all'interno della directory top-level della distribuzione sorgente. Per il corretto funzionamento di un'applicazione Struts, sono necessari i seguenti passi. Tutte le eventuali modifiche necessarie per specifici container verranno discusse pi avanti nei paragrafi ad essi dedicati. 1. 2. 3. 4. 5. 6. Copiare tutti i file JAR dalla directory lib della distribuzione alla directoryWEB-INF/lib della web application. Copiare t ut ti i file tld nella directory WEB-INF della web application. Se si intende utilizzare il framework Validator, copiareil file validation-rules.xml nella directory WEB-INF. Si veda il Capitolo 11 per maggiori informazioni sui file di configurazione richiesti dal Validator di Struts. Creare un descrittore di deploy per la web application (web.xml) e copiarlo nella directory WEB-INF. Si veda il Capitolo 4 per i dettagli sulla configurazione del file web.xml. Creare un file struts-config.xml e copiarlo nella directory WEB-INF. Si veda il Capitolo 4 per i dettagli sul file di configurazione di Struts. Creare un file di propriet denominato cammons-logging.properties e copiarlo nella directory WEBINF/classes, includendo nel file la seguente riga:

org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog
7. Creare un resource bundle di Struts e codiarlo nella directory WEB-INF/classes. Si veda il Capitolo 12 per i dettagli sulla formattazione di questo bundle e il Capitolo 4 per sapere come configurare il file di configurazione di Struts affinch utilizzi questo bundle.

Non c' altro da fare. Per quanto riguarda l'installazione di Struts, questo quanto. Sar necessario costruire le necessarie pagine JSP e altri componenti del framework, quali le classi Action e ActionForm, ma quanto esposto tutto ci di cui c' necessit per far funzionare l'ambiente operativo. Un consiglio da seguire sicuramente quello di fare pratica con le applicazioni di esempio distribuite con Struts. Si tratta di web application complete, rilasciate sotto forma di package WAR che possibile copiare nella directory webapps del container. Per far funzionare Struts possibile utilizzare uno tra i vari container esistenti. In molti casi, le fasi dell'installazione appena esposte possono essere seguite alla lettera; tuttavia, non tutti i container sono identici e alcuni hanno bisogno di qualche aggiustamento rispetto a quanto elencato: tali modifiche, quando necessarie, sono esposte di seguito.

B.2 Consigli di installazione: Struts in Tomcat


A seconda della versione di Tomcat che si usa, potrebbero essere necessari alcune ulteriori fasi. Dal momento che Struts 1.1 richiede un container che supporti le specifiche Servlet 2.2 e JavaServer Pages 1.1, la versione minima di Tomcat che funzioner la 3.2. In ogni caso, se possibile, sarebbe meglio cercare di usare l'ultima versione di Tomcat (4.1.24), visto che presenta molti perfezionamenti che consentono migliori prestazioni delle applicazioni Struts.

B.3 Consigli di installazione: Struts in WebLogic


Con le versioni pi vecchie di WebLogic tipo la 5.1, era necessario effettuare diverse azioni non intuitive per far funzionare correttamente Struts. Alcuni di questi problemi avevano a che fare con il fatto che WebLogic estraeva solo i file .class e non altri, tipo i JAR. Comunque, con WebLogic 6.0, 6.1, e adesso con la versione 7.0, non dovrebbero esserci aggiustamenti particolari da effettuare.

B.4 Consigli di installazione: Struts in WebSphere


Se sono gi state applicate le patch pi recenti, non sono necessarie azioni particolari. In ogni caso, gli sviluppatori hanno segnalato grandi problemi quando si installa Struts sulla versione 3.5 di WebSphere.

B.5 Consigli di installazione: Struts in JRun


La versione pi recente di JRun non dovrebbe richiedere particolari attenzioni.

Appendice C. Risorse
Questa appendice fornisce un elenco di risorse utilizzabili per l'approfondimento dell'argomento Struts.

C.1 La mailing list di Struts


Di gran lunga, i "luoghi" migliori in cui imparare nuovi aspetti di Struts e porre domande sul tramework sono le due mailing list, STRUTS-USER e STRUTS-DEV, che contano circa 1750 iscritti e, nello scorso anno, sono cresciute al ritmo di 50-100 nuovi utenti al mese. La mailing list STRUTS-USER rivolta a chi sviluppa applicazioni per Struts, sia poco esperti che avanzati. bene consultare le FAQ (http://www.tuxedo.org/~esr/faqs/smart-questions.html) prima di inviare una qualsiasi domanda: sebbene non sia certo una lista con flame accaniti, si viene comunque attaccati se non si pondera bene la domanda che si invia. La mailing list STRUTS-DEV dedicata a coloro che lavorano sul framework stesso. Tutti possono inviare domande, ma sarebbe meglio evitare domande sulla propria applicazione o che siano pi adatte alla lista degli utenti. In quasi tutti i casi, a meno che non si sia tra chi contribuisce allo sviluppo del framework o si segnalino soluzioni ai bug, o sia comunque questo il livello di coinvolgimento che si intende raggiungere, ci si dovrebbe rivolgere alla STRUTS-USER. possibile effettuare l'iscrizione a una lista o a entrambe dall'indirizzo http://jakarta. apache.org/struts/index.html#Involved. Sono inoltre presenti diverse modalit per effettuare ricerche negli archivi delle mailing list (e non solo su quelle relative a Struts, ma su molte del progetto Jakarta). Una possibilit rappresentata dall'indirizzo http://www.mail-archive.com/index.php3?hunt=struts dove si pu scegliere se effettuare la ricerca nelle mailing list DEV o USER. Un'altra possibilit usare la caratteristica eyebrowse della Apache Software Foundation (http://nagoya.apache.org/eyehrowse/), che d la anche possibilit di ricercare attraverso tutte le mailing list relative ad Apache. Un ulteriore metodo di usare l'eccellente funzione di ricerca del sito jApache (http://www.japache.org/index.jsp).

C.2 La pagina web Struts Resource


Molti link interessanti e ricchi di informazione sono presenti sul sito principale di Struts. Uno dei migliori rappresentato dalla pagina Struts Resource (http://jakarta.apache.org/struts/resources/index.htmt). L'elenco diviso per categorie (tutorial, articoli, libri, etc). A seguire tutti i link si pu tranquillamente trascorrere un mese intero.

C.3 Sito di Validator


Il creatore di Validator, David Winterfeldt, ha sempre un suo sito all'indirizzo http://home.earthlink.net/~dwinter/eldtl, dove possibile trovare tutorial, guide per gli utenti di livello avanzato, e informazioni generali sul framework Validator, e da dove si pu inoltre scaricare la versione pi aggiornata di Validator.

C.4 Sito di Tiles


Anche il creatore della libreria Tiles, Cedric Dumoulin, ha il suo sito, all'indirizzo http://www.lifl.fr/~dumoulin/tiles/, dove si possono trovare tutorial e informazioni generali su Tiles, e da dove si pu inoltre scaricare la versione pi aggiornata della libreria.

C.5 Sito di Nested


Le librerie di tag Nested sono state rilasciate nel novembre 2001 e aggiunte alla parte principale del framework Struts diversi mesi dopo. Il creatore di queste librerie, Arroti Bates, ha un sito dove si possono trovare ottimi tutorial e moltissime informazioni sui tag annidati. L'indirizzo http://www.keyboardmonkey.com/next/index.jsp.

C.6 La Console di Struts


Ci sono gi diversi tool di Struts sotto forma di GUI e molti ne vengono creati di giorno in giorno, ma uno dei migliori e pi usati Console. L'applicazione Console stata trattata brevemente al Capitolo 4 e si pu scaricare dall'indirizzo http://www.jameskolmes.com/struts/. Il creatore di Console, James Holmes, divenuto recentemente un committer del framework Struts.

C.7 Easy Struts Project


Easy Struts Project, presente su SourceForge, fornisce tool e plug-in per svariati IDE al fine di migliorare la produttivit nella costruzione di applicazioni Struts. Ci sono plug-in per JBuilder, Eclipse e altri diffusi ambienti integrati di sviluppo. Maggiori informazioni su Easy Struts Project si trovano su http://easystruts.sourceforge.net.

Colophon
Our look is the result of reader comments, our own experimentation, and feedback from distribution channels. Distinctive covers complement our distinctive approach to technical topics, breathing personality and life into potentially dry subjects. The animal on the cover of Programming Jakarta Struts is a Percheron draft horse. This breed originated in the province of Le Perche in northwestern France. Purebreds are predominantly black or gray, and some have white markings on their heads and feet. They weigh an average of 2000 pounds and are usually 16 to 17 hands (64 to 68 inches) high. Percherons adapt well to many climates and are extremely versatile: their ruggedness and power makes them ideal for hauling heavy loads, their placid nature makes them easy to handle, and their natural grace and beauty complement the finest horsedrawn carriages. They can be ridden, and some have even been made into jumpers. In 732 A.D., Arabian horses abandoned by the Moors after the Battle of Tours were bred with native Flemish stock, producing the first Percherons. When the Crusaders invaded ten centuries later, more Arabian blood was added to the breed. However, the number of Percherons dwindled during the French Revolution, as horse breeding was suppressed. After the revolution, the new French government revived the breed by establishing a stud program for army mounts, using two Arabian sires at Le Pin, Normandy. In 1832 a foal named Jean Le Blanc was born in Le Perche, and all current Percheron bloodlines trace directly back to this horse. Le Perche has since exported purebred stock worldwide, and an official Breed Association registers Percherons to ensure that the line remains genetically pure. The breed was most popular after World War I, when farmers from both Britain and the United States became familiar with them while serving in the armed forces. In 1930, a U.S. census showed that registered Percherons outnumbered other draft horses by a margin of three to one, but after World War II, the farm tractor nearly replaced the breed entirely. However, it was kept alive by many farmers, especially those in Amish communities. Today, Percherons continue to work on farms and often perform in competition at livestock fairs. They are also used used to provide recreational hay, sleigh, and carriage rides. Emily Quill was the production editor and proofreader for Programming Jakarta Struts. Rachel Wheeler was the copyeditor . Jane Ellin and Linley Dolby provided quality control. Genevieve d'Entremont, Andrew Savikas, and Judy Hoer provided production assistance. Julie Hawks wrote the index. Emma Colby designed the cover of this book, based on a series design by Edie Freedman. The cover image is a 19thcentury engraving from the Dover Pictorial Archive. Emma Colby produced the cover layout with QuarkXPress 4.1 using Adobe's ITC Garamond font. David Futato designed the interior layout. This book was converted to FrameMaker 5.5.6 with a format conversion tool created by Erik Ray, Jason McIntosh, Neil Walls, and Mike Sierra that uses Perl and XML technologies. The text font is Linotype Birka; the heading font is Adobe Myriad Condensed; and the code font is LucasFont's TheSans Mono Condensed. The illustrations that appear in the book were produced by Robert Romano and Jessamyn Read using Macromedia FreeHand 9 and Adobe Photoshop 6. The tip and warning icons were drawn by Christopher Bing. This colophon was written by Phil Dangler. The online edition of this book was created by the Safari production group (John Chodacki, Becki Maisch, and Madeleine Newell) using a set of Frame-to-XML conversion and cleanup tools written and maintained by Erik Ray, Benn Salter, John Chodacki, and Jeff Liggett.

Potrebbero piacerti anche