Presentiamo
il componente Digester
L'avvento dell'eXtensible Markup Language (XML) ha condotto
a una trasformazione completa nel mondo dello sviluppo
di applicazioni. Oggigiorno, tutti gli aspetti dello
sviluppo sembrano ruotare intorno a XML. In effetti,
è difficile individuare un ambito nuovo, nello
sviluppo, che non faccia affidamento, in maniera diretta
o indiretta, sull'XML. Per esempio, i web service sarebbero
inimmaginabili senza XML, e con il boom dell'uso dei
web service previsto nei prossimi cinque anni, non è
possibile evitare l'XML. In questo articolo, analizzeremo
il componente Jakarta Commons Digester e il modo in
cui esso possa facilitare il lavoro con XML. La tab.
1 mostra i dettagli del componente.
Nome
|
Versione
|
Package
|
Digester |
1.5 |
org.apache.commons.digester |
Tabella
1 - I dettagli del componente
Un
problema che ha afflitto lo sviluppo dell'XML, è
la complessità nel parsing e nell'uso di XML.
Tutti conoscono i vantaggi dell'uso di XML, ma quanti
sono in grado di scrivere una porzione di codice che
effettui il parsing di un file XML e ricavi il valore
di un determinato tag XML? È tutto fuorché
semplice scrivere codice Java che effettui il parsing
di un file XML usando direttamente le due Application
Programming Interfaces (API) fondamentali: Document
Object Model (DOM) e Simple API for XML (SAX). API come
JDOM sono relativamente semplici ma, considerando con
quale frequenza si incontra e si deve "placcare"
l'XML, il Digester appare una scelta più facile.
Sarà possibile effettuare il parsing dell'XML
e usarlo nel proprio codice Java in un tempo più
ridotto di quello che occorre per leggere quest'articolo
ma non ci resterò male se ce ne metterete un
po' di più.
Per
prendere subito la mano con il Digester, vedremo innanzitutto
un esempio: non preoccupatevi della sintassi, poiché
la analizzeremo in dettaglio più avanti. Lo scenario
per questo esempio è il seguente: viene presentato
un file XML contenente tutti i dettagli di tutti gli
studenti che seguono i vari corsi nel vostro istituto
di formazione. Ci si aspetta che tutti i dettagli presenti
nel file XML vengano prelevati e, per ciascun studente,
venga popolata un'istanza della classe Student, che
avete creato. Tutte le istanze di Student create saranno
poi salvate in un'istanza della classe java.util.Vector
per essere sottoposte a ulteriori elaborazioni.
Occorre
innanzitutto creare una classe Student che conterrà
i dettagli di uno studente (listato 1).
Listato
1 - Classe Student
package com.commonsbook.chap7;
public class Student {
private String name;
private String course;
public
Student() {
}
public
String getName() {
return name;
}
public
void setName(String newName) {
name = newName;
}
public
String getCourse() {
return course;
}
public
void setCourse(String newCourse) {
course = newCourse;
}
public
String toString() {
return("Name="+this.name
+ " & Course=" + this.course);
}
}
A
parte il metodo toString che è stato ridefinito,
non c'è nulla di speciale in questa classe. Ha
solamente due proprietà con metodi di get e di
set per entrambe. L'obiettivo ora è creare istanze
di questa classe sulla base dei dati che saranno recuperati
da un file XML.
Il listato 2 mostra il contenuto del file XML. Il numero
dei tag student non è importante: se si desidera,
si può tranquillamente introdurre più
studenti.
Listato
2 - students.xml
<?xml version="1.0"?>
<students>
<student>
<name>Java Boy</name>
<course>JSP</course>
</student>
<student>
<name>Java Girl</name>
<course>EJB</course>
</student>
</students>
NOTA:
Nei listati 1 e 2, è possibile vedere che i nomi
dei tag e delle proprietà hanno una esatta corrispondenza,
in modo tale che per un tag course si ha una proprietà
denominata course nella classe Student. È comunque
possibile avere nomi di tag e di proprietà differenti:
non è richiesta alcuna corrispondenza tra i nomi
XML e quelli della classe Java ed è tranquillamente
possibile salvare il valore di un tag ABC dentro una
proprietà XYZ. La corrispondenza tra i nomi aiuta
semplicemente a mantenere tutto più semplice,
ma non ha alcuna ragione "tecnica".
La
classe DigestStudents, mostrata nel listato 3, preleverà
i contenuti dei vari tag XML e creerà un'istanza
della classe Vector che può contenere molte istanze
della classe Student.
Listato
3 - La classe DigestStudents
package com.commonsbook.chap7;
import
java.util.Vector;
import org.apache.commons.digester.Digester;
public class DigestStudents {
Vector students; public DigestStudents(){students=
new Vector();}
public static void main(String[] args) {
DigestStudents digestStudents
= new DigestStudents();
digestStudents.digest();
}
private
void digest() {
try {
Digester digester
= new Digester();
//Push the current
object onto the stack
digester.push(this);
//Creates a new instance of the Student class
digester.addObjectCreate(
"students/student", Student.class ); setName
method of the Student instance
//Uses tag name
as the property name
digester.addBeanPropertySetter(
"students/student/name");
//Uses setCourse
method of the Student instance
//Explicitly specify
property name as 'course'
digester.addBeanPropertySetter("students/student/course","course");
//Move to next student
digester.addSetNext(
"students/student", "addStudent"
);
//Print
the contents of the Vector
DigestStudents ds
= (DigestStudents)
digester.parse(this.getClass().getClassLoader()
.getResourceAsStream("students.xml"));
System.out.println("Students
Vector "+ds.students);
}
catch (Exception ex) {
ex.printStackTrace();
}
}
public void addStudent( Student stud ) {
//Add a new Student instance
to the Vector
students.add( stud );
}
}
In
poche linee di codice, siamo riusciti a creare la Vector
delle istanze di Student. L'output del programma appare
come segue, con la visualizzazione dei valori di tag
del file students.xml:Students
Vector
[Name=Java Boy Course=JSP, Name=Java Girl Course=EJB]
Non
male, eh? Mi sarebbe piaciuto scrivere il codice DOM
e SAX corrispondente, per effettuare un confronto con
il componente Digester e illustrarne i vantaggi, ma
scrivere codice DOM e SAX è qualcosa che ho dimenticato
da tempo e non ho gran voglia di impararlo nuovamente
Continueremo con gli esempi legati al Digester; più
specificamente, analizzeremo alcuni aspetti fondamentali
del Digester per capire il modo in cui funziona effettivamente
l'esempio del listato 3.
Comprendiamo
i concetti del Digester
Il componente Digester ha le sue origini nel progetto
per il framework Struts e nacque come strumento per
effettuare un rapido parsing del file struts-config.xml
senza dover interagire direttamente con SAX. Dal momento
che le sue funzionalità potevano essere utili
a tutti i tipi di applicazione, il componente Digester
fu successivamente spostato nel progetto Commons.
Il Digester non è un parser XML ma piuttosto
una interfaccia ad alto livello che usa il sottostante
SAX per completare l'effettivo parsing XML; per il Digester
diventa quindi requisito essenziale la presenza di un
parser XML conforme alla versione 1.1 o successiva di
JAXP (Java API for XML Processing). Il Digester presenta
dipendenza inoltre dai seguenti componenti Commons:
- BeanUtils
- Collections
- Logging
Dal
momento che il Digester usa SAX per il parsing effettivo,
l'elaborazione di XML tramite Digester si verifica in
maniera indotta dagli eventi (event-driven) ossia quando
vengono innescati eventi durante il parsing del documento:
ciò che è necessario è di fornire
dei gestori (handler) per tali eventi. È questo
il meccanismo secondo cui funziona SAX: si tratta di
eventi che vengono innescati ogni volta che si riscontri
una determinata evenienza. Gli eventi in SAX vengono
innescati in base a evenienze quali tag di apertura
e di chiusura, e così via.
DOM funziona in maniera leggermente diversa: nella memoria
vengono creati dei modelli di oggetti che poi vengono
sottoposti a parsing. In ogni caso, quando si usa il
Digester, non è poi tanto importante comprendere
come funzionino effettivamente SAX o DOM, e non è
necessario compiere alcun task specificamente SAX nel
proprio codice.
L'importante è attenersi alle regole del Digester
e il parsing dei documenti XML avverrà in tutta
facilità.
Il
Digester utilizza uno stack dove salvare e da dove recuperare
oggetti mentre il file XML viene sottoposto a parsing.
Per chi non sa che cosa esattamente sia uno stack (=
"pila"), basta pensarlo come una scatola in
cui si continuano a introdurre oggetti che però
possono essere rimossi solamente sulla base del concetto
LIFO (Last In First Out, "l'ultimo a entrare è
il primo a poter uscire"). Java fornisce un'implementazione
di stack con java.util.Stack.
Sulla base delle regole definite e del codice XML incontrato,
il componente Digester impila gli oggetti sullo stack;
quando incontra l'apertura di un tag, l'oggetto associato
viene "posato" sullo stack da cui viene poi
"prelevato" dopo che tutto il contenuto annidato
di tale tag è stato elaborato. Quindi, nel listato
3, quando viene incontrato il tag student, un'istanza
della classe Student sarà posta in cima alla
pila, da cui verrà prelevata poi una volta che
l'elaborazione dei suoi tag annidati name e course sarà
stata completata.
Usare
schemi di corrispondenza
Il grande vantaggio derivante dall'usare il componente
Digester invece di altre API, è la presenza di
schemi per la corrispondenza degli elementi. A differenza
di altre API col le quali occorre preoccuparsi delle
relazioni genitore/figlio tra i tag, nel componente
Digester conta specificare lo schema di corrispondenza.
Per esempio, nel listato 3 sono stati usati gli schemi
di corrispondenza students/student, students/student/name
e students/student/course. Si tratta di un uso facile
e amichevole per lo sviluppatore per trasportare precisamente
il tag al quale si vuole fare riferimento. Se si dovessero
far coincidere i tag del listato 2 con i relativi pattern
di corrispondenza, tale mappatura sarebbe quella mostrata
in tabella 2.
Tag |
Schema |
<students>
|
students |
<students>
|
students/students |
<name>
|
students/students/name |
<course>
|
students/students/course |
Tabella
2 - Schemi di mappatura dei tagTag Schema
È
anche possibile usare il carattere jolly * se si desidera
una corrispondenza più generalizzata: in tal
caso, il pattern */name avrebbe avuto corrispondenza
con tutti i tag name presenti nel documento.
Usare
le regole
Con gli schemi di corrispondenza, si trasporta l'esatta
collocazione del tag nella struttura XML.
Tuttavia, per indicare al componente Digester che
comportamento deve essere applicato una volta individuato
un determinato tag, è necessario definire regole
di elaborazione. Queste regole entrano in azione quando
viene individuato il pattern di corrispondenza. Ci
si attende che tutte le regole estendano la classe
astratta org.apache.commons.digester.Rule e definiscano
le specifiche azioni che devono essere intraprese
quando ci si imbatte in un determinato elemento.
È possibile definire le proprie particolari
regole per gestire casi specifici di un'applicazione.
Il componente Digester include già una serie
di regole implementate che estendono la classe Rule
ed è possibile trovarle nel package org.apache.commons.digester.
Procedendo nella lettura, vedremo alcune di queste
regole all'opera negli esempi. Nel listato 3, è
stata usata ObjectCreateRule per creare un'istanza
della classe Student e BeanPropertySetterRule per
impostare le proprietà della classe.
Prima di addentrarsi in un esempio XML più
complesso di quello visto nel listato 2, vediamo i
passaggi necessari affinché il Digester recuperi
con successo i dati dall'XML:
-
Occorre creare una nuova istanza di org.apache.commons.digester.Digester
e configurarlo sando i diversi metodi setXxx forniti
dalla classe. Tra le altre proprietà, è
possibile definire se l'XML debba essere validato,
definire il logger che deve essere usato e definire
l'oggetto di implementazione Rules.
-
Si colloca il primo oggetto sulla pila di oggetti
(stack) utilizzando il metodo push del componente
Digester prima di definire gli schemi e le regole
da usare. Nel listato 3, si spingeva sullo stack
l'oggetto corrente utilizzando la parola chiave
this.
La ragione per cui occorre spingere questo oggetto
iniziale sulla pila è dovuta al fatto che
il Digester continua a mettere e togliere dalla
pila gli oggetti a mano a mano che incontra dei
tag. Pertanto il primo oggetto viene creato e collocato
sulla pila appena viene incontrato il primo tag
e questo stesso oggetto sarà smontato dalla
pila quando verrà elaborato l'ultimo tag.
Dal momento che occorre mantenere un riferimento
all'oggetto per il primo tag, l'oggetto iniziale
che si colloca sulla pila prima di effettuare il
parsing dell'XML, serve a tale scopo e mantiene
il riferimento a tale oggetto.
- Si
registrano gli schemi di corrispondenza degli elementi
e le regole che si desidera siano applicate per ciascun
caso. Nel listato 3 si registrano tre pattern e due
regole che si desidera siano applicate. 4 Infine,
si effettua il parsing del file XML usando il metodo
parse dell'istanza di Digester creata.
NOTA:
Per il Digester, l'ordine in cui si compiono le azioni
è importante. Non si possono spostare a caso
gli statement prima della invocazione del metodo parse.
Per esempio, nel listato 3, non è possibile spostare
la chiamata ad addObjectCreate dopo la invocazione di
addSetNext.
Passeremo ora a un esempio XML più complesso,
e cercheremo di elaborarlo usando il Digester. Vedremo
inoltre il modo in cui è possibile spostare la
specificazione di schemi e regole del Digester dal codice
a un file di configurazione XML.
Seguire
le regole XML
Nel listato 3, la maggior parte del codice serve a
configurare l'istanza del Digester. Praticamente nessuna
parte del codice può essere denominata come
codice orientato alle azioni. L'uso più comune
del Digester è di elaborare file di configurazione
basati su XML. La ragione per cui vengono usati tali
file di configurazione è quella di mantenere
il codice applicativo libero da informazioni di configurazione,
in maniera da rendere possibili dei cambiamenti senza
dover modificare il codice e doverlo ricompilare.
Non sarebbe il massimo collocare informazioni di configurazione
del Digester dentro il codice Java. Anche questa piccola
parte deve essere spostata in un file XML di configurazione.
Il package org.apache.commons.digester.xmlrules si
occupa di questo problema e la classe DigesterLoader
presente in esso rende possibile creare un'istanza
di Digester usando semplicemente le informazioni presenti
in un file XML.
Nell'esempio che segue, si analizzerà per prima
cosa il codice Java che svolgerà il compito
con righe in qualche modo simili a quelle del listato
3 e poi ci si sposterà a un file di configurazione
basato su XML per lo stesso esempio.
Il listato 4 mostra il file XML a partire dal quale
si desidera estrarre le informazioni. Il codice XML
contiene informazioni riguardanti un'accademia, i
suoi studenti e i suoi insegnanti. Il codice del Digester
preleva questi dettagli e li rende gestibili all'interno
di codice Java.
Listato
4 - academy.xml
<?xml version="1.0"?>
<academy name="JAcademy" >
<student name="JavaBoy" division="A">
<course>
<id>C1</id>
<name>JSP</name>
</course>
<course>
<id>C2</id>
<name>Servlets</name>
</course>
</student>
<student name="JavaGirl" division="B">
<course>
<id>C3</id>
<name>EJB</name>
</course>
</student> <teacher
name="JavaGuru">
<certification>SCJP</certification>
<certification>SCWCD</certification>
</teacher>
<teacher name="JavaMaster">
<certification>OCP</certification>
<certification>SCJP</certification>
<certification>SCEA</certification>
</teacher>
</academy>
NOTA:
Con il listato 4 ho cercato di illustrare i molti
scenari che è possibile incontrare quando si
effettua il parsing di file XML. Usando il codice
di questo esempio, è possibile rapidamente
mettere in pratica i concetti esposti.
Dal
momento che è necessario contenere i dati XML
in oggetti Java, occorre decidere quali classi devono
essere create: le istanze di questa classe conterranno
i dati. Osservando questo esempio, so dovrebbero notare
quattro classi che, insieme, possono svolgere un buon
lavoro nel mantenere i dati in un formato strutturato
in modo appropriato: si tratta delle classi Academy,
Student, Course e Teacher. Ovviamente è anche
possibile creare altre classi, tipo Certification.
L'aspetto più importante sta però nel
fatto che non si possono tenere queste classi come
semplici classi separate, ma occorre mantenere anche
le relazioni tra di esse nel modo che era illustrato
nel file XML. Innanzitutto si creeranno le classi
Java e le istanze di queste classi conterranno i dati.
Un'istanza della classe Course è pensata per
contenere semplicemente il nome e l'ID del corso.
Non sarà l'istanza di Course a mantenere la
sua relazione con Student; tale relazione sarà
mantenuta dall'istanza di Student. Il listato 5 mostra
la classe Course; ha due proprietà con i corrispondenti
metodi get e set. Va notato che il nome del package
per le classi usate in questo esempio è com.commonsbook.chap7.academy.
Listato
5 - La classe Course
package com.commonsbook.chap7.academy;
import org.apache.commons.beanutils.PropertyUtils;
import java.util.Vector;
public class Course {
private String id;
private String name;
public Course() {}
public String getId() {
return id;
}
public void setId(String newId) {
id = newId;
}
public String getName() {
return name;
}
public void setName(String newName) {
name = newName;
}
public String toString() {
StringBuffer buf = new StringBuffer(60);
buf.append("\n\tCourseId>>>
" + this.getId() + "\t");
buf.append("CourseName>>>
" + this.getName());
return buf.toString();
}
}
Poi
si definirà la classe Student che non solo
deve contenere le informazioni relative allo studente
ma anche quelle sui corsi che lo studente frequenta.
Come è possibile vedere nel listato 6, i dettagli
sullo studente sono immagazzinati usando delle proprietà,
mentre i corsi saranno salvati come un Vector di istanze
di Course.
Listato
6 - La classe Student
package com.commonsbook.chap7.academy;
import java.util.Vector;
public class Student {
private Vector courses;
private String name;
private String division; public Student()
{
courses = new Vector();
}
public void addCourse(Course course) {
courses.addElement(course);
}
public String getName() {
return name;
}
public void setName(String newName) {
name = newName;
}
public String getDivision() {
return division;
}
public void setDivision(String newDivision)
{
division = newDivision;
}
public
void setCourses(Vector courses) {
this.courses = courses;
}
public
Vector getCourses() {
return courses;
}
public
String toString() {
StringBuffer buf = new StringBuffer(60);
buf.append("\nStudent
name>> " + this.getName());
Vector courses = this.getCourses();
//Iterate through vector.
Append content to StringBuffer.
for (int i = 0; i < courses.size();
i++) {
buf.append(courses.get(i));
}
return buf.toString();
}
}
Il
listato 4 mostra che, per un insegnante, è
previsto che vengano salvate le sue generalità
e l'elenco delle certificazioni possedute. La classe
Teacher, mostrata nel listato 7, ottiene tale risultato
usando una proprietà String per il nome e un
Vector contenente istanze di String per l'elenco delle
certificazioni.
Listato
7 - La classe Teacher
package com.commonsbook.chap7.academy;
import org.apache.commons.beanutils.PropertyUtils;
import java.util.Vector;
public
class Teacher {
private String name;
private Vector certifications;
public Teacher() {
certifications = new Vector();
}
public
void addCertification(String certification) {
certifications.addElement(certification);
}
public
String getName() {
return name;
}
public
void setName(String newName) {
name = newName;
}
public
void setCertifications(Vector certifications) {
this.certifications = certifications;
}
public
Vector getCertifications() {
return certifications;
}
public
String toString() {
StringBuffer buf = new StringBuffer(60);
buf.append("\nTeacher
name>> " + this.getName());
Vector certs = this.getCertifications();
//Iterate through vector.
Append content to StringBuffer.
for (int i = 0; i < certs.size();
i++) {
buf.append("\n\tCertification>>
" + certs.get(i));
}
return buf.toString();
}
}
Il
tag academy è il tag root mostrato nel listato
4, pertanto la classe Academy non deve solamente immagazzinare
il nome dell'accademia, ma anche i riferimenti ai
dati presenti nei tag figli contenuti all'interno
del tag academy. Pertanto, la classe Academy, mostrata
nel listato 8, ha due Vector, uno dei quali conterrà
le istanze delle classi Student e un altro che conterrà
le istanze delle classi Teacher. Pertanto, direttamente
o indirettamente, si dovrebbe essere in grado di accedere
a tutti i dati definiti nel listato 4, usando un riferimento
a un'istanza della classe Academy popolata correttamente.
Il metodo toString ridefinito sarà usato più
avanti in questo articolo, per stampare i dati contenuti
da un'istanza di Academy.
Listato
8 - Classe Academy
package com.commonsbook.chap7.academy;
import org.apache.commons.beanutils.PropertyUtils;
import java.util.Vector;
public class Academy {
private Vector students;
private Vector teachers;
private String name;
public Academy() {
students = new Vector();
teachers = new Vector();
}
public
void addStudent(Student student) {
students.addElement(student);
}
public
void addTeacher(Teacher teacher) {
teachers.addElement(teacher);
}
public
Vector getStudents() {
return students;
}
public
void setStudents(Vector newStudents) {
students = newStudents;
}
public
Vector getTeachers() {
return teachers;
}
public
void setTeachers(Vector newTeachers) {
teachers = newTeachers;
}
public
String getName() {
return name;
}
public
void setName(String newName) {
name = newName;
}
public
String toString() {
StringBuffer buf = new StringBuffer(60);
buf.append("Academy
name>> " + this.getName());
Vector stud = this.getStudents();
Vector teach = this.getTeachers();
buf.append("\n\n**STUDENTS**");
//Iterate through vectors.
// Append content to StringBuffer.
for (int i = 0; i < stud.size();
i++) {
buf.append(stud.get(i));
}
buf.append("\n\n**TEACHERS**");
for (int i = 0; i < teach.size();
i++) {
buf.append(teach.get(i));
} return buf.toString();
}
}
Una volta finito il lavoro con le classi che dovranno
contenere i dati, ci si sposterà al codice
Digester che effettivamente effettuerà il parsing
dell'XML. Si noterà innanzitutto il modo in
cui si specificano le istruzioni Digester all'interno
del codice Java. Successivamente si sposteranno queste
istruzioni in un file XML facilmente configurabile
rendendo il codice Java breve e semplice.
Il listato 9 mostra il codice Java per specificare
le regole Digester ed effettuare il parsing dell'XML
secondo tali regole. L'aspetto più importante
da notare in questa porzione di codice è l'uso
delle seguenti regole: o ObjectCreate: questa regola
crea una nuova istanza delle classi Academy, Student,
Teacher e Course quando viene incontrato un pattern
di corrispondenza. o SetProperties: la regola SetProperties
imposta le proprietà della classe usando i
valori di un attributo. Poiché il nome dell'attributo
e della proprietà nella classe corrispondono
perfettamente, non occorre specificare dettagli a
tal proposito; tuttavia, se i nomi dell'attributo
in XML e della proprietà in Java sono differenti,
occorre specificare una mappatura di corrispondenza.
o BeanPropertySetter: questa regola imposta le proprietà
del bean usando i valori dei tag figli.
Per esempio, le proprietà id e name dell'istanza
della classe Course sono impostati usando questa regola.
o SetNext: la regola SetNext causa lo spostamento
ai tag course, student e teacher successivi. Viene
inoltre specificato il metodo da invocare in ciascun
caso. o CallMethod: la regola CallMethod specifica
il metodo che deve essere invocato quando viene individuato
un determinato pattern. Si specifica inoltre il numero
di parametri che tale metodo si attende. o CallParam:
la regola CallParam specifica il valore del parametro
che deve essere passato all'invocazione del metodo
definita con la regola CallMethod.
Listato
9 - Classe DigestJavaAcademy (regole del Digester
definite in codice Java)
package
com.commonsbook.chap7.academy;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.digester.Digester;
import java.util.Vector;
public
class DigestJavaAcademy {
public
static void main(String[] args) throws Exception {
DigestJavaAcademy
d = new DigestJavaAcademy();
d.digest();
}
public
void digest() throws Exception {
Digester
digester = new Digester();
digester.addObjectCreate("academy",
Academy.class);
//Set
the attribute values as properties
digester.addSetProperties("academy");
//A
new Student instance for the student tag
digester.addObjectCreate("academy/student",
Student.class);
//Set
the attribute values as properties
digester.addSetProperties("academy/student");
//A
new Course instance
digester.addObjectCreate("academy/student/course",
Course.class);
//Set
properties of the Course instance with values of two
child tags
digester.addBeanPropertySetter("academy/student/course/id",
"id");
digester.addBeanPropertySetter("academy/student/course/name",
"name"); //Next
Course
digester.addSetNext("academy/student/course",
"addCourse");
//Next
student
digester.addSetNext("academy/student",
"addStudent");
//A
new instance of Teacher
digester.addObjectCreate("academy/teacher",
Teacher.class);
//Set
teacher name with attribute value
digester.addSetProperties("academy/teacher");
//Call
Method addCertification that takes a single parameter
digester.addCallMethod("academy/teacher/certification",
"addCertification",
1);
//Set
value of the parameter for the addCertification method
digester.addCallParam("academy/teacher/certification",
0);
//Next
Teacher
digester.addSetNext("academy/teacher",
"addTeacher");
//Parse
the XML file to get an Academy instance
Academy
a = (Academy) digester.parse(this.getClass()
.getClassLoader()
.getResourceAsStream("academy.xml"));
System.out.println(a);
}
}
L'ordine
in cui le regole vengono definite è importante.
Si è appena rappresentato ciò che a
noi appare evidente dal file XML in una maniera comprensibile
anche al Digester.
Per mandare in esecuzione questa porzione di codice,
occorre che il file academy.xml sia presente nel CLASSPATH.
Il listato 10 mostra l'output risultante dall'esecuzione
di tale porzione di codice.Listato 10 - Output derivante
dall'esecuzione del codice presentato nel listato
9
Academy
name>> JAcademy**STUDENTS**
Student name>> JavaBoy
CourseId>>> C1 CourseName>>> JSP
CourseId>>> C2 CourseName>>> Servlets
Student name>> JavaGirl
CourseId>>> C3 CourseName>>> EJB**TEACHERS**
Teacher name>> JavaGuru
Certification>> SCJP
Certification>> SCWCD
Teacher name>> JavaMaster
Certification>> OCP
Certification>> SCJP
Certification>> SCEA
Osservando il listato 9 appare evidente che quasi
tutto il codice è dedicato alla configurazione
del Digester. Non ci è forse stato sempre insegnato
a livello accademico, che ogni volta che è
possibile, è bene spostare tutti gli elementi
configurabili in un file esterno che possa essere
facilmente gestito e manipolato. E allora, perché
non farlo anche in questo caso?
Il package org.apache.commons.digester.xmlrules consente
una definizione basata su XML delle regole per il
Digester. Definire le regole Digester in XML è
piuttosto semplice una volta che si sia afferrato
il senso delle varie regolo e i compiti che esse svolgono.
Considerando la grande diffusione di XML, le nostre
regole del Digester sono adesso comprensibili in maniera
più facile per un ampia schiera del personale
coinvolto: pensate, addirittura un manager potrebbe
riuscire a comprenderne una o due!
Il listato 11 mostra le regole definite con Java nel
listato 9: questa volta esse sono realizzate usando
l'XML.
Listato
11 - academyRules.xml (regole del Digester definite
in XML)
<?xml version="1.0"?>
<digester-rules>
<pattern value="academy">
<object-create-rule classname="com.commonsbook.chap7.academy.Academy"
/>
<set-properties-rule />
<pattern value="student">
<object-create-rule classname="com.commonsbook.chap7.academy.Student"
/>
<set-properties-rule /> <pattern
value="course">
<object-create-rule classname="com.commonsbook.chap7.academy.Course"
/>
<bean-property-setter-rule pattern="id"/>
<bean-property-setter-rule pattern="name"/>
<set-next-rule methodname="addCourse"
/>
</pattern>
<set-next-rule methodname="addStudent"
/>
</pattern> <pattern
value="teacher">
<object-create-rule classname="com.commonsbook.chap7.academy.Teacher"
/>
<set-properties-rule />
<call-method-rule pattern="certification"
methodname="addCertification"
paramcount="1" />
<call-param-rule pattern="certification"
paramnumber="0"/>
<set-next-rule methodname="addTeacher"
/>
</pattern>
</pattern>
</digester-rules>
Nel
codice del listato 11, le regole definite in XML si
mappano in maniera quasi diretta con i metodi definiti
in Java (listato 9). Tutte le regole sono adesso definite
usando tag di tale nome. Il modo più semplice
per controllare l'uso di tali tag sta nell'aprire
il file digester-rules.dtd che può essere facilmente
individuato nel download del sorgente del componente
Digester.
Tuttavia, anche con il download in binario, questo
file può essere estratto dal file commons-digester.jar
ed è presente nel package org.apache.commons.digester.xmlrules.
È anche possibile leggere il codice di tale
file e del Digester usando ViewCVS disponibile all'indirizzo
http://jakarta.apache.org/site/cvsindex.html.
I
file DTD (Document Type Definition) definiscono la
sintassi e la struttura dei file XML e, sebbene ci
voglia un po' di tempo per abituarsi alle loro caratteristiche,
la loro comprensione non è difficile.
Una volta definite le regole in XML, il rimanente
codice Java è semplice. Il listato 12 mostra
il codice Java in cui si definiscono i file di regole
da usare per creare un'istanza del Digester per poi
effettuare il parsing del file XML usando tale istanza.
Listato
12 - Classe DigestXMLJavaAcademy
(codice Java che usa le regole definite in XML)
package
com.commonsbook.chap7.academy;
import java.io.File;
import java.util.Vector;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.digester.Digester;
import org.apache.commons.digester.xmlrules.DigesterLoader;
public class DigestXMLJavaAcademy {
public
static void main( String[] args ) {
DigestXMLJavaAcademy
xmlDigest= new DigestXMLJavaAcademy();
xmlDigest.digest();
}
public
void digest(){
try
{
//Create
Digester using rules defined in academyRules.xml
Digester
digester = DigesterLoader.createDigester(
this.getClass().getClassLoader().getResource("academyRules.xml"));
//Parse
academy.xml using the Digester to get an instance
of Academy
Academy
a = (Academy)digester.parse(
this.getClass().getClassLoader().getResourceAsStream("academy.xml"));
Vector
vStud=a.getStudents();
Vector
vTeach=a.getTeachers();
for
(int i = 0; i < vStud.size(); i++){
System.out.println("Student>>
"+
PropertyUtils.describe(vStud.get(i)));
}
for
(int i = 0; i < vTeach.size(); i++) {
System.out.println("Teacher>>
"+
PropertyUtils.describe(vTeach.get(i)));
}
}
catch( Exception e ) {
e.printStackTrace();
}
}
}
I
due file academy.xml e academyRules.xml devono essere
presenti nel CLASSPATH. Quando tale codice viene eseguito,
si ottiene lo stesso output mostrato nel listato 10
che si era ottenuto con il codice Java del listato
9.
Altre
caratteristiche del Digester
Oltre alle funzioni del Digester viste, si farà
un rapido accenno ad altre funzionalità di
tale componente.
o Le possibilità di logging possono risultare
utili nella risoluzione dei problemi. Il Digester
utilizza il componente Logging del progetto Commons
e la classe del Digester fornisce essa stessa un metodo
setLogger con il quale è possibile definire
esattamente il logger che deve essere usato.
o Il package org.apache.commons.digester.rss fornisce
un esempio di uso del Digester per il parsing di XML
nel formato Rich Site Summary (RSS) molto usato dai
siti di notizie per fornire continui aggiornamenti
alle notizie. La maggior parte dei sistemi di gestione
dei contenuti più diffusi supporta il formato
RSS; maggiori informazioni su RSS possono essere trovate
all'indirizzo http://blogs.law.harvard.edu/tech/rss/.
o È possibile configurare il Digester per la
validazione di XML con un file DTD. Si deve registrare
il DTD usando il metodo register, e si può
passare alla validazione usando il metodo setValidating
della classe Digester.
o È possibile configurare il Digester perché
effettui le corrispondenze sui pattern in base ai
namespace. Si useranno i metodi setNamespaceAware
e setRuleNamespaceURI in modo tale che il Digester
non possa confondere un tag name in un namespace X
con un tag name simile in un namespace Y.
Riepilogo
In questo articolo abbiamo affrontato il componente
Digester, che riduce drasticamente la complessità
insita nel parsing di XML. Si è visto il modo
in cui il Digester funziona sulla base del semplice
concetto di schemi di corrispondenza tra elementi,
e come è possibile definire regole in codice
Java o anche in un file XML separato. Si sono inoltre
visti alcuni esempi che rappresentano casi comuni
in cui è necessario il parsing XML.
Usare il Digester e definire le regole in un file
XML separato ha, da parte mia, la massima approvazione:
l'uso del Digester per tutte le necessità di
parsing XML è vivamente raccomandato.
L'autore
L'autore ha scritto i libri Pro Jakarta Commons (Apress,
2004), Oracle JDeveloper 10g: Empowering J2EE Development
(Apress, 2004) ed è anche uno degli autori
di Java 2 Enterprise Edition 1.4 Bible (Wiley &
Sons, 2003). Harshad Oak ha conseguito un master in
management informatico ed è certificato da
Sun come programmatore Java e sviluppatore di componenti
per web application. Ha fondato la Rightrix Solutions
(http://www.rightrix.com) che si occupa principalmente
di sviluppo software e servizi di gestione dei contenuti.
Precedentemente, Harshad aveva preso parte a progetti
J2EE presso la i-flex Solutions e la Cognizant Technology
Solutions.
È inoltre autore di diversi articoli su Java/J2EE
per CNET Builder.com (http://www.builder.com/), e
tiene inoltre diverse conferenze su Java e J2EE. Il
suo indirizzo è harshad@rightrix.com
|