3. Introduzione all'XML

3.1. XML

Supponiamo che nel nostro scenario di riferimento il cliente voglia comunicare i dati di un'ordine al rivenditore. Supponiamo per semplicità che il rivenditore abbia bisogno solo dei modelli dei prodotti scelti, delle quantità e del corriere preferito per gestire un'ordine. I dati da inviare potrebbero essere questi:

DHL
Playstation    1
Controller     2
      

I dati presentati in questo modo sono però liberi di essere interpretati poichè privi di valore semantico. Abbiamo bisogno di un linguaggio che ci permetta specificare che la prima riga indica il corriere scelto per la spedizione, mentre le linee successive indicano il codice di un prodotto e la quantità richiesta. Per gestire questa problematica su Internet, il W3C ha progettato ad-hoc il linguaggio XML (eXtensible Markup Language), un meta-linguaggio di markup per la strutturazione dei dati.

La scelta di usare XML per la strutturazione dei dati è dettata da alcune sue importanti qualità

  • Auto descrittivo:

    non richiede file esterni che ne definiscano la semantica perchè quest'informazione è già contenuta al suo interno
  • Semplice:

    ha una sintassi caratterizzata da poche e semplici regole.
  • Estensibile:

    gli elementi già sintatticamente definiti possono essere estesi e adattati ad altri utilizzi.
  • Interoperabile:

    supportato da una grande varietà di framework e linguaggi di programmazione, garantisce un livello di interoperabilità indispensabile per l'implementazione di servizi web

3.1.1. Sintassi XML

La sintassi dell'XML è piuttosto semplice e definita da poche, ma rigide, regole. Vediamole brevemente:

È buona norma cominciare il documento XML specificando la versione e la codifica usata:

<?xml version="1.0" encoding="ISO-8859-1"?>

Tutti gli elementi devono avere il tag di chiusura.

<ordine> ... </ordine>
<ordine />

I tag sono case sensitive.

<ordine> sbagliato </Ordine>

I tag devono essere annidati in maniera corretta.

<ordine><articolo> sbagliato </ordine></articolo>
<ordine><articolo> corretto </articolo></ordine>

Un documento XML deve avere un elemento radice

<ordine>
    <articolo> 
        .. 
    </articolo>
</ordine>

I valori degli attributi devono essere tra apici

<ordine corriere = DHL> sbagliato </ordine>
<ordine corriere = "DHL"> corretto </ordine>

Il carattere minore (<) e l'ampersand (&) sono vietati all'interno degli elementi e devono essere codificati. E' consigliabile farlo anche con altri caratteri speciali anche se non strettamente necessario:

&lt; <
&gt; >
&amp; &
&apos; '
&quot; "

Tabella 3. Codifica di caratteri speciali


Si possono inserire commenti all'interno di un documento XML

<!-- commento -->

Quando parsiamo un documento XML, anche il contenuto degli elementi viene parsato alla ricerca di nodi interni. Se non vogliamo che una porzione di XML sia processata la definiamo CDATA (Character DATA) in questo modo:

<![CDATA[ .... ]]>

3.1.2. Strutturare i dati

Ora che conosciamo le regole sintattiche dell'XML, vediamo come possiamo strutturare i dati del nostro ordine:

<nome>Playstation</nome>
<quantita>1</quantita>
<nome>Controller</nome>
<quantita>2</quantita>
            

adesso abbiamo dato un significato a quei dati, ma sono ancora ambigui. Possiamo annidare gli elementi e strutturare ancora meglio le informazioni. Possiamo specificare che un codice ed una quantità sono per un articolo e che tanti articoli fanno un'ordine:

<ordine>
    <articolo>
        <nome>Playstation</nome>
        <quantita>1</quantita>
    </articolo>
    <articolo>
        <nome>Controller</nome>
        <quantita>2</quantita>
    </articolo>
</ordine>
                

Possiamo anche inserire degli attributi ad un elemento

<ordine corriere="DHL">
    <articolo>
        <nome>Playstation</nome>
        <quantita>1</quantita>
    </articolo>
    <articolo>
        <nome>Controller</nome>
        <quantita>2</quantita>
    </articolo>
</ordine>
                

3.1.3. Namespace

In XML i nomi degli elementi sono definiti dallo sviluppatore. Questo può causare dei conflitti, ad esempio quando si lavora su documenti XML di provenienze diverse. Vediamo un esempio pratico: supponiamo che nell'ordine inseriamo anche il nome del produttore e del prodotto. Potrebbe verificarsi una situazione di questo tipo:

<ordine>
    <nome>Nintendo</nome>
    ...
    <nome>Wii</nome>
    ...
</ordine>
                

Come fare a distinguerli? Per questo si definiscono due namespace e, grazie al prefisso, si risale al loro significato semantico

<ordine xmlns:ns1="http://www.shop.org/manufacturers/ns"
        xmlns:ns2="http://www.shop.org/product/ns">
    <ns1:nome>Nintendo</ns1:nome>
    ...
    <ns2:nome>Wii</ns2:nome>
    ...
</ordine>
                

Se non viene specificato un prefisso di un namespace, questo viene preso come default e assegnato a tutti i nodi discendenti privi di namespace. La definizione di un namespace è visibile nel nodo in cui viene inserita e in tutti i suoi discendenti.

3.1.4. XML in Java, DOM

Abbiamo scritto lo schema di un'ordine. Vediamo come modificare il client HTTP affichè lo invii alla servlet.

//Creo il documento XML dell'ordine
String xml = "<ordine corriere="DHL">"+
             "<articolo>" +
             "<nome>Playstation</nome>" +
             "<quantita>1</quantita>" +
             "</articolo>" +
             "<articolo>" +
             "<nome>Controller</nome>" + 
             "<quantita>2</quantita>" +
             "</articolo>" +
             "</ordine>" +
             "</ordine>";
//Creo il client per effettuare la POST dell'XML
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(servletUrl); 

//Creo l'entita' da inviare settandone il giusto ContentType
StringEntity xmlEntity = new StringEntity(xml);
xmlEntity.setContentType("text/xml");
httppost.setEntity(xmlEntity);

System.out.println("executing request " + httppost.getRequestLine());
HttpResponse response = httpclient.execute(httppost);

System.out.println("----------------------------------------");
System.out.println(response.getStatusLine());
            

3.1.5. Java API for XML Processing

La Java API for XML Processing (JAXP) è una libreria standard processare codice XML. L'attuale versione è la 1.4 inclusa in Java SE 6.0 ed una sua implementazione è scaricabile all'indirizzo jaxp.dev.java.net.

Sono forniti più modi per effettuare il parsing di un documento XML. Un parser è un programma che effettua la lettura di un documento XML e lo divide in blocchi discreti. I due classici approcci per processare i documenti XML sono:

  • Simple API for XML Processing (SAX)

  • Document Object Model (DOM)

SAX è un'API di basso livello il cui principale punto di forza è l'efficienza. Quando un documento viene parsato usando SAX, una serie di eventi vengono generati e passati all'applicazione tramite l'utilizzo di callback handlers che implementano l'handler delle API SAX. Gli eventi generati sono di livello molto basso e devono essere gestiti dallo sviluppatore che, inoltre, deve mantenere le informazioni necessarie durante il processo di parsing. Oltre ad un utilizzo piuttosto complicato, SAX soffre di due limitazioni di rilievo: non può modificare il documento che sta elaborando e può procedere alla lettura solo "in avanti": non può tornare indietro. Quindi, quello che è stato letto è perso e non è possibile recuperarlo.

DOM, invece, ha come punto di forza la semplicità d'utilizzo. Una volta ricevuto il documento, il parser si occupa di costruire un albero di oggetti che rappresentano il contenuto e l'organizzazione dei dati contenuti. In questo caso l'albero esiste in memoria e l'applicazione può attraversarlo e modificarlo in ogni suo punto. Ovviamente il prezzo da pagare è il costo di computazione iniziale per la costruzione dell'albero ed il costo di memoria.

A questi rappresentanti delle due principali tecniche di rappresentazione dei dati XML si aggiungono altri due parser di recente concezione:

  • Streaming API for XML (StAX)

  • Transformation API for XML (TrAX)

