MokaByte 60 - Febbraio 2002 
XML data binding in Java
di
Andrea Giovannini
Il data binding è il ponte fra documenti XML e un modello a oggetti Java. Questo articolo presenta la tecnologia e alcuni tool disponibili, discutendo in particolare i vantaggi per lo sviluppatore di applicazioni

Introduzione
Per l'elaborazione di documenti XML lo sviluppatore Java ha a disposizione principalmente tre strumenti: SAX, DOM e JDOM; quando si tratta di accedere ai dati contenuti in un documento ognuno di questi metodi presenta però alcuni svantaggi. Ad esempio l'uso di SAX richiede di scrivere un handler per poter gestire i dati di un documento XML, cosa non sempre agevole. DOM mette a disposizione un modello a oggetti per manipolare il documento ma è troppo generico, abbastanza verboso e inoltre un oggetto DOM non include alcuna logica applicativa. JDOM risolve alcuni problemi di DOM mettendo a disposizione una API Java-oriented per elaborare un documento ma rimane ancora il problema di come gestire la logica applicativa.Molte volte si hanno cioé necessità diverse e più complesse che necessitano di un completo modello a oggetti per il dominio applicativo in esame; si vuole cioè

  • mappare un documento XML in una gerarchia di oggetti Java
  • eseguire una qualche logica applicativa sugli oggetti
  • eventualmente trasformare la gerarchia di oggetti ancora in un documento XML da
  • salvare su DB oppure da inviare ad un'altra applicazione.

Il data binding risponde proprio a queste esigenze e i vari tool disponibili mettono a disposizione gli strumenti per risolvere i problemi precedenti.
Questo articolo mostrerà innanzitutto i principi alla base del data binding come i requisiti e alcuni idee alla base di una possibile implementazione. Si presenterà quindi un esempio reale di utilizzo di questa tecnologia; tale esempio sarà alla base della rassegna di tool/framework disponibili nel mondo
Java: Castor del Gruppo ExoLab, Zeus del progetto Enhydra e JAXB, Java Architecture for XML Binding di Sun.

Data binding XML
E' possibile trovare una analogia fra concetti in XML e in un linguaggio di programmazione a oggetti come Java (o anche altri):

  • descrizione di un tipo: rappresentata con schema o DTD in XML e classi o interfaccie in Java;
  • istanza di un tipo: documenti XML e oggetti Java;

Queste analogie sono alla base dei requisiti di uno strumento di data-binding:

  • disponibilità di un compilatore di schema/DTD, in grado di generare classi Java a partire da uno schema o da un DTD;
  • funzionalità di marshalling, per trasformare in XML un oggetto Java, e di unmarshalling per istanziare un oggetto Java con i dati di un documento XML. Tali considerazioni devono poi essere valide per una gerarchia di oggetti Java.

Considerando un compilatore di schema/DTD il primo problema da affrontare è la compatibilità fra i dati contenuti nel documento XML e l'interfaccia della corrispondente classe Java (si tralasceranno i problemi tecnici relativi alla generazione di codice).
Si supponga di avere nel documento un tag per rappresentare un codice con contenuto alfanumerico, come ad esempio <Code>C024</Code>; la classe Java dovrà quindi avere una coppia di metodi get/set per l'attributo codice, ovvero

public void setCodice(String codice);
public String getCodice();

Purtroppo le cose non sono sempre così facili; si consideri ora il caso di una stringa che può avere solo un numero limitato, ovvero una enumerazione, di valori possibili come ad esempio gli stati di un'attività

<xsd:simpleType name="stateType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="started"/>
<xsd:enumeration value="inProgress"/>
<xsd:enumeration value="suspended"/>
<xsd:enumeration value="finished"/>
</xsd:restriction>
</xsd:simpleType>

Il corrispondente idioma di programmazione Java è una classe che enumera i valori possibili come costanti pubbliche

public class State {
    public final String STARTED="started";
    public final String IN_PROGRESS="inProgress";
    public final String SUSPENDED="suspended";
    public final String FINISHED="finished";
}

La classe Java corrispondente allo schema avrà una coppia di metodi setState()/getState() è sarà possibile impostare lo stato in modo type safe con

activity.setState(State.STARTED);

