Datenaustausch mit XML / JAXB

JAXB

JAXB

Alternativ zur Arbeit mit java.beans.XMLEncoder und java.beans.XMLDecoder gibt es die Möglichkeit, Java-Objekte mit Hilfe von "JAXB" in eine XML Datei zu serialisieren und sie analog auch wieder zu deserialisieren. Ein Vorteil liegt darin, über Annotations auf die Struktur des XML Dokuments Einfluß zu nehmen.

JAXB

Zur Demonstration erweitern wir die Klasse Mensch um sogenannte Annotations. Unter Anderem sorgt "@XmlRootElement" für die Bezeichnung des Rootelements und Namespaces, "@XmlType(propOrder)" für die Reihenfolge der Elemente bzw. Attribute im XML-Ergebnisdokument. "@XmlAttribute" stellt die Information in Attributschreibweise dar.


import javax.xml.bind.annotation.*;
@XmlRootElement(name = "MENSCH", 
     namespace = "wilfried-grupe.de/jaxb")
@XmlType( propOrder = 
     {"nachname", "NN", "vorname", "VN", "hobby", "alter", "a"})
public class Mensch implements IXMLTest, Serializable {
  private String vorname, nachname;  
  private String _hobby;
  private int alter;
  //Getters, Setters
  //Constructors
  //Implemented methods
  @XmlElement(nillable=true, required=false)
  public String getNachname() {
    return nachname;
  }
  @XmlAttribute(namespace="wilfried-grupe.de/jaxb/demo", 
  required=true, name = "HOBBY")
  public String getHobby() {
    return this._hobby;
  }
  @XmlAttribute(namespace="wilfried-grupe.de/jaxb/demo", name = "ALTER1")
  public int getAlter() {
    return alter;
  }
  @XmlAttribute(namespace="wilfried-grupe.de/jaxb/demo", name = "ALTER2")
  public int getA() {
    return this.getAlter();
  }
}

Nun generieren wir ein Objekt der Klasse mit Hilfe von JAXB und "marshallen" es in eine Datei.


  Mensch m = new Mensch("Wilma", "Wolkenlos", 33, "XML");
  try {
    JAXBContext context;
    context = JAXBContext.newInstance(Mensch.class);
    Marshaller mm = context.createMarshaller();
    mm.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
    mm.marshal(m, new File("C:/wg/jaxb_demo.xml"));
  } catch (PropertyException e) {
  } catch (JAXBException e) {
  }

Im Ergebnis finden wir ein XML Dokument mit mehreren Namespaces, Attributen und Elementen.


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns3:MENSCH 
  xmlns:ns2="wilfried-grupe.de/jaxb/demo" 
  xmlns:ns3="wilfried-grupe.de/jaxb" 
  ns2:HOBBY="XML" 
  ns2:ALTER1="33" 
  ns2:ALTER2="33">
    <nachname>Wolkenlos</nachname>
    <NN>Wolkenlos</NN>
    <vorname>Wilma</vorname>
    <VN>Wilma</VN>
</ns3:MENSCH>

Anmerkungen zu den Annotations (z.B. "@XmlElement(nillable=false, required=false)"):

Umgekehrt kann dieses XML Dokument "unmarshalled" und in ein Objekt der Klasse "Mensch" (dann ist der Zugriff auf "m.getHobby()" möglich) oder in ein Schnittstellenobjekt "IXMLTest" überführt werden.


  try {
    JAXBContext context;
    context = JAXBContext.newInstance(Mensch.class);
    Unmarshaller um = context.createUnmarshaller();
    IXMLTest m = (IXMLTest) um.unmarshal(new FileReader(
      "C:/wg/jaxb_demo.xml"));
    //System.out.println(m.getHobby());
    System.out.println(m.getVN());
    System.out.println(m.getNN());
    System.out.println(m.getA());
  } catch (FileNotFoundException e) {
  } catch (JAXBException e) {
  }

Ein Casten des generierten "Mensch"-Objekts auf "Person" läuft jedoch auf einen Fehler:


Person m = (Person) um.unmarshal(new FileReader(...));
Exception in thread "main" java.lang.ClassCastException: 
Mensch cannot be cast to Person

Ebenso erhalten wir einen Fehler, wenn der JAXBContext auf die Klasse "Person" ausgerichtet ist, der XML-Stream aber unverändert ist:


