MokaByte 68 - 9mbre 2002 
JSP 1.2: JSTL La JSP
JSP 1.2: JSTL La JSP Standard Tag Library - II parte
di
Lavinio Cerquetti
Dopo aver analizzato il nuovo ruolo del framework JSP [1] come layer di interfaccia utente Web all'interno di applicazioni Java Enterprise, in particolare alla luce dell'introduzione e della prossima standardizzazione della JSP Standard Tag Library [2], siamo pronti per applicare le nostre nuove conoscenze alla realizzazione di una Web Application, ed in particolare all'implementazione dello strato di interfaccia utente Web di una semplice applicazione corporate

Il problema
Il nostro cliente è la famigerata multinazionale ACME (sì, proprio i fornitori ufficiali di Willy il Coyote) questa volta nei panni di impresa operante nel settore dell'IT; per via delle sue necessità tecniche e commerciali, tale ditta possiede un sistema informativo di notevoli dimensioni, una parte del quale siamo chiamati a gestire.
La nostra responsabilità verte in particolare sul mantenimento e sul controllo del sistema informativo del quartiere centrale di ACME, sparpagliato in quattro edifici - creativamente etichettati 'A', 'B', 'C' e 'D' - ognuno ovviamente composto di diversi piani e stanze.
La gestione di un tale parco hardware e software presenta notevoli difficoltà, ragion per cui si decide di implementare una Web Application, tramite la quale dovrà essere possibile monitorare in tempo reale lo stato di funzionamento dei diversi componenti del sistema informativo, in modo da poter inviare rapidamente in loco un tecnico in caso di problemi; dal momento che prevenire è meglio che curare, inoltre, la nostra Web Application dovrà tener conto anche dei dati in tempo reale riguardanti il carico dei diversi apparati, così da poter individuare e risolvere potenziali problemi in anticipo, prima che essi portino al malfunzionamento o all'interruzione di una parte dei servizi.

La nostra applicazione dovrà quindi interfacciarsi sia ai diversi database di ACME, contenenti l'inventario aggiornato dei sistemi da monitorare ed i loro profili di configurazione, sia ad ipotetici sistemi real-time di verifica del carico; i dati provenienti da entrambe questi sorgenti dovranno venire integrati trasparentemente, così da creare l'illusione di una sorgente dati unica.

Nella sua versione iniziale, l'applicazione in oggetto dovrà mettere in grado gli operatori di verificare lo stato di funzionamento dei diversi dispositivi, oltre che di gestire la relativa 'base dati globale' tramite inserimenti, modifiche e cancellazioni di nodi di rete.
Tali operatori utilizzeranno una quantità di sistemi client differenti, sulle cui caratteristiche non è possibile, o è semplicemente antieconomico, imporre vincoli di uniformità: in sostanza, si richiede che l'applicazione sia portabile ed indipendente da piattaforme specifiche ed eventuali moduli software installati presso i sistemi degli utenti, che potranno variare nel corso del tempo, e sui quali si vogliono minimizzare i costi di manutenzione.

 

Analisi del problema
La struttura inerentemente distribuita dell'applicazione si presta assai bene ad una sua realizzazione in un contesto a noi noto, vale a dire l'ambiente J2EE.
Inoltre le caratteristiche dell'interfaccia ed i requisiti di universalità dell'accesso ci indirizzano in maniera naturale all'implementazione di un client Web-based.
L'implementazione dell'applicazione richiesta - che con molta originalità battezzeremo 'NetView' - è tutt'altro che elementare, e come ogni sistema distribuito richiede la valutazione di un numero significativo di fattori teorici e pratici.
Nel corso della nostra trattazione ci concentreremo essenzialmente sugli aspetti legati agli scopi di questa serie di articoli, vale a dire primariamente sulle caratteristiche del layer di presentazione (interfaccia utente) e più in breve sulle scelte fondamentali nel design di sistemi distribuiti con interfacce utente in Java, senza una comprensione delle quali la conoscenza di JSP, JSTL e Java Server Faces [3] non solo non sarebbe efficace, ma rischierebbe addirittura di portare all'implementazione di architetture sbilanciate e penalizzanti.
L'implementazione di esempio di NetView è scaricabile dalla sezione Risorse: la sua installazione, nonché la verifica ed il confronto dei sorgenti - contenuti nell'archivio .war - con il materiale presentato in questo articolo, sono fortemente raccomandati e costituiscono un ausilio fondamentale per la comprensione degli argomenti trattati.
NetView, secondo quanto detto, è composto di tre blocchi fondamentali, che andiamo ora ad esaminare.

 

