Visualizzazione post con etichetta Technicalities. Mostra tutti i post
Visualizzazione post con etichetta Technicalities. Mostra tutti i post

mercoledì 13 giugno 2012

Usare JAXB con StAX+XPath per prestazioni e comodità d'uso (2)

Come visto nella prima parte, JAXB permette di implementare velocemente l'accesso a documenti XML, tuttavia l'approccio presenta lo svantaggio di non scalare rispetto alla dimensione dei documenti. Infatti il framework di binding cercherà di creare in memoria un insieme di oggetti Java che rappresenta interamente il documento XML e, al crescere della dimensione di questo, si scontrerà con il limite di memoria della JVM.

In molti casi i documenti XML di grandi dimensioni presentano una struttura con elementi ripetuti. E' il caso, ad esempio, di documenti XML che rappresentano dati statistici: la struttura XML che rappresenta le osservazioni è ripetuta all'interno di un tag contenitore. In questi caso è (spesso) possibile procedere a una elaborazione in streaming aggirando il problema dell'esaurimento della memoria.

Abbiamo già visto in un precedente post come utilizzare un parser StAX per accedere agli elementi di un documento XML effettuando opzionalmente anche la validazione. Ora vediamo come effettuare con JAXB l'unmarshalling dei soli elementi ripetuti, uno per volta.
In prossimi articoli analizzeremo una tecnologia alternativa per il trattamento di XML e faremo un'analisi comparativa anche in termini di prestazioni.

Supponiamo di avere il seguente documento, in cui la struttura corrispondente al tag mrSneerObj è ripetuta diverse volte.

<root xmlns="http://ict-tic.blogspot.com">
  <elements
   <mrSneerObj><!--other xml content--></mrSneerObj>
   <mrSneerObj><!--other xml content--></mrSneerObj>
   <mrSneerObj><!--other xml content--></mrSneerObj>
   <mrSneerObj><!--other xml content--></mrSneerObj>
   <mrSneerObj><!--other xml content--></mrSneerObj>
   <mrSneerObj><!--other xml content--></mrSneerObj>  
   <!--other mrSneerObj tags-->    
  </elements>
</root>

