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
|