Il sistema informativo
Il sistema informativo cui NetView deve interfacciarsi è costituito da un insieme di risorse eterogenee e probabilmente appartenenti a generazioni informatiche diverse. Questa è una situazione che si presenta con continuità nella realizzazione di applicazioni distribuite, in cui il nuovo deve non solo convivere, ma utilizzare e 'rimodernare' l'esistente, possibilmente senza introdurre alcun mutamento in sistemi, ancorché datati, comunque perfettamente funzionanti e centrali per la vita d'impresa e per la gran parte dei software di gestione e produzione.
Nel nostro caso è ad esempio ragionevole attendersi di aver a che fare con una pluralità di database multigenerazionali, sistemi di controllo in tempo reale, sistemi di distribuzione ad oggetti e componenti ad accesso diretto, i quali NetView dovrà integrare e gestire in maniera armonica, fornendo l'astrazione di un sistema informativo unitario.

 

Le regole di business logic
Lo strato di business logic è il contenitore delle regole di trattamento dei componenti del sistema informativo; in altre parole mentre lo strato 'sistema informativo' deve fornire a NetView una visione globale ed un accesso unitario ad un mondo in realtà segmentato e probabilmente disorganizzato, lo strato 'business logic' incapsula le regole per l'ottimizzazione e la gestione sicura di tale patrimonio informativo, indipendentemente da come esso risulti fisicamente raggiungibile ed utilizzabile.

 

Lo strato di presentazione
Il presentation tier, su cui si concentra in particolare la nostra attenzione, è responsabile per la realizzazione dell'interfaccia utente, vale a dire per quei meccanismi software che consentono agli utenti di accedere ed interagire con i contenuti del sistema informativo. Al contrario di quest'ultimo e dello strato di business logic l'interfaccia utente non è necessariamente 'una'. Il suo ruolo è infatti semplicemente quello di 'punto d'accesso alle informazioni', ed è quindi perfettamente legittimo implementare nel corso del tempo diverse interfacce utente - eventualmente basate su framework differenti (HTML, Swing, SWT [4], Applet, architetture particolari quali Droplets [5], etc.) - le quali convivono e permettono a diverse classi di utenti di accedere al medesimo sistema informativo, del quale forniscono viste di volta in volta diverse per struttura e funzionalità. Nel nostro campo, un esempio di presentation tier alternativo potrebbe consistere in un'applicazione WML o light-HTML con la quale un gruppo gli amministratori di sistema potrebbe accedere attraverso un'interfaccia utente semplificata ad informazioni on-time sui soli componenti di loro diretta responsabilità.
Si noti infine che nel senso più ampio del termine i servizi Web oggi così di moda non sono altro che interfacce utente, essendo il loro fine precisamente quello di fornire un punto d'accesso e di interazione con un patrimonio informativo a prescindere dalle sue caratteristiche fisiche; la loro particolarità è quella di non essere rivolti ad utenti reali, ma ad utenti automatizzati, vale a dire a procedure o oggetti remoti. Un servizio Web, insomma, è un'interfaccia utente per computer.

 

L'implementazione del sistema informativo
Esaminate sommariamente le componenti fondamentali del nostro sistema, passiamo a considerarne l'implementazione pratica.

Un'implementazione completa del sistema informativo è, ovviamente, al di là dei nostri interessi; per i nostri fini viene fornita una semplice implementazione costituita da una classe singleton InformationSystem, che rappresenta la visione unitaria del mondo back-end di NetView ed implementa il pattern Business Delegate. Tale implementazione non comunica con componenti esterni, ma utilizza le classi Collection di Java per implementare meccanismi minimi di memorizzazione e simula la comunicazione con componenti esterni di controllo tramite la generazione di valori casuali.
Volendo paragonare l'intero sistema informativo ad un iceberg, la classe InformationSystem ne rappresenta la punta, e ne costituisce l'unica parte visibile: per tutti i rimanenti componenti di NetView l'unico punto di interazione con il sistema informativo è la classe InformationSystem.
In un'applicazione reale tale classe verrebbe probabilmente implementata come una somma di strati EJB, facenti capo ad un Session EJB stateless secondo il Pattern Session Facade, il quale avrebbe la responsabilità di organizzare ed armonizzare i dati provenienti dai vari database, sistemi di controllo remoto e dispositivi di rete. Alla luce delle recenti evoluzioni del protocollo EJB, la comunicazione con sistemi esterni di controllo potrebbe venire realizzata tramite Message-Driven EJB - ad esempio basati su JMX - le cui caratteristiche di asincronicità risulterebbero ideali in questo contesto.
Sono ovviamente ipotizzabili implementazioni anche assai diverse da quanto abbozzato,e la sempre crescente disponibilità di protocolli alternativi, realmente object-oriented e meno penalizzanti dal punto di vista delle performance, permette - e secondo alcuni consiglia - soluzioni diverse e non basate su EJB; si rimandano i lettori interessati a tali argomenti alla vasta letteratura esistente in materia, così come agli articoli incentrati su tematiche di Software Design e Pattern in corso di pubblicazione presso MokaByte.

