Michaelis/Schmiesing JAXB 2.0 Ein Programmiertutorial für die Java Architecture for XML Binding
Samuel Michaelis Wolfgang Schmiesing
JAXB 2.0 Ein Programmiertutorial für die Java Architecture for XML Binding
Die Autoren: Samuel Michaelis, Software-Architekt Wolfgang Schmiesing, Berater und Projektleiter Beide Autoren sind für die Innovations Softwaretechnologie GmbH in Immenstaad tätig.
Alle in diesem Buch enthaltenen Informationen, Verfahren und Darstellungen wurden nach bestem Wissen zusammengestellt und mit Sorgfalt getestet. Dennoch sind Fehler nicht ganz auszuschließen. Aus diesem Grund sind die im vorliegenden Buch enthaltenen Informationen mit keiner Verpflichtung oder Garantie irgendeiner Art verbunden. Autoren und Verlag übernehmen infolgedessen keine juristische Verantwortung und werden keine daraus folgende oder sonstige Haftung übernehmen, die auf irgendeine Art aus der Benutzung dieser Informationen oder Teilen davon entsteht. Ebenso übernehmen Autoren und Verlag keine Gewähr dafür, dass beschriebene Verfahren usw. frei von Schutzrechten Dritter sind. Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Buch berechtigt deshalb auch ohne besondere Kennzeichnung nicht zu der Annahme, dass solche Namen im Sinne der Warenzeichen- und Markenschutz-Gesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften.
Bibliografische Information Der Deutschen Nationalbibliothek Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar.
Dieses Werk ist urheberrechtlich geschützt. Alle Rechte, auch die der Übersetzung, des Nachdruckes und der Vervielfältigung des Buches, oder Teilen daraus, vorbehalten. Kein Teil des Werkes darf ohne schriftliche Genehmigung des Verlages in irgendeiner Form (Fotokopie, Mikrofilm oder ein anderes Verfahren) auch nicht für Zwecke der Unterrichtsgestaltung reproduziert oder unter Verwendung elektronischer Systeme verarbeitet, vervielfältigt oder verbreitet werden.
© 2007 Carl Hanser Verlag München Wien Lektorat: Fernando Schneider Sprachlektorat: Sandra Gottmann, Münster-Nienberge Herstellung: Monika Kraus Umschlagdesign: Marc Müller-Bremer, Rebranding, München Umschlaggestaltung: MCP · Susanne Kraus GbR, Holzkirchen Datenbelichtung, Druck und Bindung: Kösel, Krugzell Ausstattung patentrechtlich geschützt. Kösel FD 351, Patent-Nr. 0748702 Printed in Germany ISBN-10: 3-446-40753-7 ISBN-13: 978-3-446-40753-4 www.hanser.de/computer
Inhalt
Vorw Java Architecture for XML Binding 2.0.................................................................................XI Technologieeinführung ........................................................................................................ XII Programmiertutorials............................................................................................................ XII Referenz ............................................................................................................................... XII Feedback ............................................................................................................................. XIII 1 1.1 1.2 1.3
1.4 2 2.1
JAXB im Überblick................................................................................................... 1 Ziele .........................................................................................................................................1 Entstehung................................................................................................................................4 Architektur ...............................................................................................................................5 1.3.1 Annotationen ..............................................................................................................7 1.3.2 Der Schema-Compiler ................................................................................................8 1.3.3 Der Schema-Generator ...............................................................................................9 1.3.4 Das Binding Framework...........................................................................................10 1.0 + 1.0 = 2.0? ......................................................................................................................14 Basistechnologien ................................................................................................. 17 XML-Schema.........................................................................................................................17 2.1.1 Namespaces verwenden............................................................................................21 2.1.2 Elementdeklarationen in XML-Schema ...................................................................24 2.1.3 Vererbung.................................................................................................................35 2.1.4 Kardinalitäten ...........................................................................................................37 2.1.5 Offene Schemas definieren.......................................................................................38 2.1.6 Namespaces importieren und referenzieren..............................................................40 2.1.7 Eindeutigkeit durch ID und IDREF ..........................................................................43 2.1.8 Dokumentation mit Annotationen ............................................................................45 2.1.9 Das fertige Beispielschema ......................................................................................46
V
Inhalt 2.2
2.3
3 3.1 3.2
3.3
3.4 4 4.1
4.2
4.3
VI
XPath......................................................................................................................................48 2.2.1 Die XPath-Sicht auf XML........................................................................................49 2.2.2 XPath-Ausdrücke verwenden ...................................................................................50 2.2.3 Beispiele ...................................................................................................................51 2.2.4 XPath in Java............................................................................................................53 ANT .......................................................................................................................................55 2.3.1 ANT-Übersicht .........................................................................................................55 2.3.2 Installation und Aufruf .............................................................................................56 2.3.3 Häufig verwendete Elemente....................................................................................57 2.3.4 Benutzerdefinierte Tasks ..........................................................................................58 2.3.5 xjc und schemaGen Tasks ........................................................................................58 2.3.6 Ein Beispiel ..............................................................................................................59 Hallo JAXB! ............................................................................................................ 61 Systemvoraussetzungen .........................................................................................................61 Die Entwicklungsumgebung einrichten..................................................................................62 3.2.1 JAXB-Referenzimplementierung installieren...........................................................62 3.2.2 Die JAXB-Bibliotheken einbinden...........................................................................62 3.2.3 Die Struktur des Beispielprojekts .............................................................................63 Am Anfang steht immer: Hallo Welt! ....................................................................................64 3.3.1 Der Weg von Java zu XML ......................................................................................64 3.3.2 Der Weg von XML zu Java ......................................................................................67 Zusammenfassung..................................................................................................................69 JAXB-API ................................................................................................................ 71 Die ersten Schritte ..................................................................................................................72 4.1.1 Am Anfang war der JAXBContext...........................................................................72 4.1.2 Die Klasse JAXBIntrospector verwenden ................................................................75 4.1.3 Objekte erzeugen mit der ObjectFactory ..................................................................77 4.1.4 Zusammenfassung ....................................................................................................78 Marshalling ............................................................................................................................79 4.2.1 Das Marshaller-Objekt anlegen ................................................................................79 4.2.2 Den Marshalling-Prozess starten ..............................................................................80 4.2.3 Den Marshalling-Prozess konfigurieren ...................................................................81 4.2.4 Das fertige Beispiel ..................................................................................................83 4.2.5 Marshalling beliebiger Objekte ................................................................................85 4.2.6 Zusammenfassung ....................................................................................................86 Unmarshalling ........................................................................................................................87 4.3.1 Das Unmarshaller-Objekt anlegen............................................................................87 4.3.2 Den Unmarshalling-Prozess starten..........................................................................88 4.3.3 Den Unmarshalling-Prozess konfigurieren...............................................................89 4.3.4 Das fertige Beispiel ..................................................................................................89 4.3.5 Das xsi:type-Attribut beim Unmarshalling verwenden.............................................90 4.3.6 Elemente ohne @XMLRootElement verarbeiten .....................................................91 4.3.7 Unmarshalling von Teilen eines XML-Dokuments ..................................................93 4.3.8 Flexibles Unmarshalling nutzen ...............................................................................95
Inhalt
4.4
4.5
4.6
5 5.1
5.2 5.3
5.4
5.5
4.3.9 Zusammenfassung ................................................................................................... 97 Validierung............................................................................................................................ 97 4.4.1 Beim Unmarshalling validieren ............................................................................... 98 4.4.2 Beim Marshalling validieren.................................................................................... 99 4.4.3 Benutzerdefinierte Validierung.............................................................................. 101 4.4.4 Zusammenfassung ................................................................................................. 107 Callback-Mechanismen einsetzen ....................................................................................... 107 4.5.1 Die Listener-Klasse verwenden ............................................................................. 108 4.5.2 Callback-Methoden auf Klassenebene definieren.................................................. 110 4.5.3 Zusammenfassung ................................................................................................. 112 Die Binder-Komponente verwenden ................................................................................... 112 4.6.1 Eine DOM-Sicht erstellen...................................................................................... 113 4.6.2 Die Klasse javax.xml.bind.Binder ......................................................................... 113 4.6.3 Transformation mit unmarshal und marshal .......................................................... 114 4.6.4 Navigation mit getXMLNode und getJAXBNode................................................. 115 4.6.5 Synchronisation mit updateXML und updateJAXB .............................................. 116 4.6.6 Konkrete Anwendungsfälle ................................................................................... 117 4.6.7 Zusammenfassung ................................................................................................. 123 XML-Schema zu Java .......................................................................................... 125 Bindungskonfigurationen .................................................................................................... 125 5.1.1 Eine Bindungskonfiguration definieren ................................................................. 126 5.1.2 Bindungskonfigurationen per xjc-Task einbinden ................................................. 130 5.1.3 Den XJC-Schema-Compiler programmatisch starten ............................................ 130 5.1.4 Externe Bindungskonfigurationen auf der Kommandozeile .................................. 134 5.1.5 Ein Wort zu Versionen .......................................................................................... 134 5.1.6 Der Sichtbarkeitsbereich........................................................................................ 134 5.1.7 Zusammenfassung ................................................................................................. 135 Die erste Bindungskonfiguration......................................................................................... 136 5.2.1 Zusammenfassung ................................................................................................. 138 Fünf Bindungsdeklarationen für den Alltag ........................................................................ 138 5.3.1 Aufzählungen anpassen mit jaxb:collectionType................................................... 138 5.3.2 Paketnamen anpassen mit jaxb:package ................................................................ 140 5.3.3 Generierte Klassen anpassen mit jaxb:class........................................................... 142 5.3.4 Änderungen auf Komponentenebene mit jaxb:property ........................................ 145 5.3.5 Dokumentieren mit jaxb:javadoc ........................................................................... 152 5.3.6 Zusammenfassung ................................................................................................. 155 XML-Bezeichner anpassen.................................................................................................. 155 5.4.1 Einzelne Namenskonflikte auflösen....................................................................... 156 5.4.2 Präfixe und Suffixe mit jaxb:nameXmlTransform................................................. 157 5.4.3 Unterstriche verarbeiten mit jaxb:underScoreBinding........................................... 159 5.4.4 Bezeichner in Enumerationen ................................................................................ 160 5.4.5 Verwenden von Java-Schlüsselwörtern als Bezeichner ......................................... 166 5.4.6 Java-Namenskonventionen .................................................................................... 168 5.4.7 Zusammenfassung ................................................................................................. 168 Datentypen anpassen ........................................................................................................... 168
VII
Inhalt
5.6 5.7
6 6.1 6.2
6.7 6.8
Java zu XML-Schema .......................................................................................... 193 JAXBContext und JAXBElement ganz ohne Schema..........................................................194 Einfache Elementkonfigurationen ........................................................................................201 6.2.1 Wurzelelemente mit @XmlRootElement definieren ..............................................201 6.2.2 Der Standardmodus.............................................................................................205 6.2.3 Abbildung als XML-Element explizit konfigurieren ..............................................210 6.2.4 Java-Eigenschaften an XML-Attribute binden .......................................................216 6.2.5 Java-Eigenschaften an XML-Elementinhalte binden..............................................221 6.2.6 Bindung an XML unterdrücken..............................................................................223 6.2.7 Reihenfolge der XML-Elemente beeinflussen........................................................225 6.2.8 Namen und Verschachtelung des XML-Typs einstellen.........................................230 6.2.9 XML-Elemente referenzieren .................................................................................234 6.2.10 Namespace konfigurieren .......................................................................................237 Listen und Elementmischungen ...........................................................................................243 6.3.1 Der Standardmodus für Java-Arrays und Java-Listen .........................................244 6.3.2 Listen und Arrays an simpleType/list-Deklarationen binden..................................249 6.3.3 Mehrere Elemente im gleichen Feld speichern.......................................................251 6.3.4 Elementlisten verpacken.........................................................................................263 6.3.5 Mischungen von Elementen und Text ....................................................................266 Enumerationen .....................................................................................................................270 6.4.1 Enumerationen an XML binden: Standardverhalten...............................................271 6.4.2 Bindung von Enumerationen konfigurieren............................................................272 Eigene Typbindungen definieren .........................................................................................275 6.5.1 Basisdatentyp als Speicherdatentyp........................................................................277 6.5.2 Komplexe Datentypen als Speicherdatentypen.......................................................280 Unbekannte Attribut- und Elementwerte binden ..................................................................284 6.6.1 Wildcard-Attribute mit @XmlAnyAttribute definieren..........................................284 6.6.2 Wildcard-Elemente per @XmlAnyElement deklarieren ........................................286 6.6.3 Lax fischen .............................................................................................................288 Objektgraphen in XML abbilden..........................................................................................290 Elemente über Factory-Klassen definieren...........................................................................295
7 7.1 7.2
JAXB-Referenz..................................................................................................... 299 XJC Kommandozeilenbefehl ...............................................................................................299 XJC ANT-Task ....................................................................................................................301
6.3
6.4
6.5
6.6
VIII
5.5.1 Datentypen explizit angeben mit jaxb:baseType ....................................................169 5.5.2 Datentypen konvertieren mit jaxb:javaType...........................................................173 5.5.3 Zusammenfassung ..................................................................................................181 Deklarationen überlagern .....................................................................................................182 Noch mehr Bindungsdeklarationen ......................................................................................185 5.7.1 Ableiten von einer Oberklasse mit xjc:superClass..................................................186 5.7.2 Ein Wurzelinterface angeben mit xjc:superInterface ..............................................187 5.7.3 Erweiterung von jaxb:javaType mit xjc:javaType ..................................................189 5.7.4 Das Experiment: xjc:simple....................................................................................191 5.7.5 Zusammenfassung ..................................................................................................192
Inhalt 7.3 7.4 7.5
7.6 7.7 7.8
schemaGen-Kommandozeilenbefehl ................................................................................... 303 schemaGen-ANT-Task........................................................................................................ 304 JAXB-Annotationen............................................................................................................ 306 7.5.1 XmlAccessorOrder ................................................................................................ 306 7.5.2 XmlAccessorType ................................................................................................. 307 7.5.3 XmlAnyAttribute................................................................................................... 311 7.5.4 XmlAnyElement .................................................................................................... 312 7.5.5 XmlAttachmentRef................................................................................................ 314 7.5.6 XmlAttribute.......................................................................................................... 314 7.5.7 XmlElement........................................................................................................... 316 7.5.8 XmlElementDecl ................................................................................................... 318 7.5.9 XmlElementRef ..................................................................................................... 320 7.5.10 XmlElementRefs.................................................................................................... 321 7.5.11 XmlElements ......................................................................................................... 323 7.5.12 XmlElementWrapper ............................................................................................. 324 7.5.13 XmlEnum .............................................................................................................. 325 7.5.14 XmlEnumValue ..................................................................................................... 326 7.5.15 XmlID.................................................................................................................... 327 7.5.16 XmlIDREF............................................................................................................. 328 7.5.17 XmlInlineBinaryData............................................................................................. 330 7.5.18 XmlList.................................................................................................................. 330 7.5.19 XmlJavaTypeAdapter ............................................................................................ 331 7.5.20 XmlJavaTypeAdapters........................................................................................... 332 7.5.21 XmlMimeType ...................................................................................................... 333 7.5.22 XmlMixed.............................................................................................................. 333 7.5.23 XmlNs.................................................................................................................... 334 7.5.24 XmlRegistry........................................................................................................... 334 7.5.25 XmlRootElement ................................................................................................... 335 7.5.26 XmlSchema ........................................................................................................... 336 7.5.27 XmlSchemaType ................................................................................................... 338 7.5.28 XmlSchemaTypes.................................................................................................. 339 7.5.29 XmlTransient ......................................................................................................... 340 7.5.30 XmlType................................................................................................................ 341 7.5.31 XmlValue............................................................................................................... 342 Typkonversionen Java zu XML-Schema............................................................................. 343 Typkonversionen XML-Schema zu Java............................................................................. 345 XML-Elemente der Bindungskonfiguration ........................................................................ 346 7.8.1 Globale Einstellungen............................................................................................ 346 7.8.2 Schemaspezifische Einstellungen .......................................................................... 359 7.8.3 Komponentenspezifische Einstellungen ................................................................ 363
Register............................................................................................................................ 381
IX
Vorwort Das Buch wendet sich an Entwickler mit guten Java-Kenntnissen, die in ihrem Arbeitsalltag oft mit Java und XML arbeiten. In diesem Buch stellen wir mit Suns Java Architecture for XML Binding, kurz JAXB, eine tolle Lösung vor, wie wir dieser Herausforderung effektiv begegnen können.
Java Architecture for XML Binding 2.0 Wer frühere Versionen der JAXB (1.x) eingesetzt oder evaluiert hat, dem mag die JAXB damals vielleicht verschroben vorgekommen sein. Tatsächlich war die technologische Hürde zum Einsatz von JAXB 1.x sehr hoch. Zum einen waren da die Verwirrungen mit den endorsed XML-Bibliotheken, die Laufzeitfehler verursachten, wenn der Code auf unterschiedlich konfigurierten JVMs ausgeführt wurde das gute, alte Java-Motto write once, run everywhere traf hier nicht mehr ohne Einschränkungen zu. Zudem waren die JAXB 1.x-Versionen noch nicht inspiriert vom jüngsten Trend zu den POJOs, den Plain Old Java Objects. Hier wurde stattdessen viel mit Factory-Methoden und Schnittstellen herumhantiert, die aus einem Schema ein unnatürliches und unhandliches JavaDatenmodell erzeugten. Von praktischer Bedeutung war in den Versionen bisher nur der Weg von einem XMLSchema zu einem Java-Datenmodell. Ergebnis ist, dass bisherige JAXB-Versionen keine besondere Unterstützung durch Hersteller oder Open-Source-Projekte erfahren haben. Statt dessen entstanden Projekte, die einen natürlicheren Ansatz zur Umwandlung von XML in Java jenseits des JAXB-Standards gesucht und gefunden haben und heute zu den Werkzeugen der Wahl gehören. Mit der JAXB 2.0 fließen nun diese Ansätze in die JSE zurück: Die Nachteile der bisherigen JAXB-Versionen sind wie weggeblasen. Übrig bleibt eine sehr komfortable Art und Weise, aus XML-Schema ein POJO-Datenmodell bzw. aus POJOs ein präzise anpassbares XML-Format zu generieren. Wir sind überzeugt, dass mit der JAXB 2.0 eine Architektur entstanden ist, die für Entwickler eine langfristige und befriedigende Lösung des XMLund Java-Themas bringt und durch alle wesentlichen Hersteller und Projekte unterstützt
XI
Vorwort werden wird. Wir möchten mit diesem Buch einen kleinen Beitrag leisten, diese feine Technologie für alle Entwickler leicht zugänglich machen. Das Buch teilt sich in drei wesentliche Teile auf: Technologieeinführung, Programmiertutorials und Referenz.
Technologieeinführung Der erste Teile beschreibt die der JAXB-Spezifikation zugrunde liegenden Technologien XML-Schema, XPath und Ant im Überblick. Den wichtigsten Anteil übernimmt dabei ein Überblick auf wesentliche Konzepte von XML-Schema. Wir haben im Alltag die Erfahrung gemacht, dass viele Entwickler XML-Schema zwar sehr gut als Format zur Beschreibung von XML-Formaten einordnen können und einfache Schemata auch ohne weiteres verstehen, im konkreten Fall aber gerne nachschlagen, welche Bedeutung einzelne Schemadeklarationen und -konzepte besitzen. Daher bieten wir in diesem Buch eine kleine Einführung in XML-Schema mit dem Ziel, Antworten für die wesentlichen Fragen zu bieten, die im Rahmen der Tutorials aufkommen können. Zu den Technologien, die in den Beispielen dieses Buches verwendet werden, gehört auch XPath, das SQL der XML-Welt, und Ant. Auch für diese beiden Technologien bieten wir jeweils eine kleine Darstellung der wesentlichen Möglichkeiten.
Programmiertutorials Der zweite Teil des Buches besteht aus vier klassischen Programmiertutorials, welche die verschiedenen Aspekte der JAXB in Beispielen von steigender Komplexität beleuchten. Nach dem obligatorischen Hallo Welt-Beispiel folgt eine Einführung in die Laufzeitbibliothek der JAXB. In Kapitel 5 behandeln wir den Weg von einem XML-Schema zu einem Java-Datenmodell, in Kapitel 6 den Weg von einem Java-Datenmodell zu einem XMLDokument.
Referenz Der dritte Teil ist die Referenz. Wir haben uns zum Ziel gesetzt, dass die Referenz im Wesentlichen für sich alleine stehen kann so dass ein Entwickler mit guten JAXBKenntnissen (die er sich in Programmiertutorial aneignen kann) alle im Alltag aufkommenden Fragen schnell und insbesondere vollständig anhand der gut strukturierten Referenz klären kann. Das hat zur Folge, dass einzelne Informationen in diesem Buch eventuell redundant im Programmiertutorial und in der Referenz vorkommen. Von einem Kommunikationsprofi habe ich mir aber versichern lassen, dass Redundanz, die wir Entwickler im Programmier-
XII
Vorwort alltag als Code Duplication verteufeln, richtig angewendet im Bereich der Didaktik eine der gerne zitierten Best Practices ist. Wir möchten uns an dieser Stelle bei Ihnen als Leser bedanken, dass Sie dieses Buch und viele weitere durch Ihren beherzten Griff ins Regal Ihres Buchhändlers möglich gemacht haben, und hoffen an dieser Stelle, dass Ihnen die Lektüre dieses Buchs viel Spaß macht und Ihnen interessante, neue Ansätze für Ihre alltägliche Arbeit bieten kann.
Feedback Kein Prozess kann sich ohne Feedback sinnvoll verbessern! Beachten Sie diesen schlauen Spruch bitte nicht nur in Ihrem Arbeitsalltag, sondern schicken Sie uns gerne Ihr Feedback zu diesem Buch an
[email protected]. Vielen Dank!
XIII
1 JAXB im Überblick Im folgenden Kapitel werden wir die Java Architecture for XML Binding 2.0 aus der Vogelperspektive betrachten. Hier gilt es, die wesentlichen Teile der JAXB-Spezifikation anschaulich und im Überblick darzustellen. Insbesondere werden wir hier die Begriffe prägen, mit denen wir die Komponenten der JAXB im weiteren Verlauf des Buches und der Tutorials referenzieren werden. Wir werden darauf eingehen, welche Ziele mit der JAXBSpezifikation 2.0 insgesamt angestrebt wurden. Um die Hintergründe der JAXB zu verstehen, darf ein Blick auf die Entstehung der JAXBSpezifikation nicht fehlen. Mit diesem Grundverständnis über den Aufbau und das Zusammenspiel der einzelnen Architekturkomponenten gerüstet, wird es uns leichter fallen, die später im Programmiertutorial vorgestellten Funktionen zu verstehen. Für Anwender, die bereits mit der Version 1.0 der JAXB gearbeitet haben, werden wir abschließend die Unterschiede und Neuerungen in der Version 2.0 vorstellen.
1.1
Ziele Eine High-Level-XML-API bieten Die Java Architecture for XML Binding ermöglicht dem Applikationsentwickler, seine Java-Anwendung optimal mit XML interagieren zu lassen. JAXB hat dabei den Anspruch, eine API auf weit abstrakterer Ebene zu bieten, als das bisher mit parser-basierten Ansätzen wie SAX und DOM möglich war. Auch Entwickler, die nicht mit diesen Parsern vertraut sind, sollen die Möglichkeit haben, XML in ihre Anwendungen zu integrieren. Die JAXB-Spezifikation bemüht sich daher um eine möglichst einfache, für JavaEntwickler intuitiv nutzbare Schnittstelle für den Zugriff auf XML-Daten. Doch was eignet sich als intuitive Schnittstelle? Java Beans sind wohl eines der Konzepte, die jedem JavaEntwickler vertraut sind. Daher liegt es nahe, Bindungsinformation und Daten durch Klassen im Java Beans-Stil zu repräsentieren. Und genau das ist das primäre Ziel der JAXB, die Bindung von XML an Java über portable Java Beans, im Fachjargon auch Pojos, kurz
1
1 JAXB im Überblick für plain old Java objects, genannt. Auf diese Weise können wir bequem über die vertrauten Java-Programmiermethoden mit schemabasierten XML-Daten arbeiten. Die konsequente Einführung des Pojo-Konzepts in die JAXB-Spezifkation und die einfache Konfiguration dieser Objekte mit Annotationen vereinfachen die Erstellung dieses Typs von Datenbindung ungemein. Java 5-Spracherweiterungen nutzen Um diese Bindung bei Bedarf individuell anzupassen, enthält JAXB Konfigurationsmechanismen, welche die Struktur der erzeugten Klassen bzw. des erzeugten Schemas beeinflussen können. Dafür werden u.a. Java-Annotationen verwendet, die eine der Erweiterungen darstellen, die mit der Java Standard Edition 5 eingeführt wurden. Die JAXB-Spezifikation macht aber auch regen Gebrauch von den weiteren Neuerungen der JSE 5 wie den typsicheren Enumerationen und den sogenannten Generics. Diese Spracherweiterungen machen die generierten Klassen und XML-Schemas einfacher und vor allem auch deren Verwendung sicherer (insbesondere typsicherer). Die Struktur der erzeugten Klassen und XML-Schemas wird durch Standardkonfigurationen der JAXB und benutzerdefinierte Konfigurationen fest vorgegeben. Oft ist es aber auch nötig, die generierten Klassen um benutzerdefinierten Code zu erweitern. Auch hierfür müssen Mechanismen definiert werden, um die generierten Klassen so flexibel wie möglich zu machen. XML-Schema vollständig unterstützen Um die JAXB in realen Anwendungen einsetzbar zu machen, ist außerdem eine vollständige Unterstützung der W3C XML-Schemasprache ein weiteres primäres Ziel der JAXB 2.0. Denn nur durch eine vollständige Unterstützung von XML-Schema wird sich JAXB als Standard durchsetzen können. Bidirektionale Bindung unterstützen Ein weiteres wichtiges Ziel der Spezifikation ist die Unterstützung einer bidirektionalen Datenbindung, d.h., sowohl die Generierung von Java Beans aus einem bestehenden Schema als auch der umgekehrte Weg, die Generierung eines Schemas aus einem bestehenden Java Beans-Datenmodell, soll durch die JAXB 2.0 unterstützt werden. Dabei soll auch ein Round Trip möglich sein. Das bedeutet, dass für den Weg JavaXML Java, also eine Generierung eines Schemas aus einem Java-Datenmodell und die erneute Ableitung eines Datenmodells aus diesem Schema, Eingabe und Ausgabe äquivalent sind. Unterstützung von Webservices Die Generierung eines Java-Datenmodells aus einem XML-Schema, um eine Anwendung mit XML-Dokumenten interagieren zu lassen, ist sicherlich vielen ein Begriff. Aber auch
2
1.1 Ziele der umgekehrte Weg gewinnt im Zeitalter der Webservices mehr an Bedeutung. Wird eine Anwendung beispielsweise als Webservice veröffentlicht, so wird das existierende Datenmodell der Anwendung durch ein WSDL-Dokument beschrieben. Dies kann durch JAXB 2.0 automatisiert werden, indem aus dem existierenden Datenmodell ein XML-Schema generiert wird, das dann bei der Kommunikation über XML und SOAP verwendet wird. In diesem Zusammenhang sollten wir nicht verschweigen, dass die JAXB 2.0Spezifikation die Grundlage für die Bindung von WSDL an Java-Datentypen in der Java API für XML Web Services (JAX-WS 2.0) bildet. Damit soll JAXB zum Standard für Datenbindungen bei Java-basierten Webservices werden. Validierung jederzeit ermöglichen Auch die Validierung von XML-Dokumenten ist eins der Themen, die durch JAXB adressiert werden sollen. Sowohl beim Unmarshalling, also der Transformation von XMLInhalten in Java-Objekte, als auch beim Marshalling, der Transformation von JavaObjekten in XML-Dokumentstrukturen, kann eine Validierung anhand eines XMLSchemas durchgeführt werden. Die Validierung soll dabei so gestaltet sein, dass flexibel auf ungültige Inhalte reagiert werden kann. Um das Rad nicht neu zu erfinden, wird bei der Validierung auf bereits bestehende Technologie anderer Java-Spezifikationen zurückgegriffen. Schemaevolution unterstützen Ein großes Problem bei der Entwicklung von Datenbindungen an XML-Schema ist die Weiterentwicklung des XML-Schemastandards. Ein Ziel der JAXB ist daher die Unterstützung des Umgangs mit dieser sog. Schemaevolution. Portabilität zwischen JAXB-Implementierungen Um die JAXB als einen De-facto-Standard für XML-Datenbindungen zu etablieren, soll die Spezifikation in die kommende Java-Version 6.0, Codename Mustang, einfließen. Daher muss die Portabilität der generierten Klassen auf Quellcode- und Binärcode-Ebene sichergestellt werden. Portabilität bedeutet hier, dass die generierten Klassen einer bestimmten JAXB-Implementierung von jeder anderen Implementierung genutzt werden können. Portabilität des Quellcodes bedeutet, dass durch JAXB erstellter Quellcode von allen JAXB-Implementierungen verstanden wird. Der erstellte Quellcode darf außerdem keine Abhängigkeiten zu einer bestimmten Implementierung besitzen. Die Portabilität des Binärcodes macht eine erneute Kompilierung dieses Quellcodes bei einem Wechsel der Implementierung überflüssig. Dieser Ansatz unterscheidet sich von bisherigen Frameworks, die sich des Mittels des Bytecode Enhancements bedienen, um eine einfache Javabean nach dem Kompilieren um Framework-spezifische Funktionalitäten zu erweitern. Solchermaßen erweiterter Bytecode ist dann nicht mehr portierbar.
3
1 JAXB im Überblick
1.2
Entstehung Spezifizierung durch JSR 222 JAXB ist aus dem Java Community Process 1 , kurz JCP, hervorgegangen. Im JCP entwickeln Expertengruppen Spezifikationen im Bereich der Java-Entwicklung. Diese Spezifikationen werden als Java Specification Requests (JSR) betrieben und veröffentlicht. Einige bekannte JSRs sind etwa die Java 5 Generics aus JSR 14 oder typsichere Enumerationen in JSR 201. Auch für die JAXB gibt es eine Expertengruppe, deren Hauptaufgabe natürlich die Weiterentwicklung der JAXB-Spezifikation ist. Neben dieser Spezifikation in Papierform besteht ein JSR in der Regel noch aus einer Codebasis, nämlich einer Referenzimplementierung (RI) und einem Technology Compatibility Toolkit (TCK). Die Referenzimplementierung dient dazu, den aktuellen Stand der Spezifikation als Implementierung widerzuspiegeln und zu validieren. Weitere solcher Implementierungen der Spezifikation durch andere Anbieter sind natürlich möglich, sogar erwünscht. So wird mit JAX-Me2 2 eine alternative Implementierung der JAXB von der Apache Group 3 betrieben. Das TCK stellt die Kompatibilität einer JAXB-Implementierung zum JAXB-Standard sicher. Es besteht aus einer Reihe von Tests, die eine JAXB-Implementierung erfolgreich durchlaufen muss, um sich für den JAXB Standard zu zertifizieren. Entwicklung durch das JAXB Project Die Entwicklung und Verwaltung der Referenzimplementierung wird durch das JAXB Project betrieben. Das JAXB Project wurde bei Sun als Open-Source-Projekt veröffentlicht. Diese Entwicklergruppe realisiert neben der oben beschriebenen Referenzimplementierung und dem TCK noch weitere Tools zur Unterstützung rund um JAXB. Das JAXB Project ist wiederum Teil des Projekts Glassfish, das eine Referenzimplementierung der neuen Java Enterprise Edition 5 darstellt.Veröffentlichung Erstmals veröffentlicht wurde JAXB in der Version 1.0 durch den JSR 31. Seit dieser Version ist JAXB auch Bestandteil des Java Web Service Developer Packages (Java WSDP, aktuell in der Version 2.0). In diesem Buch werden wir uns ausschließlich der neuen JAXB in der Version 2.0 widmen, die einige bedeutende Neuerungen umfasst und damit einen großen Schritt in Richtung Projekttauglichkeit macht. Die JAXB 2.0 wurde im April 2006 als Final Release des JSR 222 verabschiedet. Getragen wird die Spezifikation dabei hauptsächlich von Sun Microsystems. Die Expertengruppe 1 http://www.jcp.org 2 http://ws.apache.org/jaxme 3 http://www.apache.org 4 https://jaxb.dev.java.net
4
1.3 Architektur wird jedoch noch von vielen weiteren Industriegrößen wie BEA, Oracle und SAP unterstützt. Ziel dabei ist, die JAXB-Spezifikation in Zukunft in die Java Standard Edition zu integrieren. Damit wäre JAXB die Standardlösung für Java-XML-Datenbindungen. Doch genug der Geschichte, es ist an der Zeit, einen Blick auf die Architektur von JAXB zu werfen.
1.3
Architektur Als erster Überblick sind hier die einzelnen Komponenten der JAXB-Architektur gelistet. In den folgenden Abschnitten werden wir die genannten Komponenten dann etwas näher beschreiben. Architekturkomponenten Annotationen: Die JAXB ist ein annotationsgetriebenes Framework (annotation driven). Annotationen werden dazu verwendet, die Abbildung von Java- auf XMLRepräsentation zu konfigurieren. Schema-Compiler: Der Schema-Compiler bindet ein existierendes Schema an ein generiertes Java-Datenmodell. Diese Erzeugung einer Java-Repräsentation aus einem XML-Schemadokument ist ein häufig verwendeter Weg, die Java- und XML-Welten zu verbinden. Er eignet sich besonders für dokumentgetriebene Anwendungsszenarien. Schema-Generator: Der Schema-Generator bindet ein existierendes Java-Datenmodell an ein generiertes XML-Schema. Die Information für die Abbildung der JavaElemente auf ein XML-Schema definiert der Entwickler durch Annotationen im existierenden Datenmodell. Dieser Weg eignet sich am besten für modellgetriebene Anwendungsszenarien. Binding Framework: Das Binding Framework stellt die Schnittstelle zur Anwendung dar. Es bietet eine Reihe von API-Klassen und -Funktionen, die zur Laufzeit aufgerufen werden: Die wichtigsten Funktionen sind das Marshalling und Unmarshalling. Das Unmarshalling realisiert die Transformation von XML zu Java. Das Marshalling hingegen überführt Java-Objekte in XML-Dokumentinstanzen. Begleitend zu diesen Operationen kann automatisch eine Validierung der XML-Inhalte anhand eines XMLSchemas erfolgen. Mit dem Binder schließlich kann eine transparente Bearbeitung eines XML-Dokuments über ein gebundenes Java-Datenmodell erfolgen Marshalling und Unmarshalling finden hier im Hintergrund statt.
5
1 JAXB im Überblick Compile-Zeit- vs. Laufzeitkomponenten Grundsätzlich können wir die Architekturkomponenten in zwei Bereiche aufteilen: Komponenten der Compile-Zeit: Schema-Compiler und Schema-Generator werden zusammen mit den Annotationen zur Compile-Zeit, d.h. vor Ausführung der Anwendung, benötigt. Sie stellen die Bindung durch Generieren eines Java-Datenmodells bzw. eines Schemas her. Komponenten der Laufzeit: Das Binding Framework wird zur Laufzeit von der Anwendung selbst benutzt, um XML-Inhalte zu verarbeiten. Das Zusammenspiel dieser Komponenten zeigt die folgende Architekturübersicht.
XML-Schema
Bindungsdeklarationen
Portable Java Beans
JAXB-Annotationen JAXB Annotationen
Compile-Zeitkomponenten Laufzeitkomponenten
Unmarshalling
Marshalling
Binding-Framework JAXB API
Unmarshalling
Marshalling
Validierung
XML-Dokument
Abbildung 1.1 Komponenten der JAXB-Architektur
6
Java Beans-Instanzen
1.3 Architektur
1.3.1
Annotationen
Annotationen werden, wie bereits erwähnt, durchgängig durch die JAXB verwendet, sowohl vom Schema-Compiler als auch vom Entwickler selbst. Sie werden sowohl in den generierten Java Beans-Klassen verwendet als auch im XML-Schema. Wir unterscheiden daher zwei Arten von Annotationen: Mapping-Annotationen: Bei der Generierung eines Java-Datenmodells aus einem existierenden XML-Schema versieht eine JAXB-Implementierung die generierten Klassen mit Java-Annotationen, die Informationen über die Java-XML-Bindung enthalten. Java-Annotationen sind im JSR 175 5 beschrieben und eine der Spracherweiterungen der Java Standard Edition 5. Diese Mapping-Annotationen beschreiben in JAXB die Abbildung der XML-Elemente des Schemas auf die Elemente des generierten JavaDatenmodells (z.B. Klassen, Eigenschaften etc.). Dadurch beinhaltet das generierte Datenmodell neben den Java-Klassen gleichzeitig eine Repräsentation des zugrunde liegenden Schemas. Das XML-Schema selbst wird daher zur Laufzeit nicht mehr gebraucht, da alle benötigten Informationen bereits in den Annotationen gespeichert werden. Der eigentliche Vorteil der Annotationen zeigt sich jedoch erst im umgekehrten Fall, der Bindung von XML-Daten an ein existierendes Java-Datenmodell. Hier kann der Entwickler jetzt selbst seine existierenden Java-Klassen mit Mapping-Annotationen versehen. Diese Mapping-Annotationen definieren die Bindung der Java-Klassen an die XML-Daten. Sie stellen dann implizit ein XML-Schema dar. Werden keine Mapping-Annotationen vom Entwickler angegeben, greift JAXB auf Defaultkonfigurationen zurück. Die Referenz enthält in Kapitel 7.5 eine detaillierte Auflistung aller möglichen MappingAnnotationen und ihre Auswirkung auf den erzeugten Code. Bindungskonfigurationen: Auch bei der Erzeugung eines Datenmodells aus einem existierenden XML-Schema kann die Bindung individuell konfiguriert werden. Dazu kann der Entwickler Annotationen für das XML-Schema angeben. Diese Annotationen werden Bindungsdeklarationen genannt und sind selbst wieder in XML formuliert. Dadurch kann z.B. definiert werden, dass alle generierten Java-Klassen für ein XMLSchema das Interface implementieren. Bindungskonfigurationen können definiert werden, indem ein existierendes XML-Schemadokument mit Annotationen versehen wird. Diese Annotationen werden dann nicht über Java-Annotationen, sondern direkt in XML durch das -Element formuliert. Sie können entweder im Schema selbst oder über eine externe XML-Datei übergeben werden. Wir sehen also, dass Annotationen ein grundlegendes Konzept der JAXB sind. Sie tragen zur Konfigurierbarkeit und Portabilität der Java-XML-Bindung bei.
5 http://www.jcp.org/en/jsr/detail?id=175
7
1 JAXB im Überblick
1.3.2
Der Schema-Compiler
Um eine Bindung von XML-Schema nach Java zu erstellen, generiert der SchemaCompiler portable Java-Objekte aus einem bestehenden Schema.
<xs:schema ... <xs:element ... <xs:attribute ...> <xs:annotation> ...
@XmlRootElement public class {
XML-Schema Schema-Compiler <jaxb:bindings ... <jaxb:class... <jaxb:property ...>
@XmlElement ... String element ... @XmlAttribute
Java Beans mit JAXB-Annotationen
Bindungskonfiguration Abbildung 1.2 Der Schema-Compiler
Woher weiß der Schema-Compiler nun, wie das generierte Java-Datenmodell aussehen soll? Die JAXB-Spezifikation definiert dazu für jede Komponente der W3C XMLSchemasprache eine entsprechende Java-Repräsentation, z.B. eine Java-Klasse, eine Eigenschaft oder eine Enumeration. Jedem dieser Java-Sprachelemente werden zusätzlich Mapping-Annotationen hinzugefügt, welche die entsprechende Schemakomponente beschreiben. Diese Abbildung zwischen XML-Schema und Java stellt die Standardbindung des Schema-Compilers dar. In den meisten Fällen genügt diese Standardbindung den Anforderungen einer Applikationen. In manchen Fällen sind allerdings Anpassungen der generierten Java-Klassen nötig. Hier kommen die im vorigen Abschnitt erwähnten Bindungsdeklarationen zum Einsatz. Sie überschreiben die Standardbindung des Schema-Compilers und erlauben benutzerdefinierte Abbildungen von Schemakomponenten auf Java. Formuliert werden diese Bindungsdeklarationen ebenfalls in XML. Auf die einzelnen Konfigurationsmöglichkeiten gehen wir im Rahmen des Programmiertutorials genau ein. Die folgende Tabelle gibt uns einen Überblick über die Abbildung der Schemakomponenten auf Java-Programmelemente.
8
1.3 Architektur Tabelle 1.1 Abbildungsbeziehungen zwischen XML-Schema und Java-Elementen XML-Schemakomponente
Java-Komponente
Namespace
Java-Paket
Komplexer Datentyp
Benutzerdefinierte Java-Klasse
Einfacher Datentyp
Java-Datentypen und Wrapper-Klassen
Einfacher Datentyp mit Enumeration Facet
Java-Enumeration
Unterelemente eines komplexen XML-Typ
Java-Eigenschaften der gebundenen Klasse
Das bedeutet, dass wir nach dem Ausführen des Schema-Compilers eine Ansammlung von Java-Klassen bekommen, die der Schema-Compiler in dem angegebenen Paket ablegt. Der Schema-Compiler erzeugt zusätzlich dazu noch eine Klasse . Mit dieser Klasse wird dem Programmierer, der Name verrät es bereits, eine Factory-Klasse an die Hand gegeben. Mit dieser Klasse können nun bequem Instanzen der generierten JAXB Klassen erzeugt werden. Wir werden uns die im Zusammenhang mit dem Marshalling von XML-Dokumenten noch genauer ansehen.
1.3.3
Der Schema-Generator
Der Schema-Generator verwendet die in Tabelle 1.1 definierten Abbildungsregeln im umgekehrten Sinne, um ein Schema aus einem bestehenden Java-Datenmodell zu erzeugen.
9
1 JAXB im Überblick
@XmlRootElement public class ... {
XML-Dokument @XmlElement ... String element ... @XmlAttribute
Java Beans mit JAXB-Annotationen
<xs:schema ... <xs:element ... <xs:attribute ...> <xs:annotation> ...
XML-Schema Abbildung 1.3 Der Schema-Generator
Auch híer gibt es Standardbindungen, die z.B. aus einer benutzerdefinierten Klasse einen komplexen Typen im XML-Schema erzeugen. Für die Default-Bindungen müssen keine expliziten Annotationen im Datenmodell angegeben werden, die JAXB-Spezifikation setzt in diesem Fall die Standardbindung voraus. Ein Java-Datenmodell kann auf diese Weise mit der Angabe von nur einigen wenigen Annotationen vollständig auf ein Schema abgebildet werden. Die Standardbindungen können aber durch die Angabe der oben erwähnten Mapping-Annotationen ergänzt bzw. überschrieben werden, um die Bindung entsprechend anzupassen. Der Vorteil ist, dass diese Annotationen direkt in ein existierendes Datenmodell eingefügt werden können. Wann und wie wir ein Datenmodell mit Mapping-Annotationen versehen, wird ausführlich im Programmiertutorial behandelt. Eine detaillierte Auflistung der möglichen Annotationen findet sich in der Referenz.
1.3.4
Das Binding Framework
Wie bereits erwähnt, vereint das Binding Framework die API-Funktionen der JAXB zur Verarbeitung von XML-Dokumenten zur Laufzeit. Es befindet sich im Paket der JAXB-Laufzeitbibliothek. Es bietet die folgenden Funktionalitäten, die wir in diesem Abschnitt im Detail darstellen: Umarshalling: Laden von XML. Marshalling: Speichern von XML.
10
1.3 Architektur Event-Callbacks: Nachrichten im Rahmen des Unmarshallings bzw. Marshallings verarbeiten. Validierung: Die Gültigkeit von verarbeiteten XML-Dokumenten sicherstellen. Binder: Modifizieren eines XML-Dokuments ohne explizites Laden oder Speichern. 1.3.4.1
Unmarshalling
Beim Unmarshalling wird eine XML-Schemainstanz, d.h. ein XML-Dokument, das ein gegebenes Schema als Formatbeschreibung referenziert, in einen Graph aus Java-Objekten transformiert. Dieser Objektgraph besteht aus Instanzen von Java Beans, die mit MappingAnnotationen versehen sind. Der Inhalt unseres XML-Dokuments ist somit nach erfolgtem Unmarshalling im Speicher präsent. Unsere Anwendung kann dann ohne den Einsatz einer zusätzlichen XML-API auf die Java Beans wie auf normale Objekte zugreifen. Klingt einfach, oder? Ist es auch, wie wir später im Programmiertutorial sehen werden. Ein häufiges Problem, auf das wir beim Unmarshalling stoßen, sind ungültige XMLInhalte. Es ist zwar möglich, bereits vorher über die Validierung Dokumente mit ungültigen Inhalten auszuschließen. Aber nicht immer wollen wir uns den Overhead einer Validierung leisten. Oft besitzen die XML-Dokumente auch keine besonders hohe Datenqualität, sie sind z.B. unvollständig oder fehlerhaft ausgefüllt. Trotzdem muss die Anwendung mit diesen Inhalten umgehen können. Daher wurde in JAXB 2.0 die Möglichkeit vorgesehen, ein Unmarshalling von ungültigen Inhalten vorzunehmen. Generell existieren nämlich zwei unterschiedliche Verfahren des Unmarshallings. Structural Unmarshalling: Dieses Verfahren implementiert das strikte Unmarshalling eines XML-Dokuments. Die Reihenfolge der Elemente im XML-Dokument muss exakt übereinstimmen mit der im XML-Schema definierten Reihenfolge. Trifft dies nicht zu, wird das Unmarshalling mit einer Fehlermeldung abgebrochen. Das Unmarshalling basiert also auf der Struktur des XML-Dokuments. Eventuelle Abweichungen vom zugrunde liegenden Schemadokument werden sofort aufgedeckt. Dieser Ansatz wurde in früheren Versionen der JAXB verfolgt. Er eignet sich nicht besonders für die Behandlung von ungültigen oder veränderten Inhalten. Flexible Unmarshalling: Dieses Verfahren ist weniger strikt und lässt uns auch Dokumente verarbeiten, deren Elementreihenfolge nicht hundertprozentig mit dem Schema übereinstimmt. Die Elemente werden dem Namen nach transformiert statt nach ihrer Position im Dokument. Dadurch können Dokumente verarbeitet werden, die zwar vom Schema abweichen, deren Inhalte aber dennoch ausreichende Information besitzen. Dies gilt z.B. für XML-Dokumente mit abweichender Elementreihenfolge, fehlenden oder unbekannten Elementen. JAXB 2.0 verfolgt standardmäßig ein flexibles Unmarshalling, um beispielsweise die Evolution, also die Weiterentwicklung von XMLSchemas und XML-Dokumenten zu unterstützen. Denn immer häufiger unterliegen XML-Dokumente einer ständigen Weiterentwicklung, so dass der Bedarf für ein fehler-
11
1 JAXB im Überblick tolerantes, flexibles Unmarshalling ständig größer wird. Ein konkretes Beispiel hierzu findet sich im Unmarshalling-Abschnitt des Tutorials. 1.3.4.2
Marshalling
Das Marshalling geht nun den umgekehrten Weg. Ein im Speicher vorhandener Objektgraph wird serialisiert, d.h., in ein XML-Dokument überführt. Der Objektgraph besteht dabei wieder aus Instanzen von Java Beans, die mit JAXB-spezifischen Annotationen versehen sind. Durch diese Annotationen werden die Objekte auf die zugehörigen XML-Elemente abgebildet. Die Zielstruktur, in die Objekte überführt werden, kann dabei verschiedene Formate besitzen. So kann eine JAXB-Implementierung das XML in Standardausgabeströme der Java Core API wie oder schreiben, aber auch höhere Ausgabeschnittstellen und Standards wie SAX, DOM, StAX nutzen und sogar mit Transformationen per XSL kombinieren. 1.3.4.3
Event Callbacks
Sowohl für das Marshalling als auch das Unmarshalling kann der Entwickler sog. Callback-Methoden einfügen. Diese Methoden arbeiten nach dem Hollywood-Prinzip (dont call us well call you), das u.a. durch das Spring Framework 6 populär geworden ist. Dadurch kann während des Marshallings/Unmarshallings applikationsspezifische Logik ausgeführt werden. Mit der Implementierung dieser Callback-Mechanismen werden wir uns später im Programmiertutorial noch näher beschäftigen. 1.3.4.4
Validierung
Eine Validierung von Inhalten kann eine JAXB-Implementierung automatisch sowohl beim Unmarshalling als auch beim Marshalling durchführen. Diese Validierung ist jedoch bewusst optional gehalten, um Anwendungsszenarien behandeln zu können, die keine Validierung benötigen oder wo eine Validierung der Daten nicht regelmäßig möglich ist. Beim Unmarshalling wird das zu verarbeitende XML-Dokument überprüft. Beim Marshalling dagegen muss der im Speicher existierende Objektgraph auf Gültigkeit überprüft werden. Wer jetzt denkt, die Validierung beim Unmarshalling sollte doch auch genügen, der sollte sich das Fail-fast-Prinzip ins Gedächtnis rufen. Nach diesem Entwurfsprinzip für Programmierschnittstellen sollten Fehler so früh wie möglich aufgedeckt und an die Anwendung kommuniziert werden. Diesem Prinzip folgt auch die JAXB. Das bedeutet, dass ungültige Daten bereits beim Marshalling aufgedeckt werden sollten, also noch bevor die Daten eventuell an eine andere Komponente einer Architektur weitergegeben werden.
6 http://www.springframework.org
12
1.3 Architektur Denkbar wäre z.B. eine Anfrage an einen Webservice, bei der XML-Daten einem WSDLDokument gemäß versendet werden. Eine Validierung der Daten vor der Absendung der Anfrage, beim Marshalling, hilft, Fehler dort aufzudecken, wo sie entstehen. Die Validierung führt also die Überprüfung der im XML-Schema definierten Einschränkungen und Formatvorgaben durch. Dabei können die Einschränkungen in zwei Kategorien unterteilt werden. Statische Einschränkungen: Dies sind z.B. Einschränkungen auf Datentypen wie , oder . Solche Einschränkungen werden später durch die portablen Java Beans aufrechterhalten. Verletzungen dieser Einschränkungen werden bereits durch die Java-Typüberprüfung erkannt und können zur Laufzeit bei der Umwandlung von und in XML theoretisch nie auftreten. Dynamische Einschränkungen: Einschränkungen auf Wertebereiche oder die Definition von komplexen Typen mit bestimmten Kindelementen können erst zur Laufzeit überprüft werden. Diese Validierungen können durchaus sehr aufwendig werden, da unter Umständen der gesamte Objektgraph überprüft werden muss, z.B. um die Eindeutigkeit einer XML-Element-ID zu überprüfen. Tritt nun ein Verstoß gegen eine der oben genannten Einschränkungen auf, so wird ein Fehlerereignis generiert. Das Fehlerereignis besitzt Informationen zu der Art des Fehlers und dem Ort, an dem der Fehler aufgetreten ist. Solche Fehlerereignisse können entweder sofort an die aufrufende Anwendung weitergegeben oder zunächst gesammelt und nach erfolgter Validierung als Liste weitergereicht werden. Zur Durchführung der Validierung muss im einfachen Fall ein XML-Schema angegeben werden. Die eigentliche Validierung wird dabei seit JAXB 2.0 durch die in der Java Standard Edition 5 enthaltene JAXP 1.3 API übernommen. JAXP 1.3 definiert eine StandardAPI zur Validierung von XML-Dokumentinstanzen. Dieses Outsourcing der Validierung hat den zusätzlichen Vorteil einer kompakteren Implementierung. Die Verwendung der JAXP Validation API bietet weit mehr Flexibilität und Konfigurierbarkeit als die JAXB-eigene Validierung der Version 1.0. So können beispielsweise neben den Standardmechanismen zur Behandlung der Validierung auch eigene Implementierungen der sog. Validation-Event-Handler-Interfaces verwendet werden. Dadurch kann individuell auf ganz bestimmte Fehler reagiert werden, z.B. um eine gewisse Fehlertoleranz einer Anwendung zu gewährleisten. Die Validierung ist dabei nicht auf XML-Schema festgelegt so können über entsprechende Schnittstellen auch ganz eigene Implementierungen für eine XML-Validierung beigesteuert werden. 1.3.4.5
Binder
Die -Komponente der JAXB-API kann zwei verschiedene Sichten auf ein XMLDokument gleichzeitig verwalten. Angenommen, es existiert in einer Anwendung bereits
13
1 JAXB im Überblick eine Sicht auf ein XML-Dokument, z.B. als DOM 7 . Ein DOM-Modell repräsentiert ein XML-Dokument durch Objekte, die Knoten, Elemente und Attribute darstellen. Mithilfe des Binders kann zusätzlich dazu für das gesamte Dokument oder auch nur für Teile eine zweite Sicht erstellt werden. Diese Sicht besteht aus den bereits bekannten JAXB-Klassen. Wird nun von der Anwendung eine dieser beiden Sichten verändert, kann der Binder die jeweils andere Sicht mit den Änderungen aktualisieren und so beide Sichten synchron halten. Ein häufiger Anwendungsfall ist hier die Darstellung eines Ausschnitts aus dem XMLDokument durch JAXB-Klassen. Diese JAXB-Klassen stellen die zu verändernden Teile des Dokuments dar. Das gesamte Dokument ist weiterhin als DOM-Modell verfügbar, wird aber vielleicht nur lesend verwendet. Schreibzugriffe erfolgen aufgrund der einfacheren Verwendbarkeit durch JAXB. Der modifizierbare Ausschnitt des Dokuments kann dabei z.B. durch einen XPathAusdruck dargestellt werden. Die folgende Abbildung soll die Funktion der Komponente etwas veranschaulichen.
unmarshal
Binder
update
customer
load JAXB Mapping <customer>
save
XML-Dokument
address
DOM-Baum Java Beans-Instanzen
Abbildung 1.4 Die Binder-Komponente
1.4
1.0 + 1.0 = 2.0? Die Neuerungen der JAXB 2.0 Was hat sich nun seit der Version 1.0 bei der JAXB-Spezifikation getan? Lohnt es sich, meine Anwendung zu migrieren? Ist JAXB noch komplizierter geworden als in der Vorgängerversion? Diese Fragen werden sich diejenigen stellen, die bereits mit JAXB in Berührung gekommen sind. 7 Document Object Model 8 http://www.w3.org/TR/1999/RECxpath-19991116
14
1.4 1.0 + 1.0 = 2.0? Java nach XML-Schemaabbildung Die JAXB hat sich mit der Version 2.0 fundamental verändert. Die größte Änderung dürfte die Einführung der Bindung existierender Java-Datenmodelle an XML sein. Während in JAXB 1.0 im Wesentlichen ein XML-Schema in ein Java-Datenmodell umgewandelt wurde, ist jetzt eine elegante Art der Datenbindung durch die Annotation von existierenden Klassen entstanden. In JAXB 2.0 kann eine Java-XML-Bindung konfiguriert werden, ohne dass hierbei aus einem XML-Schema mithilfe des Schema-Compilers JAXB-spezifische Klassen generiert werden müssen. Die aus Sicht der Datenbindung bestehende Einbahnstraße in JAXB 1.0 wurde sozusagen zu einer bidirektionalen Autobahn in JAXB 2.0 ausgebaut. Portabilität JAXB geht außerdem in der Version 2.0 einen fundamentalen Schritt weiter in Richtung Portabilität. In JAXB 1.0 werden aus den Komponenten eines Schemas Interfaces und deren Implementierungsklassen generiert. Diese vom Schema abgeleiteten Klassen gingen eine enge Kopplung mit der jeweiligen Implementierung der JAXB und mit der JAXBLaufzeitbibliothek selbst ein. Für die Abwärtskompatibilität werden diese Interfaces zwar weiterhin unterstützt, jedoch geht man offiziell einen anderen Weg. In JAXB 2.0 sind es die mit Annotationen versehenen portablen Klassen, die an Schemakomponenten gebunden werden. Da diese Klassen im Wesentlichen Java Beans darstellen, sind sie an keine spezifische Implementierung der JAXB gebunden. Als Entwickler arbeitet man daher mit konkreten Klassen statt der bisherigen Interfaces. Es ist möglich, ein Java-Datenmodell nachträglich und ohne wesentliche Änderung von einer JAXB-Implementierung auf eine andere zu portieren, aber auch von einer XMLDatenbindung beispielsweise an eine Datenbank-Datenbindung. Volle Unterstützung von XML-Schema Ein weiteres Manko der JAXB 1.0 ist die unvollständige Unterstützung der XMLSchemasprache. Mit der Version 2.0 ist in JAXB die volle Unterstützung der W3C XMLSchemaspezifikation angestrebt. Es können jetzt alle in W3C XML-Schema vorhandenen Konzepte in einem Java-Datenmodell abgebildet werden. Hinzugefügt wurde unter anderem die fehlende Unterstützung von Wildcards und Typsubstitutionen. Java 5-Unterstützung Dem allgemeinen Trend folgend, setzt auch JAXB 2.0 voll auf die Spracherweiterungen der Java Standard Edition 5. Zum einen sind das die erwähnten Annotationen. Aber auch Generics werden unterstützt, was eine erhöhte Typsicherheit zur Folge hat, da z.B. alle generierten Listen bereits den korrekten Typ besitzen. Auf der anderen Seite bedeutet dies
15
1 JAXB im Überblick aber auch, dass Anwendungen mit JAXB 2.0 die Verwendung der JSE 5 oder höher voraussetzen. Redesign der Validierung Auch was die Validierung angeht bietet JAXB 2.0 einige Neuerungen. Um eine flexiblere Behandlung der Validierung von XML-Dokumenten zu ermöglichen, wird in der Version 2.0 die Validierungskomponente der JAXP 1.3 API verwendet. Damit wurde die bisherige On-Demand-Validierung beim Unmarshalling ersetzt. Die Validierung kann nun optional sowohl beim Unmarshalling als auch beim Marshalling durchgeführt werden. Die neue Validierungskomponente ist wesentlich flexibler und erlaubt nun auch das Unmarshalling von teilweise ungültigen XML-Dokumenten. Dadurch wurden auch Performance-Aspekte in JAXB 2.0 adressiert. So konnte die Anzahl der generierten Klassen gesenkt und die Größe der Laufzeitbibliotheken verringert werden. Umstieg von JAXB 1.0 nach 2.0 Doch was passiert nun mit Applikationen, die auf der Version 1.0 der JAXB basieren? JAXB 2.0 beinhaltet alle nötigen Bibliotheken, die für die Ausführung von existierenden JAXB 1.0-Applikationen benötigt werden. Der Schema-Compiler der Version 2.0 ist auch in der Lage, JAXB 1.0-kompatiblen Code zu generieren. Um eine auf der JAXB 1.0 basierende Applikation auf die JAXB 2.0 zu portieren, muss allerdings das Schema mit der neuen Version des Schema-Compilers kompiliert und die Anwendung mit den generierten Klassen aktualisiert werden. Außerdem muss natürlich beachtet werden, dass JAXB 2.0 nur ab der Java-Version 5 eingesetzt werden kann. Wobei im Rahmen der Spezifikation darauf geachtet wurde, dass eine JAXB-Implementierung potenziell über entsprechende Retro-Translatoren zumindest zur Laufzeit Java 1.4Kompatibilität bieten können.
16
2 Basistechnologien In diesem Kapitel gehen wir auf die im Rahmen der JAXB verwendeten Basistechnologien XML-Schema, XPath und ANT ein.
2.1
XML-Schema XML ist seit langem eins der Standardformate, wenn es um die plattformunabhängige Repräsentation von Daten geht. Das Besondere an XML: Es werden nicht nur die reinen Daten, sondern auch die Metadaten in einem XML-Format gespeichert. Also nicht nur die Information Müller, sondern auch die Information Müller ist der Nachname eines Kunden. Rein technisch betrachtet ist XML ein sehr redundantes und aufgeblähtes Format, als Datenaustauschformat hat es den großen Vorteil, dass jeder Entwickler ein XMLDokument öffnen und lesen kann, ohne dass die Daten zusätzlich aufbereitet werden müssten. Besonders vielseitig wird XML durch die Möglichkeit, die erwarteten Daten in Form einer DTD oder eines XML-Schemas zu spezifizieren. Über solche Formatbeschreibungen lassen sich Form und Gültigkeit eines Datensatzes beschreiben: Jeder Kundendatensatz muss einen Namen angeben, eine Adressangabe ist optional möglich. In diesem Kapitel möchten wir auf die wesentlichen Möglichkeiten von XML-Schema eingehen, das sich zu dem umfangreichsten und genauesten Format zur Beschreibung von Formaten gemausert hat. Mit XML-Schema können wir Grammatiken definieren, also Einschränkungen und Regeln zu den Daten in einem XML-Dokument. Ein mit XML-Schema beschriebenes Dokument kann anhand der Grammatik auf seine Gültigkeit überprüft werden. Eine solche Validierung wird von einem Schema-Validierer durchgeführt. So lassen sich z.B. Daten beschreiben, die dem Nachrichtenaustausch zwischen verschiedenen Anwendungen dienen. Durch Verwendung eines XML-Schemas wird den einzelnen Komponenten ein einheitliches, überprüfbares Format bereitgestellt.
17
2 Basistechnologien Im Zusammenhang mit XML-Schema werden oft die Begriffe Schemadokument und Schemainstanz verwendet. Ein XML-Schemadokument definiert die oben genannte Grammatik zu den XMLDaten und legt so deren Struktur für alle Beteiligten fest. Eine XML-Schemainstanz hingegen ist ein XML-Dokument, das ein Schemadokument verwendet, um XML-Daten konform zu einer Grammatik zu beschreiben. XML-Schema wird heute von vielen XML-basierten Standards und APIs verwendet wie z.B. der Web Service Description Language (WSDL). Ursprünglich vorgeschlagen von Microsoft, wurde die Sprache schließlich 2001 als Empfehlung durch das Webkonsortium W3C 1 offiziell veröffentlicht. Im Gegensatz zu anderen Grammatikdefinitionen wie DTD , die mittels einer zusätzlichen Sprache definiert werden, basiert XML-Schema selbst auch auf XML. Weitere Vorzüge sind die Erweiterbarkeit und eine Vielzahl an vordefinierten Datentypen. Außerdem werden objektorientierte Konzepte wie Namensräume und Vererbung unterstützt. Aus diesen Gründen hat sich XML-Schema mittlerweile gegen DTD durchgesetzt. Aber auch XMLSchema hat natürlich Konkurrenz, der wichtigste Standard ist hier wohl RelaxNG, für den in JAXB bereits experimentelle Unterstützung zu finden ist. Bei der Arbeit mit JAXB wird uns XML-Schema ein ständiger Begleiter sein. Es gibt eine ganze Reihe toller Bücher zum Thema XML-Schema. Daher wird dieses Kapitel sich darauf beschränken, einen Überblick über die wichtigsten Konzepte zu geben, die wir für das unmittelbare Verständnis der Beispiele in diesem Buch für notwendig halten. Um ein XML-Schema definieren zu können, benötigen wir natürlich ein Grundverständnis für den Aufbau eines XML-Schemas. Daher wird zunächst der Schemaaufbau unter Verwendung von Namensräumen erläutert. Danach werden wir uns ausführlich der Definition von Datentypen widmen, da wir diese später mit JAXB an unsere Java-Klassen binden wollen. Ein weiteres wichtiges Thema in diesem Zusammenhang ist die Formulierung von Einschränkungen auf diesen Datentypen. Da wir natürlich mit objektorientierten Datenmodellen arbeiten wollen, darf auch die Vererbung von Datentypen in XML-Schema nicht fehlen. Vererbung ist aber nur ein Mittel zur Erweiterbarkeit von Schemas. Wir werden uns daher noch mit weiteren Mitteln zur Definition von erweiterbaren, offenen Schemas befassen. In dieser Hinsicht betrachten wir abschließend das Zusammensetzen eines Schemas aus mehreren Teilschemas, was uns eine komponentenbasierte Definition der Daten ermöglicht. Die genannten Konzepte werden wir anhand eines Objektmodells darstellen, das sukzessive im Verlauf des Kapitels aufgebaut wird. Auf diesem Domänenobjektmodell, im Englischen das Domain Object Model, werden dann alle weiteren Programmierbeispiele im Verlauf des Buches aufbauen.
1 http://www.w3.org 2 Document Type Definitions
18
2.1 XML-Schema Da eine erschöpfende Behandlung von XML-Schema weit über den Rahmen dieses Kapitels hinausgeht, sei für weitere Informationen auf das Buch XML Schema von Eric van der Vlist verwiesen. Ein XML-Schemadokument bildet also die Grundlage einer Java-XML-Bindung mit JAXB. Ein solches Schemadokument beschreibt dabei sowohl Struktur als auch Datentypen einer Schemainstanz. Im einfachsten Fall besteht ein Schemadokument aus der Definition der gültigen Elemente sowie deren Reihenfolge und Anordnung. Es lassen sich aber auch komplexe Strukturen aufbauen, die sich über mehrere Schemas erstrecken und ganze Datentyphierarchien aufbauen. Im Verlauf dieses Kapitels werden wir mehrere Schemas entwickeln und diese zu einer komplexen Struktur zusammenfügen. Zunächst aber konzentrieren wir uns auf ein einfaches Beispiel: Stellen wir uns einen Online-Banking-Service vor, der auf eintreffende XML-Requests von Clients eine entsprechende Response im XML-Format zurückliefert. Im Request wird eine Kunden-ID mitgeliefert. Als Antwort liefert der Service dann entsprechende Informationen über die Stammdaten, die Kontodaten sowie über ein evtl. vorhandenes Aktienportfolio des Kunden. Diese Daten werden z.B. aus einer Datenbank gewonnen. Die Struktur der vom Service zurückgelieferten Daten wird durch die folgenden XML-Schemas definiert. response: Enthält die Antwort des Service auf einen eingehenden Request. customerElement: Enthält die Stammdaten eines Kunden. accountElement: Enthält alle kontorelevanten Daten eines Kunden. portfolioElement: Repräsentiert das Aktienportfolio eines Kunden. Das Schema nutzt dabei die in den Schemas , und erstellten Datentypen, um eine Datenstruktur für die Ausgabe an den Client zusammenzustellen.
Abbildung 2.1 Das response-Schema
Um nicht gleich die Übersicht zu verlieren, sehen wir uns zunächst nur die Stammdaten eines Kunden an.
19
2 Basistechnologien
Abbildung 2.2 Stammdaten eines Kunden
Für diese Daten sieht ein einfaches Schemadokument nun so aus:
Das Dokument oben legt für den Kunden also ein Element mit den entsprechenden Unterelementen für die Stammdaten an. Das schema-Element Sehen wir uns zunächst den Kopf dieses Dokuments ein wenig genauer an. Ein Schemadokument beginnt immer mit dem einleitenden Wurzelelement . Im obigen Beispiel sind in diesem -Element zwei weitere Attribute definiert, die beide jeweils eine URL als Wert enthalten.
20
2.1 XML-Schema Diese Attribute definieren verschiedene Namensräume in unserem Schemadokument. Diese werden gewöhnlich im einleitenden -Element definiert. Ihre Verwendung wird im nächsten Abschnitt behandelt. Sehen wir uns zunächst noch der Vollständigkeit halber ein entsprechendes XML-Dokument zu unserem Schemadokument an.
Dieses Dokument nutzt also das vorher definierte -Element, um einen Kundendatensatz zu beschreiben.
2.1.1
Namespaces verwenden
In der Einleitung wurde erwähnt, dass durch XML-Schema Grammatiken definiert werden. Eine Grammatik setzt grundsätzlich auf einem bestimmten Vokabular auf. Solche Vokabulare können wir in XML-Schema durch Definition von Elementen frei bestimmen. Was aber, wenn sich unsere Datentypen mit den Definitionen anderer Vokabulare überschneiden? Das Problem solcher Namenskonflikte wird in XML-Schema durch die Verwendung von Namensräumen, den Namespaces, gelöst. Die XML-Spezifikation definiert einen Namespace wie folgt: An XML namespace is a collection of names, identified by a URI reference [RFC2396], which are used in XML documents as element types and attribute names. Ein Namespace schafft also einen gemeinsamen Kontext für eine Menge von Elementen, Attributen und Datentypen. Ziel dabei ist es, eine einheitliche und eindeutige Bezeichnung der Elemente zu ermöglichen. Vergleichbar zu einem XML-Namespace ist ein Java-Paket, in dem Java-Klassen gruppiert werden, um Namenskonflikte zu verhindern und den Klassen einen gemeinsamen Kontext zu geben. Das Standardvokabular von XML-Schema besitzt z.B. den Namespace http://www.w3.org/2001/XMLSchema, der auch schon im einleitenden Beispiel verwendet wurde. Dieser Namespace enthält die grundlegenden Elemente zur Definition neuer Elemente, Attribute und Typen. Wenn wir nun weitere Elemente definieren, können wir diese einem anderen, von uns gewählten Namespace zuordnen. Bei der Benennung unserer Elemente bleiben wir dadurch völlig frei. 2.1.1.1
Namespaces definieren
Eine Namespace-Definition wie beispielsweise
setzt sich zusammen aus:
21
2 Basistechnologien Dem Schlüsselattribut ; dieses Attribut kennzeichnet eine Namespace-Definition. Einem optionalen Präfix, z.B. ; das Präfix kennzeichnet die Zugehörigkeit eines lements zu einem bestimmten Namespace, z.B. gehört das Element zum oben angegebenen XML-Schema-Namespace. Ein Präfix stellt also eine Art Abkürzung für den URI eines Namespace dar. Einem URI 3 , z.B. http://jaxb.transparent/customer, der dem Präfix zugewiesen wird. Dieser URI stellt einen eindeutigen Bezeichner für den Namespace dar. 2.1.1.2
XML-Schema und Target-Namespace
Es steht uns frei, eigene Namespaces in einem Dokument zu definieren.. Wir können auch ein Schemadokument erstellen, das keine benutzerdefinierten Namespaces enthält, ein sogenanntes stand-alone schema. Ein Schemadokument muss jedoch zumindest auf den XML-Schema-Namespace verweisen: Dieser Namespace definiert nämlich die Standardelemente von XML-Schema, die wir zur Definition eines Schemadokumentes nutzen können, z.B. , , , , , etc. Nach allgemeiner Konvention erhält er zumeist das Präfix oder . Da wir später in unserer Anwendung mit mehreren verschiedenen Schemas arbeiten, bietet es sich jedoch an, für jedes Schema stets einen eigenen Namespace zu definieren. Dies hilft, später Elemente eindeutig voneinander abzugrenzen. Um die in einem Schemadokument definierten Elemente, Attribute und Datentypen mit einem bestimmten Namespace zu verknüpfen, deklarieren wir diesen Namespace als sog. Target-Namespace im Wurzelelement. Der Target-Namespace lautet für unser Kundenschema beispielsweise http://jaxb.transparent/customer. Definitionen von Datentypen in einem Schemadokument beziehen sich immer auf genau einen Namespace, den Target-Namespace. Ein Schema darf daher genau einen TargetNamespace definieren. Elemente, die dieses Schema neu definiert, werden automatisch mit diesem Namespace assoziiert. Geben wir keinen Target-Namespace an, so landen die hier definierten Schemaelemente im Standard-Namespace und können von anderen Schemas nicht mehr eingebunden werden. Auf das obige Beispiel bezogen bedeutet dies, dass das Element automatisch dem Namespace http://jaxb.transparent/customer zugeordnet wird. 2.1.1.3
Namespaces in XML-Dokumenten verwenden
Bisher haben wir im Schemadokument einen eigenen Namespace durch den TargetNamespace definiert. Wie können wir nun angeben, dass die Elemente in einer Schema-
3 Uniform Resource Identifier
22
2.1 XML-Schema instanz diesen Target-Namespace verwenden? In unserem Kundenbeispiel sieht das folgendermaßen aus:
Im öffnenden Tag des Kundenelements geben wir den URI des Namespace an.
Als Präfix ist hier angegeben, d.h., Elemente mit dem Präfix werden diesem Namespace zugeordnet. Solche Namespace-Deklarationen können für Elemente an beliebiger Stelle in einem XML-Dokument vorgenommen werden. Durch das einleitende Element und das dazugehörende Attribut haben wir das -Element mit dem Namespace http://jaxb.transparent/customer assoziiert. Nun müssen wir noch den Ort des Schemadokuments angeben, auf das sich Elemente aus dem Namespace http://jaxb.transparent/customer beziehen. Dazu deklarieren wir zunächst den XMLSchema-instance-Namespace. Dieser Namespace erlaubt uns, ein existierendes Schemadokument zu referenzieren.
Das Element dieses Namespace gibt den Ort des Schemadokuments an. Es kann sich hier wie im Beispiel um einen lokalen Ort handeln oder auch um eine Internetadresse.
Mehrere Definitionen werden einfach durch Leerzeichen getrennt, die Anzahl der hier definierten Elemente muss daher gerade sein (Namespace + Schemadokument). Wird ein Schemadokument in einem XML-Dokument wie oben beschrieben referenziert, kann dieses Dokument gegen das angegebene Schema validiert werden. Der Schema-Validierer versucht bei der Validierung, das Schemadokument vom angegebenen Ort zu laden und die Schemainstanz gegen dieses Dokument auf Gültigkeit zu überprüfen. 2.1.1.4
Einen Default-Namespace verwenden
Bisher haben wir für jeden Namespace ein Präfix vergeben, so auch für den XML-SchemaNamespace das Präfix . Wir können jedoch auch einen Namespace ohne Präfix definieren, dieser ist dann der Default-Namespace für das aktuelle Dokument. Im folgenden Bei-
23
2 Basistechnologien spiel definieren wird den XML-Schema-Namespace als Default-Namespace. Wir können daher das Präfix vor den -, - und Tags weglassen, da präfixlose Tags mit dem Default-Namespace assoziiert werden.
Noch einmal zum Merken: Mit dem Default-Namespace werden alle präfixlosen Tags verknüpft. Mit dem Target-Namespace werden alle Definitionen von Elementen, Datentypen und Attributen eines Schemas verknüpft. Das obige Schemadokument ist komplett äquivalent zum folgenden Schemadokument, das allerdings keinen Default-Namespace verwendet. Es ist hier eher eine Frage des Stils, welche Variante verwendet wird. Bei der Verwendung mehrerer Namespaces in einem Dokument ist es jedoch übersichtlicher, alle Namespaces mit einem Präfix zu versehen.
2.1.2
Elementdeklarationen in XML-Schema
Bisher haben wir uns mit der allgemeinen Definition und den Namespaces eines Schemadokuments beschäftigt. Um die in unserer Anwendung verwendeten XML-Daten auf ihre Gültigkeit prüfen zu können, müssen wir erst einmal die Struktur der Daten in unserem Schemadokument festlegen. Innerhalb des -Elements werden wir daher die Elemente und Attribute definieren, aus denen sich ein Kundendatensatz zusammensetzt. Dabei soll die Adresse des Kunden noch in Straße, PLZ, Ort und Land unterteilt werden.
24
2.1 XML-Schema 2.1.2.1
Elemente
Für unseren Kundendatensatz verwenden wir zunächst Elemente einfachen Typs, d.h. Elemente, die keine anderen Elemente oder Attribute beinhalten können. Diese Elemente können direkt wie folgt definiert werden.
Ein Element wird also über seinen Namen und Datentyp definiert. Dabei können als Datentyp sowohl vordefinierte XML-Schema-Datentypen (mit dem Präfix ) verwendet werden als auch beliebige benutzerdefinierte Datentypen. Oben haben wir bereits einige der gängigen XML-Schema-Datentypen benutzt wie z.B. , und . Darüber hinaus stellt XML-Schema noch eine Vielzahl weiterer Datentypen bereit. Diese können in der Datentypreferenz der XML-Schema Recommendation 4 des W3C nachgeschlagen werden. Die zu unserem Schemadokument gültigen Elemente einer Schemainstanz sehen wie folgt aus:
Standardwerte Es lassen sich auch Standardwerte für Elemente definieren. Wird einem solchen Element kein Wert zugewiesen, so trägt es den im Attribut angegebenen Wert. Ein Beispiel:
Wenn also das so deklarierte Element in einer Instanz dieses Schemas nicht vorhanden sein sollte, so wird an dessen Stelle ein Element mit dem Wert Germany angenommen. 2.1.2.2
Attribute
Die Elemente in einem Schema können natürlich auch Attribute definieren. Die Definition eines Attributs gleicht der eines Elements einfachen Typs:
4 z.B.
unter http://www.w3.org/TR/xmlschema-2/
25
2 Basistechnologien
Beispielsweise könnte ein Attribut für das Element existieren, das die zu einer Straße gehörige Hausnummer angibt. Die Definition im Schemadokument lautet folgendermaßen:
Innerhalb des Elements geben wir ein Element an, das unsere Attributdefinition enthält. Als Typ des Attributs ist eine positive Ganzzahl angegeben. bezeichnet eine benutzerdefinierte Struktur, die weitere Elemente und Attribute enthalten kann. Wir werden dieses Konstrukt im nächsten Abschnitt noch näher beleuchten. Wir sehen aber, dass Elemente mit Attributen 1. immer durch das -Konstrukt beschrieben werden. 2. Attribute selbst als einfache Typen definiert sind. Da das Element einerseits eine Zeichenkette mit dem Straßennamen enthalten soll, zusätzlich aber auch noch ein XML-Attribut, müssen wir das Attribut bei der Definition des setzen. Setzen von gibt an, dass der komplexe Typ sowohl Text als auch Attribute enthält. Das Attribut In der obigen Attributdefinition ist außerdem noch das Attribut gesetzt. Dieses Attribut spezifiziert, dass das Element auf jeden Fall ein Attribut enthalten muss. Mögliche Werte für sind: required: Das Attribut muss für dieses Element vorhanden sein. optional: Das Attribut kann für dieses Element vorhanden sein. prohibited: Das Attribut darf in diesem Element nicht auftreten. Hinweis
Das -Attribut kann nur gesetzt werden, wenn wie oben ein Attribut innerhalb eines Elements definiert wird. Nur hier ergibt es auch einen Sinn, da sich immer auf das äußere Element bezieht.
Im XML-Dokument sieht unser Element mit dem neuen Attribut nun wie folgt aus:
26
2.1 XML-Schema 2.1.2.3
Komplexe Datentypen
Bisher haben wir für unseren Kundendatensatz eine flache Struktur aus einfachen Elementen mit vordefinierten Datentypen benutzt. Da unsere Anwendung aber Objekte verwendet, um Daten zu repräsentieren, z.B. ein Adressobjekt mit Straße, PLZ, Ort und Land, erweitern wir diese flache Datenstruktur durch einen benutzerdefinierten, komplexen Typ. Angenommen, wir wollen ein XML-Format definieren, bei dem ein Kunde einen komplexen Typ definiert. Die verschachtelten XML-Elemente sehen dann wie folgt aus:
Durch Definition entsprechender komplexer Typen können wir diese verschachtelte Struktur darstellen.
In diesem Listing haben wir die einzelnen Adressenelemente durch ein Element ersetzt, das die Adressdaten in einem komplexen Element vom Typ kapselt. Der komplexe Typ ist im gesamten Schemadokument wiederverwendbar, da er auf der Hauptebene mit dem -Konstrukt definiert wurde. Strukturierte Typen per complexType deklarieren Solche komplexen Typen, d.h. Elemente, die andere Elemente oder Attribute enthalten können, werden durch das Element definiert.
27
2 Basistechnologien
Der Typ wird durch einen Namen eindeutig identifiziert. Innerhalb des Elements können beliebig viele weitere Elemente und Attribute definiert werden. Komplexe Elemente können auch weiter verschachtelt werden. In unserem Beispiel verwenden wir außerdem noch ein weiteres Element , das die Ordnung der Unterelemente in unserem Adressobjekt bestimmt. Ordnung mit sequence, all, choice Durch Angabe eines der Schlüsselwörter , oder können wir bestimmen, wie die Elemente eines komplexen Typs verwendet werden sollen. Im obigen Beispiel wird durch Angabe des Sequenzindikators zum Beispiel die Reihenfolge bestimmt, in der die Elemente eines Adressobjekts im XML-Dokument auftreten müssen. Tabelle 2.1 zeigt die möglichen Schlüsselwörter und ihre Auswirkung auf die Ordnung komplexer Typen. Tabelle 2.1 Ordnen von Elementen mit sequence, all und choice Schlüsselwort
Effekt
Im XML-Dokument müssen die Elemente in der hier angegebenen Reihenfolge auftreten.
Im XML-Dokument müssen alle Unterelemente dieses Elements auftreten. Die Reihenfolge ist jedoch beliebig.
Im XML-Dokument darf eines dieser Elemente auftreten, nicht jedoch mehrere.
In unserer Beispielanwendung können wir diese Indikatoren nutzen, um die Konsistenz der Kundendaten zu gewährleisten. Beispielsweise möchten wir festlegen, dass in einem Kundendatensatz alle Adressdaten, d.h. Straße, PLZ, Ort und Land, vorhanden sein müssen, während die Angabe eines Wertes für E-Mail oder Telefon genügen soll. Außerdem soll der Name des Kunden immer am Anfang stehen, gefolgt von den Adressdaten und den übrigen Kontaktdaten. Das passende Schemadokument zu diesen Anforderungen sieht folgendermaßen aus:
28
2.1 XML-Schema
Wir haben hier also den Indikator für die Reihenfolge der Kundendaten verwendet. Der Indikator legt fest, dass alle Adresselemente ausgefüllt sein müssen, während der Indikator die Angabe eines der Elemente oder verlangt. Eine gültige Schemainstanz zeigt das folgende Listing.
Das Schemadokument im vorigen Listing weist allerdings noch eine Besonderheit im Vergleich zu den bisherigen Dokumenten auf. In XML-Schema gibt es verschiedene Arten, einen Datentyp zu definieren. Lokale vs. globale Definition Bisher haben wir unser Kundendatenelement folgendermaßen definiert:
Der komplexe Typ wurde einfach innerhalb des Elements definiert. Dies nennt man lokale Definition oder auch Inlining. Dieser anonyme, komplexe Typ ist nur innerhalb des Elements sichtbar. Im letzten Schemadokument wurde das Element jedoch so definiert:
29
2 Basistechnologien
In diesem Fall wird ein komplexer Typ mit dem Namen in der Wurzel des Schemadokumentes definiert und bei der Definition des Elements über das Attribut referenziert. Diese globale Definition ermöglicht es, den Typ auch in anderen Elementen zu verwenden. Das Gleiche gilt natürlich auch für Elemente, die Attribute enthalten, wie in Abschnitt 2.1.2.2 beschrieben. Die Definition der Datenstruktur für die Stammdaten der Kunden sei damit abgeschlossen. Nun brauchen wir eine weitere Datenstruktur, die Informationen über das Konto eines Kunden darstellt. Hier kommt eine weitere Möglichkeit ins Spiel, Datentypen zu definieren: die verfeinerten Basisdatentypen oder Simple Types. 2.1.2.4
Basisdatentypen verfeinern
XML-Schema bietet die Möglichkeit, auch für einfache Datentypen weitergehende Typinformationen zu definieren die sogenannten Simple Types. Diese definieren also keine Unterelemente im Sinne eines strukturierten Datentyps, sondern schränken die möglichen Werte eines der Basisdatentypen wie , etc. ein. Beispielsweise können wir hier festlegen, dass für ein Feld nur eine Zahl zwischen 3 und 13 angegeben werden darf oder etwa dass ein Wert nur A, B oder C sein darf. Solche Restriktionen auf einem der Basisdatentypen können wir mit einer Simple-Type-Deklaration in einem XML-Schema definieren. Die Informationen, die unsere Beispielanwendung über ein Kundenkonto liefert, definiert Daten wie Kontonummer, Kontotyp, das aktuelle Saldo und einen eingeräumten Dispokredit. Das zu definierende -Schema für die Kontoinformationen hat also die folgende Struktur:
Abbildung 2.3 Konto-Datenstruktur
Diese Konto-Datenstruktur definieren wir durch ein komplexes Element . Die entsprechende Schemadefinition lautet:
30
2.1 XML-Schema
Zur Abbildung dieser Datenstruktur reichen eigentlich einige Elemente mit vordefinierten Typen wie oder . Allerdings möchten wir gerne die einzelnen Elemente mit Einschränkungen versehen. Beispielsweise soll der Kontotyp nur eine der folgenden Zeichenketten STUDENT, BASIC oder PREMIUM enthalten. Daher definieren wir den Datentyp als und fügen ein -Element hinzu. Mit dem -Element können wir dann später die benötigten Einschränkungen auf dem angegebenen Basisdatentyp definieren. Zunächst geben wir nur den Basisdatentyp für den Kontotyp mit an. T
Definition Sehen wir uns zunächst die allgemeine Definition einer -Deklaration an.
Wie im ersten Beispiel bereits gezeigt, können wir bei der Definition dieses Datentyps Einschränkungen auf einem Basisdatentyp über das -Element definieren. Statt eines vordefinierten Typs wie ist hier auch die Erweiterung eines anderen benutzerdefinierten Datentyps möglich. Im -Element werden durch Einfügen sogenannter Facets die Einschränkungen definiert. Im Folgenden wollen wir uns einige dieser Facets näher anschauen. 2.1.2.5
Einschränkungen mit Facets
In XML-Schema besitzt jeder einfache Datentyp eine Reihe von typischen Facets. Der Datentyp z.B. besitzt die Facets . Im Folgenden sehen wir uns beispielhaft einige dieser Facets an.
31
2 Basistechnologien Enumeration Für die Einschränkung des Kontotyps können wir das -Facet verwenden. Die oben beschriebene Definition des Kontotyps sieht im Schemadokument jetzt folgendermaßen aus:
Die drei gültigen Werte für den Kontotyp werden also als Einschränkung des Basistyps definiert. Eine gültige Schemainstanz kann in einem Element vom Typ daher nur einen der angeführten Werte angeben. Reguläre Ausdrücke Eine weitere Einschränkung auf den Kontodaten gilt für die Kontonummer. Sie ist als ganzzahliges Attribut des Kontoelements folgendermaßen definiert:
Nun reicht die einfache Restriktion auf eine ganze Zahl nicht aus, üblicherweise sind Kontonummern in Deutschland maximal neunstellig und nie negativ. Die Kontonummer soll daher genau neun Stellen mit Ziffern zwischen 0 und 9 besitzen. Dies kann durch eine Einschränkung mit dem -Facet erreicht werden.
Das -Facet definiert beliebige reguläre Ausdrücke auf einem Basisdatentyp wie z.B. oder . Es kann daher sehr flexibel zur Definition von Wertemengen genutzt werden. Die folgenden Beispiele sollen die vielseitige Nutzbarkeit dieser Ausdrücke illustrieren.
32
2.1 XML-Schema Tabelle 2.2 Reguläre Ausdrücke Regulärer Ausdruck
Beispiel
Chapter 1
b, ab, aab, aaab
b, ab
xb, yb, zb
Wertebereiche Eine weitere gebräuchliche Einschränkung ist die Angabe eines Wertebereiches. Für den Dispokredit legen wir daher einen Wertebereich von 010000 Euro fest.
Mit diesen simplen Datentypen für Kontonummer, Kredit und Kontotyp ist unsere einfache Konto-Datenstruktur definiert. Hier noch einmal das gesamte Schemadokument in der Übersicht:
33
2 Basistechnologien Es sei noch einmal auf die Möglichkeit hingewiesen, dass auch Attribute global definiert werden können. Das Attribut für die Kontonummer wird hier inline definiert. Es könnte aber genauso auf oberster Ebene definiert werden.
Dies ermöglicht eine Wiederverwendung des Attributes, indem auf das global definierte Attribut mit dem -Tag verwiesen wird.
Eine gültige Schemainstanz zu unserem -Schemadokument sieht nun folgendermaßen aus:
34
2.1 XML-Schema
2.1.3
Vererbung
Abbildung 2.4 Das Aktienportfolio
Vererbung und Aufbau von Typhierarchien sind heute in objektorientierten Programmiersprachen sehr verbreitet. Auch in XML-Schema können wir Datentypen definieren, die hierarchisch aufeinander aufbauen. Sehen wir uns dazu einen weiteren Aspekt unserer Beispielanwendung an. Bisher liefert unser Online-Banking-Service die Stammdaten und Kontoinformationen zu einem Kunden. Besitzt ein Kunde zusätzlich noch ein Aktienportfolio, wird auch dieses
35
2 Basistechnologien ausgegeben. Dabei enthält das zugegeben sehr einfache Portfolio Wertpapiere und Optionen sowie den Gesamtwert aller im Portfolio enthaltenen Wertpapiere und Optionen. Die Datentypen und in Abbildung 2.4 repräsentieren die Optionen und Wertpapiere. Eine Wertpapierposition besitzt grundsätzlich einen Namen, eine Wertpapierwährung sowie die Anzahl der vorhandenen Stücke und den daraus resultierenden Wert. Eine Option soll die gleichen Elemente besitzen mit dem Unterschied, dass für Optionen ein zusätzlicher Optionstyp und ein Auslaufdatum angegeben ist. Der Optionstyp gibt an, ob die Option auf einem Wertpapier, einer Währung oder einem Index (z.B. DAX, NASDAQ etc.) definiert ist. Da eine Option damit eine Spezialisierung eines Wertpapiers darstellt, bietet es sich an, dem Datentyp die Elemente aus zu vererben, indem wir von ableiten. Den Datentyp für ein Wertpapier definieren wir wieder mit einem .
Um die Elemente dieses Datentyps für den Datentyp wiederverwenden zu können, geben wir als Basis des Typs den Datentyp an. Ausgehend von dieser Basis kann dann die Erweiterung mit zusätzlichen Elementen erfolgen.
Im obigen Listing wurde mit dem Element angegeben, dass dieser Datentyp eine Erweiterung des Datentyps darstellt. Innerhalb eines -Elements können wie in einem normalen weitere Elemente definiert werden. In unserem Fall sind dies die Elemente und . Der Optionstyp stellt dabei wiederum eine Enumeration dar.
36
2.1 XML-Schema
2.1.4
Kardinalitäten
Nachdem nun die erforderlichen Datentypen für das Aktienportfolio definiert sind, muss noch definiert werden, wie diese im Portfolio verwendet werden. Abbildung 2.4 ist zu entnehmen, dass das Portfolio eine beliebige Anzahl an Optionen und Wertpapieren enthalten kann. Dies wird durch Angabe der Kardinalität angegeben. In unserem Schemadokument definieren wir daher jeweils ein Element für Optionen und Wertpapiere.
Durch die Angabe der Attribute und bei der Elementdeklaration geben wir eine Unter- und Obergrenze für die Anzahl der erlaubten Elemente an. Der Wert für wird standardmäßig auf 1 gesetzt, falls das Attribut nicht angegeben wird. Nach Deklaration der Elemente für Wertpapiere und Optionen sieht das gesamte Schemadokument für unser Aktienportfolio wie folgt aus:
37
2 Basistechnologien
2.1.5
Offene Schemas definieren
Ein XML-Schema als Datenbeschreibung definieren wir häufig zu Beginn der Anwendungsentwicklung. Da wir als Schemaentwickler aber nicht zwangsläufig auch Hellseher sind, können wir unmöglich sofort ein Schema entwickeln, das allen später auftretenden Anforderungen gewachsen ist. Der Nutzer unseres Schemas sollte idealerweise die Möglichkeit haben, seine Instanzdokumente mit zusätzlichen Elementen und Attributen zu erweitern, die nicht zum Schema gehören. Das Schema muss also ein offenes Design besitzen. Für die Entwicklung offener Schemas eignen sich die im Folgenden beschriebenen Elemente und , die im XML-Schema-Sprachgebrauch Wildcards genannt werden. 2.1.5.1
Beliebige Elemente mit xs:any
Das Element definiert eine Art Platzhalter für ein beliebiges Element aus einem angegebenen Namespace. Stellen wir uns vor, wir möchten in einer XMLDokumentinstanz zu unserem -Schema einen dritten, noch nicht genau definierten Wertpapiertyp erlauben. Dieser Typ kann durch ein -Element dargestellt werden. Im Schemadokument für das Portfolio würde das so aussehen.
Der Nutzer unseres Schemadokuments kann jetzt das -Element in seinem Instanzdokument mit irgendeinem weiteren Element neben den schon definierten Optionsund Wertpapierelementen erweitern. Die einzige Beschränkung ist, dass er für das zusätzliche Element ebenfalls ein Schema angeben muss. Das könnte für ein zusätzliches Element , das ein Rentenpapier darstellt, so aussehen.
38
2.1 XML-Schema
Damit das neue Element vom Schema-Validierer akzeptiert wird, geben wir hier also ein entsprechendes Schema unter dem Namespace an. Doch nicht immer haben wir ein solches Dokument zur Verfügung. Wenn wir das neue Element ohne die Angabe des Schemadokuments einfügen wollen, müssen wir für das Element das Attribut setzen. Dieses Attribut steuert das Verhalten der Schema-Validierung für das -Element wie folgt. "": Dies ist die Standardeinstellung. Das für an-
gegebene Element muss auf jeden Fall gegen ein Schema validiert werden. "": In diesem Fall wird validiert, wenn ein Schema gefunden
werden kann, ansonsten wird dieses Element bei der Validierung übersprungen. "": Hier wird das Element gar nicht validiert, daher kann
ein völlig beliebiges Element ohne Schemaangabe deklariert werden. Die folgende Definition lässt also eine beliebige Anzahl Elemente aus irgendeinem Namespace zu.
2.1.5.2
Beliebige Attribute mit xs:anyAttribute
Das Gegenstück zu ist bei den Attributen die Wildcard . Es definiert eine Wildcard zur Ersetzung durch beliebige Attribute. Das Attribut können wir hier analog benutzen, wie für oben beschrieben. Ein Beispiel wäre die Definition beliebiger Attribute für unsere Kundendatensätze. Die Attribute werden in diesem Beispiel nicht validiert, da wir angeben.
39
2 Basistechnologien Ein gültiges XML-Dokument für diesen Kundentyp ist beispielsweise das folgende.
Hier definieren wir die Attribute und für unsere Kunden. Das erste Attribut definiert eine Kundengruppe und das zweite Attribut, ob es sich um einen Neukunden handelt. 2.1.5.3
any-Erweiterungen auf Namespaces einschränken
Die erlaubten Elemente und Attribute innerhalb einer - bzw. Deklaration lassen sich auch feiner einstellen. Mit dem Attribut können wir die innerhalb der Deklaration erlaubten Namespaces einschränken. Haben wir eine solche Einschränkung definiert, dann wird bei der Schema-Validierung überprüft, ob die hier vorkommenden Elemente innerhalb der angegebenen Namespaces definiert sind. Der Wert von ist eine Liste von URI-Bezeichnern der erlaubten Namespaces. Es gibt jedoch einige Schlüsselwörter, die ebenfalls angegeben werden können. : Bezeichnet den Target-Namespace des aktuellen Schemadoku-
ments. : Nur lokale Elemente/Attribute ohne Namespace dürfen angegeben werden. : Ein beliebiger Namespace ist möglich. : Ein beliebiger Namespace mit Ausnahme des Target-Namespace darf ver-
wendet werden. Die Werte und können dabei zusammen mit expliziten URI-Bezeichnern angegeben werden, während die Werte und als Ersatz für eine URI-Liste angegeben werden.
2.1.6
Namespaces importieren und referenzieren
Bisher haben wir die einzelnen Datentypen unserer Anwendung isoliert voneinander betrachtet: Kundendaten, Kontodaten und Aktienportfolio wurden in separaten Schemas definiert. Als Antwort soll unser Service die gesamten Daten in einem einzigen XMLDokument liefern. Dazu wollen wir ein Schemadokument definieren, das alle bisher definierten Schemas einbezieht, um die Struktur dieser Antwort festzulegen.
40
2.1 XML-Schema Was wir hier aufbauen wollen, ist also ein Überschema, das eine Komposition der bisher definierten Schemadokumente darstellt. Abbildung 2.5 zeigt eine Übersicht über ein solches Schema.
Abbildung 2.5 Schemadokument der Antwort des Online-Banking-Service
Im Folgenden werden wir uns zwei verschiedene Wege anschauen, wie eine solche Komposition von Schemas erstellt wird.
41
2 Basistechnologien 2.1.6.1
Komposition eines Schemas mit include
Der einfachste Weg, um auf Elemente anderer Schemadokumente zuzugreifen, ist die Verwendung des Elements . Dieses Element erwartet lediglich die Angabe des Ortes, an dem sich das Teilschema befindet.
Mit dem folgenden Codefragment könnten wir unsere Teilschemas in das Antwortschema der Anwendung integrieren:
Elemente und Datentypen der angegebenen Schemas können wir nun genauso verwenden, als hätten wir alle Elemente in einem einzigen Schemadokument definiert. Diese Definition funktioniert jedoch nur, wenn alle Dokumente den gleichen Namespace verwenden, nämlich den des Gesamtschemas http://jaxb.transparent/response. Daher erwartet das -Element auch keine Angabe eines eigenen Namespace für das einzufügende Schemadokument. Für einfache Szenarien ist die Verwendung von ausreichend. Schemadokumente mit verschiedenen Namespaces müssen allerdings über den -Befehl zusammengefügt werden. 2.1.6.2
Komposition eines Schemas mit import
In XML-Schema bietet uns das -Tag die Möglichkeit, ein Schemadokument aus Elementen und Datentypen anderer Schemadokumente zusammenzusetzen. Dabei werden die Teilschemas im Gesamtschema referenziert. Auf diese Weise stehen im Gesamtschema alle Elemente und Datentypen der Teilschemas zur Verfügung.
Für unsere Anwendung geben wir daher je ein -Element für Kunden-, Kontound Portfolioschema an. Neben dem Namespace, der importiert werden soll, geben wir auch den Ort des Schemadokumentes an.
Das folgende Schemadokument zeigt, wie wir die importierten Elemente aus den Teilschemas nutzen können. Dazu legen wir im Element je ein Präfix für die impor-
42
2.1 XML-Schema tierten Namespaces an. Über diese Präfixe können wir dann die importierten Typen referenzieren, z.B. , . Das Dokument definiert also ein Element , das einen Kundendatensatz, Kontoinformationen und ein Portfolio enthält. Die Reihenfolge der Elemente wird dabei wieder über ein Element festgelegt.
Hinweis
- und -Elemente müssen immer vor den Typdefinitionen und Elementdeklarationen eines Schemadokuments stehen.
2.1.7
Eindeutigkeit durch ID und IDREF
Das Konzept der Eindeutigkeit von Datensätzen ist aus dem Bereich der Datenbanken hinlänglich bekannt. Beim Erstellen eines Datenbankschemas verwenden wir Schlüsselattribute, mit denen wir einen Datensatz eindeutig referenzieren können. Auch in XMLDokumenten gibt es die Möglichkeit, die Eindeutigkeit von Datensätzen zu gewährleisten. Im DTD-Format gibt es dafür die Datentypen und , die nur auf Attributebene definierbar sind. Die W3C XML-Schemaspezifikation hat diese Datentypen sozusagen geerbt, allerdings mit dem Unterschied, dass wir dort sowohl Attribute und Elemente mit den Datentypen und definieren können. Bevor wir diese Elemente einzeln näher beschreiben, hier noch einige Eigenschaften, die sie gemeinsam besitzen: Ihr Wertebereich ist der gleiche wie der des Datentyps , d.h., Ziffern am Anfang und das Auftreten von Leerzeichen sind innerhalb der Elemente und verboten. Nachteil hierbei: Häufig gibt es Nummern, z.B. Kontonummern, die von Natur aus eindeutig sind, diese können allerdings nicht direkt als verwendet werden. Die Werte von und müssen global eindeutig sein, d.h., ein ID-Wert darf nicht im selben Dokument zur Identifikation von zwei Elementen verwendet werden.
43
2 Basistechnologien xs:ID Ein Element oder Attribut vom Typ stellt einen eindeutigen Bezeichner für das zugehörige Element dar. Das folgende Schemafragment definiert ein Element mit einem Unterelement , das einen Kunden eindeutig identifiziert.
Die folgende Tabelle zeigt dazu jeweils eine gültige und eine ungültige Schemainstanz. Tabelle 2.3 Schemainstanzen mit xs:ID Gültiger Inhalt
Ungültiger Inhalt
John Johnny
John John
xs:IDREF Elemente oder Attribute dieses Typs definieren eine Referenz auf ein Element, das wiederum eine ID mittels definiert. Im Folgenden definieren wir daher ein Schemafragment, das den eben definierten Kunden um eine Liste mit Freunden erweitert. Diese Freunde sind wiederum vom Typ und werden durch die ID-Referenz referenziert.
Die folgende Tabelle zeigt dazu jeweils eine gültige und eine ungültige Schemainstanz.
44
2.1 XML-Schema Tabelle 2.4 Schemainstanzen mit xs:IDREF Gültiger Inhalt
Ungültiger Inhalt
Johnny John Johnny
Johnny John Ben
2.1.8
Dokumentation mit Annotationen
Die in unseren Beispielen immer wieder verwendeten XML-Schemadokumente des virtuellen Online-Banking-Service sind in diesem Kapitel ausführlich beschrieben. Wie bekommen aber nun Entwickler, welche die hier definierten XML-Formate in ihren Anwendungen einsetzen wollen, Informationen über den Aufbau der gelieferten Daten? Zum einen könnten wird die Schemadokumente veröffentlichen. Dies würde jedoch nur Informationen über die technische XML-Struktur liefern. Es wäre sehr praktisch, wenn wir das XML-Format auch mit weiteren Informationen versehen könnten: Beispielsweise mit Kommentaren, aber auch mit Informationen, die eine spätere technische Verarbeitung der XML-Dokumente beeinflussen. Auch dies kann in ein XML-Schemadokument integriert werden. Über sogenannte Annotationen können wir Dokumentation für Mensch und Maschine in ein Schema einfügen.
Das -Element erlaubt das Einfügen von natürlichsprachlicher Dokumentation über das -Element. Im Element dagegen werden Informationen für Programme angegeben, die ein Schema verarbeiten. Annotationen können allerdings nicht an jeder beliebigen Stelle in ein Schemadokument eingefügt werden. Gültige Orte für Annotationen sind: vor oder nach einer globalen Komponente bei lokalen Komponenten (z.B. innerhalb eines Elements) nur am Anfang
45
2 Basistechnologien
Das -Element werden wir später bei der Verwendung von JAXB noch häufiger benötigen. Für JAXB können wir nämlich im -Tag einer Annotation benutzerdefinierte Konfigurationseinstellungen vornehmen, welche die Bindung eines JavaDatenmodells an ein XML-Schema näher definieren. Im obigen Beispiel ist bereits eine solche Einstellung dargestellt.
2.1.9
Das fertige Beispielschema
In den vorangegangenen Abschnitten haben wir alle nötigen Datenstrukturen für den in unseren Beispielen immer wieder verwendeten Online-Banking-Service durch XMLSchemadokumente definiert. Dabei sind wir ausführlich auf die für uns wichtigen Elemente von XML-Schema eingegangen. An dieser Stelle wollen wir noch einmal alle Schemadokumente auflisten. Diese Auflistung wird uns während der restlichen Kapitel als Referenz dienen. 2.1.9.1
Das response-Schemadokument
2.1.9.2
Das customer-Schemadokument
46
2.1 XML-Schema
2.1.9.3
Das account-Schemadokument
2.1.9.4
Das portfolio-Schemadokument
47
2 Basistechnologien
2.2
XPath Der Begriff XPath lässt uns sicher an Dateisysteme denken, die Pfade verwenden, um Dateien zu lokalisieren. XPath verfolgt ein ganz ähnliches Prinzip, nur dass mit den Pfaden hier Knoten in XML-Dokumenten lokalisiert werden. Denn oft stehen wir vor Fragen wie: Wo befindet sich das Element X mit der Eigenschaft Y in den 100 Datensätzen meines XML-Dokuments? Eine Möglichkeit wäre, mit einer der XML-APIs das gesamte XML-Dokument zu traversieren, bis wir auf den gewünschten Knoten stoßen. Das ist allerdings sehr mühsam und in einer Welt von 1001 APIs auch reine Zeitverschwendung. XPath bietet nämlich eine recht intuitive und vor allem mächtige Schnittstelle zur Beschreibung von Elementen und Werten in XML-Dokumenten. Daher ist XPath mittlerweile in einer ganzen Reihe von XMLAPIs präsent. So finden wir XPath in den Standards XSLT, XML-Schema und XQuery 5 wieder und auch in der JAXB. In den folgenden Seiten wollen wir einen kleinen Über-
5 http://www.w3.org/XML/Query
48
2.2 XPath blick über den Standard XPath geben. In JAXB werden nämlich XPath-Ausdrücke bei der Konfiguration der Datenbindung von XML nach Java verwendet. Wie seine großen Geschwister XML und XML-Schema ist auch XPath ein Standard des Webkonsortiums W3C 6 . Mit XPath können wir in deskriptiver Form Informationen über XML-Elemente in einem Dokument abfragen (Wie viele Elemente vom Typ B?) als auch die Inhalte der Elemente/Attribute selbst (Textinhalt von Element B?). In dieser Hinsicht besitzt XPath eine gewisse Ähnlichkeit zu den -Statements in SQL, mit denen Inhalte einer Tabelle abgefragt werden können. XPath alleine ist jedoch bei weitem nicht so mächtig wie SQL dessen Mächtigkeit erreicht vielleicht die Abfragesprache XQuery, die ihrerseits wiederum auf XPath-Ausdrücken basiert. Im folgenden Abschnitt beschränken wir uns auf die Konzepte von XPath, die wir für die sinnvolle Verwendung in JAXB kennen und anwenden müssen.
2.2.1
Die XPath-Sicht auf XML
Ein XML-Dokument wird in XPath als eine Baumstruktur angesehen, die aus miteinander verknüpften Knoten besteht. Knoten können aus XML-Konstrukten wie Elementen, Attributen und Namensräumen bestehen. Die Knoten wiederum besitzen Werte, die als Text dargestellt sind. Ein Beispiel:
Im obigen Dokument gibt es die folgenden Knoten: Dokumentknoten: bildet das Wurzelelement des Dokuments Elementknoten: , Wert Attributknoten: , Wert Namespace-Knoten: , Wert Die Knoten besitzen Beziehungen untereinander, wie wir sie aus einem Stammbaum kennen. Der Knoten besitzt z.B. die Kinder (children nodes) , , und . Er selbst stellt den Elternteil (parent node) für diese Knoten dar. Außerdem gibt es noch die Geschwisterknoten (sibling nodes), die im Fall des Knotens z.B. , und sind. Generationsübergreifend gibt es dann 6 http://www.w3.org
49
2 Basistechnologien noch Vorfahren (node ancestors}, das sind für den -Knoten und . Umgekehrt sind die Knoten und Nachfahren des Knotens. Mit dieser Sicht auf ein XML-Dokument wollen wir uns jetzt den XPathAusdrücken zuwenden.
2.2.2
XPath-Ausdrücke verwenden
Mit Xpath-Ausdrücken können wir Knoten in einem solchen XML-Baum beschreiben. Die Syntax von Xpath-Ausdrücken ähnelt der Syntax zur Beschreibung von Dateien in den gängigen Dateisystemen. Gültige XPath-Ausdrücke für das obige Dokument sind beispielsweise die folgenden:
Auch wenn die Syntax zunächst vertraut scheint, gibt es einige semantische Unterschiede zwischen der Lokalisierung von XML-Knoten in einem XML-Dokument und der Lokalisierung von Dateien im Dateisystem. Im ersten Teil eines XPath-Ausdrucks wählen wir einen oder mehrere Knoten aus dem XML-Dokument aus. Knotenauswahl Die folgende Tabelle zeigt die Elemente der XPath-Syntax, die uns für die Knotenselektion zur Verfügung stehen. Tabelle 2.5 Elemente der XPath-Syntax
50
Element
Bedeutung
Beispiel
Selektiert alle Kindknoten des angegebenen Knotens
liefert die Knoten , , und .
Bezeichnet den Wurzelknoten des Dokuments.
liefert den Wurzelknoten zurück. Stellt immer einen absoluten Pfad dar.
Selektiert alle passenden Knoten liefert alle Adressunabhängig von ihrem Ort. knoten, die unterhalb des aktuellen Knotens liegen.
Bezeichnet den aktuellen Knoten.
. liefert z.B. , falls dies der aktuelle Knoten ist.
Bezeichnet den Elternknoten des aktuellen Knotens
ist wieder der -Knoten.
Selektiert ein Attribut.
liefert die Hausnummer zurück.
2.2 XPath Bedingungen formulieren mit Prädikaten Die oben dargestellten Syntaxelemente liefern alle Knoten mit einem bestimmten Namen zurück, doch in manchen Fällen wollen wir einen Knoten präziser beschreiben beispielsweise wollen wir nur den ersten Knoten einer Liste abfragen oder nur Knoten, auf die eine bestimmte Bedingung zutrifft. Mit sogenannten Prädikaten können wir aus einer Menge von Knoten eine Teilmenge selektieren. Prädikate werden durch einen booleschen Ausdruck in eckigen Klammern definiert. Zurückgeliefert werden dann alle Knoten, auf die der boolesche Ausdruck zutrifft. Tabelle 2.6 XPath-Ausdrücke mit Prädikaten Ausdruck
Bedeutung
Liefert den ersten Kunden aus einer Kundenliste.
Liefert den Kunden mit der E-Mail-Adresse
Liefert den Straßennamen mit dem HausnummerAttribut 44 zurück. In unserem Fall BeerStreet.
Liefert die Kunden mit Postleitzahlen größer 4000 zurück.
Wildcards Wildcards können angegeben werden, wenn wir den genauen Elementnamen nicht kennen oder einfach alle Elemente, Attribute oder sonstige Knoten auswählen wollen. Tabelle 2.7 Wildcard-Ausdrücke Wildcard
Bedeutung
Listet alle Adresselemente.
Listet alle Attributknoten im aktuellen Pfad.
Listet alle beliebigen Knoten, dazu gehören auch Namespace-Angaben, Kommentare etc.
2.2.3
Beispiele
Da wir immer am schnellsten durch konkrete Beispiele lernen, führen wir hier die gängigsten XPath-Ausdrücke auf, die wir für die Arbeit mit JAXB benötigen werden. Die Ausdrücke beziehen sich dabei auf das folgende XML-Schemadokument, denn auch Schemas sind wiederum XML-Dokumente, die wir im Verlauf des Buches sogar hauptsächlich bearbeiten werden.
51
2 Basistechnologien Das Schemadokument
Auswahl des Wurzelknotens XPath-Ausdruck: (absoluter Pfad) Selektion:
Auswahl aller global definierten, komplexen Typen XPath-Ausdruck: (absoluter Pfad) Selektion:
Auswahl aller Elemente XPath-Ausdruck: (relativer Pfad) Selektion:
52
2.2 XPath
Auswahl des Elements zipcode XPath-Ausdruck: (relativer Pfad) Selektion:
2.2.4
XPath in Java
Bisher haben wir uns XPath als W3C-Standard angesehen. Der Standard ist zunächst unabhängig von einer bestimmten Integration in eine Programmiersprache. Wie sieht es nun aus, wenn wir XPath in einem Java-Programm einsetzen wollen? Im Java-Umfeld haben wir eine ganze Reihe von XPath-Implementierungen zur Auswahl. Einige Beispiele sind Xalan 7 , Saxon 8 und Jaxen 9 , die allesamt Open-Source-Projekte darstellen. Seit Java 5 stellt aber auch Sun mit der JAXP 1.3 eine XPath-API bereit, die den Vorteil besitzt, dass
7 http://xalan.apache.org 8 http://saxon.sourceforge.net 9 http://www.jaxen.org
53
2 Basistechnologien wir keine zusätzlichen Bibliotheken benötigen, um XPath-Ausdrücke zu benutzen. Daher stellen wir hier nur diese Variante vor. Eine Baumsicht erstellen Um XPath-Ausdrücke in Java anzuwenden, benötigen wir zuerst eine Java-Sicht auf unser XML-Dokument. Am einfachsten erreichen wir dies durch Parsen des Dokuments in eine DOM-Struktur, wie im folgenden Beispiel dargestellt.
Hier wird also das oben gezeigte XML-Schemadokument in ein Objekt vom Typ überführt, das unseren XML-Baum darstellt. Auf diese Baumstruktur können wir jetzt XPath-Ausdrücke anwenden.
XPath-Abfragen erzeugen und ausführen Für eine simple XPath-Abfrage mit JAXP 1.3 genügt der folgende Vierzeiler.
Wir erzeugen also auch hier eine Factory, die uns ein XPath-Abfrageobjekt bereitstellt. Diesem Objekt übergeben wir dann das XML-Dokument, den XPath-Ausdruck sowie den Typ, den wir als Rückgabe erwarten. Die Methode wertet die Abfrage aus und gibt die Ergebnisse zurück. In diesem Fall besteht das Ergebnis aus dem Wurzelknoten. Namespace-Präfixe auflösen Eine Zeile haben wir im vorigen Beispiel noch nicht beachtet.
Die Methode übergibt eine Kontextklasse, die eventuell im Dokument vorhandene Namespace-Präfixe, wie im obigen Beispiel z.B. das Präfix , in den korrekten Namespace auflöst bzw. umgekehrt. Eine solche Klasse erstellen wir, indem wir das Interface implementieren. Ein Beispiel für die Implementierung einer solchen Kontextklasse sehen wir hier.
54
2.3 ANT
Ohne einen solchen Namespace-Kontext kann die XPath-Implementierung keine Namespace-übergreifenden Dokumente auswerten.
2.3
ANT ANT ist eine Java-basierte XML-Sprache zur Erstellung von Build-Skripten. Ein BuildSkript nimmt dem Entwickler die alltäglichen Aufgaben wie Kompilieren, Packen und Verteilen von Quellcode ab. Damit lässt sich der Build-Prozess, also der Prozess von der Kompilation des Quellcodes bis zum Deployment und Test der gesamten Anwendung vollständig automatisieren. Vielleicht denken einige jetzt an das -Tool in UnixUmgebungen. ANT ist genau das, nur ist es nicht an eine bestimmte Plattform gebunden, da es vollständig auf Java basiert. Was hat das nun alles mit JAXB zu tun? Nun, eine Bindung von XML nach Java wird bei JAXB durch die Generierung von Java-Klassen realisiert. Im umgekehrten Fall, der Bindung von Java nach XML, kann das zugehörige XML-Schema generiert werden. Diese Generierung passiert zumeist vor der Laufzeit unserer Anwendung und gehört damit zum Build-Prozess. Um nicht jedes Mal mühsam die Generierung durch Aufruf der JAXBKommandos in einer Shell anzustoßen, nutzen wir ein ANT-Plugin, das diese Arbeit für uns übernimmt. Aber zunächst sehen wir uns die allgemeine Funktionsweise von ANT an.
2.3.1
ANT-Übersicht
Ähnlich wie bei anderen Build-Werkzeugen wird auch bei ANT ein Skript aufgerufen, das den gesamten Build-Prozess abarbeitet. Dieses Skript wird entsprechend auch Build-Skript
55
2 Basistechnologien genannt und ist, wie könnte es anders sein, in XML geschrieben. Neben dem Build-Skript sollten wir noch einige andere Begriffe kennen: Projekt: Jedes Build-Skript definiert ein Projekt, das den Build-Prozess für eine bestimmte Anwendung darstellt. Das Projekt definiert ein Standard-Target (Default Target). Falls kein anderes Target beim Aufruf angegeben ist, wird dieses Target aufgerufen. Target: Ein Projekt enthält also mindestens ein Target, das beim Ausführen des BuildSkriptes gestartet wird. Ein Target compile kann beispielsweise die Kompilierung des Quellcodes übernehmen, ein anderes Target deploy den kompilierten Quellcode in ein JAR-Archiv verpacken. Targets sind so konfiguriert, dass sie sich untereinander aufrufen. So können wir Abhängigkeiten zwischen den Targets definieren, etwa dass das Target deploy von dem Target compile abhängt. So kann z.B. das Verpacken des Codes erst nach einer vorherigen Kompilation der Quellen stattfinden. Man sagt hier die Verpackung ist abhängig von der Kompilierung. Dabei werden die einzelnen Targets möglichst nur ausgeführt, wenn sich relevante Quellen geändert haben. Task: Die Funktionen, die innerhalb eines Targets aufgerufen werden, sind in Tasks definiert. Ein Task kann z.B. den Java-Compiler aufrufen, den JAXB SchemaCompiler oder den Schema-Generator. Es gibt viele bereits in ANT eingebaute Tasks, die von einem Target aus aufgerufen werden können. Benutzerdefinierte Tasks, wie z.B. die JAXB Tasks, müssen erst im Build-Skript definiert werden, bevor sie benutzt werden können.
2.3.2
Installation und Aufruf
ANT kann entweder im Binärformat oder als Quellcode von der ANT-Webseite 10 heruntergeladen werden. Der Einfachheit halber werden wir im Folgenden die Existenz eines fertig kompilierten Binärpakets voraussetzen. Dies liegt zumeist in gepackter Form z.B. als ZIP-Archiv vor. Dieses ZIP-Archiv besitzt die folgende Verzeichnisstruktur: bin: Enthält die ANT-Kommandozeilenskripte. docs: Umfangreiches Benutzerhandbuch und API-Dokumentation etc: Enthält XSL-Erweiterungen. lib: Optionale ANT-Tasks und Abhängigkeiten Installation Unabhängig vom verwendeten Betriebssystem sind zur Installation von ANT die folgenden Schritte notwendig: 1. Entpacken des ANT-Archivs in ein beliebiges Verzeichnis. 2. Das Unterverzeichnis bin in den Pfad aufnehmen. 10 http://ant.apache.org
56
2.3 ANT 3. Eine Umgebungsvariable mit dem Namen ANT_HOME erstellen, die auf das Installationsverzeichnis zeigt. 4. Falls noch nicht vorhanden, eine Umgebungsvariable JAVA_HOME erstellen, die auf das Installationsverzeichnis des JDK zeigt. Aufruf Per Kommandozeile können wir nun den ANT-Befehl aufrufen. Die folgende Syntax führt das unter [] angegebene Target des Projekts in der Datei build.xml aus.
Wenn das Build-Skript den Standardnamen trägt, kann der Parameter auch weggelassen werden.
2.3.3 2.3.3.1
Häufig verwendete Elemente Properties definieren
Es ist häufig sinnvoll, in einem Build-Skript Variablen als Platzhalter zu definieren, da z.B. Verzeichnisnamen an vielen Stellen im Skript benötigt werden. Um Redundanz zu vermeiden, können diese Verzeichnisnamen mit einer -Deklaration als Variablen in einem ANT-Skript definiert werden:
Dieses Beispiel definiert eine Property , die auf das Verzeichnis zeigt. Im gesamten Skript kann diese Variable nun durch Angabe von referenziert werden. 2.3.3.2
javac-Task
Um Quellcode zu kompilieren, existiert in ANT der -Task, dem ein Quell- und ein Zielverzeichnis sowie ein Klassenpfad übergeben werden. Der Task kompiliert JavaQuellcode und legt diesen im Zielverzeichnis ab.
2.3.3.3
Der Klassenpfad
Für einige Tasks, wie oben bei , muss in ANT ein Klassenpfad definiert werden. Dafür kann zum einen explizit das -Element mit einem verschachtelten Element benutzt werden:
57
2 Basistechnologien
Eine andere Möglichkeit ist die Referenzierung eines existierenden Pfades. Das folgende Beispiel legt einen Pfad mit dem -Element an und verweist bei der Definition des Klassenpfades auf den vorher definierten Pfad. Dadurch kann die redundante Definition von Klassenpfaden vermieden werden:
2.3.4
Benutzerdefinierte Tasks
Eines der mächtigen Merkmale von ANT ist, dass wir die verfügbaren Elemente durch eigene Task-Implementierungen nach dem Baukastenprinzip erweitern können. Um ANT mit benutzerdefinierten Tasks zu erweitern, muss der betreffende Task im aktuellen Projekt erst definiert werden. Dies kann über die folgenden zwei Definitionen geschehen:
Bei der Task-Definition werden ein Name für den Task, der vollständige Klassenname der Implementierung und der zugehörige Klassenpfad angegeben. Im obigen Beispiel wird einmal ein Klassenpfad innerhalb der Definition angelegt und beim anderen Mal ein existierender Pfad verwendet. Damit ist der Task vollständig definiert und kann im weiteren Verlauf unter dem angegebenen Namen verwendet werden.
2.3.5
xjc und schemaGen Tasks
Für die komfortable Integration der JAXB-Funktionalität in einen ANT-basierten BuildProzess gibt es zwei Tasks: den -Task und den -Task. Ersterer integriert den Schema-Compiler und Letzterer den Schema-Generator von JAXB in ANT. Wie diese Tasks definiert werden, ist in der Referenz genau beschrieben. Sie werden bei der Referenzimplementierung der JAXB in einem eigenen JAR-Archiv mitgeliefert. Sehen wir uns daher ein Beispiel an, das diese Task-Definition in einem Gesamtkontext verwendet.
58
2.3 ANT
2.3.6
Ein Beispiel
Das obige Listing zeigt ein Projekt mit dem Namen , das ein Standard-Target definiert. Dieses Target automatisiert den gesamten Build-Prozess vom Aufruf des Schema-Compilers über das Kompilieren der Anwendung bis zum Archivieren der Anwendung in einer JAR-Datei. Dabei sind die Targets über Abhängigkeiten per verknüpft, d.h., zuerst wird das -Target aufgerufen, dann und erst zuletzt . Zu Beginn des Skripts werden einige Variablen definiert, die Verzeichnisse darstellen, die in den einzelnen Targets benutzt werden. Außerdem wird der Klassenpfad zu den JAXB-Bibliotheken global definiert, da der Java-Compiler und Schema-Compiler beide diesen Pfad benötigen. Das Skript führt nun die folgenden Aktionen aus: Generieren der JAXB-Java Beans aus dem Schema in das Verzeichnis Kompilieren der JAXB-Klassen und des übrigen Anwendungscodes in das Verzeichnis
59
2 Basistechnologien Verpacken der kompilierten Klassen in ein JAR-Archiv namens Als Datei abgespeichert, lässt sich dieses Skript nun von der Kommandozeile mit dem folgenden Befehl aufrufen:
Nutzer der weitverbreiteten integrierten Entwicklungsumgebung Eclipse können solche Build-Skripte natürlich auch bequem mit der in Eclipse integrierten ANT-Unterstützung starten. Für weiterführende Informationen über die Verwendung von ANT sei auf die sehr ausführliche Dokumentation in der ANT-Distribution verwiesen. Der Aufruf des SchemaGenerators funktioniert ähnlich. Hier nur ein kurzes Listing mit dem entsprechenden Aufruf.
60
3 Hallo JAXB! In den vorangegangenen Kapiteln haben wir uns mit den Voraussetzungen und den theoretischen Grundlagen zur JAXB beschäftigt. Jetzt wenden wir uns endlich der Praxis zu. Dieses Kapitel zeigt, wie wir eine funktionierende JAXB-Entwicklungsumgebung aufsetzen, mit der wir die Beispiele in dem folgenden Programmiertutorial erstellen und nachvollziehen können. Anhand des allseits bekannten Hallo Welt!-Beispiels probieren wir diese Entwicklungsumgebung gleich aus. Das Hallo Welt!-Beispiel wird uns gleichzeitig einen ersten Überblick über die Vorgehensweise bei der XML-Java-Datenbindung mit JAXB geben. Die dabei verwendeten API-Komponenten sind im folgenden Programmiertutorial detailliert erklärt. Wir werden daher in diesem ersten Beispiel nicht näher darauf eingehen, sondern uns nur auf einen Round-Trip, also eine Bindung von Java nach XML und zurück, konzentrieren.
3.1
Systemvoraussetzungen Bevor wir loslegen können, brauchen wir einige Softwarepakete auf unserem System: Java Standard Edition 5: JAXB 2.0 benötigt die Java Standard Edition 5 (JSE5) oder besser, da JAXB intensiven Gebrauch von den in Java 5 eingeführten Annotationen und Generics macht. Die aktuelle Version der JSE5 ist unter der folgenden URL erhältlich: http://java.sun.com/javase/downloads/index.jsp Apache ANT: Für den Schema-Compiler und den Schema-Generator existiert in der JAXB-Referenzimplementierung eine Unterstützung durch ANT-Tasks. Diese müssen in einem Projekt nicht zwangsläufig genutzt werden, sind aber wesentlich komfortabler als die Kommandozeilenvariante. Daher ist es ratsam, in der JAXBEntwicklungsumgebung mit ANT zu arbeiten. ANT ist erhältlich unter der folgenden URL: http://ant.apache.org/. Wir haben dem Thema ANT im vorigen Kapitel eine kurze Einführung gewidmet. Dort ist auch die Installation ausführlich beschrieben. JAXB Reference Implementation: In diesem Buch setzen wir auf der zurzeit aktuellen JAXB Reference Implementation 2.0.2 von Sun auf. Diese bietet die momentan voll-
61
3 Hallo JAXB! ständigste Implementierung der JAXB-Spezifikation und ist erhältlich unter der folgenden URL: https://jaxb.dev.java.net/ Eine alternative JAXB-Implementierung gibt es von Apache mit JaxMe2, die unter unten stehender URL bezogen werden kann. Hier kann es im Detail zu Unterschieden zwischen den in diesem Buch beschriebenen Funktionalitäten und denen der durch die JaxMe2-Implementierung gebotenen Funktionalität. Die Implementierung erhalten Sie unter http://ws.apache.org/jaxme/
3.2
Die Entwicklungsumgebung einrichten 3.2.1
JAXB-Referenzimplementierung installieren
Nachdem wir die aktuelle Referenzimplementierung von Sun bezogen haben, sollten wir eine Datei in der Form in Händen halten, metaphorisch wohlgemerkt. Dieses JAR-Archiv enthält die komplette JAXB-Distribution. Installiert wird dieses Paket mit einem Aufruf von der Kommandozeile, z.B.
Nach Akzeptieren der Lizenzvereinbarung wird der Inhalt in die folgende Verzeichnisstruktur des aktuellen Verzeichnisses entpackt. jaxb-ri-20060607 bin: Enthält die Kommandozeilenskripte für Schema-Compiler/-Generator docs: Dokumentation und Javadoc der API lib: JAXB-Bibliotheken und Quellcode-Archive samples: Beispielanwendungen mit JAXB
3.2.2
Die JAXB-Bibliotheken einbinden
Für die Arbeit mit der JAXB-Referenzimplementierung müssen sich die JAR-Bibliotheken aus dem lib-Verzeichnis der Installation auf dem Klassenpfad unserer Anwendungen befinden. Die JAXB-Referenzimplementierung enthält die folgenden JAR-Bibliotheken. jaxb-api.jar: API-Klassen der JAXB-Spezifikation jaxb-impl.jar: Klassen der JAXB-Referenzimplementierung jsr173_1.0_api.jar: XML Streaming API, z.B. activation.jar: Abhängigkeit zur Java Beans Activation API jaxb-xjc.jar: XJC Schema-Compiler und Schema-Generator jaxb1-impl.jar: JAXB 1.0-Kompatibilitätsklassen Für unsere Beispielanwendungen benötigen wir die folgenden Bibliotheken zur Laufzeit.
62
3.2 Die Entwicklungsumgebung einrichten jaxb-api.jar jaxb-impl.jar jsr173_1.0_api.jar activation.jar Um Klassen und Schemas zu generieren, benötigen wir die folgende Bibliothek zur Compile-Zeit auf dem Klassenpfad. jaxb-xjc.jar Für Anwendungen, die auf der Version 1.0 der JAXB aufsetzen, muss noch die Bibliothek jaxb1-impl.jar hinzugefügt werden. Wir fügen also im Standardfall die ersten fünf Bibliotheken der Umgebungsvariablen CLASSPATH hinzu. Damit sind wir auf der sicheren Seite. Hinweis
Wenn wir mit einer Entwicklungsumgebung wie Eclipse arbeiten, ist es hilfreich, die ebenfalls im lib-Verzeichnis enthaltenen Quellcode-Archive auf den Klassenpfad zu legen. Dadurch können bequem die Javadoc-Kommentare zu den einzelnen Komponenten eingesehen werden.
3.2.3
Die Struktur des Beispielprojekts
Wir empfehlen sehr, die in diesem Buch enthaltenen Beispiele praktisch auszuprobieren. Der dafür erforderliche Quellcode kann in Form eines Beispielprojekts auf der Webseite dieses Buchs heruntergeladen werden. An dieser Stelle sei daher kurz der Aufbau des Beispielprojekts dargestellt. Die Codebeispiele sind nach Kapiteln in einzelne Pakete unterteilt. Unter den Kapitelpaketen sind die Beispiele zu einzelnen Abschnitte in eigenen Unterpaketen zusammengefasst. Das Java-Paket, in dem sich der Quelltext befindet, ist im Quelltextauszug stets mit abgedruckt. Die Ordner des Beispielprojekts besitzen den folgenden Inhalt. src: Quellcode nach Kapiteln unterteilt src-gen: durch den Schema-Compiler generierte Klassen lib: JAXB-Bibliotheken schema: Verzeichnis mit XML-Schemas und XML-Dokumenten schema-gen: durch den Schema-Generator erzeugte XML-Schemas build.xml: Build-Skript für das Banking-Service-Schema buildHelloWorld.xml: Build-Skript für das Hallo Welt!-Beispiel Hinweis
Eclipse-Nutzer können das Beispielprojekt ganz einfach importieren und direkt verwenden, ohne den Klassenpfad verändern zu müssen. Auch ist hier die ANT-Unterstützung schon eingebaut, so dass dem Entwickler einiges an Konfiguration abgenommen wird.
63
3 Hallo JAXB!
3.3
Am Anfang steht immer: Hallo Welt! Nachdem die Entwicklungsumgebung jetzt rein technisch lauffähig ist, können wir unser erstes JAXB-Beispiel in Angriff nehmen. Dazu bedienen wir uns des Klassikers der Informatikliteratur, nämlich einer Variante des Hallo Welt!-Beispiels. Es dreht sich bei diesem Beispiel schlicht und ergreifend um ein XML-Dokument, das ein Element mit einer entsprechenden Nachricht enthält. Anhand dieses Beispiels werden wir die möglichen Szenarien der Datenbindung durchspielen. Zuerst werden wir eine existierende Java Bean an ein XML-Schema binden. Danach gehen wir den umgekehrten Weg und erzeugen aus einem bestehenden XML-Schema eine entsprechende JavaRepräsentation. Um den Schema-Compiler bzw. Schema-Generator aufzurufen, bedienen wir uns der entsprechenden ANT-Tasks.
3.3.1
Der Weg von Java zu XML
Nehmen wir einmal an, wir besitzen als Ausgangspunkt eine simple Java Bean, die wir gerne an ein XML-Datenformat binden wollen. Die Java Bean heißt und hat die folgende Struktur.
Das ist so weit nichts Ungewöhnliches. Um diese Java Bean an ein XML-Element binden zu können, müssen wir mit JAXB nur eine einzige Änderung machen. Durch Hinzufügen einer Annotation lassen wir JAXB wissen, dass es sich bei dieser Klasse um das Wurzelelement eines XML-Dokuments handelt. Die Klasse sieht jetzt folgendermaßen aus.
64
3.3 Am Anfang steht immer: Hallo Welt!
Das ist auch schon alles, was wir verändern müssen. Für die Eigenschaften der Java Bean und die später erzeugten Namen im XML-Dokument vertrauen wir auf die Standardeinstellungen der JAXB-Implementierung. Mit dem folgenden kleinen Beispielprogramm erzeugen wir jetzt aus einem -Objekt ein entsprechendes XML-Dokument.
Nachdem wir einige Initialisierungen vorgenommen haben, erzeugen wir hier einfach eine neue Instanz der Klasse , die als Nachricht den Wert Hello world! bekommt. Diese Instanz übergeben wir einer -Instanz, die für uns die Transformation übernimmt. Das fertige XML-Dokument wird dann auf der Konsole ausgegeben.
Das war ein Kinderspiel oder? Im Prinzip wird aber auch bei komplizierteren Bindungen nach dem gleichen Schema vorgegangen. Existierende Java Bean mit Annotationen versehen Instanz der Java Bean erzeugen Instanz an den übergeben und XML-Dokument ausgeben
65
3 Hallo JAXB! Apropos Schema, wo ist hier eigentlich die Bindung an ein XML-Schema, von dem vorher gesprochen wurde? Nun, JAXB erzeugt hier aus der Java Bean ein Schema, das wir uns bei Bedarf auch ausgeben lassen können. Dafür gibt es neben dem programmatischen Weg über die API oder die Kommandozeile auch einen ANT-Task, den wir an dieser Stelle kurz vorstellen wollen. Wir geben hier wirklich nur ein kurzes Beispiel an, die einzelnen Konfigurationsmöglichkeiten des -Tasks sind in der Referenz beschrieben. Schema erzeugen mit dem schemaGen-Task
Mit diesem kleinen ANT-Skript können wir jetzt das Schema durch Ausführen des Targets erzeugen und in der angegebenen Datei abspeichern. Das zu unserer kleinen Java Bean passende Schema hat nun die folgende Struktur.
66
3.3 Am Anfang steht immer: Hallo Welt!
3.3.2
Der Weg von XML zu Java
Der vorangegangene Abschnitt hat die Bindung einer Java-Klasse an ein XML-Schema gezeigt. Dieser Weg existiert erst seit JAXB 2.0. Jetzt sehen wir uns den klassischen Weg an, nämlich die Bindung eines existierenden XML-Schemas an ein generiertes JavaDatenmodell. Ausgangspunkt ist hier jetzt das XML-Schema statt der Java Bean.
Das Schema beinhaltet die gleiche Information wie zuvor unsere Java Bean. Ein Element wird als Wurzelelement deklariert und besitzt ein Unterelement , das die eigentliche Nachricht beinhaltet. Aus diesem Schema werden wir jetzt unsere JavaRepräsentation erzeugen. Dazu bemühen wir den Schema-Compiler, der genau wie der Schema-Generator über die API, über die Kommandozeile und einen ANT-Task gestartet werden kann. Das folgende Beispiel zeigt, wie wir den Schema-Compiler mit einem kleinen ANT-Skript dazu bringen, Java-Code aus dem obigen Schema zu generieren.
67
3 Hallo JAXB!
Die genaue Funktion des ANT-Tasks ist recht ausführlich in der Referenz beschrieben, daher wollen wir hier nur kurz darauf eingehen. Mit dem Target wird der SchemaCompiler für ein gegebenes Schema gestartet. Der Schema-Compiler liest das Schema und leitet daraus die entsprechenden Klassen ab. Führen wir dieses Skript nun aus, erhalten wir die folgende Klasse HelloWorld.
Wir sehen, dass der Schema-Compiler noch einige weitere Annotationen zu dieser Klasse hinzufügt, als wir das im letzten Abschnitt selbst getan haben. Ansonsten ist die Klasse aber identisch mit unserer selbst geschriebenen Java Bean . Daher können wir auch genauso damit umgehen, denn immerhin handelt es sich hier um eine ganz gewöhnliche Java Bean. Was uns natürlich am meisten interessiert, ist, wie wir die Inhalte aus einem XML-Dokument in eine Instanz dieser Klasse bekommen. Genau dies sehen wir uns jetzt an. Angenommen, wir bekommen wieder das folgende XML-Dokument mit der Hello world!-Nachricht als Eingabe.
Dieses Dokument verarbeitet das folgende Listing in eine Instanz vom Typ . Wir führen hier also ein Mini-Unmarshalling durch.
68
3.4 Zusammenfassung
Diese Minianwendung ist ähnlich wie das erste Beispiel aufgebaut. Nach einigen Initialisierungen erzeugen wir mit Hilfe des -Objektes aus dem XML-Dokument eine Instanz vom Typ . Diese Instanz besitzt jetzt den Inhalt der Elemente aus dem XML-Dokument. Die Ausgabe der Nachricht liefert uns daher wieder die simple Nachricht .
3.4
Zusammenfassung Für die JAXB-Entwicklungsumgebung werden die Bibliotheken jaxb-api.jar, jaxbimpl.jar, jsr173_1.0_api.jar, activation.jar, und jaxb-xjc.jar auf dem Klassenpfad benötigt. Die Beispielanwendungen dieses Buches sind auf der Buch-Webseite zum Download erhältlich. Die Bindung von Java nach XML geschieht über Java-Klassen mit JAXBAnnotationen. Die Bindung von XML nach Java geschieht über das Generieren von JAXB-Klassen mit dem Schema-Compiler Für die Ausführung von Schema-Compiler und Schema-Generator gibt es ANT- und Kommandozeilenskripte.
69
4 JAXB-API In diesem Kapitel wird es so richtig losgehen. Stück für Stück werden wir durch praktische Beispiele die Möglichkeiten der JAXB erschließen. Dabei wird die im Kapitel zu XMLSchema vorgestellte Domäne des Online-Banking-Service der rote Faden sein, an dem sich die Beispiele orientieren. Als Erstes werden wir uns der JAXB-API widmen. Die folgende Abbildung zeigt einen typischen Anwendungsfall unseres Online-Banking-Service und die verwendeten JAXB-API-Elemente.
Client
JAXBContext / Unmarshaller
Online-Banking-Service
JAXBContext / Marshaller
Abbildung 4.1 Aufruf des Online-Banking-Service
Die XML-Dokumente, die wir hier verwenden, basieren auf dem gleichen Gerüst aus Schemadokumenten, das bereits im XML-Schema-Kapitel aufgebaut wurde. Die am Schluss des XML-Schema-Kapitels aufgeführte Übersicht über die einzelnen Schemadokumente soll uns im weiteren Verlauf als Referenz für die benutzten Schemas dienen. Anhand dieses Schemas und der zugehörigen JAXB-Klassen stellen wir die Voraussetzungen und Grundlagen vor, die wir brauchen, um die API zu nutzen. Danach widmen wir uns den eigentlichen Funktionalitäten, die uns die JAXB-API bietet, wie z.B. das Unmarshalling und Marshalling von XML-Dokumenten. Danach stellen wir die Funktionalitäten und Aspekte vor, die sowohl beim Marshalling als auch beim Unmarshalling verwendet werden wie beispielsweise die Validierung,
71
4 JAXB-API und . Am Ende des Kapitels sollten wir damit den prakti-
schen Einsatz der gesamten JAXB-API kennen.
4.1
Die ersten Schritte Die wichtigen Klassen der Laufzeit-API der JAXB, die wir in den ersten Schritten beschreiben werden, sind: : Jeder lesende bzw. schreibende Zugriff auf XML erfolgt implizit über
eine -Instanz. : Eine Klasse, mit der wir Informationen zur XML-Bindung von
Java-Objekten abfragen können. die durch den Schema-Generator angelegte Klasse , mit der wir programmatisch die Java-Objekte eines XML-Dokuments anlegen können.
4.1.1
Am Anfang war der JAXBContext
Der bildet den Einstiegspunkt für die JAXB-API. Eine Applikation, die auf eine Datenbindung mit JAXB zugreifen möchte, muss als Erstes ein Objekt vom Typ erzeugen. Der verwaltet nämlich die Informationen über die Datenbindung zwischen einem Java-Modell und einem oder mehreren XML-Schemas. Dazu speichert die -Instanz implizit eine bidirektionale Abbildung zwischen global deklarierten Elementen im Schema und den entsprechenden Java-Klassen. Dies ermöglicht z.B. dem , ein XML-Element auf den korrekten Typ abzubilden. Für unsere Beispielanwendung sollten wir uns dieses Mapping einmal ansehen. Tabelle 4.1 Abbildung von globalen Elementen im JAXBContext XML-Element
Namespace
Abbildung im JAXBContext
http://jaxb.transparent/response Klasse Response
<customerElement>
http://jaxb.transparent/customer Klasse Customer
http://jaxb.transparent/account
Klasse Account
<portfolioElement>
http://jaxb.transparent/portfolio
Klasse Portfolio
Die Tabelle zeigt, dass nur die global definierten Elemente aus den vier Schemadokumenten in der Abbildung vorhanden sind. Nur diese Elemente kommen als Wurzel eines XMLDokuments für diese Schemadokumente in Frage. Unser Online-Banking-Service muss also als Erstes ein -Objekt definieren, um eine Antwort auf eine Anfrage erstellen zu können. Dieses Objekt bietet ihm dann die Möglichkeit, über Factory-Methoden entsprechende Service-Objekte (, etc.) für die einzelnen API-Funktionalitäten zu erstellen. Hier eine Übersicht
72
4.1 Die ersten Schritte über die verschiedenen Möglichkeiten, einen zu erzeugen, sowie die verschiedenen Service-Objekte und deren Factory-Methoden:
4.1.1.1
Einen JAXBContext anlegen
Ein -Objekt wird über die Factory-Methode erzeugt. Die -Klasse selbst ist eine abstrakte Klasse. Welche konkrete Implementierung hier von der -Methode zurückgegeben wird, hängt von der verwendeten JAXB-Implementierung ab und sollte sich nicht auf deren Verwendung auswirken. Sie wird im Fall der Referenzimplementierung aus der Datei gelesen. Wie aus dem Listing oben zu erkennen ist, gibt es eine ganze Reihe von Methoden mit verschiedenen Signaturen. Zu den wichtigsten Methoden ist hier jeweils ein Beispiel abgedruckt. JAXBContext unter Verwendung des Paketnamens erzeugen Wir können eine neue -Instanz anhand einer Zeichenkette erzeugen. Die Zeichenkette beschreibt ein oder mehrere Java-Pakete, deren Klassen über JAXB an XML gebunden werden sollen.
73
4 JAXB-API Dieser Methode übergeben wir einen Kontextpfad, der aus einem vollqualifizierten Paketnamen oder aus mehreren Paketnamen besteht, die wir per Doppelpunkt trennen. Diese Pakete enthalten die Java-Klassen, die ein oder mehrere zu bindende XML-Schemas repräsentieren. Der ist nach diesem Aufruf mit allen definierten Klassen initialisiert und kann nun Instanzen dieser Java-Klassen in XML umwandeln und umgekehrt. JAXBContext unter Verwendung eines anderen Classloaders erzeugen Der Aufruf
nutzt den des aktuellen Threads, um die Klassen im angegebenen Paket zu lokalisieren. Falls eine andere -Instanz benutzt werden soll, kann diese der folgenden Variante der -Methode übergeben werden:
JAXBContext anhand von Java-Klassen erzeugen Besonders im Fall der Bindung eines Schemas an ein existierendes Datenmodell ist die folgende Variante der -Methode zu erwähnen. Diese erwartet anstelle eines Paketnamens die Angabe einer gewöhnlichen Java-Klasse in Form einer -Instanz.
Hier wird z.B. die Klasse angegeben, die das Wurzelelement für die Antwort des Online-Banking-Service darstellt. Dadurch wird ein mit der Klasse und allen von ihr referenzierten Klassen initialisiert. 4.1.1.2
Viele Threads, ein JAXBContext
Sicher wird sich der ein oder andere fragen, ob der für jede Verarbeitung neu angelegt werden muss. Das Erzeugen des -Objektes ist ein recht aufwendiger Vorgang, da die Bindungsinformationen für alle Klassen, die dem Kontext zugeordnet sind, zunächst ermittelt werden müssen. Daher sollte das Objekt nach Möglichkeit wiederverwendet werden. Die JAXB-Spezifikation schreibt vor, dass eine Implementierung der -Klasse threadsafe sein muss. Dadurch kann eine Instanz des von beliebig vielen Threads genutzt werden. In unserem Beispiel könnte der Online-Banking-Service ein einziges Kontextobjekt benutzen, um beliebig viele Clients zu bedienen. Also statt wie in dem folgenden Quelltextauszug in jedem Methodenaufruf eine neue Instanz anzulegen:
74
4.1 Die ersten Schritte
ist es erlaubt und performanter, diese statisch anzulegen, wie wir das im unten abgedruckten Quelltext machen:
4.1.2
Die Klasse JAXBIntrospector verwenden
Eine der Klassen, die auf Anfrage durch eine -Implementierung instanziiert wird, ist die Klasse . Diese Hilfsklasse bietet Methoden, mit denen wir unbekannte Java-Objekte auf eine XML-Bindung hin prüfen können.
Den Einsatz der oben genannten Methoden schauen wir uns näher an. Die folgenden Beispiele setzen voraus, dass ein XML-Dokument wie folgt per Unmarshalling in ein Objekt vom Typ überführt wird.
75
4 JAXB-API isElement Mit der Methode können wir feststellen, ob ein beliebiges Objekt ein durch diesen Kontext gebundenes XML-Element darstellt. Objekte, für die dies zutrifft, sind: Objekte vom Typ Objekte, deren Klasse eine Annotation besitzt Beispielsweise liefert der Aufruf
den Rückgabewert , weil als Element auf der Hauptebene in unserem Schema definiert wurde und daher die Annotation trägt. Der folgende Aufruf
liefert den Wert . Auch das ist korrekt, da der Datentyp nicht mit der Annotation markiert wurde und daher auch nicht als Wurzelelement eines XML-Dokuments in Frage kommt. Wir können aber manuell ein Element aus dem Objekt erzeugen, indem wir es in einen Wrapper vom Typ verpacken, auf den wir später noch näher eingehen werden:
Das obige Beispiel liefert jetzt auch als Ausgabe. getElementName Für alle Objekte, für die den Wert zurückliefert, kann mit der Methode der Name des Elements im XML-Dokument bezogen werden.
Der Aufruf liefert den Namen des Elements in Form eines -Objekts, dessen Darstellung so aussieht:
getValue Diese wohlgemerkt statische Methode liefert den Wert eines an XML gebundenen JavaObjekts. Wie oben erwähnt, kann eine solche Bindung in zwei verschiedenen Formen auf-
76
4.1 Die ersten Schritte treten, einmal als Instanz einer Java-Klasse mit -Annotation oder aber als -Instanz. Im ersten Fall stellt die Instanz selbst den Wert dar, die -Instanz hingegen kapselt den Wert in der Eigenschaft . Falls in einer Anwendung diese beiden Formen vermischt und beliebig auftreten, kann der Zugriff auf die Werte über die Methode vereinheitlicht werden, statt jedes Mal den einen oder anderen Fall zu überprüfen. Hier ein Beispiel für unsere - und -Objekte.
4.1.3
Objekte erzeugen mit der ObjectFactory
Wie wir im Übersichtskapitel zur JAXB bereits erwähnt haben, können wir mit einer durch den Schema-Compiler angelegten Klasse -Instanzen der generierten Java Beans erzeugen. Hierzu bietet diese Klasse eine -Methode für jedes Element des zugrunde liegenden XML-Schemas. Für unseren Online-Banking-Service enthält sie z.B. die Methoden , , etc. Ein Beispiel veranschaulicht die Arbeit mit der .
77
4 JAXB-API
In dem ausführbaren Beispiel erzeugen wir eine Instanz der Klasse . Diese nutzen wir, um Objekte für Kunden-, Konto- und Portfolioinformationen zu erzeugen. Die Objekte werden über die Zugriffsmethoden miteinander verbunden und einer Variablen vom Typ zugewiesen, welche die gesamte Antwort des Online-Banking-Service darstellt. Dieses Objekt können wir nun in ein XML-Dokument transformieren. Elemente vom Typ JAXBElement erzeugen Die oben erzeugten Instanzen vom Typ , , etc. stellen die Java-Repräsentation der im XML-Schema definierten Typen dar. Ein XML-Element z.B. vom Typ kann jedoch zusätzlich Namespace-Informationen besitzen. Diese zusätzlichen Namespace-Informationen werden durch den Typ abgebildet. Daher bietet die zusätzlich Methoden, die aus einer Java-Repräsentation ein Objekt vom Typ erzeugen, das den XML-Namen und NamespaceInformationen enthält. Dadurch entfällt ein manuelles Erzeugen von XML-Elementnamen. Die folgende Definition für das Objekt
kann damit durch diesen Aufruf mit der ersetzt werden.
4.1.4
Zusammenfassung
Ein kann auf verschiedene Arten angelegt werden: unter Angabe von Paketnamen, Klassenobjekten oder eines alternativen Classloaders.
78
4.2 Marshalling Die Hilfsklasse liefert uns die Information, ob ein gegebenes Objekt per JAXB an XML gebunden ist. Der Schema-Compiler erzeugt automatisch eine Klasse , mit der sich Instanzen der generierten Java Beans und Elementinstanzen vom Typ erzeugen lassen.
4.2
Marshalling Mit dem im vorigen Abschnitt erzeugten -Objekt kann der Online-BankingService nun zur Tat schreiten und die gewünschten Informationen in einem XMLDokument an den Client senden. Die Informationen über Kunde, Konto und Portfolio wurden vom Service z.B. aus einer Datenbank bezogen und liegen jetzt als Objekte im Speicher vor. Die Transformation dieser Objekte in ein XML-Dokument, die einem vorgegebenen Schema entspricht, wird nun vom der JAXB-Implementierung übernommen. Der ist also in der Lage, Java-Objekte, die an XML gebunden sind, in ihre XML-Repräsentation zu überführen. Die Daten, die in unserem Online-Banking-Service vielleicht aus einer Datenbank gelesen wurden, liegen vielleicht noch nicht als XMLgebundene Java-Klassen vor, was dann? Sehen wir uns dazu die zwei möglichen Szenarien einer JAXB-Bindung an: Bindung eines existierenden Datenmodells: Hier wird ein existierendes JavaDatenmodell mit Annotationen manuell an ein XML-Schema gebunden. Ein solches Datenmodell können wir dem direkt übergeben. Generieren eines Datenmodells aus einem XML-Schema: In diesem Fall werden JavaKlassen eigens aus einem existierenden Schema heraus erzeugt. Um Instanzen dieser Klassen zu erzeugen, generiert der Schema-Compiler die Klasse , die wir weiter oben bereits im Einsatz gesehen haben.
4.2.1
Das Marshaller-Objekt anlegen
Über das Kontextobjekt vom Typ erzeugen wir nun über die entsprechende Factory-Methode ein -Objekt.
Die Schnittstelle vereint alle API-Methoden, die für den Marshalling-Prozess von Bedeutung sind. Dazu gehören Methoden, die den Marshalling-Prozess starten, die Registrierung von Event-Handlern, das Einschalten der Validierung etc.
79
4 JAXB-API
4.2.2
Den Marshalling-Prozess starten
Durch Aufruf von wird der Marshalling-Prozess gestartet. Diese Methode erwartet zwei Parameter. Der erste Parameter vom Typ stellt das zu transformierende Java-Objekt dar. Es wird bewusst nur der Typ erwartet, da die Java-Klassen einfache POJO1s sein können, daher keinerlei Schnittstellen implementieren müssen. Die einzige Einschränkung, die hier gilt, ist, dass nur Objekte verarbeitet werden können, die an ein gültiges Wurzelelement eines XML-Schemas gebunden wurden. Dies wird intern durch die bereits vorgestellte Methode der Klasse überprüft. Ist das übergebene Objekt kein solches Element, wird der Prozess mit einer abgebrochen. Der zweite Parameter gibt das Ausgabeformat des Marshalling-Prozesses an. Für jedes der möglichen Ausgabeformate gibt es eine eigene Methode. Tabelle 4.2 Marshalling-Ausgabeformate Marshal-Methode
Ausgabeformat
Marshalling der Objektstruktur in einen Writer, z.B. einen zur Ausgabe in einer Datei.
Ausgabe in eine entsprechende Instanz von .
Ausgabe als Ergebnis einer TrAX2 XSLTTransformation
Ausgabe in eine DOM-Instanz
Ausgabe in einen ContentHandler für SAX2 vents
Ausgabe mit dem XMLStreamWriter der StAX3API
Diese verschiedenen Ausgabeformate können in zwei Gruppen aufgeteilt werden. Mit den Standard-Java-Ausgabeschnittstellen wie oder wird der Inhalt endgültig als XML-Dokument serialisiert. Die anderen Formate ermöglichen die Anbindung und Weiterverarbeitung der Ausgabe durch Java-XML-APIs wie TrAX oder StAX. So können Inhalte nach dem Marshalling z.B. durch einen Handler für SAX2-Events modifiziert werden. Eine komplette (und insbesondere aktuelle) Auflistung der möglichen Ausgabeformate findet sich in der Javadoc-Dokumentation der Klasse .
1 Plain
Old Java Object
2 Transformation 3 Streaming
80
API for XML
API for XML
4.2 Marshalling XML in Datei schreiben Die im Beispiel mit Hilfe der erzeugten Java-Objekte können wir auf die folgende Weise als XML-Dokument in einer Datei abspeichern:
XML in ein W3C-DOM-Dokument schreiben Ein W3C-DOM-Dokument kann ebenfalls erzeugt werden. Statt einer Datei wird hier ein Objektbaum aus W3C-DOM-Knoten erzeugt.
XML auf der Konsole ausgeben Sehr nützlich für Test- und Debugging-Zwecke ist die Ausgabe auf der jeweiligen Konsole. Dies liefert der folgende Aufruf.
XML als StAX XML-Stream ausgeben Hier wird die StAX-API der JAXP verwendet, um einen XML-Ausgabestrom zu erzeugen.
4.2.3
Den Marshalling-Prozess konfigurieren
Der Marshalling-Prozess kann durch das Setzen spezieller Parameter über die Methode konfiguriert werden. Diese Parameter übergeben wir der Instanz vor dem eigentlichen Marshalling.
Das
obige
Beispiel
veranlasst den durch Setzen der Property , die XML-Elemente in der Ausgabe entsprechend einzurücken, um ein für Menschen besser lesbares Dokument mit vielen Leer- und Formatierungszeichen zu erzeugen. Praktischerweise sind für die einzelnen Property-Bezeichner StringKonstanten in der Klasse vorhanden.
81
4 JAXB-API Wir wollen einen weiteren Parameter vorstellen, nämlich . Diese Eigenschaft weist den an, den Ort des übergebenen Schemadokuments in der Ausgabe zu deklarieren. Der Ort wird dabei so angegeben, wie er auch im entsprechenden -Attribut eines XML-Dokuments deklariert werden würde, nämlich Namespace URI + URI des Schemadokuments.
Der Umfang der unterstützten Parameter hängt von der jeweiligen Implementierung der JAXB-Spezifikation ab. In der folgenden Tabelle sind die Standard-Properties, die von jeder -Implementierung der JAXB-Spezifikation unterstützt werden sollen, fett abgedruckt. Tabelle 4.3 Marshalling-Properties Property
Bedeutung
Datentyp
jaxb.encoding
Zeichensatz des ausgegebenen XML, falls nicht anders angegeben, ist hier gesetzt.
java.lang.String
jaxb.formatted.output
Gibt an, ob das ausgegebene XML einge- java.lang.Boolean rückt werden soll:
= Ausgabe eingerückt (Standard) = Ausgabe nicht eingerückt jaxb.schemaLocation
Durch diese Eigenschaft kann dem ausgegebenen XML-Dokument der Ort eines XML-Schemas mit dem -Attribut übergeben werden. Diese Eigenschaft gilt für XML-Dokumente, die einen oder mehrere Namespaces verwenden.
java.lang.String
jaxb.noNamespaceSchema- Diese Eigenschaft übergibt den Ort eines Location Schemadokuments für Dokumente, die keine Namespaces verwenden. Hier wird das gleichnamige Attribut
java.lang.String
verwendet. jaxb.fragment
Gibt an, ob der Marshaller Events während des Marshalling-Prozesses generieren soll. Die Standardeinstellung ist dabei .
java.lang.Boolean
com.sun.xml.bind.characterEscapeHandler
Mit dieser Eigenschaft kann ein eigener Handler zur Konvertierung von Steuerzeichen (Escape Characters) angegeben werden.
com.sun.xml.bind.marshaller. CharaterEscapeHandler
(nur JAXB 2.0 RI)
82
4.2 Marshalling Property
Bedeutung
Datentyp
com.sun.xml.bind.namespacePrefix Gibt eine Handlerklasse an, die den Mapper Marshaller veranlasst, benutzerdefinierte Präfixe zu generieren. (nur JAXB 2.0 RI)
com.sun.xml.bind.marshalle
com.sun.xml.bind.indentString
Gibt die Zeichenfolge an, die zur Einrückung genutzt werden soll, wenn die Eigenschaft jaxb.formatted.output gesetzt ist.
java.lang.String
Diese Eigenschaft gibt die Präambel, also das Element im Kopf eines XML-Dokuments, an. Hier können z.B. Kommentare oder -Deklarationen erfolgen.
java.lang.String
(nur JAXB 2.0 RI)
com.sun.xml.bind.xmlHeaders (nur JAXB 2.0 RI)
4.2.4
Das fertige Beispiel
Das folgende Codebeispiel zeigt den gesamten Marshalling-Prozess anhand unseres Online-Banking-Service. Dieser generiert eine Antwort, indem die vorhandenen Objekte in ein XML-Dokument transformiert werden. Das Ergebnis der Transformation wird dann in einer Datei abgespeichert. In einer realen Anwendung würde diese Antwort natürlich nicht als Datei versendet, sondern eher in eine SOAP-Nachricht verpackt werden, um dann als Antwort eines Webservice über die Netzwerkschnittstelle versendet zu werden.
83
4 JAXB-API
Das resultierende XML-Dokument nach Ausführung des oben stehenden Codes sieht dann folgendermaßen aus:
84
4.2 Marshalling
Das Listing zeigt, dass der tatsächlich das entsprechende Schema im -Attribut angibt. Die Präfixe der deklarierten Namespaces werden einfach durchnummeriert.
4.2.5
Marshalling beliebiger Objekte
Wie oben bereits erwähnt wurde, verarbeitet der nur solche Java-Objekte, die Wurzelelemente eines XML-Schemas darstellen, also Elemente, die im Schema auf oberster Ebene deklariert sind. Ein solches Element ist z.B. das -Element, welches das Wurzelelement in unserem Online-Banking-Service-Schema darstellt. Das Element dagegen ist kein solches Wurzelelement. Nun wäre es aber auch denkbar, dass wir statt eines -Objekts auch einmal einen einzelnen Kundendatensatz vom Typ transformieren wollen. Für diesen Fall gibt es zwei mögliche Wege. Die Klasse JAXBElement als Wrapper nutzen Eine einfache Möglichkeit, ein solches Marshalling durchzuführen, ist die Benutzung einer Wrapper-Klasse. Verpacken wir ein Kundenobjekt vom Typ mit der WrapperKlasse , kann das Marshalling problemlos durchgeführt werden. Das Verpacken der Objektinstanz übernimmt wieder die Klasse . Dadurch wird aus dem Objekt ein transformierbares Element vom Typ . Dieses Element enthält neben dem eigentlichen Objekt zusätzliche Informationen wie z.B. den Elementnamen und Namespace-Informationen. Im Programmcode sieht das folgendermaßen aus:
85
4 JAXB-API
Die Annotation @XmlRootElement verwenden Falls wir ein bestehendes Java-Datenmodell an ein XML-Schema binden, können wir den Typ zu einem Wurzelelement machen, indem wir die Klasse mit der Annotation versehen. Die Klasse wird dadurch automatisch als JAXBElement erkannt. Die nötigen Namespace-Informationen sind hier bereits in der Annotation enthalten. Dadurch wird ein direktes Marshalling ohne den Umweg über eine WrapperKlasse möglich. Hinweis
Ein Objektgraph muss nicht zwingend konform zu einem Schema sein, um mit dem verarbeitet werden zu können. Der ist daher in der Lage, auch ungültige Inhalte in ein XML-Dokument zu überführen.
4.2.6
Zusammenfassung
Mit dem lassen sich Java-Objekte in XML-Strukturen transformieren. Eine -Instanz wird über den angelegt. Der kann Java-Objekte in verschiedene Ausgabeformate transformieren, darunter Standardschnittstellen wie Dateien oder Streams, aber auch Ausgabeformate anderer XML-APIs wie DOM, StAX oder TrAX. Durch das Setzen von Properties können wir Einfluss nehmen auf den MarshallingProzess.
86
4.3 Unmarshalling Es können prinzipiell beliebige Objekte transformiert werden, auch einem XMLSchema nach ungültige XML-Inhalte.
4.3
Unmarshalling Bisher hat unser Online-Banking-Service eine XML-Antwort unter Verwendung von und erstellt und an einen Client versendet. Wie können wir nun dieses XML-Dokument in der Clientanwendung verarbeiten? Das Zauberwort heißt hier Unmarshalling. Wir wissen bereits, dass beim Unmarshalling XML-Elemente in einen Graph aus Java-Objekten im Speicher transformiert werden. Die XML-Elemente können dabei ein ganzes Dokument darstellen oder auch nur einen Teil davon. Wichtig ist, dass die Elemente in einem Schema definiert wurden, das dem vorher bekannt gemacht wurde. Bei Elementen sollten wir dabei zwischen global und lokal definierten Elementen unterscheiden. Globale Elemente und Datentypen (-/) sind in einem Schema auf der obersten Ebene definiert. Der kennt für jedes globale Element, Datentypen ausgenommen, eine Abbildung auf eine Java-Klasse. Ein XML-Dokument mit einem global definierten Wurzelelement an den zu übergeben, ist wohl der Standardfall. Der kann solche XML-Dokumente ohne Umwege transformieren. Ein Beispiel für ein globales Element in unserem Schema ist das -Element. Ein global definierter Datentyp ist z.B. der Datentyp , der im Kundenschema auf oberster Ebene definiert ist. Lokale Elemente werden innerhalb einer anderen Elementdeklaration definiert. Ein XML-Dokument, das ein lokales Element als Wurzelelement besitzt, kann der nicht mit dem Standardweg über den auf die korrekte Klasse abbilden. Wie das Unmarshalling solcher Teildokumente funktioniert, wird in Abschnitt 4.3.7 näher beschrieben.
4.3.1
Das Unmarshaller-Objekt anlegen
Zunächst sehen wir uns den Standardfall an, in dem wir ein global definiertes Element als Wurzel eines XML-Dokuments transformieren. Der Unmarshaller der JAXBImplementierung verwendet eine -Instanz, um dieses Wurzelelement der richtigen Java-Klasse zuzuordnen. Als Erstes müssen wir daher wieder einen solchen Kontext erzeugen. Analog zum Marshalling kann dann mit Hilfe des Kontexts ein ServiceObjekt vom Typ erzeugt werden. Die Methode des liefert uns eine Implementierung des Interface Unmarshaller zurück.
87
4 JAXB-API
4.3.2
Den Unmarshalling-Prozess starten
Die Java-Schnittstelle bietet uns jetzt analog zu einige -Methoden, die das Unmarshalling eines XML-Dokuments durchführen. Die möglichen Eingabeformate entsprechen dabei der Auswahl von Ausgabeformaten beim Marshalling. Eine Ausnahme bildet hier die -Variante, die eine Adresse vom Typ als Parameter aufnimmt. Als Rückgabe liefern alle Methoden ein Objekt vom Typ . Abhängig von dem gelesenen XML-Dokument kann die Methode beliebige Java-Objekte zurückgeben. Tabelle 4.4 Unmarshalling-Eingabeformate Unmarshal-Methode
Eingabeformat
Unmarshalling eines XML-Dokuments im Java-Dateiformat
Angabe einer Dokumentadresse vom Typ
Übergabe eines Eingabestroms, z.B. einer XML-Datei
Übergabe einer Transformationsquelle der TrAX4-API
Ausgabe als DOM-Parsebaum
Übergabe einer SAX-konformen Eingabequelle
Übergabe eines XMLStreamReaders der StAX5-API Ausgabe eines XMLEventReaders der StAX-API
Die Eingabeformate unterscheiden sich ebenfalls wieder in die Standard-JavaEingabeformate wie oder und Eingabeformaten, welche die Integration von JAXB mit anderen XML-APIs erlauben. Als Beispiel für die Anwendung der -Methode lesen wir im folgenden Quelltextauszug das XML-Dokument ein, das wir im letzten Abschnitt mit dem Marshaller erzeugt haben.
4 Transformation 5 Streaming
88
API for XML
API for XML
4.3 Unmarshalling Der benutzt den , um das Wurzelelement auf die Klasse abzubilden, und liefert uns daher ein Objekt vom Typ zurück, das genau die Antwort des Online-Banking-Service darstellt und mit dem wir jetzt in unserer Clientanwendung weiterarbeiten können.
4.3.3
Den Unmarshalling-Prozess konfigurieren
Im Gegensatz zum sieht die JAXB-Spezifikation 2.0 keine besonderen Parameter vor, die für den Unmarshalling-Prozess gesetzt werden können. Auch die einzelnen JAXB-Implementierungen bieten hier bisher keine implementierungsspezifischen Eigenschaften an.
4.3.4
Das fertige Beispiel
Hier noch einmal eine komplette Methode, die das Unmarshalling unseres Dokuments durchführt. Der wird in diesem Beispiel zur Anschaulichkeit innerhalb des Methodenrumpfes neu erzeugt. In einer realen Anwendung sollte ein bereits bestehender Kontext aus Performancegründen in die Methode hereingereicht werden. Der nutzt die im bekannte Abbildung des -Elementes auf die Java-Klasse , um den korrekten Datentyp zurückzuliefern.
89
4 JAXB-API
4.3.5
Das xsi:type-Attribut beim Unmarshalling verwenden
Angenommen, wir bekommen jetzt aus einer unbekannten Quelle ein XML-Dokument mit einem anderen Wurzelelement. Als Basis besitzt es zwar die Struktur des Elements , es beinhaltet jedoch noch ein zusätzliches Unterelement und heißt auch noch anders ...
Nun wollen wir in der Lage sein, ein solches Dokument trotzdem in ein -Objekt zu transformieren. Der Datentyp muss dazu im Schema allerdings wie folgt global definiert sein.
Mit dem -Attribut können wir mit Schemamitteln einem Parser den Tipp geben, dass ein Element A vom Typ her einem Element B entspricht. Dieses Attribut wird auch vom Unmarshaller beachtet. Findet der nämlich keinen passenden Typen für den Namen des Wurzelelements, so versucht er, bei Angabe eines -Attributs den dort spezifizierten Typen im zu finden. Die Lösung unseres Problems wäre daher, in unserem XML-Dokument eine Typsubstitution mit vorzunehmen.
Der Aufruf für das Unmarshalling eines solchen Dokumentes ist jetzt der folgende:
90
4.3 Unmarshalling
Aufgrund der Typsubstitution bekommen wir hier ein Objekt vom Typ zurück. Mit der Methode erhalten wir schließlich das an Java gebundene XMLElement in Form einer -Instanz. Hier können wir alle Werte des Datentyps verarbeiten, ausgenommen davon allerdings das zusätzliche Element .
4.3.6
Elemente ohne @XMLRootElement verarbeiten
Das eingangs mit dem verarbeitete Dokument besitzt ein Element als Dokumentwurzel. Die zu diesem Element gehörige JAXB-Klasse besitzt eine Annotation. Daher konnten wir das Ergebnis des Unmarshallers direkt an ein Objekt vom Typ zuweisen. Das -Element unseres Schemas hingegen ist nicht als globales Element im Schema definiert. Es wird innerhalb des -Elementes definiert. Seine Definition verweist auf den komplexen Datentyp , der wiederum aus dem Kundenschema importiert wurde. Wie können wir jetzt trotzdem ein XML-Dokument einlesen, das den folgenden Kundendatensatz im Element als Wurzelelement enthält?
91
4 JAXB-API Das Element ist dem aus dem importierten Kundenschema als globales Element bekannt. Der Datentyp dieses Elements ist vom Typ . Eigentlich sollten wir erwarten, dass wir beim Unmarshalling dieses Elements ein Objekt vom Typ erhalten. Solange nun das Element das einzige Element vom Typ ist, wäre dieses Verhalten stets eindeutig. Fänden wir in einer Methode ein Java-Objekt vom Typ Customer, so könnten wir sicher zur Aussage kommen, dass es sich hierbei um eine Instanz des XML-Elements handelt. Sobald jedoch mehrere solcher Elemente im Dokument vorkommen dürfen, bspw. gleichzeitig als und als , wie können wir (bzw. später der Marshaller) diese auseinanderhalten? Wir müssen solche Elemente daher mit der Information versehen, an welcher Stelle im Dokument diese stehen. Für diesen Fall bietet die JAXB-API die Klasse , mit der die Instanz eines reinen Java-Objektes mit den XML-spezifischen Informationen zu Elementname und Namespace versehen werden kann. Zurück
zu
unserem Beispiel: Die Java-Klasse definiert keine -Annotation, es handelt sich dabei also um eine Java-Klasse, die an einen XML-Typ, aber nicht an ein Wurzelelement gebunden ist. Beim Unmarshalling dieses Dokuments bekommen wir daher kein Objekt vom Typ zurück, sondern ein Objekt vom Typ . Dieses Objekt stellt nun ein XML-Element vom Typ dar, was ja für das Element aus unserem Beispiel zutrifft. Mit der Methode können wir nun auf das -Objekt zugreifen, das die Werte des Elements und seiner Unterelemente kapselt. Im folgenden Beispiel lesen wir nun das Element ein und überprüfen den Namen des Kunden:
92
4.3 Unmarshalling
Von der Variablen könnten wir nun beispielsweise den Namen des Elements abfragen (customerElement) oder in welchem Namespace dieses Element definiert ist. Diese Informationen sind in der Variablen nicht mehr verfügbar, hier haben wir nur noch die reinen Werte des Elements.
4.3.7
Unmarshalling von Teilen eines XML-Dokuments
Wir haben gelernt, dass lokal definierte Elemente unseres -Schemas im , also Teile eines XML-Dokuments, nicht ohne weiteres mit dem Unmarshaller eingelesen werden können. Auf die Formulierung ohne weiteres gehen wir im Folgenden näher ein. Betrachten wir hierzu die folgenden -Methoden, die wir bisher haben links liegen lassen:
Diese Methoden arbeiten wieder mit den bekannten Eingabeformaten, in denen das Dokumentfragment übergeben werden kann. Zusätzlich zum XML-Teildokument übergeben wir dem eine Java-Klasse als -Instanz. Diese Java-Klasse definiert für den Unmarshaller den Typ, in den das Teildokument eingelesen werden soll. Der Unmarshaller versucht dann, das Teildokument auf die Struktur der angegebenen Klasse abzubilden. Dies ermöglicht uns z.B., nur den Ausschnitt des Elements innerhalb eines -Dokuments auszulesen:
93
4 JAXB-API Im folgenden, ausführbaren Beispiel erzeugen wir zuerst ein W3C-DOM-Dokument vom Typ aus dem oben angegebenen XML-Dokument. Innerhalb dieses Objekts selektieren wird den DOM-Knoten, der dem Element entspricht. Dieses erfolgt in dem Beispiel mittels der XPath-API. Nachdem wir nun den entsprechenden Knoten in Form eines -Objektes als Teildokument isoliert haben, übergeben wir dieses der entsprechenden -Methode zusammen mit dem Tipp, dass wir uns eine Instanz von aus dem Unmarshalling erhoffen. Der Aufruf dieser Methode liefert uns ein Objekt vom Typ zurück.
Das aus dem Unmarshalling resultierende kapselt eine Instanz der Klasse , die wir mit der Methode abfragen können. Die Ausgabe des Kindele-
94
4.3 Unmarshalling ments sollte nun die Stadt als Ausgabe liefern wir haben erfolgreich ein Teildokument eingelesen. Hinweis
Dieses explizite Unmarshalling überschreibt eine evtl. vorhandene Abbildung auf eine JavaKlasse im . Es wird dann auf jeden Fall versucht, zunächst ein Unmarshalling auf die als Parameter angegebene Klasse durchzuführen. Wurde jedoch für das XML-Element ein xsi:type-Attribut angegeben, hat dieses Attribut Vorrang vor allen anderen Bindungen. Die Reihenfolge der Bindung ist hier also: 1. Wert des -Attributs 2. Falls angegeben, der -Parameter der -Methode 3. Bekannte Abbildung im
4.3.8
Flexibles Unmarshalling nutzen
In der JAXB-Übersicht wurden bereits die zwei Konzepte Structural Unmarshalling und Flexible Unmarshalling erläutert. Hier wollen wir uns ein konkretes Beispiel für die flexible Behandlung von unbekannten XML-Inhalten ansehen. Stellen wir uns vor, dass das XML-Schema unserer Beispielanwendung weiterentwickelt wurde. Man spricht hier auch gerne von Schemaevolution. Sehen wir uns dazu noch einmal das Teilschema zu unserem -Element an, das die Wertpapierdaten unseres Kunden enthält. Der Datentyp ist folgendermaßen definiert.
Die zugehörige Java-Klasse mit Annotationen sieht folgendermaßen aus:
Die Toleranz des Unmarshallers nutzen Obwohl hier für das Element eine mit definierter Reihenfolge vorgegeben ist, ist es dem zunächst recht einerlei, in welcher Reihenfolge die
95
4 JAXB-API Elemente auftauchen. Wir können z.B. auch ein solches -Element problemlos verarbeiten:
Die Reihenfolge im obigen Beispiel stimmt nicht ganz mit dem Schema überein, das Element befindet sich an der falschen Stelle. Während die Schemavalidierung hier allerlei Fehler ausgibt, wird dieses Dokument dennoch korrekt verarbeitet. Aber hier gibt es noch etwas wesentlich Interessanteres zu entdecken. Wie bereits erwähnt, hat sich das Schema weiterentwickelt. Und zwar ist in diesem Element ein weiterer Wertpapiertyp hinzugekommen, der Renten und Anleihen mit den zugehörigen Daten abbildet. Der Unmarshaller ignoriert unbekannte Elemente einfach. Auf diese Weise können prinzipiell auch ältere Versionen der Anwendung mit geänderten XML-Dokumenten arbeiten. Der Aufruf des Unmarshallers ist weiterhin gleich geblieben. Das Element any für unbekannte Elemente nutzen Es könnte auch sein, dass wir beim Entwickeln des -Schemas bereits wissen, dass ein neuer Wertpapiertyp, wie etwa Anleihen, hinzukommen könnte. Wir könnten dies in der -Definition durch ein -Element ausdrücken.
96
4.4 Validierung Die zugehörige Java-Klasse besitzt nun zusätzlich eine Liste vom Typ , die beliebige Objekte aufnehmen kann.
Führen wir jetzt das Unmarshalling durch, enthält die Liste das Element als Instanz von mit unserem Anleihepapier, das im vorigen Durchlauf noch ignoriert wurde. Wir könnten sogar probeweise versuchen, dieses unbekannte Element durch den Unmarshaller in einen der bekannten Datentypen umzuwandeln. Auf diese Weise lassen sich also erweiterbare Anwendungen bauen, die auch unbekannte Elemente oder Dokumente mit variierenden Inhalten verarbeiten können.
4.3.9
Zusammenfassung
Mit dem lassen sich XML-Dokumente in Java-Objektgraphen transformieren. Eine -Instanz wird über angelegt. Der versteht mehrere Eingabeformate, aus denen er Java-Objekte erzeugen kann. Es können Dateien, Datenströme, aber auch Formate anderer XML-APIs verarbeitet werden. Momentan existieren für den keine speziellen Konfigurationsmöglichkeiten. Neben vollständigen XML-Dokumenten, die einem Schemawurzelelement entsprechen, können mit auch Teildokumente verarbeitet werden, solange diese noch als XML gelesen werden können. Durch die Angabe eines Datentypen mit kann mit Schemamitteln ein Element auf ein anderes abgebildet werden. Objekte, deren Klasse eine Annotation besitzt, können direkt geschrieben und gelesen werden, alle anderen werden in -Instanzen verpackt. Das ist am Anfang etwas verwirrend.
4.4
Validierung Aus der JAXB-Übersicht wissen wir bereits, dass sowohl beim Marshalling als auch beim Unmarshalling eine Validierung der XML-Dokumente gegen ein XML-Schemadokument
97
4 JAXB-API durchgeführt werden kann. Diese Validierung ist optional und kann je nach Anwendungsfall eingesetzt oder weggelassen werden. Mit der JAXB-Version 2.0 wurde die bisherige Validierung vollkommen umgestellt. Die neue Version 2.0 nutzt die bereits existierende Validierungs-API der JAXP 1.3. Die JAXP 1.3 ist Teil der JSE 5, daher bietet es sich an, diese bestehende API zu nutzen statt eine zusätzliche Implementierung einer Schemavalidierung innerhalb JAXB bereitzustellen. Die Validierungs-API der JAXP ist im Paket zusammengefasst. Wie die Validierung konkret funktioniert, werden wir im Folgenden näher erläutern.
4.4.1
Beim Unmarshalling validieren
Damit wir sicherstellen können, dass die Antwort, die wir von unserem Online-BankingService bekommen, auch einen gültigen Datensatz darstellt, wollen wir eingehende XMLDokumente beim Unmarshalling validieren. Dazu sind die folgenden Schritte notwendig. Als Erstes definieren wir, mit welchem XML-Schema das Dokument validiert werden soll. Im Rahmen der Validierungs-API legen wir dieses als Schemaobjekt an, das eine Referenz auf dieses Schema darstellt und in dem die Validierungslogik für das spezielle Schema gekapselt ist. Ein solches Objekt vom Typ erzeugt die der JAXP 1.3 auf die folgende Weise:
Wir besorgen uns zunächst eine Instanz der . Mit Hilfe der können wir dann ein neues -Objekt erzeugen. Dabei übergeben wir das XMLSchemadokument als Parameter an die Methode . Dieses -Objekt registrieren wir nun an der -Instanz mit der Methode . Falls ein solches -Objekt beim registriert wurde, wird automatisch eine Validierung während des Unmarshallings durchgeführt. Hier noch einmal ein komplettes Beispiel:
98
4.4 Validierung
Im obigen Beispiel führen wir das Unmarshalling zweimal durch, einmal mit einem gültigen Dokument und einmal mit einem ungültigen Dokument . Im letzteren Dokument haben wir das Geburtsdatum unseres Kunden im Element auf den Wert gesetzt. Da der Februar maximal 29 Tage besitzt, stellt diese Angabe ein ungültiges Datum dar. Das Unmarshalling des ersten Dokuments sollte fehlerfrei verlaufen. Tritt jetzt während des Unmarshalling-Prozesses des zweiten Dokuments ein Validierungsfehler auf, d.h., verstößt der Inhalt des XML-Dokuments gegen das registrierte Schema, so wird das Unmarshalling mit der Ausnahme abgebrochen. Die -Methode wirft dabei standardmäßig eine , die wiederum eine Oberklasse der darstellt. Die Ausgabe des obigen Beispiels ist daher folgende.
Die fehlerhafte Angabe wurde nun rechtzeitig entdeckt, bevor durch das ungültige Datum Folgefehler in unserer Anwendung entstehen können.
4.4.2
Beim Marshalling validieren
Auch beim Marshalling kann eine Validierung durchgeführt werden. Der Aufruf im JavaCode ist analog zum obigen Beispiel mit der Ausnahme, dass die -Methode entsprechend auf dem -Objekt aufgerufen wird. Nun wird vor der Umwand-
99
4 JAXB-API lung in XML eine Validierung der Daten im Java-Objektgraph durchgeführt. Verstößt der Inhalt der Objekte im Speicher gegen die Einschränkungen des XML-Schemas, wird der Marshalling-Prozess durch eine Ausnahme abgebrochen.
100
4.4 Validierung
Wenn wir das obige Beispiel ausführen, wird das Marshalling mit folgender Fehlermeldung abgebrochen:
Aus der Fehlermeldung können wir erkennen, dass für den Typ eine -Einschränkung vorliegt. Der angegebene Wert ist also zu hoch. Daher wird eine entsprechende geworfen. Nach Ändern des Wertes für im Java-Code werden wir sehen, dass das Marshalling ein einwandfreies, schemakonformes XML-Dokument erzeugt. Hinweis
Die Validierung beim Marshalling ist neu seit der JAXB-Version 2.0. Sie wurde vor allem mit Rücksicht auf die Unterstützung von Webservices hinzugefügt. Ein Webservice versendet Daten typischerweise im XML-Format. Diese Daten müssen dabei konform zu einem WSDLDokument sein, das wiederum mehrere XML-Schemas einbindet. JAXB dient dabei als möglichst transparente Bindung zwischen den Java-Objekten und den im Rahmen des WebserviceAufrufs übermittelten XML-Dokumenten. Durch die Validierung während des Marshallings kann sichergestellt werden, dass nur XML-Dokumente übermittelt werden, die der WSDLBeschreibung genügen.
4.4.3
Benutzerdefinierte Validierung
Bisher haben wir uns nicht weiter um die Konfiguration der Validierung gekümmert. Wir haben einfach ein Schema angegeben, anhand dessen die jeweiligen Daten validiert werden sollten. Wie JAXB dabei auf Fehler reagiert, haben wir dabei völlig der JAXBReferenzimplementierung überlassen. Diese hat daher eine Standardfehlerbehandlung verwendet, die einfach nach dem Auftreten des ersten Fehlers das Marshalling bzw. Unmarshalling abbricht. Ein Ereignis bei der Validierung, wie z.B. ein Fehler, wird in JAXB durch ein Objekt vom Typ repräsentiert. Dieses Objekt enthält alle wichtigen Informationen zu einem Validierungsereignis. Wir können diese Ereignisse auch manuell verarbeiten und programmatisch entscheiden, wann welche Ausnahme auftreten oder wie auf Validierungsfehler reagiert werden soll.
101
4 JAXB-API Hier der Aufbau der Klasse :
Die Schnittstelle bietet uns vier verschiedene Eigenschaften eines Ereignisses, die über die entsprechenden Getter-Methoden abgerufen werden können. : Diese Eigenschaft gibt den Schweregrad des Ereignisses an. Die W3C-
XML-Spezifikation definiert dabei drei Arten von Ereignissen. Beim Typ handelt es sich um unkritische Fehler im XML-Dokument, die nicht zum Abbruch der Verarbeitung führen müssen. Die Typen und unterscheiden sich dadurch, dass nach einem Fehler vom Typ die Verarbeitung, d.h. das Marshalling bzw. Unmarshalling, abgebrochen werden muss. Wird die Verarbeitung nach einem nicht abgebrochen, ist das Verhalten des oder undefiniert. : Mit dieser Eigenschaft erhalten wir die Fehlermeldung der Validierung in
natürlichsprachlicher Form. : Der Aufruf dieser Methode liefert die mit dem Ereignis ver-
knüpfte Fehlermeldung. : Mit Hilfe dieser Methode bekommen wir nähere Informationen über den
Ort, an dem der Fehler aufgetreten ist. Das zurückgegebene Objekt vom Typ enthält Eigenschaften, die je nach Art der Validierung gefüllt sind. Tritt der Fehler z.B. beim Marshalling auf, so erhalten wir Informationen über das Java-Objekt, bei dem der Fehler auftrat. Im Rahmen des Unmarshallings liefert das Objekt uns den Ort des XML-Dokuments sowie Zeilen und Spaltennummer des ungültigen XML-Elements. Um diese Ereignisse zu verarbeiten, können wir die Schnittstelle implementieren. Die oben genannte Standardfehlerbehandlung der JAXB wird von der Klasse übernommen, die eine einfache Implementierung von zur Verfügung stellt. Treten Ereignisse der Kategorie auf, gibt diese auf der Konsole aus. Beim Auftreten des ersten Fehlers ( oder ) hingegen wird die weitere Verarbeitung abgebrochen. Fehler der Kategorie werden dabei als weitergeleitet, so dass die Verarbeitung in jedem Fall abbricht. Nun gibt es aber durchaus Szenarien, in denen flexibler auf Fehler reagiert werden muss. In unserem Online-Banking-Service möchten wir vielleicht etwas fehlertoleranter sein.
102
4.4 Validierung Warnungen und unkritische Fehler sollen nur mit der entsprechenden Zeilen- und Spaltenangabe ausgegeben werden, jedoch nicht gleich zu einem nutzerunfreundlichen Programmabbruch führen. JAXB bietet uns dazu zwei verschiedene Möglichkeiten, individuell auf zu reagieren: Definition einer eigenen Fehlerbehandlung durch Implementierung des Interface . Die Methode wird für jeden aufgerufen und kann mit einer benutzerdefinierten Implementierung gefüllt werden. die Hilfsklasse benutzen. Diese Klasse sammelt alle , die bei der Validierung auftreten, in einer Liste. Diese Liste kann nach der Validierung von der Anwendung verarbeitet werden. 4.4.3.1
ValidationEventHandler implementieren
Schauen wir uns an einem praktischen Beispiel an, wie die Implementierung einer eigenen Fehlerbehandlung funktioniert. Das folgende Listing zeigt eine Klasse , die für jeden Fehler der Kategorie den Ort des fehlerhaften Elements im XML-Dokument sowie die Fehlermeldung selbst ausgibt. Im Unterschied zur Standardfehlerbehandlung bricht sie jedoch für einen Fehler der Kategorie nicht ab. Um den Ort des aufgetretenen Fehlers herauszubekommen, greifen wir dabei auf die Klasse zurück, die Methoden zur Lokalisierung der Fehler innerhalb der XML-Dokumente bietet.
103
4 JAXB-API
Ein auftretendes Ereignis vom Typ wird also an die Methode übergeben, die unsere benutzerdefinierte Logik enthält. Eine -Anweisung prüft zuerst die Kategorie des . Für die Kategorien und wird der Wert in der Methode zurückgegeben. Dadurch wird der aufrufende Prozess, in diesem Fall der , dazu veranlasst, das Unmarshalling fortzusetzen. Auf diese Weise kann ein Dokument trotz Fehlern weiter transformiert werden. Tritt jedoch ein Ereignis der Kategorie ein, wird der Wert zurückgegeben und die Verarbeitung des Dokuments abgebrochen. Ein Abbruch erfolgt ebenfalls, wenn innerhalb der Methode eine unbehandelte Ausnahme (unchecked exception) geworfen wird. Liefern wir im Fall eines Ereignisses vom Typ zurück, kann alles passieren, aber nur selten etwas Sinnvolles. Der so implementierte kann mittels der Methode beim bzw. registriert werden. Zu beachten ist noch, dass hier der Aufruf von weggelassen wurde, da sonst zusätzlich die Standardfehlerbehandlung greift. Diese würde das Unmarshalling dann aufgrund des Fehlers abbrechen. Ein kompletter Aufruf am Beispiel des Unmarshallers sieht folgendermaßen aus.
104
4.4 Validierung
Wenn wir hier im XML-Dokument wieder das Geburtsdatum unseres Kunden im Element auf den unseligen Wert setzen, bekommen wir folgende Meldung von unserem selbst implementierten .
Der Unmarshalling-Prozess läuft jetzt reibungslos bis zum Ende. Außerdem liefert der eben definierte uns jetzt zusätzlich zu einer Fehlermeldung den genauen Ort des Fehlers im aktuell verarbeiteten XML-Dokument. Das hier am Beispiel des -Objekts gezeigte Vorgehen kann natürlich auch analog mit dem durchgeführt werden. Hinweis
Einige Dinge sollten bei der Verwendung eines eigenen beachtet werden, um inkonsistente Zustände zu vermeiden. 1. Unbedingte Rückgabe des Wertes bei Fehlern der Kategorie 2. Der Java-Objektgraph sollte von einem nicht verändert werden, da dieses ebenfalls zu allerlei unerwarteten Nebeneffekten führen kann.
4.4.3.2
Die Hilfsklasse ValidationEventCollector verwenden
Das vorangegangene Beispiel hat gezeigt, wie wir auf Fehler während des Unmarshallings bzw. Marshallings direkt reagieren können. Denkbar wäre aber auch, dass wir nicht sofort auf auftretende Fehler reagieren wollen, sondern erst im Anschluss an die Validierung, wenn das Un-/Marshalling also abgeschlossen ist. So können wir z.B. Fehler über mehrere Aufrufe des Un-/Marshallings hinweg in einer Log-Datei sammeln. Hierzu existiert in JAXB eine Hilfsklasse, die uns das Sammeln von bereits abnimmt. Die Klasse im Paket erfüllt genau diese Funktion. Da diese Klasse ebenfalls die Schnittstelle implementiert, können wir sie ganz normal mit der Methode beim oder anmelden. Nach erfolgreichem Un-/Marshalling können die aufgetretenen
105
4 JAXB-API Ereignisse dann über die Methode abgerufen werden. Diese Methode liefert ein Feld von Objekten des Typs zurück. Die Verwendung des zeigt das folgende Listing am Beispiel des Unmarshallings.
Für dieses Beispiel setzen wir nicht nur ein falsches Geburtsdatum für den Kunden, sondern auch eine Postleitzahl, die neben Ziffern auch Buchstaben enthält.
106
4.5 Callback-Mechanismen einsetzen
Auf diese Weise erhalten wir die folgenden zwei -Instanzen, nachdem das Unmarshalling durchgeführt wurde.
Diese können wir nun für Fehler- oder Log-Ausgaben weiter aufbereiten.
4.4.4
Zusammenfassung
Es können in JAXB sowohl XML-Dokumente als auch Java-Objektgraphen anhand eines XML-Schemas validiert werden. Die Validierung wird einheitlich bei Marshalling und Unmarshalling verwendet. Die Validierung wurde mit der JAXB 2.0 komplett überarbeitet und verwendet bestehende Funktionalität aus der JAXP 1.3. Die Validierung wurde flexibler gestaltet. Die Behandlung von Fehlern kann mit einer Implementierung von auf spezielle Anwendungsbedürfnisse zugeschnitten werden. Die Validierung ist ausdrücklich optional gestaltet, sie kann z.B. aus Performancegründen deaktiviert werden. Ungültige Inhalte können in der Regel trotzdem verarbeitet werden.
4.5
Callback-Mechanismen einsetzen Dieser Abschnitt wird sich mit den in der JAXB-Übersicht beschriebenen CallbackMechanismen beschäftigen. Callback-Mechanismen erfreuen sich seit längerer Zeit großer Beliebtheit in vielen Java-Frameworks. So gibt es in vielen APIs und Frameworks sog. Listener, die das Einklinken von benutzerdefiniertem Code möglich macht. Auch JAXB macht hier keine Ausnahme. Es bietet im Wesentlichen zwei Mechanismen, über die eine JAXB-Implementierung benutzerdefinierten Code einbinden kann. Mit diesen Callback-Mechanismen kann der Entwickler die generierten Klassen mit applikationsspezifischer Logik anreichern, die dann während des Marshallings oder Unmarshallings ausgeführt wird.
107
4 JAXB-API Bei der ersten Variante wird der Code zentral in einer externen -Klasse definiert, während bei der zweiten Variante der Code direkt in den generierten Klassen abgelegt wird.
4.5.1
Die Listener-Klasse verwenden
Die JAXB-API stellt sowohl für das Marshalling als auch das Unmarshalling eine abstrakte -Klasse bereit. Diese Klassen sind jeweils als abstrakte, statische Klassen innerhalb der Interfaces und definiert. Sie definieren jeweils zwei Methoden, die von einer konkreten Implementierung überschrieben werden können. Es wurden hier bewusst keine Schnittstellen definiert, da durch die abstrakten Klassen wahlweise nur eine der beiden Methoden implementiert werden kann, während die andere leer bleibt.
Anhand der Namen der Methoden können wir leicht erraten, wann JAXB diese Methoden beim Marshalling bzw. Unmarshalling aufrufen wird. Beim Unmarshalling werden dabei jeweils das aktuell verarbeitete Element sowie das übergeordnete Element, an welches das aktuelle angehängt wird, übergeben. Beim Aufruf von ist das Objekt leer, bei sind alle Eigenschaften außer solche vom Typ befüllt. Beim Marshalling wird den Methoden jeweils nur das aktuell zu verarbeitende Objekt bergeben. Die einzige Besonderheit ist hier, dass die Methode erst aufgerufen wird, wenn auch das Marshalling aller Kindelemente des betreffenden Knotens beendet ist. Nach dieser allgemeinen Übersicht über die -Klassen wollen wir uns ein konkretes Beispiel anschauen. Beispielsweise möchten wir alle Kunden speichern, über die wir Informationen von unserem Online-Banking-Service beziehen. Dazu erstellen wir uns eine eigene -Implementierung für das Unmarshalling. Die folgende Klasse leitet daher von der Klasse ab und speichert alle Kundendaten, die beim Unmarshalling auftreten, in einer Liste.
108
4.5 Callback-Mechanismen einsetzen
Wir prüfen also, ob das aktuelle Element vom Typ ist. Falls ja, wird der Kundendatensatz in einer Liste gespeichert. Aber wie binden wir den jetzt in den Unmarshalling-Prozess ein? Ganz einfach, indem wir unsere frisch definierte Klasse beim -Objekt mit registrieren.
109
4 JAXB-API
4.5.2
Callback-Methoden auf Klassenebene definieren
Die Definition einer eigenen -Implementierung stellt eine recht allgemeine Methode dar, um einen Callback-Mechanismus zu implementieren. Wir können aber ähnliche Methoden wie bei der -Klasse für einzelne Java-Klassen definieren. Damit ist es möglich, auf Klassenebene einzelne Elemente zu modifizieren. Diesen Mechanismus setzen wir ein, wenn wir z.B. auf deklarierte Attribute und Felder einer mit JAXB gebundenen Java Bean zugreifen möchten. Zum Beispiel können wir Default-Werte spezifizieren, falls Elemente nicht befüllt werden. Das folgende Listing zeigt, wie diese Callback-Methoden definiert werden.
Die entsprechenden Methoden für den lauten wie folgt.
110
4.5 Callback-Mechanismen einsetzen
Implementiert eine Klasse diese Methoden, so werden sie durch den Marshaller bzw. Unmarshaller stets aufgerufen. Benutzerdefinierter Code und generierte Klassen Eine Sache gibt es hier jedoch zu bedenken. Wurde die Klasse aus einem bestehenden Schema generiert, so werden die eingefügten Methoden bei der nächsten Generierung schlicht und einfach überschrieben. Um dies zu verhindern, können wir den Schema-Compiler anweisen, alle generierten Attribute und Methoden einer Klasse mit der Annotation zu versehen. Später werden dann nur solche Elemente bei einer erneuten Generierung überschrieben, die diese Annotation besitzen. Benutzerdefinierte Methoden, wie etwa und , bleiben dann auch nach dem erneuten Generieren der Klasse erhalten. Dem -Task übergeben wir dazu folgenden Parameter:
Nach erneuter Generierung ist jeder Klassen-, Variablen- und Methodendefinition eine Annotation vorgeschaltet, die in etwa so aussieht:
Diese Annotation ist im JSR 250 Common Annotations definiert. Die entsprechende JAR-Bibiliothek für die Common Annotations ist leider nicht im Umfang der JAXBReferenzimplementierung enthalten. Die Codebasis zu JSR 250 wird aber in einem Projekt mit dem vielversprechenden Titel Pitchfork verwaltet und ist auf den Seiten der SpringEntwickler Interface 21 erhältlich. Die URL ist http://www.interface21.com/pitchfork/. Hinweis
Die beschriebene Funktionalität wird momentan in der JAXB-Referenzimplementierung noch nicht spezifikationsgemäß umgesetzt. Die Methoden werden in der uns vorliegenden JAXBImplementierung noch überschrieben.
111
4 JAXB-API
4.5.3
Zusammenfassung
Mit Callback-Mechanismen kann applikationsspezifische Logik in den Marshallingoder Unmarshalling-Prozess integriert werden. -Klassen erhalten Nachrichten über jedes im Rahmen der Marshallings bzw.
Unmarshallings erzeugte Java-Objekt. Über Methoden, die der Signatur der in der -Schnittstelle definierten Methoden , , , entsprechen, können einzelne Java-Klassen Nachrichten im Rahmen des Marshallings verarbeiten. Beide Mechanismen lassen sich kombinieren. Die Methoden auf Klassenebene werden dann stets vor den allgemeinen Methoden einer -Implementierung aufgerufen. Hinweis
Generell ist jedoch zu bedenken, dass jeder einzelne Aufruf der Callback-Methoden sich auf die Rechenzeit auswirkt, die für das Marshalling bzw. Unmarshalling benötigt wird. Daher sollten in diesen Methoden keine sehr aufwendigen Verarbeitungen untergebracht werden, vor allem wenn es sich um performancekritische Anwendungen handelt.
4.6
Die Binder-Komponente verwenden Im Übersichtskapitel zur JAXB-Architektur haben wir die -Komponente eingeführt, mit der transparent zwischen zwei Sichten auf ein XML-Dokument hin- und hergewechselt werden kann. Wir haben also über die Binder-Komponente die Möglichkeit, Änderungen an einer der beiden Sichten vorzunehmen und die andere Sicht durch die Binder-Komponente automatisch aktualisieren zu lassen. Dadurch können wir z.B. in einer Anwendung intern mit JAXB arbeiten, obwohl die Daten an anderer Stelle in Form einer DOM-Instanz vorliegen. Die JAXB-Referenzimplementierung unterstützt hierfür derzeit allerdings nur die W3CDOM-Sicht, die ganz allgemein aus Objekten vom Typ besteht, die JAXB-Spezifikation erlaubt auch andere DOM-Repräsentationen. Die Komponente trägt also zu Recht den Namen , es werden die jeweils zueinander gehörigen Objekte zweier Sichten aneinandergebunden. Verändern wir nun ein Objekt z.B. in der JAXB-Sicht, kann der Binder den zugehörigen DOM-Knoten der anderen Sicht einfach aktualisieren, da er sich die Beziehung zwischen den beiden Objekten merkt. Das funktioniert natürlich auch für den umgekehrten Fall, falls ein DOM-Knoten verändert wird, kann auch das passende JAXB-gebundene Java-Objekt aktualisiert werden. In jedem Fall müssen wir Änderungen aber nur an einem der Objekte vornehmen, der Binder kümmert sich um den Rest.
112
4.6 Die Binder-Komponente verwenden
4.6.1
Eine DOM-Sicht erstellen
Auf den folgenden Seiten werden wir uns häufiger mit der DOM-Sicht auf ein XMLDokument beschäftigen. Daher sollten wir uns ein kurzes Beispiel anschauen, wie wir eine solche DOM-Sicht auf ein XML-Dokument erzeugen. Der folgende Quelltextauszug erzeugt eine neue DOM-Sicht durch Parsen eines XML-Dokuments mit der DOM-API der JAXP 1.3.
Wir legen also zunächst eine an, die uns wiederum einen bereitstellt. Der ist in der Lage, XML-Dokumente zu parsen und als Objekte vom Typ darzustellen. Dieses Objekt stellt das Wurzelelement eines DOM-Baums dar und definiert den Stamm einer Baumstruktur von Knoten des Typs . Damit eventuell vorhandene Namespaces verarbeitet werden, muss das Attribut der auf gesetzt werden.
4.6.2
Die Klasse javax.xml.bind.Binder
JAXB vereint die oben beschriebenen Funktionen in einer einzigen abstrakten Klasse, die den Namen Binder trägt. Um einen Überblick über die Funktionsweise des Binders zu bekommen, sehen wir uns die Methoden dieser Klasse einmal an:
113
4 JAXB-API
Eine Referenz auf ein Objekt vom Typ erhalten wir wieder über das -Objekt, das wir hier mit einer der Klasse initialisieren:
Der Aufruf von erzeugt einen Binder speziell für eine W3C-DOM-Sicht. W3C-DOM ist in JAXB die Standardeinstellung. Soll eine andere DOM-API verwendet werden, kann dies durch folgenden Aufruf erreicht werden:
Hier wird der -Methode das Klassenobjekt der -Klasse aus der jeweiligen DOM-API übergeben. Dadurch erzeugt die JAXB-Implementierung eine Instanz, die diese DOM-API unterstützt. Hinweis
In der uns vorliegenden JAXB-Referenzimplementierung wird derzeit nur die W3C-DOMAPI unterstützt. Ein Aufruf von wird daher generell mit einer vorzeitig beendet. Hier ist momentan nur der Standardweg über möglich.
Die konkrete Implementierung der abstrakten Methoden aus der -Klasse bleibt hier wieder der JAXB-Implementierung überlassen. Für uns als Anwender ist es völlig transparent, welche konkrete Klasse hier wirklich zurückgegeben wird. Die Methoden der Klasse können unterteilt werden in Methoden zur Transformation, zur Navigation und zur Synchronisation einer DOM- und einer JAXB-Sicht auf ein XML-Dokument. Die Verwendung dieser Methoden zeigen wir in dem folgenden Abschnitt.
4.6.3
Transformation mit unmarshal und marshal
Diese Methoden funktionieren ähnlich wie die gleichnamigen Methoden der - und -Klassen. Die -Methode überführt eine DOM-Sicht eines XML-Dokuments in die entsprechenden Java-Objekte. Wir übergeben dabei das Wurzelelement des DOM-Baums als Parameter.
114
4.6 Die Binder-Komponente verwenden
Dieser Aufruf funktioniert wie dargestellt, wenn die Klasse an ein gültiges Wurzelelement für ein XML-Dokument gebunden ist und einem solchen Wurzelelement entspricht. Wie beim gewöhnlichen Unmarshalling kann auch hier explizit als zweiter Parameter noch ein Typ angegeben werden, in den der DOM-Knoten überführt werden soll. Diesen Parameter müssen wir also auf jeden Fall angeben, wenn der aktuelle Knoten kein Wurzelelement darstellt. In diesem Fall besitzt die zugehörige Java-Klasse in der Regel auch keine -Annotation. Der folgende Aufruf liefert daher als Ergebnis ein Objekt vom Typ zurück.
Die -Methode hingegen überführt einen Java-Objektgraphen in die entsprechende DOM-Sicht. Der erste Parameter der Methode stellt dabei das Wurzelement des zu transformierenden Java-Objektgraphen dar. Im Unterschied zur -Methode gibt es hier keinen Rückgabewert. Die Methode verändert direkt den als zweiten Parameter übergebenen DOM-Knoten.
4.6.4
Navigation mit getXMLNode und getJAXBNode
Nachdem wir nun wie oben beschrieben verbundene Objekte der JAXB- und der DOMSicht erzeugt haben, können wir mit den Methoden und zwischen diesen beiden Sichten hin- und herwechseln. Das ist möglich, da der Binder eine Abbildung speichert, in der zu jedem Objekt der zugehörige Partner in der anderen Sicht abgelegt ist. Die Methode erwartet eine Instanz einer Java-Klasse mit XMLBindung und liefert den entsprechenden Knoten der DOM-Sicht, falls eine entsprechende Abbildung vorhanden ist. Die Methode arbeitet umgekehrt, liefert also für einen DOMKnoten das entsprechende Java-Objekt und erwartet daher einen DOM-Knoten als Parameter.
115
4 JAXB-API
4.6.5
Synchronisation mit updateXML und updateJAXB
Nachdem wir die Daten einer der beiden Sichten geändert haben, können wir diese Änderungen durch Update-Methoden in die jeweils andere Sicht übernehmen. Angenommen, wir haben ein per JAXB gebundenes Java-Objekt in unserer Anwendung geändert und möchten die Änderungen in das DOM-Modell übernehmen. Die JAXB-API gibt uns dafür zwei Methoden an die Hand, deren Aufruf wir hier kurz zeigen:
Die erste Methode überführt das geänderte Java-Objekt in den als zweiten Parameter gegebenen DOM-Knoten. Intern wird dabei ein Marshalling des Java-Objektes in die entsprechende Stelle im DOM-Modell durchgeführt. Daher wird nicht das gesamte DOM-Modell, sondern nur der übergebene Knoten neu erzeugt. Dieses Verfahren wird auch In-Place-Marshalling genannt. Der zurückgelieferte DOM-Knoten ist im Normalfall identisch zum vorher übergebenen Knoten. Für den Fall, dass sich wesentliche Aspekte des Objekts geändert haben, kann jedoch auch ein neues -Objekt zurückgeliefert werden. Die zweite -Methode ist die bequemere Variante der ersten, die sich den zugehörigen DOM-Knoten selbst über die Abbildungstabelle des holt. Für den umgekehrten Fall, nämlich eine Änderung am DOM-Modell, kann die parallel existierende JAXB-Sicht ebenfalls mit den vorgenommenen Änderungen aktualisiert werden. Dazu existiert analog zu eine Methode , deren Aufruf hier beispielhaft illustriert ist:
116
4.6 Die Binder-Komponente verwenden
4.6.6
Konkrete Anwendungsfälle
Bisher haben wir nur kurze Codebeispiele zu den einzelnen Methoden der -Klasse gesehen. Um die Verwendung der -Klasse in einer XML-basierten Anwendung zu verstehen, sehen wir uns im Folgenden zwei konkrete Anwendungsfälle an.
Erstellen einer SOAP-Nachricht Im Übersichtskapitel wurde bereits erwähnt, dass die -Klasse häufig zur Erstellung und Verwaltung einer zweiten Sicht auf ein XML-Dokument, z.B. neben einer DOMSicht, verwendet wird. Im ersten Anwendungsfall bekommen wir eine solche DOM-Sicht auf ein XML-Dokument als Eingabe. Die eigentliche Datenverarbeitung möchten wir aber mit Java-Klassen durchführen, da der Datenzugriff über die DOM-Knoten doch recht mühsam ist. Daher erstellen wir mit dem Binder eine zweite Sicht auf das XML-Dokument. Erinnern wir uns daher wieder an den Online-Banking-Service. Angenommen dieser Service wird als Webservice bereitgestellt: Dann muss der Service eventuell auch eine vollständige SOAP-Nachricht als Antwort versenden können. Eine SOAP-Nachricht besteht aus einem Umschlag, dem SOAP-Envelope, und der eigentlichen Nachricht, dem SOAP-Body. Der SOAP-Body enthält die bekannte Struktur mit den eigentlichen Daten. Als Basis erstellt der Online-Banking-Service die beschriebene SOAP-Nachricht im DOM-Format mit einem zunächst leeren ResponseElement.
Zum Befüllen des -Elements mit den entsprechenden Kunden-, Konto- und Portfolioinformationen kann jetzt die Funktionalität des eingesetzt werden. Dazu wird zunächst mit Hilfe eines XPath-Ausdrucks das -Element in der DOM-Sicht lokalisiert und als Knoten gespeichert. Wie genau die XPath-Syntax aussieht, um ein Element zu lokalisieren, kann in der kurzen XPath-Einführung nachgelesen werden. Der Knoten wird danach mit der -Methode des in ein Java-Objekt vom Typ transformiert .Die Anwendung kann dieses Java-Objekt
117
4 JAXB-API jetzt bequem mit Inhalten füllen. Haben wir alle Daten ermittelt, wird die DOM-Sicht auf die SOAP-Nachricht mit den Änderungen der gebundenen Java-Objekte synchronisiert. Die Verbindung zwischen dem Objekt und dem entsprechenden DOM-Knoten in der Variablen bleibt durch den erhalten. Das folgende Beispiel zeigt dies beispielhaft für das Anlegen eines neuen Kunden. Dieser Kunde wird einfach über eine statische Methode erzeugt.
118
4.6 Die Binder-Komponente verwenden
Nach Ausführen des oben beschriebenen Codes wird als Ergebnis das folgende XMLDokument erzeugt.
Die Anwendung muss in diesem Fall nur Schemainformationen über das Dokument besitzen. Der Rest des Dokuments kann einem beliebigen Schema folgen, das nicht durch Java-Klassen abgebildet sein muss. Das umgebende XML-Dokument kann mit den Mitteln der DOM-API beliebig geändert werden. Die Anwendung muss nur in der La-
119
4 JAXB-API ge sein, das -Element über einen XPath-Ausdruck zu lokalisieren. Die BinderKomponente eignet sich daher hervorragend für Szenarien, in denen XML-Dokumente nur teilweise verändert werden oder in denen das XML-Dokument für eine Reihe von getrennt programmierten Modifikationen nicht jedes Mal vollständig neu eingelesen werden soll. Schnelle Navigation mit XPath Im zweiten Beispiel wollen wir die DOM-Sicht nutzen, um schnell durch ein XMLDokument zu navigieren, während die JAXB-Sicht für den Zugriff auf die eigentlichen Daten verwendet wird. Zur schnellen Navigation durch das XML-Dokument nutzen wir dazu die Vorteile von XPath-Ausdrücken in der DOM-Sicht. Gehen wir daher wieder von einem Dokument aus, das ein -Element enthält.
Dieses Dokument liegt unserer Anwendung in einem Objekt vom Typ vor, das wir z.B. durch den Aufruf des Online-Banking-Service erhalten haben (hier durch die fiktive Methode dargestellt).
120
4.6 Die Binder-Komponente verwenden Im Portfolio, das durch dieses Dokument beschrieben wird, wollen wir nun den Wert der Wertpapierposition mit dem Namen BlueSteel Pty. Ltd. ändern. Wir könnten dies tun, indem wir durch den Java-Objektgraphen an die entsprechende Stelle der Wertpapierliste navigieren. Im Beispieldokument wäre dies zugegebenermaßen nicht allzu schwer. In komplexeren Dokumentstrukturen, in denen das Element an verschiedenen Stellen auftreten kann, ist dieses Vorgehen jedoch recht mühsam. Daher nutzen wir XPath-Ausdrücke in der DOM-Sicht, um das gesuchte -Element zu finden. Dazu erstellen wir zunächst ein leeres DOM-Dokument für die DOM-Sicht.
Durch Aufruf der folgenden Methode des wird dieses Dokument mit den Inhalten aus dem Java-Objekt befüllt, wobei die Beziehungen zwischen den beiden Sichten erhalten bleiben.
Jetzt lassen wir uns per XPath-Abfrage das entsprechende -Element zurückliefern. Hier wird eine statische Methode unserer Utility-Klasse verwendet. Diese Methode evaluiert einen XPath-Ausdruck und liefert das Ergebnis als Knotenobjekt vom Typ zurück.
Mit Hilfe der Methode holen wir uns eine Referenz auf ein gebundenes Java-Objekt vom Typ . An diesem Objekt nehmen wir nun programmatisch die Änderungen vor. In dem abgedruckten Fall ändern wir nur den Gesamtwert der Position. Danach übernehmen wir die Änderungen in die DOM-Sicht. Das DOM-Dokument wird zuletzt von der Anwendung als XML-Dokument auf die Standardausgabe geschrieben.
Die erzeugte Ausgabe zeigt nun ein XML-Dokument mit der aktualisierten Wertpapierposition.
121
4 JAXB-API
Der Übersichtlichkeit zuliebe ist hier noch einmal das gesamte Listing für das Beispiel aufgeführt:
122
4.6 Die Binder-Komponente verwenden
4.6.7
Zusammenfassung
Mit der Binder-Komponente können komfortabel zwei Sichten auf ein XMLDokument gleichzeitig verwendet werden. Änderungen an einer Sicht werden über einfache Aktualisierungsmethoden mit der jeweils anderen Sicht synchronisiert. Binder hält Informationen über Beziehungen der Elemente zweier Sichten, diese sind auch programmatisch von außen verfügbar. Sinnvoll, wenn wir große XML-Dokumente verarbeiten, von diesen aber nur einen kleinen Teil lesen und verändern wollen. Nur diese Teile werden dann dank des Binders in JAXB-gebundene Java-Klassen umgewandelt. XML-Kommentare bleiben beim Un/Marshalling durch die Binder-Komponente möglichst unberührt. Würden wir das komplette XML-Dokument in Java-Objekte und wieder zurück umwandeln, gingen diese Kommentare verloren.
123
5 XML-Schema zu Java Dieses Kapitel wird sich mit den schemaseitigen Bindungskonfigurationen beschäftigen, die in der JAXB 2.0 spezifiziert sind. Es Kapitel wendet sich an den Leser, dem das Schicksal ein XML-Schema vor die Füße geworfen hat und der nun nach Möglichkeiten sucht, XML-Dokumente dieses Schemas möglichst schnell mit einem schönen JavaDatenmodell zu kapseln. Stück für Stück werden wir in diesem Kapitel darstellen, wie der Schema-Compiler aus einem XML-Schema ein Java-Datenmodell erzeugt und wie wir über Bindungsdeklarationen auch erreichen können, dass das Modell im Detail den vielfältigen Anforderungen genügen kann. Die Beispiele in diesem Kapitel betten wir in den Kontext des Online-Banking-Service ein, dessen Beispielschemas immer wieder Ausgangspunkt für allerlei Konfigurationen sind. Dieses Kapitel bringt die Anforderungen und die als Lösung gebotenen Deklarationen zusammen im Detail werden hier zugunsten der Prägnanz der Beispiele immer wieder Fragen offenbleiben. Hier möchten wir auf die JAXB-Referenz dieses Buches verweisen: Dort haben wir jedes Element einzeln in seiner Bedeutung und Anwendung möglichst vollständig beschrieben.
5.1
Bindungskonfigurationen Bindungskonfigurationen bezeichnen die benutzerdefinierte Anpassung der Bindung eines existierenden Schemas an ein Java-Datenmodell. Eine Bindungskonfiguration besteht dabei aus einer Reihe von Bindungsdeklarationen, in der JAXB-Spezifikation auch Binding Declarations genannt. Der Schema-Compiler einer JAXB-Implementierung verwendet im Normalfall die in der JAXB-Spezifikation definierten Standardeinstellungen, um aus einem bestehenden XMLSchema die entsprechenden Java-Klassen zu generieren. So entstehen z.B. aus einem XML-Namespace auf wohldefinierte Weise Java-Pakete, in denen an diesen Namespace gebundene Java-Klassen zusammengefasst werden. Aus
125
5 XML-Schema zu Java XML-Datentypen entstehen Java-Klassen, auch Value Classes genannt. Die Elemente selbst, also die Instanzen der XML-Datentypen, werden als Instanzen vom Typ abgebildet. Attribute und Unterelemente werden standardmäßig als Eigenschaften einer Java Bean dargestellt und so weiter. Es gibt aber Anwendungsfälle, in denen reicht uns diese Standardbindung nicht aus. Hier geben wir dann eine Bindungskonfiguration an, welche die generierten Klassen entsprechend anpasst. Es geht dabei nicht nur um die Anpassung von Standardeinstellungen. Die Bindungskonfiguration kann den generierten Klassen auch zusätzliche Komponenten hinzufügen, wie etwa durch das Generieren zusätzlicher Methoden. Typische Anwendungsfälle, in denen Bindungskonfigurationen genutzt werden, sind z.B.: Dokumentation eines Java-Datenmodells durch Einfügen von Javadoc-Kommentaren. Anpassen der Standardnamensauflösung von XML-Bezeichnern in Java-Bezeichner bei Namenskonflikten im generierten Java-Datenmodell. Konvertierung und Anpassung von Datentypen bei Veränderungen in der Umgebung. Die einzelnen Bindungsdeklarationen, die dazu gemäß JAXB-Spezifikation in einer Bindungskonfiguration definiert werden können, werden allerdings nicht als vollständig und ausreichend angesehen. Daher gibt es einen Mechanismus, mit dem jede konkrete JAXBImplementierung ihre eigenen Bindungsdeklarationen hinzufügen kann. Der Preis für die Benutzung dieser Deklarationen ist jedoch die Portabilität unseres Codes zwischen verschiedenen JAXB-Implementierungen, die wir damit aufgeben. Auf einige Erweiterungen, die bereits die Referenzimplementierung selbst anbietet, wird am Schluss dieses Kapitels eingegangen. Zunächst sollten wir uns allerdings der allgemeinen Definition und Syntax einer Bindungskonfiguration widmen.
5.1.1
Eine Bindungskonfiguration definieren
Eine Bindungskonfiguration wird bei JAXB, wie könnte es anders sein, in XML verfasst. Dazu werden eine oder mehrere Bindungsdeklarationen angelegt. Ganz allgemein liegen all diese Deklarationen im gleichen Namespace http://java.sun.com/xml/ns/jaxb. Dieser Namespace wird nach allgemeiner Konvention mit dem Präfix versehen. Das führt dazu, dass wir eine JAXB-Bindungsdeklaration als ein XML-Element mit einem oder mehreren Unterelementen bzw. Attributen definieren. Das äußere Element gibt dabei den Gültigkeitsbereich an, während die eigentlichen Deklarationen durch Attribute oder geschachtelte Elemente angegeben werden.
Diese Bindungsdeklaration veranlasst den Schema-Compiler, alle generierten Klassen serialisierbar zu machen, d.h., alle generierten Klassen implementieren das Interface . Dies kann nützlich sein, wenn die Klassen z.B. als Parameter für
126
5.1 Bindungskonfigurationen RMI-fähige Methoden verwendet werden sollen, oder allgemein überall dort, wo Objektserialisierung/-deserialisierung verwendet wird. Der Gültigkeitsbereich wird hier durch das Element definiert, es handelt sich also um eine globale Einstellung, während die eigentliche Bindungsdeklaration im Element steckt. Zu den einzelnen Gültigkeitsbereichen erfahren wir später noch mehr. Sehen wir uns erst einmal an, wo wir diese Deklaration angeben können. Inline oder extern definieren? Die JAXB-Spezifikation lässt uns bei der Definition einer Bindungskonfiguration die Wahl zwischen zwei Möglichkeiten. Zum einen kann die Konfiguration innerhalb des betreffenden XML-Schemas definiert werden, dies wird auch eine Inline-Definition genannt. Es ist aber auch möglich, die Bindungskonfiguration in einem separaten XML-Dokument abzulegen. Eine Bindungsdeklaration selbst, wie oben gesehen, sagt jedoch nichts aus über den Ort, an dem sie definiert wird, bzw. über das Element/Attribut, auf das sie sich auswirkt. Die obige Syntax ist für Inline- und externe Definitionen gleich. Der Wirkungsbereich einer Bindungsdeklaration ergibt sich aus dem Kontext, in dem sie definiert wird. Steht die Bindungsdeklaration in einer Schema-Annotation zu einem Datentyp, so bezieht sich die Konfiguration auf diesen Datentyp. Die beiden Vorgehensweisen der Definition haben ihre Stärken und Schwächen. Für eine Definition innerhalb des Schemas, also inline, spricht: Die Zusammengehörigkeit von XML-Schemaelement und Bindungskonfiguration ist auf einen Blick erkennbar. Der Ort des Schemas muss nicht angegeben werden. Beim Verschieben und Verschicken des Schemadokuments wird die Bindungskonfiguration stets mitgenommen. Es muss im Gegensatz zur externen Definition kein XPath-Ausdruck auf das entsprechende XML-Schemaelement angegeben werden. Gegen eine Inline-Definition und für eine externe Definition spricht Eine große Anzahl Bindungskonfigurationen innerhalb eines XML-Schemas macht es unleserlich Eine Bindungskonfiguration in einer externen Datei kann leicht wiederverwendet werden, um z.B. die gleiche Konfiguration für mehrere Schemas gleichzeitig anzuwenden. Dadurch wird eine redundante Definition vermieden. Hinweis
Natürlich ist es auch möglich, sowohl inline als auch extern definierte Bindungskonfigurationen zu nutzen. Beispielsweise könnte man Bindungsdeklarationen, die allgemeiner Natur sind und für mehrere Schemas gelten können, in einer externen Definition verwalten, während die individuelle Konfiguration durch eine Inline-Konfiguration stattfindet. Zu beachten ist dabei aber, dass pro Schemaelement nur eine Bindungskonfiguration definiert werden kann. Die Konfigurationen müssen daher disjunktiv sein.
127
5 XML-Schema zu Java Inline-Definition ein Beispiel Ganz allgemein werden Bindungsdeklarationen inline durch ein XML-Element definiert. Dieses Element gehört zum Grundumfang von XML-Schema. Es dient dazu, ein XML-Schema zu dokumentieren, für Mensch und Maschine zugleich.
Der Schema-Compiler interessiert sich nur für den maschinenlesbaren Teil dieses -Elements, nämlich den -Teil. Die Bindungsdeklaration befindet sich daher im -Element. Das sehen wir uns einmal an einem konkreten Beispiel an. Das folgende Schemadokument definiert gleich auf oberster Ebene nach dem -Element eine Bindungskonfiguration durch die Angabe der einzelnen Bindungsdeklaration .
Wie wir hier sehr schön sehen können, besitzt eine Inline-Bindungskonfiguration immer einen Kontext, einen Bezug zu dem Element, in dem sie definiert wurde. In unserem Fall ist dies das -Wurzelelement selbst. Dies ist hier auch notwendig, da wir eine globale Bindungsdeklaration mit angegeben haben. Diese ist daher für das gesamte Schema gültig.
128
5.1 Bindungskonfigurationen Externe Bindungskonfigurationen Sehen wir uns jetzt das Gegenstück zur obigen Inline-Definition an. Die folgende externe Bindungskonfiguration legen wir in einer getrennten Datei binding.xjb ab. Der Name der Datei ist dabei frei wählbar.
Die Datei besitzt als Wurzelelement ein Element , durch das der SchemaCompiler erkennt, dass es sich hier um eine externe Bindungskonfiguration handelt. Das oberste -Element muss die Namespaces http://java.sun.com/xml/ns/jaxb und http://www.w3.org/2001/XMLSchema deklarieren und zusätzlich die JAXB-Version mit dem Element angeben. Wir sehen hier, dass die Bindungsdeklaration im Element an sich die gleiche geblieben ist. Da es sich hier aber um eine externe Datei handelt, müssen wir einen Bezug zum Element des XML-Schemas herstellen, das wir konfigurieren möchten. Dies wird im einleitenden Tag mit dem Attribut vorbereitet, in dem wir in der Bindungskonfiguration das Schema angeben, auf das sich die Konfiguration bezieht. Eine externe Konfiguration muss hierbei durchaus auch auf mehrere Schemas Bezug nehmen. Mit dieser Angabe weiß der Schema-Compiler jetzt zumindest, welches XML-Schema gemeint ist. Woher soll er aber wissen, welche Knoten wir eigentlich mit dieser Konfiguration anpassen wollen? Hier kommt wieder die XPath-API ins Spiel. Ein XPath-Ausdruck eignet sich prima, um das Element unseres XML-Schemas anzugeben, auf das sich die Bindungskonfiguration bezieht. Daher geben wir im verschachtelten Element mit dem Attribut den Wurzelknoten des XML-Schemas als XPath 1.0-Ausdruck an. Der Schema-Compiler wertet jetzt diesen Ausdruck aus, um das konfigurierte Element zu ermitteln. Über diesen Mechanismus kann jeder beliebige Knoten in einem Schema referenziert (und konfiguriert) werden. Hinweis
Die externe Definition einer Bindungskonfiguration mag auf den ersten Blick etwas aufwendig erscheinen. In der Praxis hat sie sich jedoch als die übersichtliche Variante bewährt und ist außerdem im Rahmen der Beispielanwendungen zu handhaben wir werden daher im Folgenden die einzelnen Bindungskonfigurationen stets als externe Definitionen angeben.
129
5 XML-Schema zu Java
5.1.2
Bindungskonfigurationen per xjc-Task einbinden
Wir haben unserer Bindungskonfiguration bereits mitgeteilt, auf welches XML-Schema sie sich auswirken soll. Der Schema-Compiler hat aber noch keinen blassen Schimmer, dass wir diese Bindungskonfiguration beim Generieren der Java-Klassen wirklich anwenden wollen. Wir können dies über eine Erweiterung unseres Build-Skripts tun. Das ANT-Task , das den Schema-Compiler aufruft, wird um das Attribut erweitert, das eine Referenz auf die Datei angibt. Es wird dabei angenommen, dass das Schema und die Bindungskonfiguration im selben Verzeichnis liegen.
Prompt beachtet nun der per ANT gestartete Schema-Compiler unsere Konfiguration.
5.1.3
Den XJC-Schema-Compiler programmatisch starten
Bisher haben wir stets ein ANT-Skript bemüht, um mit dem Schema-Compiler JavaKlassen zu generieren. Dazu haben wir den -Task aus der JAXBReferenzimplementierung benutzt. Aber nicht in allen Szenarien steht uns eine ANTUmgebung zur Verfügung. Es könnten z.B. auch andere Tools wie Maven 1 für den Build1 http://maven.apache.org
130
5.1 Bindungskonfigurationen vorgang benutzt werden. Wie auch immer, der ANT-Task selbst ist auch nur eine Art Wrapper um den eigentlichen Schema-Compiler. Der Schema-Compiler kann nämlich problemlos auch stand-alone innerhalb einer Java-Anwendung ausgeführt werden. Wir werden das jetzt einfach machen. Die folgende Beispielklasse erzeugt den gleichen Code wie das obige ANT-Beispiel, allerdings rein programmatisch.
Zuerst erzeugen wir in dem Beispiel ein -Objekt mit der Klasse .
131
5 XML-Schema zu Java Danach setzen wir den Paketnamen auf den Paketnamen unserer Beispielklasse. In dieses Paket wird, soweit nicht durch eine Bindungskonfiguration anders definiert, der erzeugte Code generiert. Um eventuelle Fehler bei der Generierung zu behandeln, können wir zusätzlich einen beim registrieren. Dazu müssen wir die einfache Schnittstelle implementieren. Unsere Implementierung dieses sei hier nur kurz dargestellt, da sie für den weiteren Prozess eher zweitrangig ist.
In unserem Fall gibt dieser also einfach nur auftretende Fehlermeldungen auf der Konsole aus. Nachdem wir die Grundvoraussetzungen geschaffen haben, brauchen wir jetzt natürlich noch Input in Form eines XML-Schemas und einer dazugehörigen externen Bindungskonfiguration. Zum Einlesen dieser Dokumente können wir verschiedene Eingabeformate verwenden. In unserem Beispiel wird das Schema über eine der StAX-API eingelesen. Dieser übergeben wir den Namen der Schemadatei und setzen die System-ID der auf den URI des Schemas.
Weitere Eingabeformate sind hier noch DOM-Elemente und der der StAX-API.
132
5.1 Bindungskonfigurationen Der Aufruf für die Bindungskonfiguration ist ähnlich wie der Aufruf zum Einlesen des XML-Schemas, nur wird hier der Bequemlichkeit halber die Methode genutzt, da unsere Beispielklasse und die Datei im gleichen Verzeichnis liegen. Als System-ID der wird hier das Basisverzeichnis gewählt. Dieses Verzeichnis nutzt der Schema-Compiler später, um Pfadangaben zu Schemas in der Bindungskonfiguration aufzulösen.
Mit dem Parsen dieser Dokumente erstellt der ein Objektmodell mit den vom Schema abgeleiteten Java-Klassen. Dieses Objektmodell binden wir jetzt an ein Objekt vom Typ , das neben dem generierten Code noch zusätzliche Informationen über die Abbildung der Schemakomponenten auf die Java-Objekte enthält.
Momentan sind wir aber nur an dem erzeugten Java-Code interessiert. Diesen können wir über die Methode erhalten. Als Parameter können wir hier ein Feld von -Erweiterungen (-Plugins) und einen übergeben. Da wir momentan aber keine -Erweiterungen definiert haben, übergeben wir hier einfach .
Als Ausgabe geliefert wird jetzt ein Objekt vom Typ . Dieses Objekt besitzt schließlich eine Methode , mit der die Daten als Java-Klassen in das angegebene Verzeichnis generiert werden. Die -Methode gibt die Namen der generierten Artefakte auf der Konsole aus.
Damit haben wir den programmatisch aufgerufen. Das Ergebnis des Aufrufs ist identisch mit dem Ergebnis, das uns zuvor der ANT-Task lieferte. Um in unseren Beispielen von ANT-Skripten unabhängig zu sein, werden wir den auf diese Weise im weiteren Verlauf des Kapitels zur Codegenerierung nutzen. Außerdem erleichtert es die Strukturierung der Beispielaufgaben, da das Anlegen separater Buildskripte entfällt. Grundsätzlich kann aber auch der Aufruf über ein ANT-Skript erfolgen.
133
5 XML-Schema zu Java Hinweis
Die vorgestellte Vorgehensweise zum Einsatz des Schema-Compilers bezieht sich auf die JAXB-Referenzimplementierung. Der dargestellte Quelltext wird nicht ohne weiteres mit anderen Implementierungen funktionieren. Auf die Beispiele selbst hat das keine Auswirkung.
5.1.4
Externe Bindungskonfigurationen auf der Kommandozeile
Wir können dem Schema-Compiler auch externe Bindungskonfigurationen über die Kommandozeile übergeben. Dies ist zwar weniger bequem als der ANT-Task und weniger dynamisch als das programmatische Starten, aber es lässt sich innerhalb von Shell-Skripten dennoch recht gut einsetzen. Daher wollen wir hier auch ein kurzes Beispiel für den Aufruf des Schema-Compilers per Kommandozeile zeigen. Wir übergeben die Dateien mit Bindungskonfigurationen über den Parameter . Es können auch mehrere Konfigurationen verarbeitet werden. Die Reihenfolge der Angabe spielt dabei keine Rolle.
Hier der Befehl mit mehreren Bindungskonfigurationen:
5.1.5
Ein Wort zu Versionen
Bisher haben wir ein Attribut unterschlagen, das in den bisherigen Bindungskonfigurationen auftaucht. Das Attribut wird sowohl bei Inline- als auch bei externen Bindungskonfigurationen angegeben, um eine Rückwärtskompatibilität in späteren Versionen der JAXB-Spezifikation gewährleisten zu können. Da wir in diesem Buch ausschließlich mit der Version 2.0 arbeiten, geben wir daher sowohl im -Element bei InlineDefinition als auch im Element bei externer Definition stets die Versionsnummer 2.0 an.
5.1.6
Der Sichtbarkeitsbereich
Bindungskonfigurationen besitzen immer einen Sichtbarkeitsbereich, auch Scope genannt. Der Begriff Scope lässt sich leider nur schwer ins Deutsche übertragen, Sichtbarkeitsbereich oder Wirkungsbereich können hier nur als gute Näherung angesehen werden. Mit dem Scope einer Bindungskonfiguration ist also die Menge der Schemaelemente gemeint, auf die sich die Bindungskonfiguration bezieht. In JAXB gibt es vier verschiedene Sichtbarkeitsbereiche bzw. Scopes. Global: Eine globale Einstellung gilt für sämtliche Schemas, die mit dieser Bindungskonfiguration verarbeitet werden. Das bedeutet auch, mit den Schemaelementen
134
5.1 Bindungskonfigurationen und referenzierte Schemas werden rekursiv einbezogen, d.h., für alle die-
se Schemas gilt die Einstellung ebenfalls. Schema: Eine schemalokale Einstellungen ist an ein bestimmtes Schema gebunden. Wenn mit dieser Bindungskonfiguration mehrere Schemas verarbeitet werden, dann können für jedes einzelne Schema andere Einstellungen definiert werden. Typ/Element: Gültig für einzelne, global definierte Typen und Elemente einer Schemainstanz. Komponente: Gültig für Unterelemente einer Typ- bzw. Elementdeklaration einer Schemainstanz. Diese Sichtbarkeitsbereiche werden durch die folgende Auswahl an Elementen repräsentiert, die jeweils eine Reihe von Bindungsdeklarationen zu einer Ebene gruppieren. Einige Bindungsdeklarationen sind in mehreren dieser Ebenen zu finden und können sich überlagern, wie wir am Ende des Kapitels noch näher betrachten werden.
<property> Komponente
Typ / Element
Schema
Global
<schemaBindings>
Abbildung 5.1 Sichtbarkeitsbereiche der Bindungsdeklarationen
5.1.7
Zusammenfassung
Das Standardverhalten des Schema-Compilers kann durch Bindungskonfigurationen angepasst werden. Eine Bindungskonfiguration besteht aus einer Reihe von Bindungsdeklarationen. Bindungskonfigurationen können sowohl inline im XML-Schema als auch über externe Konfigurationsdateien definiert werden. Bindungskonfigurationen können auf der Kommandozeile, im -Task oder auch programmatisch an den Schema-Compiler übergeben werden.
135
5 XML-Schema zu Java Die Bindungsdeklarationen gliedern sich in verschiedene Sichtbarkeitsbereiche. Je nach Sichtbarkeitsbereich ist eine Bindungsdeklaration für alle Schemas, das aktuelle Schema oder auch nur einzelne Elemente oder Datentypen eines Schemas gültig.
5.2
Die erste Bindungskonfiguration Genug der Theorie auf zu den Bindungskonfigurationen! Sehen wir uns jetzt ein Beispiel an. Wir befinden uns wieder bei unserem Banking-Service. Wir möchten sichergehen, dass die Daten, die der Service versendet, auch vollständig sind. Dazu fragen wir ab, ob die Eigenschaften der einzelnen Objekte, z.B. des -Objektes, auch befüllt sind. Eine Lösung wäre, mit der Zugriffsmethode der jeweiligen Java Bean-Eigenschaft den Wert zu holen und auf abzuprüfen. Dies muss natürlich für jede Eigenschaft geschehen. Der erstellte Code könnte für ein Account-Objekt etwa so aussehen:
Um dieses Vorgehen zu generalisieren, können wir uns vom Schema-Compiler zu jeder Eigenschaft aber auch eine -Methode generieren lassen. Diese liefert , wenn das Objekt gesetzt ist, und falls es nicht gesetzt ist. Mit solchen Methoden könnten wir eine Überprüfungsroutine schreiben, die automatisch diese Methoden aufruft, um die Elemente zu überprüfen. Als Beispiel seien hier nur die Möglichkeiten der aspektorientierten Programmierung genannt, die automatisch Methoden aufrufen, die einem bestimmten Muster entsprechen. Wir teilen dem Schema-Compiler also über die Bindungsdeklaration mit, dass er solche -Methoden generieren soll. Die externe Bindungsdefinition lautet wie folgt.
Wir haben hier also wieder eine globale Bindungsdeklaration mit dem Tag angegeben. Das bedeutet, dass alle Java-Klassen unseres kleinen Datenmodells jetzt mit -Methoden bestückt werden. Die Generierung der JavaKlassen starten wir wieder wie folgt mit dem gleichen Java-Aufruf (hier nur der Vollständigkeit halber noch einmal als -Methode).
136
5.2 Die erste Bindungskonfiguration
Betrachten wir die generierte -Klasse. Hier sehen wir das Resultat der Bindungskonfiguration. Jede Java Bean-Eigenschaft besitzt jetzt zusätzlich eine -Methode, die überprüft, ob die Eigenschaft gesetzt ist.
137
5 XML-Schema zu Java
Das ist zwar nicht Java Bean-konform, aber auch nicht ganz unpraktisch.
5.2.1
Zusammenfassung
Durch Bindungsdeklarationen können Klassen angepasst, aber auch durch zusätzliche Methoden erweitert werden. Eine globale Bindungsdeklaration wirkt sich auf alle Elemente eines Schemas aus. Importierte Schemas werden ebenfalls einbezogen.
5.3
Fünf Bindungsdeklarationen für den Alltag Im folgenden Abschnitt werden wir die fünf Tibeter in der Domäne der Bindungsdeklarationen kennen lernen. Mit diesen können wir alle wesentlichen Anpassungen unseres generierten Datenmodells vornehmen. Es handelt sich hierbei um Bindungsdeklarationen verschiedener Sichtbarkeitsbereiche. Wir werden dazu eine Bindungskonfiguration Schritt für Schritt mit den Bindungsdeklarationen erweitern. Ausgangspunkt ist hier das Schema mit den drei importierten Schemas , und . Die fünf wesentlichen Bindungsdeklarationen lauten:
5.3.1
Aufzählungen anpassen mit jaxb:collectionType
In jeder Anwendung wird mit Aufzählungen wie Listen und Mengen gearbeitet. In Java handelt es sich in der Regel um Instanzen der Schnittstellen , und oder um ein Java-Array. In einem XML-Schema sind Listen weniger augenfällig, aber ebenso oft durch das folgende Konstrukt definiert, das hier am Element aus den Beispielschemas dargestellt ist:
138
5.3 Fünf Bindungsdeklarationen für den Alltag
Aus einem solchen Element generiert der Schema-Compiler standardmäßig die folgende Java-Eigenschaft vom Typ :
Hier wird also nur die Schnittstelle als Typ angegeben. Es bleibt dem SchemaCompiler überlassen, welche -Implementierung in der Anwendung verwendet wird. Die Implementierung der Zugriffsmethoden für die Eigenschaft offenbart, dass der Schema-Compiler hier eine Liste vom Typ erzeugt. Generell möchten wir vielleicht in der Anwendung einen anderen Listentyp verwenden, beispielweise den Typ , da dieser Listentyp einen synchronisierten, thread-sicheren Zugriff auf seine Elemente bietet. Dieses können wir mit Attribut der Bindungsdeklaration erreichen, der wir als Parameter eine Implementierung von angeben können.
Diese Bindungskonfiguration bezieht sich auf das -Schema, das wir im einleitenden Element mit dem Attribut angegeben haben. Da wir den Listentyp für die gesamte Anwendung konfigurieren möchten, wählen wir über das Attribut über den XPath-Ausdruck den Schemaknoten als Ziel. Damit sind alle hier angegebenen Bindungsdeklarationen für das Schema und alle importierten Schemas also , und gültig. Die Bindungsdeklaration gibt den verwendeten Listentyp an. Nach dem Ausführen des Schema-Compilers sieht die Klasse so aus.
139
5 XML-Schema zu Java
Hier wird also gleich bei der Definition der Variablen der korrekte Listentyp zugewiesen. Damit ist ein synchronisierter Zugriff auf unsere Optionselemente gewährleistet.
5.3.2
Paketnamen anpassen mit jaxb:package
In all unseren XML-Schemas haben wir bisher Namespaces mit URLs der Form http://jaxb.transparent/<Schema-Name> deklariert. Wenn nicht anders angegeben, generiert der Schema-Compiler aus diesen Namespaces die entsprechenden Paketnamen, in denen er die Java-Klassen ablegt. Für den Namespace http://jaxb.transparent/customer bedeutet dies z.B., dass die generierten Klassen im Paket abgelegt werden. Wenn wir in den bisherigen Beispielen nachschauen, befinden sich die Klassen jedoch immer in Paketen wie . Wie kommt dies zustande? Beim Aufruf des Schema-Compilers können wir mit einen Standardpaketnamen angeben:
Dieser überschreibt dann das Standardverhalten. Doch für unsere Anwendung ist dies vielleicht nicht optimal. Wir wollen die einzelnen Schemas vielleicht in getrennten Paketen ablegen, und wir möchten einen anderen Paketnamen wählen als den, der sich durch den Namespace ergibt. Angenommen wir wollen die aus den Beispielschemas generierten Klassen in der folgenden Paketstruktur organisieren:
140
5.3 Fünf Bindungsdeklarationen für den Alltag Die abgedruckte Paketstruktur soll durch ein führendes Paket und ein Paket (für Domain Object Model) komplettiert werden. Mit der Bindungsdeklaration und dessen Attribut kann ein benutzerdefinierter Paketname für ein XML-Schemadokument gesetzt werden. Da diese Bindungsdeklaration für das gesamte Schema gültig ist, wird sie innerhalb des -Elements deklariert. Für das -Schema sieht die Bindungskonfiguration so aus:
Das Element definieren wir auf gleicher Ebene wie zuvor die globale Bindungsdeklaration . Sie hängt sozusagen am Schemaknoten des -Schemas. Wir hätten diese Konfiguration auch inline im Schemadokument definieren können, die externe Konfiguration würde dabei weiterhin gelten. Wir nutzen in diesem Fall aber nur die externen Bindungskonfigurationen. Die obige Konfiguration setzt jedoch nur den neuen Paketnamen für das Schema. Wir müssen jetzt noch analog die Paketnamen für die anderen Schemas deklarieren. Da das -Schema diese Schemas importiert, sind sie dem Schema-Compiler bekannt, und wir können einfach mit dem -Attribut auf das jeweilige Schema verweisen. Das Ergebnis ist dann die folgende, etwas umfangreichere Konfiguration.
141
5 XML-Schema zu Java
Diese Bindungskonfiguration übergeben wir jetzt wieder an den Schema-Compiler. Nach Ausführen des Schema-Compilers werden wir feststellen, dass die Klassen jetzt fein säuberlich in die folgenden vier Pakete abgelegt werden.
Die Klassen selbst ändern sich nicht. Das wird sich im nächsten Abschnitt allerdings schwer ändern. Hinweis
Die Bindungsdeklaration in Konstellation mit dem Attribut überschreibt alle anderen Einstellungen für Paketnamen, die an den Schema-Compiler übergeben wurden.
5.3.3
Generierte Klassen anpassen mit jaxb:class
Wie schon die Paketnamen zuvor, können wir auch die generierten Klassen anpassen. Dafür gibt es die Bindungsdeklaration . Bisher haben wir nur Bindungsdeklarationen auf globaler und auf Schemaebene gezeigt. Dies ist die erste Bindungsdeklaration auf der Typ- bzw. Elementebene, die wir darstellen. Da JAXB aus den global definierten XML-Datentypen und Elementen jeweils eine JavaKlasse oder Value Class generiert, definieren wir die -Deklaration im Kontext eines solchen Datentyps oder Elements. Ein häufiger Anwendungsfall ist das Umbenennen der generierten Klassen. Mit der bisherigen Standardbindung erhalten wir für das Element eine entsprechende JavaKlasse mit dem Namen .
142
5.3 Fünf Bindungsdeklarationen für den Alltag
Diese Klasse wollen wir jetzt in umbenennen, da wir über den Namen vielleicht deutlich machen wollen, dass das -Element später im Banking-Service den Körper einer SOAP-Nachricht darstellt. Einen alternativen Namen können wir mit dem Attribut der Bindungsdeklaration angeben.
Versehen wir also das XML-Element mit der Bindungsdeklaration und übergeben dem Attribut den Wert :
143
5 XML-Schema zu Java
Wir haben der Bindungskonfiguration also ein zusätzliches -Element hinzugefügt. Dies ist notwendig, da wir die Deklaration nur auf das Element anwenden wollen. Der XPath-Ausdruck liefert uns genau dieses Element zurück. Darin verschachteln wir die beschriebene Deklaration und erhalten durch den Schema-Compiler die folgende Klasse.
Wie wir sehen, hat sich nur der Name der Klasse geändert, die Annotation zeigt weiterhin auf das XML-Element . Alternative Implementierungen mit jaxb:class Die -Bindungsdeklaration kann allerdings noch mehr. Sie besitzt ein zweites Attribut , mit dem wir eine alternative Implementierungsklasse für dieses Element angeben können. Das bedeutet allerdings nur, dass die -Klasse einen anderen Rückgabewert für die -Methode bekommt.
144
5.3 Fünf Bindungsdeklarationen für den Alltag Schauen wir uns ein Beispiel für die Klasse an. Eine alternative Implementierung dieser Klasse soll durch die fiktive Klasse gegeben sein.
Starten wir den Schema-Compiler mit dieser Bindungskonfiguration, wird die Klasse entsprechend abgeändert. Die Methode weist jetzt als neuen Rückgabetyp auf.
Diese Bindungsdeklaration macht vor allem dann Sinn, wenn der Schema-Compiler durch die globale Bindungsdeklaration angewiesen wurde, nur JavaSchnittstellen statt konkreter Implementierungsklassen zu generieren. In diesem Fall können wir mittels des -Attributs eigene Implementierungsklassen angeben, die z.B. mit einem anderen Tool generiert wurden.
5.3.4
Änderungen auf Komponentenebene mit jaxb:property
Nachdem wir Bindungsdeklarationen auf globaler Ebene, auf Schemaebene und auf der Typ-/Elementebene gesehen haben, wenden wir uns jetzt der Komponentenebene zu. Das bedeutet, wir werden Anpassungen an Elementen oder Typen machen, die nicht auf globa-
145
5 XML-Schema zu Java ler Ebene definiert sind, aus denen folglich auch keine eigenen Java-Klassen generiert werden. Solche Komponenten werden vom Schema-Compiler üblicherweise an eine Java BeanEigenschaft gebunden. Mit der Bindungsdeklaration können wir benutzerdefinierte Anpassungen für diese Eigenschaften vornehmen. Einige dieser Anpassungen können auch auf anderen Ebenen definiert werden und wurden bereits behandelt wie etwa oder . Wir werden uns daher besonders auf die speziell für Eigenschaften vorgesehenen Bindungsdeklarationen konzentrieren. Wie Bindungsdeklarationen auf mehreren Ebenen definiert werden, wird der vorletzte Abschnitt behandeln. Namen einer Eigenschaft anpassen Doch zunächst einmal etwas Einfaches. Wie schon bei Paketen und Klassen kann auch für einzelne Eigenschaften der generierte Name vorgegeben werden. Mit dem Attribut der Bindungsdeklaration wird der Name angegeben, den die aktuell selektierte Komponente, sprich ein , oder auch ein , im JavaCode tragen soll. Schauen wir uns dazu einmal das -Schema an. Der Datentyp besitzt zwei Unterelemente und .
Aus diesen Namen kann nun wirklich nicht abgeleitet werden, dass es sich hier jeweils um Listen von Wertpapieren bzw. Optionen handelt. Daher erweitern wir die bisherige Bindungskonfiguration um eine kleine Namensanpassung mit der Bindungsdeklaration.
146
5.3 Fünf Bindungsdeklarationen für den Alltag
Die beiden Elemente bekommen jeweils das Suffix List, um sie als Aufzählungen zu kennzeichnen. An diesem Beispiel kann man außerdem sehr gut die Schachtelung von Elementen erkennen. Durch das Verschachteln eines Elements kann ein XPath-Ausdruck jeweils auf dem vorhergehenden Ausdruck aufbauen. Die Klasse besitzt nach erneuter Generierung die folgenden Eigenschaften.
Elementinformationen mit generateElementProperty In der Standardeinstellung des Schema-Compilers werden die zu einem Schemaelement generierten Java Bean-Eigenschaften mit entsprechenden Java-Datentypen versehen. Das hat den Vorteil, dass das entstehende Java-Datenmodell natürlich ist, also keinen Hinweis darauf gibt, dass es auf einem XML-Datenmodell basiert und durch eine JAXBImplementierung generiert wurde. Es bedeutet aber auch, dass bestimmte XML-spezifische Informationen nicht abgebildet werden. JAXB hält für jedes XML-Element Informationen über den Inhalt, den Sichtbarkeitsbereich und den Typ vor. Benötigen wir diese Informationen in unserer Anwendung, können wir den Schema-Compiler mit der Bindungsdeklaration anweisen, uns statt des Java-Datentyps eine Eigenschaft vom Typ zu generieren.
147
5 XML-Schema zu Java Die Klasse bietet zusätzliche Informationen über die folgenden Methoden: : Gibt an, ob das XML-Element leer ist. : Gibt an, ob das XML-Element global deklariert ist. : Gibt true zurück, wenn der Typ des XML-Elements im Instanz-
dokument vom deklarierten Typ im Schema abweicht, z.B. bei Angabe von . : Liefert den Namen des XML-Elements zurück. Dieser muss nicht zwingend
dem deklarierten Namen aus dem Schema entsprechen. : Gibt den Sichtbarkeitsbereich des XML-Elements zurück. Gibt zurück, falls das Element global ist, andernfalls wird der Datentyp des überge-
ordneten Elements aus dem XML-Schema zurückgegeben, in dem das Element verschachtelt ist. : Gibt den Java-Typ zurück, auf den dieses XML-Element abgebil-
det wird. Eine Anforderung in unserer Beispielanwendung könnte sein, dass wir überprüfen müssen, ob die Kunden- und Kontodaten in einem -Element gesetzt sind. Dieses -Element wird vom Banking-Service als XML-Dokument geliefert. Wenn die Eigenschaften und als -Instanzen vorliegen, können wir einfach mit abfragen, ob diese Elemente vorhanden sind oder nicht. Den -Teil unserer bisherigen Bindungskonfiguration erweitern wir also folgendermaßen, um den Eigenschaften und den Typ zuzuweisen.
Die erzeugte Klasse hat jetzt die folgenden Eigenschaften:
148
5.3 Fünf Bindungsdeklarationen für den Alltag
Wenn wir mit dem soeben erzeugten Java-Datenmodell ein XML-Dokument, das wir vom Banking-Service bekommen haben, transformieren, bekommen wir die Kunden und Kontodaten als -Objekte. Das folgende Listing illustriert dies.
attachmentRef Es definiert, ob XML-Attachments als aufgelöst werden sollen ( ) oder ob in einer entsprechenden Eigenschaft nur ein Verweis auf das XML-Attachment gespeichert wird ().
Diese Einstellung ist relevant für Umgebungen, die eine optimierte Speicherung eines XML-Dokuments unterstützt; beispielsweise in Webservice-Umgebungen oder innerhalb der Kommunikationsschicht eines Enterprise Service Bus. Allein das Aufsetzen einer sol-
149
5 XML-Schema zu Java chen Umgebung würde ein halbes Buch füllen, weswegen wir in hier nur am Rande auf diesen Aspekt der JAXB eingehen. Konstanten erzeugen mit fixedAttributeAsConstantProperty In der Regel werden Attribute, die im XML-Schema mit deklariert wurden, als normale Eigenschaften der resultierenden Java Bean generiert. Einziger Unterschied zu einer gewöhnlichen Eigenschaft: Sie ist standardmäßig auf den als angegebenen Wert gesetzt. Alternativ kann über die Einstellung ein solches Attribut als Konstante der resultierenden Java Bean generiert werden. Als Beispiel fügen wir unserem -Schema ein neues Attribut hinzu, das die Währung dieses Kontos darstellt. Der Datentyp hat also folgende Struktur.
Generieren wir die Klasse nun wie bisher mit dem Schema-Compiler, wird eine ganz gewöhnliche Java Bean-Eigenschaft vom Typ erzeugt. Allerdings gibt der Getter für die Eigenschaft den mit definierten Wert für Euro zurück, falls kein Wert für gesetzt wurde. Das Attribut gibt hier also einen Standardwert an. Dies kann nützlich sein, wenn wir die Währung variabel halten, jedoch auf die Standardwährung Euro zurückgreifen wollen, wenn das Attribut im XML-Dokument nicht gesetzt wurde.
150
5.3 Fünf Bindungsdeklarationen für den Alltag
Soll die Währung jedoch fest im XML-Schema verdrahtet werden, darf der Wert für nicht geändert werden. Dies ist äquivalent zu einer Java-Konstanten, die wir normalerweise mit definieren würden. Um aus dem Attribut eine solche Konstante zu generieren, erweitern wir die Bindungskonfiguration mit der Deklaration und setzen ihren Wert auf .
Die Eigenschaft der Klasse ist jetzt als fixe Währung mit dem Wert EUR vorbelegt und kann nicht mehr verändert werden.
151
5 XML-Schema zu Java
Wir sehen hier zusätzlich noch ein leeres Kommentarfeld oberhalb der Eigenschaft . Auch diese Kommentare können wir durch eine Bindungskonfiguration befüllen, wie der nächste Abschnitt zeigen wird.
5.3.5
Dokumentieren mit jaxb:javadoc
Das Generieren von Klassen durch den Schema-Compiler ist recht praktisch und spart eine Menge Aufwand. Eines wird dabei jedoch häufig vergessen: die Dokumentation. Das ist sicher kein neues Problem in der Informatik, jedoch stellt es sich bei generierten Klassen als besonders problematisch heraus. Dokumentieren wir die Klassen von Hand, so geht die Dokumentation bei einer Neugenerierung der Klassen möglicherweise verloren. Die Dokumentation sollte deswegen an der Quelle der Klassen, dem XML-Schema geschehen. Das erreichen wir über die Bindungsdeklaration . Die Bindungsdeklaration ist sehr flexibel. Genau wie echte Javadoc-Kommentare im Quellcode lässt sie sich an den verschiedensten Elementen definieren. Es können -Bindungsdeklarationen für Pakete, Klassen, Methoden und viele andere Elemente definiert werden. Wir geben hier für die Beispielanwendung Kommentare auf Paketebene, auf Klassenebene und Komponentenebene an. Da die Bindungskonfiguration jetzt komplett ist, führen wir sie hier noch einmal in voller Länge an.
152
5.3 Fünf Bindungsdeklarationen für den Alltag
Diese Bindungskonfiguration zeigt, wie die Kommentare an den entsprechenden Stellen gesetzt werden müssen. In Javadoc sind häufig HTML-Tags zur Formatierung vorhanden. Diese können jedoch nicht ohne weiteres übernommen werden. Wer sich fragt, ob den Autoren bei der ersten Definition ein Tippfehler unterlaufen ist, kann beruhigt sein. Die HTML-Tags müssen entsprechend konvertiert werden, um nicht als XML-Elemente der Bindungskonfiguration erkannt zu werden. Daher wird die öffnende Klammer durch die Zeichenfolge ersetzt. Diese Konvertierung können wir uns auch sparen, indem der gesamte Kommentar in ein -Element verpackt wird, das dem XML-Parser mitteilt, dass die folgende Zeichenkette nicht geparst werden soll.
Das Ergebnis dieser Deklarationen ist eine zusätzliche Datei package.html, die den Kommentar für die Paketebene enthält sowie Kommentarfelder in den Klassen und .
153
5 XML-Schema zu Java Listing 5.1 Inhalt der Datei package.html
Listing 5.2 Die Klasse SoapResponseBody
Listing 5.3 Die Klasse Account
154
5.4 XML-Bezeichner anpassen
5.3.6
Zusammenfassung
Der verwendete Collection-Datentyp lässt sich durch die globale Deklaration anpassen. Auf Schemaebene können wir mit den Paketnamen der generierten Klassen anpassen. Einstellungen auf Klassenebene werden durch die Deklaration vorgenommen. Die Deklaration bietet eine Reihe von Konfigurationsmöglichkeiten auf Komponentenebene. Teilweise überlagern sich diese Einstellungen mit globalen Deklarationen. Javadoc-Kommentare können in der Bindungskonfiguration auf Schema-, Klassen- und Komponentenebene durch angegeben werden.
5.4
XML-Bezeichner anpassen Außer dem -Attribut einiger Bindungsdeklarationen wie , oder haben wir uns um die Benennung unserer XML-Elemente noch keine allzu großen Gedanken gemacht. Aus der Sicht von XML brauchen wir das eigentlich auch nicht. Die XML-Spezifikation gibt uns relativ viel Freiraum, was die Benennung von Elementen, Attributen und Datentypen angeht. Der lexikalische Raum für gültige Bezeichner wird durch den Typ festgelegt. wird von der XML-Spezifikation als non colonized name definiert. Ein solcher Bezeichner beginnt mit einem Buchstaben und enthält keinen Doppelpunkt, ansonsten sind aber viele Sonderzeichen erlaubt. Außerdem trennt XML die Namensräume für die Bezeichnung von Elementen, Attributen, Datentypen und Gruppen. So kann ein Datentyp den gleichen Bezeichner verwenden wie ein Element. Auf der Java-Seite sieht es anders aus, denn Java stellt etwas strengere Anforderungen an die Benennung von Bezeichnern. Es gibt Schlüsselwörter und Sonderzeichen, die in Java bereits mit Semantik behaftet sind und daher nicht verwendet werden können, wie z.B. die Datentyp-Bezeichner , , etc. Andere Zeichen wie der Bindestrich sind als Operatoren vergeben. Diese Einschränkungen müssen wir bei einer Bindung eines XML-Datenmodells an ein Java-Datenmodell unbedingt beachten. Andere Einschränkungen sind nicht zwingend vorgeschrieben, werden aber in der Java-Programmierung allgemein befolgt. Diese ungeschriebenen Java-Namenskonventionen wollen wir natürlich nach Möglichkeit auch befolgen, wenn wir Java-Klassen aus unserem XML-Schema erzeugen. Um diese zwei Welten zu vereinen, löst der Schema-Compiler die XML-Bezeichner nach einem Standardalgorithmus in Java-Bezeichner auf.
155
5 XML-Schema zu Java Mit Bindungsdeklarationen können wir von diesem Standardverhalten abweichen, falls unsere Anwendung dies erfordert. Im Folgenden werden wir daher einige Bindungsdeklarationen kennenlernen, die uns helfen, Namenskonflikte in unseren XML-Schemas aufzulösen und Java-konforme Bezeichner zu erzeugen. Um einige Namenskonflikte zu provozieren, werden wir unsere Beispielschemas modifizieren. Die dabei entstehenden Konflikte lösen wir mit den entsprechenden Bindungsdeklarationen auf.
5.4.1
Einzelne Namenskonflikte auflösen
Zunächst nehmen wir einmal das folgende -Schema als Ausgangsbasis.
Die Änderung am Schema ist hier fett unterlegt. Das globale Kontoelement heißt jetzt wie der komplexe Typ auch . Starten wir den Schema-Compiler jetzt mit folgender Bindungskonfiguration:
156
5.4 XML-Bezeichner anpassen
Diese Bindungskonfiguration veranlasst den Schema-Compiler, für alle globalen Elementen eine eigene Klasse zu generieren. Beim Ausführen des Schema-Compilers erhalten wir jetzt den folgenden Fehler:
Da der komplexe Typ und das globale Element sich namensmäßig überschneiden, kommt es hier auch zu einem Namenskonflikt, denn der Schema-Compiler erzeugt für beide Elemente eine Klasse mit dem Namen . Dieses Problem können wir aber recht schnell mit der Bindungsdeklaration lösen, mit der wir den Namen einer generierten Klasse individuell anpassen können. Die folgende Bindungsdeklaration löst das obige Problem.
Das globale Element wird mit dieser Konfiguration als Klasse abgebildet, während der komplexe Typ die Klasse bekommt. Auf diese Weise können wir mit recht einfachen Mitteln einen Namenskonflikt auflösen.
5.4.2
Präfixe und Suffixe mit jaxb:nameXmlTransform
Treten Namenskonflikte schemabedingt häufiger auf, kann es recht mühsam werden, wie vorher gezeigt, die Konflikte einzeln mit Bindungsdeklarationen auf Klassenebene aufzulösen. Vor allem in größeren Datenmodellen kommt es häufiger vor, dass sich Elemente, Datentypen oder Gruppen überschneiden. In solchen Fällen ist es günstiger, eine allgemeine Namensstrategie festzulegen. Eine solche können wir mit Hilfe der Bindungsdeklaration definieren. Mit dieser Deklaration können wir Elementen, Datentypen und Gruppen automatisch Präfixe und Suffixe zuordnen, ohne die Namen von Hand anpassen zu müssen. XML lässt ja, wie an anderer Stelle bereits erwähnt, gleiche Namen innerhalb dieser vier Gruppen zu, da deren Namensräume voneinander getrennt sind. Die Bindungsdeklaration wird auf Schemaebene definiert, gilt also für die Bezeichner eines einzelnen Schemas, importierte Schemas zählen nicht zum Sichtbarkeitsbereich. Eine
157
5 XML-Schema zu Java alternative Bindungskonfiguration für unseren Namenskonflikt von vorhin könnte mit der Deklaration so gelöst werden.
Mit dem ersten Unterelement wird allen global definierten Datentypen das Suffix hinzugefügt. Für Elemente hingegen wird ein Suffix vergeben. Evtl. hinzukommende Gruppen werden im Java-Modell mit einem Präfix versehen, während anonyme Datentypen, also Datentypen ohne -Attribut, sowohl ein Präfix als auch ein Suffix erhalten. Wenn wir diese Bindungskonfiguration an den Schema-Compiler übergeben, sind wir zunächst einmal erstaunt. Wir bekommen eine Fehlermeldung!
Wie kann das sein? Nun, aus dem komplexen Typ wird mit der Bindungskonfiguration die Klasse . Diese überschneidet sich jetzt aber leider mit der Aufzählung . Um das zu vermeiden, ändern wir den Namen der Aufzählung wie folgt:
Die Aufzählung haben wir also zu geändert. Das löst unter Umständen das aktuelle Problem, zeigt aber überdeutlich, dass Namenskonflikte in der XML-Java-Datenbindung immer wieder auftreten können und manuell behoben werden müssen.
158
5.4 XML-Bezeichner anpassen
5.4.3
Unterstriche verarbeiten mit jaxb:underScoreBinding
Wenn wir nach der Umbenennung der -Deklaration von zu den Schema-Compiler erneut ausführen, erhalten wir immer noch die gleiche Fehlermeldung. Der Grund ist, dass der Schema-Compiler standardmäßig einen Unterstrich als Trennzeichen zwischen zwei Wörtern interpretiert. Häufig wird der Unterstrich in XML genau zu diesem Zweck verwendet. In der Java-Welt hat es sich allerdings etabliert, den sogenannten Camel-Case zu verwenden. Dabei werden aufeinanderfolgende Wörter durch Großbuchstaben getrennt. Genauso verhält es sich mit dem Namen , dieser Name wird von der JAXBImplementierung zu zusammengefasst und als Name der entsprechenden Enumeration in Java genommen. Dadurch ergibt sich dann auch der Namenskonflikt mit dem komplexen Typ , den wir durch die letzte Bindungskonfiguration erzeugt haben. Aber auch für dieses Problem gibt es Abhilfe. Das Standardverhalten, nämlich das Ersetzen der Unterstriche durch Großbuchstaben, lässt sich beeinflussen durch die Bindungsdeklaration . Die bereits bekannte Standardeinstellung ist hier . Um die Unterstriche zu übernehmen, verwenden wir jetzt den Wert in der global definierten Bindungsdeklaration.
Jetzt gibt es keine Namenskonflikte mehr, denn der Unterstrich im Typ bleibt nun auch in Java erhalten. Damit sieht die generierte Klasse unseres Schemas wie folgt aus.
159
5 XML-Schema zu Java
Das Verhalten für Unterstriche ist nur eins der Standardverhalten, das die JAXB beim Auflösen von XML-Bezeichnern spezifiziert. Im nächsten Teil sehen wir noch weitere Standardauflösungen im Zusammenhang mit Aufzählungen bzw. Enumerationen.
5.4.4
Bezeichner in Enumerationen
Der einfache Datentyp ist als XML-Enumeration definiert und wird in die folgende Java-Enumeration umgewandelt:
Eine Enumeration stellt in Sachen Bezeichner einen Extremfall dar. XML ist hier sehr großzügig, was die erlaubten Bezeichner für die Werte in einer Enumeration betrifft. Das Attribut eines -Elements besitzt den Datentyp , kann also beliebige nicht komplexe Datentypen aufnehmen. Java stellt hier aber für Werte einer Enumeration die gleichen Anforderungen wie für andere Bezeichner. Der Schema-Compiler muss die in Java nicht erlaubten Bezeichner allerdings nach Möglichkeit korrekt auflösen. Wir wollen die bisherige Enumeration etwas erweitern, um einige Auswirkungen bei der Namensauflösung zu beobachten. Das Beispielschema sieht dann wie folgt aus.
160
5.4 XML-Bezeichner anpassen
Die Enumeration definiert hier verschiedene gültige Bezeichner für Kontotypen. Es werden in diesem Datentyp verschiedene Notationen für die Kontotypen eingeführt, die alle eines gemeinsam haben: Sie sind als Java-Bezeichner nicht zu gebrauchen. Der Schema-Compiler versucht dennoch, aus diesen XML-Bezeichnern sinnvolle JavaBezeichner zu erzeugen. Die Enumeration wird wie folgt als Java-Enumeration abgebildet.
161
5 XML-Schema zu Java
Zunächst fällt auf, dass die Enumeration jetzt den Namen trägt. Wir haben hier statt der Unterstrichanpassung aus dem letzten Abschnitt eine weitere Bindungsdeklaration genutzt, um der Enumeration explizit einen angepassten Namen zu geben.
Mit der Bindungsdeklaration können wir einer Enumeration also explizit einen Namen zuweisen. Etwas anderes ist in der generierten Klasse jedoch bemerkenswerter. Im Gegensatz zu der zuerst gezeigten Java-Enumeration können die Werte nicht einfach übernommen werden. Der Schema-Compiler ersetzt hier die ungültigen Zeichen -, # und . durch Unterstriche. Die ursprünglichen Werte aus dem XML-Schema werden durch Annotationen des Typs dargestellt. Nach dem Ersetzen der unerlaubten Zeichen sind Werte wie z.B. statt durchaus noch erkennbar. Wir fügen jetzt noch einen Wert zu der Enumeration hinzu.
162
5.4 XML-Bezeichner anpassen
Der Schema-Compiler bricht nun mit der folgenden Fehlermeldung ab:
Diesen Bezeichner kann der Schema-Compiler nämlich nicht mit seiner Standardstrategie zur Namensauflösung in einen sinnvollen Java-Bezeichner umwandeln. Der XML-Wert für dieses Enumerationselement beginnt mit einer Ziffer, was in XML durchaus gültig, in Java jedoch nicht erlaubt ist. Die Fehlermeldung gibt aber schon einen Hinweis darauf, wie dieses Problem durch eine Bindungskonfiguration gelöst werden kann. Mit der Bindungsdeklaration kann mit dem Attribut ein angepasster Name für einen einzelnen Enumerationswert vergeben werden. Diese Bindungsdeklaration können wir also genau für unser Problem verwenden. Die folgende Bindungskonfiguration ersetzt den problematischen Wert durch einen gültigen Bezeichner. Die entsprechende Bindungsdeklaration wird dabei in das schon existierende Element eingefügt.
Diese Bindungskonfiguration führt zu der folgenden Klasse .
163
5 XML-Schema zu Java
Hinweis
Die Bindungsdeklaration kann auch direkt an einem Enumerationswert definiert werden statt verschachtelt in einer Deklaration. Dieses Vorgehen ist in der Referenz ausführlicher beschrieben.
Globale Einstellungen für Enumerationen Neben diesen Anpassungen auf der Ebene einzelner Enumerationswerte können wir auch auf globaler Ebene das Verhalten des Schema-Compilers bei ungültigen Bezeichnern konfigurieren. Generell wird bei ungültigen Bezeichnern in Enumerationen eine Fehlermeldung durch den Schema-Compiler ausgegeben, wie wir bereits oben gesehen haben. Diese Einstellung entspricht der folgenden globalen Bindungsdeklaration:
164
5.4 XML-Bezeichner anpassen Es kann aber sein, dass wir keine Fehlermeldung generieren wollen, z.B. weil Enumerationswerte für das XML-Schema dynamisch erzeugt werden und ein Abbruch des SchemaCompilers nicht erwünscht ist. Die obige Bindungsdeklaration kann hierfür auf den Wert gesetzt werden.
In diesem Fall werden für die Enumeration Bezeichner nach dem folgenden Schema generiert:
Hier werden die Bezeichner also einfach durchnummeriert und mit dem Präfix versehen. Durch eine solche globale Bindungsdeklaration können automatisch kompilierbare Klassen generiert werden, auch wenn das XML-Schema ungültige EnumerationsBezeichner enthält.
165
5 XML-Schema zu Java
5.4.5
Verwenden von Java-Schlüsselwörtern als Bezeichner
Ein weiteres Problem, das bei der Transformation von XML-Bezeichnern in JavaBezeichner auftreten kann, sind die Java-Schlüsselwörter. Wie jede Programmiersprache reserviert auch Java bestimmte Wörter als Schlüsselwörter, z.B. das Wort als Bezeichner für Klassen, als Basisdatentyp, primitive Datentypen wie , und viele mehr. Diese Schlüsselwörter können in einem XML-Dokument gültige Bezeichner darstellen. In den generierten Java-Klassen können wir diese Schlüsselwörter nicht als Bezeichner verwenden. Wir wollen uns daher an einigen Beispielen ansehen, wie JAXB mit diesem Problem umgeht. Dazu nehmen wir wieder eine kleine Erweiterung unseres Schemas vor.
166
5.4 XML-Bezeichner anpassen
Oben haben wir dem Schema einen weiteren Datentyp erweitert, der vom bestehenden Datentyp ableitet. Wir haben zu diesem Typ wahllos einige Attribute hinzugefügt, die einen Bezeichner tragen, der einem Schlüsselwort in Java entspricht. Alle drei Bezeichner kommen als Variablennamen in Java nicht in Frage. Aus diesem Datentyp erzeugt der Schema-Compiler eine Klasse, die aufgrund unserer vorher definierten Benennungsstrategie den Namen trägt. Wir starten den Schema-Compiler hier wieder mit der folgenden Bindungskonfiguration.
Die generierte Klasse sieht nun folgendermaßen aus:
Wir sehen hier zwei Strategien, die der Schema-Compiler zur Umbenennung der Schlüsselwörter verfolgt.
167
5 XML-Schema zu Java Die Bezeichner und werden jeweils mit Unterstrichen als Präfix versehen, während der Bezeichner in umbenannt wird. Wir sehen also, dass der Schema-Compiler nach bestem Wissen und Gewissen versucht, kompilierbaren Code zu erzeugen, falls keine benutzerdefinierten Anpassungen durch Bindungsdeklarationen angegeben werden.
5.4.6
Java-Namenskonventionen
Eingangs haben wir bereits die ungeschriebenen Konventionen für die Namensgebung von Bezeichnern in Java erwähnt. Standardmäßig versucht der Schema-Compiler, Bezeichner gemäß dieser Konventionen zu generieren. Die dafür zuständige Bindungsdeklaration ist daher standardmäßig auf den Wert gesetzt. Wenn wir jedoch strikt die Bezeichner aus dem XML-Schema in Java umsetzen wollen, können wir dieses Verhalten auch außer Kraft setzen. Da dies aber eher selten ist, verweisen wir hier für nähere Informationen auf das Referenzkapitel.
5.4.7
Zusammenfassung
Java besitzt im Vergleich zu XML wesentlich strengere Konventionen zur Namensgebung. Dadurch ergeben sich häufig Namenskonflikte bei der Erzeugung von JavaKlassen aus einem XML-Schema. Der Schema-Compiler besitzt Standardstrategien zur Auflösung von Namenskonflikten. Mit lassen sich bequem schemaweite Namenspräfixe und suffixe für die XML-Bezeichner vergeben. Enumerationen sind häufig Quelle von Namenskonflikten, da sie in XML freier definiert werden können als in Java. Mit und lassen sich Bezeichner in Enumerationen anpassen. Setzen wir die globale Einstellung auf , werden die Java Naming Conventions vom Schema-Compiler nicht beachtet, aber das ist eine der eher verdächtigen Optionen ...
5.5
Datentypen anpassen Wenn es um Datentypen geht, treffen mit Java und XML-Schema zwei recht unterschiedliche Welten aufeinander. XML-Schema bietet einige vordefinierte Datentypen, die so nicht in Java auftauchen. Viel wichtiger noch, mit den simpleType-Deklarationen und de-
168
5.5 Datentypen anpassen ren Einschränkungen durch Facets lassen sich sehr komplexe Datentypen aufbauen, die keine Entsprechung in Java finden. Die Qual der Wahl bei der Abbildung auf Java-Datentypen nimmt uns der SchemaCompiler normalerweise mit Standardeinstellungen ab. In diesem Abschnitt sehen wir, wie eine Bindungskonfiguration helfen kann, wenn die vom Schema-Compiler gewählten Standarddatentypen unseren Anforderungen nicht gerecht werden.
5.5.1
Datentypen explizit angeben mit jaxb:baseType
Eine einfache Möglichkeit der Anpassung von Datentypen bietet die Bindungsdeklaration . Diese Bindungsdeklaration kann auf zwei Arten innerhalb des bereits bekannten -Elements definiert werden. Eine Definition auf anderer Ebene ist nicht möglich. Als Beispiel werden wir hier das Kundenelement unseres Schemas verwenden.
Typen angeben mit dem name-Attribut
Mit dieser Variante können wir den standardmäßig für dieses Element gewählten Typ generalisieren oder spezialisieren. Wichtig ist, dass die angegebene Klasse in der Vererbungshierarchie des Standardtypen liegt.
Generalisieren Ein Beispiel für eine Generalisierung ist die folgende Bindungskonfiguration.
Das obige Beispiel definiert den Basistypen für das Kundenelement . Da die Basisklasse aller Objekte darstellt, ist es damit theoretisch möglich, der Eigenschaft unserer -Klasse ein beliebiges Objekt zuzuweisen. Der Schema-Compiler erzeugt nämlich mit dieser Bindungskonfiguration folgende Klasse:
169
5 XML-Schema zu Java
Wie wir sehen, wurde die Eigenschaft als Eigenschaft vom Typ angelegt. Falls die konkrete Implementierungsklasse hier noch offen bleiben soll, kann diese Bindungsdeklaration genutzt werden. Eine Sache fällt aber noch auf: Die Annotation gibt als Typ für die Eigenschaft immer noch die Klasse an. Dies dient dazu, dass beim Unmarshalling eines XML-Dokuments, das kein -Attribut für das Element ausweist, trotzdem noch in die Standardklasse transformiert werden kann. Auch beim Marshalling erwartet die JAXB-Implementierung hier übrigens immer noch eine Customer-Instanz, alles andere provoziert Ausnahmen. Spezialisieren Auf der anderen Seite können wir natürlich auch den genau umgekehrten Effekt erreichen, indem wir unter eine Klasse angeben, die von unserer -Klasse ableitet, also eine Spezialisierung darstellt. Dies könnte der Fall sein, wenn benutzerdefinierte Klassen existieren, die weitere Eigenschaften besitzen, die aber nicht an eine XMLDarstellung gebunden werden. Oder aber wir möchten ein Schema allgemein definieren, es jedoch durch Bindungskonfigurationen spezialisieren können. Erweitern wir hierzu unser Kundenschema um einen Geschäftskunden, der alle Eigenschaften des normalen Kundentyps erbt, zusätzlich aber noch eine Geschäftsnummer besitzt, unter der die Firma registriert ist. Das so erweiterte Schema sieht dann so aus.
170
5.5 Datentypen anpassen
Im -Schema ist der Kunde weiterhin mit dem allgemeineren Datentyp angegeben.
Mit der Bindungsdeklaration können wir jetzt, ohne das Schema verändern zu müssen, den Kunden zu einem Geschäftskunden machen. Dazu weisen wir ihm den Typ zu.
171
5 XML-Schema zu Java Diese Bindungskonfiguration führt nun dazu, dass die Java-Klasse auf den spezialisierten Typ verweist. Unser -XML-Schema ist dabei aber gleich geblieben.
Die manuelle Konfiguration der in Java verwendeten Datentypen ist eine recht sensible Angelegenheit. Leicht entstehen durch solche Konfigurationen Ausnahmen vom Typ beim Marshalling oder Unmarshalling. Hinweis
Ein weiterer, sehr beliebter Anwendungsfall für die Spezialisierung sind Elemente vom Typ , die Referenzen auf andere Elemente darstellen. Diese Referenzen werden normalerweise als Eigenschaften vom Typ generiert. Um hier die Typbindung zu verstärken, kann als Spezialisierung die Klasse des referenzierten Elements angegeben werden. Das praktische Vorgehen für diesen Fall ist in der Referenz beschrieben.
Typen konvertieren mit dem jaxb:javaType-Element
Die zweite Möglichkeit der Definition von Datentypen ist die Angabe einer -Bindungsdeklaration innerhalb der -Deklaration. Mit dieser Deklaration können für Java-Eigenschaften Datentypen jenseits der Standardabbildung von XML nach Java konfiguriert werden. Wie dies genau funktioniert, sehen wir im nächsten Abschnitt.
172
5.5 Datentypen anpassen
5.5.2
Datentypen konvertieren mit jaxb:javaType
Bisher wird in unserem -Schema der Typ auf einen abgebildet, während die Typen und als Kommazahlen auf die Klasse abgebildet werden.
Datentypen global konvertieren In den Systemen, die wir mit dem Online-Banking-Service bedienen, wird vielleicht mit anderen Datentypen gearbeitet. So wird die Kontonummer dort z.B. durch den Ganzzahltyp repräsentiert, weil es gar nicht so viele Konten gibt. Bei der einen oder anderen Schweizer Privatbank mögen eventuell sogar -basierte Kontonummern ausreichen. Da wir nicht ständig zwischen den Typen und hin- und herkonvertieren wollen, möchten wir gerne ein für allemal festlegen, dass hier verwendet werden soll. Solcherlei Konvertierarbeit können wir mit der Bindungsdeklaration automatisieren. Mit ihr können ganz flexibel Konvertierungen von einem Datentyp im XMLSchema zu dem gewünschten Java-Datentyp in den Java-Klassen definiert werden. Das kann auf den unterschiedlichsten Sichtbarkeitsebenen und mit unterschiedlicher Granularität geschehen. Am besten ist, wir fangen ganz einfach an und definieren eine erste -Deklaration für alle Elemente, die den XML-Typ tragen.
Hier haben wir also in den unseres Hauptschemas manuell eine Typkonvertierung festgelegt. Wird global deklariert, muss neben dem Namen des Java-Datentyps im Attribut auch ein XML-Datentyp mit dem Attribut angegeben werden. Definieren
173
5 XML-Schema zu Java wir im Kontext eines bestimmten Elements, so ergibt sich der Wert von aus dem Datentyp des Elements. Der XML-Datentyp muss ein atomarer XML-Typ sein oder von einem atomaren Typ ableiten, damit die Konvertierung durch die JAXB-Implementierung selbst durchgeführt werden kann. In unserem Beispiel bewirkt die Deklaration, dass alle XML-Elemente in unseren Schemas, die den Typ besitzen oder von ihm ableiten, jetzt durch den Typ dargestellt werden. Die Klasse , aber auch z.B. die Klasse werden daher durch die angegebene Bindungskonfiguration wie folgt verändert:
JAXB verwendet hier die Wrapper-Klasse anstatt den primitiven Datentyp , um das Attribut bzw. das Element abzubilden. Eine Sache fällt uns hier bestimmt ins Auge: Eine zusätzliche Annotation wurde zur Kontonummer hinzugefügt. Der Name lässt bereits erraten, dass es sich um eine vom Schema-Compiler generierte Adapterklasse handelt, die hier für und hinterlegt wurde. JAXB benötigt für die Konvertierung von zu keine besonderen Informationen, diese Umwandlung kann getrost auch Programmierern ohne Doktortitel zugetraut werden. Daher wird die Adapterklasse gleich mitgeneriert. Werfen wir einen Blick auf diese Klasse.
174
5.5 Datentypen anpassen
leitet von der Klasse ab, die eine abstrakte Basisklasse zur Abbil-
dung von XML-Datentypen auf Java-Datentypen darstellt. Ihre zwei abstrakten Methoden und werden von mit der Konvertierung des Wertes aus dem XML-Dokument zu einem -Datentyp und zurück implementiert. Diese Methoden werden vom oder aufgerufen, um die Typkonvertierungen durchzuführen. Mit dieser Deklaration haben wir also auf globaler Ebene eine Typkonvertierung durchgeführt. Wollen wir im gesamten Datenmodell den Datentyp für Ganzzahlen verwenden, können wir damit recht gut leben, da JAXB die gesamte Konvertierungsarbeit für uns macht. Anders sieht es aus, wenn wir z.B. nur die Kontonummer anpassen wollen. Eine Bindungsdeklaration auf globaler Ebene, also innerhalb von , ist in diesem Fall zu viel des Guten. Zum Glück kann die Bindungsdeklaration auch noch auf anderen Sichtbarkeitsebenen deklariert werden und zwar bei: -Definitionen -Bindungsdeklarationen -Bindungsdeklarationen
Datentypen für einzelne Elemente konvertieren Da die Kontonummer als definiert ist, können wir die -Bindungsdeklaration auch individuell für den Typ angeben. Die folgende Bindungsdeklaration veranschaulicht dieses.
Der Unterschied zur globalen Deklaration ist hier, dass wir das Attribut nicht mit angeben müssen. Der Typ des XML-Elements ergibt sich bereits aus dem Element, das durch den XPath-Ausdruck zurückgeliefert wird.
175
5 XML-Schema zu Java Die Klasse sieht nach Neugenerierung der Klassen genauso aus wie im letzten Beispiel. Jedoch sind die anderen Klassen nicht mehr von der Bindungskonfiguration betroffen, das Element in der Klasse wird wieder gemäß Standardabbildung mit dem Datentyp versehen.
Komplexere Typkonversionen Die Konvertierung, die wir bisher vorgenommen haben, ist relativ simpel. Ein XMLElement vom Typ wird in den Java-Datentyp überführt. Beide stellen einfache Ganzzahltypen dar und sind daher kompatibel zueinander. Die Abbildung wird durch eine automatisch durch die JAXB-Implementierung generierte Adapterklasse geleistet. Aber wie so oft im Programmierleben passen nicht alle Datentypen so gut zueinander, sind nicht alle Abbildungen trivial. Sehen wir uns ein weiteres Beispiel an. Das Element aus dem Kundenschema, also das Geburtsdatum des Kunden, wird durch den XML-Typ dargestellt. Daraus generiert der Schema-Compiler folgende Java Bean-Eigenschaft.
In unserer Anwendung wollen wir aber keine Abhängigkeit auf den etwas exotischeren Datumstyp . Wir halten uns lieber an die abstrakte Klasse . Ganz naiv legen wir also die folgende Bindungskonfiguration an.
176
5.5 Datentypen anpassen
Hier haben wir einfach den Typ in der Bindungsdeklaration angegeben. Wenn wir nun blind auf den Schema-Compiler vertrauen, wird dieser in der Adapterklasse den folgenden Code für uns generieren.
Die obige Klasse ist aber so nicht kompilierbar! Da eine abstrakte Klasse ist, können wir sie gar nicht instanziieren, erst recht nicht mit einem als Argument. Der Schema-Compiler geht nämlich davon aus, dass der durch angegebene Java-Typ mit der aus dem XML-Dokument stammenden Zeichenkette instanziiert werden kann, also einen Konstruktor vom Typ besitzt. Für abstrakte Klassen wie funktioniert dies so nicht. Wir müssen uns also etwas anderes überlegen. Die Zeichenkette, die wir aus dem XML-Dokument erhalten, müssen wir irgendwie in ein Datumsformat parsen. Hier kommt uns aber die JAXB-Spezifikation zu Hilfe. Typen konvertieren mit DataTypeConverter Jede
JAXB-Implementierung bietet uns eine Klasse , die eine Reihe von statischen Methoden zum Konvertieren von Zeichenketten in Zieldatentypen und zurück bietet. Einen solchen Parser selbst zu schreiben wäre recht aufwendig. Das liegt daran, dass in XML ein Wert mehrere lexikalische Repräsentationen besitzen kann. Die Zeichenkette, die wir aus dem XML-Dokument erhalten, kann daher verschiedenste gültige Formate besit-
177
5 XML-Schema zu Java zen. So kann ein Wahrheitswert in XML als oder , aber auch als oder 0 dargestellt werden. In Java jedoch sind nur die Werte oder zulässig. Datumsrepräsentationen sind natürlich noch vielfältiger, denken wir nur an die vielen nationalen Unterschiede. Statt uns selbst Parserfunktionen zu schreiben, verwenden wir daher die folgenden zwei Methoden der Klasse
Diese Methoden werden nun in die Bindungsdeklaration eingebunden. Dafür gibt es zwei weitere Attribute, nämlich und . Die so korrigierte Bindungsdeklaration lautet jetzt:
Mit dieser Bindungsdeklaration konfiguriert, generiert der Schema-Compiler jetzt die Java-Klasse sowie eine Adapterklasse , die den für die Konvertierung verwendet.
178
5.5 Datentypen anpassen
Hier die automatisch durch den Schema-Generator erstellte Adapterimplementierung.
Eigene parse/print-Methoden schreiben Der Datentyp für das Geburtsdatum unserer Kunden funktioniert in unserer Anwendung eine Weile recht gut. Eines Tages bekommen wir aber eine neue Anforderung von unserem Kunden. Vielleicht soll der Kundengeburtstag jetzt als Zahlenwert mit dem Java-Typ in der Klasse abgebildet werden (wegen der leichten Abbildung in einen Zeitstempel). Die Methode der Klasse können wir aber leider nicht benutzen, da diese kein Datum verarbeiten kann. Wir müssen also eigene Konvertierungsmethoden erstellen. In diesen Methoden können wir aber weiterhin die vorher schon benutzten Methoden und verwenden, die uns immerhin schon ein Objekt vom Typ liefern und die Verarbeitung der verschiedenen XML-Repräsentationen übernehmen. Wir erstellen also eine neue Klasse mit zwei Methoden und . Diese Methoden müssen wir statisch deklarieren, damit die später generierte Adapterklasse statisch auf sie zugreifen kann.
179
5 XML-Schema zu Java
Die obere Methode bekommt also während des Unmarshallings eine Zeichenkette mit einem Datum aus dem XML-Dokument. Die Klasse löst zunächst diese Stringrepräsentation in ein Objekt vom Typ auf. Danach können wir über ein -Objekt die Repräsentation als Zahlenwert zurückgeben. Die zweite Methode geht genau den umgekehrten Weg und wird entsprechend beim Marshalling aufgerufen. Mit den Methoden dieser Klasse können wir jetzt wie vorhin eine Bindungskonfiguration erstellen, die eine Konvertierung des XML-Datums in einen Zahlenwert durchführt.
Als Java-Typ wird hier also definiert, und für die -Methoden werden die vollqualifizierten Namen der eben erstellten Methoden angegeben. Aus dieser Bindungskonfiguration erzeugt der Schema-Compiler jetzt die gewünschte Klasse und eine entsprechende Adapterklasse , die unsere Konvertierungsmethoden aufruft.
180
5.5 Datentypen anpassen
Und hier die Klasse mit dem Aufruf der Konvertierungsmethoden:
5.5.3
Zusammenfassung
Mit können Datentypen generalisiert oder spezialisiert werden. Durch kann ein XML-Datentyp explizit auf den angegebenen JavaDatentyp abgebildet werden. Die Standardeinstellung des Schema-Compilers wird umgangen. lässt sich global für alle Elemente oder einzeln auf Komponentenebe-
ne deklarieren. Die Hilfsklasse übernimmt die Konvertierung aus bzw. in den XML-Namensraum für eine Vielzahl von Datentypen.
181
5 XML-Schema zu Java Für komplexe Konvertierungen können auch eigene Konvertierungsroutinen implementiert und über die Attribute und des Elements angegeben werden.
5.6
Deklarationen überlagern Wir haben bereits gesehen, dass die Bindungsdeklarationen sich auf verschiedenen hierarchisch angeordneten Ebenen ansiedeln. Einige der Bindungsdeklarationen lassen sich in JAXB auf verschiedenen Ebenen definieren, andere sind nur auf einer ganz bestimmten Ebene möglich. Was passiert nun, wenn wir die gleiche Bindungsdeklaration auf mehreren Ebenen in einer Bindungskonfiguration angeben? JAXB geht hier nach dem Prinzip der Überlagerung vor, ähnlich wie das Überschreiben von Methoden in der Java-Klassenhierarchie. Bindungsdeklarationen z.B. aus der globalen Ebene werden in den unteren Ebenen übernommen, es sei denn, es wurde auf einer tieferen Ebene eine spezifischere Deklaration angegeben. Handelt es sich also um die gleiche Bindungsdeklaration, so gilt immer diejenige aus der untersten Ebene. Ein Beispiel soll uns das verdeutlichen. Sehen wir uns wieder die erste Bindungskonfiguration an.
Diese Deklaration definiert also auf globaler Ebene, dass alle Eigenschaften der generierten Java-Klassen eine -Methode bekommen. Da das -Schema die Schemas , und importiert, wirkt sich diese Bindungsdeklaration auch auf diese Schemas aus. Daher besitzen zunächst alle Elemente der generierten Java-Klassen eine -Methode. Für die Portfolioklasse benötigen wir aber eigentlich keine -Methoden, da es häufig vorkommen kann, dass Kunden gar kein Portfolio besitzen. Die folgende Bindungskonfiguration überlagert daher die globale Einstellung für das -Schema.
182
5.6 Deklarationen überlagern
Die obere Deklaration zeigt, dass die - und -Attribute auch in einem gemeinsamen Element definiert werden können. Die untere Deklaration gibt für das -Schema eine spezifischere Einstellung, so dass die Portfolioklasse nun keine -Methoden mehr besitzt. Die Konfiguration kann jedoch auch auf der Komponentenebene definiert werden. In der folgenden Bindungskonfiguration fügen wir noch eine weitere Überlagerung hinzu. Die -Klasse soll nur für das Element eine -Methode bekommen.
Auf Schemaebene werden allgemein für also keine Methoden generiert, außer für das Element . Nach Ausführen des Schema-Compilers ergibt sich die folgende Java-Klasse .
183
5 XML-Schema zu Java
Die Bindungsdeklaration ist nicht die einzige Deklaration, die auf diese Weise auf verschiedenen Ebenen überlagert werden kann. Die folgende Übersicht zeigt Bindungsdeklarationen, deren globale Deklaration wir durch Deklaration auf spezielleren Ebenen überlagern können
Daneben gibt es weitere Deklarationen, die auf verschiedenen Ebenen deklariert werden können, die sich jedoch nicht überlagern, da sie jeweils nur für das aktuelle Element gelten. Das betrifft die folgenden Elemente.
184
5.7 Noch mehr Bindungsdeklarationen
5.7
Noch mehr Bindungsdeklarationen Die JAXB-Spezifikation erhebt keinen Anspruch auf Vollständigkeit bei der Definition der Bindungsdeklarationen. Es ist klar, dass im Alltag ständig neue Anforderungen an eine JAXB-Datenbindung auftreten können. Daher ist bereits in der JAXB-Spezifikation ein Mechanismus zur Erweiterung der vorhandenen Bindungsdeklarationen vorhanden. Bisher haben wir nur Bindungsdeklarationen aus dem Namespace http://java.sun.com/xml/ns/jaxb behandelt. Dieser Namespace hat das Präfix erhalten. Das einleitende Element einer Bindungskonfiguration hatte demnach das folgende Aussehen.
Zusätzliche Bindungsdeklarationen können wir in der JAXB ebenfalls über das Namespace-Konzept einbinden. Die JAXB-Referenzimplementierung definiert den Namespace http://java.sun.com/xml/ns/jaxb/xjc für ihre eigenen Bindungsdeklarationen. Um diese Bindungsdeklarationen nutzen zu können, deklarieren wir diesen Namespace in unserer externen Bindungskonfiguration.
Dem
Schema-Compiler teilen wir hier über das Attribut mit dem Wert mit, dass der Namespace mit diesem Präfix erweiterte Bindungsdeklarationen enthält. Das Attribut erwartet eine durch Leerzeichen getrennte Liste solcher Präfixe. Daher ist es möglich, hier beliebig viele weitere Namespaces mit Bindungsdeklarationen zu definieren. Wir beschränken uns hier allerdings auf die Erweiterungen, welche die JAXB RI uns bietet. Die JAXB-Referenzimplementierung definiert in der Version 2.0 vier zusätzliche Bindungsdeklarationen.
185
5 XML-Schema zu Java
5.7.1
Ableiten von einer Oberklasse mit xjc:superClass
Die Bindungsdeklaration veranlasst den Schema-Compiler, alle Klassen von einer angegebenen Oberklasse abzuleiten. Dies ist eine für den praktischen Gebrauch recht nützliche Deklaration. Stellen wir uns daher vor, dass unser kleines Objektmodell für den Online-Banking-Service in Wirklichkeit Teil eines umfangreichen Domänenobjektmodells ist. Dieses Domänenmodell stellt also eine große Anzahl von Java Beans dar, die eben Kunden-, Konto-, Transaktionsdaten etc. repräsentieren. Häufig wird ein solches Objektmodell auf eine Datenbank abgebildet. In vielen dieser Objektmodelle gibt es daher eine zentrale Basisklasse, die eine ID-Eigenschaft definiert, mit der die Objekte eindeutig in der Datenbank identifiziert werden. Eine solche Basisklasse könnte etwa so aussehen.
Wurde ein Objekt noch nicht in der Datenbank gespeichert, besitzt die Eigenschaft den Wert 0. Beim Speichern wird dem Objekt allerdings ein ID-Wert zugewiesen. Um den Status eines Objekts abzufragen, bietet noch eine Methode , die den aktuellen Speicherzustand zurückliefert. Diese Klasse könnte für all unsere Domänenobjekte wie , , etc. nützlich sein. Der Schema-Compiler generiert standardmäßig aber nur POJOs, die keinerlei Vererbung besitzen. Durch die Bindungsdeklaration können wir den Schema-Compiler dazu bringen, jeden unserer Datentypen von der Klasse abzuleiten. In der Bindungskonfiguration sieht das wie folgt aus.
186
5.7 Noch mehr Bindungsdeklarationen
Werfen wir einen Blick auf die durch den Schema-Compiler generierte Klasse , erkennen wir, dass diese Klasse jetzt von ableitet. Der besitzt damit eine Eigenschaft und kann über den Aufruf von als gespeichert oder ungespeichert erkannt werden.
5.7.2
Ein Wurzelinterface angeben mit xjc:superInterface
Die Bindungsdeklaration funktioniert ganz ähnlich wie die vorher gezeigte -Deklaration. Sie kann nur für den Fall verwendet werden, dass der Schema-Compiler reine Java-Schnittstellen generiert anstatt Klassen. Das erreichen wir über die Angabe des Attributs der Deklaration . Setzen wir dieses auf , so generiert der Schema-Compiler jetzt die Schnittstelle für unseren Kunden und zusätzlich eine Implementie-
187
5 XML-Schema zu Java rungsklasse , die der sonst standardmäßig generierten -Klasse entspricht. Der Unterschied ist hier, dass mit eine Schnittstelle angegeben wird, die durch alle per JAXB generierten Schnittstellen implementieren müssen. Beispielsweise können wir die folgende Bindungskonfiguration angeben.
Diese Bindungskonfiguration führt dazu, dass alle generierten Interfaces und Klassen serialisierbar sind. Das generierte Interface sieht wie folgt aus.
Die Implementierungsklasse, die bei deaktivierter Option in das untergeordnete Paket generiert wird, sieht dann wie folgt aus:
188
5.7 Noch mehr Bindungsdeklarationen
5.7.3 Die
Erweiterung von jaxb:javaType mit xjc:javaType Deklaration
ist
eine
Erweiterung
der
JAXB-Deklaration
. Es können benutzerdefinierte Typkonversionen angegeben werden. Da-
mit kann ein anderer Datentyp angegeben werden, als der vom Schema-Compiler standardmäßig gewählte Java-Typ. Der Unterschied zu ist jetzt, dass wir statt der /-Konvertierungsmethoden eine Klasse angeben, die von der abstrakten Klasse ableitet. In der Standardsemantik von generiert JAXB eine solche Klasse, die ihrerseits die Konvertierungsmethoden aufruft. Mit dieser Deklaration können wir uns eine Klasse sparen und die Konvertierungsroutinen gleich in der Adapterklasse implementieren. Deklariert wird dies folgendermaßen am Beispiel des XML-Datentyps aus dem -Schema.
Vor der Definition dieser Bindungskonfiguration sah die generierte Klasse folgendermaßen aus.
189
5 XML-Schema zu Java
Nach einer Neugenerierung der Java-Klassen mit der -Deklaration sieht die Klasse etwas anders aus. Die -Eigenschaft in der Klasse besitzt jetzt den geänderten Typ . Zusätzlich gibt die Annotation die folgende Klasse an, welche die Konvertierung für uns vornimmt.
In müssen wir nun die zwei abstrakten Methoden und überschreiben. Diese Methoden übernehmen dann die Konvertierung der Zeichenkette aus dem XML-Dokument in die neue Klasse und umgekehrt.
190
5.7 Noch mehr Bindungsdeklarationen
Die Klasse kann neben dem Wert aus dem XML-Dokument noch weitere Informationen besitzen. Im Prinzip kann hier eine beliebige Klasse angegeben werden. In unserem Fall ist es einfach eine weitere Java Bean-Klasse.
5.7.4
Das Experiment: xjc:simple
In der Referenzimplementierung der JAXB gibt es den sog. Simpler and Better Binding Mode. Wird dieses Verhalten aktiviert, generiert der Schema-Compiler in einigen Fällen einfachere bzw. intuitivere Klassen und Konstrukte, weicht jedoch von dem in der JAXBVersion 2.0 spezifizierten Standardverhalten ab. Der Modus ist eine Art Spielwiese, auf der die Weiterentwicklung der JAXB-Spezifikation testweise betrieben wird.
191
5 XML-Schema zu Java Da dieser Binding Mode sich in einer experimentellen Phase befindet und sich daher jederzeit grundsätzlich ändern kann, werden wir hier nur ein kurzes Beispiel zeigen. Der aktuelle Stand der Funktionalität kann jederzeit in der Dokumentation der JAXBReferenzimplementierung nachgeschlagen werden. Aktiviert wird der Simpler and better Binding Mode mit der folgenden Deklaration auf globaler Ebene.
5.7.5
Zusammenfassung
Die Standardbindungsdeklarationen der JAXB-Spezifikation sind erweiterbar. Die JAXB-Referenzimplementierung bietet einige zusätzliche Bindungsdeklarationen an, die implementierungsspezifisch sind. Mit kann global eine Oberklasse angegeben werden, von der die generierten Klassen ableiten. definiert das Gleiche auf der Schnittstellenebene. Die Deklaration erweitert die Deklaration . Es lässt sich zusätzlich explizit eine Adapterklasse angeben.
192
6 Java zu XML-Schema Eine der großen Neuerungen der Version 5 von Java sind die Annotationen. Informationen zu Java-Elementen, die bisher nur in Kommentaren oder getrennten Dateien gespeichert wurden, können nun direkt im Java-Quelltext aufgenommen werden und sind mit den Mitteln der Reflection-API programmatisch verfügbar. Nicht alle Entwickler haben diese Neuerung mit Begeisterung begrüßt es ist abzusehen, dass tausendundeine API sich der Annotationen mehr oder weniger geschickt annehmen werden und statt der ach so einfach verständlichen POJOs wird es vielleicht bald von MAJOs wimmeln, Massively Annotated Java Objects. Die Version 2.0 der Java Architecture for XML Binding bedient sich ausgiebig der Annotationen und in diesem Fall, wie wir finden, auf verführerisch praktische Art und Weise. In den vorherigen Kapiteln war stets ein XML-Schema Gegenstand des Beispiels und wie man ein Java-Datenmodell daraus zaubern kann. In diesem Kapitel beschäftigen wir uns mit dem Rückweg, den Annotationen sei Dank, und zwar wie wir von einem JavaDatenmodell passendes XML erzeugen können, indem wir für die einzelnen JavaElemente durch die entsprechenden JAXB-Annotationen definieren, wie das Element in XML abgebildet werden soll. Dieser Weg zurück ist das Besondere der JAXB 2.0. Während in früheren Versionen das aus einem XML-Schema erzeugte Java-Datenmodell nicht mit sinnvollem Aufwand für ein anderes XML-Schema angepasst werden konnte, bemüht sich die Version 2.0 um eine bidirektionale Abbildung von XML-Schema auf Java. Diese Abbildung mag vielleicht noch nicht in jedem Fall astrein sein funktioniert in der Regel aber erstaunlich gut, wie wir in den kommenden Abschnitten darstellen werden. Nun unterscheiden sich Java und XML-Datenstrukturen erheblich voneinander. Wo in Java eine Java Bean-Eigenschaft definiert ist, kann im XML ein Attribut, ein Element oder ein verschachtelter Datentyp stehen. Während in Java die Reihenfolge der definierten Java Bean-Eigenschaften irrelevant ist, muss für XML die Reihenfolge von Unterelementen explizit festgelegt (oder freigelassen) werden. Daher ist es für viele Fälle notwendig, die gewünschte Abbildung zu konfigurieren.
193
6 Java zu XML-Schema Im vorigen Teil des Buches haben wir uns mit den Bindungskonfigurationen befasst, die unmittelbar an einem XML-Schema hängen und dort diese Konfigurationen vorhalten. Das folgende Kapitel wird sich nun mit den Konfigurationen beschäftigen, die wir per Annotationen an Klassen, Variablen und Methoden anfügen können, um so eine bestimmte Form der Abbildung von Java in ein XML-Format zu erzielen. Die Annotationen der JAXB stellen im Moment einen Großteil der in der Java 5 Core API definierten Annotationen dar. Die Annotationen sind im Paket zusammengefasst. Aufbau des Kapitels Nutzung der API ohne ein vorgegebenes Schema Einfache Elementkonfigurationen Elementlisten, Referenzen und Elementmischung Enumerationen Eigene Typbindungen definieren Mit unbekannten XML-Elementen und Attributen arbeiten Objektgraphen in XML abbilden Elemente über Factory-Klassen definieren
6.1
JAXBContext und JAXBElement ganz ohne Schema Die Klassen und aus der JAXB-Laufzeit können auch ohne ein Schema gut verwendet werden. Im Tutorial zu den Klassen der Laufzeit-API haben wir die verschiedenen Varianten der Factory-Methode kennengelernt, mit denen neue -Instanzen erzeugt werden können:
Diese Factory-Methode nimmt alternativ einen Paketnamen oder eine Reihe von Instanzen auf. Wenn wir hier einen Paketnamen angeben, erwartet eine Paketstruktur, wie sie üblicherweise durch den Schema-Compiler erzeugt wird, und nutzt diese für das spätere Marshalling und Unmarshalling unpraktisch, wenn unser Datenmodell nicht durch den Schema-Compiler generiert wurde. Wenn wir allerdings einfach einen Array von -Instanzen übergeben, dann bindet diese über eine Standardbindung an XML. Hierzu müssen die Klassen noch nicht einmal mit Annotationen versehen worden sein. Angenommen, wir haben die folgende Java-Klasse :
194
6.1 JAXBContext und JAXBElement ganz ohne Schema
Wenn wir nun XML-Elemente von diesem Java-Datentyp anlegen möchten, brauchen wir dazu nur einen zu erzeugen, dem wir in der -Methode diesen Datentyp übergeben.
Doch aufgepasst: Es gibt in XML Datentypen und Elemente analog zu Klasse und Objekt. Dass bedeutet, wir können nicht ohne weiteres eine -Instanz so mir nichts, dir nichts marshallen: Denn der Datentyp wird zunächst nur an eine -Definition eines XML-Schemas gebunden was wir jedoch benötigen, ist eine konkrete Elementinstanz. Erst eine Elementinstanz kann ein gültiges XML-Dokument bilden. Eine solche Instanz können wir mit der Klasse anlegen. Ein entspricht dann einer -Deklaration in einem XML-Schema. Damit eine solche Elementdeklaration vollständig ist, brauchen wir einen XMLNamespace und einen Elementnamen, diese müssen wir daher einer JAXBElement-Instanz mitgeben. Die Klasse kann mit dem Zieldatentyp parametrisiert werden, hier also .
Jetzt haben wir die Java-Repräsentation eines XML-Elementes im Namespace http://jaxb.transparent/foo. Der zweite Parameter entspricht der -Instanz des jeweiligen Elementdatentyps. Dieser kann auch bei einem parametrisierten Datentyp nicht weggelassen werden, denn die Typparameter sind nur optional und werden später durch den Java-Compiler im generier-
195
6 Java zu XML-Schema ten Bytecode nicht übernommen. Der Marshaller braucht jedoch die -Instanz, um hier über die Mittel der Java Reflection-API den Datentyp analysieren zu können, insbesondere die eventuell bei dem Datentyp verwendeten Annotationen. Im folgenden Beispiel verwenden wir nun eine mit Werten bestückte Instanz von , um die -Instanz anzulegen, und wandeln diese mit dem angelegten in ein formschönes XML-Dokument:
Eine der möglichen Prüfungsaufgaben, die bald im Rahmen der Abschlussprüfung einer Vorlesung Programmieren mit XML gestellt werden könnte: Was wird bei der Ausführung des oben abgedruckten Programms auf der Konsole () ausgegeben? Wir haben keine Kosten und Mühe gescheut, um die Antwort hier abdrucken zu können (zzgl. einiger zusätzlicher Zeilenwechsel):
Die Java Bean-Eigenschaften von werden hierbei zu Unterelementen des per dynamisch erzeugten Elements . Die Schemabeschreibung des Elements würden wir anhand des oben abgedruckten XML-Dokuments im Namespace http://jaxb.transparent/foo vermuten bloß, wo ist dieses Schema? Denn wenn wir nun dieses XML-Dokument mit einer der XML-APIs parsen, so wird diese das Schema nicht auflösen können, ist es doch nicht existent.
196
6.1 JAXBContext und JAXBElement ganz ohne Schema Auch ohne Schema gibt es ein Schema Aus jedem lässt sich ein Schema ableiten es ist entweder ein Schema, das semantisch einem vorher mit dem Schema-Compiler geparsten XML-Schema entspricht, oder ein Schema, das der XML-Bindung der in der -Methode übergebenen Klassen entspricht. Dieses Schema lässt sich einem JAXBContext mit der Methode entlocken.
Weil in einem eventuell auch mehrere, verbundene Schemas gekapselt werden, müssen beim Generieren der Schemas eventuell gleich mehrere Dateien angelegt werden. Aus diesem Grunde gibt es die Klasse . Mit dieser Klasse kann für jedes Schema ein eigener Ausgabestrom zurückgeliefert werden, in den das Schema gespeichert werden soll: Hierzu ruft die Implementierung von die Methode von auf.
Die Methode erhält bei ihrem Aufruf zwei Parameter: Zum einen die URI des XML-Namespace, der gespeichert werden soll, zum anderen einen Vorschlag für einen Dateinamen. Als Rückgabewert liefern wir in der Implementierung der Methode für jeden XML-Namespace eine Instanz zurück. Wichtig für die spätere Generierung von -Anweisungen in den XMLSchemadokumenten ist die Angabe der der -Instanz. In der folgenden, sehr einfachen Implementierung von geben wir einfach alles auf der Konsole aus, egal welchem XML-Namespace die Ausgabe zugeordnet wird diese Vorgehensweise ist ausreichend, wenn wir wissen, dass der ohnehin nur ein Schema umfasst:
197
6 Java zu XML-Schema Hinweis:
Weitere Implementierungen von sind neben der Klasse , die wahlweise auf einem , einer - oder einer per beschriebenen Datei basiert, auch und .
Wenn wir uns also für den im vorigen Abschnitt angelegten nun das Schema auf der Konsole ausgeben lassen wollen, können wir dieses mit dem folgenden Aufruf machen:
Wir erhalten dabei folgendes Schemadokument als Ausgabe:
Hier sehen wir nun, wie der Datentyp mit den Standardeinstellungen an ein Schema gebunden wird. Wir sehen allerdings auch, dass das Schema nur eine -Deklaration enthält mit dieser allein ist noch nicht wirklich ein XML-Dokument beschrieben. Gegen das implizite Schema validieren Generell gibt es bei der Validierung von XML-basierter Ein- und Ausgabe das Problem, dass ein zwar implizit ein Schema anlegt, dieses aber beim Unmarshalling
198
6.1 JAXBContext und JAXBElement ganz ohne Schema und Marshalling nur lax für die Validierung verwendet wir müssen daher ein implizites Schema zuerst aus dem extrahieren und dann bei den spezifischen - und -Instanzen registrieren. Das Extrahieren des Schemas aus dem und die Verwendung des Schemas für die Validierung beim Lesen und Speichern von XML wollen wir im folgenden Beispiel nachvollziehen. Zunächst legen wir die Klasse an. Diese funktioniert analog zur oben dargestellten Klasse , die das durch den erzeugte Schema auf der Konsole ausgibt. Statt auf der Konsole wird die Schemaausgabe in einem Puffer gespeichert. Diesen Puffer wandelt dann auf Verlangen in ein programmatisch nutzbares Schema um. Es handelt sich hierbei um eine Instanz von , die wir später den Unmarshaller- und Marshaller-Instanzen übergeben können.
Beim Aufruf von wird ein zurückgeliefert, das auf den lokal definierten Puffer zeigt. Die -Implementierung schreibt nun das entstehende Schema in den Puffer. Anschließend können wir in der Methode mit Hilfe von auf Basis des Puffers eine -Instanz erzeugen. Wie bereits auch die Klasse funktioniert nur, wenn sich aus dem nur ein Schema ergibt. Entstehen
199
6 Java zu XML-Schema mehrere Schemas, müssen diese mehrere Aufrufe von mit verschiedenen Zielschemas verarbeiten können. In der Main-Methode der Klasse verwenden wir nun eine Instanz von , um das implizite Schema für die Validierung nutzen zu können.
Die im Quelltext angelegten Instanzen von und validieren nach dem Aufruf von gegen das dynamisch durch die Klasse definierte XML-Schema. Zusammenfassung Elemente eines XML-Dokuments entsprechen (im Normalfall) einer parametrisierten -Instanz. Die Typdefinitionen eines XML-Dokuments entsprechen semantisch einer Java-Klasse. Einem sind explizit oder explizit eine oder mehrere Schemas zugeordnet. Die Schemas eines können über die Methode ausgegeben werden. Da in einem potenziell mehrere verknüpfte Schemas definiert sein können, erfolgt die Ausgabe der Schemas vermittelt über eine Implementierung von , über die verschiedene Schemas an verschiedenen Stellen gespeichert werden können.
200
6.2 Einfache Elementkonfigurationen Der beherrscht eine Standardabbildung von Java nach XML auch JavaKlassen ohne Annotationen werden in bestimmter Weise an XML-Typen gebunden. Um bei Marshalling und Unmarshalling gegen ein Schema zu validieren, müssen wir dieses zuvor vom generieren lassen.
6.2
Einfache Elementkonfigurationen Die Standardbindung des bindet eine Java-Klasse an eine Definition eines XML-Schemas. Eine -Definition allein macht aber noch kein XML-Dokument. So enthält ein ordentliches Schema in der Regel einige Elementdeklarationen in der Schemawurzel. Auch ist die Standardbindung im Hinblick auf die Datentypen nicht immer ausreichend: So wollen wir gelegentlich zwischen Unterelementen und Attributen unterscheiden können. Ein weiterer Aspekt, den wir konfigurieren möchten, ist die Zuordnung von Elementen und Datentypen zu unterschiedlichen Namespaces. Diese grundlegenden Konfigurationsmöglichkeiten werden wir in diesem Abschnitt behandeln: Elemente konfigurieren, Datentypen konfigurieren, Namespaces konfigurieren. Die wichtigsten Annotationen, mit denen die Bindung von Java an XML-Elemente definiert werden kann, sind: @XmlElement: Bindet eine Variable oder eine Java Bean-Eigenschaft an ein XMLElement. @XmlAttribute: Bindet eine Variable oder Java Bean-Eigenschaft an einen Attributwert. @XmlValue: Definiert eine Bindung eines Wertes an den textuellen Inhalt eines XMLElements. @XmlTransient: Ignoriert das markierte Java-Element für die XML-Bindung. @XmlAccessorOrder: Definiert eine Sortierung der Elementdeklarationen im Schema. @XmlAccessorType: Definiert, welche Variablen oder Java Bean-Eigenschaften bei der Bindung an einen XML-Typ beachtet werden. @XmlElementRef: Bindet eine Variable oder eine Java Bean-Eigenschaft an ein Wurzelelement und referenziert dieses an der markierten Stelle. @XmlRootElement: Eine mit dieser Annotation versehene Java-Klasse ist gleichzeitig Datentyp und Wurzelelement. @XmlType: Konfiguriert die Bindung der Java-Klasse an eine Deklaration.
6.2.1
Wurzelelemente mit @XmlRootElement definieren
Die einfachste Variante, eine Java-Klasse an ein XML-Dokument zu binden, ist die Annotation . Eine Instanz einer mit markierten Klasse
201
6 Java zu XML-Schema kann unmittelbar als eigenständiges XML-Dokument gespeichert werden. Die JAXBImplementierung nimmt dabei die Klasse als -Deklaration in das implizite Schema auf und legt ein gleichnamiges Element von diesem Typ in der Wurzelebene an. Die Annotation kann auf Java-Klassen und Java-Enumerationen angewendet werden. Angenommen, wir versehen den Datentyp wie hier abgebildet mit der Annotation :
Beim Auswerten dieses Typs durch die -Implementierung wird nun ein implizites Schema mit einem komplexen Typ und einem Element von diesem Typ angelegt. Wenn wir uns das Schema per ausgeben lassen, dann sieht das Schema wie folgt aus:
202
6.2 Einfache Elementkonfigurationen
Jetzt können wir Instanzen dieses Datentyps unmittelbar marshallen und unmarshallen. Ein Verpacken des Datentyps in eine -Instanz ist in diesem Fall nicht notwendig. Der Name des Elements wird, wenn nicht angegeben, automatisch anhand des Klassennamens ermittelt, in unserem Fall . Wenn wir nun einen anlegen, der auf dieser Klasse basiert, dann können wir diese Klasse unmittelbar in ein XMLDokument umwandeln:
Der
Weg
von
einem
XML-Dokument
zurück zu einer Instanz von funktioniert genauso komfortabel über den Unmarshaller:
203
6 Java zu XML-Schema
Den Namen des Wurzelelements konfigurieren Der Name des Wurzelelements kann über den Parameter der Annotation konfiguriert werden. Angenommen, wir wollen das Wurzelelement zu unserem Datentyp mit dem klangvollen Namen versehen, dann können wir das mit der folgenden Konfiguration machen:
Betrachten wir nun das Schema zu diesem Datentyp, so sehen wir, dass das Wurzelelement mit dem gewünschten Namen angelegt wurde:
Schließlich lassen wir eine Instanz der auf diese Weise konfigurierten Klasse mit dem Marshaller in ein XML-Dokument speichern, und natürlich beachtet auch dieser den neuen Elementnamen. Das entstehende Dokument sieht wie folgt aus:
204
6.2 Einfache Elementkonfigurationen Fast zu einfach, oder? Zusammenfassung Objekte von Klassen, die mit der Annotation versehen wurden, können direkt als XML-Dokumente gespeichert werden
6.2.2
Der Standardmodus
Wenn wir uns den Datentyp anschauen und wie dieser Datentyp an den XML-Datentyp gebunden wird, dann fällt auf, dass alle Java Bean-Eigenschaften des Typs in gleich benannte XML-Elemente umgewandelt werden. Das ist tatsächlich die Standardvorgehensweise der JAXB-Implementierung: Findet sich keine andere Konfiguration an einer Klassenvariablen, Java Bean-Eigenschaft oder an der Klasse insgesamt, so werden alle mit der Sichtbarkeit versehenen JavaElemente in XML-Unterelemente transformiert. Dieser Standardmodus kann explizit mit der Annotation auf Klassen oder Paketebene deklariert werden. Die Annotation nimmt dabei einen Parameter vom Typ auf, eine Enumeration mit vier Elementen, wobei die Standardeinstellung ist. XmlAccessType.FIELD: Alle nicht statischen, nicht transienten Felder werden an XML-Elemente gebunden, unabhängig von deren Sichtbarkeit. XmlAccessType.NONE: Kein Java-Element wird an XML gebunden. XmlAccessType.PROPERTY: Alle durch Zugriffsmethoden definierten Java BeanEigenschaften werden an XML gebunden, unabhängig von deren Sichtbarkeit. XmlAccessType.PUBLIC_MEMBER: Alle nicht statischen, nicht transienten Felder sowie alle durch Zugriffsmethoden definierten Java Bean-Eigenschaften werden an XML gebunden Voraussetzung: Deren Sichtbarkeit ist . Die Annotation kann dabei wie erwähnt auf Klassen- sowie auf Paketebene verwendet werden. Auf Paketebene sieht eine solche Konfiguration wie folgt aus:
Wichtig: Diese Konfiguration steht in einer Datei package-info.java im Verzeichnis des Java-Pakets. Eine solche Konfiguration hat zur Folge, dass bei allen Java-Datentypen in diesem Paket vorerst keine Java-Elemente an XML gebunden werden. Allerdings wird eine Konfiguration auf Paketebene durch eine auf Klassenebene aufgehoben bzw. ersetzt.
205
6 Java zu XML-Schema XmlAccessType.FIELD Wenn wir also, wie im folgenden Quelltext dargestellt, auf der Klassenebene festlegen, so überschreibt diese Einstellung das eventuell auf Paketebene eingestellte Verhalten : Es werden jetzt doch alle Java-Elemente an XML gebunden, ungeachtet ihrer Sichtbarkeit:
Die Einstellung hat dann zur Folge, dass alle Felder der Klasse an XML-Elemente gebunden werden. Wenn wir uns das implizite Schema der Klasse betrachten, so taucht dort sogar das Feld auf, das wir in der Klasse mit der Sichtbarkeit versehen haben.
Die Java Bean-konforme Kombination aus Zugriffsmethoden, die auf eine Eigenschaft schließen ließe, wird in dieser Einstellung geflissentlich ignoriert.
206
6.2 Einfache Elementkonfigurationen XmlAccessType.NONE Die einfachste Einstellung ist .. Hier werden einfach alle JavaElemente ignoriert, egal ob diese als Felder oder als Java Bean-konforme Eigenschaften deklariert sind. Angenommen, wir versehen eine Klasse , die wie die Klasse aufgebaut ist, mit der Einstellung und verwenden auf Elementebene keine weiteren Annotationen:
Das hieraus entstehende Schema sieht unabhängig von der spezifischen Implementierung der Klasse stets eintönig wie folgt aus:
Hier kann nicht viel passieren ... XmlAccessType.PROPERTY Der Parameter der Annotation zielt auf die Java Bean-konformen Eigenschaften einer Klasse. Während die Felder einfach ignoriert werden, werden alle Java Bean-Eigenschaften mit entsprechenden Getter- und SetterMethoden in den XML-Datentyp aufgenommen. Wir nehmen also die folgende Klasse:
207
6 Java zu XML-Schema
Hier sind genau zwei Eigenschaften in Java Bean-konformen Zugriffsmethoden beschrieben: und . Alle anderen Felder werden ignoriert auch das Feld , selbst wenn dieses Feld mit der Sichtbarkeit deklariert wurde, also eigentlich immer sichtbar ist. Interessant an dieser Stelle ist auch, dass die Java BeanEigenschaften unabhängig von ihrer Sichtbarkeit in die -Definition übernommen werden. So sieht das Schema zu unserer Klasse aus:
XmlAccessType.PUBLIC_MEMBER Die vierte und letzte Variation von bindet alle mit der Sichtbarkeit versehenen Felder und Java Bean-Eigenschaften an einen XML-Datentyp. Diese Einstellung ist zugleich die Standardeinstellung, die angenommen wird, wenn die Annotation für eine Java-Klasse nicht explizit angegeben wurde. Passen wir unsere Beispielklasse aus den obigen Beispielen wie unten abgedruckt an und übergeben der Annotation den Parameter :
208
6.2 Einfache Elementkonfigurationen
Diesmal werden von der Klasse alle Felder und Java Bean-Eigenschaften mit der Sichtbarkeit an XML-Elemente gebunden. In diesem Fall sind das die Eigenschaft und das Feld . Übergangen werden alle Felder und Java BeanEigenschaften mit eingeschränkter Sichtbarkeit, also die Java Bean-Eigenschaft mit der Sichtbarkeit und das als deklarierte Feld . Damit sieht das Schema zur Klasse wie folgt aus:
Hinweis: Bei der Einstellung kann es zu Namenskonflikten kommen, wenn parallel ein Feld und eine gleichnamige Java Bean-Eigenschaft mit der Sichtbarkeit versehen wurden.
209
6 Java zu XML-Schema
Wenn wir die oben abgebildete Klasse nun einem übergeben, kann dieser das Feld und die Java Bean-Eigenschaft mit dem Namen nicht eindeutig einem XML-Element zuordnen. Es kommt hierbei zu einer Fehlermeldung. Diese Namenskonflikte lassen sich beispielsweise mit Hilfe der Annotation (siehe weiter unten) beseitigen.
Zusammenfassung Welche Felder und Java Bean-Eigenschaften einer Java-Klasse an XML gebunden werden, hängt von der Annotation ab. Geben wir nichts oder nichts anderes an, so gilt . Nicht jede Java-Klasse kann ohne weiteres an XML gebunden werden hier gilt es manchmal, beispielsweise Namenskonflikte manuell zu beheben.
6.2.3
Abbildung als XML-Element explizit konfigurieren
Wir haben im vorigen Abschnitt gesehen, wie Felder und Java Bean-konforme Zugriffsmethoden einer Java-Klasse standardmäßig an XML-Elemente gebunden werden. Diese Bindung kann über die Annotation für einzelne Java-Elemente im Detail konfiguriert werden. Die Annotation alleine bedeutet dabei, dass die markierte Java-Eigenschaft als ein Unterelement abgebildet werden soll. Im folgenden Beispiel definieren wir mit der Annotation auf Klassenebene, dass standardmäßig keine Bindung von Java-Elementen an XML erfolgen soll. Das Feld markieren wir jedoch mit der Annotation wie im Quelltext unten dargestellt:
210
6.2 Einfache Elementkonfigurationen
Die auf der Ebene des Felds vorgenommene Konfiguration überschreibt hierbei jene auf Klassenebene: Das Feld wird nun an ein XML-Element gebunden. Wenn wir nun einen auf Basis der Klasse initialisieren und uns mit der Methode das Schema ausgeben lassen, dann sieht die Ausgabe etwa wie folgt aus:
Zwingende Angabe des XML-Elements konfigurieren Wenn wir die Elementdeklaration betrachten, fällt auf, dass der die Deklaration mit der Einschränkung versehen hat, wobei die Einschränkung hier sehr liberal mit 0 formuliert ist. Die Angabe des Elements innerhalb einer XMLInstanz von ist daher optional. Nun gibt es in Java selbst keine Möglichkeit, unmittelbar anzugeben, ob ein Feld oder eine Java Bean-Eigenschaft zwingend mit Werten bestückt werden muss in XML-Schema sehr wohl. Daher bietet die Annotation unter anderem den Parameter . Dabei kann mit angegeben werden, ob dieses Element angegeben werden muss. Der Parameter ist vom Typ . Im folgenden Beispiel geben wir für die Eigenschaft der Java-Klasse an, dass dieses Unterelement für eine gültige Instanz des XML-Typs unbedingt angegeben werden muss.
211
6 Java zu XML-Schema
Der Parameter mit dem Wert wird durch die -Instanz nun so interpretiert, dass diese im abgeleiteten Schema das Attribut der Elementdeklaration von weglässt, was implizit der Einstellung gleich 1 entspricht. Damit muss in einer gültigen Instanz des XML-Typs das Element mindestens einmal vorkommen. An dieser Stelle das generierte Schema für die Klasse :
Standardwert des XML-Elements definieren Wenn der Wert eines Elements nicht zwingend im XML-Dokument angegeben werden muss, weil sich aus der Anwendungsdomäne ein Standardwert ergibt, können wir im XML-Schema diesen Standardwert über das Attribut in der Elementdeklaration setzen. Die Annotation bietet hier mit gleicher Funktionalität den Parameter . Über diesen Parameter kann innerhalb der Java-Annotation ein solcher Standardwert definiert werden. Der Parameter ist vom Typ . In der folgenden Klasse haben wir das Element und den Parameter mit dem Wert bestückt.
212
6.2 Einfache Elementkonfigurationen
Wenn wir die Klasse durch den Schema-Generator schicken, erhalten wir das folgende XML-Schema:
Fehlt nun in der XML-Repräsentation von ein Wert für das Element , dann setzt die JAXB-Implementierung an dieser Stelle den Standardwert ein. Im folgenden Beispiel unmarshallen wir ein im Fluge generiertes XML-Dokument mit einem -Element:
213
6 Java zu XML-Schema Statt oder eines Leerstrings entspricht der zurückgelieferte Wert für das Element dem definierten Standardwert . Einen anderen Namen für das XML-Element angeben Bisher erhielten alle XML-Elemente den Namen ihres Java-Gegenparts. Diese triviale Abbildung des Namens kann über den Parameter der Annotation beeinflusst werden. Hier kann ein beliebiger, gültiger XML-Elementname angegeben werden. Die JAXB-Implementierung bildet dann das anders benannte XML-Element auf das entsprechende Java-Element ab und umgekehrt. In der folgenden Klasse sind sowohl die Wurzelelementdeklaration als auch die Unterelementdeklaration über den Parameter mit anderem Namen versehen worden:
Wenn wir uns für die solchermaßen konfigurierte Klasse durch den Schema-Generator das abgeleitete XML-Schema ausgeben lassen, so erhalten wir das folgende Schema für den Typ :
Diese Namensspielereien werden etwas anschaulicher, wenn wir diese anhand einer Instanz des obigen Schemas nachvollziehen. So könnte eine solche gültige Instanz beispielweise wie folgt aussehen:
214
6.2 Einfache Elementkonfigurationen
Wenn wir dieses XML-Dokument an den entsprechenden Unmarshaller verfüttern, so erzeugt dieser daraus ein Objekt vom Typ :
Die hier dargestellte Abbildung stellt sehr schön dar, wie bereits mit einfachen Mitteln sehr unterschiedlich anmutende Repräsentationen der gleichen Daten in XML und in Java aufeinander abgebildet werden können. Zusammenfassung Mit werden Java-Felder und Java Bean-Eigenschaften explizit an -Deklarationen in einem Schema gebunden. Der Parameter der Annotation gibt dem XML-Element einen anderen Namen. Der Parameter entspricht der Deklaration von im Schema. Über den Parameter kann man den Standardwert eines XML-Elements angeben.
215
6 Java zu XML-Schema
6.2.4
Java-Eigenschaften an XML-Attribute binden
Eine Spezialität des XML-Formats sind die Attribute. Bei der Definition eines XMLDatentyps haben wir die Wahl, Werte in Unterelementen oder in Attributen zu speichern. Hierbei können Unterelemente selbst einen komplexen Typ darstellen, Attribute müssen vom Typ her hingegen einem der XML-Basisdatentypen entsprechen. Besitzt eine JavaKlasse also ein Feld oder eine Java Bean-Eigenschaft, die vom Typ her einem der XMLBasisdatentypen zugeordnet werden kann, so können wir diese mit der Annotation an eine Attributdeklaration binden. Im folgenden Quelltext haben wir das Java-Feld mit der Annotation versehen.
Dieses Feld wird nun an eine Attributdeklaration innerhalb des abgeleiteten XMLDatentyps gebunden. Das Ergebnis des Schema-Generators für diese Klasse sieht dann wie folgt aus:
216
6.2 Einfache Elementkonfigurationen
Legen wir nun ein kleines XML-Dokument an, das diesem XML-Schema entspricht. Hier sehen wir deutlich den Unterschied zur Deklaration als Unterelement.
Aus Sicht der Java-Implementierung hingegen bleibt die Abbildung auf einen XMLAttributwert ohne Folgen. Wir können hier in der programmatischen Nutzung des Typs keinen Unterschied zwischen einer Elementbindung und einer Attributbindung feststellen. Die JAXB-Implementierung kümmert sich transparent um das richtige Marshalling und Unmarshalling.
XML-Attributnamen einstellen Auch im Fall der Attribute werden die Namen zunächst trivial abgebildet der Name des mit markierten Java-Felds wird hierbei als Name des XML-Attributs bernommen. Mit dem Parameter der Annotation kann ein anderer
217
6 Java zu XML-Schema Name angegeben werden, der dann im XML-Dokument als Name für das Attribut verwendet wird. In der unten abgebildeten Klasse ist das Feld mit der Annotation versehen als Parameter übergeben wir den Attributnamen , der für die XML-Umwandlung genutzt werden soll.
Im passenden Schema zu dieser Java-Klasse enthält die Elementdeklaration von nun eine Attributdeklaration für ein Attribut vom Schemadatentyp . Das Schema sieht damit dann wie folgt aus:
Der Anschaulichkeit halber seit hier eine einfache Instanz dieses XML-Schemas dargestellt, die durch einen entsprechend konfigurierten als Objekt vom Typ gelesen und gespeichert werden kann:
Angabe von XML-Attributen erzwingen In einem XML-Schema kann für ein XML-Attribut angegeben werden, ob dieses zwingend angegeben werden muss, um ein gültiges XML-Dokument zu formulieren. Hierzu setzen wir im Schema in der Attributdeklaration deren auf . Diese Einstellung können wir mit der Annotation nachvollziehen. Die Annotation bie-
218
6.2 Einfache Elementkonfigurationen tet den gleichnamigen Parameter , den wir auf setzen können, um die Angabe des Attributs innerhalb von XML zu erzwingen. Der folgende Datentyp deklariert ein XML-Attribut , wir haben hier den Parameter auf gesetzt.
Im Schema zu dieser Java-Klasse finden wir nun das Attribut der Attributdeklaration von auf gesetzt. Wenn jetzt das XML-Element ohne die Angabe von auftritt, so gibt es hier Validierungsfehler beim Lesen bzw. Schreiben dieses XML-Elements.
Wenn wir den Validierungsfehler nachvollziehen wollen, müssen wir zuvor die Validierung aktivieren. Im Kapitel zur JAXB-API ist das Aktivieren der Validierung detailliert beschrieben. Im folgenden Beispiel wollen wir den Validierungsfehler für ein fehlendes -Attribut provozieren. Dazu registrieren wir das obige Schema beim Unmarshaller und versuchen, eine unvollständige XML-Instanz von zu parsen. Generell gibt es hier das Problem, dass ein zwar implizit ein Schema anlegt, aber nur sehr lax gegen dieses validiert wir müssen daher das Schema zuerst aus dem extrahieren und dann bei den verwendeten -/Instanzen registrieren. Hierbei lassen wir uns durch die Klasse unterstützen, die wir am Anfang dieses Kapitels vorgestellt haben.
219
6 Java zu XML-Schema
Da wir hier versuchen, das Element zu parsen, dieses aber entgegen den Anforderungen des XML-Schemas keinen Wert für das Attribut definiert, gibt es hier einen Validierungsfehler. Je nach verwendeter Kombination aus XML-Treiber und JAXB-Implementierung erhalten wir dabei eine Fehlermeldung mit etwa folgendem Wortlaut:
Zusammenfassung Die Annotation macht ein Java-Feld oder eine Java Bean-Eigenschaft zu einem Attributwert in einem XML-Dokument. Der Parameter erzwingt im XML-Dokument die Angabe des Attributs. Mit dem Parameter können wir dem XML-Attribut einen anderen Namen geben.
220
6.2 Einfache Elementkonfigurationen
6.2.5
Java-Eigenschaften an XML-Elementinhalte binden
Bisher wurden die Werte von Java-Eigenschaften stets an die textuellen Inhalte eines Unterelements gebunden. Wenn die Java-Klasse eine Eigenschaft hat, so wurde bisher im XML-Typ ein Unterelement vom Schema-Basistyp deklariert. Allerdings ist es ja denkbar, dass ein XML-Element zwar Attribute, aber keine Unterelemente definiert, dafür aber Text innerhalb der Elementklammer erlaubt:
Die Inhalte eines solchen XML-Elements können wir mit der Annotation an eine passende Java-Eigenschaft binden. Stellen wir das oben abgedruckte XML-Beispiel nach. Hierzu legen wir eine Java-Klasse an und geben ihr drei Eigenschaften , und , alle drei vom Typ . Die beiden Eigenschaften und versehen wir dabei mit der eben eingeführten Annotation , die Eigenschaft mit der neuen Annotation .
Hinweis
Ist eine Eigenschaft einer Klasse mit der Annotation versehen worden, so darf zunächst keine andere Eigenschaft an ein Unterelement gebunden werden, sei es explizit per oder aufgrund des Standardverhaltens der XML-Bindung.
Lassen wir uns zu dieser Klasse das Schema generieren, so sehen wir, dass hier anstelle der üblichen Sequenz von Unterelementen nun eine -Deklaration in der Typdeklaration des XML-Typs steht:
221
6 Java zu XML-Schema
Unsichtbar aus der XML-Sicht, für die Verwendung in der Java-Welt aber sehr praktisch die Inhalte eines -Elements werden nun beim Unmarshalling in dem JavaFeld gespeichert, wie wir im folgenden Beispiel überprüfen:
Zusammenfassung Mit der Annotation kann der Textinhalt eines XML-Elements an eine JavaEigenschaft gebunden werden kann in einer Java-Klasse nicht gleichzeitig mit anderen, impliziten oder
expliziten, Bindungen an XML-Elemente () verwendet werden.
222
6.2 Einfache Elementkonfigurationen
6.2.6
Bindung an XML unterdrücken
In manchen Fällen ist die Bindung eines Java-Felds oder einer Java Bean-konformen Eigenschaft an eine XML-Repräsentation unerwünscht. Das kann beispielsweise der Fall sein, wenn ein Wert von anderen abgeleitet wird, also nicht eigenständig gespeichert werden soll, oder wenn Java-Eigenschaften auf Ressourcen wie Datenbankverbindungen, Dateien oder Ein- und Ausgabeströme verweisen. Wenn ein Java-Element nicht an XML gebunden werden soll, kann dieses über die Annotation eingestellt werden. Es wird dann von der JAXB-Implementierung ignoriert. Im folgenden Beispiel klammern wir die Eigenschaft aus der XMLDatenbindung aus, weil diese sich aus den Eigenschaften und ableitet und nicht getrennt gespeichert werden soll.
Übergeben wir diese Klasse wieder dem Schema-Generator, so wird im XML-Schema ein Typ definiert, der die Unterelemente und enthält, jedoch nicht.
223
6 Java zu XML-Schema Die Annotation kann insbesondere verwendet werden, um Namenskonflikte zu beseitigen. Definiert beispielsweise eine Klasse sowohl ein Feld als auch eine Java Bean-konforme Eigenschaft mit gleichem Namen und sind beide mit der Sichtbarkeit versehen, so ergibt sich für die JAXB-Implementierung hier ein Namenskonflikt. Wir haben dieses Szenario in der folgenden Klasse nachgestellt. Hier sind sowohl die Zugriffsmethoden als auch das Feld mit der Sichtbarkeit versehen. Für die JAXB-Implementierung ist es jetzt nicht klar, ob beim Speichern und Lesen von XML-Daten hier das Feld unmittelbar verwendet werden soll oder deren Zugriffsmethoden.
Wenn wir diese Klasse einem zur Initialisierung übergeben, müssen wir mit einer Ausnahme rechnen. Im Fall der Referenzimplementierung wird hierbei etwa folgende Fehlermeldung ausgegeben:
Diesen Konflikt können wir nun mit Hilfe der Annotation beheben, indem wir eine der beiden möglichen Bindungen (Feld vs. Zugriffsmethoden) damit versehen. Markieren wir das Feld mit , so erfolgt die Bindung an XML über die Zugriffsmethoden und .
224
6.2 Einfache Elementkonfigurationen
Der Namenskonflikt ist aufgelöst, die Klasse kann für die Initialisierung einer -Instanz verwendet werden, ohne eine Ausnahme zu provozieren. Zusammenfassung Versieht man ein Java-Element mit der Annotation , so wird dieses von der JAXB-Implementierung ignoriert. Diese Annotation muss manchmal bemüht werden, um Namenskonflikte aufzulösen. Sie ist auch sinnvoll angewendet, wo Objekte nicht ohne weiteres in einem XMLDokument gespeichert werden können und sollen, beispielsweise bei Datenbankverbindungen oder Klassen der Anwendungslogik.
6.2.7
Reihenfolge der XML-Elemente beeinflussen
Während in Java die Reihenfolge der Eigenschaften und Felder kaum eine Rolle spielt, ist die Reihenfolge von XML-Elementen in einem XML-Dokument in der Regel fest vorgegeben. Wollen wir also aus Java heraus die Reihenfolge der XML-Elemente beeinflussen, so haben wir hierzu zwei Möglichkeiten: Wir können zum einen die Java-Klasse mit der Annotation versehen und somit eine alphabetische Sortierung erreichen, oder wir können über den Parameter der Annotation die Reihenfolge manuell vorgeben. Normalerweise unsortiert Betrachten wir zunächst den Standardfall. Wir legen hierzu eine einfache Klasse an, die vier Felder , , und definiert:
Lassen wir uns das Schema für diese Klasse ausgeben, so sehen wir, dass die Referenzimplementierung die Reihenfolge der vier Felder mit in die -Definition im Schema übernimmt.
225
6 Java zu XML-Schema
Aber schon der folgende Fall ist anders. Modifizieren wir nämlich die Klasse und versehen wir die Eigenschaften wie unten abgedruckt mit Zugriffsmethoden, so ändert sich flugs die Reihenfolge der XML-Elemente im Schemadatentyp.
Die Sortierung der per Zugriffsmethoden gebundenen Eigenschaften im XML ist nun alphabetisch:
226
6.2 Einfache Elementkonfigurationen Wir stellen also fest: Wird bezüglich der Sortierung der XML-Elemente nichts konfiguriert, können wir uns auf keine bestimmte Sortierung verlassen. Im Gegenteil können Änderungen an der Java-Klasse zu einer inkompatiblen Änderung des XML-Datentyps führen. 6.2.7.1
Alphabetisch sortieren mit XmlAccessorOrder
Eine triviale Konfigurationsmöglichkeit bietet die Annotation mit dem Parameter . In diesem Fall werden die Unterelemente innerhalb eines XML-Datentyps stets alphabetisch sortiert. Ändern wir also den Datentyp und versehen wir diesen mit der Annotation :
Jetzt ist im Gegensatz zu gewährleistet, dass die Reihenfolge der Abbildung von , , und als Unterelement alphabetisch erfolgt.
Interessant ist die streng alphabetische Sortierung der Unterelemente, weil eine spätere Erweiterung der Java-Klasse um weitere Eigenschaften stets kompatible Änderungen am gebundenen XML-Datentyp zur Folge hat. Angenommen, wir erweitern die Klasse um eine Eigenschaft . Wenn wir hier nicht auf die Sortierung achten, kann es sein, dass der neu entstehende XMLDatentyp nicht mehr kompatibel zu alten Versionen des Typs ist, weil sich die erwartete Reihenfolge der XML-Elemente verändert hat. Das ist ungünstig, wenn es bereits eine Reihe von XML-Dokumenten gibt, die auf Basis der alten Bindung erstellt wurden.
227
6 Java zu XML-Schema Hat man von vornherein die Sortierung der Unterelemente alphabetisch eingestellt, so sind Erweiterungen stets kompatibel XML-Dokumente, die auf Vorgängerversionen der Typbindung basieren, können auch mit der neuen Bindung gelesen werden. 6.2.7.2
Exakt sortieren mit propOrder
Präziser kann die Reihenfolge der Elemente mit dem Parameter der Annotation festgelegt werden. Die Annotation kann auf Klassenebene definiert werden. Der Parameter ist dabei ein Java-Array vom Typ . In diesem Array müssen alle Java-Felder und Java Bean-Eigenschaften aufgelistet werden, die in der Klasse an einen XML-Datentyp gebunden werden. Die Reihenfolge der Namen im Java-Array wird dann in den XML-Datentyp übernommen. Alternativ kann der Array auch leer übergeben werden, dann werden die Unterelemente statt in einer Deklaration in einer -Deklaration verschachtelt, in diesem Moment ist dann die Reihenfolge der XML-Elemente gleichgültig. Im folgenden Datentyp haben wir die Eigenschaften , , und über die Annotation und deren Parameter sortiert.
Im XML-Schema zur Klasse wird nun die definierte Reihenfolge übernommen.
228
6.2 Einfache Elementkonfigurationen Dem Parameter kann auch ein leerer Array übergeben werden. Etwas unerwartet, aber praktisch, werden dann die Unterelemente schlicht in einer -Deklaration zusammengefasst. Die Komposition verlangt nur, dass alle definierten Unterelemente vorkommen, nicht aber, in welcher Reihenfolge diese stehen müssen. Diese Deklaration ist eigentlich die flexibelste. In der unten angelegten Klasse bestücken wir den Parameter wie beschrieben mit einem leeren Array:
Generieren wir uns über eine entsprechend initialisierte -Instanz das Schema zu diesem Datentyp, so sehen wir, dass die Elemente , , und nun in einer -Deklaration zusammengefasst sind.
Hinweis
Wenn der Parameter nicht mit einem leeren Feld initialisiert wird, so müssen alle Eigenschaften in dem Feld vorkommen fehlen ein oder mehrere Namen von Java-Feldern oder Java Bean-Eigenschaften, so provoziert diese Konfiguration eine Ausnahme.
In der hier abgedruckten Java-Klasse haben wir mit und nur zwei der vier an XML gebundenen Elemente dem Parameter übergeben:
229
6 Java zu XML-Schema
Versuchen wir nun, mit dieser Klasse eine -Instanz zu initialisieren, so erhalten wir im Fall der Referenzimplementierung etwa folgende Fehlermeldung:
Die JAXB-Implementierung kann in diesem Fall die Klasse nicht eindeutig an eine XMLRepräsentation binden, da die Reihenfolge der beiden Elemente und sich nicht aus ergibt und daher undefiniert ist. Zusammenfassung Die Reihenfolge von XML-Elementen kann für die Gültigkeit eines XML-Dokuments relevant sein. Geben wir nichts an, so können wir uns auf keine bestimmte Reihenfolge von Elementen verlassen. Viel bringt schon die alphabetische Sortierung von Unterelementen mit der Annotation zumindest gibt es hier keine Überraschungen mehr. Die Reihenfolge kann mit dem Parameter der Annotation genau festgelegt werden. Übergeben wir dem Parameter ein leeres Feld, so werden Unterelemente in einer -Deklaration zusammengefasst: Die Reihenfolge der XML-Elemente ist dann egal.
6.2.8
Namen und Verschachtelung des XML-Typs einstellen
Eine Java-Klasse wird von der JAXB-Implementierung automatisch in einen XMLDatentyp in Form einer -Deklaration umgewandelt. Der XML-Datentyp erhält dabei den Namen der Java-Klassen mit kleinen XML-spezifischen Anpassungen. In einem XML-Dokument selbst tauchen diese Datentypen nicht unmittelbar auf der Datentyp wird in der Regel nur verwendet, um Wurzelelementen und Elementen eine Syntax zu geben. Viele Schemas verzichten dabei auf eigenständige Datentypen, sondern verschachteln die Datentypen anonym in der Elementdeklaration:
230
6.2 Einfache Elementkonfigurationen
Die Annotation konfiguriert einige Details der Bindung der Java-Klasse an einen XML-Datentyp, unter anderem kann zum einen der Name des XML-Typs eingestellt werden, zum anderen, ob dessen Deklaration als eigenständiger Typ im Schema erfolgen soll oder als anonymer Typ. 6.2.8.1
Dem XML-Typ einen eigenen Namen geben
Die Annotation hat einen Parameter , über den der Name des XML-Typs eingestellt werden kann. Im folgenden Beispiel haben wir die Java-Klasse mit der Annotation versehen und der Annotation mit dem Parameter den Wert accountType übergeben.
231
6 Java zu XML-Schema
Geben wir diese Klasse einem und generieren wir das passende Schema hierzu, so sehen wir, dass die -Deklaration im Schema nun den Namen trägt.
Hinweis
Namen für XML-Elemente und Datentypen können in XML viel flexibler als in Java gewählt werden. XML erlaubt insbesondere die Angabe von Punkten und Bindestrichen in Namen.
6.2.8.2
XML-Datentypen anonymisieren
Geben wir für den Parameter der Annotation eine leere Zeichenkette an, dann passiert eine kleine Zauberei: Der Datentyp als solches verschwindet aus dem Schema. Wenn diese Java-Klasse nun vom Typ her Grundlage für eine Bindung an ein XMLElement ist, so wird eine entsprechende -Deklaration unmittelbar und anonym in der Elementdeklaration verschachtelt. Im folgenden Typ haben wir dieses Verhalten nachgestellt. Der Annotation übergeben wir hier also eine leere Zeichenkette. Damit wir nun nicht nur ein leeres Schema aus dieser Java-Klasse erhalten, versehen wir die Klasse zusätzlich mit der Annotation .
232
6.2 Einfache Elementkonfigurationen
Das obligate Schema zur Klasse enthält nun ein Wurzelelement und, wie angekündigt, nur eine verschachtelte, lokale Deklaration.
Dabei ist es unerheblich, wie häufig der Datentyp in dem Schema vorkommt, der Typ wird redundant für jedes Vorkommen aufgefaltet. Gültige XML-Dokumente sehen bei beiden Vorgehensweisen gleich aus, mit zwei Ausnahmen allerdings: Zum einen kann über das Attribut aus dem Namespace http://www.w3.org/2001/XMLSchema-instance ein XML-Typ aus einem Schema unmittelbar referenziert werden. Dieses ist im Fall einer anonymen Deklaration des Datentyps nicht möglich. Zum anderen können über eigenständige Datentypen auch Typrekursionen modelliert werden. Dass heißt, ein XML-Typ A kann ein Unterelement vom Typ B deklarieren. Der Typ B kann wiederum Unterelemente vom Typ A erlauben. Auch das ist mit anonymen Datentypen nicht abbildbar. Letzteres wollen wir an einem kleinen Beispiel zeigen. Angenommen, wir definieren einen Typ , der eine Eigenschaft vom eigenen Typ aufweist:
233
6 Java zu XML-Schema
Beim Versuch, auf unendlicher Ebene den XML-Typ unter der Elementdeklaration von zu verschachteln, scheitert nun die JAXB-Implementierung mit oder ohne sinnvolle Fehlermeldung. Da wir uns das Schema nicht mehr programmatisch erstellen lassen können, hier der Versuch, das Schema manuell zu rekonstruieren:
Wir könnten eine ganze Bibliothek mit diesem Listing füllen und wären noch nicht fertig. Zusammenfassung Mit dem Parameter von können wir den Namen der an eine JavaKlasse gebundenen -Deklaration festlegen. Übergeben wir dem Parameter einen leeren String, so wird, wo immer diese JavaKlasse ein XML-Element wird, eine anonyme -Deklaration gewählt. Definiert die Java-Klasse eine Typrekursion und wählen wir die Bindung an eine anonyme -Deklaration, so kann kein gültiges Schema daraus erzeugt werden.
6.2.9
XML-Elemente referenzieren
In Schema können verschachtelte Elementdeklarationen alternativ auf andere Elemente auf der Wurzelebene dieses oder eines anderen Schemas verweisen. Ein solches Element definiert dann keinen eigenen Typ und auch keinen eigenen Namen. Im Schema sieht eine solche Referenz wie folgt aus:
234
6.2 Einfache Elementkonfigurationen
Mit der ersten Elementdeklaration legen wir in der Wurzelebene des Schemas ein Element an, dieses verwenden wir dann in der Deklaration des zweiten Elements wieder hier allerdings als Elementreferenz. Da der Datentyp des Elements lokal deklariert wurde, gibt es auch keine andere Möglichkeit, als mit einer Elementreferenz die Datenstruktur von an anderer Stelle wiederzuverwenden. In Java gibt es diese Unterscheidungsmöglichkeit nicht. Für eine Variablendeklaration in Java muss immer ein eindeutiger Datentyp angegeben werden, wir können nicht wie bei einer Elementreferenz in XML einfach sagen Variable x hat den gleichen Datentyp wie y. Wenn wir trotzdem die Semantik der Elementreferenz in der Abbildung Java-XML beachtet sehen wollen, können wir die entsprechenden Java-Eigenschaften mit der Annotation versehen. Die Annotation kann nur für Java-Eigenschaften vergeben werden, deren Klasse mit der Annotation versehen wurde die also auf der Wurzelebene dieses oder eines anderen Schemas auch wirklich vorkommen. Im Folgenden wollen wir ein Java-Datenmodell mit Annotationen versehen, dessen Schema ein Beispiel für eine solche Elementreferenz ist. Stellen wir uns einfach vor, ein Schema definiert ein Wurzelelement , dieses Element wird als Elementreferenz durch ein weiteres Wurzelelement wiederverwendet. Damit das Element analog zum Element A im obigen Schema mit einer anonymen Typdeklaration versehen wird, markieren wir die Java-Klasse sowohl mit der Annotation als auch mit der Annotation mit einem leeren Parameter .
235
6 Java zu XML-Schema
Die Java-Klasse , die einer Instanz des Elements entsprechen soll, versehen wir nun ebenfalls mit den gleichen Annotationen, mit denen wir die Klasse markiert haben.
Die Eigenschaft der Klasse versehen wir hier mit der Annotation diese Eigenschaft sollte nun im XML-Schema als Elementreferenz abgebildet werden. Wenn wir uns nun das Schema zu den beiden Java-Klassen erzeugen lassen wir brauchen hierbei dem nur die Java-Klasse zu übergeben so erhalten wir das folgende Schema:
Der Aufbau dieses Schemas ist nun dem Beispiel aus dem Anfang des Abschnitts ähnlich, wobei dem Element , dem Element entspricht.
236
6.2 Einfache Elementkonfigurationen Zusammenfassung Mit der Annotation kann eingestellt werden, dass ein Unterelement als Elementreferenz deklariert wird. kann nur auf Java-Eigenschaften angewendet werden, deren Daten-
typ an ein XML-Wurzelelement gebunden ist.
6.2.10 Namespace konfigurieren Wenn wir uns die bisherigen Beispiele alle ansehen, so werden wir einen erstaunlichen Mangel an Namespace-Deklarationen feststellen. Alle generierten Schemas wurden bis dato ohne definiert. Das ist in einer Umgebung, in der mehrere XMLFormate für den Datenaustausch verwendet werden, nicht die optimale Lösung. Es entspräche der Programmierung von Java-Klassen ohne Pakete stellen Sie sich vor, wir würden alle Klassen einer Anwendung im Standardpaket ablegen schnell würden wir den Überblick verlieren. baut intern XML-Namespaces auf. Dabei können auf fast allen Ebenen in-
nerhalb des Java-Datenmodells Elemente über Annotationen in den einen oder anderen Namespace hineindefiniert werden. Vorbereitung: Mehrere Schemas auf der Konsole ausgeben Jeder Namespace in einem entspricht beim Generieren der Schemas einem eigenen XML-Schema. Wenn wir also über Annotationen mehrere Namespaces in einem aufbauen, müssen wir dieses bei der Implementierung der Schnittstelle beachten. Wir wollen daher zunächst ausgehend vom eine -Implementierung einführen, die mit der Generierung von mehreren Schemas durch die Methode zurechtkommt. Wir werden diese Klasse zwar nur ausnahmsweise in einem Listing in diesem Buch verwenden, sie finden diese Klasse jedoch oft in den Quelltexten zu diesem Buch wieder. Das wichtigste Element der Klasse ist die -Instanz , in der die Zuordnung von Namespace-Namen und Schemaausgabe gespeichert wird. Diese wird bei jedem Aufruf von um einen weiteren Eintrag erweitert und sollte nach Abschluss der Methode alle generierten Schemas enthalten.
237
6 Java zu XML-Schema
Rund um ist eine Reihe von Hilfsmethoden implementiert, die zum einen alle generierten Namespaces zurückliefert, zum anderen zu einem Namespace das Schema und dessen . Die identifiziert dabei eine systemspezifische Dateiressource, die den Speicherort der Datei beschreibt. Wir übernehmen hier die jeweils durch die JAXB-Implementierung vorgeschlagene . Nach Spezifikation wird hierbei für das erste Schema der Name schema1.xsd übergeben. Für weitere Schemas wird aufsteigend die Nummer um jeweils eins erhöht: schema2.xsd, schema3.xsd etc. Über können wir uns nun für beliebige -Instanzen alle darin definierten Namespaces und Schemas ausgeben lassen.
238
6.2 Einfache Elementkonfigurationen 6.2.10.1 Namespace definieren Namespace-Informationen können auf verschiedenen Ebenen in einem Java-Datenmodell angegeben werden. Auf der Ebene eines Java-Pakets, auf der Ebene der einzelnen JavaKlasse und auf der Ebene des einzelnen Felds bzw. der Java Bean-konformen Eigenschaft. Namespace auf Java-Paketebene angeben Die einfachste Art und Weise, einen Namespace zu definieren, geht über die Annotation auf Paketebene. Mit dem Parameter der Annotation kann für ein Paket insgesamt ein Namespace angegeben werden. Damit werden die XMLBindungen aller Klassen, die in diesem Paket liegen, dem angegebenen Namespace zugeordnet. Hierzu legen wir eine Datei package-info.java in dem Ordner des Java-Pakets an. Diese Datei kann nun Annotationen und Kommentare enthalten, die sich auf das Paket beziehen, eben auch die Annotation . Da eine solche Paketdeklaration keine Anweisungen unterstützt, verwenden wir in dieser Datei vollqualifizierte Namen.
Legen wir in diesem Paket nun eine Klasse an, so wird deren Bindung an ein XML-Element oder einen XML-Datentyp in dem Schema des Namespace http://jaxb.transparent/foo aufgenommen.
Das Schema enthält nun die entsprechende Angabe eines sowie die lement- und Datentypbindung von :
239
6 Java zu XML-Schema
Das durch die JAXB-Implementierung erzeugte Schema ist vielleicht etwas expliziter und umständlicher als das gleiche Schema, das wir manuell anlegen würden. So würden wir vielleicht die Namespace-Deklaration von in der Elementdeklaration von auf Schemaebene anlegen. Es wird aller Wahrscheinlichkeit je nach JAXBImplementierung und Schema-Generator hier zu im Detail unterschiedlich formulierten, wenn auch gleichbedeutenden Schemas kommen. Namespace-Informationen auf Klassenebene hinzufügen Für jede Java-Klasse, oder präziser, für jede XML-Bindung, die sich aus dieser Klasse ableiten lässt, können Ziel-Namespaces angegeben werden. So kann für eine Wurzelelementbindung der Klasse ein Namespace angegeben werden, aber auch für die XMLTypbindung. Es können hier auch unterschiedliche Namespaces angegeben werden. Für eine Wurzelelementbindung mit der Annotation kann mit deren Parameter der Namespace angegeben werden, in den dieses Wurzelelement aufgenommen wird. Den gleichen Parameter bietet die Annotation , mit der die XML-Typbindung eingestellt werden kann. Werden auf Klassenebene keine Vorgaben bezüglich der Namespaces gemacht, so gelten Einstellungen, die auf Paketebene vorgenommen wurden. In der folgenden Klasse konfigurieren wir für die Wurzelelementbindung den Ziel-Namespace http://jaxb.transparent/foo, für den Typ jedoch den ZielNamespace http://jaxb.transparen/foo2.
240
6.2 Einfache Elementkonfigurationen Aus diesem Typ werden nun gleich zwei Schemas abgeleitet, das erste Schema enthält die -Deklaration des Typs , das zweite Schema enthält eine Wurzelelementdeklaration, die ein Element von diesem Typ anlegt. Zunächst das erste Schema, das den XML-Typ enthält:
Wie erwartet wird hier auf den Wert http://jaxb.transparent/foo2 gesetzt. Etwas komplexer nun das zweite Schema, das auf einen Typ aus dem ersten Schema verweist. Dieses Schema enthält nun eine -Deklaration, die das erste Schema importiert.
Namespaces für Java-Felder und -Eigenschaften festlegen Auch für Bindungen, die auf Feld- und Eigenschaftsebene definiert werden, können unter Umständen Namespace-Informationen festgelegt werden. Dieses sind im Wesentlichen die Attributbindung per und die Elementbindung über die Annotation . Beide Annotationen haben einen Parameter , über den der Namespace angegeben werden kann, in dem das Element bzw. das Attribut aufgenommen werden soll. Wurden auf der Ebene der Java-Eigenschaften keine Einstellungen bezüglich der Namespaces gemacht, so gelten Einstellungen, die auf Klassenebene konfiguriert wurden. Werden auf der Klassenebene keine gefunden, so sucht die JAXB-Implementierung bei den Paketdeklarationen nach Namespace-Konfigurationen.
241
6 Java zu XML-Schema Legen wir also eine Klasse an, in der wir ein Feld mit der Annotation versehen, andere mit der Annotation . Den Annotationen übergeben wir unterschiedliche Namespaces. Nicht unmittelbar im folgenden Quelltext ersichtlich, aber nichtsdestotrotz wirksam, ist die für das Paket vorgenommene Konfiguration des Ziel-Namespace http://jaxb.transparent/foo mit der Annotation hier landen also alle XML-Bindungen, die im folgenden Quelltext keine eigenen Namespaces ins Spiel bringen.
Wenn wir nun für die Klasse die Methode von anwenden, so erhalten wir drei Schemas. Ein Schema mit dem ZielNamespace http://jaxb.transparent/foo, in dem das Wurzelelement und der XML-Typ zur Klasse definiert sind und das wiederum die beiden anderen Schemas mit den Namespaces http://jaxb.transparent/foo/name http://jaxb.transparent/foo/ birthdate importiert und verwendet:
242
6.3 Listen und Elementmischungen Die beiden Elementbindungen der Java-Felder und werden gemäß ihrer Konfiguration mit in ein Schema mit dem Ziel-Namespace http://jaxb.transparent/foo/name aufgenommen:
Das dritte und letzte Schema enthält nun allein die Deklaration des Attributs .
Zusammenfassung Ein führt intern eine Liste von Namespaces, jeder Namespace entspricht dabei einem eigenen Schema. Geben wir dem Java-Modell keine Namespace-Informationen durch Annotationen mit, so landen die gebundenen XML-Elemente und -Typen im Standard-Namespace das sollte in einer produktiven Anwendung vermieden werden. Die Annotationen , , , und bieten einen Parameter , mit dem der Ziel-Namespace für die jeweilige XML-Bindung konfiguriert werden kann. Namespace-Angaben auf Feldebene überschreiben Angaben auf Klassenebene, Angaben auf Klassenebene überschreiben Angaben auf Paketebene.
6.3
Listen und Elementmischungen In diesem Abschnitt gehen wir auf fortgeschrittene Konfigurationsmöglichkeiten der XML-Bindung ein. Im Mittelpunkt steht hier die Bindung von Java-Listen und -Arrays an entsprechende Elementsequenzen oder Elementmischungen. Hierbei gibt es zwei verschiedene Typen von Mischungen: zum einen eine mit deklarierte Menge von unterschiedlichen Elementen, zum anderen eine mit dem Attribut deklarierte Mischung aus Text und Elementen. Hier gibt es einige Unterschiede zwischen der Java-Sicht und der XML-Welt, die mit den entsprechenden Annotationen zusammengebracht werden können. Hier gilt es beispielsweise, zwischen der Bindung an eine Elementdeklaration oder einer verschachtelten
243
6 Java zu XML-Schema /-Deklaration zu unterscheiden; hier will eine Mischung mehrerer Ele-
mente und Textknoten auf eine Java-Liste mit sinnvollem Datentyp abgebildet werden. In diesem Abschnitt werden wir auf die Verwendung der folgenden Annotationen eingehen: @XmlList: Bindet einen Java-Array oder eine Java-Liste an eine verschachtelte -Deklaration. @XmlElements: Bindet ein Java-Feld an mehrere in einer -Deklaration verschachtelte Elemente. @XmlElementRefs: Wie , allerdings auf Elementreferenzen bezogen. @XmlElementWrapper: Erzeugt ein zusätzliches Element, in dem eine Sequenz von Unterelementen zusammengefasst werden könnte. @XmlMixed: Konfiguriert die Bindung eines Java-Felds an eine mit dem Schemaattribut definierte Mischung von Text und Elementen.
6.3.1
Der Standardmodus für Java-Arrays und Java-Listen
Java-Arrays und Java-Listen werden vom Prinzip her gleich behandelt wie deren Komponentendatentyp. Wird also eine solche Java-Eigenschaft an XML gebunden, so wird in der -Deklaration wie bisher ein entsprechendes Unterelement angelegt. Einziger Unterschied: Für das Unterelement wird nun das Attribut auf gesetzt das Element darf sich also an dieser Stelle mehrfach wiederholen. Beim Lesen entsprechender XML-Dokumente werden die Elemente dann von der JAXB-Implementierung in dem Array bzw. in der Liste gespeichert. Arrays und Listen mit Basisdatentypen Der einfachste Fall sind Java-Arrays und -Listen, die auf einem Datentyp basieren, der sich in einen der Schemadatentypen abbilden lässt, also beispielsweise String oder Integer. Im folgenden Beispiel haben wir eine Klasse Customer1 mit zwei solchen Eigenschaften versehen:
244
6.3 Listen und Elementmischungen Für beide Java-Felder wird nun eine Elementdeklaration mit dem entsprechenden Basistyp bzw. angelegt, beide Elementdeklarationen werden mit dem Attribut mit dem Wert versehen.
Etwas unglücklich an dieser Stelle ist, dass der Name der Java-Eigenschaft, hier also , als Name für das einzelne Attribut übernommen wird. Hier unterscheiden sich zwischen Java und XML die Ansätze, um Namen für Elemente und Felder zu vergeben: Während in Java eine Liste in der Regel von vornherein einen Namen erhält, der eine Mehrzahl signalisiert, hier , erhält in XML das Element einen einzahligen Namen; eine Mehrzahl entsteht erst implizit, wenn mehrere Elemente definiert wurden. Um hier stilgetreu zu bleiben, sollte die Namensbindung in einem solchen Fall manuell angepasst werden, im obigen Fall würde dass dann wie folgt aussehen:
Das Listing ist auch gleich ein gutes Beispiel, wie die bekannten Annotationen auch für Listen und Arrays angewendet werden können. Im folgenden Beispiel verwenden wir nun die Klasse , um einen zu initialisieren. Mit diesem bzw. dessen lesen wir dann eine Instanz des XML-Formats ein und überprüfen, ob alle Informationen ohne Verluste bertragen wurden.
245
6 Java zu XML-Schema
Arrays und Listen mit komplexen Datentypen Wurde das Java-Feld auf der Basis eines Datentyps deklariert, der sich nicht auf einen der XML-Basisdatentypen abbilden lässt, also typischerweise eine Java Bean-Klasse, so wird der Datentyp kurzerhand an einen Schemadatentyp gebunden. Das Feld selbst wird zu einer Elementdeklaration mit entsprechend gesetztem -Attribut. Im folgenden Datentyp haben wir eine Liste definiert, die mit dem Datentyp parametrisiert wurde:
246
6.3 Listen und Elementmischungen Hier wird das Java-Feld an eine XML-Elementdeklaration gebunden deren Datentyp wird nun wie bei einem einfachen Feld ausgewertet und an einen XML-Datentyp gebunden. Wenn wir uns das Schema zu der Klasse generieren lassen, so sieht dieses etwa wie folgt aus:
Assoziative Arrays a.k.a. Map Des kleinen Mannes Speicherobjekt der Wahl: eine der zahllosen Implementierungen von . Es gibt sie als , , , es gibt sie als , als und auch als . Allen gemeinsam ist eine Reihe von Schlüsseln, mit denen jeweils ein Wert assoziiert ist und dass ihre Flexibilität öfter auch über Gebühr genutzt wird. Die JAXB bietet auch für diesen wichtigen Datentyp eine standardmäßige Abbildung, die ohne besondere Konfiguration brauchbare Ergebnisse liefert. So wird eine Implementierung an eine Sequenz von -Elementen gebunden, die wiederum ein lement für den Schlüssel und ein Element für den Wert definiert. Der folgende Datentyp deklariert eine Eigenschaft vom Typ :
247
6 Java zu XML-Schema Jagen wir diesen Datentyp dann durch den Schema-Generator, erhalten wir von der entsprechenden -Instanz dieses Schema zurückgeliefert:
Um die Abbildung weiter zu veranschaulichen, folgt eine Instanz des oben abgedruckten Schemas. Hierbei entspricht jedes -Element nach dem Einlesen des XMLDokuments einem Eintrag in der Map-Instanz.
Zugegebenermaßen ist diese Abbildung praktisch. Sie sollte aber unseres Erachtens immer Anlass sein, darüber nachzudenken, den Informationen, die in der Map-Instanz gespeichert sind, einen eigenen Datentyp zu verleihen.
248
6.3 Listen und Elementmischungen Zusammenfassung Java-Listen und -Arrays werden an eine Sequenz von Elementen gebunden. Ist der Komponentendatentyp der Liste bzw. des Arrays komplex, wird der Datentyp ebenfalls in das Schema aufgenommen. Die üblichen Namensgebungen für Listen in XML und Java unterscheiden sich wo in Java gerne mehrzahlige Namen verwendet werden, steht in XML in der Regel die Einzahl. Die Namen sollten daher stets entsprechend konfiguriert werden. Assoziative Arrays, also Implementierung von java.util.Map, können ebenfalls ohne Konfiguration an XML gebunden werden hierbei werden für jeden Eintrag verschachtelte entry-/key-/value-Elemente in das XML geschrieben.
6.3.2
Listen und Arrays an simpleType/list-Deklarationen binden
Eine einfache Form der Liste bietet Schema mit der verschachtelten /Deklaration. Hierbei werden dann alle Elemente der Liste einfach mit Leerzeichen getrennt. Der Basistyp der Liste muss für diese Deklarationen auf einen der SchemaBasistypen abgebildet werden können. Umgekehrt können keine komplexen Datentypen an eine -Deklaration gebunden werden. Es können hierbei sowohl Elementwerte als auch Attributwerte als eine solche Liste deklariert werden. Das folgende Dokument ist ein Beispiel für die Verwendung dieses Listentyps. Hier ist ein Attribut mit einer Liste von drei Werten definiert sowie ein lement mit drei Werten:
Wir können Listen- und Array-Felder eines Java-Datentyps mit der Annotation an eine solche Liste binden. Im folgenden Java-Datentyp stellen wir das oben abgedruckte XML-Format nach:
249
6 Java zu XML-Schema
Klar ist die Verwendung von im Fall von würden wir die Annotation hier weglassen, würde dieses Array an eine Sequenz von -Elementen gebunden werden. Warum aber ist die Annotation hervorgehoben? Ganz einfach: Weil eine Liste im Fall eines Attributs an nichts anderes als eine -Deklaration gebunden werden kann, kann hier angegeben werden, muss aber nicht. Gleiches gilt übrigens auch für auch hier wird eine Liste automatisch an eine Deklaration gebunden. Hier das Schema, das wird aus dem Datentyp generieren lassen können:
Deutlich zu sehen: die generierten, verschachtelten /-Deklarationen für das Attribut und das Element . Hier noch einmal ein Beispiel mit hier wird ebenfalls automatisch an eine -Deklaration gebunden, wenn der Komponentendatentyp der Liste sich auf einen Basisdatentyp abbilden lässt.
250
6.3 Listen und Elementmischungen Das Schema zur Java-Klasse sieht dann wie folgt aus:
Zusammenfassung Über die Annotation wird eine Liste bzw. ein Array nicht an eine Sequenz von Elementen gebunden, sondern an eine -Deklaration. Wird eine Liste mit der Annotation und versehen, so wird auf eine -Deklaration abgebildet, auch ohne dass diese zusätzlich mit markiert wurde.
6.3.3
Mehrere Elemente im gleichen Feld speichern
Eine besondere Spezialität von XML-Schema ist die -Deklaration. Für dieses Entweder-oder gibt es in Java keine Entsprechung. Es gibt in Java nicht die Möglichkeit für einen Datentyp zu formulieren, dass er entweder die Eigenschaft oder die Eigenschaft hat, aber nicht beide. Noch interessanter wird es, wenn man in die -Deklaration zusätzlich das Attribut auf setzt jetzt haben wir eine Liste, in der mehrere, eventuell ganz unterschiedliche Elemente vorkommen können. Natürlich gibt es fast nichts, was sich nicht mit den Annotationen konfigurieren ließe. Um eine Java-Eigenschaft mit der Information zu versehen, dass hier allerlei Elemente vorkommen dürfen, können wir sie mit der Annotation markieren. Die Annotation nimmt hierbei einen Array von Annotationen als Parameter auf. Es handelt sich um die gleiche Annotation, die wir im Rahmen der Elementkonfigurationen besprochen haben. Jede dieser -Annotationen beschreibt dann ein Element, das in dieser Liste vorkommen kann. Wichtige Parameter, die der Annotation hierbei übergeben werden sollten, sind und , die den Namen bzw. den Datentyp des Elements beschreiben. Lassen wir den Parameter weg, so wird der Name des Java-Felds übernommen. Unterschlagen wir eine Typangabe, so wird hier der mächtige Schematyp hinter dem Ofen hervorgeholt. Arrays von Annotationen bilden Auf der Ebene der Annotationen sind alle Arrays statisch definierbar, wenn wir also ein Array von definieren, so können wir einfach mehrere Deklarationen in einer statischen Array-Deklaration verschachteln:
251
6 Java zu XML-Schema
Diesen Array können wir nun der Annotation übergeben. Vollständig sieht die Annotation dann wie folgt aus:
6.3.3.1
@XmlElements für ein einfaches Feld anwenden
Zunächst wollen wir für ein einfaches Java-Feld definieren, dass hier mehrere XMLElemente gemäß der -Semantik gespeichert werden. Damit es mit dem Datentyp recht einfach bleibt, nehmen wir mehrere Elemente mit dem gleichen Basistyp . Diese lassen sich dann problemlos allesamt in einem Java-Feld vom Typ speichern. Im folgenden Beispiel haben wir unseren geliebten Kunden mit einer Eigenschaft versehen. Diese kann nun entweder den Wert des Elements , den Wert des Elements oder des Elements enthalten.
Das Schema zu diesem Datentyp sieht bereite sehr vielversprechend aus, wird in diesem tatsächlich wie erwartet eine -Deklaration generiert, die eines der drei Elemente an dieser Stelle erlaubt.
252
6.3 Listen und Elementmischungen
Wenn wir allerdings diesen Datentyp einsetzen, stellen wir fest, dass wir zwar die Information in den Elementen lesen können, hier 4711, aber nicht mehr herausfinden können, ob es sich um ein Element vom Typ , oder gehandelt hat. Es geht sogar noch weiter: Wenn wir eine Instanz von der obigen -Klasse marshallen, so weiß nicht einmal mehr die JAXB-Implementierung, in welches Element die Information aus dem Java-Feld gespeichert werden soll die Information wird in das erste, beste Element verpackt:
253
6 Java zu XML-Schema
Die Ausgabe, die der Aufruf von mit dem Ziel erzeugt, zeigt den Informationsverlust bei dieser Art von Bindung aus wurde nach Prinzip Zufall :
Wir benötigen also einen Träger für die Information, um welches Element es sich handelt. @XmlElements in Java auseinanderhalten, Teil 1 Die Lösung, um mehrere Elemente einer choice-Deklaration auseinanderzuhalten, ist es, jedem Element einen eigenen Datentyp zu geben. Sowohl die JAXB-Implementierung als auch wir als Verwender der Klasse können dann ermitteln, welcher Wert welchem Element zugeordnet werden kann. Wir können nun die Java-Eigenschaft des oben abgedruckten Typs zu einer -Instanz machen; jetzt können dort allerlei Objekte gespeichert werden. Geben wir dann den drei Elementen , und drei unterschiedliche Datentypen: , und , so erhalten wir die folgende Java-Klasse :
Jetzt können wir diesen Datentyp einsetzen und zwischen den Elementen , und perfekt unterscheiden:
254
6.3 Listen und Elementmischungen
Nun, der Begriff perfekt trifft für das obige Beispiel eher im Vertriebs- oder Marketingsinne zu. Aber zumindest die JAXB-Implementierung kommt mit dieser Lösung bereits sehr gut zurecht und kann die Elemente beim Marshalling jetzt schön auseinanderhalten, wie wir in der folgend abgedruckten Ausgabe des obigen Programms erkennen:
Noch mehr perfekt wäre eine Lösung, in der auch wir als Programmierer die Elemente , und unterscheiden können, ohne hier in einer Typzu-Element-Tabelle nachschlagen zu müssen, die zudem durch die Anzahl der verfügbaren Basisdatentypen schnell an ihre Grenzen gerät.
255
6 Java zu XML-Schema @XmlElements in Java auseinanderhalten, Teil 2 Bemühen wir also die Mittel der Objektorientierung für dieses Beispiel. Wir könnten der Java-Eigenschaft den Datentyp geben und von diesem nun drei Ableitungen formulieren: , und . Jetzt wären wir in der Lage, programmatisch zwischen den drei Elementen zu unterscheiden und eines dieser drei Elemente, analog zur -Deklaration im XML-Schema, in der Eigenschaft zu speichern. Zunächst also brauchen wir den Datentyp . Da die Ableitungen von zunächst keine individuellen Unterschiede haben, vergeben wir einfach auf der Ebene des Typs eine String-Eigenschaft , die wir mit der Annotation versehen. Zusätzlich gönnen wir uns hier den Luxus eines Konstruktors mit StringParameter, der den späteren Einsatz der Klassen besonders offensichtlich praktisch machen wird.
Die drei Ableitungen , und sind für unser Beispiel dann denkbar trivial:
bzw.
256
6.3 Listen und Elementmischungen
und als letzter Typ noch :
Mit diesem Datenmodell können wir nun programmatisch zwischen den drei Elementen unterscheiden. Allen drei Elementen gemeinsam ist ein Inhalt vom Typ . Legen wir also einen Datentyp an, in dem wir die neuen Klassen gleich verwenden.
Wenn wir diese Klasse nun in einem Java-Programm verwenden, sehen wir sofort die Vorteile dieser Lösung:
257
6 Java zu XML-Schema
Obwohl dieses Beispiel mit weniger Kommentaren versehen ist als das vorherige, so ist es doch deutlich aussagekräftiger und leicht erweiterbar. Schema ist nicht immer gleich Schema Ein sehr interessanter Aspekt dieses speziellen Beispiels ist das durch die JAXBImplementierung generierte Schema für den Datentyp . Dieses bildet nämlich sehr aufwendig die Hierarchie von , , und ab, wie in dem folgenden Schema schön zu sehen ist:
258
6.3 Listen und Elementmischungen
Nun sind wir aber am Anfang von einem einfachen ausgegangen, in dem die Elementdeklarationen , und den Schematyp haben. Zur Erinnerung: Die entsprechende Deklaration sah sehr einfach und wie folgt aus:
Nun könnte man die beiden Schemas vergleichen und zum Schluss kommen, dass die XML-Formate nicht miteinander kompatibel sind dem ist glücklicherweise nicht so. Denn wenn wir die Typen aus dem ersteren, komplizierten Schema zusammenfalten, so stellen wir fest, dass sich diese eindeutig auf abbilden lassen. Das ganze Drumherum mit , und wird erst relevant, wenn die Datentypen sich tatsächlich unterscheiden in diesem Fall sind dann XML-Dokumente des einen Formats in dem anderen Format eventuell nicht mehr gültig. Bis dahin sind die beiden Schemas zwar nicht gleich, aber den XML-Instanzen kann man diese Unterschiede nicht ansehen. 6.3.3.2
@XmlElements und Listen
Grundsätzlich verhält sich die Annotation für eine Liste oder einen Array gleich wie bei einem einfachen Element. Der wesentliche Unterschied besteht darin, dass für die -Deklaration an dieser Stelle das Attribut mit bestückt wird. Ziehen wir für das Beispiel unseren Lieblingsdatentyp in einer neuen Variante heran. Im vorigen Beispiel haben wir jedem über die Eigenschaft ge-
259
6 Java zu XML-Schema nau eine Instanz von , bzw. gegönnt. Im folgenden Quelltext machen wir aus dieser Eigenschaft nun eine Liste:
Das Schema zum Datentyp sieht nun fast haargenau so aus wie das bereits gezeigte Schema zum Datentyp . Einziger relevanter Unterschied: Das Attribut der -Deklaration ist, wie bereits angedeutet, nun auf gesetzt.
260
6.3 Listen und Elementmischungen Wie hier beschrieben funktioniert auch die Verwendung von Basisdatentypen in der -Deklaration. Hier werden dann die Elemente als Einträge vom entsprechenden Typ (, , ) in der Liste gespeichert. Nehmen wir also das Beispiel aus , in dem wir für die Eigenschaft ein einfaches Feld vom Typ wählen, und passen es insofern an, als dass wir für die Eigenschaft den Typ wie unten dargestellt verwenden:
Auch hier enthält das Schema nun in der -Deklaration das Attribut mit gesetztem Wert .
Lassen wir die JAXB-Implementierung Instanzen von diesem Typ in XML umwandeln, so werden in der Liste vorhandene -Instanzen in -Elemente, Instanzen in -Elemente und -Instanzen in -Elemente gespeichert. 6.3.3.3
Mehrere Elemente per @XmlElementRefs referenzieren
Statt Elemente an eine Typdeklaration zu binden, bietet Schema die Möglichkeit, auf andere Referenzen zu verweisen. Eine solche Bindung kann mit der Annotation konfiguriert werden. Nun können mehrere solcher Elementreferenzen über die Annotation zusammengefasst werden. Diese werden dann, wie per , in einer -Deklaration verschachtelt. Bedingung ist hierbei, dass die referenzierten Elemente auf der Wurzel eines Schemas deklariert sind. Als Vorbereitung für ein Beispiel der Anwendung von benötigen wir also sinnvollerweise mindestens zwei Wurzelelemente, auf die wir per verweisen können. Nehmen wir also die Datentypen und und modifizieren diese mit der Annotation insoweit, dass diese als eigenständige Elemente zur Verfügung stehen. Hier zunächst der Datentyp :
261
6 Java zu XML-Schema
Analog dazu der Datentyp :
Jetzt haben wir zwei Elemente zur Verfügung, auf die wir per verweisen können, um im folgenden Beispiel die Eigenschaft des Datentyps an eine Liste von - und -Elementen zu binden:
Die Eigenschaft wird hier auf eine -Deklaration abgebildet, die entweder das Element oder das Element enthalten darf. Dabei werden die Wurzelelemente im Schema direkt über das Attribut der Elementdeklaration referenziert:
262
6.3 Listen und Elementmischungen
Zusammenfassung Mit der Annotation können mehrere XML-Elemente mit der Semantik einer -Deklaration an ein Java-Feld gebunden werden. Die Datentypen aller in der Annotation verwendeten Elemente müssen dem Java-Feld zugewiesen werden können. Die Annotation kann gleichermaßen für Listen als auch für normale Felder angewendet werden. Haben mehrere XML-Elemente innerhalb einer -Deklaration den gleichen Datentyp, so geht beim Lesen von XML die Information verloren, von welchem Element ein bestimmter Wert beigesteuert wurde. Beim Schreiben kann die JAXBImplementierung nur raten, welchem Element der Wert zugeordnet werden soll. Per können die Unterelemente als Elementreferenzen eingebunden werden.
6.3.4
Elementlisten verpacken
Binden wir eine Java-Liste an eine Sequenz von XML-Elementen, so ist der Zusammenhang der Elemente durch die Listen-Instanz in Java deutlich sichtbar, in XML jedoch nicht. Gerade bei einer Java-Klasse, die mehrere Listeneigenschaften enthält, wird das XML-Dokument schnell unübersichtlich. Angenommen, wir legen eine Java-Klasse mit zwei Listeneigenschaften an:
263
6 Java zu XML-Schema
Wenn wir nun aus einer Instanz dieser Klasse ein XML-Dokument erstellen, so stehen die Elemente und gleichberechtigt nebeneinander.
Sowohl von den Namen als auch von der Anordnung her ist dieses XML-Format nicht besonders übersichtlich die Namen können wir über die Annotation anpassen, aber was ist mit der Anordnung der Elemente selbst? Hier bietet die Annotation die Möglichkeit, die Elemente in einem zusätzlichen, künstlichen Element zusammenzufassen. Die Annotation besitzt dabei einen Parameter , mit dem der Name dieses Elements definiert werden kann. Wenn wir die Klasse nun so modifizieren, dass erstens die Elemente einzahlige Namen erhalten und zweitens die Elemente der Listen in einem solchen künstlichen Element zusammengefasst werden, sieht diese wie folgt aus:
In dem Schema zu diesem Typ finden wir nun die Sequenz von - und Elementen unter einem - bzw. -Element verschachtelt.
264
6.3 Listen und Elementmischungen
Instanzen dieses XML-Schemas sind jetzt vielleicht etwas übersichtlicher als zuvor:
Wenn wir dieses Dokument in einer Baumsicht betrachten, können wir nun zusätzlich auf der Ebene von und die angezeigten Elemente aus- bzw. einblenden, wie im folgenden Screenshot dargestellt. Zusammenfassung Über die Annotation können Elementsequenzen, die sich aus einer Java-Liste ergeben, in einem zusätzlichen Element verschachtelt werden.
265
6 Java zu XML-Schema
Abbildung 6.1 Besser strukturierte XML-Darstellung dank @XmlElementWrapper
6.3.5
Mischungen von Elementen und Text
In XML-Schema kann für Typdeklarationen über das Attribut festgelegt werden, dass zwischen den Elementen normaler Fließtext stehen darf. Dieses Verhalten ist insbesondere für Formate interessant, bei denen die XML-Elemente das Layout des Fließtextes beeinflussen, wie etwa bei HTML oder DocBook, oder in denen die XML-Elemente inhaltlichen Metainformationen entsprechen, die beispielsweise indizierte Suchbegriffe markieren oder Inhalte miteinander verknüpfen. Angenommen, wir haben in unserer Anwendung ein Format, in dem wir Nachrichten mit indizierten Suchbegriffen und mit Querverweisen versehen können möchten. Hierzu fassen wir die Nachricht in einem Element zusammen. Überall innerhalb der Nachricht sollen nun Suchbegriff mit dem Element und Kreuzverweise mit dem Element markiert werden können. Die Instanz eines solchen Formats hätte dann vielleicht die folgende Form:
Ein solches Format können wir mit der Annotation an ein Java-Modell binden. Die Annotation markiert für eine Liste oder ein Array, dass hier eine Mischung von Elementen und Text vorliegt. Die Annotation hat auf die Klasse eine ähnliche Wirkung wie die Annotation die Bindung der anderen Java-Felder kann sich durch die Annotation ändern. Zur Annotation kann man über die Annotation angeben, welche Elemente innerhalb des Textes vorkommen können. Die Verwendung von Elementreferenzen ist hierbei eine Beschränkung, die im Rahmen der JAXB-Spezifikation
266
6.3 Listen und Elementmischungen vereinbart wurde, die aber innerhalb von XML-Schema nicht besteht. In XML-Schema können auch normale Unterelemente innerhalb eines gemischten Datentyps deklariert werden. Wenn wir also die Annotation verwenden möchten, müssen wir zuvor alle Elemente, die wir mit Text mischen wollen, per als Wurzelelemente deklarieren. Diese Elemente können wir dann per zusammenfassen und so an den gemischten Typ binden. Hinweis
Die Abbildung von Datentypen, die eine Mischung von Elementen und Fließtext erlauben, ist durch die Beschränkung auf Elementreferenzen seitens der JAXB 2.0 nicht immer bidirektional. Kompliziert verschachtelte, gemischte Datentypen können hier eventuell nicht vollständig aufeinander abgebildet werden. Die saubere Unterstützung des Attributs in -Deklarationen ist eine der Baustellen der Spezifikation.
Für das Beispiel mit dem Element bedeutet das, dass wir hier für die Elemente und Java-Klassen anlegen, die wir mit der Annotation versehen. Das wäre zum einen die Klasse :
Und dann noch die Klasse . Beide Klassen erhalten eine Eigenschaft , die per an den Inhalt der Elemente gebunden wird.
Diese beiden Elemente können nun durch die JAXB-Implementierung innerhalb einer Mischung aus Element und Text verwendet werden. Diese Mischung deklarieren wir in der Klasse hier legen wir eine Eigenschaft vom Typ an, in welche die Element- und Textinstanzen abgelegt werden sollen, und versehen diese mit der für unser Beispiel adäquaten Kombination aus und .
267
6 Java zu XML-Schema
Aus dieser Klasse leitet die JAXB-Implementierung das folgende Schema ab die -Deklaration für das Element enthält nun das Attribut mit dem Wert , die beiden Elemente und können beliebig oft innerhalb des -Elements auftreten.
In der Liste landen nun Instanzen von , von oder . Die Reihenfolge der Instanzen entspricht dabei der Reihenfolge im Fließtext. Am Anfang dieses Abschnittes haben wir ein kleines Beispiel-XML eingeführt, dieses Beispiel unmarshallen wir im folgenden Quelltext und zerlegen es programmatisch in seine Einzelteile:
268
6.3 Listen und Elementmischungen
Zusammenfassung Mit der Annotation können wir eine Liste an eine Mischung aus Text und Elementen binden. Die Elemente, die in dieser Liste vorkommen dürfen, werden über die Annotation definiert. Insgesamt ist die Unterstützung für das Attribut in einer XMLDatentypdeklaration durch die JAXB 2.0 nicht ganz astrein spezifiziert, hier sind unangenehme Überraschungen möglich.
269
6 Java zu XML-Schema
6.4
Enumerationen Was haben wir uns noch in den Java-Versionen vor Tiger mit Konstanten, die irgendwie zusammengehören, schwergetan! Etwas Erleichterung brachte das Typesafe Enum Pattern, das einen mehr oder weniger standardisierten Weg bot, auch in Java so etwas wie die gute, alte aus der C-/C++-Welt zu haben. In Java 5 haben wir nun zwei wesentliche Möglichkeiten, eine Enumeration zu formulieren: mit oder ohne Werte. In der einfachen Variante definieren wir für die Elemente der Enumeration keine besonderen Werte. In der etwas komplexeren Variante der Enumeration geben wir ihrer Elemente einen bestimmten Wert. Zunächst ein Beispiel für eine einfache Enumeration, bei der die Elemente keine besonderen Werte erhalten:
Hier hat die Enumeration nun zwei Elemente, die als Werte unmittelbar referenziert und verglichen werden können:
Die Spezifikation des Enumerationstyps sieht darüber hinaus vor, dass für die einzelnen Elemente einer Enumeration auch eigene Werte angegeben werden können diese sind dann zwar für die Verwendung der Enumeration nicht direkt relevant, können aber für die Ein- und Ausgabe genutzt werden. Im folgenden Beispiel definieren wir hierbei eine solche Enumeration, in der jedem ihrer Elemente ein -Wert zugeordnet werden muss.
270
6.4 Enumerationen
Im obigen Beispiel wird nun für jedes Element der Enumeration der Konstruktor der Enumerationsklasse aufgerufen es obliegt dann dem Entwickler der Klasse, mit diesem Wert etwas Sinnvolles anzufangen. Wir werden im folgenden Abschnitt auf diese beiden Typen von Enumerationen eingehen und Lösungen für deren Bindung an XML-Enumerationen bieten. Hinweis
Es gibt keine direkte Unterstützung des Typesafe Enum Patterns durch die JAXB Enumerationen, die auf diesem Muster basieren, aber kein Java 5 Enum-Typ sind, müssen daher für eine saubere Bindung zunächst zu echten Enumerationen umformuliert werden.
6.4.1
Enumerationen an XML binden: Standardverhalten
Es gibt die Annotation , mit der eine Enumeration explizit an eine XMLEnumeration gebunden werden kann. Analog zu werden Enumerationsklassen auch ohne diese Annotation an XML-Enumerationen gebunden, die Angabe der Annotation ist daher zunächst optional. Eine Java-Enumeration wird hierbei an eine -Deklaration mit einer verschachtelten Kombination von und gebunden. Die Namen der Elemente der Enumeration in Java werden hierbei für die -Elemente übernommen. Basisdatentyp der XML-Enumeration ist standardmäßig . Angenommen, wir haben die folgende Beispielklasse , die auf eine Enumeration verweist:
Die Enumerationsklasse entspricht hierbei der oben als Beispiel abgedruckten Enumeration:
271
6 Java zu XML-Schema
Übergeben wir die Klasse nun einer -Instanz, so wird das Unterelement des Wurzelelementes an eine auf dem Datentyp basierende XML-Enumeration mit den Werten CORPORATE und PRIVATE gebunden. Lassen wir uns das Schema zu der Klasse generieren, so sieht dieses wie folgt aus:
Zusammenfassung Java-Enumerationsklassen werden standardmäßig an eine -Deklaration mit verschachtelter /-Deklaration gebunden. Die Namen der Enumerationselemente werden aus Java in die XML-Enumeration übernommen. Basisdatentyp der XML-Enumeration ist .
6.4.2
Bindung von Enumerationen konfigurieren
Wir können die Bindung von Java-Enumerationsklassen an XML in zweierlei Weise konfigurieren: Wir können für die Enumeration insgesamt einen Basisdatentyp angeben, und wir können für die einzelnen Elemente einer Enumeration Werte angeben, die in der XMLRepräsentation der Enumeration verwendet werden sollen. In der Regel gehen beide Konfigurationen Hand in Hand, da ein anderer Basisdatentyp in der Regel auch bedeutet, dass hier die Namen der Elemente in Java nicht zu dem neuen XML-Basisdatentyp der Enumeration passen. Den Basisdatentyp der XML-Enumeration können wir über den Standardparameter der Annotation einstellen. Der Annotation können wir eine -Instanz überge-
272
6.4 Enumerationen ben. Für die angegebene Klasse wird der dazugehörige XML-Basisdatentyp ermittelt, dieser wird dann als Basisdatentyp für die XML-Enumeration übernommen. Um beispielsweise den Basisdatentyp der Enumeration auf einzustellen, übergeben wir der Annotation die -Instanz .
Wenn wir eine Enumerationsklasse mit dieser Annotation versehen, so scheitert die XMLBindung an einem korrupten Schema es gibt in Java keine Namen, die ohne weiteres in XML als Werte einer -basierten Enumeration übernommen werden können. Wir müssen daher den Elementen der Java-Enumeration die Information mitgeben, an welche Werte im Schema diese gebunden werden. Für ein Element einer Enumeration kann über die Annotation angegeben werden, welchem Wert in der XML-Enumeration dieses Element entspricht.
Es ist dabei möglich, aber nicht notwendig, dass hier in Java eventuell vorgegebene Werte gleichzeitig den in XML vorgegebenen Werten entsprechen:
Der oben für die XML-Repräsentation von konfigurierte Wert 69 steht (technisch) in keinem Widerspruch zu dem in Java angegebenen Wert 66. Hinweis
Wurden in Java Werte für das Enumerationselement vorgegeben, so sind diese für die Bindung an einen bestimmten Wert einer XML-Enumeration nicht relevant.
Im folgenden Beispiel stellen wir eine vollständige Konfiguration der XML-Bindung einer Enumeration dar. Hierbei haben wir in der Enumerationsklasse den Basistyp der XML-Enumeration auf gesetzt und für die einzelnen Elemente neue, -kompatible Werte angegeben.
273
6 Java zu XML-Schema
Damit wir diese Enumeration auch im Kontext eines XML-Elements verwenden können, legen wir noch folgende Klasse an, die eine Eigenschaft vom Typ der Enumeration besitzt:
Lassen wir uns nun das Schema zu der Klasse generieren, so sieht dieses wie folgt aus:
Von der ursprünglichen Java-Enumeration ist so nicht mehr viel zu sehen. Im folgenden ausführbaren Beispiel prüfen wir daher, ob die Bindung tatsächlich so funktioniert, wie wir das konfiguriert haben.
274
6.5 Eigene Typbindungen definieren
Zusammenfassung Über den Standardparameter der Annotation können wir für die Bindung an eine XML-Enumeration einen anderen Basisdatentyp für die XML-Enumeration angeben. Mit der Annotation können wir für die Elemente einer JavaEnumeration angeben, an welchen Wert in einer XML-Enumeration diese gebunden werden sollen. Auch wenn die Elemente einer Java-Enumeration per Konstruktor mit zusätzlichen Werten versehen wurden für die Bindung an die Werte einer XML-Enumeration ist nur der Name des Elements oder ein per angegebener Wert relevant.
6.5
Eigene Typbindungen definieren In manchen Fällen sind Typumwandlungen, die wir von einem Java-Modell zu einem XML-Format machen wollen, nicht über Annotationen konfigurierbar. Ein solcher Fall wäre, wenn wir beispielsweise eine Eigenschaft vom Typ auf einen komplexen XML-Typ abbilden wollen oder umgekehrt eine Zeichenkette aus einem XML-
275
6 Java zu XML-Schema Dokument an eine Instanz einer bestimmten Java-Klasse. Auch wenn sich Java und XMLTyp in ihrer Struktur so stark unterscheiden, dass eine einfache Abbildung von Eigenschaft auf Element sich nicht ohne weiteres konfigurieren lässt. Für solche Fälle bietet die JAXB-Spezifikation die Möglichkeit, einen eigenen Typadapter zu implementieren. Ein Typadapter ist hierbei eine Klasse, welche die abstrakte Klasse implementiert. Die Implementierung von wird hierbei mit zwei Datentypen parametrisiert: dem Speicherdatentyp (Value Type) und dem Bindungsdatentyp (Bound Type). Der Speicherdatentyp wird hierbei später mit den üblichen JAXB-Mechanismen an XML gebunden. Der Bindungsdatentyp entspricht dem Typ, der im Java-Modell verwendet wird und der nur mittelbar über den Speicherdatentyp an XML gebunden werden soll. Hierzu gibt die Klasse zwei Methoden und vor. Mit der Methode implementieren wir die Umwandlung einer Instanz des Bindungsdatentyps in eine Instanz des Speicherdatentyps. Die Methode beschreibt den anderen Weg, also von einer Instanz des Speicherdatentyps in eine Instanz des Bindungsdatentyps. unmarshal
Bindungsdatentyp
XmlAdapter
Speicherdatentyp
JAXB (standard)
XML-Dokument
marshal
Abbildung 6.2 Vom Bindungsdatentyp über den Speicherdatentyp zu XML
Sowohl Speicher- als auch Bindungsdatentyp sind gewöhnliche Java-Klassen der Bindungsdatentyp wird jedoch von der JAXB-Implementierung nicht unmittelbar beachtet und nicht an XML gebunden. Der Bindungsdatentyp wird vor der eigentlichen Bindung an XML immer über die Methoden von in eine Instanz des Speicherdatentyps umgewandelt. Diese Instanz wird dann mit im XML-Dokument gespeichert.
Haben wir eine Implementierung von , können wir nun Java-Eigenschaften vom Bindungsdatentyp mit der Annotation versehen. Der Annotation übergeben wir hierbei die Klasse unserer Implementierung.
276
6.5 Eigene Typbindungen definieren Angenommen, wir haben eine Implementierung von , die den Speicherdatentyp in einen Bindungsdatentyp umwandelt, dann können wir mit dieser Implementierung Java-Eigenschaften vom Typ folgendermaßen an den Bindungsdatentyp adaptieren:
6.5.1
Basisdatentyp als Speicherdatentyp
Zunächst wollen wir in einem einfachen Beispiel einen Basisdatentyp als Speicherdatentyp verwenden. Dieser soll über einen Adapter in einen komplexen Bindungsdatentyp umgewandelt werden. Das bedeutet, dass wir aus einem XML-Dokument eine Zeichenkette auslesen und diese über den Adapter in eine Instanz eines Java-Datentyps umwandeln. Nehmen wir an, wir haben das folgende XML-Dokument vorliegen, das einer Liste von Kunden entspricht.
Abgesehen davon, dass dieses Format die Möglichkeiten von XML zur Metamarkierung der einzelnen Informationen innerhalb des Elementes etwas mutwillig ignoriert, entspricht der Inhalt von konzeptionell nicht einer Zeichenkette, sondern einer Struktur mit den Informationen Vorname, Nachname, Geburtsdatum, Stadt und Land. Mit den bekannten JAXB-Annotationen kommen wir in einem solchen Fall nicht weiter die Umwandlung der Zeichenkette in eine Instanz einer entsprechenden Java-Klasse müssen wir programmatisch in Form einer Implementierung von definieren. Wir wollen die oben abgedruckten Zeichenketten in den -Elementen in unserem Beispiel in eine Instanz der folgenden Klasse umwandeln:
Diese Umwandlung definieren wir in der folgenden Implementierung von . Hierbei legen wir durch die Parametrisierung mit als Value Type und als
277
6 Java zu XML-Schema Bound Type fest, dass diese -Implementierung die Abbildung von als in XML-Dokumenten beschreibt. In der Methode beschreiben wir hierbei, wie aus einer -Instanz ein erzeugt werden soll. Die generierte Zeichenkette soll dann später in das XMLDokument geschrieben werden.
In der Methode beschreiben wir, wie aus einer Zeichenkette aus dem XMLDokument eine -Instanz angelegt werden soll. Mit dieser Implementierung können wir nun über die Annotation eine Bindung von -Objekten an Zeichenketten für Java-Eigenschaften vom Typ einstellen. Um das oben dargestellte XML-Format nachzustellen, können wir
278
6.5 Eigene Typbindungen definieren nun eine Klasse anlegen und dieser Klasse eine Liste vom Typ spendieren. Diese Liste versehen wir nun mit der Annotation .
Im folgenden ausführbaren Beispiel nehmen wir uns das oben abgedruckte XMLDokument zur Brust und prüfen, ob die Abbildung auch so funktioniert, wie wir uns das vorstellen.
279
6 Java zu XML-Schema
Wenn wir das Beispiel ausführen, erhalten wir als Konsolenausgabe genau das XMLDokument, das wir bereits in der Variablen im Quelltext angelegt haben die Abbildung funktioniert in beide Richtungen. Aus einer Zeichenkette im XML werden -Objekte in Java.
6.5.2
Komplexe Datentypen als Speicherdatentypen
Der wesentliche Unterschied zwischen der Verwendung von Basisdatentypen und komplexen Datentypen als Speicherdatentypen ist, dass komplexe Datentypen zusätzlich über Annotationen die Bindung an XML konfigurieren können. Solche Konfigurationsmöglichkeiten entfallen bei Basisdatentypen. Auf diese Möglichkeit der Konfiguration wollen wir im folgenden Abschnitt eingehen. In manchen Fällen sind Java-Datentypen aus einer Anwendung und Datentypen eines XML-Formats nicht kompatibel. So sind Informationen, die im Java-Datentyp in einer Eigenschaft zusammengefasst sind, möglicherweise im XML-Datentyp auf mehrere Elemente und Attribute verteilt. Angenommen, wir haben in unserer Anwendung eine Klasse , die nur einige wenige Informationen trägt, die für unsere Anwendung relevant sind:
Nun könnte es uns im Rahmen einer Integration von externen Daten passieren, dass wir das folgende XML-Format vorliegen haben und importieren wollen:
280
6.5 Eigene Typbindungen definieren
Einige Unterelemente von lassen sich recht einfach auf den Datentyp abbilden, so zum Beispiel das Element oder das Element . Die restlichen Elemente bedürfen einer programmatischen Konversion: Das Element mit den verschachtelten Unterelementen , und müssen wir sinnvollerweise in eine Instanz von umwandeln. Die Elemente und bzw. die Elemente und sind im Datentyp in den Eigenschaften bzw. zusammengefasst. Diese Konversion können wir in einem XML-Adapter implementieren. Doch zunächst brauchen wir unseren Speicherdatentyp, den wir an das XML binden können, diesen können wir per Hand oder mit den Mitteln des Schema-Compilers erzeugen. Wir haben die folgende Klasse als Speicherdatentyp angelegt:
Jetzt brauchen wir eine Implementierung von , die eine Instanz des Speicherdatentyps in eine Instanz von umwandelt.
281
6 Java zu XML-Schema
In der Adapterklasse ist nun die etwas asymmetrische Abbildung der Informationen in den Methoden und programmatisch festgelegt. An dieser Stelle müssen wir nun etwas tricksen. Die Annotation kann zwar auf Klassenebene gesetzt werden, wird in diesem Fall jedoch nicht für das Wurzelelement der Klasse selbst angewendet, sondern nur für Referenzen dieses Typs im JavaDatenmodell. Es reicht in diesem Beispiel also nicht, die Klasse mit den Annotationen und zu versehen das entstehende Wurzelelement zumindest würde die Klasse unmittelbar an XML binden. Zudem ist es vielleicht nicht gewünscht, die Klasse der Anwendungsdomäne nachträglich mit JAXB-Annotationen zu versehen. Um diese beiden Herausforderungen zu lösen, legen wir für unseren Import eine kleine Hilfsklasse an, die, an XML gebunden, jetzt das mit der Speicherklasse definierte Format lesen kann. Diese Hilfsklasse definiert eine Eigenschaft , diese versehen wir mit der Annotation . Als Adapterimplementierung geben wir hier die oben abgedruckte Klasse an.
282
6.5 Eigene Typbindungen definieren
In einer produktiven Umgebung würde diese Klasse den Elementen entsprechen, in die das Element eingebettet wäre. Hier fassen wir das Element in ein künstliches Element ein. Mit der Klasse können wir nun das Element in eine Instanz von umwandeln, wie der folgende Quelltext zeigt:
283
6 Java zu XML-Schema
Zusammenfassung Lassen sich XML-Datentypen und Java-Klassen nicht mit den normalen Annotationen zusammenbringen, können wir eigene Bindungen in Form einer Implementierung von programmatisch definieren. Eine Implementierung von beschreibt die Umwandlung von einem Bindungsdatentyp (Bound Type), der im Java-Modell verwendet wird, und einem Speicherdatentyp (Value Type), der an XML gebunden wird und von der JAXBImplementierung gelesen und geschrieben werden kann. Über die Annotation kann die in der Adapterimplementierung programmatisch definierte Java-XML-Bindung für passende Java-Eigenschaften konfiguriert werden.
6.6
Unbekannte Attribut- und Elementwerte binden XML-Schema ermöglicht es, über die Deklarationen und ein XMLFormat insofern erweiterbar zu gestalten, als dass an diesen Stellen beliebige Attribute und Elemente vorkommen dürfen. Dieses Verhalten können wir aus Java heraus mit den Annotationen und konfigurieren. Wie die Namen der Annotationen bereits andeuten, können mit beliebige Attribute, mit beliebige Elemente gebunden werden.
6.6.1
Wildcard-Attribute mit @XmlAnyAttribute definieren
Die Annotation kann auf einer Java-Eigenschaft vom Typ bzw. genauer vom Typ
definiert werden. In diese Eigenschaft werden dann alle Attribute gespeichert, die nicht bereits anderweitig gebunden wurden. Dabei entspricht der Schlüssel vom Typ dem Namen des Attributes zzgl. eventuell gegebener Namespace-Informationen, der Wert vom Typ dem Inhalt des Attributs.
284
6.6 Unbekannte Attribut- und Elementwerte binden Angenommen, wir wollen in einem Datentyp beliebige Attribute zulassen, so können wir in diesem Datentyp eine Eigenschaft als anlegen und diese mit der Annotation versehen:
Wenn wir uns zu diesem Datentyp das Schema generieren lassen, so finden wir hier die entsprechende -Deklaration im XML-Datentyp:
Jetzt dürfen innerhalb eines Elements beliebige Attribute stehen wenn solche frei definierten Attribute vorkommen, dann finden wir diese nach dem Unmarshalling in der Eigenschaft wieder. Um sicherzugehen, überprüfen wir das Verhalten im folgenden ausführbaren Beispiel:
285
6 Java zu XML-Schema
Wie zu erwarten, finden wir die Attributwerte für die beiden undeklarierten Attribute und nach dem Unmarshalling in der Eigenschaft wieder.
6.6.2
Wildcard-Elemente per @XmlAnyElement deklarieren
Mit der Annotation können wir Elemente, die im XML-Schema nicht vorgegeben sind, einer Eigenschaft zuordnen. Geben wir nichts weiter an, so werden vorkommende Elemente als Instanzen von gelesen und geschrieben die Eigenschaft muss dann vom Typ , oder sein. Wenn wir allerdings bei der Annotation den Parameter auf setzen, so werden Elemente, deren Typ an anderer Stelle im Schema bereits bekannt ist, automatisch an die entsprechende Java-Repräsentation gebunden. Setzen wir also auf , so muss die Eigenschaft vom Typ sein, da hier dann sowohl Instanzen als auch normale Java-Objekte erwartet werden müssen. Zunächst ein Beispiel für die Bindung an -Instanzen. Im folgenden Datentyp definieren wir eine Eigenschaft als Array von Instanzen. Diese Eigenschaft versehen wir mit der Annotation .
Im XML-Schema zur Klasse sehen wir nun die -Deklaration innerhalb des gebundenen XML-Datentyps:
286
6.6 Unbekannte Attribut- und Elementwerte binden
Im folgenden ausführbaren Beispiel unmarshallen wir eine Instanz des Elements mit zwei frei eingeführten Unterelementen und . Diese beiden Elemente werden vom Unmarshaller in dem Array der Eigenschaft abgelegt und können dort ausgewertet und modifiziert werden:
287
6 Java zu XML-Schema Die Veränderung des Elementinhaltes von von auf wird beim Marshalling nun beachtet in der Ausgabe dieses Programms ist der Wert von entsprechend .
6.6.3
Lax fischen
Wenn wir in der Annotation den Parameter auf setzen, prüft der Unmarshaller für die gefundenen Elemente, ob hier eine Java-XML-Bindung für diese Elemente besteht. Findet der Unmarshaller eine Bindung, so versucht er, das Element in seine Java-Repräsentation zu transformieren. Beim Speichern der Elemente akzeptiert der Marshaller entsprechend gebundene Java-Objekte neben den ansonsten erwarteten -Instanzen. Modifizieren wir also das obige Beispiel und setzen den Parameter der Annotation auf . Zusätzlich ändern wir den Typ der Eigenschaft auf . Herauskommt die Klasse :
Damit wir tatsächlich außer einem Element noch ein weiteres Element in unserem als Kandidaten für eine laxe Bindung zur Verfügung haben, legen wir noch eine einfache Klasse an:
Wenn jetzt inmitten der unbekannten Unterelemente ein Element vorkommen sollte, so wird dieses an seine Java-Repräsentation gebunden statt als ein anonymes Dasein zu fristen. Dieses Verhalten stellen wir im folgenden Beispiel dar:
288
6.6 Unbekannte Attribut- und Elementwerte binden
Zusammenfassung Mit der Annotation kann eine Java-Eigenschaft vom Typ an beliebige, mit der Deklaration erlaubte Elemente gebunden werden. Über kann eine Eigenschaft vom Typ beliebige, per mögliche Attribute mit deren Wert speichern. Die Einstellung der Annotation weist den Unmarshaller an, bekannte Elemente an ihre Java-Repräsentationen zu binden. Eine so markierte Eigenschaft muss dann vom Typ sein.
289
6 Java zu XML-Schema
6.7
Objektgraphen in XML abbilden Ein großer Unterschied zwischen Java und XML ist, dass in XML Daten im Normalfall echt ineinander verschachtelt sind, während in Java Daten als Objektgraphen über Referenzen entstehen. Die echte Verschachtelung von Daten ist in Java, zumindest für Instanzen von komplexen Datentypen, auch gar nicht möglich Instanzen von komplexen Datentypen müssen stets referenziert werden. Nur Instanzen von primitiven Datentypen sind fest im Speicherbereich einer Java-Klasse angelegt. In XML können Referenzen auf andere Elemente über Werte vom Schemadatentyp definiert werden. Solche Werte können dann auf Elemente im XML-Dokument verweisen, die den gleichen Wert in einem Attribut oder Element vom Schemadatentyp definieren. Im Gegensatz zu Java, wo der Datentyp einer Referenz fest definiert ist, kann über ein beliebiges Element im Dokument referenziert werden. Angenommen, wir haben einen geometrischen Graphen mit drei Knoten A, B und C, die mit drei Linien zu einem Dreieck verbunden sind, so könnten wir einen solchen Graphen mit dem folgenden XML-Dokument beschreiben:
Hierbei könnte das Attribut des Elements ein Wert vom Typ sein, die beiden Attribute , des Elements vom Typ . Jetzt verweisen die -Elemente in diesem Dokument auf die -Elemente bereits bei der Validierung des XMLDokuments fallen Verweise auf nichtexistente Elemente auf. Die Semantik der XMLReferenz ergibt sich nicht unmittelbar aus einem XML-Dokument, sondern ist durch das XML-Schema vorgegeben. Wir können über die Annotation eine Java-Eigenschaft für die Verwendung als eindeutiges, bestimmendes Merkmal, als Primärschlüssel, für diese Objektinstanz in einem XML-Dokument markieren. Es kann immer genau eine Eigenschaft pro Java-Klasse mit der Annotation versehen. Die Eigenschaft muss hierbei vom Typ sein.
Haben wir eine Klasse auf diese Weise in XML referenzierbar gemacht, können wir nun Java-Eigenschaften vom entsprechenden Typ mit der Annotation versehen.
290
6.7 Objektgraphen in XML abbilden Jetzt wird in XML nicht mehr die komplette Objektinstanz mit ihren Daten an dieser Stelle abgebildet. Es wird nur noch der Wert der mit markierten Eigenschaft als Referenz angegeben.
Im folgenden Abschnitt wollen wir ein vollständiges Beispiel für die Abbildung eines Java-Objektgraphen in ein XML-Dokument aufbauen. Angenommen, wir haben ein XML-Format, das eine Reihe von neu angelegten Bankkonten beschreibt. Zu jedem neuen Bankkonto wird im XML-Dokument auch der Kunde festgehalten, dem das neue Konto gehören soll. Potenziell kann ein Kunde mehrere Bankkonten anlegen, die Daten des Kunden sollen daher nicht in den Kontodaten verschachtelt (und somit eventuell mehrfach im XML-Dokument vorkommen), sondern als XML-Referenz angegeben werden. Eine Instanz dieses XML-Formats würde dann wie folgt aussehen:
Um das obige XML-Format in Java abzubilden, legen wir zunächst einen Datentyp an, der eine Eigenschaft besitzt. Diese versehen wir mit den Annotationen und . Jetzt kann dieser Datentyp potenziell von anderen Datentypen mit der Semantik einer XML-Referenz an XML gebunden werden. Dieser Datentyp könnte wie folgt aussehen:
291
6 Java zu XML-Schema
Dem Datentyp für das neue Bankkonto, , geben wir nun eine Eigenschaft vom Typ der obigen Klasse . Diese Eigenschaft versehen wir mit der Annotation . Jetzt werden hier gespeicherte -Instanzen als XMLReferenzen gebunden.
Jetzt brauchen wir natürlich noch ein Dokument, in dem mehrere Konten und Kunden zusammenkommen und sich gegenseitig referenzieren können. Dazu legen wir eine Klasse an, welche die im Beispiel-XML angedeuteten Listen und als Eigenschaften an XML-Elemente bindet.
In der Klasse brauchen wir auf die referenzielle Beziehung zwischen und nicht einzugehen wir müssen jedoch bei ihrer Verwendung darauf achten, dass alle -Instanzen, die in der Liste von -Objekten referenziert werden, auch wirklich in der Liste vorkommen. Es obliegt hier dem Ent-
292
6.7 Objektgraphen in XML abbilden wickler die referenzielle Integrität des resultierenden XML-Dokuments sicherzustellen. Vergessen wir an dieser Stelle einen oder mehrere referenzierte -Objekte, so ist das erzeugte XML-Dokument ungültig. Wenn wir uns nun für die Klasse das Schema generieren lassen, so sehen wir die im Schema definierten Attribute vom Typ bzw. :
Hinweis
Wenn in einem XML-Schema mehrere Typen ein Attribut oder ein Element vom Typ ID deklarieren, so können durch einen IDREF-Wert alle Instanzen von diesen Typen referenziert werden. In XML muss die Referenz keinen bestimmten Typ besitzen. Dieses Verhalten kann zu Ausnahmen (in der Regel ) beim Lesen von an sich gültigen XML-Dokumenten führen, wenn eine im Java-Sinne ungültige Referenz definiert wurde.
In dem folgenden, ausführbaren Beispiel treten wir dann den Nachweis an, dass die Abbildung als Objektgraph tatsächlich funktioniert:
293
6 Java zu XML-Schema
Wie wir an dem Vergleich der Referenzen in den -Anweisungen sehen, werden hier die XML-Referenzen als echte Referenzen im Java-Datenmodell abgebildet. Zusammenfassung Über die Kombination von und können wir Java-Objektgraphen in XML abbilden. Über die Annotation können wir eine String-Eigenschaft einer Javaklasse zu einem eindeutigen Schlüssel machen, mit der wir Instanzen der Java-Klasse im XMLDokument referenzieren können. Die String-Eigenschaft wird an ein Element oder Attribut vom Schemadatentyp gebunden.
294
6.8 Elemente über Factory-Klassen definieren Versehen wir eine Eigenschaft mit der Annotation , so wird der Wert der Eigenschaft im XML-Dokument per referenziert Voraussetzung: Der Datentyp der Eigenschaft ist komplex und definiert per einen Schlüssel.
6.8
Elemente über Factory-Klassen definieren Der Schema-Compiler erzeugt stets eine Klasse , in der für jedes Element im verarbeiteten Schema eine Factory-Methode angelegt wird, mit der dessen JavaRepräsentation angelegt werden. Diese Klasse ist nicht nur ein reines Hilfsmittel, sondern beeinflusst in bestimmten Punkten auch die Bindung von Java nach XML. So werden über deren Factory-Methoden Elemente und Namespaces dem bekannt gemacht. Wir können auch selbst eine solche Klasse mit Factory-Methoden anlegen. Hierzu bedarf es nur der Annotation auf Klassenebene. Die JAXB-Implementierung analysiert eine mit markierte Klasse auf Methoden, die wiederum mit der Annotation versehen wurden in solchen Methoden sucht die JAXBImplementierung, wie der Name der Annotation bereits andeutet, nach Elementdeklarationen. Es gibt im Wesentlichen zwei Gründe für das manuelle Anlegen von Factory-Methoden: Es sollen im Schema Elemente von einem der Basisdatentypen angelegt und an Java gebunden werden. Wir wollen einen Java-Datentyp als Wurzelelement verwenden, können oder wollen diesen aber nicht mit einer -Annotation versehen. Das kann beispielsweise der Fall sein, wenn der Datentyp Basis für mehrere Wurzelemente ist oder der Datentyp nicht verändert werden kann. Wenn wir manuell eine Factory-Methode anlegen, so ist es auf konzeptioneller Ebene für die JAXB-Implementierung nicht wichtig, wie diese implementiert ist. Es ist sogar möglich, diese Methoden abstrakt zu definieren die JAXB-Implementierung wertet für die Java-XML-Bindung nur die Methodensignatur und die Parameter der Annotation aus. Für einen Entwickler, der eine mit Hilfe der Factory-Methode konfigurierte Bindung produktiv verwenden möchte, sind sinnvolle Implementierungen der Factory-Methoden sehr praktisch. Elemente eines Basisdatentyps anlegen Angenommen, wir wollen über eine Factory-Methode ein Wurzelelement definieren, das eine Zeichenkette als Textinhalt haben darf. Hierzu legen wir eine Klasse an und versehen diese mit der Annotation . Wenn wir diese Klasse nun einer -Instanz übergeben, so analysiert diese die Klasse auf Methoden mit der Annotation .
295
6 Java zu XML-Schema Damit der in der Beispielklasse fündig werden kann, legen wir eine solche Methode an. Gemäß der JAXB-Spezifikation muss diese Methode einen Parameter bieten, der Instanzen vom Typ aufnehmen kann und einen Rückgabewert vom Typ definiert. Durch die Parametrisierung des Rückgabewertes bzw. durch den Datentyp des Parameters geben wir der -Instanz einen Hinweis auf den Datentyp des Elements. Verzichten wir hier auf Datentypen, so definieren wir mit der Factory-Methode ein Element vom XML-Schema-Typ . In der Beispielklasse geben wir als Typ- und Methodenparameter an:
Im Methodenrumpf legen wir eine zur Deklaration passende Instanz von an. Alle Parameter der Annotation sollten sich hier wiederholen, der Parameter der Methode sollte als Wert dem neuen weiter übergeben werden. Aus dieser Klasse entsteht nun ein Schema mit einem Wurzelelement mit dem Datentyp :
Komplexe Elemente mit einer Factory-Methode anlegen Ein interessanter Aspekt der Definition von XML-Elementen über eine Factory-Methode ist, dass wir den Datentyp selbst nicht verändern müssen. Angenommen, wir wollen den folgenden Datentyp an ein XML-Wurzelelement binden, wollen diesen aber nicht modifizieren:
296
6.8 Elemente über Factory-Klassen definieren
Wir könnten zwar per Hand eine entsprechende -Instanz anlegen und diese sogar über den Marshaller speichern, doch spätestens beim Unmarshalling wird der das Element als unbekannt entlarven und die Bindung des XML-Dokuments an Java verweigern. Wenn wir analog zur Klasse nun eine Klasse anlegen, die eine Factory-Methode für -Element definiert, so können wir über diesen Weg eine Bindung einer Instanz von an ein Element legal definieren.
Wenn wir diese Klasse einem zur Bindung übergeben, so erhalten wir implizit das folgende Schema:
Es können jetzt -Elemente an -Instanzen gebunden werden.
297
7 JAXB-Referenz 7.1
XJC Kommandozeilenbefehl Der Binding-Compiler der Referenzimplementierung von Sun Microsystems kann über den Befehl auf der Kommandozeile gestartet werden. Der Befehl befindet sich im bin-Ordner der Distribution. Dort ist er einmal als MS DOSBatchdatei und ein anderes Mal als Unix-Shellskript abgelegt. Die möglichen Kommandozeilenparameter können mit der Option ausgegeben werden. Fügen Sie den bin-Ordner der JAXB-Distribution zu Ihrer PATH-Variablen hinzu der Befehl ist dann immer verfügbar. Die Syntax des -Befehls:
Beispiel:
Auf der Kommandozeile können Sie sich mit dem Parameter alle möglichen Kommandozeilenoptionen ausgeben lassen. Sie erhalten dann eine Kurzübersicht über die folgenden Kommandozeilenoptionen:
Tabelle 7.1 Option
Bedeutung
Eingabeschemata werden nicht streng auf ihre Konformität zur XML-Schemaspezifikation überprüft. Es bleibt dabei der einzelnen JAXB-Implementierung überlassen, wie umfassend die Schemas überprüft werden.
299
Option
Bedeutung
Spezifische Erweiterungen der verwendeten JAXBImplementierung werden aktiviert. Der erzeugte Java-Quelltext bzw. der Umfang der verarbeitbaren XML-Schemakonstrukte kann von der JAXB-Spezifikation abweichen.
Mit diesem Parameter kann dem Binding-Compiler eine Datei mit einer JAXB-Binding-Konfiguration übergeben werden. Wenn die JAXB-Binding-Konfiguration in mehrere Dateien aufgeteilt wurde, wird jede Datei einzeln mit dem Parameter übergeben:
Verzeichnis, in das die erzeugten Java-Quelltexte geschrieben werden sollen.
Java-Paketname der generierten Java-Klassen.
Eine Proxy-Konfiguration festlegen. Die Proxy-Konfiguration wird dabei in der folgenden Notation übergeben:
Die Proxy-Konfiguration ist notwendig, wenn Schemas beispielsweise über eine Internetadresse referenziert sind der SchemaValidierer versucht dann, das Schema aus dem Internet zu laden. Über eine Katalogkonfiguration (siehe Option ) können dem Binding-Compiler lokale Kopien solcher Schemas bekannt gemacht werden.
300
Java-Klassenpfad, der vom Binding Compiler verwendet wird, um anwendungsspezifische Klassen aufzulösen. Beispielsweise kann in einer JAXB-Binding-Konfiguration auf eine externe Klasse verwiesen werden. Diese Klasse muss dann im Klassenpfad verfügbar sein.
Globale Schemas und DTDs werden oft über eine InternetAdresse eingebunden. Der Schema-Validierer versucht dann in der Regel, eine Internetverbindung herzustellen, um diese Schemas herunterzuladen. Ausnahme: In einem Katalog sind die lokalen Kopien dieser Schemas verzeichnet. Ein solcher Katalog kann dem Binding-Compiler übergeben werden. Unterstützte Katalogformate sind TR9401, XCatalog und OASIS XML Catalog.
Generierte Dateien werden als schreibgeschützt markiert (Bedeutung variiert je nach Betriebssystem).
Es werden keine Annotationen auf Paketebene erzeugt (**/package-info.java).
Eingabeschemas entsprechen der W3C XMLSchemaspezifikation (Standardeinstellung).
Eingabeschemas sind in als RELAX NG-Dokumente formuliert (experimentell, optional).
Eingabeschemas sind in als RELAX NG compact-Dokumente formuliert (experimentell, optional).
7.2 XJC ANT-Task
7.2
Option
Bedeutung
Eingabeschemas sind DTDs (experimentell, optional).
Eingabeschemas sind WSDL-Dokumente (experimentell, optional).
Detaillierte Informationen auf der Kommandozeilenkonsole ausgeben.
Ausgabe von Informationen auf der Kommandozeilenkonsole unterdrücken.
Alle Kommandozeilenoptionen ausgeben.
Versionsinformationen ausgeben.
Aktiviert die Quelltextlokalisierungsunterstützung im generierten Java-Code.
Fügt das Schlüsselwort zur Methodenspezifikation von Getter-Methoden.
Markiert die generierten Klassen mit der Annotation .
XJC ANT-Task Der Binding-Compiler kann in einem ANT-Skript über das ANT-Task eingebunden werden. Die Referenzimplementierung von Sun liefert hier eine Task-Implementierung in der Bibliothek jaxb-xjc.jar mit. Das folgende Listing stellt dar, wie wir die Task-Implementierung in ein ANT-Skript einbinden.
In der folgenden Beschreibung gehen wir davon aus, dass das -Task wie oben dargestellt in das ANT-Skript eingebunden wurde. Attribute und Werte des xjc-Tasks Die Attribute des -Tasks entsprechen im Wesentlichen den Kommandozeilenparametern des -Befehls. Bei gleicher Bedeutung des Parameters verweisen wir daher auf den entsprechenden Kommandozeilenparameter.
301
Tabelle 7.2 Attribut
Bedeutung
Schemadatei, die verarbeitet werden soll.
Wie .
Wie .
Wie .
Wie .
Wie .
Wie .
Dieses Attribut wird gemeinsam mit verschachtelten -Elementen verwendet. Wenn hier angegeben wurde, dann werden alle Dateien, die in den -Elementen referenziert sind, gelöscht, bevor der Binding-Compiler die Quelltexte neu generiert.
JAXB-Spezifikationsversion der generierten Java-Klassen. Alternativ können diese entweder konform zur Version 1.0 oder 2.0 generiert werden.
Verschachtelte Elemente des xjc-Tasks In einem -Task können weitere Elemente verschachtelt werden. Die Elemente unterscheiden sich dabei in zwei Gruppen. Die erste Gruppe entspricht den obigen Parametern, erlaubt aber die Angabe von (Mengen von) Dateien über Hausmittel von ANT. Das sind zum einen die Elemente , und , die einem ANT entsprechen, sowie das Element , das ebenfalls zum Grundumfang von ANT gehört. Notnagel für alle restlichen Parameter ist das Element hier können die restlichen Parameter des xjc-Befehls untergebracht werden. Die zweite Gruppe dient der Steuerung der Arbeit des -Tasks. Über die Elemente und kann beeinflusst werden, wann das Java-Datenmodell neu generiert werden soll. Tabelle 7.3
302
Element
Bedeutung
Alle Schema-Dateien, die von dem -Task verarbeitet werden sollen. Entspricht einem ANT.
Alle Binding-Konfigurationen, die beachtet werden sollen. Entspricht einem ANT-.
Java-Klassenpfad, der vom Binding-Compiler verwendet wird, um anwendungsspezifische Klassen aufzulösen. Entspricht einem ANT-.
Entspricht dem ANT-Task-Element.
7.3 schemaGen-Kommandozeilenbefehl Element
Bedeutung
Kommandozeilenparameter können über das -Element direkt übergeben werden.
Wann generiert der xjc-Task was? Im Standardfall generiert der -Task stets alle Eingabeschemas auf Neue ob diese sich nun geändert haben oder nicht. Mit ein wenig Konfiguration können wir dieses Verhalten günstig beeinflussen, denn optimal werden die Eingabeschemas nur noch neu generiert, wenn diese einen neueren Zeitstempel aufweisen als die generierten Java-Quelltexte. Dieses Verhalten können wir mit den Elementen und konfigurieren. Dabei liest der -Task die Zeitstempel der in -Elementen beschriebenen Dateien und vergleicht diese mit den Zeitstempeln der Dateien, die in den -Elementen stehen. Standardmäßig werden alle Dateien, die in - und -Elementen deklariert wurden, zur Menge der per eingebundenen Dateien hinzugefügt. Der -Task erkennt jedoch nicht, wenn diese Schemadateien intern auf weitere Schemas verweisen. Diese importierten Abhängigkeiten müssen dem -Task dann explizit übergeben werden. Alle Dateien auf der Ausgabeseite müssen explizit per angegeben werden, hier gibt es keinen Automatismus. Sowohl als auch entsprechen dabei syntaktisch einem ANT-Element. Nun kann es passieren, dass durch eine Änderung des Schemas eine oder mehrere Quelltextdateien nicht mehr generiert werden. Um solche Überbleibsel zu verhindern, kann man das Attribut auf setzen, dann werden alle durch Elemente beschriebenen Dateien vor dem Generieren der neuen Quelltexte gelöscht.
7.3
schemaGen-Kommandozeilenbefehl Mit dem Befehl können aus Java-Klassen XML-Schemas generiert werden. Die Java-Klassen können dabei alternativ als Quelltexte oder als kompilierte ByteCodedateien vorliegen. Der Befehl liegt im Ordner bin der JAXB 2.0-Referenzimplementierung von Sun. Über die Option können wir uns die möglichen Kommandozeilenoptionen ausgeben lassen. Der Befehl benötigt Klassen des Java-Compilers des JDK. Die Umgebungsvariable JAVA_HOME sollte daher auf ein JDK zeigen.
303
Die Syntax des Befehls:
Beispiel:
Hier werden die Quelltexte Sample.java und Demo.java aus dem Verzeichnis src eingelesen und daraus ein Schema schema1.xsd in das Verzeichnis out generiert. Der Name der Ausgabedatei(en) lässt sich durch den Befehl nicht beeinflussen. Tabelle 7.4 Option
Bedeutung
Verzeichnis, in das die erzeugten Schemas abgelegt werden.
Verzeichnisse und Dateien, in denen nach den spezifizierten Java-Klassen gesucht wird.
7.4
Ausgabe aller Optionen
schemaGen-ANT-Task Der -ANT-Task entspricht dem -Befehl, bietet aber zusätzlich die Möglichkeit, die Namen der erzeugten Schemadateien zu beeinflussen. Die TaskImplementierung liegt in der Bibliothek jaxbxjc.jar. In der folgenden Übersicht gehen wir davon aus, dass die Task-Implementierung wie folgt in das ANT-Skript eingebunden wurde:
Das -Task unterstützt die meisten Parameter des -Tasks aus dem Grundumfang von ANT, darüber hinaus können die folgenden Attribute angegeben werden: Tabelle 7.5 Attribut
Bedeutung
Wie Kommandozeilenparameter . Verzeichnis, in das die erzeugten Schemas abgespeichert werden sollen.
304
Wie bzw. .
7.4 schemaGen-ANT-Task Verschachtelte Elemente das schemaGen-Tasks Innerhalb des -Tasks können zusätzlich zu den erlaubten Elementen des Tasks die folgenden beiden Elemente verschachtelt werden: Tabelle 7.6 Element
Bedeutung
Mit diesem Element kann mit der Syntax eines ANT--Tags der Klassenpfad zusammengesetzt werden.
Das -Tag definiert zwei notwendige Attribute: und , über welche die Namen der erzeugten Schemadokumente definiert werden können.
Das -Task kompiliert wenn notwendig vorhandene Java-Quelltexte. Wenn die Klassen JAXB-spezifische Annotationen verwenden, müssen die JAXB-Bibliotheken im Klassenpfad des -Tasks explizit übergeben werden (auch wenn diese im -Element scheinbar bereits eingebunden wurden). Namespaces in verschiedene Schemadokumente generieren Über das verschachtelte -Element kann für jeden Namespace in einem JavaDatenmodell eine eigene Zieldatei angegeben werden. Diese Namespace-Informationen müssen dabei als Annotationen im Java-Datenmodell angegeben werden. Die passende Annotation ist bzw. dessen Attribut . Diese Annotation wird dabei auf Paketebene deklariert, steht also in der package-info.java des jeweiligen Java-Pakets. Alle Java-Klassen, die über diese Annotation einem bestimmten Schema-Namespace zugeordnet sind, werden dann auch in ein eigenes Schemadokument generiert. Unabhängig von vorhandenen, verschachtelten -Elementen erzeugt das -Task dann mehrere Schemadokumente. Diese erhalten den generischen Namen schema.xsd, wobei aufsteigend und zufällig für jeden Namespace vergeben wird. In den verschachtelten -Elementen kann dann für jeden Namespace der Dateiname angegeben werden. Das Paket jaxb.transparent können wir wie folgt mit der Information versehen, dass alle hier enthaltenen Klassen dem Namespace http://jaxb.transparent/foo zugeordnet werden sollen:
305
Jetzt kann in einem verschachtelten -Element Bezug auf diese Informationen genommen werden: Alle Klassen, die zu dem Namespace http://jaxb.transparent/foo gehören, werden in das Schemadokument foo.xsd generiert.
7.5
JAXB-Annotationen Im folgenden Abschnitt sind alle Annotationen aus der JAXB-API alphabetisch gelistet und in Kurzform erläutert. Wenn für ein Element mehrere, eventuell widersprüchliche Annotationen definiert sind, zum Beispiel gleichzeitig auf Klassen und Paketebene, dann gilt stets die spezifischste Annotation: Eine Annotation auf Feldebene überschreibt eine auf Methodenebene. Eine Annotation auf Methodenebene eine Annotation auf Klassenebene. Letztere wiederum eine Annotation auf Paketebene. Über explizit deklarierte Annotationen hinaus gibt es für viele Java-Elemente ein Standardverhalten, das einem der hier beschriebenen Annotationen entspricht.
7.5.1
XmlAccessorOrder
Mit der Annotation kann definiert werden, dass die Felder einer Klasse im generierten Schema alphabetisch sortiert werden sollen. value() Hierzu übergeben wir der Annotation den Parameter . Der alternative Wert, , überlässt es der JAXB-Implementierung zu bestimmen, in welcher Reihenfolge die Felder in das Schema übernommen werden wenn keine Reihenfolge anderweitig vorgegeben wurde, kann diese allerdings auch alphabetisch sein. Standardmäßig gilt .
306
7.5 JAXB-Annotationen Beispiel Angenommen, wir haben die folgende Klasse mit der Annotation versehen:
Wenn wir diese Klasse mit dem Schema-Generator verarbeiten, erhalten wir für diese Klasse das folgende Schema:
Im XML-Schema sind jetzt die Unterelemente des Elementes alphabetisch sortiert.
7.5.2
XmlAccessorType
definiert, welche Felder einer Klasse im erzeugten XML-Schema
standardmäßig verfügbar sind. value()
nimmt
einen
Parameter
vom
Typ
auf. Diese Enumeration besteht aus vier Werten:
307
: Alle Felder der Klasse werden im Schema aufgenommen, unab-
hängig von deren Sichtbarkeit oder ob für die Felder Getter-/Setter-Methoden deklariert sind. : Grundsätzlich werden keine Eigenschaften der Klasse im Schema
aufgenommen. : Alle Eigenschaften, die durch Getter-/Setter-Methoden ge-
setzt werden können, werden in das XML-Schema aufgenommen, deren Sichtbarkeit wird nicht beachtet. : Wie , es werden jedoch nur Ei-
genschaften aufgenommen, deren Sichtbarkeit ist. Standardmäßig gilt . Diese Einstellung kann für einzelne Eigenschaften zusätzlich durch die Annotationen und im Detail angepasst werden. Beispiel Angenommen,
wir haben versehen:
die
folgende
Java-Klasse
308
mit
der
Annotation
7.5 JAXB-Annotationen
In dieser Klasse kommen alle vier Szenarien vor Eine Klassenvariable ohne Zugriffsmethoden mit der Sichtbarkeit private: . Eine Klassenvariable mit Zugriffsmethoden mit der Sichtbarkeit protected: . Eine weitere mit der Sichtbarkeit public: Eine letzte Variable mit der Sichtbarkeit public, die zusätzlich mit der Annotation markiert wurde: . Wenn wir für die obige Klasse mit dem Schema-Generator ein XML-Schema erzeugen, also mit der Einstellung , erhalten wir das folgende Schema.
Die Einstellung unterdrückt zunächst die Bindung der Java BeanEigenschaften an Elemente eines XML-Formats. Die Javab Bean-Eigenschaft jedoch ist explizit mit der Annotation markiert und wird daher an den generierten XML-Typ gebunden und in das Schema generiert. Wenn wir den Parameter vom auf setzen, dann wird auch die mit der Sichtbarkeit versehene Java Bean-Eigenschaft in das Schema übernommen:
Die Einstellung nimmt jetzt alle typischen Java BeanEigenschaften in das Schema auf, unabhängig von deren Sichtbarkeit. Wesentlich ist hier, dass die Eigenschaft mit Zugriffsmethoden versehen sein muss. In unserem Fall kommt nun auch die Eigenschaft mit in das Schema.
309
Bleibt nur noch die Einstellung . Hier werden nun alle Klassenvariablen unabhängig von ihrer Sichtbarkeit an das generierte XML-Format gebunden auch die eigentlich durch die Sichtbarkeit gekapselte Variable .
Gerade bei der Einstellung kann es zu Pseudo-Namenskonflikten zwischen den als Eigenschaft erkannten Zugriffsmethoden und deren zugrunde liegender Klassenvariable kommen. Damit der Schema-Generator bzw. später die JAXB-Laufzeit wissen kann, ob die Variable über die Zugriffsmethoden oder direkt über einen Feldzugriff mit Werten aus dem XML-Dokument bestückt werden soll, müssen solche Namenskonflikte manuell behoben werden. Hierbei muss entweder die Zugriffsmethode oder die Klassenvariable mit der Annotation versehen werden. Im folgenden Quelltextauszug ist ein solcher Namenskonflikt dargestellt (und aufgelöst). Der Schema-Generator erkennt hier bei aktiver Einstellung zwei Möglichkeiten, eine Eigenschaft zu setzen: über die Methode oder den unmittelbaren Zugriff auf die private Klassenvariable . Einen dieser Zugriffe markieren wir also mit . Jetzt ist für den Schema-Generator eindeutig, wie auf die Eigenschaft zugegriffen werden soll eben über die Zugriffsmethode bzw. deren lesendes Pendant .
310
7.5 JAXB-Annotationen
7.5.3
XmlAnyAttribute
Die Annotation markiert ein Java-Feld als Empfänger für nicht näher bestimmte Attribute, die im XML-Schema per an dieser Stelle erlaubt sind. Die markierte Eigenschaft muss vom Typ sein. Im Schema erscheint dieses Feld nicht, es wird stattdessen ein -Element in die Deklaration des Typs aufgenommen. Es kann pro Klasse nur jeweils eine Eigenschaft mit der Annotation versehen werden. Die Eigenschaft entspricht dann einer , in der die Schlüssel vom Typ die Attributnamen, die Werte vom Typ die dazugehörigen Attributwerte enthalten.
Beispiel Eine typische Verwendung von ist im folgenden Quelltext dargestellt:
Der Schema-Generator erzeugt aus der Java-Klasse auf Anfrage dieses XMLSchema:
311
In dem XML-Element können nun beliebige Attribute stehen. Werden solche freien Attribute gefunden, werden diese in der Java Bean-Eigenschaft gespeichert. Umgekehrt werden die in der Java Bean-Eigenschaft definierten Schlüsselwertpaare als Attribute in das XML-Dokument gespeichert.
7.5.4
XmlAnyElement
Über die Annotation können beliebige XML-Elemente an eine Java Bean-Eigenschaft gebunden werden. Dabei wird im generierten XML-Schema innerhalb der Deklaration des Typs ein -Element verschachtelt. Alle entsprechenden XMLElemente werden dann beim Lesen als geparst und in der Java Bean-Eigenschaft gespeichert. Ist die Eigenschaft als oder definiert, so erlaubt das XML-Schema hier mehrere Elemente. value() Über
den
Standardparameter
kann
eine
eigene
Implementierung
von
übergeben werden. Diese Implementierung
kann dann kontrollieren, welche Umwandlungen im Hintergrund vorgenommen werden. lax Für die standardmäßig vorgegebene -Implementierung kann der Parameter definiert werden. Ist gleich , werden alle gefundenen XMLElemente wie beschrieben als Instanzen von eingelesen. Stellen wir hier ein, so werden XML-Elemente, die dem aktuellen bekannt sind, gleich in die entsprechenden Java-Klassen überführt. In diesem Fall muss die Eigenschaft vom Typ sein, damit diese die unterschiedlichen Typen speichern kann. Beispiel Hier eine beispielhafte Anwendung der Annotation .
312
7.5 JAXB-Annotationen
Aus dieser Klasse entsteht das folgende XML-Schema. Der Name der Java BeanEigenschaft ist hierbei für das Schema nicht relevant.
Wenn wir den Parameter auf setzen, muss die Java Bean-Eigenschaft vom Typ sein, weil die -Implementierung jetzt bekannte Elemente erkennt und in die entsprechenden Java-Klassen umwandelt.
Jetzt dürfen in einem zu der obigen Klasse passenden XML-Dokument innerhalb des XML-Elements beliebig viele, beliebig andere XML-Elemente verschachtelt werden.
Wenn wir dieses XML-Dokument jetzt einlesen, enthält die Liste der erzeugten Instanz von zwei Elemente: Ein unbekanntes Element vom Typ , also , und eine weitere Instanz von .
313
7.5.5
XmlAttachmentRef
Eine Java Bean-Eigenschaft kann mit der Annotation an einen Verweis auf eine externe, MIME-kompatible Datenquelle gebunden werden. In der Regel handelt es sich bei diesen externen Quellen um binäre Anhänge eines Webservice-Aufrufs gemäß des WS-I Attachments Profile Version 1.0. Beispiel Hier ein einfaches Beispiel für den Einsatz der Annotation .
Diese Klasse entspricht dabei dem folgenden XML-Schema:
7.5.6
XmlAttribute
314
7.5 JAXB-Annotationen
Über die Annotation können einzelne Eigenschaften und Variablen einer Java Bean an XML-Attribute gebunden werden. Dabei muss der Java-Datentyp sich auf einen einfachen XML-Schemadatentyp wie bspw. oder abbilden lassen. Attribute, die einen komplexen Datentyp besitzen, sind nicht möglich. Möglich sind jedoch Listen und Java-Arrays, diese werden dann als des zugrunde liegenden Basistyps im Schema aufgenommen. name Über den Parameter kann der Name des Attributes im XML-Format definiert werden. required Im XML-Dokument muss dieses Attribut an dieser Stelle vorkommen. namespace Gibt man einen Wert für an, der von dem Namespace der Java-Klasse abweicht, so wird das Attribut in ein neues Schema übernommen, dieses Schema wird dann von dem Schema der Java-Klasse importiert. Beispiel Ein Anwendungsbeispiel der Annotation ist in den folgenden Quelltextauszügen dargestellt. In dem Beispiel ist: ein einfaches Attribut ohne weitere Einstellungen. als Java-Array definiert, muss stets angegeben werden und stellt für das XML-
Attribut den Namen ein. als definiert und hat einen anderen Ziel-Namespace als der
Rest der Klasse. Hier die Klasse SomeClass mit den genannten Eigenschaften:
315
Die in der Klasse gegebenen Informationen wandelt der Schema-Generator in zwei Schemas um. Das erste Schema entspricht dem Namespace der Java-Klasse:
Für die Eigenschaft wurde ein einfaches, gleichnamiges Attribut vom Typ angelegt. Das Attribut, das der Java-Eigenschaft entspricht, trägt den in der Annotation definierten Namen und enthält eine -Deklaration. Die Eigenschaft , für die wir einen eigenen Namespace angegeben haben, nämlich http://jaxb.transparent/bar, wird in diesem Schema nur referenziert und über eingebunden. Das Schema für den Namespace http://jaxb.transparent/bar sieht wie folgt aus:
7.5.7
XmlElement
316
7.5 JAXB-Annotationen
Mit der Annotation kann eine Java Bean-Eigenschaft oder eine Variable an eine -Deklaration in einem XML-Schema gebunden werden. Deklariert die Java Bean-Eigenschaft einen komplexen Datentyp, so wird dieser Datentyp in das XMLSchema aufgenommen. defaultValue Der Parameter beschreibt für ein Element mit einem einfachen Datentyp den Standardwert. name Über kann dem XML-Element ein anderer Name zugewiesen werden als der, der sich aus der Java Bean-Eigenschaft ergibt. namespace Gibt man bei einen anderen Namespace als den, der sich für die umgebende Java-Klasse aus der Konfiguration ableitet, so werden dieses Element und sein Datentyp in ein separates Schema aufgenommen. nillable Der Parameter bestimmt, ob dem Element der Wert zugewiesen werden kann. type Über kann dem Schema-Generator ein Hinweis auf den Java-Datentyp gegeben werden, der von dieser Variable referenziert wird (wenn es sich beispielweise um eine Variable vom Typ handelt). Beispiel Hier ein Anwendungsbeispiel, das einige der oben dargestellten Parameter illustriert.
317
Der Schema-Generator erstellt aus dieser Klasse das unten abgedruckte XML-Schema.
7.5.8
XmlElementDecl
Mit dieser Annotation werden die Factory-Methoden der durch den Schema-Compiler generierten Klasse markiert. Grundsätzlich kann diese Annotation auch manuell gesetzt werden, wenn ein bestehendes Java-Datenmodell an ein gewünschtes XMLSchema gebunden werden soll. Wichtig: Eine Klasse, deren Methoden mit der Annotation versehen werden, muss auf Klassenebene mit der Annotation markiert werden. Eine potenzielle Factory-Methode liefert eine Instanz von als Rückgabewert zurück und besitzt einen Parameter, der sich nach umwandeln lässt. Im generierten Schema werden dann entsprechende Elementdeklarationen aufgenommen.
318
7.5 JAXB-Annotationen name Erforderlich ist die Angabe des Parameters , der hier übergebene Wert bestimmt den Namen des Elementes im XML-Schema. namespace Über den Parameter kann das deklarierte Element einem bestimmten Namespace zugeordnet werden. scope Mit dem Parameter können Namenskonflikte zwischen unterschiedlich verschachtelten Elementen aufgelöst werden dabei wird die Java-Klasse, innerhalb derer das jeweilige Element verschachtelt ist, als übergeben. substitutionHeadName/-space Mit den Parametern und können Substitutionsgruppen und deren Namespace definiert werden. Beispiel Im folgenden Java-Quelltext ist der Einsatz der Annotation dargestellt.
In der obigen Klasse ist einmal ein Element mit dem Namen deklariert. Das Element ist vom Typ . Ein weiteres Element von diesem Typ, , deklarieren wir mit der Annotation an einer passenden Factory-Methode. Für den Schema-Generator ist der Inhalt bzw. der Rückgabewert der Factory-Methode unerheblich die Methode darf hier sogar abstrakt ohne Implementierung bleiben. Der Unmarshaller instanziiert die JAXBElement-Instanzen direkt anhand des vorliegenden XML-
319
Dokuments, ohne dabei die Factory-Methode aufzurufen. Praktisch ist es natürlich sinnvoll, wenn die Factory-Methode tatsächlich zur Annotation passende Instanzen zurückliefert. Es ist daher empfehlenswert, die Factory-Methoden durch geeignete Tests daraufhin abzusichern, dass die JAXB-Implementierung die zurückgelieferten -Instanzen serialisieren und deserialisieren kann, ohne dass es hierbei zu Fehlern kommt. Wenn wir die Klasse durch den Schema-Generator schicken, generiert dieser das folgende XML-Schema:
7.5.9
XmlElementRef
Mit der Annotation können Java Bean-Eigenschaften oder Variablen an eine bestehende -Deklaration in einem XML-Schema gebunden werden. Im Gegensatz zu wird dabei im XML-Schema der Typ des Elementes über das Attribut festgelegt. name Über kann das referenzierte XML-Element explizit angegeben werden.
320
7.5 JAXB-Annotationen namespace Gibt man bei dem Parameter der Annotation einen Namespace an, so wird das Element aus diesem Namespace importiert. type Über kann dem Schema-Generator ein Hinweis auf den Java-Datentyp gegeben werden, der von dieser Variablen referenziert wird (wenn es sich beispielweise um eine Variable vom Typ handelt). Beispiel Hier ein Anwendungsbeispiel, das die Verwendung der Annotation beschreibt.
Wäre die Variable mit der Annotation markiert worden, so würde hier das -Attribut auf den erzeugten Datentyp gesetzt. Für die Annotation generiert der Schema-Generator stattdessen an dieser Stelle eine Element-Deklaration, die das Wurzelelement referenziert:
7.5.10 XmlElementRefs
321
Da eine Annotation in Java 5 nicht mehrfach für das gleiche Java-Element gesetzt werden darf, dient die Annotation dazu, mehrere Annotationen vom Typ zusammenzufassen. In der Regel werden damit Listen markiert, die Elementreferenzen verschiedenen Typs enthalten können. value() Der Standardparameter definiert eine Liste von Elementreferenzen, die in dem markierten Feld vom Typ , oder einem entsprechenden Array-Typ gespeichert werden können. Beispiel Im folgenden Beispiel deklarieren wir zunächst zwei verschiedene Elemente, und . Anschließend legen wir in der Klasse eine Liste an, die später beide Elemente enthalten darf. Diese Liste versehen wir mit der Annotation mit zwei verschachtelten -Deklarationen, die auf die beiden Elemente und verweisen.
Aus dieser Klasse wird nun das folgende Schema erzeugt. Zunächst werden die Elemente und aufgrund der an den Klassen hängenden Annotationen in das generierte Schema aufgenommen. Die per deklarierte Liste von alternativen - und -Elementen ist im folgenden Quelltext hervorgehoben:
322
7.5 JAXB-Annotationen
7.5.11 XmlElements
Analog zur Annotation können mit mehrere Annotationen vom Typ für eine Java Bean-Eigenschaft deklariert werden. Die Annotation kann nur auf Felder vom Typ , oder einem entsprechenden Array-Typ angewendet werden. value() Alle im Parameterwert definierten Elemente werden an die markierte Eigenschaft gebunden. Beispiel Im folgenden Beispiel deklarieren wir auf der Java-Ebene eine einfache Liste . Durch die Annotation definieren wir, dass die Liste zweierlei Elemente enthalten darf: zum einen Elemente mit dem Namen vom Typ , zum anderen Elemente vom Typ .
323
Diese Deklaration wird vom Schema-Generator an das folgende Schema gebunden:
7.5.12 XmlElementWrapper
Über die Annotation kann eine Sequenz von Unterelementen in einem extra Element zusammengefasst werden. Die Annotation kann nur auf Felder vom Typ , oder einem entsprechenden Array-Typ angewendet werden. name Über den Parameter kann der Name des erzeugten Elements bestimmt werden. namespace Der Parameter bestimmt den Namespace, in dem das erzeugte Element angelegt wird.
324
7.5 JAXB-Annotationen nillable Per kann angegeben werden, ob dem Element explizit der Wert zugewiesen werden darf. Beispiel Im folgenden Beispiel verschachteln wir ein solches Element.
Im Normalfall wird die Variable an eine Sequenz von Elementen mit dem gleichen Namen gebunden werden. Diese Sequenz wird direkt unter dem Wurzelelement verschachtelt. Die im obigen Quelltext deklarierte Annotation fasst die -Elemente in einem eigenen Element zusammen das generierte Schema sieht dabei wie folgt aus:
7.5.13 XmlEnum
Über die Annotation können Java-Enumerationen explizit an eine SchemaEnumeration gebunden werden. Siehe auch .
325
value() Über den Standardparameter der Enumeration kann der Typ bestimmt werden, der im Schema als Basistyp der -Deklaration angenommen wird. Wird hier kein Datentyp angegeben, so wird als Parameter angenommen. Der angegebene Datentyp muss sich in einen Schema-Basisdatentypen umwandeln lassen. Beispiel Im folgenden Beispiel ist eine Java-Enumeration mit der Annotation versehen:
Aus dieser Klasse erstellt der Schema-Generator das abgedruckte XML-Schema:
7.5.14 XmlEnumValue
Über die Annotation kann für ein Element einer Enumeration der Wert angegeben werden, der im Schema verwendet werden soll. Über lassen sich insbesondere Enumerationen mit einem anderen Basistyp als abbilden.
value() Der Wert wird dabei als Standardparameter der Annotation übergeben. Beispiel
326
7.5 JAXB-Annotationen
Die obige Java-Enumeration lässt sich nicht ohne weiteres an eine Schema-Enumeration vom Typ binden damit eine solche Bindung funktionieren kann, muss per für jedes Element der Enumeration ein Integerwert angegeben werden.
7.5.15 XmlID
Die Annotation markiert ein Attribut oder Element als einen Wert vom Schematyp . Über den Wert in diesem Feld kann später ein Element dieses Typs innerhalb eines XML-Dokuments eindeutig referenziert werden. Es darf maximal eine Eigenschaft einer Java Bean mit dieser Annotation versehen werden. Siehe hierzu auch . Die Eigenschaft muss eine Instanz von sein. Beispiel In dem unten abgedruckten Quelltext ist die Variable mit der Annotation versehen:
327
Die Variable wird nun an ein Schemaattribut vom Typ gebunden:
7.5.16 XmlIDREF
Über die Schemadatentypen und können Objektgraphen in einem XMLDokument abgebildet werden. Hierbei wird ein Element über ein Attribut oder ein Unterelement vom Typ mit einem eindeutigen Schlüssel versehen. Über diesen Schlüssel können dann Elemente und Attribute vom Typ dieses Element referenzieren. Die Annotation markiert eine Eigenschaft als solch eine Referenz. Der beim Lesen des XML-Dokuments erzeugte Java-Objektgraph entspricht dann dem Graph des XML-Dokuments. Beispiel Im folgenden Beispiel ist ein Element definiert, das mehrere Elemente vom Typ zusammenfasst. Jedes Element vom Typ definiert ein Attribut , das dieses Element innerhalb des XML-Dokuments eindeutig identifiziert, sowie mehrere Unterelemente , in denen auf andere Elemente vom Typ im gleichen Dokument verwiesen werden kann. Dabei ist das Attribut mit der Annotation versehen, die Liste mit den -Unterelementen mit der Annotation .
328
7.5 JAXB-Annotationen
Diese Klasse wird nun mit dem Schema-Generator an das folgende Schema gebunden:
Eine Instanz des oben skizzierten XML-Elements gruppiert mehrere Personen diese Personen können nun in dem Unterelement aufeinander verweisen. Das folgende XML-Dokument wäre eine gültige Instanz dieses XML-Schemas:
329
7.5.17 XmlInlineBinaryData
Wurde eine Eigenschaft mit der Annotation markiert, so werden die binären Inhalte der Eigenschaft stets an dieser Stelle im XML-Dokument inline gespeichert. Standardmäßig darf die JAXB-Implementierung nämlich eine optimierte Speicherung binärer Daten gemäß der XOP-Spezifikation vorsehen. Die Verwendung der Annotation unterdrückt solche Optimierungen für diese Eigenschaft. Die Annotation kann nur für Java Bean-Eigenschaften angewendet werden, deren Datentyp im Schema auf abgebildet wird.
7.5.18 XmlList
Einfache Listen können in XML als deklariert werden. Bedingung ist hierbei, dass die Elemente der Liste einem der Basisdatentypen von XML-Schema entsprechen, bspw. oder . Über die Annotation kann dieses Verhalten für eine Java Bean-Eigenschaft eingestellt werden. Der Datentyp der Java Bean-Eigenschaft muss dabei entweder eine Java-Collection oder ein Array sein, deren Elementdatentyp in einen der Schemabasistypen abgebildet werden kann. Beispiel Im folgenden Beispiel haben wir die Eigenschaft , eine Liste mit Elementen vom Typ , mit der Annotation versehen.
330
7.5 JAXB-Annotationen
Statt an eine Sequenz von Unterelementen wird diese Eigenschaft nun an eine /-Deklaration gebunden. Wenn wir die oben skizzierte Klasse mit dem SchemaGenerator erzeugen, sieht das wie folgt aus:
Jetzt wird die Liste von Namen unmittelbar in das Element geschrieben, wobei die Namen durch Leerzeichen getrennt werden:
Beim Lesen des XML-Dokuments werden dann die einzelnen Namen in der Java-Liste gespeichert.
7.5.19 XmlJavaTypeAdapter
Mit der Annotation kann eine vom Standard abweichende Bindung eines Datentyps mittels einer Implementierung der abstrakten Klasse definiert werden. value() Klasse der -Implementierung
331
type Wenn die Annotation auf Paketebene angewendet wurde, so muss hier der Datentyp angegeben werden, für den die abweichende Bindung definiert werden soll. Beispiel Angenommen, wir markieren die Eigenschaft der folgenden Klasse mit der Annotation und verweisen in dieser Annotation auf eine Adapterimplementierung :
Jetzt wird bei der XML-Bindung der Eigenschaft nicht der Datentyp verwendet, sondern der in der -Implementierung angegebene Zieldatentyp, hier: :
Generieren wir jetzt aus dem Datentyp ein Schema, so sehen wir diese Bindung an einen String auch im XML-Schema:
7.5.20 XmlJavaTypeAdapters
332
7.5 JAXB-Annotationen Über die Annotation können auf Paketebene mehrere -Annotationen zusammengefasst werden. value() Alle in diesem Paket anzuwendenden -Implementierungen.
7.5.21 XmlMimeType
Mit der Annotation kann für binäre Inhalte ein MIME-Typ angegeben werden. Gültige Werte sind beispielsweise image/jpeg, image/* oder text/xml;charset=iso-8859-1. Der Datentyp der Java Bean-Eigenschaft muss auf den XML-Datentyp abgebildet werden. Diese Einstellung ist relevant in einer Umgebung, in der die XML-Daten durch die JAXB-Implementierung optimiert gespeichert werden.
7.5.22 XmlMixed
Die Annotation bindet eine Liste bzw. ein Array an eine Mischung aus Unterelementen und Text. Die erlaubten Unterelemente werden durch parallele Annotationen mit , bzw. beschrieben. In der Liste werden dann die Textinhalte als Instanzen von , Elementreferenzen als -Instanzen und beliebige andere Elemente als Instanzen von gebunden. Beispiel In der unten abgebildeten Klasse ist die Eigenschaft mit der Annotation zusammen mit markiert. Die Referenz verweist dabei auf die Klasse selbst.
333
Das Schema deklariert nun einen XML-Typ , der neben Text beliebig viele Unterelemente vom Typ enthalten darf.
Dieses Schema erlaubt nun XML-Dokumente wie das folgende:
7.5.23 XmlNs
Die Annotation wird ausschließlich im Kontext der Annotation verwendet. Dort können mit der Annotation mehrere Namespaces in einem Schemadokument deklariert werden. namespaceURI Der Parameter definiert dabei den Namen des Namespace. prefix Über den Parameter kann der definierte Namespace mit einem bestimmte Präfix versehen werden.
7.5.24 XmlRegistry
Eine per markierte Klasse bindet Elementdeklarationen eines XMLSchemas an Factory-Methoden. Der Schema-Compiler legt beim Erzeugen eines JavaDatenmodells aus einem XML-Schema standardmäßig eine Klasse an, in
334
7.5 JAXB-Annotationen der die zu den Elementdeklarationen passenden Factory-Methoden gebündelt sind. Die Factory-Methoden sind wiederum mit der Annotation versehen. Beispiel Im folgenden Quelltext ist eine benutzerdefinierte Java-Klasse mit der Annotation versehen:
Aus dieser Klasse leitet der Schema-Generator nun aufgrund der zusätzlichen Annotation das folgende, sehr einfache Schema ab:
7.5.25 XmlRootElement
Die Annotation markiert Java-Klassen, deren Instanzen Elementen auf der Wurzelebene eines XML-Dokuments entsprechen. name Über den Parameter kann der Name des XML-Elements beeinflusst werden. namespace Mit dem Parameter kann das Element einem bestimmten XML-Namespace zugewiesen werden.
335
Beispiel Das folgende Beispiel zeigt den Einsatz der Annotation :
Die Klasse kann jetzt über die JAXB-Implementierung als eigenständiges XMLDokument serialisiert werden. Das Schema dieses Dokuments, mit dem Schema-Generator erzeugt, sieht etwa wie folgt aus:
7.5.26 XmlSchema
Über die Annotation kann ein Java-Paket mit der Information versehen werden, an welchen XML-Namespace die in dem Paket definierten Java-Klassen gebunden werden. Insbesondere können mit der Annotation mehrere Java-Pakete in den gleichen XML-Namespace übernommen werden. Standardmäßig nimmt die JAXBImplementierung an, dass ein Java-Paket genau einem XML-Namespace entspricht.
336
7.5 JAXB-Annotationen namespace Die URI des XML-Namespace, zu dem die Klassen in dem markierten Java-Paket zugeordnet werden sollen. xmlns Über den Parameter können weitere Namespaces mit bestimmten Präfixen in das Schema eingebunden werden. Werden keine expliziten Angaben zu den importierten Namespaces gemacht, so wählt die JAXB-Implementierung bei generierten Importen eigene Präfixe nach Bedarf. Hierbei werden als Parameter Annotationen vom Typ verwendet. xxxFormDefault Über die Parameter und können die entsprechenden Attribute auf der Wurzelebene des XML-Schemas eingestellt werden. Beispiel Im folgenden Quelltext ist eine Paketdeklaration mit der Annotation versehen:
Wenn aus diesem Paket nun mit dem Schema-Generator ein Schema erstellt wird, dann wird für das Schema der der Wert http://jaxb.transparent/foo definiert. Es werden zwei XML-Namespaces http//jaxb.transparent/bar und http/jaxb.transparent/other mit den entsprechenden Präfixen bar und other eingebunden. Für die Attribute und des Schemas werden die in der Annotation definierten Werte gesetzt. Das entstehende Schema sieht dann wie folgt aus:
337
7.5.27 XmlSchemaType
Die Annotation definiert eine nicht standardgemäße Typkonversion primitiver Datentypen. Diese wird entweder auf Paketebene generell für alle Elemente eines bestimmten Datentyps definiert oder speziell für ein bestimmtes Element. name Der Parameter gibt hierbei den XML-Datentyp an, an den das Element/die Elemente gebunden werden sollen. namespace Der Wert von muss einem Datentyp aus dem angegebenen Namespace entsprechen im Standardfall einem der Schemabasistypen aus XML-Schema: http://www.w3.org/2001/XMLSchema. type Wurde die Annotation auf Paketebene definiert, so muss über den Parameter der Java-Typ angegeben werden, der dem Typ zugewiesen wird. Beispiel Die Verwendung der Annotation auf Paketebene sieht wie folgt aus:
Auf der Ebene einer einzelnen Variable:
338
7.5 JAXB-Annotationen
Wenn die beiden oben deklarierten Annotationen zu einem Schema zusammengefasst werden, dann sieht das Schema wie folgt aus:
Folge der Konfiguration mit : Im XML-Dokument werden statt Werte vom Typ nun Werte vom Typ erwartet. Diese Konfiguration gilt paketweit für alle Elemente vom Java-Datentyp bzw. einzeln für das von der Variable abgeleitete XML-Element.
7.5.28 XmlSchemaTypes
Über die Annotation können auf Paketebene mehrere spezielle JavaXML-Typkonversionen eingestellt werden. Dabei werden die einzelnen Annotationen als Array der -Annotation übergeben.
Beispiel Im folgenden Beispiel ist paketweit definiert, dass alle gebundenen Java-Eigenschaften vom Typ , , und an den XML-Schema-Typ gebunden werden.
339
7.5.29 XmlTransient
Java Bean-Eigenschaften oder Variablen, die nicht an XML-Elemente gebunden werden sollen, können mit der Annotation versehen werden. Die JAXBImplementierung ignoriert diese dann geflissentlich. Diese Annotation verträgt sich mit keiner der anderen Annotationen. Beispiel Im folgenden Beispiel ist eine abgeleitete Eigenschaft mit der Annotation versehen:
Wie zu erwarten, glänzt im generierten XML-Schema durch Abwesenheit:
340
7.5 JAXB-Annotationen
7.5.30 XmlType
Die Annotation beschreibt die Bindung einer Java-Klasse an einen XMLDatentyp. name Der Parameter definiert dabei den Namen des gebundenen XML-Datentyps. Sonderfall ist dabei die Angabe einer leeren Zeichenkette diese bindet die Java-Klasse an eine anonyme -Definition. namespace Der Parameter gibt den XML-Namespace vor, in dem sich der XML-Datentyp befindet. propOrder Mit kann die Reihenfolge der Unterelemente im XML-Datentyp angegeben werden. Dem Parameter übergeben wir hierbei die sortierten Namen der gebundenen JavaEigenschaft als String-Array. factoryXxx Über eine Kombination von und kann eine Methode angegeben werden, mit der Instanzen von dieser Java Bean-Klasse angelegt werden können (falls diese beispielsweise keinen Standardkonstruktor besitzt).
341
Beispiel Im folgenden Beispiel ist eine Anwendung der Annotation dargestellt.
Dem Javatyp wird nun der XML-Typ zugeordnet. Der XML-Typ wird im Namespace http://jaxb.transparent/foo angesiedelt. Die JAXB-Implementierung legt Instanzen dieses Typs über einen Aufruf der Factory-Methode an. Wenn wir die Schemabindung mit dem Schema-Generator erstellen, so erhalten wir das folgende XML-Schema:
7.5.31 XmlValue
Über die Annotation kann eine Java Bean-Eigenschaft oder eine Variable an eine -Deklaration in einem XML-Schema gebunden werden. Das heißt, der Text, der von dem entsprechenden XML-Element eingeschlossen wird, kann über diese Java Bean-Eigenschaft programmatisch ausgelesen werden.
342
7.6 Typkonversionen Java zu XML-Schema Es kann stets nur eine -Annotation pro Java-Klasse vorkommen. Unterelemente sind ebenfalls nicht abbildbar, dürfen sich also weder implizit noch explizit aus der Bindung ergeben (sonst wären die Inhalte eine Mischung aus Textinhalten und Unterelementen, siehe hierzu ). Beispiel Hier ein einfaches Beispiel für die Anwendung der Annotation :
Standardmäßig würde die oben abgedruckte Java-Klasse einem XML-Datentyp mit einem Unterelement vom Typ zugeordnet. Die Konfiguration mit bindet die Zeichenkette nun an den Textinhalt des entstehenden XML-Elements. Das Vorhandensein solcher Inhalte kann in einem XML-Schema über eine -Deklaration angegeben werden. Wenn wir also die obige Klasse durch den Schema-Generator schicken, erhalten wir nun das folgende Schema:
7.6
Typkonversionen Java zu XML-Schema Beim Erzeugen einer Schemainstanz aus einem Java-Datenmodell per werden standardmäßig die hier beschriebenen Typzuordnungen vorgenommen. Alle Schemadatentypen sind im Folgenden mit dem Präfix xsd versehen. Die Konversionen sind in Abschnitt 8.5 der JAXB-Spezifikation vorgegeben.
343
Primitive Datentypen Die Konversion der primitiven Datentypen von Java zu XML-Schema stellt sich recht trivial dar. Tabelle 7.7 Konversionen primitiver Java- zu Schemadatentypen Java-Datentyp
XML-Datentyp
boolean
xsd:boolean
byte
xsd:byte
short
xsd:short
int
xsd:int
long
xsd:long
float
xsd:float
double
xsd:double
byte[]
xsd:base64Binary
Komplexe Datentypen der Core-API Für eine Reihe von Klassen aus der Java Core-API sind in der JAXB Konversionen zu XML-Datentypen vorgesehen. Tabelle 7.8 Konversionen wichtiger Core-API-Klassen zu Schemadatentypen
344
Java-Datentyp
XML-Datentyp
java.lang.String
xsd:string
java.math.BigInteger
xsd:integer
java.math.BigDecimal
xsd:decimal
java.util.Calendar
xsd:dateTime
java.util.Date
xsd:dateTime
javax.xml.namespace.QName
xsd:QName
java.net.URI
xsd:string
javax.xml.datatype.XMLGregorianCalendar
xsd:anySimpleType
javax.xml.datatype.Duration
xsd:duration
java.lang.Object
xsd:anyType
java.awt.Image
xsd:base64Binary
javax.activation.DataHandler
xsd:base64Binary
javax.xml.transform.Source
xsd:base64Binary
java.util.UUID
xsd:string
7.7 Typkonversionen XML-Schema zu Java
7.7
Typkonversionen XML-Schema zu Java Beim Erzeugen eines Java-Datenmodells aus einer bestehenden Schemainstanz per werden die Schema-/XML-Datentypen standardmäßig in bestimmte Java-Datentypen übertragen. Die Schemadatentypen sind im Folgenden mit dem Präfix xsd versehen. Die Typkonversionen sind in der JAXB-Spezifikation in Abschnitt 6.2 vorgegeben. Tabelle 7.9 Konversionen XML-Datentypen zu Java-Datentypen XML-Datentyp
Java-Datentyp
xsd:string
java.lang.String
xsd:integer
java.math.BigInteger
xsd:int
int
xsd.long
long
xsd:short
short
xsd:decimal
java.math.BigDecimal
xsd:float
float
xsd:double
double
xsd:boolean
boolean
xsd:byte
byte
xsd:QName
javax.xml.namespace.QName 1
xsd:base64Binary
byte[]
xsd:hexBinary
byte[]
xsd:unsignedInt
long
xsd:unsignedShort
int
xsd:unsignedByte
short
xsd:dateTime xsd:time xsd:date xsd:gYearMonth javax.xml.datatype.XMLGregorianCalendar xsd:gYear xsd:gMonthDay xsd:gDay xsd:gMonth xsd:anySimpleType
Elemente: java.lang.Object Attribute: java.lang.String
xsd:duration
javax.xml.datatype.Duration
xsd:NOTATION
javax.xml.namespace.QName
345
7.8
XML-Elemente der Bindungskonfiguration Über die Bindingkonfigurationen kann das aus einem XML-Datenmodell per erzeugte Java-Datenmodell auf die Bedürfnisse eines bestimmten Anwendungsfalls angepasst werden. Die Syntax der Bindungskonfiguration ist durch ein XML-Schema vorgegeben. Das aktuelle Schema der Bindungskonfiguration ist im Netz unter der URL http://java.sun.com/xml/ns/jaxb verfügbar. Einstellungen in einer Bindungskonfiguration können auf vier Ebenen bzw. Sichtbarkeitsbereichen definiert werden: Global: Eine globale Einstellung gilt für sämtliche Schemas, die mit dieser Bindungskonfiguration verarbeitet werden. Das betrifft auch importierte Schemas, die in einem Schema referenziert werden. Schema: Eine schemalokale Einstellungen ist an ein bestimmtes Schema gebunden. Wenn mit dieser Bindungskonfiguration mehrere Schemas verarbeitet werden, dann können für einzelne Schemas andere Einstellungen definiert werden. Typ/Element: Gültig für einzelne Typen und Elemente in einem Schemadokument. Komponente: Gültig für Unterelemente einer Typ- bzw. Elementdeklaration in einem Schemadokument. Einstellungen für den gleichen Aspekt können im Einzelfall auf mehreren Ebenen definiert werden. Der Schema-Compiler beachtet dann die Einstellung in der Reihenfolge ihrer Genauigkeit. D. h., eine Einstellungen auf Komponentenebene überlagert eine Einstellung auf Typ-/Elementebene, eine solche überlagert eine Einstellung auf Schemaebene diese überlagert wiederum globale Einstellungen. In der folgenden Referenz sind die Einstellungen für Typ-/Element- und Komponentenebene zusammengefasst, da die meisten dieser Einstellungen auf beiden Ebenen definiert werden können.
7.8.1
Globale Einstellungen
In dem Element können generelle Einstellungen betreffend des generierten Java-Datenmodells angegeben werden. Im folgenden Listing sind alle möglichen Attribute des Elements mit möglichen Werten aufgelistet. Die Attribute sind allesamt optional die dann gültigen Standardwerte sind in dem Listing hervorgehoben.
346
7.8 XML-Elemente der Bindungskonfiguration
7.8.1.1
collectionType
Über das Attribut kann der Schema-Compiler angewiesen werden, eine bestimmte Implementierung von im generierten Java-Code zu verwenden. Werden dem Schema-Compiler keine Vorgaben bezüglich der zu verwendenden -Implementierung gemacht, so obliegt es diesem, eine passende auszuwählen. Gültige Werte für das Attribut sind beispielsweise oder . Angenommen, in einem Schema ist das folgende Element definiert. Für das Unterelement generiert der Schema-Compiler nun eine Implementierung von in den Code.
Wenn wir nun bei die Klasse angeben, wird in etwa der folgende Java-Code generiert:
347
7.8.1.2
fixedAttributeAsConstantProperty
In der Regel werden Attribute, die im XML-Schema mit deklariert wurden, als normale Eigenschaften der resultierenden Java Bean generiert. Einziger Unterschied zu einer gewöhnlichen Eigenschaft: Sie ist standardmäßig auf den als angegebenen Wert gesetzt. Alternativ kann über die Einstellung ein solches Attribut als Konstante der resultierenden Java Bean generiert werden. Das sieht in einem knappen Beispiel wie folgt aus:
Oben ist also so ein festgelegtes Attribut deklariert. Dieses wird dann im Standardfall, wenn also auf gesetzt ist, in den unten abgebildeten Java-Code überführt:
Wenn wir nun alternativ an dieser Stelle den Wert angeben, wird jetzt als Konstante angelegt. Es entsteht Quelltext im Sinne des folgenden Listings.
348
7.8 XML-Elemente der Bindungskonfiguration
7.8.1.3
generateIsSetMethod
Wenn auf gesetzt ist, generiert der Schema-Compiler zu jeder Eigenschaft einer erstellten Java Bean eine mit dem Präfix isSet versehene Methode. Diese liefert dann zurück, wenn diese Eigenschaft mit einem Wert bestückt wurde, bzw. , wenn diese Eigenschaft gleich ist. Die unten abgedruckte Klasse wurde mit einer solchen isSet-Methode generiert.
7.8.1.4
enableFailFastCheck
Normalerweise wird das Datenmodell nach dem Laden und optional vor dem Speichern auf seine Gültigkeit überprüft. Wenn auf gesetzt wurde, dann werden Änderungen des Modells unmittelbar darauf hin überprüft, ob diese im Sinne des XML-Schemas erlaubt sind. Ungültige Änderungen scheitern also so schnell wie möglich, fail-fast eben. Potenziell gehen diese Tests auf Kosten der Performance und des Entwicklungskomforts, weswegen diese Option standardmäßig ist. Hinweis: In der aktuellen Version der JAXB-Referenzimplementierung wird diese Bindungsdeklaration noch nicht unterstützt. choiceContentProperty Ein aus einem XML-Schema lässt sich nicht eins zu eins in Java abbilden. Entweder erhält jedes im verschachtelte Element eine eigene Java Bean-Eigenschaft, oder für das wird insgesamt eine Java Bean-Eigenschaft angelegt. Ersteres Vorgehen verhüllt, dass nur jeweils eine dieser Eigenschaften gesetzt werden darf, letzteres Vor-
349
gehen generiert dann eine Choice Content Property und führt zu interessanten Methodennamen wie . Angenommen, wir haben im XML-Schema ein Element mit drei in einem verschachtelten Unterelementen.
Generieren wir aus dieser Elementdeklaration eine Java-Klasse mit gleich , dann wird für jedes Element eine eigene Eigenschaft angelegt:
Alternativ können wir auf setzen, jetzt werden die drei Unterelemente zu einer Eigenschaft zusammengefasst.
350
7.8 XML-Elemente der Bindungskonfiguration
7.8.1.5
underscoreBinding
Definiert, wie Unterstriche in XML-Elementbezeichnern in Java-Bezeichner umgewandelt werden. Standardmäßig werden mit der Einstellung Unterstriche in XML-Bezeichnern als Camel Case abgebildet. Mit der Einstellung bleiben die Unterstriche auch in den Java-Namen erhalten. Tabelle 7.10 Generierte Java-Bezeichner je nach underscoreBinding underscoreBinding
XML-Name
Java-Klasse
Java-Eigeschaft
Java-Konstante
my_other-car
MyOtherCar
getMyOtherCar
MY_OTHER_CAR
My_otherCar
getMy_otherCar
MY_OTHER_CAR
my_other-car
typesafeEnumBase Enthält eine Liste von Datentypen, die standardmäßig als eigene Enumerationsklasse generiert werden, wenn diese im Schema als Basistyp eines -Elements mit verschachtelten -Elementen gesetzt sind. Standardwert ist . Nicht in die Liste aufgenommen werden dürfen die Datentypen , , , , , , , , , , , , , und. Die -Definition muss, damit der Standardmechanismus greift, mit eigenem Namen in der Wurzelebene des Schemas deklariert werden. Wenn eine Enumeration definiert wurde, deren Basistyp nicht in gelistet ist, dann werden einfache Eigenschaften von diesem Typ in die entsprechende Java Bean generiert. Es ist dann zunächst nicht ersichtlich, dass es sich hier um eine Enumeration handelt.
351
7.8.1.6
typesafeEnumMemberName
Es gibt hier drei mögliche Einstellungen: , und . Diese Einstellung wird beachtet, wenn sich aus einem oder mehreren Werten einer Enumeration keine Java-Bezeichner ableiten lassen. Ist eingestellt, dann wird die Enumeration nicht erzeugt, sondern stattdessen als unsichere Java BeanEigenschaft angelegt (so steht es in der JAXB-Spezifikation, auch wenn es durch die uns vorliegende Referenzimplementierung nicht unterstützt wird). Bei wird eine Fehlermeldung ausgegeben. Im Fall von erhält der erste Wert den Name , alle weiteren werden dann aufsteigend mit bezeichnet. Standardmäßig ist eingestellt, diese Einstellung sollte auch beibehalten werden über die Bindungskonfiguration kann einer problematischen Enumeration bei Bedarf ein Java-fähiger Namen verpasst werden. 7.8.1.7
typesafeEnumMaxMembers
Hier kann eine positive, ganze Zahl angegeben werden. Standardmäßig wird 256 angenommen. Wenn eine Enumeration mehr als die hier bezeichnete Anzahl von Werten definiert, wird diese nicht als Java-Enumerationsklasse generiert, sondern als einfache, unsichere Java Bean-Eigenschaft. 7.8.1.8
enableJavaNamingConventions
Wenn auf gesetzt wurde, generiert der SchemaCompiler Java-Klassennamen stets gemäß den ungeschriebenen Konventionen für die Formulierung von Java-Bezeichnern, auch wenn hierzu der im XML-Schema verwendete Bezeichner verändert werden muss. Konventionskonforme Java-Bezeichner gemäß der JAXB-Spezifikation (siehe dort Anhang D.2) erfüllen die folgenden drei Bedingungen: Klassen und Schnittstellennamen beginnen mit einem Großbuchstaben. Die weiteren Buchstaben sind alphanumerisch und kleingeschrieben. Besteht der Name aus mehreren Teilworten, so beginnt jedes Teilwort wieder mit einem Großbuchstaben. Akronyme werden in Großbuchstaben in den Namen aufgenommen. Methodennamen beginnen stets mit einem Kleinbuchstaben. Es gelten ansonsten die Regeln für Klassen- und Schnittstellennamen. Konstantennamen werden komplett mit Großbuchstaben geschrieben, wenn der Name aus mehreren Teilworten besteht, werden die Teilworte mit einem Unterstrich voneinander getrennt. 7.8.1.9
generateElementClass
In der Default-Einstellung von , also , werden für typisierte Elementdeklarationen in einem Schema keine eigenen Klassen generiert. Wenn wir ein solches Element anlegen wollen, bemühen wir die generierte und legen
352
7.8 XML-Elemente der Bindungskonfiguration eine passende Instanz von an. Setzen wir auf , dann legt der Schema-Compiler eine passende Klasse für das Element an. Den Unterschied wollen wir an dem folgenden Beispiel erläutern. In dem folgenden Schemaschnipsel sind ein Typ und ein Element von diesem Typ definiert.
Wenn wir dieses Teilschema mit der Option gleich generieren, erhalten wir für den Datentyp etwa den folgenden Java-Code:
Um jetzt eine Instanz des Elements aus dem obigen Teilschema anzulegen, können wir die generierte bemühen. Diese sollte folgende Methode aufweisen:
Das Element selbst ist jedoch in dem generierten Java-Datenmodell nicht sichtbar. Alternativ können wir uns über die Option zu jedem Element eine eigene Klasse generieren lassen. Für unser Teilschema wäre das die folgende Klasse .
353
Der aufmerksame Beobachter erkennt vielleicht die Analogie zwischen der FactoryMethode in der und dem generierten Konstruktor von . 7.8.1.10 generateValueClass Die Option hat den größten Einfluss auf die Struktur des generierten Java-Datenmodells. In der Standardeinstellung werden alle Datentypen als echte Java Beans generiert. Wenn wir hier einstellen, wird für jeden Datentyp zunächst nur eine Java-Schnittstelle angelegt sowie eine Reihe von Implementierungen, die komplett hinter der generierten verborgen bleibt. Während ersteres Modell etwas handlicher in der Nutzung ist, kann letzteres Modell durch seine strikte Entkopplung von der XML-Repräsentation besser an andere Datenformate angebunden werden. Wenn wir für das Teilschema mit dem Datentyp aus dem Beispiel zur globalen Einstellung den Quelltext mit generieren lassen, dann entstehen zwei Java-Artefakte: zum einen die Schnittstelle mit den Eigenschaften des Datentyps , zum anderen eine Implementierung dieser Schnittstelle, , die im entsprechenden Java-Paket abgelegt wird.
Dazu die passende Implementierung, in der jetzt alle JAXB-spezifischen Annotation etc. landen.
354
7.8 XML-Elemente der Bindungskonfiguration
Die generierte kennt zwar die zugrunde liegenden Implementierungen, gibt diese aber nicht nach außen weiter:
7.8.1.11 optionalProperty Mit der Einstellung kann gesteuert werden, wie ein nur optional durch das XML-Schema vorgegebenes Element im Java-Datenmodell abgebildet werden soll. Hier sind drei verschiedene Arten wählbar: , und . Standardmäßig gilt . Im Fall von werden optionale Elemente gleich behandelt wie notwendige. Setzt man hier ein, dann wird zu jedem optionalen Element eine passende isSet-Methode (siehe Einstellung ) generiert. Im Standardfall werden optionale Elemente stets mit einem komplexen Datentyp abgebildet, der sein kann. Wenn also ein einfacher, ganzzahliger Wert ein optionales Element sein sollte, wird hier die entsprechende Wrapper-Klasse aus der Core-API genommen. Diese Einstellung wird von der Referenzimplementierung im Moment nicht unterstützt.
355
7.8.1.12 mapSimpleTypeDef Über kann bestimmt werden, ob benannte -Definitionen als eigenständige Klassen generiert werden sollen. Wird hier nichts angegeben, dann nimmt der Schema-Compiler an. In diesem Fall werden -Definitionen nicht generiert. Um das Ganze an einem kleinen Beispiel zu illustrieren, nehmen wir folgendes Teilschema an: Wir deklarieren einen benannten und legen ein Element an, das ein Unterelement von diesem Typ besitzt.
Generieren wir aus diesem Teilschema nun Quelltext mit der Standardeinstellung von , so erhalten wir nur eine Klasse . Deren Unterelement ist durch den Basistyp der -Deklaration substituiert worden:
Setzen wir die Einstellung auf , so bleibt die Deklaration im generierten Java-Quelltext des Elementes erhalten:
356
7.8 XML-Elemente der Bindungskonfiguration
Auch die -Deklaration selbst wird in diesem Fall als eigene Java-Klasse generiert die Eigenschaft speichert dann den eigentlichen Wert:
7.8.1.13 localScoping Bestimmt die Art und Weise, wie verschachtelte Elemente generiert werden. Zwei Einstellungsmöglichkeiten sind möglich: und . Standardmäßig gilt . Sind Elemente ineinander verschachtelt, so werden in der Standardeinstellung die JavaKlassen der verschachtelten Elemente als innere Klassen des Wurzelelementes generiert. Stellt man hier stattdessen ein, erhalten alle Elemente, auch die verschachtelten, eine eigenständige Java-Klasse im Paket des Java-Datenmodells. Angenommen, wir haben folgendes Teilschema mit einer verschachtelten Elementdeklaration:
In der Standardeinstellung wird für das Element eine innere Klasse generiert, wird durch dessen Datentyp substituiert und taucht im Java-Datenmodell nicht mehr explizit auf.
357
Wenn wir für die Einstellung stattdessen angeben, so wird die Klasse nicht mehr innerhalb von verschachtelt, sondern es werden zwei eigenständige Klassen und generiert. Hier die Klasse :
Jetzt eine eigenständige Klasse: .
358
7.8 XML-Elemente der Bindungskonfiguration
Für die Generierung der verschachtelten, inneren Klasse spricht, dass hier keine Namenskonflikte auftreten können und dass es stets eindeutig ist, wo welche Klasse hingehört. Für die Einstellung spricht, dass sich die Klassen komfortabler verwenden lassen, da man nicht stets die umgebende Klasse mit angeben muss. Gerade bei tiefen Verschachtelungen ergeben sich durch die -Einstellung sehr lange und unhandliche Deklarationen und Konstruktoren:
In so einem Fall empfiehlt es sich vielleicht, den Code mit = zu generieren.
7.8.2
Schemaspezifische Einstellungen
7.8.2.1
package
Über das Attribut des -Elementes kann der Name des Java-Pakets bestimmt werden, in dem die generierten Klassen des entsprechenden Schemas abgelegt werden. In der folgenden Deklaration würden alle Klassen des betroffenen Schemas in das Paket generiert werden.
359
javadoc In das -Element kann ein weiteres Element verschachtelt werden: . Über dieses kann der generierte Javadoc-Kommentar des Java-Pakets definiert werden. Der in diesem Element angegebene Text wird dann in eine Datei package.html im entsprechenden Paketordner gespeichert.
7.8.2.2
nameXmlTransform
Wann immer Elemente, Typen und Gruppen in einem XML-Schema den gleichen Namen tragen, kann dieses im generierten Java-Datenmodell zu Namenskonflikten führen. Im Einzelfall können solche Konflikte mit entsprechenden Bindungskonfigurationen oder Annotationen problemlos behoben werden. Treten diese Namenskonflikte gehäuft auf, können mit dem Element Präfixe und Suffixe definiert werden, die dann generell an die entsprechenden, generierten Java-Namen angehängt werden. Dabei kann zwischen vier Typen von Schemaelementen unterschieden werden: Typen: Alle benannten - und -Deklarationen Elemente: Alle Elementdeklarationen im Schema Gruppen: -Deklarationen Anonyme Typen: Implizite Datentypen, also verschachtelte - bzw. -Deklarationen typeName Über können Präfixe und Suffixe für Datentypen definiert werden. Nehmen wir das folgende Teilschema an:
Was im Schema durch den Kontext des Typattributes eindeutig ist, wird nun in Java ein Namenskonflikt auf der Java-Paketebene: Es gibt gleichzeitig das Element und den
360
7.8 XML-Elemente der Bindungskonfiguration Datentyp (vorausgesetzt, die Bindungsdeklaration ist aktiviert). Wenn wir jetzt für dieses Schema folgende Bindungskonfiguration einstellen, können wir diesen Konflikt auf der Ebene der Schemaeinstellungen generell beheben:
Jetzt generiert der Schema-Compiler für den Datentyp in etwa die folgende Klasse :
Die Java-Klasse, die dem Element entspricht, erhält den Standardnamen , der Namenskonflikt ist aufgelöst. elementName Wie . Passt analog zu die Namen der generierten Java-Klassen für XML-Elemente an. modelGroupName Wie . Passt analog zu die Namen der generierten Java-Klassen für -Deklarationen in einem XML-Schema an.
361
anonymousTypeName Wie . Passt analog zu die Namen der generierten Java-Klassen für anonyme, verschachtelte XML-Elemente an. Wann diese Regel greift, wollen wir kurz an einem Beispiel erläutern. Angenommen, wir haben das folgende Teilschema:
Hier ist ein verschachteltes, komplex typisiertes Element definiert. Die in definierten Präfixe und Suffixe gelten für die Java-Klasse, die für generiert wird. Im Element ist die -Deklaration anonym in dem Element verschachtelt. Wenn wir also für das Teilschema die folgende Konfiguration vornehmen:
dann wird mit dieser Konfiguration für das Element die folgende Java-Klasse generiert:
362
7.8 XML-Elemente der Bindungskonfiguration
Der Schema-Compiler übernimmt hier für die verschachtelte innere Klasse die in definierten Namensanpassungen. Statt heißt der Typ jetzt dank des Präfixes Inner und des Suffixes Type jetzt InnerType.
7.8.3
Komponentenspezifische Einstellungen
7.8.3.1
class
Über die Bindungskonfiguration kann die Generierung einer Java-Klasse aus einem einzelnen XML-Schema-Element angepasst werden. Hierbei können der Name der generierten Klasse(n) und der Javadoc-Kommentar angegeben werden. Ebenfalls kann mit der Einstellung die konkrete Implementierungsklasse bestimmt werden, die von der zurückgegeben wird. Dies wirkt sich jedoch nicht darauf aus, wenn ein Objekt durch erzeugt wird.
Tabelle 7.11 Beschreibung der Einstellungsmöglichkeiten Einstellung
Beschreibung
name
Name der zu generierenden Klasse.
implClass
Die Klasse ObjectFactory wird angewiesen, in der jeweiligen Factorymethode den hier angegebenen vollqualifizierten Typ zurückzugeben. Dadurch kann die konkrete Implementierung für ein einzelnes Schemaelement angepasst werden.
<javadoc>
Mit dem verschachtelten -Element kann für eine Klasse ein Javadoc-Kommentar angegeben werden.
Angenommen, wir haben das folgende Teilschema mit eingebetteter Bindungskonfiguration :
363
Dann generiert der Schema-Compiler für das Schemaelement die folgende Klasse.
Die generierte Klasse enthält jetzt den in definierten Kommentar und trägt den im Attribut angegebenen Namen. 7.8.3.2
property
Über kann die Bindung eines Schemaelements an eine Java Bean-Eigenschaft konfiguriert werden.
Tabelle 7.12 Beschreibung der Einstellungsmöglichkeiten für property
364
Einstellung
Beschreibung
name
Name der zu generierenden Java Bean-Eigenschaft. Muss ein gültiger Java-Bezeichner sein.
generateElementProperty
Standardwert: false. Beschreibung siehe unten.
7.8 XML-Elemente der Bindungskonfiguration Einstellung
Beschreibung
attachementRef
Definiert, ob XML-Attachments als aufgelöst werden sollen ( oder ob in einer entsprechenden Eigenschaft nur ein das XML-Attachment gespeichert wird ().
Über das Element kann ein Basistyp oder ein zu verwendender Java-Datentyp angegeben werden.
<javadoc>
Mit dem verschachtelten -Element kann für eine Java BeanEigenschaft ein Javadoc-Kommentar angegeben werden.
collectionType
Entspricht der gleichnamigen, globalen Einstellung. Sind hier keine Werte angegeben, so werden die Werte der globalen Einstellungen bernommen.
fixedAttributeAsConstantProperty generateIsSetMethod enableFailFastCheck
generateElementProperty In der Standardeinstellung des Schema-Compilers werden die zu einem Schemaelement generierten Java Bean-Eigenschaften mit entsprechenden Java-Datentypen versehen. Das hat den Vorteil, dass das entstehende Java-Datenmodell natürlich ist, also keinen Hinweis darauf gibt, dass es auf einem XML-Datenmodell basiert und durch eine JAXBImplementierung generiert wurde. Es bedeutet aber auch, dass bestimmte XMLspezifische Informationen nicht abgebildet werden. Wenn diese Informationen für das eine oder andere Element vorhanden bleiben sollen, kann man auf setzen. Statt den passenden Java-Datentyp für die Deklaration der Eigenschaft zu verwenden, generiert der Schema-Compiler jetzt Eigenschaften vom Typ Angenommen, wir haben das folgende Teilschema:
365
Im Standardfall würden die im obigen Teilschema deklarierten Elemente und in die entsprechende Java Bean-Eigenschaft vom Typ bzw. generiert werden. Wir haben aber für beide Elemente die Einstellung auf gesetzt. Der Schema-Compiler erzeugt jetzt etwa den folgenden Java-Quelltext für die Klasse :
Jetzt sind die eigentlichen Werte in Instanzen von eingepackt. bietet jetzt Methoden, mit denen XML-spezifische Informationen abgefragt werden können, wie z.B. , mit der abgefragt werden kann, ob das Element im XML explizit auf gesetzt wurde, oder die Methode , die den Namen des der Eigenschaft zugrunde liegenden XML-Elements zurückliefert. baseType Die automatische Typauflösung des Schema-Compilers kann für einzelne Eigenschaften mit der Einstellung umgangen werden. Typen, die hier explizit als Basistyp festgelegt sind, werden als solche ungefragt in den Quelltext übernommen. Das kann zum einen zu unkompilierbarem Quelltext führen, zum anderen können damit eigene Klassen in den generierten Code eingebunden oder nachträgliche Typumwandlungen konfiguriert werden.
366
7.8 XML-Elemente der Bindungskonfiguration Tabelle 7.13 Beschreibung der Einstellungsmöglichkeiten für baseType Einstellung
Beschreibung
name
Name der an dieser Stelle zu verwendenden Java-Klasse. Der Name wird ohne Prüfungen in den generierten Java-Code übernommen daher den Namen möglichst voll qualifizieren.
<javaType>
Siehe Beschreibung zu , allerdings ist hier das Attribut durch das von der Konfiguration betroffene Element vorgegeben. Es können nicht gleichzeitig und verwendet werden.
Ein guter Anwendungsfall für sind Elemente vom Typ . Angenommen, wir haben in einem XML-Schema folgende Liste von Kunden definiert:
Das Element enthält also eine Reihe von -Elementen, die über ein Element aufeinander verweisen können. Das sieht dann in einer Instanz von diesem Schema folgendermaßen aus:
Nun verhält es sich im oben dargestellten Datenmodell so, dass jedes -Element eventuell auf ein anderes -Element verweisen kann. Weil der Schema-Compiler dieses nicht erahnen kann denn potenziell können über beliebige Elemente referenziert werden , generiert dieser für das Element sicherheitshalber eine sehr anonyme Eigenschaft vom Typ :
367
Hier können wir dem Schema-Compiler mit der -Konfiguration auf die Sprünge helfen. Wenn wir nämlich für die Eigenschaft einen festlegen, z.B. sinnvollerweise , dann wird für diese Eigenschaft dieser Typ angenommen. Hierzu fügen wir zu dem oben dargestellten Schema folgende Bindungskonfiguration hinzu:
Jetzt generiert der Schema-Compiler eine Eigenschaft vom Typ statt vom Typ was sehr praktisch ist, solange alle Referenzen auch wirklich von diesem Typ sind ...
368
7.8 XML-Elemente der Bindungskonfiguration
7.8.3.3
javaType
Über können eigene Typkonversionen konfiguriert werden. D.h., für ein im XML-Schema definiertes Element kann ein anderer Java-Typ angegeben werden als der, der standardmäßig durch den Schema-Compiler gewählt würde. Da die JAXBLaufzeitumgebung dann in der Lage sein muss, Werte von dem verwendeten Schematyp zum gewünschten Java-Typ umzuwandeln, geben wir bei Konversionsmethoden an, die diese Umwandlung beherrschen.
Eine -Konfiguration kann in drei Kontexten vorgenommen werden: Global innerhalb eines -Elements, wo die definierte Typbindung für alle verarbeiteten Schemas angewendet wird. Für eine -Definition in einem XML-Schema, wo diese für alle Elemente gilt, die diesen verwenden. Innerhalb von verschachtelten /-Elementen, wo die Einstellung für die entsprechende Java Bean-Eigenschaft gilt. Tabelle 7.14 Beschreibung der Einstellungsmöglichkeiten für javaType Einstellung
Beschreibung
name
Der vollqualifizierte Java-Klassenname des zu verwendenden Typs.
xmlType
Der XML-Schema-Datentyp, der durch den hier definierten Typ ersetzt werden soll.
parseMethod
Methode, mit der eine Zeichenkette aus dem XML-Dokument in den Zieldatentyp umgewandelt wird.
printMethod
Methode, mit der eine Instanz des Typs als Zeichenkette in das XMLDokument geschrieben wird.
name Wenn per eine Typsubstitution definiert wurde, verwendet der SchemaCompiler für die betroffenen Elemente den in definierten Datentyp. Im erzeugten
369
Quelltext werden die Elemente mit der Annotation versehen. Der Annotation wird eine in der Regel durch den Schema-Compiler erzeugte Adapterklasse übergeben, welche die Konversion von zu und umgekehrt vornehmen kann. Die Konversionsmethoden dieser Adapterklassen werden anhand der Angaben aus , und generiert. Der Schema-Compiler überprüft hierbei nicht, ob der generierte Quelltext gültig ist. Eine sehr einfache Konfiguration ist im folgenden Teilschema dargestellt:
Hier ist in einem Wurzelelement ein Unterelement verschachtelt. Für das Element ist eine Bindungskonfiguration definiert, und zwar , dieses ist wiederum verschachtelt in einem -Element. Das -Element benötigt in dieser Konstellation nur den Namen der Java-Klasse, die statt des implizit vorgegebenen Typs verwendet werden soll. Alle anderen Informationen ergeben sich aus dem Standardverhalten. Der Schema-Compiler generiert nun etwa folgenden Quelltext für das Element .
Statt einer Eigenschaft vom Typ besitzt die Klasse nun eine Eigenschaft vom konfigurierten Typ . In der Annotation wird auf eine Klasse verwiesen. Schauen wir uns diese mal an.
370
7.8 XML-Elemente der Bindungskonfiguration
Die Klasse leitet von ab und implementiert zwei Methoden und . In der Methode wird nun die aufgerufen, im Standardfall wird hier ein Aufruf für einen Konstruktor mit einem Parameter vom Typ generiert. In der Methode wird die aufgerufen ist kein Wert für spezifiziert, wird einfach verwendet. Der Vollständigkeit halber sei noch erwähnt, dass beim Schreiben, beim Lesen eines XML-Dokuments aufgerufen wird. parseMethod, printMethod Über das Attribut können wir eine Methode angeben, die aus der Zeichenkette aus dem XML-Dokument eine Instanz des im Attribut angegebenen Datentyps erzeugt. Diese Methode muss statisch sein und einen Parameter mit dem Datentyp definieren. Die Methode wird, wenn der Methodenaufruf nicht weiter qualifiziert wurde, in der im Attribut angegebenen Klasse vermutet. Geben wir hier keine Methode an, so versucht die JAXB-Implementierung, einen Konstruktor mit einem -Parameter aufrufen. Mit geben wir eine Methode an, die aus einer Instanz des angegebenen Datentyps eine Zeichenkette erzeugt. Geben wir nur den Methodennamen an, so wird diese Methode beim Marshalling bei dem Objekt selbst aufgerufen, die Methode benötigt dann keinen Parameter. Geben wir als Methode einen qualifizierten Namen einer statischen Methode an, so erwartet die JAXB-Implementierung, dass die Methode einen Parameter von dem Datentyp aufnimmt, der in eine Zeichenkette umgewandelt werden soll. Geben wir keinen Wert an, so ruft die JAXB-Implementierung die immer vorhandene Methode auf. Beide Methoden werden durch eine automatisch generierte Adapterklasse aufgerufen. Angenommen, wir haben eine Elementdeklaration in einem XML-Schema mit der folgenden Annotation versehen:
371
Jetzt wird eine an diese Elementdeklaration gebundene Java-Eigenschaft mit der Annotation versehen:
Diese verweist nun auf eine generierte Adapterklasse , welche die angegebene bzw. an der Klasse aufruft:
7.8.3.4
typesafeEnumClass
Mit dieser Einstellung kann die Art und Weise beeinflusst werden, wie eine im XMLSchema deklarierte Enumeration an eine Java-Enumerationsklasse gebunden wird.
Die -Konfiguration kann nur für -Deklarationen mit verschachtelten -Elementen angewendet werden.
372
7.8 XML-Elemente der Bindungskonfiguration Beschreibung der Einstellungsmöglichkeiten für typesafeEnum
Einstellung
Beschreibung
name
Name der generierten Java-Enumerationsklasse (wenn ein bestimmter Name verwendet werden soll)
map
Ob eine Java-Enumerationsklasse an diese Enumeration gebunden werden soll oder nicht. Standardmäßig gilt true, es wird also eine JavaEnumerationsklasse angelegt.
<javadoc>
Der Javadoc-Kommentar für die Java-Enumerationsklasse
Konfigurationen für einzelne Elemente der Enumeration
Das Unterelement kann entweder innerhalb von , aber auch eigenständig als XML-Annotation bei einem -Element stehen. Tabelle 7.16 Beschreibung der Einstellungsmöglichkeiten für typesafeEnumMember Einstellung
Beschreibung
name
Name des Elements in der Java-Enumerationsklasse
value
Wert des Elements.
<javadoc>
Der Javadoc-Kommentar für das entsprechende Element in der JavaEnumerationsklasse.
map Das Attribut ist, falls nichts anderes angegeben ist, . Fügt man also einer Enumeration in einem XML-Schema die Konfiguration hinzu, so reicht diese allein, um die Generierung einer Java-Enumerationsklasse anzustoßen. Je nachdem, welche XML-Typen in der globalen Einstellung aufgelistet sind, werden Java-Enumerationsklassen für entsprechende Enumerationen auch ohne die Konfiguration generiert. Hier können wir die Generierung der JavaEnumerationsklasse unterdrücken, indem wir auf setzen. Angenommen, wir haben die folgende Enumeration in unserem XML-Schema deklariert:
373
Das einfache Hinzufügen der Bindungskonfiguration reicht an dieser Stelle, um den Schema-Compiler anzuweisen, die folgende Klasse zu generieren:
name Über das Attribut kann der Name der gebundenen Java-Enumerationsklasse beeinflusst werden. Der hier angegebene Wert muss ein gültiger Java-Bezeichner sein und darf kein Paketpräfix enthalten. Bei benannten -Deklarationen ist die Angabe von optional. Bei anonymen, verschachtelten -Deklarationen muss hier unbedingt ein Name für die Java-Enumerationsklasse angegeben werden. Insgesamt darf es dabei zu keinen Namensüberschneidungen kommen. Um für die im folgenden Teilschema deklarierte Enumeration von , und eine Java-Enumerationsklasse generieren zu lassen, ist die Angabe von innerhalb der -Konfiguration unabdingbar:
374
7.8 XML-Elemente der Bindungskonfiguration
Der Schema-Compiler erstellt nun folgende Klasse mit einer als statischen, inneren Klasse formulierten Java-Enumeration :
typesafeEnumMember Ermöglicht, für einzelne Elemente einer XML-Enumeration einen anders lautenden Namen zu konfigurieren. Die Konfiguration kann dabei an eine -Deklaration innerhalb einer -Konfiguration gehängt werden oder unmittelbar an eine -Deklaration.
375
Wichtig ist hierbei, dass eine -Konfiguration auf der Ebene einer -Deklaration unbedingt über das Attribut den Bezug zu einem der Elemente in der verschachtelten XML-Enumeration herstellt:
Dieser Bezug ist bei einer -Konfiguration auf der Ebene einer -Deklaration bereits implizit gegeben hier führt die Angabe des Attributs zu Fehlermeldungen. Für ein Element einer XML-Enumeration kann über das Attribut der in Java zu verwendende Bezeichner konfiguriert werden. Bemühen wir also das oben eingeführte -Beispiel, und konfigurieren wir für die drei Wettertypen , und neue Namen:
Oben ist der neue Name für das Element in der -Deklaration verschachtelt, hier muss das Attribut angegeben werden, damit der Schema-Compiler erken-
376
7.8 XML-Elemente der Bindungskonfiguration nen kann, welches Element mit dem neuen Namen versehen werden soll. Die neuen Namen für und sind hingegen direkt für das entsprechende Element definiert, das Attribut entfällt. Der Schema-Compiler generiert für dieses Teilschema nun die folgende /-Klasse:
Die im Attribut angegebene Zeichenkette wird dabei unmodifiziert übernommen. 7.8.3.5
javadoc
In einer Bindungskonfiguration können zu vielen Elementen Javadoc-Kommentare hinzugefügt werden. Diese werden durch den Schema-Compiler beim Erstellen der JavaQuelltexte beachtet und an den entsprechenden Stellen eingefügt. So können generierte Klassen, Methoden und Variablen mit eigenen Kommentaren versehen werden. Die Verwendung des -Elementes ist an allen Stellen gleich. Dabei wird der innerhalb des Elementes gefundene Text im generierten Code in einen Javadoc-Kommentar gefasst. Oft enthalten Javadoc-Kommentare HTML-Formatierungen. Damit diese nicht mit den XMLElementen der Bindungskonfiguration kollidieren, werden HTML-Formatierungen wie folgt in das -Element aufgenommen:
Durch die Substitution vorkommender eckiger Klammen durch die Entität werden die HMTL-Formatierungen nicht als eigenständige XML-Elemente erkannt und können so problemlos durch den Schema-Compiler verarbeitet werden. Alternativ kann mit den Mitteln von XML der Kommentar explizit als CDATA angelegt werden. Hier brauchen wir dann auf XML-spezifische Besonderheiten keine Rücksicht zu nehmen.
377
7.8.3.6
dom
Mit der Konfiguration kann die Bindung an ein statisches Java-Modell für einzelne Teile eines Schemas unterdrückt werden. Salopp ausgedrückt können wir für einzelne Teile eines Schemas die JAXB-Mechanismen ausschalten. Diese Teile sind dann als generische, rohe DOM-Elemente (Document Object Model) verfügbar. Die entsprechenden Elemente werden einfach mit der folgenden Konfiguration markiert:
Das optionale Attribut kann auf eine bestimmte DOM-Implementierung verweisen. Standardmäßig wird hier der Wert angenommen, dieser entspricht den Klassen aus dem Paket , die mit der von Sun bereitgestellten JSE mitgeliefert werden. Andere DOM-Implementierungen sind denkbar, in der JAXB-Spezifikation jedoch noch nicht vorgesehen. Die Konfiguration kann für fast alle Elemente eines XML-Schemas vorgenommen werden vorausgesetzt, diese liegen nicht in der Wurzelebene des XML-Schemas. Elementdeklarationen mit Typdeklarationen mit und Generische Teile, die per deklariert sind Einzelne Teile von Typdeklarationen, die über , , oder zu Modellgruppen zusammengefasst wurden. Einzelne XML-Schemapartikel Dabei wird jedes Vorkommen des jeweiligen Schemaelements in dem Datenmodell durch eine Eigenschaft vom Typ ersetzt. Ausnahme sind Elementdeklarationen mit , bei denen nur die über definierten Referenzen auf das Element durch eine solche Eigenschaft ersetzt werden, während das eigentliche Element normal behandelt wird. Eine weitere Ausnahme stellen Teile dar, die über das Attribut eine Mischung aus XML-Elementen und Text erlauben. Solche Eigenschaften werden dann so angelegt, dass sie neben auch einfache -Instanzen aufnehmen können, in der Regel dann . Das folgende Beispiel illustriert den Einsatz der Konfiguration . Im abgedruckten Teilschema haben wir einen komplexen Typ angelegt. Dieser Typ besitzt zwei Unterelemente, und . Das Unterelement markieren wir mit dem Konfigurationselement als JAXB-freie Zone:
378
7.8 XML-Elemente der Bindungskonfiguration
Während das Element wie gewohnt an eine Java Bean-Eigenschaft vom Typ gebunden wird, wird der Inhalt von in einer Instanz von gespeichert:
7.8.3.7
inlineBinaryData
Ein relativ neuer Standard, XML-binary Optimized Packaging (XOP), dessen interessante Details unter http://www.w3.org/TR/xop10/ eingesehen werden können, beschäftigt sich mit den Möglichkeiten, XML-Dokumente mit binären Anteilen optimiert zu speichern. Die wesentliche Grundidee dahinter: Nur weil etwas auf der konzeptionellen Ebene Base64codiert ist, muss es das Speicherformat noch lange nicht sein. Unsichtbar für den Nutzer einer XML-API könnten auf diesem Standard basierende Optimierungen an der Art und Weise werkeln, wie binäre Anteile eines XML-Dokuments geladen bzw. gespeichert wer-
379
den. Diese Optimierungen können mit dem Konfigurationselement unterdrückt werden. Hierzu definieren wir für eine -, - oder -Deklaration mit einem Binärdatentyp wie ein leeres -Element. So es denn Optimierungen für das betroffene gab, sind diese jetzt deaktiviert.
7.8.3.8
factoryMethod
Über kann der Name der Factory-Methode in der durch den SchemaCompiler generierten Klasse modifiziert werden. Diese Einstellung kann sinnvoll sein, um Namenskonflikte durch gleichnamige Factory-Methoden aufzulösen.
Das Konfigurationselement kann für Element- und Typdefinitionen angewendet werden. Das Attribut wird vom Schema-Compiler analog zum originalen Element- bzw. Typnamen verwendet. Der Schema-Compiler stellt dem hier angegebenen Wert das Präfix voran. Geben wir für bspw. meinTyp an, so wird die Factory-Methode genannt.
380
Register A all 28 Annotationen 5 XmlAccessorOrder 227, 306 XmlAccessorType 205, 307 XmlAnyAttribute 284, 311 XmlAnyElement 286, 312 XmlAttachmentRef 314 XmlAttribute 216, 314 XmlElement 210, 316 XmlElementDecl 295, 318 XmlElementRefs 236, 261, 320, 321 XmlElements 251, 323 XmlElementWrapper 264, 324 XmlEnum 271, 325 XmlEnumValue 273, 326 XmlID 291, 327 XmlIDREF 290, 328 XmlInlineBinaryData 330 XmlJavaTypeAdapter 181, 276, 331, 332, 370 XmlList 249, 330 XmlMimeType 333 XmlMixed 266, 333 XmlNs 334 XmlRegistry 295, 334
XmlRootElement 201, 335 XmlSchema 239, 336 XmlSchemaType 338, 339 XmlTransient 223, 340 XmlType 228, 341 XmlValue 221, 342 anonymousTypeName nameXmlTransform 362 ANT 55 classpath-Task 57 javac-Task 57 property-Task 57 schemaGen-Task 66, 304 Target 56 Task 56 taskdef-Task 58 xjc-Task 68, 301 any 38 anyAttribute 38 attachmentRef 149 attribute 25
B baseType 169, 366 Beispielschemas 46 Binder 13, 112
381
Register Binding Framework 5, 10 Bindungsdeklarationen attachementRef 149 baseType 169, 366 choiceContentProperty 349 class 138, 363 collectionType 138, 347 dom 378 enableFailFastCheck 349 enableJavaNamingConventions 168, 352 Externe importieren 185 factoryMethod 380 fixedAttributeAsConstantProperty 150, 348 generateElementClass 352 generateElementProperty 147, 365 generateIsSetMethod 136, 349 generateValueClass 354 globalBindings 346 inlineBinaryData 379 javadoc 138, 360, 363, 365, 373, 377 javaType 172, 369 localScoping 357 mapSimpleTypeDef 356 nameXmlTransform 157, 360 optionalProperty 355 package 138, 359 parseMethod 371 printMethod 180, 371 property 138, 364 schemaBindings 359 typesafeEnumBase 351 typesafeEnumClass 162, 372 typesafeEnumMaxMembers 352 typeSafeEnumMember 163, 375 typesafeEnumMemberName 164, 352 underScoreBinding 159, 351 Build-Prozess 55 Build-Skript 56
C choice 28 choiceContentProperty 349 class 138, 363 collectionType 138, 347 complexType 27
D DataTypeConverter 177 Default-Namespace 24 DocumentBuilderFactory 54 dom 378
E element 25 elementName nameXmlTransform 361 enableFailFastCheck 349 enableJavaNamingConventions 168, 352 Entwicklungsumgebung 61 enumeration 32 Event Callbacks 12
F Facets 31 factoryMethod 380 fixedAttributeAsConstantProperty 150, 348 Flexible Unmarshalling 11
G generateElementClass 352 generateElementProperty 147, 365 generateIsSetMethod 136, 349 generateValueClass 354 globalBindings 346
H Hallo Welt 61
I ID 43 IDREF 43
382
Register import 42 include 42 inlineBinaryData 379
O
J
P
javac 57 javadoc 138, 377 class 363 package 360 property 365 typesafeEnum 373 typesafeEnumMember 373 javaType 172, 369 JAXB-API 71 JAXB-Bibliotheken 62 JAXBContext 72, 194, 295 JAXBElement 78, 85, 91, 194, 296 JAXBIntrospector 75 JAXB-Referenzimplementierung 62
package 138, 359 parseMethod 180, 371 printMethod 180, 371 property 364
L localScoping 357
M mapSimpleTypeDef 356 Marshaller 79 Callback-Mechanismus 108 formatierte Ausgabe 81 schemaLocation angeben 82 validieren 99 Marshalling 12, 79 modelGroupName nameXmlTransform 361
N Namespaces 21, 237 XPath 54 nameXmlTransform 157, 360 anonymousTypeName 362 elementName 361 modelGroupName 361 typeName 360
ObjectFactory 77, 295 optionalProperty 355
R restriction 31
S schema 20 schemaBindings 359 Schema-Compiler 5, 8 schemaGen 58, 304 Kommandozeilenbefehl 303 Schema-Generator 5, 9 schemaGen-Task 66 SchemaOutputResolver 197 sequence 28 simpleType 30 Structural Unmarshalling 11
T Target 56 Target-Namespace 22 Task 56 typeName nameXmlTransform 360 typesafeEnumBase 351 typesafeEnumClass 162, 372 typesafeEnumMaxMembers 352 typesafeEnumMember 163, 375 typesafeEnumMemberName 164, 352 Typkonversionen 177 Java-XML 343 XML-Java 345
383
Register
U underScoreBinding 159, 351 Unmarshaller Callback-Mechanismus 108 Unmarshalling 11, 87
V ValidationEvent 101 ValidationEventCollector 105 ValidationEventHandler 102 ValidationEventLocator 102 Validierung 12, 97
X xjc 58, 301 Kommandozeilenbefehl 299 xjc-Task 68 XmlAccessorOrder 227, 306 XmlAccessorType 205, 307 XmlAccessType 205 XmlAdapter 276 XmlAnyAttribute 284, 311 XmlAnyElement 312 XmlAttachmentRef 314 XmlAttribute 216, 314 namespace 241 XmlElement 210, 316 namespace 241 XmlElementDecl 295, 318 XmlRegistry 318 XmlElementRefs 236, 261, 320, 321 XmlElements 251, 323 XmlElementWrapper 264, 324 XmlEnum 271, 325 XmlEnumValue 273, 326 XmlID 291, 327 XmlIDREF 290, 328 XmlInlineBinaryData 330 XmlJavaTypeAdapter 181, 276, 331, 370 XmlList 249, 330 XmlMimeType 333 XmlMixed 266, 333
384
XmlNs 334, 337 XmlRegistry 295, 334 XmlRootElement 64, 86, 201, 335 XmlSchema 239, 336 XML-Schema 17 all 28 Annotationen 45 any 38 anyAttribute 38 attribute 26 Beispiele 46 choice 28 complexType 27 Default-Namespace 24 element 25 Elementreferenzen 43 enumeration 32 Facets 31 Globaler Datentyp 30 ID 43 IDREF 43 import 42 include 42 Kardinalitäten 37 Komplexe Datentypen 27 Lokaler Datentyp 29 Namespaces 21 restriction 31 schema 20 Schema-Import 40 sequence 28 simpleType 30 Target-Namespace 22 Vererbung 35 Wertebereiche definieren 33 Wildcards 38 xmlns 23 XMLSchema-instance 23 XmlSchemaType 338, 339 XmlTransient 223, 340 XmlType 341 namespace 240
Register propOrder 228 XmlValue 221, 342 mit Listen 250 XPath 48 Beispiele 51
Knotenselektion 50 Namespaces 54 Prädikate 51 XPathFactory 54 XQuery 48
385