MokaByte 75 - Giugno 2003 
JSP e Web User Interface
Custom Tag Library in ambienti J2EE
III parte
di
Lavinio Cerquetti
JSP Custom Tag Library: gestione di attributi, variabili di scripting e tag nesting

Introduzione
Terminata con il numero precedente [1] la trattazione dei principali elementi strutturali che caratterizzano le Custom Tag Library nella versione 1.2 del framework Java Server Pages - tag handler, tag library descriptor e validazione - vogliamo iniziare con l'articolo di questo mese l'analisi dei loro componenti di secondo livello; in particolare ci concentreremo sulla gestione e l'utilizzo di attributi, variabili di scripting e tag nesting.

 

Attributi
In maniera perfettamente analoga agli usuali tag HTML ed XML, anche i tag definiti da una Custom Tag Library (in breve CTL) possono accettare uno più attributi, sia opzionali sia obbligatori, il cui fine consiste nella personalizzazione dell'effettivo comportamento di un tag.
L'implementazione di attributi in ambiente JSP avviene in maniera estremamente naturale: allo sviluppatore si richiede unicamente di implementare come property del relativo tag handler i tag definiti nel tag library descriptor.
Il JSP engine si farà quindi carico di tutta l'effettiva gestione dei tag, sfruttando a questo fine la Reflection API di Java. Vale la pena di notare come gli attributi possano essere basati su qualunque classe Java, e non solamente sui tipi primitivi. In effetti l'intera definizione degli attributi è totalmente type-agnostic, dal momento che nel TLD non viene specificata nessuna informazione a riguardo.
Il carattere di opzionalità di un attributo e la possibilità di essere valorizzato a run-time anziché al momento della compilazione della pagina vengono decisi - come abbiamo avuto modo di analizzare il mese scorso - attraverso gli attributi required e rtexprvalue del tag <attribute> a livello di TLD.

È evidente come la parametrizzazione del comportamento di un tag sia ottenibile, oltre che tramite attributi, anche per mezzo di informazioni fornite al tag sotto forma di body content / tag nesting.
A titolo di esempio consideriamo un'ipotetica CTL adabas - il cui fine consista nel fornire una possibilità di accesso non mediato ad una base dati ADABAS - ed in particolare un tag <connect> il quale permetta, sulla base di un apposito insieme di informazioni di localizzazione ed autenticazione, di stabilire una connessione verso il server. Appaiono possibili in linea di principio due definizioni del tag, la prima basata su attributi e la seconda basata su body content / tag nesting, alla luce delle quali le relative invocazioni avrebbero il seguente aspetto:

  • Parametrizzazione del tag tramite attributi:

    <adabas:connect server="adabas-srv.acme.net" user="XXX" password="YYY" />

  • parametrizzazione del tag tramite body content / tag nesting:

    <adabas:connect>
    <adabas:server>adabas-srv.acme.net</adabas:server>
    <adabas:user>XXX</adabas:user>
    <adabas:password>YYY</adabas:password>
    </adabas:connect>

Entrambe queste possibilità sono perfettamente lecite e la questione di quale delle due sia da considerarsi preferibile o più corretta ha rappresentato negli ultimi anni un tema di acceso dibattito teorico, tuttora non completamente sopito.
Gli attributi presentano il vantaggio di una gestione innegabilmente semplice ed intuitiva; d'altra parte le limitazioni sulla natura dei valori che possono venire forniti ad un tag tramite attributi sono estremamente significative. In particolare non è possibile fornire tramite attributi valori multipli, complessi o strutturati. Alla luce di queste considerazioni, ci sentiamo di proporre in ambito JSP le seguenti linee guida:

  • Far libero uso di attributi per comunicare ad un tag valori semplici, essenzialmente numeri e stringhe, la cui complessità non è probabilmente destinata ad aumentare nel tempo;
  • Prediligere body content / tag nesting nel caso di valori complessi o strutturati, o per i quali non ci si voglia precludere nessuna possibilità di espansione futura;
  • Ad un livello più generale, consigliamo l'utilizzo di tecniche basate su body content e tag nesting per rappresentare valori semanticamente legati al contenuto del tag, piuttosto che alla natura delle operazioni richieste al tag per fare fronte ai propri compiti; in altre parole reputiamo corretto utilizzare gli attributi per descrivere il "come" di un tag ed il body content / tag nesting per descriverne il "cosa".

