Apache Axis2: AXIOM – das neue Objektmodell für XML-Verarbeitung
Kommentare

AXIOM API
Das Programmiermodell von AXIOM wirkt auf den ersten Blick ähnlich wie das von DOM oder JDOM sehr intuitiv. Zuerst wird ein Builder instanziiert, der für den Aufbau des Objektmodells zuständig

AXIOM API

Das Programmiermodell von AXIOM wirkt auf den ersten Blick ähnlich wie das von DOM oder JDOM sehr intuitiv. Zuerst wird ein Builder instanziiert, der für den Aufbau des Objektmodells zuständig ist. Mithilfe des Builder erhält man eine Referenz zum Dokumentobjekt oder direkt zum Dokumentelement (Wurzelelement des XML-Infosets). Von da aus kann man frei im Objektmodell navigieren, um an die gewünschte Stelle zu gelangen und die benötigte Information auszulesen. Die Methoden im AXIOM API sehen größtenteils identisch oder ähnlich aus wie die in DOM. Auch die Modellklassen und deren Hierarchie kann zum großen Teil eins zu eins vom DOM-Modell abgebildet werden. Alle Modellklassen befinden sich im Package org.apache.axiom.om und haben als Namen immer „OM“ als Präfix zu ihren DOM-Pendents. Die wichtigsten Modellklassen sind OMDocument,OMElement,OMText und OMComment, die alle wiederum von OMNode abgeleitet sind. Listing 2 zeigt, wie man die vorhin erwähnte Aufgabe, den Namen des Hotelmanagers zu ermitteln, löst.

Abb. 2: Klassenhierarchie in AXIOM
Listing 2: XML parsen mit AXIOM 
----------------------------------------------------------------
(AXIOMSample1.java)
public class AXIOMSample1 {
  public static void main(String[] args) throws Exception {
    AXIOMSample1 sample = new AXIOMSample1();    
    sample.getManagerName();
  }
  
  private void getManagerName() throws Exception {
    InputStream in =  
      getClass().getResourceAsStream("/AXIOMsample.xml");
    XMLStreamReader reader = 
      XMLInputFactory.newInstance().createXMLStreamReader(in); 
    StAXOMBuilder builder = new StAXOMBuilder(reader);     
    OMDocument doc = builder.getDocument();
    OMElement docEl = doc.getOMDocumentElement();
    OMElement managerEl = 
      docEl.getFirstChildWithName(new QName("manager"));
    System.out.println(managerEl.getText());
  }
}

Zuerst wird eine XMLStreamReader-Instanz mit dem einzulesenden XML-Dokument angelegt, die im nächsten Schritt beim Erzeugen eines AXIOM Builder als Parameter übergeben wird. Nach der Erzeugung des Builder kann entweder das Dokument vom Typ OMDocument oder direkt das Dokumentelement vom Typ OMElement vom Builder abgefragt werden.

Anschließend kann man frei im Baum navigieren, wie man es von DOM oder JDOM gewohnt ist. Die Methode getChildren() liefert einen Iterator auf alle Kindknoten zurück. Dabei ist zu beachten, dass diese Methoden auch die Leerräume (White Spaces) als OMText zurückgibt, mit denen man nicht immer rechnet. Wenn nur die Kindelemente berücksichtigt werden sollen, was normalerweise bei einer gut strukturierten XML-Infoset der Fall ist, bietet sich dafür die Methode getChild­Elements() an, welche nur die Kindknoten vom Typ OMElement zurückgibt. Um die Ergebnismenge weiter einzuschränken, können die Kindelemente auch gezielt gefiltert werden, indem die Methode get­ChildrenWithName(QName elementQName) benutzt wird. Alle drei Methoden liefern interessanterweise keine Collection, sondern immer einen Iterator zurück. Der Grund dafür liegt darin, dass AXIOM das Objektmodell immer auf Anforderung aufbaut. Ein voll instanziiertes Collection-Objekt würde bedeuten, dass sämtliche Kindelemente eingelesen und als Objekte aufgebaut werden müssen, damit die Methode von size() auch sinnvoll implementiert werden kann. Dagegen erlaubt die Iterator-Klasse nur einen iterativen Zugriff, was genau der Building-on-demand-Philosophie von AXIOM entspricht. Beim Iterieren bewegt sich der Iterator im Infoset nur soweit vorwärts bzw. fordert nur so viele Ereignisse vom XMLStreamReader an, bis entweder ein gesuchter Knoten gefunden ist (sodass iterator.next() „true “ zurückgeben kann) oder das schließende Elternelement gefunden ist (sodass iterator.next() „false“ zurückgeben kann).