StAX è un pull parser. A differenza di SAX, che è un push parser, non riceve passivamente i segnali inviati all'handler per elaborarli, ma è l'utente a controllare il flusso degli eventi. Questo significa che il client richiede (pull) i dati XML quando ne ha bisogno e nel momento in cui può gestirli, a differenza del modello push, dove è il parser a inviare i dati non appena li ha disponibili a prescindere che l'utente ne abbia bisogno o sia in grado di elaborarli. Le librerie pull parsing sono molto può semplici delle push parsing e questo permette di semplificare il lavoro dei programmatori, anche per documenti molto complessi. Inoltre è bidirezionale, nel senso che oltre a leggere dati XML è anche in grado di produrli. Rimane il limite di poter procedere solo "in avanti" nell'elaborazione del documento XML.

TrAX, con l'utilizzo di un XSLT stylesheet, permette di trasformare l'XML. Inoltre, poiché i dati vengono solitamente manipolati con SAX o DOM, questo parser può ricevere in ingresso sia eventi SAX che documenti DOM. Può anche essere usato per convertire i dati da un formato all'altro, infatti, è possibile prendere un documento DOM, trasformarlo e scriverlo su file, oppure prendere l'XML da file, trasformarlo e restituirlo in forma di documento DOM.

La tabella seguente riassume brevemente le caratteristiche principali dei parser presentati:

Feature StAX SAX DOM TrAX
Tipo di API Pull, streaming Push, streaming In memory tree XSLT Rule
Facilità d'uso Alta Media Alta Media
Efficenza CPU e Memoria Buona Buona Dipende Dipende
Solo "in avanti" Si Si No No
Legge XML Si Si Si Si
Scrive XML Si No Si Si
CRUD (Create Read Update Delete) No No Si Si

Tabella 4. Parser


Supponiamo di avere un documento XML contenente le informazioni di una biblioteca. Non sappiamo con esattezza la struttura del documento, ma sappiamo che il titolo dei libri è contenuto in un elemento di come titolo. Vediamo come parsare il documento con gli strumenti offerti da JAXP e stampare i titoli dei libri.

3.1.5.1. DOM Parser

Vediamo come ottenere un documento DOM partendo dall'XML

  import javax.xml.parsers.DocumentBuilder; 
  import javax.xml.parsers.DocumentBuilderFactory;        
  import org.w3c.dom.Document;
          
         ...
         ...
          
  DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
  DocumentBuilder builder = factory.newDocumentBuilder();
  Document document = builder.parse( new File("/tmp/mysource.xml") );
  
  
  NodeList nodes = document.getElementsByTagName("title");
  while(int i = 0; i < nodes.length(); i ++) {
      Element titleElem = (Element)nodes.item(i);
      Node childNode = titleElem.getFirstChild();
      if (childNode instanceof Text) {
          System.out.println("Book title is: " + childNode.getNodeValue());
      }
  }
              
3.1.5.2. SAX Parser

Vediamo come effettuare il parsing con SAX

SAXParser saxParser = new SAXParser();
MyContentHandler myHandler = new MyContentHandler();
saxParser.setContentHandler(myHandler);
saxParser.parse(new File("/tmp/mysource.xml"));
              

Questa è l'implementazione del nostro handler:

public class MyContentHandler extends DefaultHandler {
    public void startElement(String uri, String localName, String qName, Attributes atts) {
        if (localName.equals("title")) 
        isTitle = true; 
    }
    public void endElement(String uri, String localName, String qName) {
        if(localName.equals("title")) 
        isTitle = false;
    }
    public void characters(char[ ] chars, int start, int length) {
        if(isTitle) 
        System.out.println(new String(chars, start, length));
    }
}
              
3.1.5.3. StAX Parser

Vediamo come fare il parsing di un documento xml con StAX

XMLInputFactory fac = XMLInputFactory.newInstance();
XMLEventReader eventReader = fac.createXMLEventReader(new FileInputStream("/tmp/mysource.xml"));
while(eventReader.hasNext()) { 
    XMLEvent event = eventReader.next();
        if (event instanceof StartElement && ((StartElement)event).getLocalName().equals("title")) {
        System.out.println( ((Characters)eventReader.next()).getData());
    }
}
              

3.2. XSD

Abbiamo appena visto come strutturare i dati e dar loro un significato grazie all'uso dell'XML, adesso abbiamo bisogno di uno strumento che ci permetta di descrivere tale struttura. Questo ci consentirebbe di validarne un messaggio XML, ovvero controllare che la sua struttura rispetti un determinato schema. Per definire uno schema si utilizza l'XSD (XML Schema Definition), un linguaggio bassato su XML.