L'idea per limitare l'uso della memoria è di scorrere il documento XML con il parser StAX fino al tag relativo al primo degli elementi ripetuti (nell'esempio <mrSneerObj>). Raggiunto questo tag è possibile usare JAXB per effettuare l'unmarshalling solo della porzione di XML corrispondente a uno degli elementi ripetuti. Al termine dell'unmarshalling è possibile pilotare nuovamente il parser StAX in modo da posizionarsi al successivo elemento ripetuto, preparandosi per un nuovo unmarshalling. E così in ciclo finché tutti gli elementi ripetuti sono elaborati.

Un primo approccio per navigare con StAX tra gli elementi XML fino a raggiungere il punto di interesse (gli elementi ripetuti) è quello di guidare il parser con un automa a stati finiti. Tuttavia questo approccio è abbastanza laborioso in quanto è necessario specificare esplicitamente tutti gli stati e le transizioni in funzione degli eventi StAX. E' anche poco leggibile e manutenibile.

Una tecnologia che permette di accedere comodamente a porzioni di XML è XPath. Purtroppo le implementazioni delle specifiche XPath non sono compatibili con un parser di tipo streaming, ma richiedono un parser DOM. Questo sembra escludere la possibilità di usare implementazioni standard di XPath che, usando DOM, amplificherebbero il problema della saturazione di memoria della JVM.

Esistono tuttavia implementazioni parziali delle specifiche XPath che sono compatbili con parser di tipo streaming. Uno di questi è Simple XML Compiler (SXC). SXC è un tool che permette di creare parser XML ottimizzati. Dispone, in particolare, di un modulo XPath che è capace di pilotare un parser StAX per navigare il documento XML, individuando gli elementi XML che soddisfano una o più espressioni XPath. In corrispondenza dei punti in cui si ha il "match" dei nodi con le espressioni XPath, SXC invoca un Hanlder fornito dal chiamante.

Di seguito è mostrato come utilizzare SXC per accedere ai nodi /root/elements/mrSneerObject del documento di esempio invocando l'handler matchHandler.

XMLStreamReader streamReader = ...

XPathBuilder builder = new XPathBuilder();
builder.addPrefix("c", "http://ict-tic.blogspot.com");
builder.listen("/c:root/c:elements/c:mrSneerObj", matchHandler);
XPathEvaluator evaluator = builder.compile();      
evaluator.evaluate(
streamReader);

Ad ogni "match" l'handler di seguito riportato permette di effettutare l'unmarshalling con JAXB. A parte le porzioni di codice utilizzate per gestire gli errori di validazione (che è opzionale - l'integrazione tra StAX e MSV è descritta in questo articolo), il pattern d'uso di JAXB resta quello standard. L'unica differenza è che il riferimento al parser è recuperato dall'evento SXC e non tramite Factory.

JAXBContext jc = JAXBContext.newInstance("it.mrsneer");
final Unmarshaller u = jc.createUnmarshaller();

XPathEventHandler matchHandler = new XPathEventHandler() {
  public void onMatch(XPathEvent event) throws XMLStreamException {
    try {
      if (!listOfProblems.isEmpty()) {
        // gestisce problemi nel parsing 
        // tra due istanze di MrSneerClass
        // ...
        listOfProblems.clear();
      }

      XMLStreamReader streamReader = event.getReader();
      JAXBElement<MrSneerClass> obj;
      obj = (JAXBElement<MrSneerClass>) u.unmarshal(streamReader,  
                                                 MrSneerClass.class);
      if (listOfProblems.isEmpty()) {
           // usa obj.getValue();
      } else {
        
        // gestisce problemi durante l'unmarshalling 
        // di una istanza di MrSneerClass
        // ...
        listOfProblems.clear();      
      }
    } catch (Exception e) {
        
        // gestisce Exception
        // ...
    }
  }
}; 


venerdì 8 giugno 2012

Usare JAXB con StAX+XPath per prestazioni e comodità d'uso (1)

Una delle tecnologie Oracle per l'elaborazione di documenti XML è Java Architecture for XML Binding (JAXB).

Questo framework permette di effettuare il collegamento tra Java e XML; in particolare JAXB fornisce la possibilità di serializzare oggetti Java in XML (marshalling) e di effettuare l'operazione inversa (unmarshalling).

Immagine dal sito Oracle
Il collegamento Java-XML può essere effettuato a partire dallo Schema Xml che rappresenta il modello di documento da elaborare. Il compilatore JAXB produce le classi Java da utilizzare nell'applicazione per effettuare il marshalling e l'unmarshalling dei documenti istanza.

Supponiamo di avere il seguente documento e il relativo Schema Xml.

<root xmlns="http://ict-tic.blogspot.com">
  <elements
   <mrSneerObj><!--other xml content--></mrSneerObj>
   <mrSneerObj><!--other xml content--></mrSneerObj>
   <mrSneerObj><!--other xml content--></mrSneerObj>
   <mrSneerObj><!--other xml content--></mrSneerObj>
   <mrSneerObj><!--other xml content--></mrSneerObj>
   <mrSneerObj><!--other xml content--></mrSneerObj>  
  </elements>  
</root>

L'approccio classico di JAXB prevede, dopo la generazione delle classi a partire dallo Schema, la creazione di un contesto JAXB relativo al package prodotto dal compilatore JAXB e del relativo Unmarshaller.

L'Unmarshaller può quindi essere usato per produrre gli oggetti Java che rappresentano il documento XML. L'invocazione del metodo unmarshal accetta diverse rappresentazioni del documento XML di input. Di seguito è mostrata l'invocazione usando un reader StAX come parametro.

JAXBContext jc = JAXBContext.newInstance("it.mrsneer");
final Unmarshaller u = jc.createUnmarshaller();
JAXBElement<Root> obj = (JAXBElement<Root>) u.unmarshal(streamReader, Root.class);

L'oggetto restituito corrisponde al tag <root> e può essere navigato tramite i metodi getter come un normale POJO. Ovviamente in memoria, nella JVM, ci sarà un albero di oggetti Java collegati tra loro in modo da rappresentare tutti i nodi del documento XML di partenza.

mercoledì 23 maggio 2012

Parser STAX e validazione XSD con Woodstox e MSV

Woodstox è un parser particolarmente efficiente che implementa l'interfaccia StAX... un candidato ideale quindi per l'elaborazione di documenti XML di dimensioni considerevoli.

Una caratteristica interessante di questo parser è la sua capacità di interfacciare il Sun Multi-Schema Validator (di seguito MSV) per la validazione dell'XML tramite diverse tipologie di Schema, ad esempio DTD, XML Schema e RELAX NG.

Per la validazione è necessario ricorrere a una estensione specifica di Woodstox che è al di fuori dell'interfaccia StAX. Tuttavia l'utilizzo di questa estensione è abbastanza limitata e non compromette in modo eccessivo la migrazione a altri parser StAX (eliminando la validazione ove questi non la supportino).

Di seguito è descritto un esempio di utilizzo di Woodstox con MSV per effettuare la validazione con XML Schema.

Dopo la creazione dell'istanza del parser tramite la factory XMLInputFactory, si effettua il cast dell'oggetto XMLStreamReader ottenuto (che fa parte dello standard StAX) all'interfaccia XMLStreamReader2 (che è invece specifica di Woodstox) e si associa tramite il metodo validateAgainst() l'istanza dello XMLValidationSchema (ottenuto da un factory XMLValidationSchemaFactory definita in MSV).

Per catturare gli errori di validazione è possibile definire uno specifico ValidationProblemHandler invocando il metodo setter opportuno sull'istanza di XMLStreamReader2. L'handler potrà gestire i problemi di validazione registrandoli in una collection oppure potrà lanciare una eccezione di tipo XMLValidationProblem che sarà restituito al client del parser durante l'elaborazione degli eventi.

Il parser e le librerie MSV possono essere scaricate a partire da questo link.

File inFile = ... // documento
File xsdFile = ... // XML schema 


XMLInputFactory staxFactory = XMLInputFactory.newInstance();       
XMLStreamReader streamReader = staxFactory.createXMLStreamReader(new FileInputStream(inFile));
       
XMLValidationSchemaFactory schemaFactory = XMLValidationSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
       
XMLValidationSchema schemaGrammar = schemaFactory.createSchema(xsdFile);
 
XMLStreamReader2 sr2 = ((XMLStreamReader2)streamReader);
       
final List<XMLValidationProblem> listOfProblems = new ArrayList<XMLValidationProblem>();
       
if (schemaGrammar!=null) {
  sr2.validateAgainst(schemaGrammar);
  sr2.setValidationProblemHandler(new ValidationProblemHandler(){
     public void reportProblem(XMLValidationProblem problem) throws XMLValidationException {
       listOfProblems.add(problem);
     }
  });
}


// usa lo stream Reader
// streamReader.next();

martedì 22 maggio 2012

Parsing di XML: DOM, SAX, StAX

L'interfaccia di un componente può vincolare pesantemente le sue caratteristiche prestazionali e di usabilità. Un esempio abbastanza significativo è rappresentato dalle tre diverse API che sono state disegnate per il parsing dei documenti XML.

Per l'elaborazione dei documenti XML esistono infatti tre interfacce standard, ciascuna con particolari caratteristiche di prestazione e di usabilità.

fornisce una modalità di navigazione all'interno del documento XML di tipo "ad albero"; il parser, a partire dal documento XML, memorizzato come stream di byte, crea una struttura in memoria (appunto modello a oggetti del documento) che rappresenta il documento in tutti i suoi aspetti. Il programmatore può navigare la struttura ad albero ed accedere in modo diretto (anche usando XPath) ai suoi elementi e attributi.

contrariamente al parser DOM, il parser SAX elabora i documenti in una modalità "a eventi" (streaming parser) senza caricare in memoria l'interno documento. Il parser scorre il documento XML dall'inizio alla fine e, per ciascun elemento XML incontrato e che può essere significativo per l'elaborazione, produce un evento. L'evento è passato a una funzione di callback che il programmatore ha definito prima dell'attivazione del parser.  Questo tipo di parser è detto parser di tipo "push" in quanto gli eventi vengono inviati all'utilizzatore del parser senza che questo possa decidere quando ricevere il successivo evento.
L'elaborazione di documenti XML con un parser SAX richiede la creazione di uno specifico  automa a stati finiti (dipendente sia dalla struttura dell'XML sia dalla logica applicativa) ed è pertanto abbastanza laboriosa da implementare.
Gli unici vantaggi dei parser SAX sono: l'elevato livello di prestazioni e la ridotta quantità di memoria utilizzata, indipendentemente dalla dimensione del documento da elaborare.

è un parser di tipo "a eventi" (streaming parser) che quindi fornisce le prestazioni e la ridotta occupazione di memoria tipiche dei parser a eventi, ma che punta a offrire una maggiore comodità di programmazione.
Il parser, infatti, una volta invocato su un documento XML, resituisce al suo utilizzatore un "cursore" che rappresenta la posizione corrente nel documento XML durante l'elaborazione.
L'applicazione, quando vuole ottenere ed elaborare il successivo evento (che rappresenta un elemento XML, nodo o attributo presente nel documento), può pilotare il parser facendo avanzare il cursore.
Per questa particolare modalità di interazione con il client, il parser viene detto di tipo "pull"

In sintesi:
XML Parser API Feature Summary (adattata da Oracle)

Feature

StAX

SAX

DOM

API Type

Pull, streaming

Push, streaming

In memory tree

Ease of Use

High

High

Medium

XPath Capability

No

No

Yes

CPU and Memory Efficiency

Good

Good

Varies

Forward Only

Yes

Yes

No

Read XML

Yes

Yes

Yes

Write XML

Yes

No

Yes

Create, Read, Update, Delete

No

No

Yes

martedì 15 maggio 2012

Esportare le dipendenze di un progetto maven

Per il build dei progetti Java, in particolare per progetti open source con un'ampia comunità di sviluppatori, ci si ritrova spesso a lavorare con il tool Maven.

Maven è un tool di build che utilizza file XML (pom.xml) per la descrizione sia dei passi richiesti per le diverse fasi di build, sia della struttura del progetto in termini di moduli e relative dipendenze (anche verso componenti esterni).

Così un progetto che dipenda, ad esempio, dalla libreria Hibernate, può semplicemente dichiarare tale dipendenza e Maven provvederà in fase di build a recuperare (eventualmente da Internet) il jar di Hibernate e tutte le librerie richieste da Hibernate stesso (effettuando il download quindi di tutte le librerie che costituiscono la chiusura transitiva del grafo delle dipendenze del progetto). Le librerie scaricate da Internet, sono memorizzate in una cache locale dell'utente in modo da essere utilizzate nelle successive fasi di build.

In alcuni casi (ad esempio per importare il progetto in un tool che non supporta Maven) è utile ottenere tutte le librerie necessarie per la build del progetto. Poichè Maven è basato su un sistema a plugin, ed è disponibile uno specifico plugin che effettua proprio questo compito, per ottenere il risultato desiderato è sufficiente una piccola modifica al file pom..xml del progetto.

Nella sezione project/build/plugins si può aggiungere il seguente plug-in di Maven:

  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>2.4</version>
    <executions>
      <execution>
        <id>copy-dependencies</id>
        <phase>package</phase>
        <goals>
          <goal>copy-dependencies</goal>
        </goals>
        <configuration>
         <outputDirectory>${project.build.directory}/depLibs</outputDirectory>
         <overWriteReleases>false</overWriteReleases>
         <overWriteSnapshots>true</overWriteSnapshots>
         <excludeTransitive>true</excludeTransitive>
       </configuration>
     </execution>
    </executions>
  </plugin>


La configurazione del plugin descritta sopra è tale eseguire la copia delle librerie durante la fase di pacakge. In questo modo, durante l'esecuzione del comando mvn package oltre al build, Maven provvederà a esportare tutte le librerie da cui il progetto dipende (direttamente e non) nella cartella depList (posizionata nello stesso folder in cui sono prodotti gli artefatti derivanti dal processo di build).

lunedì 30 aprile 2012

Modificatore volatile in Java

La piattaforma Java supporta la programmazione concorrente tramite il concetto di thread di esecuzione. Una applicazione Java può avere più flussi di esecuzione paralleli, ciascuno rappresentato da un oggetto java.lang.Thread.

Thread di esecuzione - Immagine recuperata da Internet


Quando due thread hanno necessità di interagire tra loro devono "sincronizzarsi" in modo da evitare situazioni non deterministiche (in cui il risultato varia da un'esecuzione all'altra a causa di variazioni, anche piccole, nei tempi di esecuzione dei singoli step elaborativi). Ed è proprio la sincronizzazione uno degli aspetti più complessi della programmazione concorrente.

La sincronizzazione è caratterizzata da due aspetti in un certo senso ortogonali tra loro:
  1. mutua esclusione
  2. visibilità
La mutua esclusione è utilizzata per evitare l'accesso concorrente a strutture dati condivise. La mutua esclusione in Java si basa sul concetto di monitor. Ad ogni oggetto Java è associato un monitor che offre le classiche primitive di lock e unlock. Il runtime garantisce che un solo thread per volta può ottenere il lock su uno stesso monitor. Utilizzando un monitor è possibile impedire l'accesso concorrente a una struttura dati condivisa.

Java mette a disposizione, oltre alle primitive di basso livello sul monitor, anche altri costrutti di sincronizzazione quali i metodi e i blocchi "synchronized" e l'intero package java.util.concurrent. Queste feature non essendo oggetto di questo articolo non saranno però illustrate ulteriormente (magari potremmo ritornarci in futuro, se interessa).

La visibilità è invece un aspetto po' più subdolo. Esso riguarda la capacità di vedere il valore corrente di una varibile da parte dei thread differenti rispetto a quello che ha modificato la variabile (sembra complicato ma leggendo di seguito dovrebbe risultare più chiaro).

Per motivi di prestazioni, la piattaforma Java può conservare in una cache locale a ciascun thread (o in registri della CPU) il valore delle variabili anche quando queste sono condivise tra più thread. Se il valore di una variabile è modificato in un thread, nella cache di tutti gli altri thread potrebbero esserci valori non più aggiornati. Il programmatore deve quindi sempre usare meccanismi di controllo della visibilità per assicurarsi che i thread vedano i valori corretti delle varibili quando accedono a dati condivisi.

La visibilità tra thread è sicuramente garantita usando le primitive di lock/unlock di un monitor (quelle usate per gestire la mutua esclusione). Nella seguente figura sono mostrati i due thread A e B sincronizzati tramite il monitor M. Il modello di memoria Java garantisce che i valori delle variabili disponibili nel thread A, immediatamente prima dell'unlock di M, siano visibili al thread B, subito dopo che questo ha ottenuto il lock di M.

Figura dal sito IBM

Esiste tuttavia anche un modo diverso per forzare la visibilità di una variabile condivisa e che non richiede l'utilizzo di primitive di lock/unlock: il modificatore volatile. Tale modificatore può essere utilizzato per informare la piattaforma che una variabile è acceduta da più thread e si intende garantire la visibilità del valore corrente della variabile (senza bloccare i thread quindi con un miglioramento delle prestazioni dell'applicazione).