Bei der Nutzung eines baumbasierten API kennt die Applikation meistens die zu erwartende Struktur. Für diesen Fall kann mithilfe der Methode getFirstChild­WithName(QName elementQName) gezielt auf ein Kindelement mit bestimmtem Namen zugegriffen werden. Dann stehen weitere Methoden wie getFirst­Element, getFirstOMChild und getNext­OMSibling für die Navigation im Baum zur Verfügung. Für den Zugriff auf die elementspezifischen Daten wie Elementnamen, Attributwert und Textinhalt usw. stellen die jeweiligen OM-Klassen die üblichen Methoden wie getLocalName, getAttribute und getText bereit.

Für ein Standard-XML-Infoset reicht es, den Builder StAXOMBuilder einzusetzen. Sollte dagegen ein Infoset einer SOAP-Nachricht vorliegen, ist der Einsatz von StAXSOAPModelBuilder ratsam, da dieser SOAP-spezifische Unterklassen von OMElement wie SOAPEnvelope, SOAPHeader und SOAPBody liefern kann. Während die generischen Modellklassen wie OMElement oder OMAttribute in dem Package org.apache.axiom.om zu finden sind, befinden sich die SOAP-Spezialisierungen in dem Package org.apache.axiom.soap. Die Beziehung zwischen beiden Packages ist vergleichbar mit der Beziehung zwischen DOM und SAAJ (SOAP with Attachment API for Java). Es ist an dieser Stelle zu vermerken, dass in Axis2 (nicht in AXIOM) eine vollwertige SAAJ-Implementierung auf AXIOM-Basis vorhanden ist.

Das Dokumentelement, das mit einem StAXSOAPModelBuilder bei der Verarbeitung einer SOAP-Nachricht zurückgeliefert wird, kann zu einem SOAPEnvelope gecastet werden. Eine bessere Alternative ist es, die Methode getSOAPEnvelope() in StAXSOAPModelBuilder direkt zu benutzen, welche gleich einen SOAPEnvelope zurückgibt. Nach dem Erhalt von SOAPEnvelope kann man schnell zu dem eingekapselten SOAPHeader und dem SOAPBody gelangen. Die Nutzung ist sehr intuitiv und zum großen Teil identisch mit SAAJ (Listing 3).

Listing 3: SOAP-Nachrichten mit StAXSOAPModelBuilder verarbeiten 
----------------------------------------------------------------------------------------
(AXIOMSoap1.java)
InputStream in = 
  getClass().getResourceAsStream("/soapmessage.xml");
XMLStreamReader reader = 
  XMLInputFactory.newInstance().createXMLStreamReader(in); 
StAXSOAPModelBuilder builder = 
  new StAXSOAPModelBuilder(reader, null);
SOAPEnvelope envelope = builder.getSOAPEnvelope();
SOAPHeader soapHeader = envelope.getHeader();
Iterator iter = soapHeader.examineAllHeaderBlocks();
while (iter.hasNext()) {
  SOAPHeaderBlock soapHeaderBlock = (SOAPHeaderBlock) iter.next();
  System.out.println(soapHeaderBlock.getLocalName());
  System.out.println(soapHeaderBlock.getMustUnderstand());        }
