• +49-(0)721-402485-12
Ihre Experten für XML, XQuery und XML-Datenbanken

Data-Binding

Bei dem, was ich bisher beschrieben habe, und bei der Reihenfolge, in der ich es getan habe, habe ich versucht, die ganze Skala zu durchlaufen. Als ich anfing, im Kapitel Einstieg in XML über Java zu reden, hatten Sie noch die vollständige Kontrolle. SAX leimte ein dünnes Furnier über XML und lieferte ein allgemeines Framework zum Schreiben von einer Menge von Callbacks. Als ich zu DOM und JDOM überging, bekamen Sie ein wenig mehr Hilfe, verloren aber gleichzeitig ein wenig Kontrolle. Diese Baum-basierten In-Memory-Modelle waren sehr angenehm, allerdings verliert man dabei ein wenig Performance. Als wir zu JAXP übergingen, wurden Sie in der Befehlskette noch weiter nach oben gesetzt, erhielten aber eine zusätzliche Abstraktionsebene. An diesem Punkt hatten Sie noch ein wenig Kontrolle, arbeiteten aber weiterhin strikt in XML.

Dann habe ich hochgeschaltet, und wir haben Web Publishing Frameworks, XML-RPC, SOAP, B2B- und Web Services betrachtet. Das war ein großer Schritt von dem Ansatz des »Alles-selber-Machens« in der ersten Hälfte des Buches. Sie hatten eine sehr viel größere Spezialisierung, und das Ganze war einfacher einzusetzen, jedoch haben Sie die Kontrolle über XML eingebüßt und sahen stellenweise gar kein XML mehr (wie in XML-RPC). Dabei könnte es Sie eventuell gestört haben (wie es mich stört), daß Sie sich die Hände nicht mehr so richtig schmutzig machen konnten.

Es ist nicht möglich, die kleinen Optimierungen an den Werten eines XML-Dokuments vorzunehmen, wenn man mit SOAP anstelle von beispielsweise SAX oder JDOM arbeitet. Jedoch war die Annehmlichkeit von WSDL nett und hatte Vorteile gegenüber der Möglichkeit, den Namen eines Elements in DOM falsch einzugeben. Kurz gesagt, ich wünschte mir einen goldenen Mittelweg.

Das Data-Binding ist dieser Mittelweg, und ich möchte Sie zunächst zu einer kurzen Reise an die beiden extremen Enden des Spektrums mitnehmen, bevor ich etwas dazu sage. In diesem Kapitel möchte ich Ihnen zeigen, wie Sie die große Macht eines Low-level-APIs mit der Annehmlichkeit eines XML-basierten Frameworks (wie SOAP) kombinieren können. Das ist etwas, das wahrscheinlich jeder an irgendeinem Punkt einmal nützlich finden wird und das viele für unterschiedlichste Aufgaben nutzen können.

Zunächst zeige ich die Hauptprinzipien, die der Grund dafür sind, daß das Data-Binding überhaupt funktionieren kann. Das wird ein allgemeiner Überblick, da die verschiedenen Data-Binding-Pakete jeweils auf eine eigene Methodik zurückgreifen. Wenn Sie dann erstmal Grund unter den Füßen spüren, werde ich Sie auf eine schnelle Rundfahrt zum Thema Data-Binding mitnehmen. Auf dieser Rundfahrt sehen wir uns zwei Open Source-Lösungen und das API JAXB an, das derzeit von Sun entwickelt wird. Also – anschnallen und eintauchen.

Grundprinzipien

Bevor wir in spezifische Pakete und Frameworks eintauchen, die sich mit Data-Binding befassen, brauchen Sie ein Grundverständnis davon, was XML-Data-Binding ist. Das erscheint ziemlich einfach, so daß Sie in kürzester Zeit mit dem Programmieren beginnen können. Als erstes nehmen wir ein einfaches altes XML-Dokument wie das in Beispiel 15-1 gezeigte.

Beispiel 15-1: Katalog selbstproduzierter Bänder

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

<catalog xmlns="http://www.homespuntapes.com">
  <item id="VD-DOK-GT01" level="4">
    <title>Doc's Gitarren-Fingerpicking und -Flatpicking</title>
    <teacher>Doc Watson</teacher>
    <guest>Pete Seeger</guest>
    <guest>Mike Seeger</guest>
    <guest>Jack Lawrence</guest>
    <guest>Kirk Sutphin</guest>
    <description>Doc Watson, ein wahrer Meister des traditionellen Gitarrenspiels, 
      stellt einige der meistgewünschten Fingerpicking- und Flatpicking-Titel aus
      seinem riesigen Repertoire für Gitarristen jeden Könnens vor.</description>
  </item>
  <item id="VD-WLX-GT01" level="4">
    <title>Die Gitarre von David Wilcox</title>
    <teacher>David Wilcox</teacher>
    <description>Kreieren Sie Ihre eigenen Sounds mit reichen, klingenden Melodien!
      David zeigt Ihnen, wie Sie eigene Stücke entwickeln.</description>
  </item>
  <item id="VD-THI-MN01" level="3">
    <title>Grundlegende Techniken für das Mandolinenspiel</title>
    <teacher>Chris Thile</teacher>
    <description>Hier ist eine Lektion, die Mandolinenspieler unabhängig von ihren
      Fähigkeiten erfreuen und anregen wird.</description>
  </item>
  <item id="CDZ-SM01" level="4">
    <title>Sam Bush lehrt Mandolinen-Repertoire und -Techniken</title>
    <teacher>Sam Bush</teacher>
    <description>Lernen Sie komplette Solos zu acht traditionellen und modernen
      Stücken, jedes vollgepackt mit Licks, Runs und musikalischen Variationen.
    </description>
  </item>
</catalog>

In früheren Kapiteln haben Sie gelernt, wie Sie mittels SAX, DOM, JDOM und JAXP auf die Inhalte dieses Dokuments zugreifen können. Sie konnten sowohl die Struktur (die Namen und die Reihenfolge der Elemente, Attribute und anderer lexikalischer Konstrukte) als auch den Inhalt (die eigentlichen Daten) lesen und modifizieren. Jedoch benötigt man oft keinen Zugriff auf die Struktur des Dokuments und möchte nur mit den enthaltenen Daten arbeiten.

Sollte das der Fall sein, ist es Overkill und außerdem ein wenig langweilig, Code zu schreiben, der das Dokument parst und die Daten extrahiert und in eine Form bringt, die Sie benutzen können. Es wäre wesentlich schöner, ein Programm ablaufen zu lassen (oder ein API ... na, klingelt da was?), das das für Sie tut und nutzbare Java-Klassen produziert. Das ist übrigens genau das, was Data-Binding tut. Data-Binding vereinigt drei unterschiedliche Prozesse, die hintereinander ablaufen können, in anderer Reihenfolge oder völlig unabhängig voneinander. Ich werde jeden im einzelnen betrachten.

Klassengenerierung

Der erste Prozeß – die Klassengenerierung – bietet Mittel, ein XML-Dokument nach Java zu konvertieren. Wenn das Data-Binding ein XML-Dokument in eine Java-Repräsentation umwandelt, versucht es, Zugriff nur auf die Daten zu gewähren. Gleichzeitig wird versucht, den Daten eine gewisse Bedeutung beizumessen. Das geschieht durch die Erzeugung entsprechender Accessor- und Mutator1-Methoden wie getItem( ) und setTeacher( ) statt getElement( ) und setAttribute( ). Das führt dazu, daß man sich beim Arbeiten mit Dokumenten wie in Beispiel 15-1 weniger auf Java als vielmehr auf die Geschäftslogik konzentrieren kann, was unbestreitbar eine gute Sache ist. Jedoch müssen diese wundervollen Klassen existieren, bevor ein XML-Dokument in eine Instanz umgewandelt werden kann, und damit sind wir wieder bei der Klassengenerierung.

Die Klassengenerierung ist ein Prozeß, der eine Menge von XML-Beschränkungen nimmt und daraus Java-Klassen (und möglicherweise Interfaces) erzeugt. Stellen Sie sich das Ganze einfach so vor: XML-Beschränkungen (wie die in DTDs oder XML Schemas) sind äquivalent zu Java-Klassen-Definitionen. Sie definieren die Art und Weise, in der Daten repräsentiert werden. Auf der anderen Seite ist ein XML-Dokument äquivalent zu einer Instanz dieser Klassen, da es einfach Daten enthält, die den Kontrakt erfüllen, der durch die Beschränkungen des Dokuments definiert wurde. Lesen Sie diesen Abschnitt jetzt noch einmal langsam durch, und Sie haben es.

Die Data-Binding-Frameworks, über die ich in diesem Kapitel sprechen werde, verfügen alle über Mittel, die Beschränkungen eines Dokuments zu repräsentieren (normalerweise durch eine DTD oder ein XML Schema; es gibt aber auch andere Möglichkeiten, auf die ich an entsprechender Stelle noch einmal eingehen werde). Diese Beschränkungen können dann einem Werkzeug zur Klassengenerierung übergeben werden, und Sie bekommen Java-Sourcecode, der bereit zum Kompilieren ist. Dieser Code kann kompiliert dazu benutzt werden, Instanzen aus einem XML-Dokument zu erzeugen. Damit kommt man zu einem Prozeß wie dem in Abbildung 15-1 gezeigten.

Abbildung 15-1: Klassengenerierung bei XML-Data-Binding
Klassengenerierung bei XML-Data-Binding

Beachten Sie, daß das Endprodukt hier konkrete Klassen, Interfaces, Interfaces und Implementierungen und jede nur denkbare Kombination von Java-Klassen sein können. Im Fall von Beispiel 15-1 (angenommen, die Beschränkungen sind in passender Form dargestellt) könnte das ein Interface Catalog wie folgt ergeben:

public interface Catalog {
    public List getItemList(  );
    public void addItem(Item item);
    public void setItemList(List items);
}

Außerdem könnte ein Interface Item wie folgt entstehen:

public interface Item {
    public String getID(  );
    public void setID(String id);
    public int getLevel(  );
    public void setLevel(int level);

    public String getTitle(  );
    public void setTitle(String title);
    public String getTeacher(  );
    public void setTeacher(String teacher);
    public List getGuests(  );
    public void addGuest(String guest);
    public void setGuestList(List guests);
    public String getDescription(  );
    public void setDescription(  );
}

Das ist schon etwas nützlicher, als Hunderte Zeilen an SAX-Callbacks zu schreiben. Es macht die Arbeit mit einem Dokument zum Kinderspiel, anstatt zu einer Prüfung Ihrer Java- und XML-API-Kenntnisse auszuarten. Das sind nur Beispiele, die nicht notwendigerweise dafür repräsentativ sind, was Sie mit den APIs in diesem Kapitel erhalten. Ich werde Ihnen jedoch in den folgenden Abschnitten genau zeigen, wie die einzelnen APIs angewendet werden, und Sie wissen lassen, was Sie erwarten können.

Unmarshalling

Vergessen Sie nicht, daß Sie die erzeugten Klassen immer noch nicht recht anwenden können. Sie könnten sicher ein existierendes Low-level-API nehmen, damit das XML-Dokument lesen, die Daten extrahieren, Instanzen der Java-Klassen erzeugen und diese mit den Daten füllen. Aber Data-Binding liefert all das bereits fix und fertig, also warum sollten Sie sich darum kümmern? Tatsächlich modellieren Data-Binding-Frameworks genau diesen Prozeß. In diesem Kontext ist das Unmarshalling der Prozeß der Umwandlung eines XML-Dokuments in die Instanz einer Java-Klasse.

Ich habe eine Menge Verwirrung um den Begriff Marshalling/Unmarshalling gesehen und war stellenweise sogar daran beteiligt. Ich benutze den Begriff konform zu Suns aktueller Version der JAXB-Spezifikation, die sicher das Standardvokabular werden wird. In dieser Spezifikation steht Marshalling für die Richtung Java nach XML und Unmarshalling für die Richtung XML nach Java. Ich würde, wenn ich Sie wäre, ebenfalls bei diesen Definitionen bleiben.

Das sieht dann ziemlich einfach aus; Sie bekommen ein XML-Dokument, übergeben es an ein Werkzeug oder eine Klasse in Ihrem Data-Binding-Framework und bekommen ein Java-Objekt zurück. Das ist normalerweise eine Klasseninstanz des Top-level-Java-Objekts, das Ihr Dokument repräsentiert. Wiederum bezogen auf Beispiel 15-1 würden Sie also eine Instanz der Klasse Catalog erhalten. Normalerweise müssen Sie von java.lang.Object zu der erwarteten Klasse casten, da das Framework naturgemäß nichts über die Natur Ihrer Klassen wissen kann (da sie ja generiert wurden).

Nach dem Cast können Sie mit dem Objekt als Catalog statt als einem XML-Dokument weiterarbeiten. Sie können die verschiedenen Accessor- und Mutator-Methoden benutzen, um mit den Daten zu arbeiten. Wenn Sie die Arbeit mit den Daten beendet haben und bereit sind, die Daten wieder zu XML zu schicken, wo Sie einst herkamen, müssen Sie das Marshalling bemühen.

Marshalling

Das Marshalling ist das genaue Gegenteil des Unmarshalling. Es bezeichnet den Prozeß der Umwandlung eines Java-Objekts und darin enthaltener Objekte in eine XML-Repräsentation. In vielen Fällen ist das Marshalling Teil wiederholter Transformationen zwischen Java und XML und mit entsprechendem Unmarshalling gepaart. Sehen Sie sich dazu das Beispiel eines typischen Kontrollflusses in Abbildung 15-2 an.

Abbildung 15-2: XML-Data-Binding-Kontrollfluß
XML-Data-Binding-Kontrollfluß

Es gibt zwei unterschiedliche Herangehensweisen zum Marshalling eines Java-Objekts. Die erste ist der Aufruf der Methode marshal( ) eines Objekts; diese Methode wird normalerweise mit den Accessor- und Mutator-Methoden während der Klassengenerierung erzeugt. Diese Methode ruft rekursiv die Methode marshal( ) für jedes enthaltene Objekt auf, bis als Resultat ein vollständiges XML-Dokument entsteht. Sie sollten beachten, daß dieses Resultat dem Original nicht notwendigerweise gleicht. Wenn Sie die Dateinamen beim Marshalling ändern, können Sie sich plötzlich einer großen Anzahl archivierter XML-Dokumente gegenübersehen.

Ein anderer Ansatz zum Marshalling – und der, den ich persönlich bevorzuge – ist eine eigene Klasse, die das Marshalling durchführt. Anstatt die Methode marshal( ) für ein generiertes Objekt aufzurufen, wird die Methode marshal( ) dieser Klasse benutzt, wobei ihr das entsprechende Objekt mitgegeben wird. Das ist nützlich, da prinzipiell die gleichen Abläufe wie oben gezeigt stattfinden.

Es erlaubt es jedoch andererseits auch, Objekte einzubeziehen, die nicht durch das Unmarshalling eines XML-Dokuments entstanden sind. Stellen Sie sich das einfach folgendermaßen vor: Data-Binding stellt – so benutzt – ein Persistenz-Framework dar. Jedes Objekt mit Bean-ähnlichen Eigenschaften (setXXX( ) und getXXX( )) kann einfach nach XML konvertiert werden! Man bekommt die Macht des Data-Bindings in Kombination mit der Flexibilität der Persistenz. Das ist eine nützliche Kombination und wird auch von einigen Frameworks unterstützt, über die ich in diesem Kapitel spreche.

Ich erkenne, daß das vielleicht ein wenig vage und konfus erscheint, wenn Sie noch nichts mit Data-Binding zu tun hatten – fast wie ein Gespräch über Chemie. Ich lasse lieber ein paar Sachen explodieren (na ja ... Sie wissen schon!) und daher zeige ich Ihnen die Benutzung einiger Data-Binding-Frameworks im Rest dieses Kapitels. Da ich vier davon abdecken möchte, werden die Beispiele nicht übermäßig komplex; statt dessen werde ich mich darauf konzentrieren zu zeigen, wie die Klassengenerierungs-, die Marshalling- und die Unmarshalling-Fähigkeiten jedes Frameworks eingesetzt werden. Das sollte mehr als ausreichen, damit Sie starten können.

Anwendungsbereiche

Als letzten Teil der Erklärung dessen, was Data-Binding ist und warum es eingesetzt werden sollte, werde ich nun eine kleine Auswahl an Einsatzgebieten präsentieren. Einige davon sind am besten für die Lower-level-APIs wie SAX oder DOM geeignet, und einige sind perfekt für das Data-Binding. In Tabelle 15-1 sind jeweils ein verbreiteter Anwendungsbereich, der passende API-Typ und eine kurze Begründung meiner Entscheidung aufgeführt. Dies sollte Ihnen bei der Einordnung von Data-Binding in die Java-Landschaft helfen.

Tabelle 15-1: API-Anwendungsbereiche
AnwendungsbereichGut passende APIBegründung
XML-IDEDOM oder JDOMBaum-basierte Ansichten von XML wie in einer IDE, lehnt sich eng an die Darstellung in DOM und JDOM an.
XML-Messaging ServerSAXDa Geschwindigkeit der wichtigste Faktor ist und SAX das schnellste Stream-basierte Lesen von Messages erlaubt.
KonfigurationsdatenData-BindingDer Inhalt und nicht so sehr die Struktur ist wichtig. Data-Binding spart Zeit und fügt Bequemlichkeit zum Lesen von Konfigurationsdaten hinzu.
XML-TransformationenDOM oder JDOMStruktur wie auch Inhalt zu ändern erfordert Modifikationen an Inhalt (schließt SAX aus) und Struktur (schließt Data-Binding aus).
XML-Messaging ClientData-BindingWenn das Format der Message vorher bekannt ist, kann der Client von leichten Java-Objekten profitieren, die das Data-Binding bietet.

Das sind natürlich nur einige verbreitete XML-Anwendungen, aber sie sollten Ihnen eine Ahnung davon vermitteln können, wann es angezeigt ist, ein Lower-level- und wann ein Higher-level-API zu benutzen.

Castor

Das erste Data-Binding-Framework, das ich näher vorstellen möchte, ist Castor – online verfügbar unter http://castor.codehaus.org. Dieses Framework existiert schon eine ganze Weile, und die aktuellste Version zum Zeitpunkt der Entstehung dieses Buches war 0.92. Zunächst möchte ich klarstellen, daß Castor wesentlich mehr bietet als Data-Binding. Das Paket bietet Bindings in Java nicht nur von XML; Sie können genauso mit LDAP-Objekten, OQL zum Mapping von SQL-Abfragen auf Objekte und Java Data Objects (JDO) arbeiten. JDO ist eine neue Spezifikation von Sun, die sich mit Java-zu-RDBMS-Persistenz beschäftigt (RDBMS steht für relationale Datenbank-Management-Systeme). Da das hier aber ein XML-Buch ist, werde ich nur über die XML-Bindings sprechen.

Installation

Um Castor zu nutzen, werden Sie zunächst ein Release von der Download-Seite http://castor.codehaus.org/download.html herunterladen müssen. Diese Seite hat einen Link zu der Exolab-FTP-Site (Sie können natürlich auch manuell ftp machen, so wie ich) und listet die verfügbaren Dateien auf. Ich würde empfehlen, sich das vollständige Release castor-0.9.2.zip oder castor-0.9.2.tgz zu holen (falls Sie später auch ein wenig mit OQL oder JDO herumspielen möchten). Packen Sie die jar-Dateien aus diesem Archiv aus, fügen Sie sie zum Klassenpfad hinzu, und schon kann es losgehen.2

In diesem und den folgenden Beispielen habe ich angenommen, daß Sie zusätzlich zu den hier erwähnten Bibliotheken immer noch einen SAX-konformen Parser, wie zum Beispiel Xerces, in Ihrem Klassenpfad haben. Ist dem nicht so, fügen Sie die Datei xerces.jar oder die entsprechende jar-Datei Ihres Parsers zusätzlich zu denen des benutzten Data-Binding-Frameworks zum Klassenpfad hinzu.

Quelltextgenerierung

Castor kann Java-Klassen erzeugen, wenn ein bestimmter Satz von Beschränkungen verwendet wird. Dazu wird ein XML Schema mit den Beschränkungen benötigt. Beispiel 15-2 stellt ein solches Schema für das in Beispiel 15-1 gezeigte Dokument dar.

Beispiel 15-2: XML Schema für Beispiel 15-1 (zur Benutzung mit Castor)

<?xml version="1.0"?>

<schema xmlns="http://www.w3.org/2000/10/XMLSchema"
        targetNamespace="http://www.homespuntapes.com">
  <element name="catalog">
    <complexType>
      <sequence>
        <element ref="item" minOccurs="1" maxOccurs="unbounded" />
      </sequence>
    </complexType>
  </element>

  <element name="item">
    <complexType>
      <sequence>
        <element name="title" type="string" />
        <element name="teacher" type="string" />
        <element name="guest" type="string" minOccurs="0" maxOccurs="unbounded" />
        <element name="description" type="string" />
      </sequence>
      <attribute name="id" type="string" />
      <attribute name="level">
        <simpleType>
          <restriction base="integer"> 
            <enumeration value="1" />
            <enumeration value="2" />
            <enumeration value="3" />
            <enumeration value="4" />
            <enumeration value="5" />
          </restriction>
        </simpleType>
      </attribute>
    </complexType>
  </element>
</schema>

Natürlich können Sie auch eigene XML Schemas, die Sie gerne ausprobieren möchten, benutzen. Solange sie der XML Schema-Spezifikation entsprechen, sollten sie mit allen Beispielen in diesem Abschnitt funktionieren.

Zumindest zum Zeitpunkt der Entstehung dieses Buches hat Castor nur die XML Schema Candidate Recommendation vom Oktober 2000 unterstützt und nicht die finale Version dieser Spezifikation. Daher müssen Sie unter Umständen einige kleinere Änderungen in Ihren Schemas vornehmen, um nicht in Konflikt mit dieser Version zu geraten. Ich hoffe, daß das Framework zu dem Zeitpunkt, da Sie dies lesen, aufgeholt hat.

Wenn Sie das XML Schema definiert haben, sind Sie in der Lage, die Klassen aus den Beschränkungen zu generieren. Ich habe mein Schema aus Beispiel 15-1 catalog.xsd genannt, wie Sie in den folgenden Anweisungen sehen werden.

Hat man das Schema, ist die Generierung von Klassen mit Castor ein Kinderspiel. Sie müssen dazu, wie im Folgenden gezeigt, die Klasse org.exolab.castor.builder.SourceGenerator benutzen:

java org.exolab.castor.builder.SourceGenerator -i castor/catalog.xsd
    -package javaxml2.castor

In diesem Beispiel lasse ich das Kommando mit meinem Schema im Unterverzeichnis castor/ ablaufen. Ich gebe seinen Standort mit dem Kommandozeilenschalter »-i« und das Package, zu dem die generierten Klassen gehören sollen, mit »-package« an. Es gibt noch einen ganzen Schwung anderer Optionen, die Sie sich anschauen können, wenn Sie die Klasse ganz ohne Parameter eingeben. Die Klasse wird dann alle Flags und Optionen ausspucken.

Wenn das Kommando ausgeführt wurde (Sie werden Fehler sehen, wenn Ihr Schema Probleme hat), werden Sie ein Verzeichnis erhalten, das dem von Ihnen angegebenen Package entspricht. In meinem Beispiel war das ein Verzeichnis javaxml2 und darin ein Verzeichnis castor. In diesem Verzeichnis wiederum befanden sich die Dateien Catalog.java und CatalogDescriptor.java sowie Item.java und ItemDescriptor.java. In den meisten Fällen werden Sie nur mit den jeweils ersten Dateien jedes Paars arbeiten müssen.

Sie sollten außerdem noch ein zusätzliches Verzeichnis mit Namen types sehen, das einige weitere Dateien enthält. Diese werden wegen dem benutzerdefinierten Typ für das Attribut »level« im Schema angelegt. Das Ergebnis ist eine Klasse LevelType. Da nur fünf Werte erlaubt sind, muß Castor Klassen generieren, um das zu behandeln. Die Arbeit mit solchen Typ-Klassen ist sehr schmerzhaft, da keine Möglichkeit existiert, zum Beispiel so etwas zu tun:

// Erzeugen einer neuen Instanz mit dem Wert "1"
LevelType levelType = new LevelType(1);

Statt dessen müssen Sie zuerst den Wert in einen String umwandeln. Danach können Sie die statische Methode valueOf( ) benutzen, um eine Instanz von LevelType mit dem korrekten Wert zu erhalten:

LevelType levelType = LevelType.valueOf("1");

Wenn Sie sich einmal daran gewöhnt haben, ist das natürlich keine große Sache mehr. Wenn Sie das trotzdem ein wenig verwirrt, werden Sie im nächsten Abschnitt hoffentlich klarer sehen, wo diese Klasse in der Praxis benutzt wird. Sie können die Typ-Dateien wie auch die anderen von Castor generierten Dateien mit diesem einfachen Kommando kompilieren:

javac -d . javaxml2/castor/*.java javaxml2/castor/types/*.java

Zu diesem Zeitpunkt verfügen Sie über Klassen, die sofort benutzbar sind. Ich werde Ihnen den Sourcecode zu diesem Klassen hier nicht zeigen, da er sehr lang ist (und Sie ihn sich selbst anschauen können). Ich habe statt dessen die wichtigsten Methoden der Klasse Catalog hier aufgeführt, damit Sie einen Eindruck davon erhalten, was Sie erwartet:

package javaxml2.castor;

public class Catalog {

    // neues Item hinzufügen
    public void addItem(  );
    // Items als Enumeration holen
    public Enumeration enumerateItem(  );
    // alle Items holen
    public Item[] getItem(  );
    // Anzahl der Items abfragen
    public getItemCount(  );
}

Beachten Sie, daß man Items hinzufügen und auch über alle Items iterieren kann. Die Namen zweier Methoden – enumerateItem( ) und getItem( ) – sind ein bißchen komisch, passen Sie also auf. Ich erwartete nicht, daß getItem( ) ein Array zurückgibt, und suchte zuerst nach getItems( ) oder getItemList( ). Haben Sie diese generierten Klassen erst einmal, können Sie sie in eigenen Anwendungen benutzen.

Marshalling und Unmarshalling

Nachdem Sie die von Castor generierten Klassen kompiliert haben, sollten Sie sie zu Ihrem Klassenpfad hinzufügen. Nun können Sie sie in eigenen Anwendungen einsetzen. Beispiel 15-3 zeigt ein einfaches HTML-Formular, das es einem Benutzer gestattet, Informationen über ein neues Item einzugeben.

Beispiel 15-3: HTML-Formular zum Hinzufügen von Items

<HTML>
 <HEAD><TITLE>Neues Item zum Katalog hinzuf&uuml;gen</TITLE></HEAD>
 <BODY>
  <H2 ALIGN="CENTER">Neues Item hinzuf&uuml;gen</H2>
  <P ALIGN="CENTER">
   <FORM ACTION="/javaxml2/servlet/javaxml2.AddItemServlet" METHOD="POST">
    <TABLE WIDTH="80%" CELLSPACING="3" CELLPADDING="3" BORDER="3">
     <TR>
      <TD WIDTH="50%" ALIGN="right"><B>Item ID:</B></TD>
      <TD><INPUT TYPE="text" NAME="id" /></TD>
     </TR>
     <TR>
      <TD WIDTH="50%" ALIGN="right"><B>Item Level:</B></TD>
      <TD><INPUT TYPE="text" NAME="level" SIZE="1" MAXLENGTH="1" /></TD>
     </TR>
     <TR>
      <TD WIDTH="50%" ALIGN="right"><B>Titel:</B></TD>
      <TD><INPUT TYPE="text" NAME="title" SIZE="20" /></TD>
     </TR>
     <TR>
      <TD WIDTH="50%" ALIGN="right"><B>Lehrer:</B></B></TD>
      <TD><INPUT TYPE="text" NAME="teacher" /></TD>
     </TR>
     <TR><TD COLSPAN="2" ALIGN="CENTER"><B>G&auml;ste:</B></TD></TR>
     <TR>
      <TD COLSPAN="2" ALIGN="CENTER"><INPUT TYPE="text" NAME="guest" /></TD>
     </TR>
     <TR>
      <TD COLSPAN="2" ALIGN="CENTER"><INPUT TYPE="text" NAME="guest" /></TD>
     </TR>
     <TR>
      <TD COLSPAN="2" ALIGN="CENTER"><INPUT TYPE="text" NAME="guest" /></TD>
     </TR>
     <TR>
      <TD COLSPAN="2" ALIGN="CENTER"><INPUT TYPE="text" NAME="guest" /></TD>
     </TR>
     <TR><TD COLSPAN="2" ALIGN="CENTER"><B>Beschreibung:</B></TD></TR>
     <TR>
      <TD COLSPAN="2" ALIGN="CENTER">
       <TEXTAREA NAME="description" COLS="30" ROWS="10"></TEXTAREA>
      </TD>
     </TR>
     <TR>
      <TD COLSPAN="2" ALIGN="CENTER"><INPUT TYPE="submit" value="Add Item" /></TD>
     </TR>
    </TABLE>
   </FORM>
  </P>
 </BODY>
</HTML>

Sie sollten eigentlich Tomcat oder eine andere Servlet-Engine noch von den vorhergehenden Kapiteln laufen haben, daher werde ich diesbezüglich hier nicht ins Detail gehen. Fügen Sie dieses Formular zu einer Ihrer Web-Applikationen hinzu, geben Sie dann das Servlet aus Beispiel 15-4 ein, und kompilieren Sie es.

Beispiel 15-4: Das AddItemServlet für Castor

package javaxml2;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

// Castor-Klassen
import org.exolab.castor.xml.Marshaller;
import org.exolab.castor.xml.Unmarshaller;

// Von Castor generierte Klassen
import javaxml2.castor.Catalog;
import javaxml2.castor.Item;
import javaxml2.castor.types.LevelType;

public class AddItemServlet extends HttpServlet {

    private static final String CATALOG_FILE =
        "c:\\java\\tomcat\\webapps\\javaxml2\\catalog.xml";

    public void doPost(HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException {

        PrintWriter out = res.getWriter(  );
        res.setContentType("text/html");

        // Input-Parameter holen
        String id = req.getParameterValues("id")[0];
        String levelString = req.getParameterValues("level")[0];
        String title = req.getParameterValues("title")[0];
        String teacher = req.getParameterValues("teacher")[0];
        String[] guests = req.getParameterValues("guest");
        String description = req.getParameterValues("description")[0];

        // Neues Item erzeugen
        Item item = new Item(  );
        item.setId(id);
        item.setLevel(LevelType.valueOf(levelString));
        item.setTitle(title);
        item.setTeacher(teacher);
        if (guests != null) {
            for (int i=0; i<guests.length; i++) {
                if (!guests[i].trim(  ).equals("")) {
                    item.addGuest(guests[i]);
                }
            }
        }
        item.setDescription(description);

        try {
            // aktuellen Katalog laden
            File catalogFile = new File(CATALOG_FILE);
            FileReader reader = new FileReader(catalogFile);
            Catalog catalog = 
                (Catalog)Unmarshaller.unmarshal(Catalog.class, reader);

            // Item hinzufügen
            catalog.addItem(item);

            // Schreiben des modifizierten Katalogs
            FileWriter writer = new FileWriter(catalogFile);
            Marshaller.marshal(catalog, writer);

            out.println("Item hinzugefügt.");
        } catch (Exception e) {
            out.println("Fehler Lesen/Schreiben Katalog: " + e.getMessage(  ));
        } finally {
            out.close(  );
        }
    }

    public void doGet(HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException {

        doPost(req, res);
    }
}

Dieses Servlet akzeptiert die Parameter aus dem Formular in Beispiel 15-3. Es liest zunächst das XML, das den aktuellen Katalog repräsentiert (catalog.xml genannt und ebenfalls in dem Verzeichnis meiner Web-Applikation untergebracht). Zu diesem Zeitpunkt muß das Servlet auf den aktuellen Katalog zugreifen; natürlich könnte ich da einen Klumpen SAX-Code schreiben, aber warum sollte ich? Castor macht diese Arbeit sehr gut. Ich benutze einen FileReader, um Lese-Zugriff auf das XML-Dokument zu erhalten, und einen FileWriter für den Schreibzugriff. Um den Rest kümmert sich Castor.

Bekommt das Servlet Werte von dem Formular, legt es zunächst eine neue Instanz der Klasse Item an (wozu die von Castor generierten Klassen benutzt werden) und setzt die verschiedenen Werte in dieser Instanz. Sie werden bemerken, daß das Servlet, da »level« ein benutzerdefinierter Typ ist (Sie erinnern sich an die Erörterung weiter oben?), die statische Methode LevelType.valueOf(String) benutzt, um den String-Wert für den Level des Items in die korrekte Instanz der Klasse LevelType zu schreiben. Das ist einer der kleinen Nachteile von Castor: Die generierten Klassen für benutzerdefinierte Typen sind ein wenig unhandlich, bis man sich an sie gewöhnt hat.

Hat das Servlet dann eine benutzbare Instanz der Klasse Item erzeugt, benutzt es die Klasse org.exolab.castor.Unmarshaller, um sich den aktuellen Katalog zu holen. Das könnte nicht einfacher sein – das Servlet übergibt die Klasse für das Unmarshalling und den Dateizugriff (mittels des schon genannten FileReaders). Das Ergebnis ist ein Java-Object, das dann auf den übergebenen Klassentyp gecastet wird. An dieser Stelle ist das Hinzufügen eines neuen Items ein Kinderspiel. Sie arbeiten mit Java statt XML und können einfach die Methode addItem( ) aufrufen, der Sie die vorher angelegte neue Instanz der Klasse Item übergeben. Anschließend wird der Prozeß umgekehrt.

Die statische Methode marshal( ) aus der Klasse Marshaller (in demselben Package wie sein Geschwisterchen Unmarshaller) wird benutzt, um die Instanz der Klasse Catalog mittels des FileWriters nach XML zurückzuschreiben. Ein Kinderspiel, oder nicht? Ist dieser Prozeß abgeschlossen, existiert ein neuer Eintrag in dem XML-Dokument (es ist möglich, daß Sie Ihre Servlet-Engine stoppen müssen, um darauf zuzugreifen), der ungefähr so aussieht:

<item id="CD-KAU-PV99" level="1">
  <title>Parking Lot Pickers, Vol. 3</title>
  <teacher>Steve Kaufman</teacher>
  <guest>Debbie Barbra</guest>
  <guest>Donnie Barbra</guest>
  <description>Dieses Video zeigt, was man im Bluegrass-Style spielen sollte,
   wenn der Gesang aufhört!</description>
 </item>

Und das ist auch schon alles. Es gibt noch einige Optionen mehr, die man mit dem Marshaller und dem Unmarshaller nutzen kann – schauen Sie dazu in die Dokumentation der API. Um das Ganze nachvollziehbarer zu machen, folgt hier noch eine Liste der Dateien und Verzeichnisse im Verzeichnis meiner Web-Applikation, die dafür sorgen, daß das Beispiel funktioniert:

$TOCAT_HOME/lib/xerces.jar
$TOMCAT_HOME/webapps/javaxml2/addItem.html
$TOMCAT_HOME/webapps/javaxml2/catalog.xml
$TOMCAT_HOME/webapps/javaxml2/WEB-INF/lib/castor-0.9.2.jar
$TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/AddItemServlet.class
$TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/Catalog.class
$TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/Catalog.class
$TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/
    CatalogDescriptor.class
$TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/
    CatalogDescriptor$1.class
$TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/Item.class
$TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/
    ItemDescriptor.class
$TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/
    ItemDescriptor$1.class
$TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/
    ItemDescriptor$2.class
$TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/
    ItemDescriptor$3.class
$TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/
    ItemDescriptor$4.class
$TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/
    ItemDescriptor$5.class
$TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/
    ItemDescriptor$6.class
$TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/types/
    LevelType.class
$TOMCAT_HOME/webapps/javaxml2/WEB-INF/classes/javaxml2/castor/types/
    LevelTypeDescriptor.class

Es gäbe noch sehr viel mehr zu Castor zu sagen, wie auch zu allen anderen Paketen, die ich hier vorstelle. Diese kurze Einführung war dazu gedacht, Ihnen einen sicheren Startpunkt zu verschaffen – die Dokumentation hilft Ihnen dann durch den Rest. Von Interesse ist bestimmt noch die Fähigkeit, Mappings zu definieren, die es Ihnen erlauben, ein XML-Element namens »item« in eine Java-Variable namens »inventory« zu konvertieren. Das erlaubt verschiedene Repräsentationen gleicher Daten in XML und Java und kann bei der Portierung alter Java-Klassen sehr helfen.

Stellen Sie sich vor, daß Sie eine alte Java-Klasse mittels Marshalling nach XML umwandeln und dann dieses XML-Dokument mittels Unmarshalling in eine Instanz der neuen Java-Klasse. In zwei einfachen Schritten ist es möglich, allen alten Java-Code in ein neues Format zu bringen! Ziemlich schick, oder? Auf der anderen Seite sind Castors fehlende Unterstützung für die aktuelle Schema-Recommendation und die Unmöglichkeit, neben Interfaces auch konkrete Klassen zu definieren, ein ziemlicher Nachteil. Probieren Sie es selbst aus, und sehen Sie, ob Sie sich damit anfreunden können. Jedes der Frameworks in diesem Kapitel hat Vor- und Nachteile, suchen Sie sich das aus, welches für Sie das richtige ist.

Zeus

Als nächstes ist das Data-Binding-Framework Zeus an der Reihe. Dieses Projekt ist unter Enhydra angesiedelt (http://www.enhydra.org) und basiert auf einigen Artikeln, die ich ursprünglich für die Leute von IBMs Developer Works (http://www.ibm.com/developerWorks) geschrieben habe. Es war das unmittelbare Resultat meines Bedürfnisses nach einer einfachen Lösung für das Data-Binding (zu dieser Zeit erschien Castor als viel zu komplex für ein einfaches Data-Binding-Problem). Seit damals hat es sich zu einem kompletten Projekt bei Enhydra entwickelt, an dem mehrere andere Leute arbeiten. Einen vollständigen Einblick können Sie unter http://zeus.enhydra.org erhalten. Ich werde dieselben Themen beleuchten wie auch bei Castor, damit Sie beides kompetent in eigenen Anwendungen benutzen können.

Installation

Zeus befindet sich noch immer in einem ziemlich frühen Stadium seiner Entwicklung (es existiert zwar schon eine Weile, aber es wird immer noch an der Architektur gearbeitet). Deswegen würde ich Ihnen nahelegen, die aktuellste Version von CVS zu holen, anstatt ein Binary herunterzuladen. Auf diese Weise können Sie sicher sein, die aktuellsten und großartigsten Features zu bekommen. Sie können komplette Anweisungen zum Herunterladen von Zeus über CVS auf der Website bekommen, aber kurz gesagt brauchen Sie dafür CVS (zum Beispiel unter http://www.cvshome.org). Benutzen Sie einfach das folgende Kommando:

cvs -d :pserver:anoncvs@enhydra.org:/u/cvs login

Geben Sie als Paßwort »anoncvs« ein. Danach schreiben Sie:

cvs -d :pserver:anoncvs@enhydra.org:/u/cvs co toolsTech/Zeus

Sie werden alles bekommen, was Sie brauchen. Wechseln Sie in das Zeus-Verzeichnis (das durch den CVS-Download erzeugt wurde), und erzeugen Sie die Binaries:

bmclaugh@GANDALF ~/dev/Zeus
$ ./build.sh

Oder unter Windows:

c:\dev\Zeus> build.bat

Sie sollten die Beispiele ebenfalls erzeugen, was Sie durch das Anhängen von »samples« an das Kommando erreichen:

bmclaugh@GANDALF ~/dev/Zeus
$ ./build.sh samples

Nun sollten Sie eine Datei zeus.jar im Verzeichnis build/ erhalten haben. Fügen Sie diese Datei, wie auch jdom.jar und xerces.jar, die sich beide im Verzeichnis lib/ befinden, zu Ihrem Klassenpfad hinzu. Wollen Sie DTDs zur Klassengenerierung benutzen, müssen Sie schließlich noch die Datei dtdparser113.jar zum Klassenpfad hinzufügen. Damit Sie die Beispiele benutzen können, muß dann noch das Verzeichnis build/classes angehängt werden, damit alles etwas einfacher wird. Mein Klassenpfad sieht dann folgendermaßen aus:

bmclaugh@GANDALF ~/dev/Zeus
$ echo $CLASSPATH
 /dev/Zeus/lib/jdom.jar:/dev/Zeus/lib/xerces.jar:/dev/Zeus/lib/dtdparser113.jar:
 /dev/Zeus/build/zeus.jar:/dev/Zeus/build/classes

Oder unter Windows:

c:\dev\Zeus> echo %CLASSPATH%
 c:\dev\Zeus\lib\jdom.jar;c:\dev\Zeus\lib\xerces.jar;
 c:\dev\Zeus\lib\dtdparser113.jar;c:\dev\Zeus\build\zeus.jar;
 c:\dev\Zeus\build\classes

Das ist alles. Haben Sie den Klassenpfad entsprechend gesetzt und das XML aus dem vorhergehenden Teil des Kapitels zur Hand, können Sie loslegen.

Klassengenerierung

Der Hauptunterschied zwischen Zeus und Frameworks wie Castor und sogar JAXB – dem Angebot von Sun – ist die Art und Weise der Klassengenerierung. Sehen Sie sich Abbildung 15-1 noch einmal an. Der Standardweg der Klassengenerierung nimmt eine Menge von Beschränkungen, liest sie ein und erzeugt eine Menge Java-Sourcecode. Obwohl das sehr gut funktioniert, ist es doch schwierig, Unterstützung für andere Arten von Beschränkungen, wie zum Beispiel DTDs oder neuere Schema-Alternativen wie Relaxx oder TREX, hinzuzufügen. Um dem Rechnung zu tragen, fügt Zeus noch einen Zwischenschritt ein.

Eine Menge von Beschränkungen (als Schema, in einer DTD, ...) wird zunächst in Zeus-Bindings konvertiert. Diese Bindings sind nicht an eine spezielle Art der Repräsentation von Beschränkungen gebunden. Mit anderen Worten: Es ist möglich, einen gewissen Satz an Beschränkungen als XML Schema zu formulieren und dieselben Beschränkungen auch in eine DTD zu packen. Wandelt man diese dann in Zeus-Bindings um, erhält man identische Ergebnisse. Diese Bindings werden genutzt, um Java-Sourcecode zu erzeugen. Das Ergebnis ist ein Prozeß wie in Abbildung 15-3 dargestellt.

Abbildung 15-3: Die Methode von Zeus zur Klassengenerierung
Die Methode von Zeus zur Klassengenerierung

Besonders schön daran und an den zugrundeliegenden Gedankengängen ist, daß es sehr einfach ist, Unterstützung für eine neue Methode der Darstellung von Beschränkungen hinzuzufügen. Dazu schreibt man eine Klasse, die das Interface org.enhydra.zeus.Binder implementiert, einen Satz Beschränkungen nimmt und diese in Zeus-Bindings konvertiert. Für die Klassengenerierung ist bereits Sorge getragen, so daß Sie sich nicht mit Hunderten enervierender Anweisungen vom Typ out.write( ) herumärgern müssen. Im Zeus-Paket sind bereits die zwei Binder DTDBinder und SchemaBinder im Package org.enhydra.zeus.binder enthalten.

Abgesehen von diesem Unterschied in der Architektur (der Sie aber nicht betrifft, solange die benötigten Binder schon bestehen), gleichen sich Zeus und Castor bei der Klassengenerierung. Beispiel 15-5 zeigt eine DTD mit den entsprechenden Beschränkungen für das XML-Dokument aus Beispiel 15-1. Ich werde die Klassengenerierung aus DTDs an diesem Beispiel demonstrieren.

Beispiel 15-5: DTD für Beispiel 15-1

<!ELEMENT catalog (item+)>
<!ATTLIST catalog
          xmlns   CDATA    #FIXED    "http://www.homespuntapes.com"
>

<!ELEMENT item (title, teacher, guest*, description)>
<!ATTLIST item
          id      CDATA    #REQUIRED
          level   CDATA    #REQUIRED
>

<!ELEMENT title (#PCDATA)>
<!ELEMENT teacher (#PCDATA)>
<!ELEMENT guest (#PCDATA)>
<!ELEMENT description (#PCDATA)>

Zeus ist so eingerichtet, daß auch Anwendungen den Klassengenerator starten können, und deshalb existiert auch kein statischer Eintrittspunkt in der API dafür. Da dies aber eine häufige Aufgabe ist, existiert eine Klasse in den Beispielen (Sie erinnern sich an die Erzeugung des Pakets mit dem Parameter »samples«?), die genau das tut. Wenn Ihr Klassenpfad so eingerichtet ist, wie ich es Ihnen weiter oben gezeigt habe, müssen Sie nur noch ein Verzeichnis »output« anlegen. Das Beispielprogramm schreibt seine Resultate dort hinein (Sie können das natürlich im Programm ändern). Wenn Sie ein solches Verzeichnis angelegt haben, können Sie das folgende Kommando ausführen:

C:\javaxml2\build>java samples.TestDTDBinder 
    -file=c:\javaxml2\ch14\xml\zeus\catalog.dtd 
    -package=javaxml2.zeus 
    -quiet=true

Ich habe das Ganze der Klarheit wegen ein wenig formatiert. Ich habe die Datei angegeben, aus der der Sourcecode generiert werden soll, und das Package, zu dem das Ergebnis gehören soll – die Erzeugung der Klassen sollte still passieren (ohne Informationen darüber auszuspucken). Die Erzeugung aus einem Schema verläuft ähnlich. Da Zeus die finale Version der XML Schema-Recommendation unterstützt, müssen Sie eine neuere Version des XML Schemas als die im Abschnitt über Castor benutzen. Beispiel 15-6 zeigt das aktualisierte Schema.

Beispiel 15-6: Aktualisieren des Castor-XML-Schemas

<?xml version="1.0"?>

<schema xmlns="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.homespuntapes.com">
  <element name="catalog">
    <complexType>
      <sequence>
        <element ref="item" minOccurs="1" maxOccurs="unbounded" />
      </sequence>
    </complexType>
  </element>

  <element name="item">
    <complexType>
      <sequence>
        <element name="title" type="string" />
        <element name="teacher" type="string" />
        <element name="guest" type="string" minOccurs="0" maxOccurs="unbounded" />
        <element name="description" type="string" />
      </sequence>
      <attribute name="id" type="string" />
      <attribute name="level">
        <simpleType>
          <restriction base="integer"> 
            <enumeration value="1" />
            <enumeration value="2" />
            <enumeration value="3" />
            <enumeration value="4" />
            <enumeration value="5" />
          </restriction>
        </simpleType>
      </attribute>
    </complexType>
  </element>
</schema>

Nachdem das getan ist, können Sie folgendes Kommando zur Erzeugung der Klassen benutzen:

C:\javaxml2\build>java samples.TestSchemaBinder 
    -file=c:\javaxml2\ch14\xml\zeus\catalog.xsd 
    -package=javaxml2.zeus 
    -quiet=true

Sie werden identische Resultate erzielen, egal ob Sie nun die DTD oder das Schema als Ausgangspunkt benutzen.

Das ist eine kleine Übertreibung. Sie werden nur dann identische Ergebnisse erzielen, wenn Sie es schaffen, die Beschränkungen in der DTD und dem Schema identisch zu repräsentieren. In diesem Beispiel wurde »level« im Schema als integer definiert, während es in der DTD als PCDATA definiert werden muß. Das Ergebnis ist, daß »level« bei der Benutzung des Schemas »getypt« wird (es wird zu int) und bei der Benutzung der DTD zu einem String wird. Ich habe für diesen Abschnitt die DTD-Version benutzt, um Ihnen auch diese Option vorzustellen.

Im Verzeichnis output finden Sie anschließend ein Verzeichnis javaxml2 und darin ein Verzeichnis mit dem Namen zeus (passend zur Package-Hierarchie). Sie können sich die generierten Sourcecodes darin ansehen.

Sie werden sofort bemerken, daß Zeus ein Interface (zum Beispiel Catalog.java) und eine Default-Implementierung dieses Interfaces (zum Beispiel CatalogImpl.java) generiert. Dahinter steht der Gedanke, daß es – anders als bei den konkreten Klassen, die Castor erzeugt – ja auch möglich ist, daß Sie Ihre eigenen Klassen haben, die lediglich das Interface Catalog implementieren. Sie können so Ihre eigenen Klassen benutzen, aber trotzdem von der Klassengenerierung profitieren.

Ich sehe oft Programmierer, die mit Zeus Interfaces generieren und dann eigene Implementierungen dieser Interfaces als Enterprise JavaBeans (EJBs) erzeugen. Diese EJBs können dann mittels Zeus persistent gemacht werden, was ein Kinderspiel ist (wie ich Ihnen im nächsten Abschnitt zeigen werde). Ohne die Möglichkeit benutzerdefinierter Implementierungen würde man dazu den generierten Quellcode modifizieren müssen, was eventuell das Marshalling und Unmarshalling stört.

Ein weiterer Unterschied, den Sie beachten sollten, sind die anders gestalteten Methodennamen. Als Beispiel gebe ich hier das Interface Item an (zu seiner einfachsten Form zusammengekürzt):

package javaxml2.zeus;

public interface Item {

    public Title getTitle(  );
    public void setTitle(Title title);

    public Teacher getTeacher(  );
    public void setTeacher(Teacher teacher);

    public java.util.List getGuestList(  );
    public void setGuestList(java.util.List guestList);
    public void addGuest(Guest guest);
    public void removeGuest(Guest guest);

    public Description getDescription(  );
    public void setDescription(Description description);

    public String getLevel(  );
    public void setLevel(String level);

    public String getId(  );
    public void setId(String id);
}

Beachten Sie, daß die Namenskonventionen für JavaBeans genutzt werden und Elemente, die mehrfach benutzt werden, als Java 2-Collection-Klassen zurückgegeben werden. Schauen Sie sich den Quellcode einfach einmal an, und kompilieren Sie ihn:

javac -d . output/javaxml2/zeus/*.java

Nun können wir einen Blick auf das Marshalling und Unmarshalling mit Zeus werfen.

Marshalling und Unmarshalling

Der Prozeß von Marschalling und Unmarshalling in Zeus ist dem in Castor ziemlich ähnlich. Das Herz beider Operationen sind die zwei Klassen org.enhydra.zeus.Marshaller und org.enhydra.zeus.Unmarshaller. Wie bei Castor auch sind die zwei Methoden, an denen Sie wahrscheinlich interessiert sind, marshal( ) und unmarshal( ) in den entsprechenden Klassen. Es gibt allerdings ein paar Unterschiede. Zunächst bieten die Klassen Marshaller und Unmarshaller in Zeus keine statischen Eintrittspunkte.

Das ist gewollt und soll den Benutzer daran erinnern, Optionen zu setzen, wie zum Beispiel beim Unmarshalling das Package oder anzugeben, ob beim Marshalling bestimmte Methoden ignoriert werden sollen. Zusätzlich benutzt Zeus den JAXP-Style für Input und Output und stellt damit das Data-Binding-Verhalten der Klassen Source und Result, über die ich im Kapitel JAXP sprach, nach. Wie auch JAXP stellt Zeus einige Default-Implementierungen in den Packages org.enhydra.zeus.source und org.enhydra.zeus.result zur Verfügung. Auch mit diesen Änderungen ähneln sich die Prozesse in beiden Frameworks sehr:

File catalogFile = new File("catalog.xml");
FileReader reader = new FileReader(catalogFile);
FileWriter writer = new FileWriter(catalogFile);

// Castor: Unmarshalling
Catalog catalog =
    (Catalog)org.exolab.castor.xml.Unmarshaller.unmarshal(Catalog.class, reader);

// Zeus: Unmarshalling
StreamSource source = new StreamSource(reader);
org.enhydra.zeus.Unmarshaller unmarshaller = new org.enhydra.zeus.Unmarshaller(  );
Catalog catalog = (Catalog)unmarshaller.unmarshal(source);

// Castor: Marshalling
org.exolab.castorMarshaller.marshal(catalog, writer);

// Zeus: Marshalling
StreamResult result = new StreamResult(writer);
org.enhydra.zeus.Marshaller marshaller = new org.enhydra.zeus.Marshaller(  );
marshaller.marshal(catalog, result);

Bei der Benutzung von Zeus muß man sich zwar mit ein wenig mehr Tipparbeit abfinden, die Vorgehensweise sollte aber JAXP-Nutzern vertraut vorkommen, was ja auch das Ziel war. Zeus erlaubt außerdem die Nutzung von SAX-Streams und DOM-Bäumen bei Benutzung entsprechender Implementierungen für Source und Result. Beispiel 15-7 ist ein Beispiel für Zeus in Aktion. Das Programm liest den ersten Kommandozeilenparameter, gibt die Item-Namen und IDs aus, ändert ein vorhandenes Item und schreibt den Katalog dann wieder als XML zurück. Es ist zwar keine Interaktion mit dem Programm möglich, Sie sollten aber trotzdem erkennen, wie Zeus arbeitet.

Beispiel 15-7: Benutzung von Zeus zur Manipulation des Katalogs

package javaxml2;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;

// Zeus-Klassen
import org.enhydra.zeus.Marshaller;
import org.enhydra.zeus.Unmarshaller;
import org.enhydra.zeus.source.StreamSource;
import org.enhydra.zeus.result.StreamResult;

// von Zeus generierte Klassen
import javaxml2.zeus.Catalog;
import javaxml2.zeus.Guest;
import javaxml2.zeus.GuestImpl;
import javaxml2.zeus.Item;
import javaxml2.zeus.ItemImpl;

public class CatalogViewer {

    public void view(File catalogFile) throws IOException {
        FileReader reader = new FileReader(catalogFile);
        StreamSource source = new StreamSource(reader);

        // Konvertieren von XML nach Java
        Unmarshaller unmarshaller = new Unmarshaller(  );
        unmarshaller.setJavaPackage("javaxml2.zeus");
        Catalog catalog = (Catalog)unmarshaller.unmarshal(source);

        List items = catalog.getItemList(  );
        for (Iterator i = items.iterator(); i.hasNext(  ); ) {
            Item item = (Item)i.next(  );
            String id = item.getId(  );
            System.out.println("Item ID: " + id);
            String title = item.getTitle().getValue(  );
            System.out.println("Item Title: " + title);

            // Modifizieren eines Items
            if (id.equals("CDZ-SM01")) {
                item.getTitle(  ).setValue("Sam Bush Teaches Mandolin " +
                    "Repertoire and Technique, 2nd edition");
                Guest guest = new GuestImpl(  );
                guest.setValue("Bela Fleck");
                item.addGuest(guest);
            }
        }
 
        // Zurückschreiben
        FileWriter writer = new FileWriter(new File("newCatalog.xml"));
        StreamResult result = new StreamResult(writer);
        Marshaller marshaller = new Marshaller(  );
        marshaller.marshal(catalog, result);
    }

    public static void main(String[] args) {
        try {
            if (args.length != 1) {
                System.out.println("Usage: java javaxml2.CatalogViewer " +
                    "[XML Catalog Filename]");
                return;
            }

            // XML-Katalog holen
            File catalogFile = new File(args[0]);
            CatalogViewer viewer = new CatalogViewer(  );
            viewer.view(catalogFile);
            
        } catch (Exception e) {
            e.printStackTrace(  );
        }
    }
}

Das liest die angegebene Datei ein und spuckt die ID und den Titel jedes Items im Katalog aus. Es sucht außerdem nach einer speziellen ID (»CDZ-SM01«) und modifiziert dieses Item, um eine zweite Auflage vorzutäuschen (und wir alle lieben zweite Auflagen!). Schließlich wird der Katalog mit den geänderten Informationen in eine neue Datei geschrieben. Versuchen Sie es. Dazu ist es nur erforderlich, daß Sie die von Zeus generierten Klassen in Ihrem Klassenpfad eingetragen haben.

Nachdem Sie jetzt wissen, wie Data-Binding grundsätzlich funktioniert, kann man Sie nicht mehr leicht überraschen. Sie müssen nur das Framework finden, das am besten zu den besonderen Anforderungen Ihrer Anwendung paßt. Eine Sache, die Zeus Castor voraus hat, ist die Generierung von Interfaces und Implementierungen dazu, die es Ihnen erlaubt, eigene Klassen mit nur kleinen Änderungen zu benutzen. Außerdem kann Zeus DTDs benutzen, was viele von Ihnen mit Hunderten DTDs in alten XML-Anwendungen lieben werden. Auf der anderen Seite ist Zeus neuer als Castor und hat weniger Schwung und zum jetzigen Zeitpunkt eine kleinere Entwicklergemeinde. Informieren Sie sich online, und nehmen Sie, was Ihnen hilft.

JAXB

Last but not least möchte ich JAXB, die Java-Architektur von Sun für das Data-Binding vorstellen. Es könnte Sie überraschen, daß ich JAXB zuletzt behandle oder sogar, daß ich die anderen APIs vorgestellt habe. Suns Angebot hat lange gebraucht, bis es endlich da war, und es ist zu dem Zeitpunkt, da dieses Buch entsteht, immer noch in einem sehr frühen Stadium. Es wird wahrscheinlich noch nicht in einer endgültigen Fassung vorliegen, wenn Sie dieses Kapitel lesen, und hat ein sehr schmales Angebot an Features.

Natürlich werden in kommenden Releases mehr und mehr Features hinzugefügt werden. Im Moment ist es aber Suns Hauptanliegen, ein stabiles Angebot zu veröffentlichen. Sie werden in Zeus und Castor auf Features stoßen, die einfach noch nicht in JAXB vorhanden sind. Aus all diesen Gründen wollte ich Sie mehr sehen lassen als nur das JAXB-Framework – und schließlich, meine ich, ist Open Source doch cool!

Installation

Um beginnen zu können, müssen Sie JAXB von Suns Website herunterladen. Es ist unter http://java.sun.com/xml/jaxb/index.html zu finden. Ich empfehle, gleich noch die Dokumentation und die Spezifikation mitzunehmen.

Zu der Zeit, da ich dies schreibe, befindet sich JAXB noch in einem sehr frühen Entwicklungsstadium. Sun könnte noch leicht etwas vor der finalen Version ändern, obwohl das in EA-Software (early access: früher Zugriff) nicht üblich ist.

Seien Sie dennoch gewarnt, daß Sie den hier präsentierten Code vielleicht noch ein wenig nachbessern müssen, damit er zum Zeitpunkt des Ausprobierens auch funktioniert. Außerdem gibt es noch eine Menge Fehler in der Dokumentation (zumindestens in der momentan aktuellen Version). Ich habe versucht, diese Fehler zu umschiffen, daher können Sie die hier gegebenen Beispiele als korrekte Syntax ansehen, wenn Sie bei der Benutzung der JAXB-Instruktionen Probleme bekommen.

Wenn Sie das Paket heruntergeladen haben, fügen Sie einfach die zwei enthaltenen jar-Dateien jaxb-xjc-1.0-ea.jar und jaxb-rt-1.0-ea.jar zu Ihrem Klassenpfad hinzu. Außerdem benutzt JAXB noch ein Skript – xjc – zur Schema-Kompilation. Ich habe keine Ahnung, warum das keine Java-Klasse ist, aber so ist das Leben. Sie können die entsprechende Klasse natürlich auch von Hand aufrufen, wenn Sie sich durch das Shell-Skript hindurchgraben wollen. Außerdem ist bei dieser Version kein Skript vorhanden, das man unter Windows benutzen kann, was aber sicherlich viele von Ihnen lieber hätten. Beispiel 15-8 zeigt ein Windows-kompatibles Skript, welches der mitgelieferten Unix-Version entspricht.

Beispiel 15-8: xjc.bat für Windows

@echo off

echo JAXB Schema Compiler
echo --------------------

if "%JAVA_HOME%" == "" goto errorJVM
if "%JAXB_HOME%" == "" goto errorJAXB

set JAXB_LIB=%JAXB_HOME%\lib
set JAXB_CLASSES=%JAXB_HOME%\classes

echo %JAVA_HOME%\bin\java.exe -jar %JAXB_LIB%\jaxb-xjc-1.0-ea.jar %1 %2 %3 %4 %5
%JAVA_HOME%\bin\java.exe -jar %JAXB_LIB%\jaxb-xjc-1.0-ea.jar %1 %2 %3 %4 %5

goto end

:errorJVM

echo ERROR: JAVA_HOME im Environment nicht gefunden.
echo Bitte setzen Sie die Umgebungsvariable JAVA_HOME auf das Verzeichnis der
JVM, die Sie benutzen möchten.
echo zum Beispiel:
echo  set JAVA_HOME=c:\java\jdk1.3.1

goto end

:errorJAXB

echo ERROR: JAXB_HOME im Environment nicht gefunden.
echo Bitte setzen Sie die Umgebungsvariable JAXB_HOME
echo auf das JAXB-Installationsverzeichnis.
echo For example:
echo  set JAXB_HOME=c:\java\jaxb-1.0-ea

:end

Speichern Sie diese Datei im Verzeichnis bin der JAXB-Installation zusammen mit xjc; wie Sie sehen können, habe ich meine Datei xjc.bat genannt. Schließlich müssen Sie noch die Umgebungsvariablen JAVA_HOME und JAXB_HOME setzen (Sie brauchen nur JAVA_HOME, wenn Sie unter Unix arbeiten). Auf meinem Windows-System mußte ich folgendes eingeben:

set JAVA_HOME=c:\java\jdk1.3.1
set JAXB_HOME=c:\javaxml2\jaxb-1.0-ea

Nun können Sie weitermachen.

Klassengenerierung

Während Castor nur Schemas unterstützt, unterstützt JAXB nur DTDs. Daher werde ich hier die DTD aus Beispiel 15-5 zur Klassengenerierung benutzen. Jedoch braucht JAXB auch noch ein sogenanntes Binding-Schema für die Klassengenerierung. Ein solches Schema teilt JAXB die Randbedingungen der Umwandlung der Beschränkungen aus der DTD in eine (oder mehrere) Jave-Klasse(n) mit. Im einfachsten Falle braucht man nur das (oder die, wenn Ihre DTD mehrere spezifiziert) root-Element(e) anzugeben. Beispiel 15-9 stellt das einfachste mögliche Binding-Schema für das in diesem Kapitel benutzte Dokument catalog.xml dar.

Beispiel 15-9: Binding-Schema für das Dokument catalog

<?xml version="1.0"?>

<xml-java-binding-schema version="1.0ea">
  <element name="catalog" type="class" root="true" />
</xml-java-binding-schema>

Das spezifiziert das Dokument als ein Binding-Schema und definiert das root-Element. Speichern Sie die Datei als catalog.xjc.

Mit einer DTD (catalog.dtd aus dem letzten Abschnitt über Zeus) und einem Binding-Schema können Sie nun Java-Klassen generieren. Führen Sie folgendes Kommando aus:

xjc c:\javaxml2\ch14\xml\jaxb\catalog.dtd c:\javaxml2\ch14\xml\jaxb\catalog.xjc

Oder unter Unix, beziehungsweise Cygwin:

xjc /javaxml2/ch14/xml/jaxb/catalog.dtd /javaxml2/ch14/xml/jaxb/catalog.xjc

Ist dieses Kommando beendet, sind die zwei Klassen Catalog.java und Item.java im aktuellen Verzeichnis entstanden. Diese haben die erwartete typische Menge von Methoden:

public class Item {
    public String getLevel(  );
    public void setLevel(  );

    public String getId(  );
    public void setId(String id);

    public List getGuest(  );
    public void deleteGuest(  );
    public void emptyGuest(  );

    // Und so weiter...
}

Beachten Sie, wie JAXB Listen behandelt. Es ist (meiner Meinung nach) ein Schritt vorwärts verglichen mit Castor, da kein Zugriff auf die gesamte List der guests möglich ist. Jedoch erreicht es (meiner Meinung nach) nicht die Annehmlichkeit der Methode addGuest( ) in Zeus. Sie müssen die Liste holen und dann Manipulationen direkt auf ihr durchführen:

// Der Zeus-Weg
item.addGuest(new Guest("Bela Bleck"));

// Der Java-Weg
List guests = item.getGuest(  );
guests.add("Bela Fleck");

Die generierten Klassen sind konkrete Klassen, wie es auch bei Castor der Fall ist.

Das Binding-Schema bietet einige nette Optionen (obwohl es kein XML ist). Erstens kann man das Package bestimmen, dem die generierten Klassen angehören sollen. Dies wird durch das Attribut package des Elements options erreicht. Fügen Sie folgende Zeile in Ihr Binding-Schema ein:

<?xml version="1.0"?>

<xml-java-binding-schema version="1.0ea">
  <options package="javaxml2.jaxb" />
  <element name="catalog" type="class" root="true" />
</xml-java-binding-schema>

Nun wird der generierte Source-Code im Verzeichnis javaxml2/jaxb mit derselben Struktur wie die der Package-Hierarchie auftauchen. Als nächstes bestimmen wir, daß das Attribut level des Elements item eine Zahl sein soll (statt eines Strings):

<?xml version="1.0"?>

<xml-java-binding-schema version="1.0ea">
  <options package="javaxml2.jaxb" />
  <element name="catalog" type="class" root="true" />
  <element name="item" type="class">
    <attribute name="level" convert="int" />
  </element>
</xml-java-binding-schema>

Wie Sie sehen können, habe ich zunächst eine Element-Deklaration für das Element item eingefügt. Dadurch ist es möglich, sein Attribut level über das Konstrukt attribute anzusprechen. Um den gewünschten Typ festzulegen, habe ich int als Wert des Attributs convert angegeben.

Wenn wir mit den Möglichkeiten fortfahren, die Binding-Schemas bieten, kommen wir nun zu einem wirklich heißen Feature. Es ist nämlich möglich, den in der DTD vereinbarten Namen einer Eigenschaft zu ändern. Ich, zum Beispiel, hasse Methodennamen wie getId( ). Statt dessen mag ich viel lieber so etwas wie getID( ), was auch sehr viel besser aussieht. Ich möchte also die Eigenschaft id aus der DTD in der Java-Klasse ID nennen. Das ist mit JAXB recht einfach:

<?xml version="1.0"?>

<xml-java-binding-schema version="1.0ea">
  <options package="javaxml2.jaxb" />
  <element name="catalog" type="class" root="true" />
  <element name="item" type="class">
    <attribute name="level" convert="int" />
    <attribute name="id" property="ID" />
  </element>
</xml-java-binding-schema>

Haben Sie alle diese verschiedenen Änderungen vorgenommen, lassen Sie den Schema-Compiler (xjc) noch einmal arbeiten. Sie werden die modifizierten Klassen erhalten, von denen ich erzählt habe, und können diese im Anschluß auch gleich kompilieren:

javac -d . javaxml2/jaxb/*.java

Sollten Probleme auftauchen, stellen Sie sicher, daß sich die Datei jaxb-rt-1.0-ea.jar noch im Klassenpfad befindet.

Es gibt noch einige Optionen mehr, die man in Binding-Schemas benutzen kann, tatsächlich sind viele undokumentiert. Ich habe sie gefunden, indem ich einen Blick auf die bei JAXB mitgelieferte DTD xjc.dtd geworfen habe. Ich empfehle Ihnen, das zusätzlich zum Lesen der Dokumentation auch zu tun. Wenn Sie Ihre Klassen generiert haben, kann es mit dem Marshalling und Unmarshalling weitergehen.

Marshalling und Unmarshalling

Der Prozeß des Marshalling und Unmarshalling erscheint als dasselbe Thema, die dritte Variation. Aus diesem Grunde mache ich hier gleich mit ein wenig Code weiter – dargestellt in Beispiel 15-10.

Beispiel 15-10: Die Klasse Categorizer

package javaxml2;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

// JAXB-Klassen
import javax.xml.bind.UnmarshalException;

// von JAXB generierte Klassen
import javaxml2.jaxb.Catalog;
import javaxml2.jaxb.Item;

public class Categorizer {

    public void categorize(File catalogFile) throws IOException, 
        UnmarshalException {

        // Konvertieren von XML nach Java
        FileInputStream fis = new FileInputStream(catalogFile);
        Catalog catalog = new Catalog(  );
        try {
            catalog = Catalog.unmarshal(fis);
        } finally {
            fis.close(  );
        }

        // Erzeugen eines neuen Katalogs für die verschiedenen Kategorien
        Catalog fingerpickingCatalog = new Catalog(  );
        Catalog flatpickingCatalog = new Catalog(  );
        Catalog mandolinCatalog = new Catalog(  );

        List items = catalog.getItem(  );
        for (Iterator i = items.iterator(); i.hasNext(  ); ) {
            Item item = (Item)i.next(  );
            String teacher = item.getTeacher(  );
            if ((teacher.equals("Doc Watson")) ||
                (teacher.equals("Steve Kaufman"))) {
                flatpickingCatalog.getItem(  ).add(item);
            } else if (teacher.equals("David Wilcox")) {
                fingerpickingCatalog.getItem(  ).add(item);
            } else if ((teacher.equals("Sam Bush")) ||
                       (teacher.equals("Chris Thile"))) {
                mandolinCatalog.getItem(  ).add(item);
            }
        }

        // XML zurückschreiben
        FileOutputStream fingerOutput = 
            new FileOutputStream(new File("fingerpickingCatalog.xml"));
        FileOutputStream flatpickOutput = 
            new FileOutputStream(new File("flatpickingCatalog.xml"));
        FileOutputStream mandolinOutput = 
            new FileOutputStream(new File("mandolinCatalog.xml"));
        try {
            // Validieren des Katalogs
            fingerpickingCatalog.validate(  );
            flatpickingCatalog.validate(  );
            mandolinCatalog.validate(  );

            // Ausgabe des Katalogs
            fingerpickingCatalog.marshal(fingerOutput);
            flatpickingCatalog.marshal(flatpickOutput);
            mandolinCatalog.marshal(mandolinOutput);
        } finally {
            fingerOutput.close(  );
            flatpickOutput.close(  );
            mandolinOutput.close(  );
        }
    }

    public static void main(String[] args) {
        try {
            if (args.length != 1) {
                System.out.println("Benutzung: java javaxml2.Categorizer " +
                    "[Dateiname für XML-Katalog]");
                return;
            }

            // Zugriff auf den XML-Katalog holen
            File catalogFile = new File(args[0]);
            Categorizer categorizer = new Categorizer(  );
            categorizer.categorize(catalogFile);
            
        } catch (Exception e) {
            e.printStackTrace(  );
        }        
    }

}

Hieran ist eigentlich nichts interessant – alles hier haben Sie schon mehrfach gesehen. JAXB arbeitet trotzdem an manchen Stellen anders. Zunächst existieren die Methoden marshal( ) und unmarshal( ) in den generierten Klassen selbst und nicht in statischen Marshaller- und Unmarshaller-Klassen:

// Konvertieren von XML nach Java
FileInputStream fis = new FileInputStream(catalogFile);
Catalog catalog = new Catalog(  );
try {
    catalog = Catalog.unmarshal(fis);
} finally {
    fis.close(  );
}

Die generierten Klassen stellen statische Methoden zum Marshalling und Unmarshalling zur Verfügung. Diese Methoden werden für Instanzen der Klasse ausgeführt, die mit den Daten der übergebenen Datei gefüllt sind. Daher müssen Sie sicherstellen, daß Sie den Rückgabewert dieser Methoden einer Instanz-Variablen zuweisen! Ein extrem frustrierender Fehler ist dieser:

// Konvertieren von XML nach Java
FileInputStream fis = new FileInputStream(catalogFile);
Catalog catalog = new Catalog(  );
try {
    catalog.unmarshal(fis);
} finally {
    fis.close(  );
}

Beachten Sie die fettgedruckte Zeile: Wenn Sie versuchen, auf die Instanzvariable der Klasse catalog zuzugreifen, werden Sie keine Daten erhalten, egal, was das XML-Dokument enthält. Der Grund dafür ist, daß die Methode unmarshal( ) statisch ist und eine Instanz der Klasse Catalog liefert – da dieser Wert nicht zugewiesen wird, geht sie verloren. Das kann ziemlich nerven, passen Sie also auf! Genau dieser Sachverhalt ist eigentlich ein Fall für statische Marshaller- und Unmarshaller-Klassen wie in Zeus und Castor.

In dem Beispiel wird, wenn eine Instanz des XML-Katalogs vorliegt, über die Items iteriert. Abhängig vom »teacher« fügt der Code ein Item zu einer von drei Kategorien hinzu: »flatpicking«, »fingerpicking« und »mandolin«. Anschließend wird jede dieser Kategorien mittels Marshalling in ein neues XML-Dokument geschrieben. Als Beispiel zeige ich Ihnen mein Dokument mandolinCatalog.xml:

<?xml version="1.0" encoding="UTF-8"?>

<catalog>
  <item level="3" id="VD-THI-MN01">
    <title>Essential Techniques for Mandolin</title>
    <teacher>Chris Thile</teacher>
    <description>Das ist eine Lektion, die Mandolinenspieler aller 
    Erfahrungsstufen begeistern wird.</description></item>
  <item level="4" id="CDZ-SM01">
    <title>Sam Bush lehrt Mandolinen-Repertoire und -Techniken</title>
    <teacher>Sam Bush</teacher>
    <description>Lerne acht traditionelle und moderne Solos, jedes davon 
    vollgepackt mit Licks, Runs und musikalischen Variationen.</description>
</item></catalog>

Das Format hier hängt wie immer vom Layout des Buches ab, daher kann sich die Zeilenaufteilung bei Ihnen unterscheiden. Das Marshalling und Unmarshalling in JAXB ist, wie gesehen, einfach zu nutzen. Wenn man erst einmal mit dem Thema statische Methoden klargekommen ist, laufen diese Prozesse fast identisch zu den beiden anderen Frameworks ab.

Obwohl ich Sie ermutige, JAXB heute zu testen, sollten Sie dennoch vorsichtig sein, wenn Sie es in ernsthaften Projekten einsetzen, bevor die finale Version erscheint. Es gibt immer noch eine Anzahl undokumentierter Features, die sich leicht vor einer solchen finalen Version ändern könnten. Außerdem erlaubt es JAXB zur Zeit nicht, beliebige Objekte, deren Klassen nicht mittels JAXB generiert wurden, mittels Marshalling zu behandeln. Das ist etwas, was andere Frameworks können und was Ihre Anwendungen womöglich einschränkt. Dazu bietet JAXB auch keine Unterstützung für XML Schemas und Namensräume. Positiv ist, daß Sun wahrscheinlich viele Ressourcen in die Entwicklung von JAXB stecken und es sich in den kommenden Monaten deshalb sehr mausern wird.

In jedem Fall sollten Sie nun einen guten Eindruck davon erhalten haben, was das Data-Binding bietet. Die Ecken und Kanten eines bestimmten Frameworks auszuloten überlasse ich Ihnen. Mit den hier vermittelten Grundlagen sollten Sie aber in der Lage sein, jedes der hier vorgestellten Projekte zum Laufen zu bringen. Informieren Sie die Entwickler des Projekts, für das Sie sich entschieden haben, darüber, was funktioniert und was nicht, das kann die zukünftige Entwicklung nur positiv beeinflussen – besonders in der Open Source-Bewegung.

Und was kommt jetzt?

Wir nähern uns langsam den letzten Seiten des Buches, daher wird es nicht überraschen, daß ich nunmehr die behandelten Dinge zusammenfasse. Ich werde ein wenig zaubern und Ihnen verraten, was sich meiner Meinung nach im Auge zu behalten lohnt. Das Kapitel Ausblick ist aus zwei Gründen mein »Ausschau-halten«-Kapitel. Der erste ist offensichtlich: Ich möchte Sie an der Spitze der Bewegung wissen. Der zweite, interessantere Punkt ist, Ihnen zu zeigen, wie schnell sich die Dinge ändern. Das trifft besonders für Java und XML zu, und ich erwarte, daß Sie und ich in einem Jahr über einige der Aussagen des nächsten Kapitels herzhaft lachen werden. Immerhin wird es Ihnen helfen, über das nachzudenken, was sich heute schon am Horizont abzeichnet, und Sie auf das Auftauchen dieser neuen Technologien vorbereiten.

1)
Wenn ich »Accessor« und »Mutator« sage, meine ich damit das, was die meisten Menschen »Getter-« und »Setter-Methoden« nennen. Da ich jedoch weiß, daß ein »Setter« eine Hunderasse und keine Java-Methode ist, lege ich meinen Studenten nahe, diese Bezeichnung zu unterlassen. Eine Eigenart von mir!
2)
Tatsächlich existieren zwei jar-Dateien im Archiv: castor-0.9.2.jar und castor-0.9.2-xml.jar. Die erste ist eine Obermenge der zweiten, daher benötigen Sie nur die erste; möchten Sie ein kleineres Archiv, können Sie auch die zweite nehmen.