Un esempio classico di utilizzo di questo costrutto è il seguente: supponiamo di avere un thread che effettua un lavoro in background finché non gli viene segnalato di terminare. La richiesta di terminazione può essere registrata in una variabile shutdownRequested di tipo boolean che, in generale, sarà impostata da un differente thread.

In questo scenario non vi è un conflitto tra i due thread (perché il thread che lavora in background accede alla variabile shutdownRequested in sola lettura) e non è pertanto necessario usare un monitor per proteggere la risorsa condivisa. Tuttavia il modello di memoria di Java richiede che, per garantire la visibilità del valore della variabile condivisa tra i due thread, sia comunque utilizzato un costrutto di sincronizzazione. In questo caso in cui un monitor sarebbe eccessivo... si può ricorrere al modificatore volatile. Di seguito è riportato il frammento di codice.

volatile boolean shutdownRequested;
...
public void shutdown() { 
  shutdownRequested = true; 
}

public void doSomeWork() {
    while (!shutdownRequested) {
        // ciclo thread1
    }
}

Cosa accade se "dimentichiamo" il modificatore volatile? Magari su alcune implementazioni della piattaforma Java il programma continuerà a funzionare correttamente... tuttavia in altre implementazioni (in particolare quelle dedicate a sistemi multiprocessore) l'esecuzione del programma potrebbe far andare il thread di background in un loop infinito: il valore true della variabile shutdownRequest (impostato da un altro thread) non sarebbe visibile al thread di background che continuerebbe a ciclare sul valore iniziale false memorizzato nella propria cache!