SOAPBody body = envelope.getBody();
XMLOutputFactory xof = XMLOutputFactory.newInstance();
XMLStreamWriter writer = xof.createXMLStreamWriter(System.out);        
body.serialize(writer);
Caching

Caching ist ein zentrales Konzept in ­AXIOM. Um Caching genau verstehen zu können, muss die Designphilosophie von AXIOM genauer unter die Lupe genommen werden. AXIOM wird benutzt, um ein XML-Infoset mit seinem Objektmodell zu repräsentieren. Dieses Objektmodell kann auf verschiedene Arten und Weisen aufgebaut werden: Entweder wird der Builder mit einem Pull Event Stream bzw. einem Push Event Stream gefüttert oder das Modell wird direkt mit API-Aufrufen aufgebaut. Nach außen bietet AXIOM wiederum verschiedene Möglichkeiten, auf sein Objektmodell bzw. die XML-Infoset-Repräsentation zuzugreifen. Neben dem baumbasierten AXIOM API ist es auch möglich, von einem beliebigen OMElement direkt den gekapselten XMLStreamReader zu verlangen und alle weiteren Daten mit dem StAX API (bzw. über einen Adapter mit SAX API) im Streaming-Modus zu verarbeiten. AXIOM bietet an der Stelle quasi einen Schalter für die Applikation, der jederzeit umgelegt werden kann, um zwischen StAX (bzw. SAX) und DOM zu wechseln, wie in der Abbildung 3 verdeutlicht wird.

Abb. 3: AXIOM als XML-Infoset-Repräsentierung

Wie das folgende Beispielszenario belegen soll, ist es durch die immer komplexer werdenden Anforderungen der Web-Service-Applikationen erforderlich, vielfältige und flexible Zugriffsmöglichkeit auf ein XML-Infoset anzubieten. Eine SOAP-Nachricht wird oft nicht einfach von Absender direkt zu Empfänger verschickt, sondern durchläuft ggf. auch mehrere Intermediaries. Diese Intermediaries inte­ressieren sich im Normalfall nur für einen bestimmten SOAP Header, der wiederum aufgrund seiner geringen Datenmenge bevorzugt mit baumbasiertem API verarbeitet wird. Die Serviceimplementierung zieht dagegen vor, den SOAP Body zuerst mithilfe eines XML-Data-Binding-Werkzeugs in Objekte bzw. POJOs umzuwandeln, weil die Objekte leichter zu handhaben sind. Da die XML-Data-Binding-Werkzeuge meistens mit SAX oder StAX arbeiten und in diesem Fall auf den SOAP Body sonst nicht mit einem baumbasiertem API zugegriffen wird, wäre es eine Verschwendung, den kompletten Baum für die SOAP-Nachricht im Speicher aufzubauen.

Sinnvoller ist es, wenn die XML-Data-Binding-Werkzeuge direkt auf die von AXIOM gekapselten Streaming-Daten zugreifen können. Nur so lassen sich optimale Performance und effiziente Speichernutzung gewährleisten. Schon bei diesem gängigen Beispielszenario stellt sich he­raus, dass es vorteilhafter ist, auf unterschiedliche Bereiche eines XML-Infosets über unterschiedliche APIs zuzugreifen. Um dieser Anforderung gerecht zu werden, bietet AXIOM eine Vielfalt von Zugriffsmöglichkeiten, um dadurch eine optimale Balance zwischen Performance, Speichernutzung und API-Komplexität zu erzielen. Dank verzögertem Aufbau kann im obigen Szenario so verfahren werden, sodass der SOAP Body nicht von AXIOM als Objektmodell aufgebaut, sondern in Form von Streaming-Daten direkt dem Data-Binding-Werkzeug zur Verfügung gestellt wird.

