Introduzione
Quando all'interno di una applicazione è necessario
gestire la persistenza dei dati, la miglior soluzione
possibile consiste nel ricorrere ad un database e, grazie
a JDBC, Java ne facilita l'utilizzo, per non parlare
di librerie esterne che ne rendono la gestione ancora
più semplice.
Vi sono, però, situazioni nelle quali non si
vuole o non si può utilizzare un database, pur
necessitando di gestire la persistenza di alcuni dati,
normalmente attraverso l'utilizzo di soluzioni che si
basano sul filesystem. Sebbene la più diffusa
ed utilizzata si basa sulla serializzazione di oggetti,
attraverso l'interfaccia java.io.Serializable, col trascorrere
del tempo sta sempre più affermandosi la soluzione
XML, il cui grosso vantaggio consiste anche nel permettere
di intervenire manualmente sui dati.
Mapping
bidirezionale
Nel precedente articolo di questa serie abbiamo visto
come il componente Digester potesse essere utilizzato
per effettuare un mapping tra un documento XML ed un
JavaBean e degli esempi a supporto dimostrava come questa
operazione potesse essere fatta in maniera piuttosto
semplice e veloce. Ma, nonostante questo, la soluzione
Digester presenta il problema della monodirezionalità,
ossia un documento XML può essere "mappato"
ad un JavaBean ma non viceversa.
Per ottenere una soluzione bidirezionale è necessario
utilizzare un altro componente del progetto Jakarta
Commons, Betwixt, che, come recita la descrizione sulla
sua homepage, ha lo scopo di trasformare un JavaBean
in un documento XML, utilizzando un meccanismo di introspezione
simile a quello proposto nelle specifiche di Sun sui
JavaBean. In più, per poter completare la bidirezionalità,
Betwixt prepara anche le regole di Digester per poter
poi effettuare la lettura del documento XML all'interno
del bean.
Figura 1 - La homepage del componente Betwixt
Betwixt
è un componente ancora giovane e, ad oggi, l'unica
versione disponibile per il download pubblico è
la Alpha 1. Per poter utilizzare questo componente è
necessario, prima di tutto, scaricare il pacchetto contenente
i file binari oppure i sorgenti, nel caso in cui vi
vogliate dilettare nella compilazione. In entrambi i
casi è anche necessario ottenere le librerie
di supporto indispensabili per il componente che, attualmente,
non sono inserite all'interno dei pacchetti e che sono:
Digester (disponibile attraverso la sua homepage http://jakarta.apache.org/commons/digester/index.html),
BeanUtils (http://jakarta.apache.org/commons/beanutils.html),
Collections (http://jakarta.apache.org/commons/collections.html)
e Logging (http://jakarta.apache.org/commons/logging.html).
Tutti i vari jar di questi componenti andranno poi inseriti
nel classpath, insieme a quello di Betwixt.
Da
JavaBean a XML
Il primo esempio è piuttosto elementare. Si parte
da un JavaBean molto semplice tipo questo:
import
java.io.*;
import java.beans.*;
import org.xml.sax.*;
import org.apache.commons.betwixt.io.*;
public
class SimpleBean {
/*
Attributi semplici del bean
*/
private long codice;
private String descrizione;
private int valore;
/*
Costruttori
*/
public SimpleBean() {
// nessuna inizializzazione
}
public SimpleBean(long codice, String descrizione, int
valore) {
this();
setCodice(codice);
setDescrizione(descrizione);
setValore(valore);
}
/*
Getters & Setters dei vari attributi
*/
public void setCodice(long arg) {
codice = arg;
}
public long getCodice() {
return codice;
}
public void setDescrizione(String arg) {
descrizione = arg;
}
public String getDescrizione() {
return descrizione;
}
public void setValore(int arg) {
valore = arg;
}
public int getValore() {
return valore;
}
/*
Implementazione del metodo toString
*/
public String toString() {
StringBuffer retBuff = new StringBuffer("[");
retBuff.append("Codice=").append(getCodice()).append(",");
retBuff.append("Descrizione=").append(getDescrizione()).append(",");
retBuff.append("Valore=").append(getValore());
return retBuff.append("]").toString();
}
}
Sono
molte le soluzioni per poter scrivere un metodo di trasformazione
in XML del nostro Bean. In questo caso utilizzeremo
un metodo simile a toString(), che chiameremo toXMLString(),
che ci ritornerà un oggetto di tipo StringBuffer
contenente la versione XML dell'oggetto.
Ecco come ottenere questo risultato attraverso Betwixt:
public
StringBuffer toXMLString() {
// creazione string writer di output
StringWriter outWriter = new StringWriter();
// crea e configura il bean writer
BeanWriter beanWriter = new BeanWriter(outWriter);
beanWriter.getXMLIntrospector().setAttributesForPrimitives(false);
beanWriter.setWriteIDs(false);
beanWriter.enablePrettyPrint();
Per
inizializzare la classe che si occuperà di scrivere
l'XML, è necessario avere a disposizione un OutputStream
o un Writer. In questo caso, volendo fornire uno StringBuffer,
utilizzeremo uno StringWriter.
Una volta creato il nostro scrittore, potremo definire
le proprietà in base a come vogliamo ottenere
l'output del nostro documento. Nel nostro esempio abbiamo
scelto di scrivere tutti i membri del bean come tag
e non come attributi, non vogliamo far scrivere l'ID
della classe e soprattutto, vogliamo che il documento
finale abbia un aspetto leggibile (indentato).
Figura 2 - Betwixt "Getting Started"
Definite
le varie proprietà, possiamo quindi passare alla
generazione del documento, attraverso il metodo write()
al quale forniremo il nome dell'elemento root. Questo
metodo genera delle eccezioni che, in questo caso, non
vengono realmente gestite. Il consiglio, ovviamente,
è di non ignorare mai le eccezioni e di gestirle
sempre nel modo più consono.
//
scrivi il bean sotto l'elemento root
try {
beanWriter.write("simpleBean", this);
} catch (IOException ioE) {
ioE.printStackTrace();
} catch (SAXException saxE) {
saxE.printStackTrace();
} catch (IntrospectionException iE) {
iE.printStackTrace();
}
// return the String
return outWriter.getBuffer();
}
Una
volta completata la scrittura, il nostro metodo ritornerà
la versione StringBuffer dello StringWriter creato.
Per
completare l'esempio possiamo scrivere un metodo main
che potremo utilizzare per testare il nostro oggetto
e, soprattutto, per vedere il risultato della nostro
metodo di generazione XML.
public
static final void main(String[] args) {
// Creazione di un semplice bean e stampa del suo contenuto
SimpleBean bean = new SimpleBean(0L, "ZERO",
1);
System.out.println(bean.toString());
// Scrittura del documento
FileWriter writer = null;
try {
// ottieni la versione XML del bean ed aggiungi la
// linea di definizione
StringBuffer xmlBuffer = bean.toXMLString();
xmlBuffer.insert(0, "<?xml version='1.0' ?>");
// crea e scrivi il file di output
writer = new FileWriter("test.xml");
writer.write(xmlBuffer.toString());
}
catch (IOException ioE) {
ioE.printStackTrace();
}
finally {
try {
writer.close();
}
catch (Throwable t) {
/* ignora */
}
}
}
Se
non vi saranno errori inaspettati, questo codice dovrebbe
creare un documento chiamato test.xml, contenente la
versione XML del bean:
<?xml
version="1.0"?>
<simpleBean>
<codice>0</codice>
<descrizione>ZERO</descrizione>
<valore>1</valore>
</simpleBean>
E'
possibile cambiare il risultato semplicemente modificando
la definizione delle proprietà dello scrittore.
Ad esempio facendo scrivere i membri del bean come attributi
e non come tag:
beanWriter.getXMLIntrospector().setAttributesForPrimitives(true);
In
questo caso il file test.xml generato dal nostro esempio,
sarà strutturato in questo modo:
<?xml
version="1.0"?>
<simpleBean codice="0" descrizione="ZERO"
valore="1" />
Da XML a JavaBean
L'operazione opposta, ossia la creazione del JavaBean
partendo dal documento XML viene garantita da Betwixt
grazie all'uso di Digester. Betwixt, infatti, ne genera
tutte le regole necessarie in modo da poter leggere
il documento XML ed ottenere l'oggetto richiesto.
Per provare questa operazione, possiamo utilizzare un
approccio molto elementare, scrivendo un semplice programmino
contenente il solo metodo main.
import
java.io.*;
import java.beans.*;
import org.apache.commons.betwixt.io.*;
import org.xml.sax.*;
public
class SimpleBeanReadAndWrite {
public static final void main(String[] args) {
Per
semplificare tutto possiamo utilizzare il JavaBean costruito
in precedenza. Una volta inizializzato con dei valori
qualsiasi, possiamo ottenerne la versione XML attraverso
il metodo toXMLString() e la creazione di uno StringReader.
//
crea il bean
SimpleBean bean = new SimpleBean(1L, "ALEX",
100);
// ottieni la sua versione XML
StringBuffer xmlBean = bean.toXMLString();
xmlBean.insert(0, "<?xml version='1.0' ?>");
// crea un reader
StringReader xmlReader = new StringReader(xmlBean.toString());
Ottenuto
un reader contenente l'oggetto in formato XML, è
necessario istanziare un BeanReader, tramite il quale
Betwixt potrà generare il JavaBean. Così
come per lo scrittore, anche il lettore ha la necessità
di essere configurato attraverso la definizione di alcuni
parametri. E' ovvio che i parametri devono essere impostati
nello stesso modo, altrimenti la lettura non avverrebbe
in modo corretto.
I questo esempio, quindi, possiamo definire che gli
attributi del bean sono tag e che non intendiamo utilizzare
l'ID della classe.
//
crea e configura il Bean Reader
BeanReader beanReader = new BeanReader();
beanReader.getXMLIntrospector().setAttributesForPrimitives(false);
beanReader.setMatchIDs(false);
Prima
di poter leggere il JavaBean è necessario registrare
il tipo di oggetto che il nostro BeanReader dovrà
leggere, fornendogli il nome del tag principale del
nostro documento XML e la classe alla quale questo bean
appartiene.
Fatto questo potremo effettuare la lettura, attraverso
il metodo parse(), che l'oggetto BeanReader eredita
proprio dall'oggetto Digester. Entrambi i metodi (la
registrazione ed il parsing) generano delle eccezioni
ed è quindi importante ricordare che in questo
esempio queste non sono gestite in modo serio, ma durante
lo sviluppo di applicazioni serie, le gestione delle
eccezioni è fondamentale.
//
Registra e leggi il bean
try {
beanReader.registerBeanClass("simpleBean",
SimpleBean.class);
bean = (SimpleBean)beanReader.parse(xmlReader);
} catch (IOException ioE) {
ioE.printStackTrace();
} catch (SAXException saxE) {
saxE.printStackTrace();
} catch (IntrospectionException iE) {
iE.printStackTrace();
}
// se il bean non e' nullo, "stampalo" a video
if (bean != null) {
System.out.println(bean.toString());
}
}
}
Per
concludere il programma e verificare che la lettura
sia andata a buon fine, facciamo scrivere a video il
bean appena letto, utilizzando il suo toString().
Nel
mondo reale è comunque piuttosto difficile che
il documento XML da cui generare un JavaBean sia generato
all'interno dello stesso metodo, come abbiamo visto
in questo esempio. Probabilmente il documento di partenza
è presente su file, oppure proviene da una fonte
esterna come un database oppure la rete internet.
Se vogliamo utilizzare il documento XML che abbiamo
generato in precedenza, possiamo modificare il metodo
main in qualcosa di simile:
public
static final void main(String[] args) {
SimpleBean bean = null;
// Registra e leggi il bean
try {
beanReader.registerBeanClass("simpleBean",
SimpleBean.class);
bean = (SimpleBean)beanReader.parse(new FileReader("test.xml"));
} catch (IOException ioE) {
ioE.printStackTrace();
} catch (SAXException saxE) {
saxE.printStackTrace();
} catch (IntrospectionException iE) {
iE.printStackTrace();
}
// se il bean non e' nullo, "stampalo" a video
if (bean != null) {
System.out.println(bean.toString());
}
}
Non solo primitive
Il bean che è stato utilizzato negli esempi precedenti
è composto solo da primitive o, comunque, oggetti
molto semplici (in questo caso String). Betwixt è,
ovviamente, in grado di gestire allo stesso modo anche
i JavaBean i cui attributi possono essere più
complessi, come altri oggetti, array o anche collezioni.
La logica è ovviamente la stessa e l'unica cosa
che cambia, nel caso in cui si usino collezioni o arrays,
è la presenza di un metodo il cui scopo è
quello di aggiungere un nuovo elemento all'array o alla
collezione, che verrà utilizzato dalla parte
di lettura di Betwixt. Questo metodo dovrà chiamarsi
"add", seguito dal nome dell'attributo.
Anche
in questo caso, ecco un esempio per vedere come Betwix
si comporta con JavaBean più complessi:
import
java.io.*;
import java.util.*;
import java.beans.*;
import org.apache.commons.betwixt.io.*;
import org.xml.sax.*;
public
class ComposedBean {
/*
Attributi del bean
*/
int codice;
SimpleBean bean;
List datiList;
int[] datiArray;
/*
Costruttori
*/
public ComposedBean() {
datiList = new ArrayList();
datiArray = new int[0];
}
public ComposedBean(int codice, SimpleBean bean) {
this();
setCodice(codice);
setBean(bean);
}
/*
Getters & Setters dei vari attributi
*/
public void setCodice(int arg) {
codice = arg;
}
public int getCodice() {
return codice;
}
public void setBean(SimpleBean arg) {
bean = arg;
}
public SimpleBean getBean() {
return bean;
}
public void setDatiList(List arg) {
datiList = arg;
}
public List getDatiList() {
return datiList;
}
public void addDatiList(int arg) {
datiList.add(new Integer(arg));
}
public void setDatiArray(int[] arg) {
datiArray = arg;
}
public int[] getDatiArray() {
return datiArray;
}
public void addDatiArray(int arg) {
int[] newArray = new int[datiArray.length + 1];
for (int i=0; i<datiArray.length; i++) {
newArray[i] = datiArray[i];
}
newArray[datiArray.length] = arg;
datiArray = newArray;
}
/*
Implementazione metodo toString()
*/
public String toString() {
StringBuffer retBuff = new StringBuffer("[");
retBuff.append("Codice=").append(getCodice()).append(",");
retBuff.append("Bean=").append(getBean()).append(",");
retBuff.append("DatiList=");
for (int i=0; i<datiList.size(); i++) {
retBuff.append(datiList.get(i));
if (i < (datiList.size()-1)) {
retBuff.append(",");
}
}
retBuff.append(",");
retBuff.append("DatiArray=");
for (int i=0; i<datiArray.length; i++) {
retBuff.append(datiArray[i]);
if (i < (datiArray.length-1)) {
retBuff.append(",");
}
}
return retBuff.append("]").toString();
}
/*
Implementazione metodo toXMLString()
*/
public StringBuffer toXMLString() {
// creazione string writer di output
StringWriter outWriter = new StringWriter();
// crea e configura il bean writer
BeanWriter beanWriter = new BeanWriter(outWriter);
beanWriter.getXMLIntrospector().setAttributesForPrimitives(false);
beanWriter.setWriteIDs(false);
beanWriter.enablePrettyPrint();
// scrivi il bean sotto l'elemento root
try {
beanWriter.write("composedBean", this);
} catch (IOException ioE) {
ioE.printStackTrace();
} catch (SAXException saxE) {
saxE.printStackTrace();
} catch (IntrospectionException iE) {
iE.printStackTrace();
}
// return the String
return outWriter.getBuffer();
}
/*
Metodo main di test
*/
public static final void main(String[] args) {
// creazione di un SimpleBean e di un ComposedBean
// che lo contenga
SimpleBean simpleBean = new SimpleBean(1L, "SIMPLE",
1);
ComposedBean bean = new ComposedBean(1, simpleBean);
// inserimento alcuni valori nella lista e nell'array
bean.addDatiList(0);
bean.addDatiList(1);
bean.addDatiList(2);
bean.addDatiArray(10);
bean.addDatiArray(11);
bean.addDatiArray(12);
// Generazione file XML su disco
FileWriter writer = null;
try {
writer = new FileWriter("./composed.xml");
writer.write(xmlBuffer.toString());
}
catch (IOException ioE) {
ioE.printStackTrace();
}
finally {
try { writer.close(); } catch (Exception e) { /* ok
*/ }
}
}
}
E'
possibile notare come il codice di questa classe sia
sensibilmente simile a quello della sua versione semplice.
Infatti, a parte i due metodi addDatiArray() e addDatiList(),
non vi sono differenze sostanziali, nemmeno sul metodo
toXMLString().
Nemmeno
la lettura del documento XML subisce modifiche. L'unica
modifica che potremmo apportare nel codice main del
programma che abbiamo visto in precedenza per la lettura
del bean semplice, riguarderebbe solo poche linee:
public
static final void main(String[] args) {
ComposedBean bean = null;
// Registra e leggi il bean
try {
beanReader.registerBeanClass("composedBean",
ComposedBean.class);
bean = (SimpleBean)beanReader.parse(new FileReader("./composed.xml"));
} catch (IOException ioE) {
ioE.printStackTrace();
} catch (SAXException saxE) {
saxE.printStackTrace();
} catch (IntrospectionException iE) {
iE.printStackTrace();
}
// se il bean non e' nullo, "stampalo" a video
if (bean != null) {
System.out.println(bean.toString());
}
}
Conclusioni
Betwixt non è l'unica soluzione per il mapping
tra XML e JavaBean e probabilmente in rete è
possibile trovare altre librerie sia open source che
commerciali, anche in stati di sviluppo più avanzati,
se non già ufficialmente rilasciate, contrariamente
a Betwixt che, come già detto in precedenza,
è un componente ancora giovane.
Figura 3 - La "ToDo page" del progetto.
C'è ancora un po' di strada da fare...
Il
suo sviluppo, infatti, procede piuttosto lentamente,
considerato che, ad oggi, l'unica versione pubblica
disponibile sul sito (http://jakarta.apache.org/commons/betwixt/index.html)
è la Alpha 1, di fine gennaio 2003. A difesa,
però, bisogna sottolineare che in fondo i test
e le prove effettuate sulla librerie hanno dato esiti
positivi e le stesse prestazioni si sono rivelate adeguate.
Nonostante questo, credo che il componente sia ancora
troppo giovane per essere utilizzato in ambienti di
produzione, almeno sino a quando il gruppo di lavoro
che si occupa del suo sviluppo ne rilasci una versione
più stabile. Ma per prototyping o per lo sviluppo
di piccole applicazioni non critiche Betwixt può
essere tranquillamente preso in considerazione.
|