JAXBContext context = JAXBContext.newInstance(Person.class);
Person m = (Person) um.unmarshal(new FileReader(...));
unerwartetes Element (URI:"wilfried-grupe.de/jaxb", lokal:"MENSCH"). 
Erwartete Elemente sind (none)

JAXB und XML Schema: xjc.exe

JAXB läßt sich aber noch in anderer Form verwenden, nämlich ohne eine vordefinierte Klasse "Mensch", und nur via XML Schema und XML.


xjc.exe <SCHEMADATEI>

generiert aus einem XML Schema Dokument automatisch zwei Java-Dateien, von denen die eine ObjectFactory.java heißt, die andere (abhängig vom XML Schema) z.B. Orte.java. Mit diesen Klassen ist es möglich, die Informationen im XML Dokument sofort in Java-Objekte zu übernehmen. Darüberhinaus können wir dem Unmarshaller noch das XML Schema - Dokument zuweisen, dadurch wird das XML Input Dokument auch gleich validiert.


 public static void JaxBinding() {
  JAXBContext jaxbContext;
  SchemaFactory factory;
  Schema schema;
  Unmarshaller umsh;
  String xmlfile = "C:/wg/Ort_Elemente.xml";
  try {
    jaxbContext = JAXBContext.newInstance(Orte.class);
    factory = SchemaFactory.newInstance(
              XMLConstants.W3C_XML_SCHEMA_NS_URI);
    schema = factory.newSchema(
             new StreamSource(
                 new File("C:/wg/Ort_Elemente.xsd")));
    umsh = jaxbContext.createUnmarshaller();
    umsh.setSchema(schema);
    Orte x = (Orte)umsh.unmarshal(new File(xmlfile));
    java.util.List listOrte = x.ort;
    for (int i = 0; i < listOrte.size(); i++) {
     Orte.Ort ort = (Orte.Ort) listOrte.get(i);
     System.out.println(ort.getName());
     for(int m=0; m<ort.getMensch().size(); m++){
      Orte.Ort.Mensch mm = ort.getMensch().get(m);
      System.out.println( "\t" + mm.getVorname() 
                         + " " + mm.getName() );
     }
    } 
  } catch (JAXBException e) {
  } catch (SAXException e) {
  }
 }

pic/jaxb.png

Das Resultat:


Neustadt
	Hugo Holzflos
	Nicole Nixlos
	Stefan Sprachlos
	Stefan Sagblos
	Siggi Sorglos
	Heini Herzlos
Darmstadt
	Rudi Rhodos
	Karl Kolos
	Simone Sinnlos
	Horst Hirnlos
	Werner Wertlos
	Ludwig Lustlos
Kapstadt
	Willi Wasistlos
	Rita Ruhelos
	Susi Schlaflos
	Lotte Rielos
	Betty Bodenlos
	Martin Muehelos
	Liane Leinenlos

JAXB und XSD - konkret

Im vorliegenden Fall würde das XML-Dokument "<ns3:MENSCH xmlns:ns3='wilfried-grupe.de/jaxb'>" eingelesen und in ein Java-Objekt der via "xjc.exe" generierten Klasse überführt werden.

Aber ganz so einfach ist es nicht, weil wir es hier wieder einmal mit Namespaces zu tun haben, die sich auf drei verschiedene XML-Schema-Dateien verteilen.


<?xml version="1.0" encoding="utf-8"?>
<xs:schema
    elementFormDefault="qualified"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="vorname" type="xs:string"/>
  <xs:element name="nachname" type="xs:string"/>
  <xs:element name="NN" type="xs:string"/>
  <xs:element name="VN" type="xs:string"/>
  <xs:element name="age" type="xs:nonNegativeInteger"/>
</xs:schema>

Die erste XML Schema-Datei, nennen wir sie "Mensch_jaxb_2.xsd", definiert einige Standard-Elemente mit Standard-Typen; hier ist noch kein TargetNamespace deklariert.


<?xml version="1.0" encoding="utf-8"?>
<xs:schema 
    targetNamespace="wilfried-grupe.de/jaxb/demo"
    xmlns:ns2="wilfried-grupe.de/jaxb/demo"
    elementFormDefault="qualified"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:attribute 
      name="HOBBY" 
      type="xs:string"/>
  <xs:attribute 
      name="ALTER1" 
      type="xs:nonNegativeInteger"/>
  <xs:attribute 
      name="ALTER2" 
      type="xs:nonNegativeInteger"/>
