Validazione
dei dati
La validazione dei dati è un aspetto fondamentale
delle applicazioni enteprise ma non solo: si pensi ad
esempio a un programma di scacchi che deve assicurarsi
che la mossa inserita dall'utente sia valida. Ritornando
alle applicazioni gestionali a tre livelli si possono
individuare altrettanti punti in cui eseguire validazioni:
-
client: vengono in genere realizzate in Javascript
sul browser e tipicamente si tratta di verifiche sull'obbligatorietà
dei dati inseriti o sul loro formato;
- server:
questo è il punto in cui si deve concentrare
tutta la logica applicativa e quindi anche tutte le
validazioni. Non è infatti ammissibile che
alcune validazioni vengano eseguite solo sul client:
in questo modo un client che non esegue quelle validazioni
potrebbe provocare un aggiornamento di dati inconsistente.
- DBMS:
trigger e stored procedure sono un ulteriore strumento
di validazione; si tratta però di un approccio
costoso e difficile da gestire.
Si
assumerà come punto fermo della discussione il
fatto che tutte le logiche di validazione siano implementate
sul server in Java. Il primo problema riguarda l'estendibilità;
si supponga che sorga la necessità di modificare
una regola di validazione o di inserirne una nuova:
in questi casi è necessario implementare queste
logiche in Java e ricompilare le classi corrispondenti.
Una soluzione migliore consiste nel codificare queste
nuove regole in un file esterno che viene letto e interpretato
a run-time; è a questo punto che entra in gioco
XML perchè offre un modo standard per scrivere
e interpretare dati.
Nelle prossime sezioni si presenterà la validazione
dei dati in XML: si discuteranno le principali tecniche
disponibili, DTD e Schema, e il modo in cui utilizzarle
in Java. Quindi si approfondirà il problema di
come estendere validazioni scritte in Java mediante
XML.
DTD
Un DTD (Document Type Definition) permette di definire
gli elementi utilizzabili in un particolare documento
XML e la loro struttura. Si consideri ad esempio il
seguente documento che presenta le informazioni di una
semplice lista di attività:
<?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>
Segue
ora il DTD di definizione del precedente documento:
<!ELEMENT
todolist (item*)>
<!ELEMENT item (number,priority,description,state)>
<!ELEMENT number (#PCDATA)>
<!ELEMENT priority (#PCDATA)>
<!ELEMENT description (#PCDATA)>
<!ELEMENT state (#PCDATA)>
La
prima riga indica che l'elemento todolist contiene vari
elementi item mentre il carattere * specifica la cardinalità
di questa relazione e indica che una todolist comprende
0 o più item. Gli altri possibili qualificatori
sono:
- il
carattere + per indicare una o più occorrenze
di un elemento;
- il
carattere ? per indicare un elemento opzionale;
- nessun
qualificatore per specificare una e una sola occorrenza
di quel dato elemento.
La
seconda riga del DTD definisce la struttura di un item
come contenente gli elementi number, priority, description
e state:
<!ELEMENT
item (number,priority,description,state)>
Infine
si specifica che i vari elementi contengono testo, chiamato
parsed character data (PCDATA) in questo contesto. Il
carattere # che precede PCDATA indica che si tratta
di una parola chiave e non del nome di un elemento.
Il
DTD viene referenziato dal documento con
<?xml
version="1.0"?>
<!DOCTYPE todolist SYSTEM "todolist.dtd">
<todolist>
...
</todolist>
In
alternativa potrebbe essere incluso direttamente nel
documento:
<?xml
version="1.0"?>
<!DOCTYPE todolist SYSTEM [
...
]>
<todolist>
...
</todolist>
Da
quanto visto emergono alcune limitazioni per i DTD come
ad esempio le seguenti:
- non
è possibile specificare per gli elementi un
tipo diverso da PCDATA;
- non
si possono definire enumerazioni;
- non
si possono definire range di validità;
Solo
questi problemi rendono improponibile specificare validazioni
complesse mediante DTD.
Schema
Gli schema XML sono uno standard W3C per la definizione
della struttura di un documento XML e i suoi vincoli
di validità. L'aspetto particolarmente interessante
degli schema è dato dal fatto che si tratta di
un linguaggio XML; ecco ad esempio lo schema per la
definizione del documento di todolist:
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="todolist">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="item" minOccurs="0"
maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element
name="item">
<xsd:complexType>
<xsd:all>
<xsd:element name="number" type="xsd:integer"/>
<xsd:element name="priority" type="xsd:integer"/>
<xsd:element name="description" type="xsd:string"/>
<xsd:element name="state" type="xsd:integer"/>
</xsd:all>
</xsd:complexType>
</xsd:element>
</xsd:schema>
Come
si può vedere lo schema permette di definire
molte più informazioni e vincoli rispetto ai
DTD:
- per
ogni elemento è possibile definire una cardinalità
minima e massima mediante gli attributi minOccurs
e maxOccurs;
- l'attributo
type permette di specificare il tipo di un elemento
in maniera dettagliata.
Per
maggiori informazioni relative all'utilizzo degli schema
si rimanda a [1].
Come
per i DTD è possibile specificare il riferimento
ad uno schema nel documento XML mediante un attributo
nell'elemento root del documento; la sintassi varia
a seconda che il documento usi o meno i namespace. Si
può usare xsi:schemaLocation
(dove XSI sta per XML Schema Instance) se il documento
usa i namespace, ad esempio
<todolist
xmlns="http://www.mytodolist.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="todolist.xsd">
...
</todolist>
Si
usa xsi:noNamespaceSchemaLocation
altrimenti, ad esempio
<todolist
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="todolist.xsd">
...
</todolist>
E'
interessante vedere come associare più schema
ad uno stesso documento. Si supponga ad esempio di voler
includere nel documento todolist informazioni relative
ad una specifica attività, come ad esempio quella
di scrivere articoli per MokaByte; queste ulteriori
informazioni saranno contenute in un altro schema e
referenziate dal documento nel modo seguente:
<todolist
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="todolist.xsd"
xsi:schemaLocation="http://www.mokabyte.it/ article.xsd">
...
</todolist>
Queste
prime sezioni hanno sviluppato il discorso della validazione
dei documenti XML mantenendolo però indipendente
dalla particolare tecnologia. Nel seguito si approfondirà
la validazione mediante parser Java e si vedrà
poi come utilizzare XML per applicare validazioni in
modo dinamico ad oggetti applicativi.
Validazione
con JAXP
Per poter utilizzare un parser validante con JAXP è
necessario configurare opportunamente il factory; il
seguente esempio mostra come ottenere un parser SAX
validante usando il metodo setValidating() di SAXParserFactory:
SAXParserFactory
factory = SAXParserFactory.newInstance();
factory.setValidating(true);
SAXParser parser = factory.newSAXParser();
DefaultHandler handler = new DummyHandler();
parser.parse(new FileInputStream(docURI), handler);
Il
DTD deve essere referenziato dal documento come mostrato
nelle sezioni precedenti. L'esempio utilizza come handler
SAX una classe che implementa solo i metodi di gestione
degli errori che saranno mostrati in seguito.
Si osservi che un DTD risulta utile anche usando un
parser SAX non validante. Un documento può contenere
spazi bianchi, ad esempio per indentare gli elementi
in un file di configurazione scritto a mano. Questi
spazi verranno inutilmente segnalati all'handler come
comune testo e passati al metodo characters(). Specificando
un DTD un parser Java invocherà il metodo ignorableWhiteSpace(),
anche se le specifiche SAX in generale non vincolano
i parser a questo comportamento.
Per
utilizzare gli schema è necessario specificare
altre informazioni: innanzi tutto, poiché gli
schema sono basati sull'utilizzo dei namespace, il parser
deve supportare i namespace e si deve inoltre specificare
che si stanno utilizzando gli schema e non i DTD. Queste
configurazioni vengono impostate property del parser,
come mostra il seguente esempio:
private
static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
private static final String JAXP_SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource";
private static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
.
. .
SAXParserFactory
factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
factory.setValidating(true);
SAXParser parser = factory.newSAXParser();
parser.setProperty(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
parser.setProperty(JAXP_SCHEMA_SOURCE, new File(schemaURI));
DefaultHandler handler = new DummyHandler();
parser.parse(new FileInputStream(docURI), handler);
Per
associare più schema al documento è sufficiente
definire un array contenente gli URI degli schema e
impostare con questo la property .../schemaSource:
String[]
shemas = {"todolist.xsd", "article.xsd"};
SAXParserFactory
factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
factory.setValidating(true);
SAXParser parser = factory.newSAXParser();
parser.setProperty("
http://java.sun.com/xml/jaxp/properties/schemaLanguage",
"http://www.w3.org/2001/XMLSchema");
parser.setProperty("
http://java.sun.com/xml/jaxp/properties/schemaSource",
shemas);
...
Quanto
visto in precedenza si applica immediatamente anche
a un parser DOM. Le property in precedenza impostate
sul parser SAX ora sono attributi del factory e si deve
comunque impostare un ErrorHandler per intercettare
gli errori:
DocumentBuilderFactory
factory = DocumentBuilderFactory.newInstance();
factory.setValidating(true);
DocumentBuilder parser = factory.newDocumentBuilder();
parser.setErrorHandler(new DummyHandler());
Document doc = parser.parse(new FileInputStream(docURI));
Se
non si imposta un handler per gli errori allora il parser
utilizza un handler di default e a fronte di un errore
si ottiene il seguente output:
Warning:
validation was turned on but an org.xml.sax.ErrorHandler
was not set, which is probably not what is desired.
Parser will use a default ErrorHandler to print the
first 10 errors. Please call the 'setErrorHandler' method
to fix this.
...
Errori
di validazione
Per poter intercettare gli errori di validazione con
un parser SAX si deve utilizzare un opportuno handler,
org.xml.sax.ErrorHandler, che definisce i seguenti metodi:
public
void warning(SAXParseException e) throws SAXException;
public void error(SAXParseException e) throws SAXException;
public void fatalError(SAXParseException e) throws SAXException;
Utilizzando
un parser validante viene rilevato un error() se il
documento non è valido mentre si solleva un fatalError()
per un errore non recuperabile, come nel caso del parsing
di un documento non ben formato.
Tutti i metodi rendono disponibile una SAXParseException
che permette di recuperare informazioni relative all'errore
verificatosi; più precisamente i metodi getColumnNumber()
e getLineNumber() permettono di risalire alla posizione
del testo che ha causato l'errore; invece i metodi getPublicId()
e getSystemId() restituiscono gli identificatori dell'entità
nella quale si è verificato l'errore.
SAXParseException
estende inoltre SAXException ed eredita da questa eccezione
la possibilità di contenere altre eccezioni.
La
classe DummyHandler inclusa negli esempi implementa
per semplicità solo i metodi error() e warning():
public
void error(SAXParseException e) throws SAXParseException
{
System.out.println("Errore di validazione:
linea " + e.getLineNumber());
System.out.println(e.getMessage());
}
public
void warning(SAXParseException e) throws SAXParseException
{
System.out.println("Warning: linea
" + e.getLineNumber());
System.out.println(e.getMessage());
}
La
classe ValidationTest, anch'essa inclusa negli esempi,
permette di eseguire il parsing del documento specificato
usando un DTD o uno schema per la validazione. Modificando
il documento XML di esempio sostituendo all'elemento
item un imprecisato elemento numbero
<?xml
version="1.0"?>
<!DOCTYPE todolist SYSTEM "file:/C:\Documenti\Articoli\In
Lavorazione\validazione\todolist.dtd">
<todolist>
<item>
<numbero>1</numbero>
<priority>6</priority>
. . .
si
ottiene il seguente output
c:>\java
ValidationTest sax-dtd todolist_dtd.xml
Errore di validazione: linea 5
Element type "numbero" must be declared.
Errore di validazione: linea 9
The content of element type "item" must match
"(number,priority,description,state)".
Utilizzando
lo schema si ottiene invece
...
Errore di validazione: linea 4
cvc-complex-type.2.4.a: Invalid content starting with
element 'numbero'.
The content must match 'all(("":number),("":priority),("":description),("":state))'.
Un
meccanismo di validazione estendibile
Nelle sezioni precedenti sono state gettate le basi
per poter gestire la validazione di documenti XML in
Java. A questo punto si vuole tornare al problema originale,
ovvero definire delle regole di validazione per oggetti
applicativi Java in maniera dinamica. Si presenterà
ora un semplice esempio basato sull'utilizzo degli schema.
Sono stati definiti i seguenti componenti:
-
un oggetto applicativo Item definisce un metodo validate()
che contiene alcune validazioni Java:
public
void validate() throws ValidationException {
if (number < 0) {
throw new ValidationException("L'attributo
number
non
può essere negativo");
}
if (priority < 0) {
throw new ValidationException("L'attributo
priority non può essere
negativo");
}
if (state < 0) {
throw new ValidationException("L'attributo
state
non può essere negativo");
}
// Validazioni estese mediante schema XML
Validator validator = new Validator();
validator.validate(this, "todolist.xsd");
}
-
l'interfaccia BusinessObject, implementata da Item,
definisce un oggetto applicativo minimale con un metodo
validate() e un metodo toXML(), utilizzato per ottenere
il documento XML a cui applicare lo schema:
package
com.mokabyte.javaxml.validation;
public interface BusinessObject {
public String toXML();
public void validate() throws ValidationException;
}
- l'oggetto
Validator incapsula le validazioni da eseguirsi mediante
schema e definisce a questo proposito un metodo validate()
al quale vengono passati l'oggetto e lo schema corrispondente:
package
com.mokabyte.javaxml.validation;
import java.io.*;
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
public class Validator {
private static final String JAXP_SCHEMA_LANGUAGE
= "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
private static final String JAXP_SCHEMA_SOURCE =
"http://java.sun.com/xml/jaxp/properties/schemaSource";
private static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
public void validate(BusinessObject bo, String schemaURI)
throws ValidationException {
String xmlDoc = bo.toXML();
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
factory.setValidating(true);
SAXParser parser = factory.newSAXParser();
parser.setProperty(JAXP_SCHEMA_LANGUAGE,
W3C_XML_SCHEMA);
parser.setProperty(JAXP_SCHEMA_SOURCE,
new File(schemaURI));
DefaultHandler handler = new DummyHandler();
parser.parse(new InputSource(new StringReader(xmlDoc)),
handler);
} catch(ParserConfigurationException e) {
System.err.println("Errore
di configurazione del parser");
throw new RuntimeException(e);
} catch(IOException e) {
System.err.println("Errore
di I/O");
throw new RuntimeException(e);
} catch(SAXParseException e) {
throw new ValidationException(e.getMessage());
} catch(SAXException e) {
System.err.println("Errore
durante il parsing");
throw new RuntimeException(e);
}
}
}
Il
seguente codice innesca il meccanismo di validazione:
Item
item = new Item();
item.setNumber(5);
item.setPriority(2);
item.setDescription("Scrivere articolo su validazione
dei dati");
item.setState(4);
try {
item.validate();
} catch(ValidationException e) {
System.err.println("Errore di validazione:
" + e.getMessage());
}
L'architettura
presentata, anche se semplice, permette di cogliere
i vantaggi dell'utilizzo di XML/schema per estendere
le validazioni su oggetti Java. Ovviamente esistono
diversi approcci: ad esempio in [2] si propone l'utilizzo
di un modello a oggetti che incorpori le regole specificate
in uno schema mentre in [3] si presenta un motore di
validazione basato su un linguaggio XML specifico per
la particolare applicazione in esame.
Conclusioni
Questo articolo ha innanzi tutto mostrato gli strumenti
fondamentali per gestire la validazione dei documenti
XML: conoscenza di DTD e schema e delle API di parsing
JAXP. Quindi si è visto come è possibile
definire regole di validazione in modo dinamico mediante
gli schema XML.
Un approccio completo prevede l'utilizzo di strumenti
di business process automation come un motore di regole
applicative (business rule engine). Sono stati sviluppati
diversi linguaggi XML per la definizione di questo tipo
di regole ed è stato proposto uno standard, SRML
(Simple Rule Markup Language). In [4,5] si possono trovare
validi punti di partenza su questo argomento.
Bibliografia
[1] W3C - XML Schema, http://www.w3.org/XML/schema.html
[2] Brett McLaughlin - "Validation with Java and
XML schema, Part 1", http://www.javaworld.com/javaworld/jw-09-2000/jw-0908-validation_p.html
[3] Jing Xue, Daniel Cox - "Java and XML: Learn
a Better Approach To Data Validation", http://www.devx.com/java/free/articles/schwartz0402/schwartz0402-1.asp
[4] Ahmed Abulsorour, Siva Visveswaran - "Business
process automation made easy with Java, Part 1",
http://www.javaworld.com/javaworld/jw-09-2002/jw-0906-process_p.html
[5] Ahmed Abulsorour, Siva Visveswaran - "Business
process automation made easy with Java, Part 2",
http://www.javaworld.com/javaworld/jw-10-2002/jw-1018-process2_p.html
Andrea
Giovannini si occupa di architetture per applicazioni
enterprise Web-based con tecnologia J2EE, con particolare
interesse per XML.
Risorse
Scarica
qui i sorgenti presentati
nell'articolo
|