Qualora si desideri utilizzare il body content / tag nesting per rimpiazzare un attributo per il quale sia stata configurata a livello di TLD l'impostazione rtexprvalue sarà necessaria - come avremo modo di vedere in seguito - la definizione da parte del tag di un apposito set di variabili di scripting richiamabili dall'interno del body content.

Si noti che le linee guida proposte, notevolmente meno restrittive nei confronti degli attributi di quanto oggi consigliato da gran parte della comunità XML e sostanzialmente in linea con la visione delineata da Sun nel suo J2EE Tutorial [2], sono esplicitamente pensate per l'implementazione di Custom Tag Library in ambito JSP, e non sono necessariamente valide in contesti più generali - quali la produzione o l'elaborazione di documenti XML in situazioni e con framework differenti - all'interno dei quali alcune delle assunzioni su cui le nostre linee guida sono basate potrebbero non rivelarsi più corrette. In linea generale, ad esempio, ogni utilizzo di attributi all'interno di documenti XML validati su base DTD conduce a delle potenziali incongruenze, non essendone possibile in sede di Document Type Definition una soddisfacente verifica semantica, questione al contrario elegantemente risolta dal paradigma Java Server Pages attraverso quei meccanismi di custom validation che abbiamo già avuto modo di considerare ed apprezzare.

 

Variabili di scripting
Nella nostra trattazione dei tag abbiamo sino ad ora sempre sottinteso che il fine di un tag consista, in ultima analisi, nella produzione di output a partire da un determinato insieme di dati di input, specificati sotto forma di attributi o di body content / tag nesting; in quest'ultimo caso abbiamo menzionato la possibilità di procedere in maniera ricorsiva, innestando nel body content ulteriori tag, in numero e con profondità a piacere, sia standard JSP - inclusi gli oramai sconsigliati scriptlet - sia provenienti dalla stessa o da altre Custom Tag Library.

È tuttavia immaginabile un'ampia casistica di tag il cui obiettivo non sia tanto quello di produrre output quanto piuttosto di fornire informazioni ad un altro strato di tag, sulla scorta delle quali procedere all'effettiva generazione di contenuti. Un classico esempio è rappresentato da ogni contesto nel quale si desideri dividere il ruolo del reperimento o produzione delle informazioni dal layer di decodifica e presentazione, tipicamente basato su linguaggi HTML o XML / XSL.

Tale funzionalità viene implementata in ambito JSP dalle cosiddette variabili di scripting, tramite le quali diversi tag possono comunicare e scambiarsi informazioni a vicenda.

Le variabili di scripting possono venire definite secondo due diverse modalità:

  • Modalità dichiarativa: introdotta a partire dalla versione 1.2 di JSP, essa è composta di una definizione di interfaccia a livello di TLD e di una implementazione in sede di tag handler;
  • Modalità comportamentale: presente sin dalla versione 1.1 di JSP, essa si differenzia dalla precedente per il fatto che tanto la dichiarazione quanto l'impostazione delle variabili avvengono a livello di codice Java.

Si noti che le suddette modalità di definizione sono mutuamente esclusive: ogni tag che definisca delle variabili di scripting deve farlo o in modalità integralmente dichiarativa o in modalità integralmente comportamentale.

La modalità dichiarativa è caratterizzata da una notevole semplicità di utilizzo ed è da considerarsi come lo schema standard di definizione di variabili di scripting da utilizzare in tutte le nuove Custom Tag Library. Essa passa per la definizione delle variabili a livello di TLD attraverso l'utilizzo del tag <variable>, figlio dell'elemento <tag> analizzato il mese precedente:

<!ELEMENT variable ((name-given | name-from-attribute), variable-class?, declare?, scope?) >