</xs:schema>

Die zweite XML Schema-Datei, nennen wir sie "Mensch_jaxb_ns2.xsd", deklariert enige Attribute, aber mit einem TargetNamespace.


<?xml version="1.0" encoding="utf-8"?>
<xs:schema
  targetNamespace="wilfried-grupe.de/jaxb"
  xmlns:ns3="wilfried-grupe.de/jaxb"
  xmlns:ns2="wilfried-grupe.de/jaxb/demo"
    elementFormDefault="qualified"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:import namespace="wilfried-grupe.de/jaxb/demo" 
       schemaLocation="Mensch_jaxb_ns2.xsd"/>
  <xs:import schemaLocation="Mensch_jaxb_2.xsd"/>
  <xs:complexType name="MENSCH_TYP">
    <xs:sequence>
      <xs:element ref="nachname"/>
      <xs:element ref="NN"/>
      <xs:element ref="vorname"/>
      <xs:element ref="VN"/>
    </xs:sequence>
    <xs:attribute ref="ns2:HOBBY" use="required"/>
    <xs:attribute ref="ns2:ALTER1" use="required"/>
    <xs:attribute ref="ns2:ALTER2" use="required"/>
  </xs:complexType>
  <xs:element name="MENSCH" type="ns3:MENSCH_TYP"/>
</xs:schema>

Die dritte XML Schema-Datei "Mensch_jaxb.xsd" import die beiden anderen XSD-Dateien und arbeitet mit den dort deklarierten Elementen und (Namespace-abhängigen) Attributen.

Nun kommt das Kommando "xjc.exe" und die automatische Generierung von Java-Klassen ins Spiel.


xjc.exe Mensch_jaxb.xsd

generiert das Java-Package "de.wilfried_grupe.jaxb" und die Klassen "package-info.java", "ObjectFactory.java" und "MENSCHTYP.java" (aus Platzgründen spare ich mir den Abdruck des Quellcodes). Der weitere Ablauf ist analog wie oben dargestellt.

Konvertierung zu Person

Die De-Serialisierung von XML in Java-Objekte via JAXB funktioniert also, soweit die generierten Objekte von einer bestimmten Klasse sind. Noch offen ist die Deserialisierung eines XML-Inputs in Objekte anderer Klassen, etwa "Person": schließlich soll das XML-Abbild eines Objekts ja (auch) dem Datenaustausch dienen.

Zwar wäre es möglich, die Daten aus dem JAXB-Objekt einzeln in ein Objekt der Klasse "Person" zu mappen.


de.wilfried_grupe.jaxb.MENSCHTYP jaxbp;
jaxbp = (de.wilfried_grupe.jaxb.MENSCHTYP)
    jaxbUnmarshaller.unmarshal(
        new File("C:/wg/jaxb_demo.xml"));
Person p = new Person();
p.setFirstname(jaxbp.getVorname());
p.setAge(jaxbp.getALTER1().intValue());
p.setLastname(jaxbp.getNachname());

Es gibt aber eine einfachere Alternative: XSLT. Um mittels JAXB ein Objekt der Klasse "Person" generieren zu können, wird die folgende XML-Struktur benötigt:


<Person>
    <a>33</a>
    <age>33</age>
    <firstname>Wolkenlos</firstname>
    <lastname>Wilma</lastname>
    <NN>Wilma</NN>
    <VN>Wolkenlos</VN>
</Person>

Ein XSL-Stylesheet, das das vorher dargestellte XML-Dokument (ns3:MENSCH xmlns:ns3="wilfried-grupe.de/jaxb" ...) in die Zielstruktur "<Person>" konvertieren kann, sehen wir hier:


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:ns2="wilfried-grupe.de/jaxb/demo"
  xmlns:ns3="wilfried-grupe.de/jaxb">
  <xsl:output method="xml" indent="yes" />
  <xsl:template match="/">
    <xsl:for-each select="ns3:MENSCH">
      <Person>
        <a><xsl:value-of select="@ns2:ALTER1"/></a>
        <age><xsl:value-of select="@ns2:ALTER2"/></age>
        <firstname><xsl:value-of select="vorname"/></firstname>
        <lastname><xsl:value-of select="nachname"/></lastname>
        <NN><xsl:value-of select="NN"/></NN>
        <VN><xsl:value-of select="VN"/></VN>
      </Person>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