Il nostro sistema informativo è basato su due tabelle: la tabella Nodi, che contiene sostanzialmente l'inventario dei dispositivi di ACME che dobbiamo monitorare, e la tabella Categorie, che contiene l'elenco delle classi di dispositivi gestibili (Router, Application Server, etc.). Tali tabelle possiedono chiavi primarie elementari e sono collegate tra di loro tramite una relazione uno a molti (Categorie -> Nodi).

 

L'implementazione dello strato di business logic
In applicazioni di produzione il nostro business tier potrebbe venire implementato tramite una pluralità di oggetti remoti - quali Entity EJB confinati al server layer dell'applicazione o oggetti JDO, anche in considerazione delle caratteristiche delle diverse basi dati - fisicamente distribuiti in rete tramite CORBA, RMI o framework innovativi come JavaSpaces; tale pluralità di componenti risulterebbe accessibile all'interfaccia utente tramite uno o più oggetti proxy basati sui pattern Business Delegate e Data Access Object.

Nell'implementazione d'esempio lo strato di Business Logic è fuso all'interno della classe InformationSystem, ed esporta all'interfaccia utente i contenuti del 'database' attraverso oggetti facenti capo ad apposite classi lightweight. Tali oggetti - usualmente denominati Value Object - sono per definizione 'plain object', vale a dire poco più che semplici contenitori di dati funzionalmente elementari, costruiti in maniera tale da ridurre il traffico di rete e da non generare accessi distribuiti per l'accesso alle loro proprietà, ed in implementazioni reali potrebbero essere classi 'specchio' di oggetti Entity EJB.

La versione proposta di NetView conosce due classi di questo genere, CategoryRow e NodeRow, i cui oggetti corrispondono rispettivamente alle righe delle tabelle Categorie e Nodi.
Alla classe NodeRow spetta anche il compito di integrare informazioni non ottenute dal nostro ipotetico database: a questo fine, per simulare una lettura da sistemi remoti di controllo, le colonne 'Carico attuale' e 'Data ed ora di ultima rilevazione del carico' della tabella Nodi vengono rigenerate ad ogni accesso.
Si noti che la classe InformationSystem, nella sua veste di strato di business logic, funge da Business Delegate verso i metodi generici di accesso ai dati contenuti nelle classi Row, ricoprendo così nei confronti degli altri componenti NetView il ruolo di punto unico di accesso ai dati.

 

L'implementazione dello strato di interfaccia utente
L'analisi del layer di presentation tier completa la sintesi strutturale dell'implementazione di NetView che, seppur applicazione giocattolo, deve per evidenti motivi didattici presentare un'impostazione ed un'architettura quanto più realistiche possibili.
A livello di presentation tier si pone il problema della separazione tra forma e contenuti, la cui soluzione ideale consiste in un decoupling formale e rigoroso.
La struttura implementata rispetta questi requisiti e separa chiaramente le funzionalità di presentazione dei dati da quelle della loro preventiva elaborazione e della comunicazione con gli strati superiori del sistema, ed è basata su un'architettura integrale MVC e sui pattern Front Controller e Service to Worker.
Un nodo fondamentale di ogni applicazione distribuita, infine, è costituito dai servizi di autenticazione / autorizzazione e tracing; nel contesto del nostro esempio, tuttavia, tali aree funzionali non rivestono per noi alcun interesse, e non essendo peraltro legate alla comprensione dei meccanismi e delle dinamiche di funzionamento ed integrazione di JSP e JSTL non verranno prese in considerazione dall'implementazione di NetView.

 

