Realizzare un Pet Store/seconda parte
Tutorial Java – Seconda puntata dedicata alla costruzione di un negozio virtuale con Pet Store, la più nota applicazione J2EE sviluppata da Sun. Questa volta esaminiamo la view, ovvero l’interfaccia del nostro negozio on line. Nell’ultima puntata chiuderemo con il controller .
Nella prima parte dell’articolo è stata fornita una visione d’insieme del PetStore, evidenziando le scelte “architettoniche/strutturali” effettuate a monte della sua implementazione. E’ stata in particolar modo sottolineata la suddivisione funzionale dell’applicazione in diversi moduli ed un’ulteriore suddivisione strutturale, ortogonale alla prima, in tre parti ben distinte tali da implementare il paradigma model-view-controller. Infine si è passati ad analizzare più nel dettaglio il model, evidenziando la correlazione tra le diverse esigenze scaturite dall’analisi dei singoli moduli e le corrispondenti scelte che hanno guidato la progettazione dei model di ciascun modulo. In questa e nella prossima parte di quest’articolo saranno prese in esame le rimanenti due parti del PetStore: la view ed il controller.
La view
La view determina il modo in cui l’interfaccia di un’applicazione si presenta all’utente. Nel Pet Store l’implementazione della view è contenuta completamente nel web tier. Ad ogni modo, anche in questo caso risulta evidente la flessibilità fornita dal paradigma model-view-controller: nulla vieterebbe, infatti, di sostituire o addirittura di far coesistere l’attuale view, che come si è detto è di tipo web-oriented ,con una costruita, ad esempio, mediante componenti swing che potrebbe riutilizzare al 100% il model ed il controller già sviluppati. Attualmente, comunque, la view è costituita da tre tipi di componenti: le pagine JSP usate per la generazione dinamica delle pagine HTML, i custom tag JSP, che implementano la presentation logic e i componenti JavaBeans che incapsulano i dati recuperati dal model. In particolare l’introduzione dei custom tag a partire dalla versione 1.2 della tecnologia JSP ha consentito, come verrà evidenziato nel seguito, di disaccoppiare la definizione della conformazione grafica di una pagina web, dai meccanismi con cui l’applicazione recupera i dati che dinamicamente andranno a popolare tale pagina. L’effetto di questo disaccoppiamento è tanto più importante se si tiene conto che, soprattutto per progetti di grandi dimensioni, i grafici che definiscono l’aspetto di un sito web impiegando principalmente l’HTML hanno delle competenze ben diverse dai programmatori che invece hanno il compito di sviluppare i dettagli implementativi dell’applicazione. In altri termini, mediante i custom tag JSP, gli sviluppatori hanno la possibilità di mettere a disposizione dei grafici delle primitive con una sintassi simile all’HTML tramite i quali questi ultimi possono interagire con il resto dell’applicazione.
Il meccanismo dei template
Nel Pet Store la struttura di tutte le videate che vengono presentate all’utente (a cui nel seguito si farà riferimento come screen) è definita in un unico template. L’uso dei template consente di separare gli elementi comuni a tutti gli screen, da quelli che invece cambiano ad ogni screen. Questo meccanismo, oltre a soddisfare la necessità di avere un look & feel comune a tutti gli screen, facilita il lavoro dello sviluppatore permettendogli di concentrarsi sulle problematiche relative ad un singolo screen e lasciando che il templete si occupi del resto. Il template comune a tutti gli screen del Pet Store è definito nella pagina template.jsp riportata di seguito:
<%@ taglib uri=”/WEB-INF/template.tld” prefix=”template” %>
<%@ page contentType=”text/html;charset=UTF-8″ %>
<html>
<head>
<title><template:insert parameter=”title” /></title>
</head>
<body bgcolor=”#FFFFFF”>
<table width=”100%” border=”0″ cellpadding=”5″ cellspacing=”0″>
<tr>
<td colspan=”2″><template:insert parameter=”banner” /></td>
</tr>
<tr>
<td width=”20%” valign=”top”><template:insert parameter=”sidebar” /></td>
<td width=”60%” valign=”top”><template:insert parameter=”body” /></td>
<td valign=”top”><template:insert parameter=”mylist” /></td>
</tr>
<tr>
<td colspan=”2″><template:insert parameter=”advicebanner” /></td>
</tr>
<tr>
<td colspan=”2″><template:insert parameter=”footer” /></td>
</tr>
</table>
</body>
</html>
Come si può notare, questa pagina jsp fa uso di una tag library costituita da un unico tag il cui comportamento è definito dalla classe com.sun.j2ee.blueprints.waf.view.template.tags.InsertTag e il cui compito è evidentemente quello di inserire nel template i vari file che di volta in volta customizzeranno il template stesso, in modo da formare lo screen richiesto dall’utente. Ciascuno screen può quindi essere visto come l’evoluzione di un template in funzione di come lo screen stesso è stato definito. Gli screen sono definiti nei file denominati screendefinitions_language.xml. Esiste uno di questi file xml per ciascuno dei linguaggi supportati dall’applicazione. Ad esempio, nel file screendefinitions_en_US.xml lo screen product, che mostra l’elenco dei prodotti di una determinata categoria, è stato definito come segue:
<screen>
<screen-name>product</screen-name>
<parameter key=”title” value=”Product” direct=”true”/>
<parameter key=”banner” value=”/banner.jsp” direct=”false”/>
<parameter key=”sidebar” value=”/sidebar.jsp” direct=”false”/>
<parameter key=”body” value=”/product.jsp” direct=”false”/>
<parameter key=”mylist” value=”/mylist.jsp” direct=”false”/>
<parameter key=”footer” value=”/footer.jsp” direct=”false”/>
</screen>
In altri termini, questo blocco di xml definisce quali stringhe di testo (nel caso la variabile booleana direct sia settata a true) o quali file (nel caso sia false) debbano essere inserite nel template nei punti indicati dai custom tag template:insert, al fine di ottenere lo screen product. La servlet com.sun.j2ee.blueprints.waf.view.template.TemplateServlet, alla sua inizializzazione, legge i file screendefinitions_language.xml per ciascun linguaggio definito e ne fa il parsing indicizzandone il risultato in una HashMap interna. Ogni volta che l’utente richiede un nuovo screen (ovvero un URL terminante con il suffisso .screen) la richiesta viene processata dalla stessa servlet. Il compito principale di questa servlet è quello di recuperare, dall’HashMap precedentemente inizializzata, l’oggetto Screen relativo allo screen richiesto nel linguaggio corrente e di metterlo nella request prima di fare il forward della pagina template.jsp. L’oggetto Screen è semplicemente un’altra HashMap in cui sono memorizzati i parametri relativi allo screen corrente, così come sono stati letti del file xml. Esso verrà poi recuperato dalla request dai custom tag template:insert dai quali verrà utilizzato per decidere cosa inserire nel template.
Il catalogo dei prodotti
Una volta compreso il meccanismo con cui gli screen vengono costruiti a partire dal template può rivelarsi utile scendere nei dettagli implementativi dei singoli screen per capirne il funzionamento. Il primo screen che si è scelto di analizzare è il product, che mostra una lista dei prodotti venduti in una singola categoria e di cui è stata precedentemente riportata la definizione xml. Come si vede da tale definizione la parte centrale di questo screen identificata dalla key body è il file product.jsp, di cui di seguito si riporta la prima parte del contenuto:
<%@ taglib uri=”/WEB-INF/waftags.tld” prefix=”waf” %>
<jsp:useBean id=”catalog” scope=”session”
class=”com.sun.j2ee.blueprints.catalog.client.CatalogClientHelper”/>
<p class=”petstore_title”>Products for this Category</p>
<waf:extract id=”catalog” scope=”session” property=”getProducts”
destinationScope=”request” destinationId=”pageresults”>
<waf:extract_parameter parameter=”category_id” />
<waf:extract_parameter parameter=”start” default=”0″ type=”int”/>
<waf:extract_parameter parameter=”count” default=”2″ type=”int”/>
<waf:extract_parameter arg=”en_US” type=”java.lang.String”/>
</waf:extract>
<waf:extract id=”pageresults” scope=”request” property=”getList”
destinationScope=”request” destinationId=”extractedCollection” />
<waf:extract id=”pageresults” scope=”request” property=”hasNextPage”
destinationScope=”request” destinationId=”hasNext” />
<waf:extract id=”pageresults” scope=”request” property=”hasPreviousPage” destinationScope=”request” destinationId=”hasPrevious” />
Come si può notare, per prima cosa viene recuperato dalla sessione un JavaBean istanza della classe CatalogClientHelper. Questa classe offre alcune facilities per leggere i dati relativi al catalogo dal model. Quindi viene invocato il metodo getProducts su questo JavaBean e l’oggetto com.sun.j2ee.blueprints.catalog.model.Page da questo ritornato viene messo nella request. Questa operazione viene effettuata usando il custom tag waf:extract che accetta i seguenti parametri di input:
• id: l’oggetto su cui viene chiamato il metodo da invocare;
• scope: lo scope da cui leggere l’oggetto di cui sopra;
• property: il nome del metodo da invocare;
• destinationScope: lo scope in cui riporre l’oggetto ritornato dall’invocazione;
• destinationId: l’id da assegnare all’oggetto ritornato.
All’interno del custom tag waf:extract viene inoltre utilizzato un altro custom tag che serve a leggere dei parametri dalla request e passarli al metodo che verrà invocato. In particolare, in questo caso, al metodo CatalogClientHelper.getProducts vengono passati il categoryID della categoria richiesta dall’utente, insieme all’indice del primo prodotto, al numero dei prodotti da mostrare ed alla lingua dell’utente. L’oggetto Page, che viene costruito dal model con le modalità già discusse nella prima parte dell’articolo, contiene semplicemente una List costituita dai Product richiesti, oltre a fornire alcuni metodi ausiliari che facilitano la suddivisione dei prodotti in più pagine. Successivamente, utilizzando ancora il custom tag waf:extract, vengono estratte dall’oggetto Page e messe nella request la lista dei prodotti presenti nella pagina corrente insieme a due variabili booleane che indicano se tale pagina ne ha almeno un’altra che la precede e un’altra che la segue. La seconda parte della pagina product.jsp, riportata nel seguito, stampa sullo stream di output la lista dei prodotti presenti nella pagina corrente:
<table width=”100%” cellspacing=”0″ cellpadding=”2″ border=”0″
bgcolor=”#FFFFFF”>
<waf:list collectionId=”extractedCollection” scope=”request”>
<tr><td class=”petstore_listing”>
<a href=”items.screen?product_id=<waf:listItem property=”id” />”>
<waf:listItem property=”name” /></a><br>
<waf:listItem property=”description” /></td></tr></waf:list>
</table>
Il custom tag waf:list mette a disposizione un iteratore sulla lista dei prodotti precedentemente inseriti nella request. In tal modo è possibile accedere ai singoli oggetti Product della lista che, come detto, raccolgono i dati relativi ai singoli prodotti. I custom tag waf:listItem consentono poi di accedere alle singole proprietà dei Product via reflection con una tecnica identica a quella usata per i comuni componenti JavaBean. La terza ed ultima parte della pagina mette a disposizione dei link alle pagine immediatamente precedente e successiva a quella corrente se presenti:
<div align=”right”><p class=”petstore_listing”>
<waf:bool id=”hasPrevious” scope=”request”>
<waf:true>
<waf:link href=”product.screen”>
<waf:queryParameter name=”category_id”>
<waf:parameter name=”category_id” />
</waf:queryParameter>
<waf:queryParameter name=”start”>
<jsp:getProperty name=”pageresults” property=”startOfPreviousPage” />
</waf:queryParameter>
<waf:queryParameter name=”count”>
<jsp:getProperty name=”pageresults” property=”size” />
</waf:queryParameter>
<waf:linkContent>Previous</waf:linkContent>
</waf:link>
</waf:true>
</waf:bool>
<waf:bool id=”hasNext” scope=”request”>
<waf:true>
<waf:link href=”product.screen”>
<waf:queryParameter name=”category_id”>
<waf:parameter name=”category_id” />
</waf:queryParameter>
<waf:queryParameter name=”start”>
<jsp:getProperty name=”pageresults” property=”startOfNextPage” />
</waf:queryParameter>
<waf:queryParameter name=”count”>
<jsp:getProperty name=”pageresults” property=”size” />
</waf:queryParameter>
<waf:linkContent>Next</waf:linkContent>
</waf:link>
</waf:true>
</waf:bool>
</p></div>
Il custom tag waf:bool consente di valutare una variabile booleana accettando come parametri il nome della variabile stessa e lo scope in cui essa è presente. Per cui se la variabile hasPrevious, precedentemente messa nella request mediante il custom tag waf:extract, ha valore true, vuol dire che la pagina corrente ne ha almeno un’altra che la precede. In tal caso la parte di jsp racchiusa nel custom tag waf:true verrà valutata e quindi verrà aggiunto, mediante il custom tag waf:link, un link alla pagina precedente, mentre in caso contrario verrà ignorata. Analogamente, se esiste una pagina successiva, verrà aggiunto un link che punti ad essa.
Il carrello
Quando l’utente richiede al Pet Store di compiere una particolare azione, come potrebbe essere quella di aggiungere un prodotto al carrello, viene invocato un URL terminante con il suffisso .do. In tal caso, in virtù di quanto specificato nel file di configurazione web.xml, tale azione viene gestita dalla servlet com.sun.j2ee.blueprints.waf.controller.web.MainServlet. Quando questa servlet viene inizializzata, viene letto il file mappings.xml, che fa il mapping tra ciascuna azione e lo screen che il sistema utilizzerà per rispondere a quell’azione, e il risultato del parsing di questo file viene memorizzato in un HashMap che poi viene salvata nel ServletContext. Ad esempio, per quanto riguarda l’azione cart.do, che viene appunto invocata ogniqualvolta si interagisce con il carrello, il mapping prevede che l’applicazione debba rispondere mostrando lo screen cart.screen:
<url-mapping url=”cart.do” screen=”cart.screen” isAction=”true”>
<action-class>
com.sun.j2ee.blueprints.petstore.controller.web.actions.CartHTMLAction
</action-class>
</url-mapping>
Oltre a ciò, come si può notare, in questo mapping, tramite il tag action-class, è anche definita la classe che dovrà occuparsi di eseguire la reale processazione dell’azione. Pertanto, quando questa servlet viene invocata, la prima cosa che fa è quella di passare la request all’oggetto RequestProcessor, che a sua volta la passa alla classe definita nel tag action-class. Come detto nell’articolo introduttivo, il RequestProcessor e le classi da questo invocate costituiscono la parte del controller che risiede sul web tier e pertanto verranno discusse nell’articolo che tratterà di quella parte dell’applicazione. Una volta che il controller ha eseguito tutte le azioni necessarie a soddisfare la richiesta dell’utente, il flusso di controllo dell’applicazione ritorna alla MainServlet che mediante la classe com.sun.j2ee.blueprints.waf.controller.web.flow.ScreenFlowManager decide qual è il prossimo screen da mostrare all’utente e ne fa il foward. Tale screen verrà poi composto con il meccanismo dei template precedentemente illustrato. Ad esempio, nel caso dell’azione cart.do lo ScreenFlowManager , indirizzerà l’utente verso il cart.screen, così come definito nel file mappings.xml. Nello stesso file xml sarebbe anche stato possibile definire una classe di tipo FlowHandler. In tal caso questo FlowHandler sarebbe stato invocato dallo ScreenFlowManager ed avrebbe deciso in maniera dinamica quale avrebbe dovuto essere il successivo screen da mostrare, in funzione del risultato dell’azione precedentemente effettuata. Per quanto riguarda la pagina cart.jsp, che costituisce la parte principale dello screen cart, essa appare molto simile alla pagina product.jsp, con la differenza che mentre le prima mostra la lista degli articoli inseriti nel carrello dell’utente, la seconda elenca i prodotti di una determinata categoria merceologica. A questo scopo viene usata la classe ShoppingCartWebHelper in maniera del tutto analogo a quanto veniva fatto con la classe CatalogClientHelper, con la differenza, già evidenziata durante l’analisi del model, che in questo caso i dati degli oggetti presenti nel carrello vengono acceduti passando per un ejb tier.