In einem unserer aktuellen Java-Kundenprojekte standen wir kürzlich vor der Aufgabe, den als XML verpackten Datenbestand einer externen Altanwendung in die Datenbank unserer Neuentwicklung zu importieren. Was tut man also, wenn man es mit potenziell sehr großen XML-Dateien zu tun bekommt, von denen man vorher nicht weiß, ob man sie problemlos als Ganzes in den Speicher lesen kann oder nicht?
Transformers
Für das Einlesen von XML-Daten gibt es eine Vielzahl von Möglichkeiten: Angefangen von generischen baumbasierten Modellen wie W3C-DOM oder JDOM über spezifische XML-Bindings wie JAXB bis hin zu Streaming-APIs wie SAX oder StAX.
Diejenigen Varianten, die das gesamte XML-Dokument in den Speicher lesen, scheiden von vornherein für unsere Zwecke aus. Die Streaming-APIs hingegen können zwar Dokumente jeder Größe verarbeiten, bieten dafür aber meist nur einfache Basisfunktionen und sind daher umständlich zu benutzen.
Transformers2
Eine Variante, die nicht unmittelbar auf der Hand liegt, besteht im Einsatz eines XML-Transformationswerkzeugs. Zwar sind diese in erster Linie dazu gedacht, (vereinfacht gesagt) XML-Dokumente in andere XML-Dokumente umzuwandeln, doch durch die Anbindung einer allgemeinen Programmiersprache wie Java lassen sich solche XML-Transformatoren auch in Template-Engines oder Daten-Importierer verwandeln.
Leider basiert die vom W3C dafür standardisierte XML-Transformationssprache XSLT auf einem baumbasierten Modell der Eingabedaten und disqualifiziert sich damit genauso wie die DOM-API für unseren Anwendungsfall. Als Alternative entstand vor einigen Jahren die Sprache STX, die eine Transformation von XML als Datenstrom ermöglicht.
Ein typischer Transformationsprozess (egal ob mit XSLT oder STX) lässt sich folgendermaßen veranschaulichen:

Alle in Java implementierten Transformatoren bieten (in der Regel proprietäre) Schnittstellen, um aus der in XSLT oder STX realisierten Transformationslogik auf Java-Klassen und -Methoden zugreifen zu können. Wenn man die Java-Schnittstelle nun dazu nutzt, die Rolle der Datenquelle bzw. -senke vollständig zu übernehmen, erhält man die folgenden Grenzfälle, die letztendlich mit einer XML-Transformation nur noch formal zu tun haben.
Transformation ohne XML-Quelldokument:

In diesem Fall stammen sämtliche Daten aus der Java-Anwendungslogik – die Transformationsregeln fungieren hier damit als XML-Template. Da es mittlerweile unzählige Template-Engines für HTML und XML gibt (u.a. besitzt unser Web-Framework Catapult einen sehr leistungsstarken Template-Mechanismus), besteht kein Anlass, hier XSLT oder STX einzusetzen.
Transformation ohne XML-Zieldokument:

Hier werden sämtliche eingelesenen Daten über die Java-Schnittstelle an die Anwendung gegeben, ohne dass dabei ein Ausgabedokument erzeugt wird. Dies entspricht damit genau unserem Anliegen, den Import von XML-Quelldaten zu ermöglichen.
Ein einfaches Code-Beispiel
Das folgende Beispiel benutzt die STX-Implementierung Joost, die sehr einfach über die seit Java 1.4 standardisierte Transformations-API in Java-Anwendungen eingebunden werden kann. Als externer Stylesheet-Parameter wird hier unter dem Namen service ein Service-Objekt an die Transformation übergeben, dessen Methoden damit innerhalb der STX-Regeln verfügbar sind.
InputStream xmlStream = ... // XML-Eingabedokument
Service service = ... // Beispielservice
TransformerFactory factory = new net.sf.joost.trax.TransformerFactoryImpl();
Transformer transformer = factory.newTransformer(
new StreamSource(getClass().getResourceAsStream("address.stx")));
transformer.setParameter("service", service);
transformer.transform(
new StreamSource(xmlStream),
new SAXResult(new DefaultHandler())); // Dummy-Result
Für den Import von Daten hat sich ein einfaches Muster bewährt. In unserem Beispiel besteht die XML-Eingabedatei aus einfachen Adressdaten. Für jedes gefundene address-Element wird ein entsprechendes Address-Objekt konstruiert und mit Hilfe des übergebenen Service abgespeichert.
<?xml version="1.0"?>
<stx:transform xmlns:stx="http://stx.sourceforge.net/2002/ns"
xmlns:s="java:de.mindmatters.example.Service"
xmlns:a="java:de.mindmatters.example.Address"
version="1.0">
<!-- externer Service-Parameter -->
<stx:param name="service" required="yes" />
<stx:variable name="address" />
<stx:template match="addresses">
<stx:process-children />
</stx:template>
<stx:template match="address">
<!-- zuerst ein neues Objekt konstruieren und zuweisen -->
<stx:assign name="address" select="a:new()" />
<!-- dann die Kindelemente verarbeiten -->
<stx:process-children />
<!-- und schließlich das Objekt an den externen Service übergeben -->
<stx:value-of select="s:saveAddress($service, $address)" />
</stx:template>
<stx:template match="street">
<!-- Member "street" setzen -->
<stx:value-of select="a:setStreet($address, .)" />
</stx:template>
<!-- usw für die anderen address-Kinder -->
<!-- für alle "vergessenen" Elemente eine Warnung ausgeben -->
<stx:template match="*">
<stx:message>
Unprocessed element: <stx:value-of select="concat(name(), ': ', .)" />
</stx:message>
</stx:template>
</stx:transform>
Anmerkung: Die STX-Anweisungen <stx:value -of select="..."/> dienen hier nur als Vehikel, um auf die Java-Methoden innerhalb eines STXPath-Ausdrucks zugreifen zu können. Eine eventuelle Ausgabe würde am Ende einfach ignoriert werden.
Foto von mdverde via flickr

Kommentare zu diesem Eintrag sind geschlossen.