I principali componenti di tale tag sono:

  • name-given: deve essere un identificatore valido secondo le regole di sintassi di Java - vale a dire una sequenza di lettere, cifre e caratteri '_' preceduti da una lettera o da '_' iniziali - e rappresenta il nome con cui la variabile di scripting risulterà accessibile in contesto JSP;
  • name-from-attribute: rappresenta una possibilità alternativa a name-given per specificare il nome di una variabile di scripting, il quale verrà desunto dal valore dell'attributo specificato del tag corrente in sede di compilazione di pagina. Si noti che per tale attributo non sarà quindi possibile impostare il flag rtexprvalue;
    variable-class: nome completo della classe Java che costituisce il tipo della variabile; se non specificato esso vale per default java.lang.String;
  • declare: parametro booleano impostabile a true / yes ovvero false / no, il quale determina se la variabile di scripting sia da intendersi alla stregua di una nuova variabile o se costituisca invece una ridefinizione di una variabile già esistente ed inserita nell'oggetto javax.servlet.jsp.PageContext della pagina JSP da un tag processato in precedenza. Di norma si raccomanda di impostare ovvero di lasciare declare al suo valore di default pari a true. Così facendo in caso di doppia definizione di variabili con scope contrastanti da parte di due tag separati o innestati, il motore JSP sarà in grado di produrre un opportuno messaggio d'errore in translation time, prevenendo quindi conflitti e bug di difficile identificazione e soluzione;
  • scope: JSP supporta tre classi di scope per le variabili di scripting, e precisamente:
    • javax.servlet.jsp.TagExt.VariableInfo.NESTED: la variabile risulterà accessibile nel body content a partire dal tag di apertura sino al relativo tag di chiusura, ed eventualmente anche a tutti i tag innestati. A livello del tag handler padre essa potrà quindi venire utilizzata ed impostata all'interno dei metodi doStartTag(), doInitBody() e doAfterBody(). Il livello di scope NESTED minimizza i rischi di conflitti con variabili definite da altri tag ed è pertanto la classe di scope raccomandata nel normale sviluppo di librerie CTL e rappresenta il valore di default per il parametro scope;
    • javax.servlet.jsp.TagExt.VariableInfo.AT_BEGIN: lo scope di validità della variabile parte dal tag di apertura e si protrae sino al termine dell'intera pagina JSP corrente. Il tag handler padre potrà quindi farne uso in tutti i metodi, vale a dire doStartTag(), doInitBody(), doAfterBody() e doEndTag();
    • javax.servlet.jsp.TagExt.VariableInfo.AT_END: lo scope di validità della variabile parte dal tag di chiusura e si protrae sino al termine dell'intera pagina JSP corrente. Il tag handler padre potrà quindi farne uso esclusivamente nel metodo doEndTag().

Il secondo passo nella definizione di una variabile di scripting in modalità dichiarativa consiste nella sua impostazione a livello di tag handler da parte di uno o più dei metodi autorizzati in tal senso in funzione della classe di scope dichiarata. La variabile non sarà effettivamente disponibile alla pagina JSP, vale a dire al body content o ad eventuali tag innestati, sino alla sua effettiva impostazione. Le variabili di scripting vengono trattate come attributi dell'oggetto PageContext corrente e vengono quindi impostate tramite una semplice invocazione del relativo metodo setAttribute(); questo significa che variabili il cui valore sia costituito da un tipo primitivo dovranno venire memorizzate tramite la rispettiva wrapping class: java.lang.Integer per variabili di tipo int, e così via.

Vale qui la pena di porre l'accento sulla semplicità del metodo dichiarativo di definizione di variabili di scripting, il quale nella maggior parte dei casi non consiste in altro che nell'aggiunta di tre o quattro semplici righe al tag library descriptor e nella scrittura di una o due righe di codice nel tag handler. Eppure, a fronte di queste semplici operazioni, il risultato consiste nell'implementazione di una primitiva di comunicazione tra tag potente e flessibile, sulla scorta della quale è effettivamente possibile garantire quella netta separazione di ruoli e responsabilità che è presupposto fondamentale alla realizzazione di tag e CTL riusabili e adattabili ad un ampio spettro di situazioni.

Il secondo schema di definizione di variabili di scripting consiste nella cosiddetta modalità comportamentale, caratteristica della versione 1.1 del framework Java Server Pages; come avremo modo di vedere essa richiede allo sviluppatore uno sforzo implementativo lievemente maggiore ed il suo uso viene oggi limitato a poche e ben definite circostanze.

La modalità comportamentale di definizione della variabili si articola in tre passi fondamentali:

  • Dichiarazione di una classe figlia di javax.servlet.jsp.tagext.TagExtraInfo a livello di tag library descriptor;
  • implementazione della suddetta classe TagExtraInfo;
  • impostazione delle variabili di scripting a livello di tag handler in maniera esattamente identica a quanto avviene nella modalità dichiarativa.

La dichiarazione della classe TagExtraInfo avviene, come già analizzato in [3], all'interno dell'elemento <tag> del TLD. È da notare come, in questo caso, non vada definita nessuna variabile di scripting tramite il sottotag <variable> dell'elemento <tag>.

