Introduzione
Questa nuova serie iniziata il mese scorso è
dedicata alla progettazione di applicazioni J2EE: lo
scopo è quello di guidare il lettore nella realizzazione
di applicazioni Java di fascia enterprise utilizzando
le tecnologie e le API più avanzate del momento,
prendendo in esame un caso concreto: MokaShop il negozio
online di MokaByte. Il progetto che si cercherà
di analizzare mese dopo mese è una applicazione
multicanale (che permetta la comunicazione con con client
web, applicazioni stand alone, client per dispositivi
embedded, e così via).
Nella puntata precedente si sono analizzate le caratteristiche
della applicazione nel suo complesso descrivendo l'architettura
complessiva ed anche il modello MVC che caratterizza
pesantemente la struttura della parte web di MokaShop.
Una delle caratteristiche di una applicazione multicanale
come quella in esame è la possibilità
di essere utilizzata in ambiti e per motivi differenti:
ad esempio gli utenti di MokaShop possono essere i clienti
finali, dei fornitori, addetti di MokaByte alle vendite,
collaboratori esterni ed altro ancora. Ricordando la
figura 1 (pubblicata anche nell'articolo precedente),
ognuno di questi utenti finali rappresenta una particolare
area di lavoro.
Figura 1 - Le aree di lavoro di MokaShop
In
tale figura si possono individuare aree di lavoro corrispondenti
alle sigle B2B (business to business) che indica l'interfacciamento
dall'azienda verso un'altra azienda ad esempio un fornitore,
il B2C (business to consumer) per la comunicazione azienda-cliente
finale, ed il B2E (business to employ) che indica la
serie di procedure operative in atto fra l'azienda ed
i suoi dipendenti/agenti/collaboratori.
Sebbene tali definizioni siano nate nell'ambito del
commercio elettronico, hanno finito per assumere una
valenza più ampia riferendosi a tutti quei casi
in cui vi è una interazione fra soggetti appartenenti
ad aree logiche differenti.
Il mese scorso abbiamo iniziato ad affrontare il canale
web di MokaShop, canale che nella maggior parte dei
casi è finalizzato al B2C, ma grazie alla sua
flessbilità è ampiamente utilizzando anche
nei settori B2B e B2E.
Questo mese, continuando a focalizzare l'attenzione
sul canale web, si approfondiranno gli aspetti relativi
alla View del Modello MVC in web application basate
su Servlet/JSP e si analizzeranno alcuni problemi tipici
di questo scenario presentando alcune possibili soluzioni.
Inzializzazione e configurazione di una web application
Una delle problematiche che ricorre spesso nella progettazione
di web application è quella relativa all'impostazione
dei parametri di configurazione utilizzati dai bean
presenti nella applicazione. Il nome del database a
cui connettersi oppure l'indirizzo di posta a cui inviare
messaggi di log in caso di errore, sono solo due esempi
di parametri da impostare nella applicazione al momento
della sua inizializzazione.
Uno degli strumenti utilizzabili in Java è quello
dei file in formato properties contenenti i parametri
di configurazione di una determinata classe.
Pur essendo questo un sistema molto utile e semplice
da utilizzare, porta con se alcuni piccoli inconvenienti:
dato che un file di properties può essere associato
ad una singola classe, si pone il problema di utilizzare
due file differenti per due istanze della stessa classe.
Inoltre poiché i file properties in genere risiedono
nella directory dove è presente la classe corrispondente
(secondo la logica dei packages), si ottiene come risultato
una mescolanza fra codice e file di configurazione,
fatto questo alquanto fastidioso nel caso in cui si
debba spostare una applicazione da un ambiente di lavoro
ad un altro (sviluppo test produzione).
In ogni caso avere molti file di configurazione sparsi
fra le molte directory della applicazione può
portare ad errori nel caso in cui si dimentichi di apportare
le necessarie modifiche anche uno solo di questi file.
Utilizzare
le pagine JSP come file di configurazione
Nelle web application si può ovviare a questo
problema ad esempio inserendo tutti i parametri di configurazione
non in un file qualsiasi ma in una delle pagine JSP.
Ad esempio nel caso in cui il flusso della web application
parta dalla index.jsp si può considerare tale
file come il punto di innesco di tutta l'applicazione,
e con la dovuta approssimazione lo si può utilizzare
come file di configurazione.
Nella applicazione MokaFind, applicazione utilizzata
per effettuare una ricerca di un articolo sul sito MokaByte,
nel file index.jsp si trova qualcosa del tipo
<%@
page errorPage = "MokafindError.jsp"%>
<jsp:useBean id="finder" scope="session"
class="com.mokabyte.mokafind.MokafindBean"
/>
<jsp:setProperty name="finder" property="serverName"
value="mokabyte.it/>
<jsp:setProperty name="finder" property="databaseName"
value="search"/>
<jsp:setProperty name="finder" property="maxNumberItems"
value="7"/>
<jsp:setProperty name="finder" property="maxLengthTitle"
value="50"/>
<jsp:setProperty name="finder" property="logAddress"
value="log@mokabyte.it"/>
Anche se non rappresenta la soluzione a tutti i problemi,
tale tecnica è piuttosto comoda dato che i file
JSP, essendo semplici file di testo, possono essere
editati anche da remoto all'interno di una sessione
telnet tramite un qualsiasi editor di testo; inoltre
essendo le pagine JSP ricaricate dinamicamente ad ogni
modifica, si possono eseguire cambiamenti anche a runtime
senza dover per questo ricompilare o far ripartire il
server.
Utilizzando questa tecnica si ha l'inconveniente di
dover prima creare il bean tramite il tag JSP usebean,
e solo successivamente impostarne i parametri. In conseguenza
di ciò all'interno del costruttore del bean tali
parametri non sono disponibili: quindi tutte le operazioni
per la cui esecuzione siano necessari tali valori, dovranno
essere effettuate in un secondo momento in un metodo
di inizializzazione invocato manualmente dalla pagina
JSP.
Nel caso in esame tale inizializzazione viene effettuata
nella pagina index.jsp tramite il seguente codice
<%
finderBean.init();
%>
Oltre
ad essere poco elegante tale soluzione può portare
facilmente ad errori nel caso in cui ci si dimentichi
di invocare il metodo init().
Un altro problema legato a questo tipo di soluzione
è dato dal fatto che per funzionare si prevede
che la web application abbia un flusso che parte dalla
pagina JSP contenente il codice di inizializzazione;
a volte però può capitare di avere flussi
paralleli all'interno della stessa web application,
e quindi la index.jsp può non essere il solo
punto d'innesco della applicazione.
Questo significa dover replicare in più punti
(pagine JSP) l'impostazione dei parametri (si veda il
caso di jump fra web application differenti, analizzato
più avanti in cui ).
Inoltre anche in questo caso non è risolto il
problema della indipendenza del codice dai parametri
di inizializzazione per gestire in modo agevole la migrazione
della applicazione fra i vari ambienti di lavoro.
Figura 2 - Una web application può non avere
un solo punto di innesco,
ma essere strutturata in modo da permettere vari flussi,
aventi inizio e
fine differenti da quello standard. In questo caso tutti
i file di inizio dei
flussi devono essere considerati come punti di innesco
e per questo
devono contenere i parametri di configurazione della
applicazione
L'evoluzione
della tecnica appena vista fa uso di un custom tag appositamente
pensato per inizializzare un bean tramite l'utilizzo
di un file di properties esterno alla web application,
senza la necessità di dover invocare alcun metodo
di inizializzazione.
Utilizzando tale bean, si potrebbe pensare di inserire
in ogni pagina di innesco il seguente codice
<%@
taglib uri='mbtl' prefix='mbtl' %>
<mbtl:usebean bean="mokacartBean"
scope="session"
classname="com.mokabyte.mokashop.mokacart.MokacartBean"
method="init"
filename="/../config/mokacart/MokacartBean.properties">
</mbtl:usebean>
Si
noti come il file properties è posizionato in
un path relativo indicato dal parametro filename, e
comunque esterno alla directory contenente la web application.
Come si è avuto modo di accennare nell'articolo
precedente, questa scelta permette, ad esempio nel passare
da un ambiente di sviluppo a quello di test, di spostare
solo la directory (o alternativamente il file .jar)
relativa alla web application. I file di configurazione
non dovranno essere toccati, ma ovviamente copiati ed
impostati i contenuti una volta per tutte in ogni ambiente
di lavoro.
Ad esempio, come si è avuto modo di accennare
nell'articolo del mese precedente, utilizzando per lo
sviluppo JBuilder, la struttura a directory del progetto
potrebbe essere la seguente
Figura 3 - struttura della directory del progetto
di JBuilder
in
questo caso la directory ../config/ è effettivamente
un directory parallela alla web application dove si
troverà il file di configurazione.
Nel momento in cui si dovesse passare all'ambiente di
produzione, si potrebbe immaginare di creare una web
application fittizia, dal nome config, contenente tutti
i file di configurazione di tutte le web application.
Ad esempio
Figura 4 - struttura delle directory delle varie
web application
A
questo punto dovrebbe essere intuitivo come, spostando
la sola directory della web application, i file di configurazione
non verrebbero toccati e per questo il tutto continuerebbe
a funzionare perfettamente senza dover tenere a mente
molti particolari relativi alla differente configurazione
dei due ambienti di lavoro.
Analizzando
ulteriormente il tag usebean, si può notare come
nel metodo doStartTag() di effettui il caricamento del
file di properties
public
int doStartTag() throws JspException{
System.out.println("UseBeanTag:doStartTag()");
...
properties = new Properties();
ServletContext sc = pageContext.getServletContext();
InputStream is = sc.getResourceAsStream(getFilename());
properties.load(is);
memorizzando
tali coppie di valori in un oggetto Properties.
All'iterno del metodo doAfterBody(), dopo aver ricavato
tramite reflection il nome del metodo inizializzazione
ed averlo invocato, si inserisce il bean nel contesto
con lo scope indicato dal parametro della pagina invocante;
a tal punto il bean sarà disponibile all'interno
della pagina tramite il tag JSP usetag
public
int doAfterBody() throws JspException {
System.out.println("UseBeanTag:doAfterBody()");
Class cls = null;
Object obj = null;
try {
cls = Class.forName(getClasse());
obj
= cls.newInstance();
Enumeration
keys = properties.keys();
Enumeration values = properties.elements();
Method dmth[] = cls.getMethods();
while(keys.hasMoreElements()){
String props = (String)keys.nextElement();
String iniProps
= ""+props.charAt(0);
String nameMethod
= "set"+iniProps+props.substring(1);
String value = (String)values.nextElement();
boolean trovato
= false;
for(int i = 0; i
< dmth.length; i++) {
if(!dmth[i].getName().equals(nameMethod)
||
dmth[i].getModifiers() != 1)
continue;
try
dmth[i].invoke(obj,
new Object[] {value });
catch(Exception
e)
throw
new JspException(e);
trovato
= true;
}
. . .
// si posiziona il bean come
attributo di
// contesto specificando lo
scope indicato nella pagina JSP
pageContext.setAttribute(getBean(),
obj, getPageScope());
}
}
catch( . . .
In
definitiva tale tag effettua con una sola scrittura
il caricamento del file di properties per il bean e
la successiva inizializzazione, senza dover inserire
nella pagina JSP pezzi di codice dipendenti dal caso
specifico. Anche nel caso di applicazioni con più
punti di innesco, si dovrà fare sempre riferimento
ad un unico file di configurazione.
Comunicazione
fra applicazioni differenti
Un modo sicuramente saggio di organizzare una applicazione
ad interfaccia web è quello di suddividere le
singole funzionalità in altrettante web application
basate su Servlet e pagine JSP. Ad esempio nel caso
di MokaShop si possono ricordare le web application
Community, MokaCart (carrello elettronico), Customers
(gestione del profilo dei clienti del negozio), MokaTrack
(gestione delle spedizioni), MokaPay (per il pagamento
ondine tramite carta di credito).
Lo svantaggio di utilizzare questa organizzazione differenti
è dato dal fatto che ogni web application viene
eseguita dall'application server in un container differente,
e questo implica anche la separazione dei vari spazi
di indirizzamento della memoria delle varie web application.
Questa organizzazione si scontra però con l'architettura
di J2EE: infatti per una precisa e corretta scelta progettuale
di Sun, è però impossibile mettere in
comunicazione due web application tramite il passaggio
di oggetti e o parametri vari.
Un tipico caso in cui sia necessaria la comunicazione
fra web application differenti è quello del salto
fra applicazioni: ad esempio in MokaShop Community e
MokaCart lavorano insieme nel caso in cui un cliente,
durante la procedura di acquisto desideri modificare
il suo profilo utente prima di procedere al pagamento.
In questo caso si effettua un salto da MokaCart verso
Community per permettere la modifica dei dati utente
ed a fine modifica si rientra in MokaCart. Durante questo
doppio salto si vuole che si verifichino due condizioni:
- dato
che l'utente ha già effettuato il login in
MokaCart si deve poter entrare in Community utilizzando
lo stesso profilo utente utilizzato in MokaCart senza
dover rieffettuare il login iniziale
- Alla
fine della procedura di modifica in Community si vuole
rientrare in MokaCart esattamente nello stesso punto
da cui si era usciti precedentemente (infatti MokaCart
può uscire verso Community da più punti
e può uscire anche verso altre applicazioni)
Figura 5 - funzionamento di un salto da una web
application A ad una web
application B e ritorno. Nella maggior parte dei casi,
durante questi salti si
desidera che sia mantenuto lo stato o che siano passati
dei parametri da
una applicazione all'altra.
Queste due condizioni sono facilmente ottenibile se
in un qualche modo si riesce a passare un oggetto da
una web application all'altra. In figura 5 è
riportato il grafico di questo scenario.
Ci possono essere molte possibilità per ottenere
questo obiettivo: in MokaShop si è scelto di
utilizzare una strada che per certi versi è un
misto delle tecniche che abitualmente si trovano nella
letteratura ufficiale [].
Si immagini di dover uscire dalla paginaA.jsp di MokaCart
verso paginaB.jsp di Community.In
questo caso, invece di passare direttamente da paginaA.jsp
a paginaB.jsp con un link diretto si può pensare
di creare due pagine di collegamento, che preparino
tale passaggio. Ad esempio in MokaCart si potrebbe aggiungere
una pagina dal nome mokacart_paginaA_EXITTO_community_paginaB.jsp
All'interno della quale effettuare le seguenti operazioni
<jsp:useBean id="WebAppLinkerBean" scope="session"
class="com.mokabyte.util.WebAppLinkerBean"
/>
<%
// si ricava dalla bean WebAppLinkerBean l'url contenente
la
//
codifica dei parametri da passare alla webappB
String nextPage = WebAppLinkerBean.getWebServer() +
WebAppLinkerBean.getStartApplicationReturnUrl();
// si rimuove dal contesto il WebAppLinkerBean per evitare
// errori in nel caso di back col browser
pageContext.removeAttribute("WebAppLinkerBean",
pageContext.SESSION_SCOPE);
//
si effttua un forward all'url della webappB
response.sendRedirect(nextPage);
%>
In questo caso, tramite un bean apposito, WebAppLinkerBean
si inseriscono tutti i dati che si vogliono passare
alla web application di arrivo, compreso il nome della
pagina di partenza che verosimilmente sarà il
punto dove si dovrà rientrare dopo aver effettuato
il salto di ritorno.
Invece nella pagina di entrata di Community si provvederà
a creare un webapplinkerbean nel quale si memorizzerà
il nome della pagina a cui rientrare dopo aver concluso
tutte le operazioni in Community, mentre nel bean CommunityBean
si memorizzeranno i parametri utente passati da MokaCart.
I valori necessari per configurare i due bean verranno
passati untamente come parametri della stessa invocazione,
ma grazie alle due semplici istruzioni
<jsp:setProperty
name="WebAppLinkerBean" property="*"
/>
<jsp:setProperty name="CommunityBean" property="*"/>
ogni
valore verrà settato nel bean corrispondente,
a patto di non avere nomi simili fra le proprietà
dei due bean.
Per quanto riguarda il salto di ritorno da Community
dovrà essere effettuato solo nel caso in cui
sia stato effettuato un salto di entrata da una web
application esterna, ma non se l'utente si è
collegato direttamente a Community senza effettuare
salti da altre applicazioni. In questo caso se l'ultima
pagina di Community si chiama end.jsp si può
pensare di predisporre una pagina di preEnd.jsp che
effettua il semplice controllo
if
(WebAppLinkerBean != null) {
pageContext.removeAttribute("MokacartLinkerBean",
pageContext.SESSION_SCOPE);
nextPage = WebAppLinkerBean.getWebServer() +
WebAppLinkerBean.getEndApplicationExitUrl();
}
else
nextPage="CommunityChangeEnd.jsp";
response.sendRedirect(nextPage);
infatti
la presenza nello scope di sessione della applicazione
di un bean di tipo WebAppLinkerBean è condizione
necessaria e sufficiente a garantire il fatto che sia
stato effettuato un salto in entrata. Proprio per garantire
tale condizione nella pagina di uscita si effettua una
rimozione di tale bean (dato che la pressione del tasto
di back del browser potrebbe portare a comportamenti
imprevisti).
Si noti come, nel caso in cui sia presente il bean di
collegamento, si ricavi il nome della pagina su cui
saltare indietro in modo automatico. Per questo motivo
la preEnd.jsp svolge il suo compito in modo indipendente
dalla particolare applicazione di salto.
Risulta chiaro allora come la pagina di salto in uscita
di MokaCart sia l'unica pagina che cambia in funzione
della applicazione a cui saltare.
Un
unico esempio per unirli
Gli esempi allegati questo mese raccolgono in due web
application tutto quanto presentato in questi due primi
articoli relativamente alle tecniche di programmazione
Servlet e JSP. Basate sul modello MVC le due web application
webappA e webappB sono implementate tramite l'utilizzo
di Servlet/JSP e Action.
Entrambe sono organizzate in directory secondo il seguente
schema (in grassetto tutto quello che riguarda il deploy
della web application) relativo alla webappA
webappA
src: sorgenti
webappA: radice della web application.
Contiene i file .jsp
WEB_INF
classes: contiene
i .class della web application
lib: contiene il
file tld della tag library
jsp: contiene alcuni
file .jar fra cui anche mbtl.jar
archivio
della tagl ibrary
config
webappA:
file di configurazione
classes: directory di output in fase di
compilazione
Questo
esempio è stato confezionato in modo da poter
verificare con mano i seguenti aspetti:
Inizializzazione: nel file index.jsp della web application
webappA si utilizza il tag mb:usebean per leggere un
parametro memorizzato all'interno del file Bean.properties
contenuto nella dir config. Tale parametro verrà
poi utilizzato come titolo nella pagina InsertUserName.jsp.
Come già accennato in precedenza per ottenere
questo scopo si utilizza il custom tag usebean facente
parte della tag library MokaTagLib: tale libreria di
tag è contenuta nel file mbtl.jar che secondo
le specifiche Sun è stato inserito nella dir
WEB-INF/lib di entrambe le due web application. Per
chi fosse interessato, i sorgenti relativi a tali custom
tag sono reperibili sotto le directory src ed in particolare
sotto i package com.mokabyte.taglibrary.
Si osservi che invece la webappB utilizza il codice
all'interno di index.jsp per la configurazione del bean
utilizzato.
MVC:
il flusso delle pagine e delle operazioni eseguite nella
web application è gestito tramite il famoso modello
MVC di cui si è abbondantemente parlato nella
puntata precedente. Le operazioni, gestite da tre servlet
indipendenti, da effettuare in corrispondenza delle
invocazioni dell'utente possono essere essenzialmente
tre: jump, show e run. La prima operazione corrisponde
ad un salto da una applicazione all'altra. La show corrisponde
a mandare in visualizzazione una pagina JSP, mentre
il run provocherà l'esecuzione di una azione.
Nel file WEB-INF/web.xml sono state inserire le associazioni
fra url pattern e servlet da eseguire
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.run</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>router</servlet-name>
<url-pattern>*.show</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>jump</servlet-name>
<url-pattern>*.jump</url-pattern>
</servlet-mapping>
I jump sono gestiti dal servlet JumpServlet il quale
legge i parametri di configurazione (gli url su cui
effettuare i salti) dal file /config/webappa/jumps.xml
Ad esempio ecco il codice XML che definisce un salto
<jump
NAME="webappb" CLASS="com.mokabyte.webappa.JumpAction">
<webServer>http://localhost/</webServer>
<startAppExitUrl>webappa/webappa.jump</startAppExitUrl>
<startAppReturnUrl>webappa/AfterModify.jsp</startAppReturnUrl>
<endAppExitUrl>webappb/End.jsp</endAppExitUrl>
<endAppEnterUrl>webappb/index.jsp</endAppEnterUrl>
</jump>
Il
cui significato dei parametri dovrebbe essere piuttosto
intuitivo traducendo il loro nome, servono per impostare
le coordinate del salto, ovvero da quale pagina di webappA
si vuole uscire (parametro startAppExitUrl), dove rientrare
in caso di errore (parametro startAppReturnUrl), in
quale pagina entrare in webappB (parametro endAppEnterUrl)
e da dove uscirne (parametro endAppExitUrl)
Il
file mappings.xml contengono tutte le informazioni necessarie
per associare un url del tipo *.show con una pagina
JSP, ad esempio
<mapping
CODE="insert-name-form.show"
PAGE="InsertUserName.jsp"
FORWARD="true"/>
Mentre
in actions.xml sono inserite le configurazioni per poter
eseguire le varie azioni: ad esempio
<action
NAME="insert-name"
CLASS="com.mokabyte.webappa.InsertUserNameAction">
<executed>name-updated</executed>
<failed>name-not-valid</failed>
</action>
<mappings>
<response CODE="name-updated"
PAGE="welcome.show"
FORWARD="false"/>
<response CODE="name-not-valid"
PAGE="insert-name.show"
FORWARD="false"/>
</mappings>
Si
noti che il tag action indica il nome della classe corrispondente
alla azione, i codici da restituire in caso di risultato
corretto/errato ed anche la vista da visualizzare nel
caso in cui il controllo non debba essere passato in
cascata ad un'altra azione.
Conclusione
Questo mese si è conclusa con una ulteriore approfondimento
pratico della teoria esposta il mese scorso ed in parte
anche in questo articolo. Dopo aver quindi affrontato
la superficie della applicazione, ovvero l'interfacciamento
con il client del canale web, inizierà la trattazione
delle parti più centrali della applicazione.
Rimando alla analisi degli esempi che possono essere
scaricati tramite il link presente nella sezione successiva.
Bibliografia
[MSHOP1] - "Realizzazione di applicazioni J2EE
multicanale" - di Giovanni Puliti - MokaByte n°
60 Febbraio 2002 - www.mokabyte.it/2002/02
[PT] - "Pet Store" url della web application
di esempio
[JW] - "UI design witrh Tiles and Struts"
di Prakash Malani pubblicato su JavaWorld Gennaio 2002
- www.javaworld.com/javaworld/jw-01-2002/jw-0104-tilestrut.html
[MS] - Le web application del negozio online di MokaByte
si possono provare collegandosi all'indirizzo di http://www.mokabyte.it/mokashop
|