SAX
& DOM
Indipendentemente dalla motivazione e dall'obiettivo
finale, per poter utilizzare un documento XML prima
di tutto è necessario potervi accedere e acquisire
le informazioni in esso contenute. Questa operazione,
comunemente chiamata "parsing", può
essere realizzata utilizzando due metodologie differenti,
DOM e SAX.
Nel primo caso (Document Object Model) il documento
XML viene letto integralmente e, terminata la lettura,
viene generata una struttura ad albero che fornisce
agli sviluppatori un sistema piuttosto semplice per
accedere alle informazioni ed ai dati contenuti all'interno
del documento. Nel secondo metodo (Simple Api for XML)
che è comunque alla base dello stesso DOM, il
documento viene letto e ogni qualvolta viene incontrato
un tag, viene generato un evento. La maggior complessità
di questa metodologia deriva dal fatto che il compito
dello sviluppatore è di gestire questi eventi,
a seconda delle proprie necessità e del tipo
di applicazione.
Le soluzioni che si basano su DOM sono normalmente le
più comuni e diffuse a causa della maggior semplicità
ed immediatezza nell'uso ma, allo stesso tempo, nascondono
molte insidie, prima fra tutte la maggior richiesta
di risorse di sistema che aumentano con l'aumentare
della dimensione dei documenti XML da leggere. Con SAX
invece il problema si ribalta, visto le ottime prestazioni
e la totale assenza di limitazioni. Purtroppo la complessità
nella gestione degli eventi spesso scoraggiano gli sviluppatori
ad adottare soluzioni che utilizzano questo metodo.
Figura 1 - La homepage di Digester
Una
possibile risposta a questo è offerta da "Digester",
uno dei componenti del progetto "Jakarta Commons",
che propone una metodologia di parsing basata sul metodo
SAX, mantenendone potenza e versatilità, ma cercando
di renderla più semplice da utilizzare.
I
concetti di base
La logica di Jakarta Digester è piuttosto semplice
e si basa sul concetto di applicare delle regole quando
si incontra una determinata sequenza di tag. Per meglio
comprendere cosa si intende, partiamo da un semplice
documento XML che potrebbe rappresentare un piccolo
file di configurazione:
<?xml
version="1.0"?>
<configurazione>
<proprieta>
<nome> database.driver </nome>
<valore> com.mysql.jdbc.Driver </valore>
</proprieta>
<proprieta>
<nome> database.url </nome>
<valore> jdbc:mysql://localhost/database </valore>
</proprieta>
<proprieta>
<nome> username </nome>
<valore> dba </valore>
</proprieta>
<proprieta>
<nome> password </nome>
<valore> dba123 </valore>
</proprieta>
</configurazione>
Digester
identifica i nodi della struttura originale del documento
secondo una logica molto simile a quella di XPath, paragonabile
a quella che viene utilizzata comunemente per i file
system. Ogni nodo viene quindi visto in questo modo:
configurazione
configurazione/proprieta
configurazione/proprieta/nome
configurazione/proprieta/valore
Quando
Digester, durante la lettura del documento, incontra
una di queste sequenze, esegue la regola ad essa associata,
se esiste. Nel nostro caso, ad esempio, potremmo voler
inserire ogni proprietà, caratterizzata dalla
combinazione del contenuto dei tag "nome"
e "valore", all'interno di un oggetto di configurazione.
Per fare questo dobbiamo associare alla sequenza "configurazione/proprieta"
l'esecuzione di un metodo che utilizza i valori contenuti
nelle due sequenze "configurazione/proprieta/nome"
e "configurazione/proprieta/valore". Vi assicuro,
è più difficile da spiegare che da realizzare.
Ma
per poter passare dalla teoria ai fatti bisogna prima
acquisire tutti i "pezzi" necessari, partendo
dalla homepage del componente Digester, all'indirizzo
http://jakarta.apache.org/commons/digester.html. All'interno
di questa pagina è presente il link per poter
scaricare l'ultima versione del pacchetto (sorgenti
o versione compilata).
E' molto probabile che questo pacchetto contenga solo
il jar di Digester e non le altre librerie che sono
richieste per il suo funzionamento. Se così fosse,
nessun panico. Basta scaricare gli altri componenti,
tutti provenienti dallo stesso progetto Jakarta Commons,
ossia: BeanUtils (http://jakarta.apache.org/commons/beanutils.html),
Collections (http://jakarta.apache.org/commons/collections.html)
e Logging (http://jakarta.apache.org/commons/logging.html).
Tutti i vari jar di questi componenti andranno poi inseriti
nel classpath, insieme a quello di Digester.
Primo
esempio
Una volta preparato l'ambiente, possiamo costruire la
classe Configurazione che utilizzeremo per il nostro
esempio, limitandoci, all'inizio, alle funzionalità
di base:
import
java.util.*;
public
class Configurazione {
Properties
proprieta;
// Il costruttore si occupa di inizializzare
l'oggetto che conterrà le proprietà
public Configurazione() {
proprieta = new Properties();
}
// Questo metodo aggiungerà la combinazione
nome/valore come proprietà
public void addProprieta(String nome, String
valore) {
proprieta.put(nome, valore);
}
// Questo metodo verrà utilizzato
per testare il nostro oggetto
public String toString() {
StringBuffer retBuff = new StringBuffer();
Enumeration enum = proprieta.propertyNames();
while (enum.hasMoreElements())
{
String nome = (String)enum.nextElement();
retBuff.append(nome).append("
= ");
retBuff.append(proprieta.get(nome));
if (enum.hasMoreElements())
{
retBuff.append(System.getProperty("line.separator"));
}
}
return retBuff.toString();
}
}
Come
si può facilmente vedere, non c'è nulla
di speciale all'interno di questa classe. L'unico elemento
che ha una particolare importanza per l'esempio è
il metodo "addProprieta" che è quello
che verrà utilizzato da Digester.
Una
volta verificato che tutto sia a posto, possiamo iniziare
a scrivere il metodo che si occuperà di leggere
il documento XML attraverso Digester, che chiameremo
semplicemente "parse":
public
void parse(String filename) throws IOException, SAXException
{
// Creazione dell'oggetto Digester ed inserimento
// dell'oggetto Configurazione all'interno
dello stack
Digester digester = new Digester();
digester.push(this);
La
prima operazione consiste nell'inizializzare l'oggetto
Digester e nel comunicargli attraverso lo stack su quale
oggetto noi intenderemo eseguire le operazioni che seguiranno.
Essendo questo oggetto proprio la classe che ospita
il metodo, abbiamo utilizzato "this".
A questo punto possiamo definire le regole:
//
Definizione delle regole
digester.addCallMethod("configurazione/proprieta",
"addProprieta", 2);
digester.addCallParam("configurazione/proprieta/nome",
0);
digester.addCallParam("configurazione/proprieta/valore",
1);
La
prima regola viene eseguita ogni qualvolta viene letto
il tag di partenza di una proprietà. In questo
caso indichiamo che deve essere chiamato il metodo chiamato
"addProprieta", passandogli due argomenti.
La seconda e la terza regola sono identiche e servono
per definire i due argomenti richiesti dal metodo indicato
in precedenza.
A questo punto possiamo semplicemente chiamare il metodo
che effettuerà il parsing del documento, applicando
le regole da noi definite, completando il nostro metodo.
//
Parsing del documento XML
digester.parse(filename);
}
Il
metodo parse può generare due tipi di eccezioni,
la IOException e la SAXException. In questo caso non
sono gestite internamente, ma rimandate al metodo chiamante.
Per poter compilare questo oggetto è necessario
aggiungere alla lista degli import i due package java.io.*
e org.xml.sax.*, necessari per le eccezioni ed il package
org.apache.commons.digester.* per tutto quello che riguarda
Digester.
Per
completare l'esempio e, quindi, provarlo, possiamo aggiungere
alla nostra classe il metodo "main". In fase
di esecuzione utilizzeremo il primo argomento per passargli
il file XML che abbiamo creato in precedenza.
public
void static final main(String[] args) {
try
{
// Inizializza l'oggetto ed
esegui il parse
Configurazione config = new
Configurazione();
config.parse(args[0]);
// Per verificare che tutto
sia ok, eseguiamo il metodo toString()
System.out.println();
System.out.println(config.toString());
}
catch (Exception e) {
e.printStackTrace();
}
}
Per
l'esecuzione è quindi sufficiente fare così
(ricordatevi di aver incluso nel classpath tutto il
necessario).
java
Configurazione "configurazione.xml"
database.driver
= com.mysql.jdbc.Driver
password = dba123
database.url = jdbc:mysql://localhost/database
username = dba
Da XML a JavaBean
Talvolta si possono creare situazioni dove è
necessario trasformare il contenuto di un documento
XML in uno o più JavaBean ed anche in questo
caso Digester di rivela essere un ottimo aiuto, facilitando
lo sviluppo.
Come in precedenza partiamo da un semplice documento
XML a rappresentare un piccolo archivio di film, per
i quali sono indicati un codice, un titolo e la durata
in minuti.
<?xml
version="1.0"?>
<archivio>
<film>
<codice> 123 </codice>
<titolo> Una pura formalita' </titolo>
<durata> 108 </durata>
</film>
<film>
<codice> 124 </codice>
<titolo> Office Space </titolo>
<durata> 89 </durata>
</film>
<film>
<codice> 125 </codice>
<titolo> Swimming with sharks </titolo>
<durata> 101 </durata>
</film>
</archivio>
Dal
punto di vista Java possiamo rappresentare il film attraverso
un piccolo JavaBean, il cui codice può essere
più o meno questo:
public
class Film {
// Attributi
private long codice;
private String titolo;
private int durata;
// Costruttori
public Film() {
// costruttore vuoto
}
public Film(long codice, String titolo,
int durata) {
setCodice(codice);
setTitolo(titolo);
setDurata(durata);
}
// Getters & Setters
public
void setCodice(long arg) {
codice = arg;
}
public
long getCodice() {
return codice;
}
public void setTitolo(String arg) {
titolo = arg;
}
public String getTtiolo() {
return titolo;
}
public void setDurata(int arg) {
durata = arg;
}
public int getDurata() {
return durata;
}
// Questo metodo verrà utilizzato
per testare il nostro oggetto
public String toString() {
StringBuffer retBuff = new StringBuffer();
retBuff.append("Codice=").append(codice).append(",");
retBuff.append("Titolo=").append(titolo).append(",");
retBuff.append("Durata=").append(durata);
return retBuff.toString();
}
}
A
completare l'esempio, possiamo creare un secondo oggetto,
che chiamiamo "Archivio" che avrà il
compito di contenere la lista dei film. Per semplificare
le operazioni, così come in precedenza, inseriremo
all'interno di questo oggetto sia il codice main per
il testing, sia quello per effettuare il parsing. In
questo caso, però, utilizzeremo una logica differente,
per mostrare un ulteriore aspetto di Digester.
import
java.io.*;
import java.util.*;
import org.xml.sax.*;
import org.apache.commons.digester.*;
public class Archivio {
private List listaFilm;
//
Il costruttore inizializzerà la lista
public Archivio() {
listaFilm = new ArrayList();
}
// Questo metodo aggiungerà un nuovo
Film alla lista
public void addFilm(Film film) {
listaFilm.add(film);
}
// Questo metodo verrà utilizzato
per testare il nostro oggetto
public String toString() {
StringBuffer retBuff = new StringBuffer();
for (int i=0; i<listaFilm.size();
i++) {
retBuff.append(listaFilm.get(i));
retBuff.append(System.getProperty("line.separator"));
}
return retBuff.toString();
}
}
Fino
a qui nulla di strano. Il nostro oggetto Archivio contiene
solo un oggetto List che conterrà, a sua volta,
tutti i JavaBean di tipo "Film" che verranno
creati tramite il Digester.
Il metodo di parsing sarà statico e ritornerà
una istanza dell'oggetto Archivio, costruito in base
al contenuto del documento XML:
public
static Archivio parse(String filename) throws IOException,
SAXException {
// Creazione oggetto Digester
Digester digester = new Digester();
Dopo
aver creato l'oggetto Digester, non inseriremo nulla
all'interno dello stack, ma definiremo subito una regola
il cui compito sarà quello di inizializzare una
nuova istanza della classe Archivio quando Digester
incontrerà il tag "archivio":
//
Creazione oggetto Archivio
digester.addObjectCreate("archivio",
Archivio.class);
All'interno
del nostro documento avremo una o più tag contenenti
le informazioni necessarie per la creazione dei JavaBean
di tipo Film. Il compito di Digester sarà, quindi,
quello di instanziare un oggetto Film (quando incontra
il tag di apertura "archivio/film"), settarne
le proprietà, (tramite "archivio/film/codice",
" archivio/film/titolo" e " archivio/film/durata")
ed infine inserire questo oggetto all'interno della
collezione. Ecco un possibile modo per realizzare questo:
//
Creazione oggetto Archivio
digester.addObjectCreate("archivio/film",
Film.class);
//
Inserimento proprietà attraverso i setters
digester.addBeanPropertySetter("archivio/film/codice",
"codice");
digester.addBeanPropertySetter("archivio/film/titolo",
"titolo");
digester.addBeanPropertySetter("archivio/film/durata",
"durata");
// Inserimento oggetto Film all'interno
della lista
digester.addSetNext("archivio/film",
"addFilm" );
Il
metodo chiave di questa soluzione è "addBeanPropertySetter"
che trasferisce all'interno del bean il valore contenuto
nel tag indicato, dopo aver trasformato il valore di
tipo String letto dal documento XML, nel tipo richiesto
dal setter.
A
questo punto effettueremo il parsing del documento attraverso
lo stesso metodo parse visto in precedenza utilizzando
l'oggetto che ci verrà restituito, in questo
caso di tipo Archivio, come valore di ritorno del nostro
metodo:
return
(Archivio)digester.parse(filename);
}
Per
completare l'esempio basta creare il metodo main di
test:
public
static final void main(String[] args) {
try
{
Archivio
archivio = Archivio.parse(args[0]);
System.out.println();
System.out.println(archivio.toString());
}
catch
(Exception e) {
e.printStackTrace();
}
}
Funzionalità avanzate
Nei due esempi, piuttosto elementari, che abbiamo proposto,
abbiamo notato come tutte le operazioni di parsing si
basano su regole che vengono inserite all'interno del
codice. Sebbene nella maggior parte dei casi questo
possa essere sufficiente, vi sono situazioni nelle quali
può essere necessario poter applicare delle regole
che sono definite da un file di configurazione esterno,
aumentando la portabilità del codice.
Digester, ovviamente, permette di costruire un sistema
che si possa "configurare" piuttosto che "programmare",
attravero l'utilizzo del package org.apache.commons.digester.xmlrules.
Figura 2 - Il package xmlrules per la configurazione
esterna delle regole
Per
definire le regole si utilizzerà, ovviamente,
un documento XML, il cui DTD, "digester-rules.dtd"
è inserito all'interno del package. All'interno
del codice l'oggetto Digester verrà inizializzato
attraverso il metodo statico "createDigester"
dell'oggetto "DigesterLoader", che si occuperà
di leggere le regole definite nel documento XML. Fatto
questo potremo chiamare il metodo "parse",
così come abbiamo visto negli esempi precedenti,
per effettuare le operazioni di parsing sui nostri file
XML.
Figura 3 - Il package per gestire documenti con
il formato RSS
XML,
tra le altre cose, è anche alla base di Rich
Site Summary (RSS), il formato che sta diventando uno
standard per i newsfeed presenti in rete. Tra le API
di Digester esiste il package org.apache.commons.digester.rss,
il cui scopo è proprio quello di aiutare il programmatore
ad accedere a questi documenti e, quindi, utilizzarli
all'interno delle proprie applicazioni.
Digester,
però, non si esaurisce qui, soprattutto per quello
che concerne il discorso dulle regole. Il componente
del progetto Jakarta Commons, infatti, permette allo
sviluppatore di personalizzare il sistema costruendo
le proprie regole, attraverso l'estensione della classe
Rule.
Conclusioni
Questo, però, non significa che Digester debba
essere adottato come la soluzione finale a tutti i problemi
legati all'integrazione di XML all'interno dei nostri
progetti. Di soluzioni alternative ce ne sono e può
darsi che per alcune situazioni specifiche queste si
possano rivelare più efficienti rispetto a questo
componente o che forniscano maggiori funzionalità
generiche, come, ad esempio, la possibilità di
generazione di documenti XML partendo dal codice.
Il grosso vantaggio di Digester, però, è
la sua grossa versatilità, che non toglie nulla
né alla potenza e tantomeno alla semplicità
d'utilizzo, proponendosi come uno strumento da tenere
sempre a portata di mano... sono certo che quando inizierete
ad usarlo, lo userete più di quanto avevate preventivato.
Link
e risorse
[1] Erik Swenson - "Usimplify XML file processing
with the Jakarta Commons Digester", JavaWorld.com,
2002
[2] Philipp K. Janert - "Learning and Using Jakarta
Digester", OnJava.com, 2002
|