3.2.1. Costruzione di uno Schema XML

Vediamo come si costruisce lo schema di un documento XML.

<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
           targetNamespace="http://www.shop.com/ns"
           elementFormDefault="qualified">
    
    ...
    
</xs:schema>
            

Il nodo radice deve essere uno schema definito nel namespace http://www.w3.org/2001/XMLSchema. Con il frammento

targetNamespace="http://www.shop.com/ns"

si indica che gli elementi che si andranno a definire avranno quel namespace, mentre con

elementFormDefault="qualified"

indichiamo che tutti gli elementi definiti saranno qualificati (ovvero avranno un namespace), non solo quelli globali.

A questo punto inseriamo gli elementi. Gli elementi si suddividono in semplici e complessi.

Un elemento semplice è un elemento XML che può contenere solo testo. Non può contenere nessun altro elemento o attributo. Il testo contenuto può comunque avere tipi differenti, quindi essere uno dei tipi inclusi nella definizione di XML Schema (boolean, string, date, etc.) oppure un tipo definito da noi. Inoltre possiamo imporre restrizioni al tipo di dato inserito per limitarne il contenuto.

Vediamo ad esempio che codice e quantita sono elementi semplici, dal momento che contengono solamente testo. La sintassi per definire un elemento semplice è la seguente:

<xs:element name="xxx" type="yyy"/>

dove xxx è il nome dell'elemento e yyy il suo tipo. l'XML Schema ha molti tipi di dato predefiniti. I più comuni sono:

  • xs:string

  • xs:decimal

  • xs:integer

  • xs:boolean

  • xs:date

  • xs:time

Possiamo quindi specificare i due elementi in questo modo:

<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://www.shop.com/ns"
    elementFormDefault="qualified">
    <xs:element name="quantita" type="xs:integer"/>
    <xs:element name="nome" type="xs:string"/>
</xs:schema>

            

Supponiamo di voler limitare il nome dell'articolo ad una stringa di 7 caratteri alfanumerici maiuscoli, privi di spazi. Per far questo definiamo un nuovo tipo imponendo una restrizione sul tipo base string:

<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://www.shop.com/ns"
    elementFormDefault="qualified">
    <xs:element name="quantita" type="xs:positiveInteger"/>
    <xs:element name="nome" type="nomeType"/>
    
    <xs:simpleType name="nomeType">
        <xs:restriction base="xs:string">
            <xs:pattern value="[A-Z0-9]{7}"/>
        </xs:restriction>
    </xs:simpleType>
        
</xs:schema>

            

Definiamo a questo punto l'elemento articolo, costituito dai due elementi semplici descritti in precedenza. Non essendo solo testo, non è un elemento semplice, ma complesso. I tipi complessi sono elementi con attributi o costituiti da altri elementi.

<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://www.shop.com/ns"
    elementFormDefault="qualified">
    
    <xs:element name="articolo" type="articoloType"/>
    
    <xs:complexType name="articoloType">
        <xs:sequence>
            <xs:element name="nome" type="nomeType"/>
            <xs:element name="quantita" type="xs:integer"/>
        </xs:sequence>
    </xs:complexType>
    
    <xs:simpleType name="nomeType">
        <xs:restriction base="xs:string">
            <xs:pattern value="[A-Z0-9]{7}"/>
        </xs:restriction>
    </xs:simpleType>
        
</xs:schema>

            

Quando definiamo un nuovo tipo complesso, alcuni degli elementi utilizzabili sono:

  • sequence: richiede che gli elementi compaiono nell'ordine specificato

  • group: un raggruppamento di altri tipi da utilizzare per un complexType

  • all: ammette che elementi compaiano (o non compaiano) in qualsiasi ordine

  • choice: ammette uno e uno solo degli elementi contenuti

Concludiamo il nostro schema definendo l'elemento ordine come sequenza di molti elementi articolo. Utilizzeremo nuovamente l'elemento xs:sequence specificando questa volta il numero di occorrenze (di default impostate a 1)

Per specificare un attributo nell'elemento ordine basta inserire il seguente elemento nella sua definizione

<xsd:attribute name="corriere" type="xsd:string" />

