MokaByte 78 - 8bre 2003 
JSP e Web User Interface
Custom Tag Library in ambienti J2EE
VI parte
di
Lavinio Cerquetti
Sintesi e conclusione: implementazione di una Custom Tag Library

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

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