Il secondo passo consiste nell'implementazione di una classe figlia di javax.servlet.jsp.tagext.TagExtraInfo, la quale possiede in ambito JSP 1.2 due diverse funzioni: con la prima abbiamo già fatto conoscenza nel precedente articolo [1], allorché abbiamo utilizzato tale classe al fine di implementare dei meccanismi di custom validation. Il secondo ruolo ricoperto dalla classe TagExtraInfo consiste nella dichiarazione di un insieme di variabili di scripting per tramite del seguente metodo:

public javax.servlet.jsp.tagext.VariableInfo[] getVariableInfo(javax.servlet.jsp.tagext.TagData tagData)

La responsabilità del metodo getVariableInfo() consiste nel ritornare al motore JSP un vettore di oggetti VariableInfo, ognuno dei quali definisce una variabile di scripting in maniera esattamente analoga a quanto possibile con il tag <variable> nella schema dichiarativo di definizione delle variabili di scripting. Per convincerci di questo sarà sufficiente considerare le property della non-mutable class VariableInfo:

  • java.lang.String varName: essa rappresenta, in maniera identica al parametro name-given del tag <variable>, il nome della variabile di scripting;
  • java.lang.String.className: nome completo della backend-class della variabile di scripting, analogo al parametro variable-class;
  • boolean declare: richiesta di definizione di una nuova variabile alla medesima stregua del parametro declare;
  • int scope: classe di scope della variabile di scripting corrispondente al parametro scope del tag <variable> ed impostabile ai medesimi valori (NESTED, AT_BEGIN e AT_END).

Una tipica implementazione del metodo getVariableInfo() consisterà quindi semplicemente nella ripetuta istanziazione di oggetti VariableInfo(), nella loro coagulazione in un vettore e nel loro ritorno al JSP engine.
Da quanto detto emerge con chiarezza quella che è la caratteristica precipua del metodo comportamentale di definizione di variabili di scripting: la dichiarazione a livello di codice Java permette infatti allo sviluppatore di definire in maniera dinamica il numero e le caratteristiche delle variabili di scripting definite da un tag - eventualmente in dipendenza dal contesto e da altri parametri ambientali - laddove il metodo dichiarativo comporta una dichiarazione statica ed immutabile delle variabili di scripting.

L'utilizzo della modalità comportamentale di definizione della variabili di scripting - cui si possono in ogni caso muovere fondate obiezioni in ragione dei problemi di chiarezza e manutenibilità del codice e della pagine JSP cui essa conduce - è raccomandato solamente in quegli infrequenti casi in cui si manifesti la reale esigenza di disporre di un insieme di variabili di scripting di cui non siano noti a priori il numero ed il tipo.

Come abbiamo già fatto notare in precedenza non è possibile per un medesimo tag definire variabili di scripting sia in modalità dichiarativa sia comportamentale. Questo significa in pratica che, qualora un tag definisca le proprie variabili di scripting a livello di TLD tramite il tag <variable>, il metodo getVariableInfo() del suo eventuale oggetto TagExtraInfo dovrà ritornare null. Per converso, qualora tale metodo non ritorni null, non sarà ammissibile per il tag in questione far uso del tag <variable> nel proprio tag library descriptor.

 

Tag nesting
Le API JSP permettono ad uno o più tag di riconoscere il proprio contesto di nesting e di comportarsi di conseguenza in maniera 'intelligente', vale a dire cooperando con il proprio parent tag al perseguimento di un obiettivo comune.

Abbiamo già presentato un esempio implicito di tag innestati in sede di trattazione degli attributi, allorché abbiamo mostrato un possibile esempio di codice JSP:

<adabas:connect>
<adabas:server>adabas-srv.acme.net</adabas:server>
<adabas:user>XXX</adabas:user>
<adabas:password>YYY</adabas:password>
</adabas:connect>

Quello che ci aspetta a fronte del suddetto codice è che i tre tag innestati <adabas:server>, <adabas:user> e <adabas:password> cooperino con il proprio parent tag <adabas:connect>, comunicandogli il hostname del server ed i relativi parametri di autenticazione, e permettendogli quindi - presumibilmente nel metodo doEndTag() del relativo tag handler - di stabilire l'effettiva connessione al server.
Il framework Java Server Pages permette in effetti di realizzare tag dotati di questo genere di 'intelligenza' ed il nostro obiettivo è esporre gli strumenti forniti allo sviluppatore in questo senso e comprenderne le funzionalità ed i possibili schemi di utilizzo.

