MokaByte 61 - Marzo 2002 
MokaShop il negozio online di MokaByte
Progettare applicazioni J2EE multicanale
II parte: MVC ed approfondimenti sulla view
di
Giovanni Puliti
Prosegue la serie di articoli dedicati alla progettazione di applicazioni J2EE. Questo mese parliamo ancora di web applications proseguendo l'analisi della parte viewIntroduzione

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:

  1. 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
  2. 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

Giovanni Puliti, laureato in scienze dell'informazione è uno dei membri del team di consulenti di MokaByte per il quale svolge regolarmente attività di docenza presso le conferenze organizzate da MokaByte, consulenza e sviluppo progetti nel settore J2EE.
Membro della redazione di MokaByte, di cui è anche uno degli ideatori originali, dirige a tempo pieno il magazine da ormai qualche anno. Può essere contattato all'indirizzo gpuliti@mokabyte.it


Una commento conclusiva sulla foto dell'autore: "da ormai troppo tempo indugiavo nel cambiare la foto che mi ritraeva qualche anno fa in sede di (post)discussione della tesi, felice e sorridente, ormai libero da impegni accademici.
La foto che ho deciso di mettere, benché potrebbe in qualche modo far pensare ad un peggioramento sullo stato di salute mentale dell'autore, è stata scelta per una serie di motivi che esulano dal contesto di questo articolo. Confido nella clemenza del lettore nel non giudicare troppo duramente i segni del tempo trascorso...."

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it