Chi volesse approfondire... sul sito Oracle, al link indicato di seguito, sono pubblicate le specifiche della JVM con un capitolo dedicato al modello di memoria di Java (link: Threads e Locks). Un testo specifico sulla programmazione concorrente è Java Concurrency in Practice.

sabato 31 marzo 2012

Design - Array e firma di un metodo

Il caso: si deve progettare un metodo dell'interfaccia InterfB che permette al componente B di restituire al chiamante (nell'esempio il componente A) un insieme di oggetti di tipo String.
B restituisce ad A un insieme di String
Come semplificazione supponiamo che, per l'elaborazione del risultato, il metodo richieda solo un parametro intero.

Ovviamente sono possibli diverse soluzioni, alcune delle quali sono:
  1. String[] calcolaElencoDiValori(int parametro)
  2. Collection<String> calcolaElencoDiValori(int parametro)
  3. void calcolaElencoDiValori(int parametro, Collection<String> risultatoDaB)
Quale scegliereste?

Personalmente trovo le soluzioni 1 e 2 abbastanza equivalenti. Dal punto di vista del chiamante occorre solo invocare il metodo e assegnare il valore restituito a una variabile nello scope corrente (di seguito b è una istanza di B).

String[] risultatoDaB = b.calcolaElencoDiValori(p);

