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:
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.
|