in questo modo però non si ha alcun modo di verificare che il metodo setState() venga richiamato con valori legali. Ci sono diverse alternative:

  • generare il metodo setState() in modo che includa il controllo sui valori possibili

    public void setState(String state) {
        if (state != State.STARTED &&
            state != State.IN_PROGRESS &&
            state != State.SUSPENDED &&
            
    state != State.FINISHED) {
        throw IllegalArgumentException("Invalid state.");
        }
        else {
            this.state = state;
        }
    }


  • definire un'ulteriore classe State per rappresentare gli stati;
  • includere un ulteriore metodo validate() che esegue un controllo su tutti gli attributi della classe in base ai vincoli definiti nello schema.
  • Per quanto riguarda gli aspetti di marshalling/unmarshalling lo strumento chiave è rappresentato dalla Reflection API e una trattazione completa degli aspetti tecnici richiederebbe troppo spazio. E' possibile trovare in [4,5,6,7] una serie di articoli che spiegano in dettaglio la realizzazione di un tool di data binding.

Esempio
Le sezioni successive presenteranno una rassegna di tool di data-binding XML. Per poterli confrontare si presenterà ora l'esempio di riferimento. Si supponga di voler gestire i dati di una serie di attività, una todo-list. I dati saranno rappresentati in un documento XML che conterrà alcune informazioni generali,
come un codice identificativo e il nome dell'utente, e la lista di attività.Per la generazione delle classi Java sono necessari lo schema XML oppure il DTD del documento. Per completezza vengono presentati entrambi.

Schema XML
<?xml version="1.0"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="todolist">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="item" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="number" type="xsd:integer" minOccurs="1" maxOccurs="1"/>
<xsd:element name="priority" type="xsd:integer" minOccurs="1" maxOccurs="1"/>
<xsd:element name="description" type="xsd:string" minOccurs="1" maxOccurs="1"/>
<xsd:element name="state" type="xsd:integer" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>