Il Front Controller
Il punto d'accesso unico all'applicazione è costituito dal servlet FrontControllerServlet, per cui viene impostato nel file web.xml l'alias 'main' così da permettere l'accesso alla nostra applicazione tramite un'URL del tipo 'http://<server>/netview/main'.
La responsabilità di tale servlet consiste unicamente nel ricevere dal browser la richiesta di una determinata azione da parte dell'utente ('visualizza un elenco di nodi', 'modifica un nodo', etc.) e nel forwardare tale richiesta ad un'appropriata classe di gestione Java, genericamente indicata come Dispatcher: il Front Controller non possiede alcuna conoscenza semantica in merito alle azioni dell'interfaccia utente, e si limita semplicemente a smistare le richieste ricevute alla classe pertinente.
Tale classe ritorna l'URL di un componente dinamico - denominato View e nel nostro caso implementato tramite pagine JSP - il quale implementa la vera e propria 'interfaccia utente' e che verrà attivato dal Front Controller.
Spetta al Front Controller anche il compito di gestire situazioni di errore a livello globale di applicazione e di impostare nel contesto di esecuzione delle View eventuali valori globali, nel nostro caso un riferimento al sistema informativo.
Nell'implementazione fornita l'associazione tra classi Dispatcher ed azioni viene impostata in maniera statica all'interno del codice di FrontControllerServlet; in un'applicazione reale tali dati verrebbero piuttosto desunti da un repository esterno - quale un servizio JNDI - presso il quale ogni singolo Dispatcher si potrebbe registrare all'avvio.

 

I Dispatcher
Per ogni azione richiesta dall'utente viene invocata un'apposita classe di gestione, denominata Dispatcher. La responsabilità di tale classe consiste nell'analizzare il contesto di esecuzione e, alla luce dei parametri ricevuti e delle proprie conoscenze semantiche, nell'impostare il contesto di pagina per l'attivazione della View successiva.
Questo passo comprende l'istanziazione o l'acquisizione di dati e riferimenti ad oggetti in grado di gestire sezioni complesse del processo di presentazione ed elaborazione dei dati. Tali oggetti vengono denominati View Helper, e vengono resi disponibili alla View in maniera da permetterle di delegare ogni attività non elementare a componenti esterni.
L'obiettivo è semplificare al massimo il contenuto delle View, che dovrebbero idealmente consistere unicamente in presentazione di dati ed in interazioni algoritmicamente elementari con componenti esterni di gestione.
Così facendo si riduce al minimo l'accoppiamento tra presentation e business layer, aumentando la flessibilità, l'estensibilità e la scalabilità del sistema, abbattendone al contempo i costi di manutenzione e gestione. Inoltre questa pratica conduce a pagine JSP effettivamente prive di codice Java - nell'intera implementazione di NetView non è presente nessuno scriptlet JSP - che gli Web Designer possono progettare, realizzare ed estendere in piena autonomia, a tutto vantaggio dell'economia dello sviluppo collaborativo del software.
Nell'implementazione d'esempio di NetView vengono utilizzati come oggetti View Helper i Value Object CategoryRow e NodeRow, oltre allo stesso oggetto singleton InformationSystem.
Infine il Dispatcher può comunicare liberamente con l'InformationSystem attraverso l'handle fornitogli dal FrontController a cui, in caso di errori che non è in grado di correggere o gestire, può propagare eventuali eccezioni.

 

Gli oggetti View
La presentazione dell'applicazione è responsabilità degli oggetti View, le cui caratteristiche tecniche dipendono fortemente dall'architettura della soluzione distribuita: una finestra in Swing, per intenderci, è un oggetto View tanto quanto una pagina HTML.
Nel nostro caso gli oggetti View sono pagine JSP, le quali realizzano l'interfaccia utente sulla base del contesto impostato dal Dispatcher, sfruttano gli algoritmi di gestione dei dati contenuti negli oggetti View Helper per elaborare e presentare le informazioni tramite JSTL ed HTML e dialogano con il resto del sistema NetView per mezzo del Front Controller, interagendo con il quale ottengono l'effetto di 'far girare' l'applicazione.
Si noti che le cardinalità di View, Dispatcher ed azioni non devono necessariamente coincidere: un unico Dispatcher, o un'unica View, possono essere responsabili per diverse azioni.

 

Analisi dell'interfaccia utente
Dal momento che la nostra attenzione verte essenzialmente sull'interfaccia utente, ha senso analizzare NetView a partire dalle diverse azioni cui esso è in grado di rispondere.