L'idea che un tag sia in grado di cooperare con il proprio contesto, rintracciando i tag con cui è in grado di cooperare, o per conoscenza diretta o sulla scorta di una comune interfaccia funzionale, è per sua natura essenzialmente dinamica.
Per questo motivo il tag nesting è sostanzialmente incompatibile con una dichiarazione a livello di TLD, che sarebbe forzatamente statica - vale qui lo stesso principio analizzato nel paragrafo precedente in relazione alla definizione delle variabili di scripting - e viene piuttosto definito ed implementato interamente a livello di codice Java.
Sarà d'altronde il caso di notare come l'impossibilità di fornire una descrizione formale del meccanismo di tag nesting a livello di XML non permetta al motore JSP l'implementazione automatica di meccanismi di validazione preventiva o cooperativa, delegando di fatto tale onere allo sviluppatore della Custom Tag Library.

Effettuate queste considerazioni generali possiamo passare all'analisi delle API che implementano il tag nesting. Come più volte ci è successo nel corso della nostra cavalcata all'interno del mondo JSP / JSTL, non possiamo fare a meno di stupirci di fronte alla semplicità dei meccanismi che soggiacciono all'implementazione di una funzionalità che, di primo acchito, potrebbe non sembrare esattamente elementare.

In effetti l'intera struttura del tag nesting si basa su di un unico metodo statico della classe javax.servlet.jsp.tagext.TagSupport:

public static final javax.servlet.jsp.tagext.Tag findAncestorWithClass(javax.servlet.jsp.tagext.Tag from, java.lang.Class class)

Tale metodo cerca, nella struttura gerarchica dei tag della pagina JSP corrente, il primo tag posto in un livello gerarchico superiore al tag rappresentato dal parametro from ed il cui tag handler sia implementato da una classe di tipo class.
Esso permette quindi ad un tag handler di rintracciare, se esiste, un'istanza di tag con cui esso sia in grado di cooperare e di porsi con essa in diretta comunicazione.
Si noti che tale metodo permette di trovare il tag handler desiderato indipendentemente dal livello di nesting e rappresenta quindi un meccanismo assai più generale e flessibile rispetto all'invocazione, eventualmente ripetuta, del metodo getParent(), per tramite del quale un tag handler ottiene un riferimento al parent tag, vale dire al tag handler di livello gerarchico immediatamente superiore all'interno della pagina JSP corrente.

Torniamo ora al nostro esempio, supponendo che il tag handler del tag <adabas:connect> sia rappresentato dalla classe AdabasTagHandler la quale, tra le altre, possiede una proprietà serverName pari al nome host del server verso il quale deve avvenire la connessione. Il tag handler del tag <adabas:server> potrebbe quindi leggere e comunicare il hostname del server al tag <adabas:connect> attraverso la seguente, elementare definizione del metodo doEndTag(), dalla quale omettiamo per brevità ogni gestione di errori ed eccezioni:

public int doEndTag() throws JspTagException {
BodyContent bodyContent=getBodyContent();
String hostName=bodyContent.getString();
AdabasTagHandler adabasTH=(AdabasTagHandler) TagSupport.findAncestorWithClass(this,AdabasTagHandler.class();
adabasTH.setServerName(hostName);
}

 

Conclusione
Tema del presente articolo è stata la trattazione dei meccanismi di gestione degli attributi, delle variabili di scripting e del tag nesting all'interno di una Custom Tag Library.
Nel prossimo numero concluderemo la trattazione teorica delle CTL rivolgendo la nostra attenzione alla gestione delle eccezioni e degli eventi, e cercheremo di fare il punto sulle future evoluzioni delle Servlet API e del paradigma Java Server Pages analizzando le novità in cantiere per le prossime versioni di questi due framework.

 

Bibliografia
[1] Lavinio Cerquetti: "Custom Tag Library in ambienti J2EE - II parte", Mokabyte N. 74 - Maggio 2003
[2] Sun J2EE Tutorial: http://java.sun.com/j2ee/tutorial/1_3-fcs/index.html
[3] Lavinio Cerquetti: "Custom Tag Library in ambienti J2EE - I parte", Mokabyte N. 73 - Aprile 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