DTD
<!ELEMENT todolist ( item* ) >
<!ELEMENT item (number, priority, description, state) >
<!ELEMENT number (#PCDATA) >
<!ELEMENT priority (#PCDATA) >
<!ELEMENT description (#PCDATA) >
<!ELEMENT state (#PCDATA) >

Ecco ora il documento XML che verrà usato negli esempi successivi:

<?xml version="1.0"?>
<todolist>
<item>
<number>1</number>
<priority>6</priority>
<description>Leggere la posta</description>
<state>2</state>
</item>
<item>
<number>2</number>
<priority>9</priority>
<description>Riunione</description>
<state>2</state>
</item>
<item>
<number>3</number>
<priority>8</priority>
<description>Andare a correre nel parco</description>
<state>1</state>
</item>
</todolist>

Con ognuno dei tool/framework presentati si procederà nel seguente modo:

  • si genereranno le classi corrispondenti alle entità gestite nel documento;
  • si popolerà una collezione di oggetti a partire dal documento XML precedente;
  • si modificheranno gli oggetti nella collezione;
  • verrà creato infine un nuovo file XML a partire dalla collezione modificata.


Castor XML
Castor è un framework sviluppato dal gruppo Exolab che implementa

  • data binding XML;
  • JDO Java Database Object, una recente specifica di Sun per la gestione della persistenza degli oggetti Java;
  • directory LDAP;

In questo articolo si valuteranno solo le caratteristiche del framework di data binding XML ma si tenga comunque presente che l'interoperabilità fra JDO, documenti XML e LDAP fornisce a un framework di questo tipo un notevole valore aggiunto.
Il generatore di codice di Castor permette di generare classi Java a partire da uno schema che rispetti la Candidate Reccomendation (10/24/2000). Oltre alle classi Java vengono generati anche i class descriptor.
Dopo aver incluso nel classpath castor-0.9.jar e xerces.jar si invoca il generatore di codice nel modo seguente

java org.exolab.castor.builder.SourceGenerator -i todolist.xsd

Verranno generati i file Todolist.java, TodolistDescriptor.java, Item.java e ItemDescriptor.java.

Todolist.java e Item.java sono le classi Java che rappresentano le entità definite nello schema mentre TodolistDescriptor.java e ItemDescriptor.java sono, nella terminologia di Castor, i corrispondenti class descriptor. Un class descriptor contiene le informazioni sulla struttura della classe che il framework utilizza durante le fasi di marshalling e unmarshalling. Ci sono quattro modi di generare un class descriptor:

  • utilizzando il generatore di codice, come nel nostro caso;
  • implementando l'interfaccia org.exolab.castor.xml.XMLClassDescriptor;
  • fornire al framework un file di configurazione XML;
  • lasciare che il framework 'deduca' le informazioni sulla classe mediante introspezione, ovvero reflection, sugli oggetti Java;

Le prime due modalita' vengono attivate a compile-time e offrono vantaggi di prestazioni rispetto alle altre due possibilità sono basate sul supporto a run-time del framework. Gli stessi class descriptor possono essere usati anche con JDO.

Le classi generate, oltre alle property get/set applicative, presentano caratteristiche molto interessanti:

  • incapsulano il codice di marshalling/unmarshalling, ovvero ogni classe ha un metodo unmarshal() per popolare un oggetto da un java.io.Reader e due metodi marshal() per trasmettere in output il corrispondente documento XML a un java.io.Writer, a un Handler SAX oppure creare un documento DOM.
  • le classi contengono inoltre la gestione delle relazioni fra le entità; in questo caso la classe Todolist ha una serie di metodi per accedere alla collezione di oggetti Item ovvero il generatore di codice ha mantenuto la relazione 1:N fra Todolist e Item;
  • ogni classe dispone inoltre di un metodo validate() che permette di validare gli oggetti in base allo schema.

Come si vedrà in seguito tali caratteristiche sono più o meno presenti anche negli altri tool presentati.
Nella compilazione delle classi precedenti si otterranno dei warning dovuti all'utilizzo dell'interfaccia org.xml.sax.DocumentHandler che è stata deprecata in SAX 2 e sostituita all'interfaccia org.xml.sax.ContentHandler.
Segue ora il codice che utilizza le classi generate

import java.io.*;

public class TestCastor {

    public static void main(String args[]) throws Exception {
        Reader reader = new FileReader(args[0]);

        // Popola la todolist con i dati specificati
        // nel file XML
        Todolist list = Todolist.unmarshal(reader);

        // Modifica la collezione
        for(int i=0; i<list.getItemCount(); i++) {
            // impostiamo tutte le attività come da iniziare
            list.getItem(i).setState(0);
        }

        // Aggiunge una nuova attività
        Item item = new Item();
        item.setNumber(4);
        item.setPriority(10);
        item.setDescription("Leggere MokaByte");         item.setState(0);
        
        list.addItem(item);

        // Validazione dei dati
        list.validate();

        // Salva i dati sul file XML
        Writer writer = new FileWriter(args[0]);
        list.marshal(writer);
    }
}

La semplicità del codice precedente mostra decisamente tutti i vantaggi del data-binding: eseguire le stesse operazioni usando SAX, DOM o anche JDOM avrebbe richiesto molte più righe di codice delle 23 viste nella classe TestCastor.
L'oggetto Todolist viene popolato dal file XML specificato dalla linea di comando, quindi si modificano tutte le attività della lista impostando il loro stato a 0, come se le attività fossero tutte ancora da iniziare. Si aggiunge poi una nuova attività semplicemente creando un nuovo oggetto e impostando le sue proprietà. Si valida la Todolist ottenuta e infine si aggiorna il documento salvando l'oggetto modificato.

 

JAXB
Sun sta sviluppando una serie di framework per l'elaborazione di documenti XML in Java (parsing, trasformazioni XSLT, messaging asincrono di documenti XML, XML RPC, ) o di questi framework è proprio JAXB, Java Architecture for XML binding. Al momento le uniche API disponibili sono JAXP (XML Processing) e JAXM (XML Messaging) distribuite nel Java XML Pack, scaricabile dal sito Sun. La release di JAXB è invece ancora una early access, la si può cioé scaricare e usare per fare i propri test.
Per la generazione di codice si userà il DTD mostrato in precedenza in quanto JAXB al momento non supporta ancora gli schema; questa feature è comunque prevista nelle prossime versioni. Prima di questo passo è necessario scrivere un ulteriore documento, il JAXB binding schema, sfruttato dal compilatore nella
generazione delle classi. L'idea è quella di fornire una maggiore espressività nella definizione dei tipi e separare i dettagli di programmazione dallo schema. Il binding schema permette ad esempio di definire fra le altre cose costruttori custom e i nomi per i package, classi e metodi.
Ecco il binding schema per la todo list

<?xml version="1.0"?>
<xml-java-binding-schema version="1.0ea">
<element name="todolist" type="class" root="true" />

<element name="number" type="value" convert="int"/>
<element name="priority" type="value" convert="int"/>
<element name="state" type="value" convert="int"/>
</xml-java-binding-schema>

Il binding schema specifica innanzitutto che l'elemento radice del documento è todolist, quindi vengono specificate le informazioni sul fatto che gli elementi number, priority e state sono interi. Senza queste informazioni il generatore di codice avrebbe gestito tali valori come stringhe.

A questo punto dati il DTD e il binding schema si è in grado di generare il codice mediante lo schema compiler, incluso nella libreria jaxb-xjc-1.0-ea.jar; per la todolist vengono generate due classi: Todolist.java e Item.java.
Come nel caso di Castor anche le classi generate da JAXB incapsulano il codice di gestione delle relazioni e di validazione.
Per quanto riguarda l'unmarshalling vengono generati metodi che permettono di popolare l'oggetto a partire da un InputStream o da un documento DOM. Per leggere i dati non viene utilizzato un parser completo in quanto alcuni controlli di validità vengono lasciati al codice generato: a basso livello si utilizza l'oggetto
javax.xml.marshal.XMLScanner che permette di scorrere il contenuto del documento con un meccanismo di pattern matching.
Il marshalling del documento viene eseguito su un OutputStream; con la versione attuale non è possibile generare stream SAX o documenti DOM.
Ecco il codice che utilizza le classi generate:

import java.io.*;
import java.util.*;
import javax.xml.bind.*;
import javax.xml.marshal.*;

public class TestJAXB {

  public static void main(String args[]) throws Exception {
    File inputFile = new File(args[0]);
    FileInputStream fis = new FileInputStream(inputFile);

    // Popola la todolist con i dati specificati nel file XML
    Todolist list = Todolist.unmarshal(fis);

    List todolist = list.getItem();

    // Modifica la collezione
    for(Iterator i=todolist.iterator(); i.hasNext(); ) {
        Item item = (Item)i.next();
        // impostiamo tutte le attività come da iniziare
        item.setState(0);
    }

    // Aggiungiamo una nuova attività
    Item item = new Item();
    item.setNumber(4);
    item.setPriority(10);
    item.setDescription("Leggere MokaByte");
    item.setState(0);

    todolist.add(item);

    // Validazione dei dati
    list.validate();

    // Salva i dati sul file XML
    File outFile = new File(args[1]);
    FileOutputStream fos = new FileOutputStream(outFile);
    list.marshal(fos);
    }
}

Come si può osservare è del tutto analogo a quello usato con Castor e non verrà ulteriormente commentato.

 

Enhydra Zeus
Zeus è un tool sviluppato all'interno del progetto Enhydra, un application server Java open source. E' interessante notare che fra i suoi sviluppatori figura Brett McLaughlin, molto attivo come scrittore tecnico di articoli e libri sulla programmazione XML in Java.
La versione di Zeus utilizzata, 1.0 beta 3.1, permette di generare il codice a partire da un DTD e anche da un XSD. Lo schema visto in precedenza ha però dato luogo ad alcuni problemi nel codice generato in quanto, tra le altre cose, Zeus non ha riconosciuto la struttura gerarchica ed è stato quindi necessario modificare lo schema definendo elementi separati. Per questi motivi il test è stato fatto utilizzando il DTD.
Per lanciare il generatore di codice è necessario includere le librerie zeus.jar, dtdparser113.jar, jdom.jar e xerces.jar quindi si esegue

java org.enhydra.zeus.util.DTDSourceGenerator -constraints=todolist.dtd

Una peculiarità del codice generato è che non necessita di alcun supporto a run-time per il marshalling/unmarshalling; non deve quindi sorprendere che per la todo list si abbiano ben 14 classe generate. Per ogni elemento definito vengono infatti generate un'interfaccia (es. Todolist.java e Item.java), con i metodi di gestione delle relazioni e di marshalling, e un handler SAX (es. TodolistImpl.java e ItemImpl.java) che implementa i metodi dell'interfaccia oltre che ovviamente quelli di unmarshalling via SAX. Esaminando le classi si può vedere come il marshalling venga invece eseguito mediante concatenazione di stringhe.
Viene generata una coppia di classi anche per ogni altro elemento del documento quindi anche per number, priority, description e state dell'attività.
Le classi generate non contengono, a differenza di Castor e JAXB, metodi di validazione.
Oltre alle precedenti vengono generate due ulteriori classi: Unmarshallable.java, interfaccia che estende org.xml.sax.ContentHandler utilizzata per rappresentare i nodi durante la fase di unmarshalling, e TodolistUnmarshaller.java che contiene i metodi statici per l'unmarshalling dei dati.
Ecco il codice di esempio che utilizza le classi generate da Zeus:

import java.io.*;
import java.util.*;

public class TestZeus {

    public static void main(String args[]) throws Exception {
        File inputFile = new File(args[0]);

        // Popola la todolist con i dati specificati
        //
nel file XML
        Todolist list = TodolistUnmarshaller.unmarshal(inputFile);

        List todolist = list.getItemList();

        // Modifica la collezione
        for(Iterator i=todolist.iterator(); i.hasNext(); ) {
            Item item = (Item)i.next();
            // impostiamo tutte le attività come da iniziare
            State state = new StateImpl();
            state.setValue("0");
            item.setState(state);
        }

        // Aggiungiamo una nuova attività
        Item item = new ItemImpl();
        NumberImpl number = new NumberImpl();
        number.setValue("4");
        item.setNumber(number);
        Priority priority = new PriorityImpl();
        priority.setValue("10");
        item.setPriority(priority);
        Description description = new DescriptionImpl();
        description.setValue("Leggere MokaByte");
        item.setDescription(description);
        State state = new StateImpl();
        state.setValue("0");
        item.setState(state);

        todolist.add(item);

        // Salva i dati sul file XML
        list.marshal(new File(args[1]));
    }
}


La differenza principale rispetto agli esempi precedenti è data dal fatto che anche gli attributi della classe Item vengono rappresentati da classi, ognuna delle quali ha una coppia di metodi getValue()/setValue() per accedere e impostare il valore. Poiché il codice è stato generato da un DTD e quindi senza informazioni sui
tipi tutti i valori vengono rappresentati come stringhe.

Conclusioni
Questo articolo ha introdotto i concetti di data binding XML, discusso alcuni particolari tecnici e presentato una rassegna di alcuni tool disponibili: Castor, Zeus e JAXB. Fra questi Castor sembra sicuramente il più maturo mentre la principale limitazione di JAXB è il mancato supporto agli schema. Da questo punto di vista la necessità di scrivere in JAXB due documenti, il DTD e il binding schema, è da una parte un problema ma dall'altra offre maggiori possibilità di personalizzazione nel codice generato. Infatti anche Castor permette di parametrizzare la generazione di codice mediante un file di configurazione.
Zeus è un tool ancora in beta ma presenta comunque un'architettura interessante; ad esempio il generatore di codice è pensato per supportare in futuro nuovi linguaggi per la definizione di documenti XML.
Una caratteristica di Castor non presente negli altri tool consiste nell'integrazione con i JDO: in questo modo si ottiene una gestione completa dei passaggi fra tabelle su database, oggetti Java e documenti XML.

Dagli esempi presentati emergono i vantaggi comuni a un qualunque tool di data binding:

  • semplicità nell'accesso ai dati;
  • non è necessario scrivere codice specifico per le operazioni di input/output sul documento XML;
  • il generatore di codice produce inoltre il codice di validazione in base ai constraint definiti nello schema.

La tecnologia di data binding permette quindi di semplificare notevolmente la vita del programmatore evitandogli di dover scrivere codice monotono di marshalling/unmarshalling per potersi concentrare sulla logica applicativa.

 

Esempi
Scarica i sorgenti descritti nell'articolo

 

Bibliografia
[1] Sam Brodkin, "Use XML data binding to do your laundry", JavaWorld, Gennaio 2002
[2] Castor, http://castor.exolab.org
[3] Zeus Enhydra, http://zeus.enhydra.org
[4] Brett McLaughlin - "Objects, objects everywhere", http://www-106.ibm.com/developerworks/library/data-binding1/?dwzone=xml, Luglio 2000
[5] Brett McLaughlin - "Make classes from XML data", http://www-106.ibm.com/developerworks/library/data-binding2/index.html?dwzone=xml, Agosto 2000
[6] Brett McLaughlin - "From text to byte code", http://www-106.ibm.com/developerworks/library/data-binding3/index.html?dwzone=xml, Settembre 2000
[7] Brett McLaughlin - "From bits to brackets", http://www-106.ibm.com/developerworks/library/data-binding4/index.html?dwzone=xml, Ottobre 2000
[8] Sun, "Java Architecture for XML Binding", http://java.sun.com/xml/jaxb
[9] I sorgenti discussi nell'articolo: src.zip

Andrea Giovannini si occupa di architetture per applicazioni enteprise Web-based con tecnologia J2EE, con particolare interesse per XML.

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it