Untitled Document
   
 
Usare il componente Digester
di Harshad Oak, traduzione di Francesco Saliola

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:

  1. 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.
  2. 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.
  3. 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