Mit der folgenden Java-Logik wird eine XSL-Transformation gestartet; das Ergebnis der Transformation wird umgehend mit JAXB in ein Objekt der Klasse Person übernommen. Dabei war es notwendig, das Ergebnis der Transformation in ein ByteArrayOutputStream zu leiten, um den Output-Stream der XSL-Transformation in einen Input-Stream für JAXB umzuwandeln.


  try {
    TransformerFactory tfactory;
    Transformer transformer;
    InputStream is;
    JAXBContext context;
    tfactory = TransformerFactory.newInstance();
    transformer = tfactory.newTransformer(
      new StreamSource(
        new File("Mensch2Person.xsl")));
    Source s = new StreamSource(
      new File("C:/wg/jaxb_demo.xml"));
    ByteArrayOutputStream outputStream;
    outputStream = new ByteArrayOutputStream();
    Result r = new StreamResult(outputStream);      
    transformer.transform(s, r);
    System.out.println("XSL-Transformation OK");
    is = new ByteArrayInputStream(
      outputStream.toByteArray());
    context = JAXBContext.newInstance(Person.class);
    Unmarshaller um = context.createUnmarshaller();
    Person m = (Person) um.unmarshal(is);
    System.out.println("Instanz von Person erstellt");
    System.out.println(m.getVN());
    System.out.println(m.getNN());
    System.out.println(m.getA());
  } catch (TransformerConfigurationException e) {
  } catch (TransformerFactoryConfigurationError e) {
  } catch (TransformerException e) {
  } catch (JAXBException e) {
  }

Ein Vorteil für den XSLT-Ansatz liegt darin, daß sich die fachlichen Anforderungen der Konvertierungen häufig ändern können. Jede Änderung der Klassen "Mensch" (Daten-Lieferant) oder "Person" (Daten-Konsument) kann eine Strukturänderung der gesendeten oder der für JAXB erforderlichen XML-Dokumente nach sich ziehen. Diese Konvertierung kann sehr einfach durch XSLT abgebildet werden.

In diesem Zusammenhang ist vorteilhaft, daß wir in XSLT mehrere Transformationslogiken einbauen können, die verschiedene XML Input-Strukturen in ein einheitliches Zielformat konvertieren, das dann via JAXB in Objekte überführt wird. Damit ist der Daten-Consumer auch in der Lage, mehrere Datenlieferanten unterschiedlichen Typs (.NET, PHP, ...) anzusprechen.

Ebenso leichter lösbar ist damit das häufige Problem, daß der Datenlieferant seine XML-Struktur teilweise (minor change) oder grundlegend (major change) ändert und die Data-Consumer einen zeitlich punktgenauen Switch der Konvertierungslogik vornehmen müssen.

Ein weiterer Vorteil ist, daß der ursprüngliche Inputstream (also der Output der "Mensch"-Klasse) keineswegs in XML vorliegen muß, sondern ein anderes Format haben kann, meinetwegen CSV. Es ist in XSLT 2.0 durchaus möglich, auch Nicht-XML-Formate einzulesen ("unparsed text") und in XML zu konvertieren.

pic/JAXB_XSLT.jpg

Die JAXB vorgeschaltete XSLT-Konvertierung bringt erhebliche Vorteile für die Flexibilität des Datenaustausches. Ohne die verarbeitenden Klassen selbst ändern zu müssen (Neukompilierung nicht erforderlich), kann die komplette XML-XSL-XML-Konvertierung in einer Vorstufe erfolgen.

Sehr sinnvoll ist, das zu transformierende XML Dokument "jaxb_demo.xml" vor der XSL-Transformation gegen ein XML Schema zu validieren. Auf diese Weise sollte sichergestellt werden, daß sich die Struktur des XML Dokuments nicht grundlegend geändert hat, mit der möglichen Folge eines gravierenden Informationsverlustes.

wg / 6. Januar 2018



Fragen? Anmerkungen? Tips?

Bitte nehmen Sie Kontakt zu mir auf:

Vorname
Nachname
Mailadresse







Vielen Dank für Ihr Interesse an meiner Arbeit.


V.i.S.d.P.: Wilfried Grupe * Klus 6 * 37643 Negenborn

Mobil: 0151. 750 360 61 * eMail: info2018@wilfried-grupe.de

www.wilfried-grupe.de/JAXB.html