MokaByte 68 - 9mbre 2002 
Validazione dei dati con Java e XML
di
Andrea Giovannini
Introduzione

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

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