Introduzione
Il presente articolo esaurisce l'esempio pratico di
realizzazione di Custom Tag Library (CTL) cui ci siamo
accinti nell'ultimo numero, con l'obiettivo di dotare
NetView - la Web Application sviluppata iterativamente
nel corso della nostra serie di interventi ([1] - [12])
- di funzionalità dirette di accesso ed interrogazione
dei dispositivi monitorati.
In particolare dopo aver rivolto nel mese precedente
([11]) la nostra attenzione alle istanze di progettazione
della CTL ed alla sua integrazione in seno all'applicazione
NetView, desideriamo in questo numero abbandonare il
ruolo di Application Developer per quello di Tool Developer,
concentrandoci quindi sulla codifica di tale Custom
Tag Library all'interno del paradigma Java Server Pages.
Per
la massima comprensione dei contenuti che andremo a
trattare si consiglia di fare frequente riferimento
all'applicazione NetView, i cui sorgenti commentati
sono liberamente disponibili e scaricabili dalla sezione
Risorse dell'articolo pubblicato nel mese precedente
([11]).
Tool
Developer: sviluppo di una Custom Tag Library
Il compito di implementare secondo le direttive stabilite
in sede di design la CTL nvtags spetta, nel nostro esempio,
all'ipotetico reparto IT del committente ACME, il cui
ruolo, in termini di J2EE, è quello del Tool
Developer.
La nostra Custom Tag Library è composta di un
insieme di classi Java, nel caso specifico collocate
nel package nvtags, e da un file XML a carattere dichiarativo
denominato Tag Library Descriptor (TLD) e di cui ci
siamo occupati esaustivamente in [8].
La
nostra analisi inizia proprio a partire dal TLD, rappresentato
dal file META-INF/taglib.tld dell'archivio JAR che costituisce
la Custom Tag Library. Subito dopo le dichiarazioni
iniziali di tipo si apre il tag <taglib>, che
racchiude l'intera definizione della Custom Tag Library,
ed i cui sottotag dichiarano nell'ordine la versione
della libreria nvtags, la versione dell'ambiente JSP
cui è destinata, il codice della libreria convenzionalmente
utilizzato come prefisso nonché il suo nome ed
una descrizione a piacere.
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<short-name>nvtags</short-name>
<display-name>NetView Tags</display-name>
<description>
Tag Library di esempio per l'applicazione NetView
</description>
Segue
quindi il primo tag <tag>, che dichiara il tag
<nodeInfo>, specificandone nell'ordine il nome,
il FQN della classe Java che provvede ad implementare
il tag, la modalità di elaborazione del contenuto
e la descrizione.
<tag>
<name>nodeInfo</name>
<tag-class>nvtags.NodeInfoTag</tag-class>
<body-content>JSP</body-content>
<description>
Ritorna informazioni sulla configurazione di un nodo
</description>
È
quindi la volta della definizione delle variabili di
scripting generate da questo tag; essendo String il
valore di default, non è necessario dichiarare
esplicitamente il tipo della variabile productName.
<variable>
<name-given>productName</name-given>
</variable>
<variable>
<name-given>lastBoot</name-given>
<variable-class>java.util.Date</variable-class>
</variable>
La
dichiarazione del tag <nodeInfo> si conclude con
la definizione dei suoi due attribute nodeId e catId;
si tratta di due attributi obbligatori, la cui valorizzazione,
come stabilito in sede di design, avviene a run-time:
<attribute>
<name>nodeId</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>catId</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
Il
secondo tag della libreria nvtags è <interfaceIterator>,
per il quale vengono dichiarati gli usuali attributi
e le variabili di scripting generate, in entrambi i
casi di tipo String. Avendo stabilitato di utilizzare
il tag <interfaceIterator> in maniera innestata,
non è necessario dotarlo degli attributi nodeId
e catId atti ad identificare il nodo su cui dovrà
operare: tali informazioni verranno desunte a run-time
ricercando nell'albero dei tag della vista JSP il primo
tag <nodeInfo> genitore.
Si noti che nel Tag Library Descriptor non viene in
alcun modo dichiarata la natura di un tag: che un tag
sia di tipo base, iterativo o di contenuto dipende unicamente
dalla sua implementazione a livello Java.
<tag>
<name>interfaceIterator</name>
<tag-class>nvtags.InterfaceIteratorTag</tag-class>
<body-content>JSP</body-content>
<description>
Ritorna informazioni sulle interfacce di rete di un
nodo
</description>
<variable>
<name-given>ipAddress</name-given>
</variable>
<variable>
<name-given>snMask</name-given>
</variable>
</tag>
Il
Tag Library Descriptor si conclude con il tag <sendCommand>.
L'assenza di attributi si spiega con il fatto che anche
in questo caso abbiamo a che fare con un tag progettato
per operare in modalità innestata. Inoltre, avendo
optato per l'invio diretto dell'output al motore Java
Server Pages non si presenta neppure la necessità
di generare variabili di scripting per la comunicazione
con la vista JSP.
<tag>
<name>sendCommand</name>
<tag-class>nvtags.SendCommandTag</tag-class>
<body-content>JSP</body-content>
<description>
Invia un comando ad un nodo, ritornandone l'output
</description>
</tag>
</taglib>
Implementazione
del tag <nodeInfo> e interfaccia NodeContext
Iniziamo la disamina dell'implementazione Java della
Custom Tag Library nvtags con l'analisi del sorgente
del tag <nodeInfo>, implementato dalla classe
nvtags.NodeInfoTag.
Essendo
tale tag di tipo base, la relativa classe è tenuta
ad implementare l'interfaccia javax.servlet.jsp.tagext.Tag,
da noi trattata in [7]. Osservando la definizione della
classe notiamo l'assenza della relativa direttiva implements,
dovuta alla scelta di ereditare dalla classe javax.servlet.jsp.tagext.TagSupport,
la quale oltre a implementare tale interfaccia ci consegna
una definizione di default di diversi metodi standard
richiesti dall'interfaccia Tag, consentendoci quindi
di concentrare la nostra attenzione sui soli metodi
effettivamente significativi per la definizione del
comportamento del tag <nodeInfo>.
Si
noti altresì che la classe NodeInfoTag implementa
l'interfaccia nvtags.NodeContext: nell'ottica di un
design corretto, tale interfaccia segnala che una classe
fornisce un contesto di nodo, ovvero un nodo corrente
utilizzabile - nel nostro caso - da eventuali tag figli
<interfaceIterator> e <sendCommand>.
public
class NodeInfoTag extends TagSupport implements
NodeContext {
Segue
quindi la definizione del metodo doStartTag(), il quale
si avvale del metodo fillData() per impostare le variabili
di scripting e segnala quindi al motore JSP di processare
normalmente il corpo del tag tramite il valore di ritorno
javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE:
public
int doStartTag() throws JspTagException {
fillData();
return EVAL_BODY_INCLUDE;
}
Il
secondo ed ultimo metodo dell'interfaccia Tag definito
da NodeInfoTag è doEndTag(), che si limita a
continuare normalmente la valutazione della vista JSP
corrente ritornando javax.servlet.jsp.tagext.Tag.EVAL_PAGE.
La definizione di questo metodo non è in realtà
necessaria, essendo l'implementazione qui presentata
coincidente con quella già fornita dalla classe
TagSupport, e viene proposta a solo titolo esemplificativo.
public
int doEndTag() throws JspTagException {
return EVAL_PAGE;
}
Il
metodo fillData(), in una implementazione reale, dovrebbe
provvedere a contattare il nodo selezionato tramite
un apposito protocollo per estrarne le informazioni
desiderate; nel nostro caso il sorgente proposto genera
dei dati pseudocasuali.
Si noti come l'attributo catId del tag <nodeInfo>
si tramuti in una normale property Java di tipo String,
qui utilizzata in lettura attraverso l'accessore getCatId().
Come risulta dall'implementazione, le variabili di scripting
productName e lastBoot non sono altro, a livello di
codice, che attributi del contesto corrente di pagina.
private
void fillData() {
int category=Integer.parseInt(getCatId());
pageContext.setAttribute("productName",
products[category-1][random.nextInt(3)]);
pageContext.setAttribute("lastBoot",new
java.util.Date(
System.currentTimeMillis()-
random.nextInt(1000000000)));
}
La
definizione della classe NodeInfoTag giunge a conclusione
con l'implementazione degli attributi nodeId e catId;
come abbiamo discusso in [9] gli attributi di tag JSP
vengono, in maniera assolutamente intuitiva, implementati
come property a livello Java.
private
String nodeId;
public String getNodeId() { return nodeId; }
public void setNodeId(String nodeId) { this.nodeId=nodeId;
}
private String catId;
public String getCatId() { return catId; }
public void setCatId(String catId) { this.catId=catId;
}
Consideriamo
infine la dichiarazione dell'interfaccia NodeContext;
come visto in precedenza, essa ha il compito di rappresentare
la nozione di contesto di nodo, che si realizza in pratica
attraverso l'accesso in lettura alle property nodeId
e catId, che forniscono le necessarie informazioni identificative
del nodo corrente.
Si noti come nel caso di NodeInfoTag l'implementazione
di tale interfaccia non richieda la codifica di alcun
metodo aggiuntivo, sovrapponendosi perfettamente i metodi
dell'intefaccia NodeContext agli accessori in lettura
degli attributi nodeId e catId di un tag <nodeInfo>.
public
interface NodeContext {
public String getNodeId();
public String getCatId();
}
Implementazione
del tag <interfaceIterator>
Il tag <interfaceIterator> è un tag di
tipo iterativo e viene implementato dalla classe InterfaceIteratorTag,
la quale implementa pertanto la classe javax.servlet.jsp.tagext.IterationTag
.
Si noti che l'interfaccia di un tag iterativo, tranne
che per l'aggiunta di un metodo, è identica a
quella di un tag base: ciò spiega l'assenza nelle
JSP API di un'apposita classe di supporto e l'ereditare
di InterfaceIteratorTag dalla consueta classe TagSupport.
Questa situazione richiede ovviamente l'implementazione
esplicita dell'interfaccia IterationTag.
public
class InterfaceIteratorTag extends TagSupport implements
IterationTag {
private int interfaceCount;
Trattandosi
di un tag da utilizzare in modalità innestata,
il metodo doStartTag() ha la responsabilità di
identificare il contesto di nodo corrente, vale a dire
il tag più vicino nell'albero dei tag della vista
JSP corrente che implementi l'interfaccia NodeContext.
Tale compito viene enormemente facilitato dalla disponibilità
all'interno delle JSP API dell'apposito metodo findAncestorWithClass().
Dopo aver identificato, se presente, il contesto corrente,
InterfaceIterator ottiene il numero di interfacce di
rete del dispositivo - nel nostro caso generandolo casualmente
in base alla categoria del nodo corrente - ed inizia
l'iterazione, impostando tramite il metodo iterateData()
le opportune variabili di scripting e ritornando javax.servlet.jsp.tagext.Tag.EVAL_BODY_INCLUDE
per richiedere la normale elaborazione JSP del corpo
del tag.
In generale il metodo doStartTag() di un tag iterativo
dovrebbe venire codificato per ritornare javax.servlet.jsp.tagext.Tag.SKIP_BODY
nel caso in cui non vada effettuata alcun'iterazione:
così facendo il corpo del tag verrà completamente
ignorato.
Si noti tuttavia come nel caso del tag <interfaceIterator>
questa eventualità sia a priori impossibile,
possedendo per definizione ogni dispositivo monitorabile
perlomeno un'interfaccia di rete.
public
int doStartTag() throws JspTagException {
NodeContext ancestor=(NodeContext)findAncestorWithClass(this,
NodeContext.class);
if (ancestor==null)
throw new JspTagException(
"<interfaceIterator>:
non trovo un tag <nodeInfo> padre");
int category=Integer.parseInt(ancestor.getCatId());
interfaceCount=random.nextInt(3)+1;
// Diamo sempre un'interfaccia in piu' ai
router
if (category==1)
++interfaceCount;
iterateData();
return EVAL_BODY_INCLUDE;
}
L'unico
metodo specifico dell'interfaccia IterationTag - da
noi analizzata in [7] è doAfterBody(): esso viene
invocato successivamente a doStartTag(), ed ha la responsabilità
di decidere se proseguire o meno nell'iterazione, impostando
le opportune variabili di scripting e generando l'output
atteso.
Nel caso di InterfaceIteratorTag esso verifica la presenza
di ulteriori interfacce di rete su cui iterare: in caso
positivo provvede ad impostare le opportune variabili
di scripting ed a ritornare javax.servlet.jsp.tagext.Tag.EVAL_BODY_AGAIN,
mentre in caso negativo interrompe l'iterazione ritornando
javax.servlet.jsp.tagext.Tag.SKIP_BODY al motore JSP.
public
int doAfterBody() throws JspTagException {
if (interfaceCount==0)
return SKIP_BODY;
iterateData();
return EVAL_BODY_AGAIN;
}
Al
termine delle iterazioni del tag <interfaceIterator>,
ovvero allorché doAfterBody() ritorna javax.servlet.jsp.tagext.Tag.SKIP_BODY
, l'engine Java Server Pages provvede ad invocare il
metodo doEndTag(). Ancora una volta ne proponiamo il
sorgente per motivi di chiarezza, quantunque la sua
implementazione combaci con quella ereditata da TagSupport.
public
int doEndTag() throws JspTagException {
return EVAL_PAGE;
}
L'implementazione
di InterfaceIteratorTag si conclude con il metodo iterateData(),
il quale imposta le variabili di scripting - nel nostro
esempio a dei valori semicasuali - e decrementa il numero
di interfacce di rete ancora da enumerare.
private
void iterateData() {
String ipAddress;
String snMask;
.
.
.
pageContext.setAttribute("ipAddress",ipAddress);
pageContext.setAttribute("snMask",snMask);
--interfaceCount;
}
Implementazione
del tag <sendCommand>
Il terzo ed ultimo tag della Custom Tag Library nvtags,
<sendCommand>, è un tag di contenuto: in
altre parole, il risultato della sua invocazione dipende
da quanto specificato nel corpo del tag stesso. Nel
caso di <sendCommand> tale corpo è da intendersi
come un comando da inviare al nodo corrente, l'output
del quale viene inviato direttamente al motore Java
Server Pages, saltando il meccanismo di comunicazione
con la vista JSP tramite variabili di scripting utilizzato
nei tag precedenti.
I tag di contenuto implementano l'interfaccia javax.servlet.jsp.tagext.BodyTag,
da noi trattata in [7]: come già accaduto per
la classe NodeInfoTag, tuttavia, anche la classe SendCommandTag
non implementa esplicitamente tale interfaccia, ereditando
dall'apposita classe di supporto javax.servlet.jsp.tagext.BodyTagSupport,
la quale si fa carico dell'implementazione di default
di tutti i metodi di BodyTag e lascia a noi la responsabilità
di codificare i soli metodi effettivamente significativi.
public
class SendCommandTag extends BodyTagSupport {
private int category;
Dal
momento che anche <sendCommand> opera in modalità
innestata, il suo metodo doStartTag() provvede ad identificare
il contesto corrente di nodo in maniera analoga a quanto
visto in InterfaceIteratorTag ed a salvarne in un membro
privato il codice di categoria, necessario per la determinazione
del comportamento del tag, nonché a segnalare
al motore JSP la volontà di gestire l'output
in modalità bufferizzata, ritornando a quest'ultimo
il valore javax.servlet.jsp.tagext.EVAL_BODY_BUFFERED
.
public
int doStartTag() throws JspTagException {
NodeContext ancestor=(NodeContext)findAncestorWithClass(this,
NodeContext.class);
if (ancestor==null)
throw new JspTagException(
"<sendCommand>:
non trovo un tag <nodeInfo> padre");
category=Integer.parseInt(ancestor.getCatId());
return
EVAL_BODY_BUFFERED;
}
Alla
chiusura del tag viene invocato il metodo doEndTag(),
il quale accede al corpo del tag - salvato dal motore
JSP e dall'implementazione ereditata da BodyTagSupport
nella property bodyContent - e ne analizza i contenuti
per determinare il comando da eseguire ed il protocollo
da utilizzare.
Nella nostra implementazione d'esempio vengono gestiti
i soli comandi "who" e "LIST DATABASES",
indirizzati rispettivamente ad un nodo di tipo network
server (categoria 2) e database server (categoria 4),
implementati rispettivamente dai metodi generateWhoOutput()
e generateListDatabasesOutput().
public
int doEndTag() throws JspTagException {
BodyContent bodyContent=getBodyContent();
if (bodyContent==null)
throw new JspTagException("<sendCommand>:
nessun comando specificato");
try {
String body=bodyContent.getString();
if (body.indexOf("who")!=-1)
generateWhoOutput(
bodyContent.getEnclosingWriter());
else if (body.indexOf("LIST
DATABASES")!=-1)
generateListDatabasesOutput(
bodyContent.getEnclosingWriter());
} catch (IOException e) {
throw new JspTagException("<sendCommand>:
errore di I/O: "+e);
}
return EVAL_PAGE;
}
L'implementazione
del tag <sendCommand> si chiude con i metodi generateWhoOutput()
e generateListDatabasesOutput(), i quali inviano l'output
- fittizio nella nostra implementazione - del relativo
comando al javax.servlet.jsp.JspWriter loro fornito
come parametro. A titolo esemplificativo proponiamo
le righe più significative del metodo generateWhoOutput().
private
void generateWhoOutput(JspWriter jspWriter)
throws
IOException {
int userCount=random.nextInt(4)+1;
String row;
jspWriter.write("\n");
while (userCount--!=0) {
row=...
jspWriter.write(row);
};
}
Conclusione
Con questo numero giunge a conclusione la lunga serie
di contributi intitolata "JSP e Web User Interface",
nel corso della quale abbiamo rivolto la nostra attenzione
ad alcune fondamentali tecnologie legate alla realizzazione
dello strato di interfaccia utente di applicazioni Web:
la JSP Standard Tag Library (JSTL), analizzata nei numeri
[1] - [4], Java Server Faces - il framework Java per
la realizzazione di interfacce utente ad applicazioni
Web-based - trattato in [5] - [6] ed infine le Custom
Tag Library, esaminate dapprima teoricamente in [7]
- [10] e quindi a livello pratico in [11] - [12].
Il tema delle Web User Interface in contesto J2EE è
caratterizzato da ricchezza, varietà e dinamicità
straordinarie, a testimonianza del ruolo primario giocato
dallo strato di presentazione nei moderni ambienti distribuiti
e business-oriented.
Per quanto oggettivi limiti di spazio abbiano richiesto
una difficile ed inevitabilmente parziale cernita delle
tecnologie da discutere, portando in certi casi a soprassedere
totalmente all'analisi di alcuni ambienti ed in altri
a contenere e limitare la completezza delle trattazioni,
l'autore vuole sperare che i suoi dodici contributi
- pubblicati nell'arco di tempo che va da Ottobre 2002
ad Ottobre 2003 - abbiano consentito al pubblico di
MokaByte di aggiornare ed allargare le proprie conoscenze,
ponendosi come spunto per ulteriori ricerche ed approfondimenti.
Da
ultimo l'autore desidera ringraziare lo staff di MokaByte
per la collaborazione prestata così come tutti
i lettori, i quali con le loro assidue e-mail hanno
manifestato un sorprendente interesse nei contenuti
di questa serie, esprimendo i propri pareri, richiedendo
maggiori informazioni teoriche e pratiche, ovvero comunicando
le proprie critiche costruttive. I lettori interessati
a proseguire il discorso sono invitati a far uso dei
nuovi forum di MokaByte - disponibili all'indirizzo
http://www2.mokabyte.it/forum/ - i quali si pongono
come luogo informatico ideale di incontro e discussione.
Bibliografia
[1] Lavinio Cerquetti: "JSP 1.2: JSTL - I parte",
Mokabyte N. 67 - Ottobre 2002
[2] Lavinio Cerquetti: "JSP 1.2: JSTL - II parte",
Mokabyte N. 68 - Novembre 2002
[3] Lavinio Cerquetti: "JSP 1.2: JSTL - III parte",
Mokabyte N. 69 - Dicembre 2002
[4] Lavinio Cerquetti: "JSP 1.2: JSTL - IV parte",
Mokabyte N. 70 - Gennaio 2003
[5] Lavinio Cerquetti: "JSF: Java Server Faces
- I parte", Mokabyte N. 71 - Febbraio 2003
[6] Lavinio Cerquetti: "JSF: Java Server Faces
- II parte", Mokabyte N. 72 - Marzo 2003
[7] Lavinio Cerquetti: "Custom Tag Library in ambienti
J2EE - I parte", Mokabyte N. 73 - Aprile 2003
[8] Lavinio Cerquetti: "Custom Tag Library in ambienti
J2EE - II parte", Mokabyte N. 74 - Maggio 2003
[9] Lavinio Cerquetti: "Custom Tag Library in ambienti
J2EE - III parte", Mokabyte N. 75 - Giugno 2003
[10] Lavinio Cerquetti: "Custom Tag Library in
ambienti J2EE - IV parte", Mokabyte N. 76 - Luglio-Agosto
2003
[11] Lavinio Cerquetti: "Custom Tag Library in
ambienti J2EE - V parte", Mokabyte N. 77 - Settembre
2003
[12] Lavinio Cerquetti: "Custom Tag Library in
ambienti J2EE - VI parte", Mokabyte N. 77 - Ottobre
2003
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
|