oppure

Collection<String> risultatoDaB = b.calcolaElencoDiValori(p);

La soluzione 3 invece richiede che il chiamante passi al metodo, oltre che il parametro intero da usare per l'elaborazione, anche la lista designata a ricevere il risultato.

Collection<String> risultatoDaB = new ArrayList<String>();
b.calcolaElencoDiValori(p, risultatoDaB);

Con quest'ultima soluzione l'invocazione si complica (leggermete) ma la flessibilità che il chiamante ottiene, in molti casi, permette di migliorare sensibilmente le prestazioni del software. Il chiamante può iniettare, con l'invocazione del metodo, l'istanza di Collection<String> per lui più vantaggiosa (in termini di implementazione - ArrayList vs LinkedList vs HashSet - e in termini di parametri di inizializzazione della collection - ArrayList(n) vs ArrayList() ).

Si potrebbe inoltre pensare di implementare il metodo in modo da aggiungere i valori calcolati alla collection senza ripulirla prima; in questo modo il client potrà raccogliere (se occorre) nella stessa collection i risultati di più invocazioni senza dover effettuare scansioni/copie dei risultati temporanei.

Collection<String> risultatoDaB = new ArrayList<String>();
b.calcolaElencoDiValori(p1, risultatoDaB);
b.calcolaElencoDiValori(p2, risultatoDaB);

Eppure da un punto di vista "funzionale" (o meglio astraendo opportunamente - l'astrazione sarà oggetto di prossimi articoli) tutti e tre gli approcci sono equivalenti!