La prima azione che considereremo è front: essa è anche l'azione di default, vale a dire l'azione automaticamente eseguita se l'utente non ha richiesto alcuna azione particolare tramite il parametro action.
Tale azione fa capo alla View front.jsp, la cui prima riga - come per tutte le View - dichiara l'utilizzo della libreria JSTL core:

"<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>"
.

Il compito dell'azione front è presentare la home page della nostra applicazione, che visualizza un messaggio di benvenuto unitamente alla data ed ora attuali ed alla stringa di identificazione del browser dell'utente.
La prima informazione viene ottenuta mediante una semplice JSP action "<jsp:useBean>" tramite la quale viene istanziato un oggetto java.util.Date, rigenerato ad ogni accesso alla pagina e visualizzato tramite un tag JSTL <c:out>. Come ci si può attendere ,la visualizzazione di un oggetto Java passa per la sua rappresentazione sotto forma di String, ottenuta automaticamente da JSTL invocando il relativo metodo toString().
L'identificazione del browser è ancora più semplice, e consiste semplicemente nella visualizzazione della proprietà user-agent dell'oggetto header - uno degli oggetti impliciti presentati nel precedente articolo - corrispondente al request header della richiesta HTTP corrente. Trattandosi di un oggetto implicito non è ovviamente richiesta la sua dichiarazione tramite "<jsp:useBean>".
La home page - come ogni pagina di NetView - include le due risorse esterne topbar.jsp e bottombar.jsp.
La struttura delle pagine di NetView è in effetti composta di tre sezioni: una barra superiore, un frame di contenuti ed una bottom bar. Questa struttura, con diverse variazioni, è una costante di gran parte delle Web Application. Le due barre hanno il compito di realizzare un look & feel standard che dia un senso di continuità tra le diverse pagine prodotte dall'applicazione.
Il frame di contenuti rappresenta la parte dell'interfaccia che effettivamente varia durante la navigazione all'interno dell'applicazione. Nella nostra implementazione ogni View (ossia ogni pagina JSP) si preoccupa unicamente del disegno di tale frame, e si limita ad utilizzare le due risorse suddette per le altre sezioni.
La barra superiore ha inoltre la funzione di menu principale, e consente all'utente di accedere alle funzionalità di visualizzazione dei nodi (sia globale che per categoria). La selezione della categoria di visualizzazione dei nodi avviene tramite un'interazione JavaScript / JSTL realizzata dal seguente codice:

<form method="post" name="nodesbyidForm" action="/netview/main">
<input type="hidden" name="action" value="nodesbycatid" />
<select name="catId" onChange="document.nodesbyidForm.submit();" >
<c:forEach var="category" items="{informationSystem.allCategories}">
<option value="<c:out value='{category.id}'/>">
<c:out value="{category.desc}" />
</option>
</c:forEach>
</select>
</form>

La comprensione di questo codice è la chiave per l'utilizzo corretto di JSTL: sorgenti di NetView alla mano, i punti salienti di queste righe sono:

Tag <select>: Apriamo una listbox, che in seguito alla selezione di un elemento causerà automaticamente il post del form, portando all'esecuzione dell'azione nodesbycatid (visualizza nodi per categoria);
tag <c:forEach>: iteriamo sulla proprietà allCategories di informationSystem, un riferimento al nostro sistema informativo che viene reso disponibile ad ogni View; il suo metodo getAllCategories() ritorna l'elenco di tutte le categorie disponibili sotto forma di Collection di oggetti CategoryRow; la variabile category, dichiarata nell'attributo var del tag <c:forEach>, sarà il nostro iterator su tale Collection;
a questo punto per ogni elemento della suddetta Collection, vale a dire per ogni CategoryRow, verrà eseguito il codice sino alla chiusura del tag <c:forEach>;
per ogni CategoryRow viene generato un tag <option>, il cui attributo value è pari al valore visualizzato dal tag <c:out value='{category.id}' />, vale a dire al codice della categoria (proprietà getId() di CategoryRow). L'innesto di tag JSTL ed HTML è perfettamento legittimo e completamente trasparente al client: visualizzando il sorgente HTML all'interno del browser si noterà che i tag JSTL sono stati sostituiti dal loro valore. Nei sorgenti riportati, per chiarezza, in ogni innesto JSTL / HTML si è provveduto ad utilizzare caratteri terminatori di stringa differenti (" e ') anche se ciò non è assolutamente richiesto da JSP. In effetti, queste due righe avrebbero prodotto esattamente lo stesso risultato:


<option value="<c:out value='{category.id}'/>">
<option value="<c:out value="{category.id}"/>">

il valore visualizzato in ogni <option> all'interno della list box è {category.desc}, vale a dire la descrizione della categoria (proprietà getDesc() di CategoryRow);
si noti che né l'oggetto informationSystem né l'oggetto category vengono dichiarati o definiti in azioni JSP "<jsp:useBean>"; nel primo caso, infatti, ci limitiamo ad utilizzare il meccanismo di ricerca automatica di JSTL, già esaminato nel precedente articolo, che raggiunge automaticamente gli oggetti nei contesti di pagina, richiesta HTTP, sessione ed applicazione. Nel secondo caso è invece sufficiente la definizione di tale elemento come Iterator nell'attributo var del tag <c:forEach>.

La top bar e la bottom bar, il cui contenuto è tendenzialmente statico, vengono incluse da ogni View con delle direttive "<%@ include %>".

Selezionando 'Tutti i nodi' dal menu principale viene eseguita l'azione allnodes: tale azione, attraverso il Dispatcher AllNodesDispatcher, porta all'attivazione della view nodes.jsp.
Selezionando invece dal menu principale una categoria viene eseguita l'azione nodesbycatid, la quale attraverso il Dispatcher NodesByCatIdDispatcher ci conduce alla medesima View nodes.jsp.
Tale View è in grado di discriminare sulla presentazione che le viene richiesta in base al valore del parametro action, che viene reso disponibile ad ogni View.
In ambedue i casi la nostra View creerà una tabella HTML per visualizzare i dati della Collection nodes, che viene inserita nel contesto della richiesta di pagina dai Dispatcher. Il Dispatcher AllNodesDispatcher inserisce in tale attributo l'elenco di tutti i nodi disponibili (AllNodesDispatcher.java:19-23) mentre il Dispatcher NodesByCatIdDispatcher vi inserisce i soli nodi appartenenti alla categoria richiesta:

// Recuperiamo la categoria che funge da criterio
int catId=Integer.parseInt(request.getParameter("catId"));

// Ed otteniamo i nodi corrispondenti
Collection nodes=informationSystem.getAllNodesByCategoryId(catId);

// Inseriamo i nodi nel contesto della richiesta
request.setAttribute("nodes",nodes);

// Rendiamo disponibile la categoria selezionata alla View
request.setAttribute("category",
                     informationSystem.getCategoryById(catId));

Il codice presentato non fa altro che ricuperare il parametro catId (confrontare le righe esaminate in precedenza, topbar.jsp:25-38) ed utilizzarlo per recuperare l'elenco dei nodi richiesti, che vengono inseriti nel contesto della View come attributo di nome "nodes".
Nel caso di azione nodesbycatid viene anche passato alla View un oggetto CategoryRow corrispondente alla categoria richiesta, in modo che la pagina JSP possa riproporre all'utente il criterio da lui impostato.
A titolo di esempio, consideriamo come la View nodes.jsp utilizza il valore del parametro action per impostare correttamente il titolo HTML della pagina:

<title>NetView -
<c:choose>
<c:when test="{action=='allnodes'}">
Tutti i nodi
</c:when>
<c:when test="{action=='nodesbycatid'}">
Nodi per categoria
</c:when>
</c:choose>
</title>

La leggibilità di JSTL è sicuramente uno dei suoi maggiori pregi, anche a costo di una certa ridondanza.
L'esperienza nell'utilizzo di questa tag library insegna che, proprio per via di questa prolissità, l'espressione di algoritmi non elementari in JSTL è estremamente scomoda. Questa caratteristica è paradossalmente da intendersi come assolutamente positiva, dal momento che conduce in maniera naturale all'estrapolazione di tali algoritmi in oggetti Java esterni di tipo View Helper, e quindi all'implementazione di soluzioni distribuite corrette, flessibili e manutenibili.

La produzione dell'elenco dei nodi in forma tabellare è estremamente semplice, e consiste nell'iterazione sulla collezione di oggetti NodeRow e nella visualizzazione delle proprietà di ogni nodo all'interno di corrispondenti elementi <td>:

<c:forEach var="node" items="{nodes}">
<tr>
<td><a href="/netview/main?action=nodeform&subaction=edit&id=<c:out value='{node.id}' />" title="Modifica"><img src="Open24.gif" border="0" alt="Apri" /></a\>
</td>
<td><a href="javascript:deleteNode(<c:out value='{node.id}' />);" title="Cancella">
<img src="Remove24.gif" alt="Cancella" border="0" /></a\>
</td>
<td><c:out value="{node.id}"/></td>
<td><c:out value="{node.desc}"/></td>
<td><c:out value="{node.catDesc}"/></td>
<td><c:out value="{node.state}"/></td>
<td><c:out value="{node.load}"/></td>
<td><c:out value="{node.lstRdLoad}"/></td>
<td><c:out value="{node.tdsLoad}"/></td>
<td><c:out value="{node.building}/{node.floor}/{node.room}"/></td>
</tr>
</c:forEach>

I punti interessanti del codice presentato sono i due tag <a>, in cui si utilizza ancora la tecnica di innesto JSTL / HTML per creare rispettivamente un link dinamico ed un'invocazione ad una funzione JavaScript.

Esaminiamo ora l'azione nodeform: essa viene invocata dalla View nodes.jsp per visualizzare i dati di un singolo nodo all'interno di un form per permetterne l'edit (subaction edit con parametro id pari al codice del nodo) ovvero per effettuare l'inserimento di un nuovo nodo (subaction add).
Tale azione porta all'esecuzione del Dispatcher NodeFormDispatcher, il quale ha il compito di rendere disponibili alla View i dati da precaricare nel form, vale a dire - in caso di modifica - i dati del nodo selezionato, ed in caso di inserimento il progressivo automatico del codice del nodo.
Per far questo NodeFormDispatcher utilizza il parametro id per risalire ai valori del nodo, che inserisce uno ad uno nel contesto di pagina:

NodeRow nr=informationSystem.getNodeById(Integer.parseInt(
request.getParameter("id")));

request.setAttribute("id",Integer.toString(nr.getId()));
request.setAttribute("desc",nr.getDesc());


Tali attributi verranno proposti come valori di default nel form di edit dalla View nodeform.jsp, ancora una volta innestando JSTL in HTML:

<input type="text" name="desc" value="<c:out value='{desc}' />" size="50" />

Tale View utilizza il meccanismo già visto in topbar.jsp per produrre la listbox delle categorie, questa volta arricchito di un <c:if> per selezionare automaticamente l'elemento della listbox corrispondente alla categoria del nodo corrente.
Un <c:if> viene utilizzato anche per tradurre lo stato del nodo (un carattere tra 'C', 'T' e 'P') in un radio button di facile utilizzo.

Si noti che alla tecnica di innesto di JSTL / HTML si affiancherà, nelle specifiche di JSP 2.0, una soluzione alternativa chiamata 'implicit scripting', la quale permette di utilizzare espressioni in linguaggio EL liberamente all'interno delle pagine Web. La linea HTML precedente potrebbe quindi venire così riscritta:

<input type="text" name="desc" value="{desc}" size="50" />

I vantaggi dell'implicit scripting, in termini di leggibilità, sono piuttosto ovvi, ma vengono bilanciati da possibili rischi di incompatibilità con pagine HTML che già utilizzano le sequenze di caratteri '{' e '}'.
Tale tecnologia è in effetti già disponibile in alcuni Application Server - quali il Tomcat 5 - ma non è stata utilizzata nell'implementazione di NetView per garantire una maggiore compatibilità della nostra Web Application con i diversi ambienti J2EE Server attualmente disponibili.

In seguito alla conferma del form viene eseguita l'azione nodepost. A tale azione, cui si può arrivare anche direttamente dalla View nodes.jsp confermando la cancellazione di un nodo, spetta la responsabilità di riportare nella base dati l'operazione di inserimento, modifica o cancellazione di nodi effettuata dall'utente.
Il Dispatcher NodePostDispatcher ricupera in caso di inserimento o modifica tutti i dati del nodo inseriti dall'utente del form, e li utilizza per riempire una nuova istanza di NodeRow, di cui poi richiede la persistenza ad InformationSystem:

NodeRow nr=new NodeRow();
nr.setId(Integer.parseInt(request.getParameter("id")));
nr.setDesc(request.getParameter("desc"));
.
.
.
if ("add".equals(subaction))
informationSystem.addNode(nr);
.

L'esito della richiesta di persistenza viene tradotto in un assegnamento all'attributo di richiesta HTTP "result", che viene utilizzato dalla View nodepost.jsp per fornire all'utente un opportuno messaggio di conferma o di errore.
Nel caso di subaction delete l'unico parametro da estrarre dalla richiesta è il codice del nodo, la cui richiesta di cancellazione viene quindi inviata ad InformationSystem.
L'ultima azione da esaminare è error: tale azione viene automaticamente invocata dal Front Controller in presenza di situazioni non risolvibili, e provoca la visualizzazione di una semplice pagina di errore.

 

La quadratura del cerchio
L'implementazione presentata non può che lasciarci soddisfatti: NetView, nella sua semplicità, è basato su di un'architettura distribuita formalmente corretta, organizzata nel rispetto delle best practice e dei pattern più aggiornati, e potenzialmente dotata di quei requisiti di estensibilità, scalabilità e performance che costituiscono la cifra dello sviluppo distribuito.

D'altronde - come in ogni sistema complesso - è nei punti di giuntura dei componenti che si nascondono i problemi. Nel caso delle interfacce utente, in particolare, sono perfettamente ammissibili diverse interpretazioni, spesso squisitamente 'filosofiche' del rapporto tra View, Dispatcher e Helper: la logica di NetView, che abbiamo cercato di partizionare tra queste tre grandezze con la massima oculatezza, avrebbe potuto venire implementata interamente in ognuna di esse, senza il bisogno ricorrere ad altre componenti.
Sarebbe in particolare stato perfettamente legittimo rendere i Dispatcher poco più di 'gusci vuoti', spostando tutta la logica di gestione - si pensi in particolare alle azioni effettuate dai Dispatcher NodeFormDispatcher e NodePostDispatcher - a livello di Helper e di View JSP.
O, addirittura, l'intera applicazione avrebbe potuto essere codificata in JSP e JSTL, senza alcun ricorso a strutture esterne. Per quale motivo, ad esempio, non può essere direttamente la View, in base alle azioni dell'utente, a caricare i dati dei nodi e popolare il form di edit, ovvero a comunicare le variazioni dell'utente al sistema informativo ?
Nonostante l'architettura presentata in questo articolo sia teoricamente corretta, è possibile individuare un numero di approcci alternativi e non meno corretti. E sono, ovviamente, immaginabili anche implementazioni di NetView decisamente più sbilanciate verso l'interfaccia utente, in cui potrebbero mescolarsi presentation e business tier.
La scelta tra queste possibilità rappresenta la 'quadratura del cerchio', in cui oltre alle inevitabili considerazioni teoriche è sempre più necessario far posto a riflessioni pratiche: il bisogno di produrre applicazioni distribuite a ritmi sempre più rapidi non può che portare alla semplificazione delle architetture, ed in particolare allo sbilanciamento verso lo strato di interfaccia utente, il quale - in particolare nelle applicazioni di livello medio-basso - rappresenta il punto di partenza e di arrivo nel design delle soluzioni software, ed il metro di misura e di paragone della qualità dell'applicativo.
La crescente importanza delle interfacce utente si riflette sia nella struttura di framework come .NET sia nelle direttive di sviluppo del mondo Java, caratterizzato da un pullulare di strati di presentazione open-source e commerciali, uno dei quali - Java Server Faces - porremo presto al centro della nostra attenzione.

 

Conclusioni
In questo articolo abbiamo introdotto e sommariamento descritto l'architettura di NetView, una semplice applicazione distribuita corporate, passando quindi all'analisi dell'implementazione del suo presentation tier, realizzato attraverso un Web Client JSP basato su JSTL Core.
Nel prossimo articolo svilupperemo ulteriormente il nostro Web Client arricchendolo di funzionalità basate sugli altri moduli di JSTL: XML Processing, Internationalization Capable Formatting e Relational DB Access.

Bibliografia
[1] JSP - JavaServer Pages: http://java.sun.com/products/jsp
[2] JSTL - JSP Standard Tag Library: http://java.sun.com/products/jsp/jstl
[3] JSF - Java Server Faces: http://java.sun.com/j2ee/javaserverfaces
[4] SWT / Eclipse: http://www.eclipse.org
[5] Droplets: http://www.droplets.com

 

Risorse

Scarica qui il codice presentato nell'articolo. (link a <<netview-1.zip>>)
L'implementazione di esempio di NetView è stata testata sotto JBoss 3.0.3 e Tomcat 5 (Milestone 5.0.0) in ambiente Linux.

Lavinio Cerquetti si occupa di design e sviluppo del software in ambienti distribuiti ed in architetture J2EE multi-tier. Può essere contattato all'indirizzo di e-mail lcerquetti@mokabyte.it

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