<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://www.shop.com/ns"
    elementFormDefault="qualified">
    
    <xs:element name="ordine" type="ordineType"/>
    
     <xs:complexType name="ordineType">
        <xs:sequence maxOccurs="unbounded">
            <xs:element name="articolo" type="articoloType"/>
        </xs:sequence>
        <xsd:attribute name="corriere" type="xsd:string" />
    </xs:complexType>
    
    <xs:complexType name="articoloType">
        <xs:sequence>
            <xs:element name="nome" type="nomeType"/>
            <xs:element name="quantita" type="xs:positiveInteger"/>
        </xs:sequence>
    </xs:complexType>
    
    <xs:simpleType name="nomeType">
        <xs:restriction base="xs:string">
            <xs:pattern value="[A-Z0-9]{7}"/>
        </xs:restriction>
    </xs:simpleType>
        
</xs:schema>

            

A questo punto abbiamo definito l'XSD e possiamo validare il contenuto del documento XML. Uno strumento di validazione online è reperibile all'indirizzo http://www.xmlforasp.net/SchemaValidator.aspx grazie ad esso possiamo controllare se un documento XML rispetta lo schema definito in un documento XSD.

3.2.2. Validazione con JAXP

JAXP, oltre ad effettuare il parsing e la costruzione dell'albero DOM, fornisce gli strumenti per validare i documenti XML rispetto ad uno o più XML Schema.

Per essere notificati di eventuali errori di validazione devono essere rispettati questi vincoli:

  • La Factory deve essere configurata e settato l'handler dell'errore

  • Al documento deve essere associato almento uno schema

Vediamo come gestire la validazione con JAXP. Per prima cosa configuriamo il parser

    static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
    static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema"; 
    
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance()
    factory.setNamespaceAware(true);
    factory.setValidating(true);
    try {
      factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
    } 
    catch (IllegalArgumentException x) {
      ...
    } 
            

Poi settiamo l'XML Schema. Questo può esser fatto o dichiarandolo nel documento XML stesso

                
<documentRoot
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation='YourSchemaDefinition.xsd'>
   
                

Oppure specificandolo nella factory

    static final String schemaSource = "YourSchemaDefinition.xsd";
    static final String JAXP_SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource";
        ...
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance()
        ...
    factory.setAttribute(JAXP_SCHEMA_SOURCE, new File(schemaSource));
                

3.3. Applicazioni XML su HTTP

Abbiamo adesso un client che invia ad una servlet il codice XML di un'ordine. Implementiamo la servlet in modo che lo validi e ne legga il contenuto.

try{	
    // Imposto la factory per la validazione
    
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setIgnoringElementContentWhitespace(true);
    factory.setNamespaceAware(true);
    factory.setValidating(true);
    factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
    factory.setAttribute(JAXP_SCHEMA_SOURCE, new File(xsdSchema));
    
    // Prendo il parser, valido l'xml e ne ottengo il DOM
    DocumentBuilder builder = factory.newDocumentBuilder();
    builder.setErrorHandler(new MyErrorHandler());
    Document request = builder.parse( req.getInputStream() );
    
    NodeList articoli = request.getElementsByTagName("articolo");
    for(int i = 0; i < articoli.getLength(); i++){
		Node articolo = articoli.item(i);
		NodeList articolo_children = articolo.getChildNodes();
		
		// Cerco e stampo nome e quantita' degli articoli
		
		for(int h = 0; h < articolo_children.getLength(); h++){
			Node articolo_child = articolo_children.item(h);
			
			if(articolo_child.getLocalName().compareToIgnoreCase("nome") == 0) 
				System.out.print(articolo_child.getTextContent());
			
			if(articolo_child.getLocalName().compareToIgnoreCase("quantita") == 0) 
				System.out.println(articolo_child.getTextContent());
		}
	}
}        
            

Infine rispondiamo al Client con un messaggio che confermi la ricezione dell'ordine.

try{
    // Imposto il Content-Type            
	res.setContentType("text/xml");
	
	// Scrivo il corpo del messaggio
	String xml = "<esito>OK<esito>";
	res.getWriter().write(xml);
}
catch(IOException e){
	e.printStackTrace();
	res.setStatus(500);
}
                
            

Footer BGFooter BG
Tito Flagella - © 2007-2015