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
        // ...
    }
  }
}; 


Nessun commento:

Posta un commento