Diese Performanceverbesserung durch direkte Verbindung zwischen Datenstrom und Data-Binding-Werkzeug hat jedoch ihren Preis und funktioniert nur gut, wenn auf den SOAP Body nicht zweimal zugegriffen wird. Diese Voraussetzung wird im Normalfall auch erfüllt, da der SOAP Body meistens nur von der Serviceimplementierung verarbeitet wird. Diese Situation ändert sich jedoch schnell, wenn ein Logging Handler plötzlich in der Verarbeitungskette eingehängt werden soll, welcher mit einem XMLStreamWriterdie komplette SOAP-Nachricht protokolliert. Diese Kon­stellation wird durch ein künstlerisches Beispiel im nun folgenden Code­abschnitt demonstriert.

StAXOMBuilder builder = new StAXOMBuilder(reader);     
OMElement docEl = builder.getDocumentElement();
XMLOutputFactory xof = XMLOutputFactory.newInstance();
XMLStreamWriter writer = xof.createXMLStreamWriter(System.out);
docEl.serialize(writer);

OMElement managerEl = docEl.getFirstChildWithName(new QName("manager"));
System.out.println(managerEl.getText());

Da auf das Dokumentelement (docEl) zuerst nicht zugegriffen wird, wird es aufgrund des verzögerten Aufbaus nicht als Objektmodell aufgebaut. Bei der anschließenden Serialisierung verbindet AXIOM den XMLStreamWriterdirekt mit dem durch den Builder gekapselten XMLStreamReader und klemmt sich vom Datenstrom ab. Wenn aber nach der Serialisierung der Textinhalt des manager-Kindelements abgefragt werden soll, ist dies jedoch nicht mehr möglich, weil das Objektmodell bei verzögertem Aufbau nicht erzeugt wurde und sämtliche XML-Ereignisse bereits vom XMLStreamWriter konsumiert sind. Diese Situation ist vergleichbar mit der Verarbeitung einer SOAP-Nachricht, nur dass in diesem Fall die XML-Ereignisse von einem XMLStreamWriter statt von einem Databinding-Werkzeug konsumiert werden.

Die einzige Lösung für das obige Problem ist es, zu vermeiden, dass sich AXIOM bei der Serialisierung komplett abklemmt. Die AXIOM-Terminiologie dafür ist Caching. AXIOM erlaubt es, direkt auf den XML-Datenstrom zuzugreifen mit oder ohne gleichzeitigen Aufbau des Objektmodells. Die Applikation kann selbst entscheiden, wann sie direkt auf den Datenstrom zugreift und ob der noch nicht aufgebaute Teilbaum beim Streaming-Zugriff für späteren Gebrauch gecacht werden soll. Ist Caching eingeschaltet, wird der Teilbaum immer als Objektmodell im Speicher aufgebaut, sodass die Applikation auch später auf das gecachte Modell zurückgreifen kann. Dabei müssen aber der höhere Speicherverbrauch und die längere Verarbeitungszeit als Preis bezahlt werden. Um Caching im obigen Beispiel einzuschalten, muss der obige Codeabschnitt nur leicht verändert werden, und zwar von docEl.serialize(writer);  zu doc­El.serializeWithCache(writer); Wenn AXIOM den gekapselten XML­StreamReader ausgibt (z.B. an ein Databinding-Werkzeug), muss das Caching-Verhalten ebenfalls festgelegt werden.

OMElement.getXMLStreamreader()
OMElement .getXMLStreamreaderWithoutCaching()

Interessant ist auch, wenn ein Teilbaum zum Zeitpunkt des Aufrufs einer der beiden obigen Methoden bereits aufgebaut ist bzw. wenn ein Teil von XML-Ereignissen schon von AXIOM konsumiert ist. Hier ist AXIOM in der Lage, StAX Parser zu emulieren, sodass ein Teil der XML-Ereignisse vom ­AXIOM-Objektmodell stammt, während der Rest direkt von XMLStreamReader kommt. Für den Benutzer bleiben das natürlich transparent.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -