user504342 user504342 - 3 days ago 5
Java Question

Using java.util.Locale in JAXB generated classes using XmlAdapter

The problem:

Based on the following documentation provided by Oracle about the use of the java.util.Locale: [Internationalization: Understanding Locale in the Java Platform] , I have the following question related to JAXB and the Locale.

I have an XML file that looks like this:

<?xml version="1.0" encoding="utf-8"?>
<dataschema>
<delimited>
<locale language="en" country="US" variant="SiliconValley" />
</delimited>
</dataschema>


Which is based on the following XML schema:

<?xml version="1.0" encoding="utf-8" ?>
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="dataschema">
<xs:complexType>
<xs:choice>
<xs:element minOccurs="1" maxOccurs="1" name="delimited" type="DelimitedSchemaType"/>
<xs:element minOccurs="1" maxOccurs="1" name="fixedwidth" type="FixedWidthSchemaType"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:complexType name="DelimitedSchemaType">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="1" name="locale" type="LocaleType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="FixedWidthSchemaType">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="1" name="locale" type="LocaleType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="LocaleType">
<xs:attribute name="language" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[a-z]{2,3}"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="country" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[A-Z]{2}"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="variant" use="optional">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[A-Z]{2}"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:schema>


Now the problem is that I get the following generated classes for the LocaleType xml complexType which does not seem to reflect the actual java.util.Locale datatype within the generated DelimitedDataSchema class. I would have expected this to be of type java.util.Locale and NOT of type org.mylib.schema.LocaleType?

The generated classes by JAXB 2.x are:

Dataschema.java:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"delimited",
"fixedwidth"
})
@XmlRootElement(name = "dataschema")
public class Dataschema {

protected DelimitedDataSchema delimited;
protected FixedWidthDataSchema fixedwidth;

public DelimitedDataSchema getDelimited() {
return delimited;
}

public void setDelimited(DelimitedDataSchema value) {
this.delimited = value;
}

public FixedWidthDataSchema getFixedwidth() {
return fixedwidth;
}

public void setFixedwidth(FixedWidthDataSchema value) {
this.fixedwidth = value;
}
}


DelimitedDataSchema.java:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "DelimitedSchemaType", propOrder = {
"localeType"
})
public class DelimitedDataSchema {

@XmlElement(required = true)
protected LocaleType locale;

public LocaleType getLocale() {
return locale;
}

public void setLocale(LocaleType value) {
this.locale = value;
}
}


LocaleType:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "LocaleType")
public class LocaleType {

@XmlAttribute(name = "language", required = true)
protected String language;
@XmlAttribute(name = "country", required = true)
protected String country;
@XmlAttribute(name = "variant")
protected String variant;

public String getLanguage() {
return language;
}

public void setLanguage(String value) {
this.language = value;
}

public String getCountry() {
return country;
}

public void setCountry(String value) {
this.country = value;
}

public String getVariant() {
return variant;
}

public void setVariant(String value) {
this.variant = value;
}
}


I bravely followed the instructions in the following blog posts from Blaise Doughan about JAXB XmlAdapters: JAXB and Package Level XmlAdapters and also XmlAdapter - JAXB's Secret Weapon

So I created an XmlAdapter myself, hoping that the generated class (DelimitedDataSchema) would contain the java.util.Locale return datatype in the getter and the java.util.Locale parameter datatype in the setter. Which I mistakenly assumed.

LocaleXmlAdapter.java:

public class LocaleXmlAdapter extends XmlAdapter<org.mylib.schema.LocaleType, java.util.Locale> {
@Override
public java.util.Locale unmarshal(org.mylib.schema.LocaleType pSchemaLocale) throws Exception {
if (pSchemaLocale == null) {
throw new NullPointerException("LocaleXmlAdapter.unmarshal(...) received a NULL literal.");
}

java.util.Locale mLocale = null;
String mLanguage = pSchemaLocale.getLanguage().toLowerCase();
String mCountry = pSchemaLocale.getCountry().toUpperCase();
String mVariant = pSchemaLocale.getVariant();

if (mVariant == null) {
mLocale = new java.util.Locale(mLanguage, mCountry);
} else {
mLocale = new java.util.Locale(mLanguage, mCountry, mVariant);
}
return mLocale;
}

@Override
public org.mylib.schema.LocaleType marshal(java.util.Locale pJavaLocale) throws Exception {
if (pJavaLocale == null) {
throw new NullPointerException("LocaleXmlAdapter.marshal(...) received a NULL literal.");
}

org.mylib.schema.LocaleType mLocale = new org.mylib.schema.LocaleType();
mLocale.setLanguage(pJavaLocale.getLanguage().toLowerCase());
mLocale.setCountry(pJavaLocale.getCountry().toUpperCase());
String mVariant = pJavaLocale.getVariant();
if (mVariant != null) {
mLocale.setVariant(mVariant);
}

return mLocale;
}
}


To let the JAXB library know that it must use the LocaleXmlAdapter, I provided the library with an external binding file, in which the LocaleXmlAdapter is defined for the Locale class.

External JAXB binding file:

<?xml version="1.0" encoding="utf-8"?>
<jaxb:bindings jaxb:version="2.1" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
schemaLocation="dataschema.xsd" node="/xs:schema">

<jaxb:schemaBindings>
<jaxb:package name="org.mylib.schema">
<jaxb:javadoc>
Package level documentation for generated package org.mylib.schema.
</jaxb:javadoc>
</jaxb:package>
</jaxb:schemaBindings>

<jaxb:bindings node="//xs:complexType[@name='LocaleType']">
<jaxb:class name="LocaleType"/>
<jaxb:property>
<jaxb:baseType name="org.mylib.schema.LocaleXmlAdapter"/>
</jaxb:property>
</jaxb:bindings>

<jaxb:bindings node="//xs:complexType[@name='DelimitedSchemaType']">
<jaxb:class name="DelimitedDataSchema"/>
</jaxb:bindings>

<jaxb:bindings node="//xs:complexType[@name='FixedWidthSchemaType']">
<jaxb:class name="FixedWidthDataSchema"/>
</jaxb:bindings>
</jaxb:bindings>


Now the weird part, which I obviously don't get, is that I would have expected that the JAXB library would translate the org.mylib.schema.LocaleType type into the java.util.Locale type for the DelimitedDataSchema class, so you would see the following method signatures in the DelimitedDataSchema class:


  • public java.util.Locale getLocale() {}

  • public void setLocale(java.util.Locale value) {}



What I want to accomplish is that the java.util.Locale datatype is used instead of the org.mylib.schema.LocaleType datatype. How else do I get the translation done between the user code and the JAXB generated code? I can't call the LocaleXmlAdapter class myself to translate the locale type for me, that must be done by the JAXB library, but I do want to call: getLocale() and in return get a java.util.Locale datatype.

What am I doing 'wrong'?

Update:

So far I figured out that the <jaxb:baseType /> should NOT be used. Instead the <xjc:javaType > should be used within the binding file as a child element of <jaxb:baseType>.
I also falsely assumed that the <jaxb:baseType> had to be defined under the LocaleType node, which is NOT true. It must be defined under the element node of the DelimitedSchemaType node and FixedWidthSchemaType node. Like this:

...
<jaxb:bindings node="//xs:complexType[@name='DelimitedSchemaType']">
<jaxb:property>
<jaxb:baseType>
<xjc:javaType name="org.mylib.schema.LocaleType" adapter="org.mylib.schema.LocaleXmlAdapter"/>
</jaxb:baseType>
</jaxb:property>
</jaxb:bindings>
...


This should be correct, but somehow the XJC compiler produced compile errors.
The following error occurs:

[ERROR] Error while parsing schema(s).Location [ file:/C:/IdeaProjects/JaxbMarshalling/src/main/resources/dataschema.xjb{25,113}].
com.sun.istack.SAXParseException2; systemId: file:/C:/IdeaProjects/JaxbMarshalling/src/main/resources/dataschema.xjb; lineNumber: 25; columnNumber: 113; compiler was unable to honor this conversion customization. It is attached to a wrong place, or its inconsistent with other bindings.
at com.sun.tools.xjc.ErrorReceiver.error(ErrorReceiver.java:86)
etc.


It keeps nagging about "compiler was unable to honor this conversion customization. It is attached to a wrong place, or its inconsistent with other bindings.", while there is no mistake within the bindings file to be found.

I have improved my bindings file, but still something isn't 'right'. I can't pinpoint the exact location where it goes 'wrong'.

BTW: I am using the following tools:


  • Oracle Java JDK 64-bit, version 1.8.0_112-b15

  • xjc, version 2.2.8-b130911.1802 (shipped with above mentioned JDK)

  • maven3, version 3.3.9

  • IntelliJ IDEA 2016.3, version 163.7743.44

  • maven-jaxb2-plugin, version 0.13.1



Because I am struggling with this for a few days now, I have started a bounty. The person that really solves the problem using the external bindings file and correct annotations gets my bounty points.

Answer

This question/problem has several flaws in it that first must be addressed in order to successfully answer the question/problem. Because you requested a detailed canonical answer, which addresses ALL concerns, here it is:

First, some observations:

  1. The DelimitedDataSchema.java and FixedWidthDataSchema.java both contain an object field 'locale' of datatype org.mylib.schema.LocaleType which must be corrected to the java.util.Locale datatype.
  2. The object field 'locale' within DelimitedDataSchema.java and FixedWidthDataSchema.java must contain the @XmlJavaTypeAdapter annotation, which is now absent.
  3. The object field 'locale' within DelimitedDataSchema.java and FixedWidthDataSchema.java already contain an @XmlElement annotation, which now only specifies the 'required' element. It must also include the 'type' element.
  4. The to be (un)marshalled 'locale' xml data is a complexType datastructure, namely: LocaleType, having only three attributes of type xs:string. The current Xml Java Compiler (= XJC) does not (yet) support complexTypes to be processed into the correct annotations.
  5. The node LocaleType in the external binding file contains a wrongly defined property. The 'localeType' only has three attributes, which are not defined.
  6. The defined LocaleXmlAdapter is wrongly defined, and should be moved to the property (xml element) of the DelimitedDataSchema and FixedWidthDataSchema class.
  7. The <jaxb:baseType> declarations of the field/element 'locale' in the DelimitedDataSchema and FixedWidthDataSchema class is missing.
  8. You have not specified your maven pom.xml file in your question. Please include this too when asking your question next time. It provides valuable information to the users who want to answer your question as precise as possible. And even for new seekers coping with the same questions you had, will find the answers they need in your post. Be as complete as possible.

Second, some modifications/addition:

  1. Your XML Schema file is well-defined. So, no modifications are needed in this file.
  2. Your bindings file (datasource.jxb) does contain flaws.

In order to correct these flaws, do the following:

  1. recommendation: rename the class name of the LocaleType to XmlLocale, so you can easily match the XmlLocale.class and LocaleXmlAdapter.class together, without getting confused between java.util.Locale and org.mylib.schema.Locale (which is now renamed to org.mylib.schema.XmlLocale)
  2. correction: remove the element and child element completely, because these MUST NOT be specified at the level of LocaleType, but at the level of the 'locale' element within the DelimitedSchemaType and FixedWidthSchemaType.
  3. correction: within the complexType DelimitedSchemaType and FixedWidthSchemaType define a child XPath node that represents the 'locale' element specified in these complexTypes.
  4. correction: place under the 'locale' element node defined within the complextType DelimitedSchemaType and FixedWidthSchemaType a <jaxb:property> element with a child <jaxb:baseType> element. Give the property a name. This can be the same name as specified within the XML Schema, but can also be named differently. If you name it differently than the XML Schema, be aware that your object field is called like the <jaxb:property> name and not the XML Schema element name. This has also effect on the getter and setter method names.
  5. correction: Define the datatype of the property using the <jaxb:baseType> name. The wanted object field datatype is of type java.util.Locale. Thus you get: <jaxb:baseType name="java.util.Locale"/>. You do NOT specify the org.mylib.schema.XmlLocale datatype as the name!
  6. addition: In order to add the @XmlJavaTypeAdapter to the element 'locale' in DelimitedDataSchema and FixedWidthDataSchema classes, you normally would specify this using the <xjc:baseType> element. But because the Xml Java Compiler (=XJC) does NOT (yet) handle this type of conversion between complexTypes and Java datatypes, you cannot use the default specification as described in the documentation. This just doesn't work (yet) with the current XJC:

    <jaxb:baseType>
        <xjc:javaType name="org.mylib.schema.XmlLocale" adapter="org.mylib.schema.LocaleXmlAdapter"/>
    </jaxb:baseType>
    

    So you must provide the @XmlJavaTypeAdapter annotation yourself. This is where the jaxb2-basics-annotate plugin [link] comes in handy. With this plugin you can annotate ANY existing Java annotation at any location within a Java class: class-level, field-level, method-level, etc. In order to annotate the 'missing' annotation you have to set up a few things, which are described later on.
    In the bindings file you have to specify the following settings:
    a) specify the namespace (annox) used by the plugin in the root of your bindings file;
    b) specify the extensionBindingPrefixed in the root of your bindings file;

    <jaxb:bindings jaxb:version="2.1" xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
        xmlns:annox="http://annox.dev.java.net"
        schemaLocation="dataschema.xsd" node="/xs:schema"
        jaxb:extensionBindingPrefixes="xjc annox">
    

    c) specify the preferred annotation using the element.
    Because the adapter needs to be specified on the object field 'locale' of the DelimitedDataSchema and FixedWidthDataSchema classes, the annotate element must be specified within the <jaxb:bindings> node of the 'locale':

    <jaxb:bindings node="//xs:complexType[@name='DelimitedSchemaType']">
        <jaxb:class name="DelimitedDataSchema"/>
        <jaxb:bindings node=".//xs:element[@name='locale']">
            <jaxb:property name="locale">
                <jaxb:baseType name="java.util.Locale" /> 
            </jaxb:property> 
            <annox:annotate target="field">
                @javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter(value = org.mylib.schema.LocaleXmlAdapter.class)
            </annox:annotate>
        <jaxb:bindings> 
    </jaxb:bindings>
    

    Likewise for the FixedWidthSchemaType complexType. Notice that the FULL package name and class name, including the annotation parameter(s) must be specified!

  7. recommendation: make sure that all your nodes within the bindings file are specified and mapped to the XML Schema elements and attributes. This is recommended because by specifying each node and giving each node a name (can be the same or different than the XML Schema name) in the bindings file you make sure that the generated classes, fields, getters/setters are named the way YOU like it. This prevents a lot of manual work later on, when someone decides to rename the XML Schema names, which obviously leads to recompiling of java classes and reference mismatched with other (manually written) classes. For example: your manually written XmlAdapter class: LocaleXmlAdapter, which refers to method names in the generated XmlLocale class. What you actually do is decouple the XML Schema names from the Java names using one single binding file. That saves you from a lot of trouble later on (trust me: I've seen it happen many times in different development teams)! That is just wasting valuable time and money. With this binding file fully specified, you only have to adopt the name of the XML Schema node, the Java-side does not change!

    Your binding file now results in this complete bindings file:

    <jaxb:schemaBindings>
        <jaxb:package name="org.mylib.schema"/>
    </jaxb:schemaBindings>
    
    <jaxb:bindings node="//xs:complexType[@name='DelimitedSchemaType']">
        <jaxb:class name="DelimitedDataSchema"/>
        <jaxb:bindings node=".//xs:element[@name='locale']">
            <jaxb:property name="locale">
                <jaxb:baseType name="java.util.Locale" />
            </jaxb:property>
                <annox:annotate target="field">@javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter(value = org.mylib.schema.LocaleXmlAdapter.class)
            </annox:annotate>
        </jaxb:bindings>
    </jaxb:bindings>
    
     <jaxb:bindings node="//xs:complexType[@name='FixedWidthSchemaType']">
        <jaxb:class name="FixedWidthDataSchema"/>
        <jaxb:bindings node=".//xs:element[@name='locale']">
            <jaxb:property name="locale">
                <jaxb:baseType name="java.util.Locale"/>
            </jaxb:property>
                <annox:annotate target="locale">@javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter(value = org.mylib.schema.LocaleXmlAdapter.class)
            </annox:annotate>
        </jaxb:bindings>
    </jaxb:bindings>
    
    <jaxb:bindings node="//xs:complexType[@name='LocaleType']">
        <jaxb:class name="XmlLocale"/>
        <jaxb:bindings node=".//xs:attribute[@name='language']">
            <jaxb:property name="language"/>
        </jaxb:bindings>
        <jaxb:bindings node=".//xs:attribute[@name='country']">
            <jaxb:property name="country"/>
        </jaxb:bindings>
        <jaxb:bindings node=".//xs:attribute[@name='variant']">
            <jaxb:property name="variant"/>
        </jaxb:bindings>
    </jaxb:bindings>
    


3. Your maven pom.xml file is not specified, but for the complete overview of this topic, it is mentioned here as well.

There are a few things to take into consideration when generating JAXB classes using maven.

In order to do the 'right thing', do the following: - Since JAXB XJC is specific on which Java version you compile and run it, you should specify to which source and target Java version it must be compiled. This can be accomplished with the maven-compiler-plugin. Instead of letting XJC compile the Java classes with default level, version 1.5, you now specify version 1.8.

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>

        <groupId>org.mylib</groupId>
        <artifactId>mytool</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>jar</packaging>
        <name>project-name</name>

        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>

        <build>
            <resources>
                <resource>
                    <directory>${pom.basedir}/src/main/resources</directory>
                </resource>
            </resources>

            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.6.0</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>

                <plugin>
                    <groupId>org.jvnet.jaxb2.maven2</groupId>
                    <artifactId>maven-jaxb2-plugin</artifactId>
                    <version>0.13.1</version>
                    <executions>
                        <execution>
                            <phase>generate-sources</phase>
                            <goals>
                                <goal>generate</goal>
                            </goals>
                            <configuration>
                                <!-- allow specific vendor extension bindings (jaxb:extensionBindingPrefixes) -->
                                <extension>true</extension>
                                <!-- Generate lots of output (for debug purposes) -->
                                <verbose>true</verbose>
                                <locale>en</locale>
                                <specVersion>2.2</specVersion>
                                <schemaLanguage>XMLSCHEMA</schemaLanguage>

                                <schemaDirectory>src/main/resources</schemaDirectory>
                                <schemaIncludes>
                                    <schemaInclude>dataschema.xsd</schemaInclude>
                                </schemaIncludes>

                                <bindingDirectory>src/main/resources</bindingDirectory>
                                <bindingIncludes>
                                    <bindingInclude>dataschema.xjb</bindingInclude>
                                </bindingIncludes>

                                <generateDirectory>${project.build.directory}/generated-sources/jaxb2</generateDirectory>
                                <args>
                                    <!-- covered by the jaxb2-basics-annotate plugin (YOU ONLY NEED THIS ONE IN YOUR SITUATION!) -->
                                    <arg>-Xannotate</arg>
                                    <!-- covered by the jaxb2-basics-plugin -->
                                    <arg>-Xsimplify</arg>
                                    <arg>-XtoString</arg>
                                    <arg>-Xequals</arg>
                                    <arg>-XhashCode</arg>
                                </args>
                                <plugins>
                                    <!-- plugin for generated toString, hashCode, equals methods -->
                                    <plugin>
                                        <groupId>org.jvnet.jaxb2_commons</groupId>
                                        <artifactId>jaxb2-basics</artifactId>
                                        <version>1.11.1</version>
                                    </plugin>
                                    <!-- plugin for adding specified annotations (YOU ONLY NEED THIS ONE IN YOUR SITUATION!) -->
                                    <plugin>
                                        <groupId>org.jvnet.jaxb2_commons</groupId>
                                        <artifactId>jaxb2-basics-annotate</artifactId>
                                        <version>1.0.2</version>
                                    </plugin>
                                </plugins>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </project>

Third, some results (before and after):

When executing the maven compile phase before the modifications in the bindings file and maven pom.xml file, you ended up with the following DelimitedDataSchema class (excerpt is shown):

package org.mylib.schema;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "DelimitedSchemaType", propOrder = {
   "locale"
})
public class DelimitedDataSchema {

@XmlElement(required = true)
protected XmlLocale locale; // XmlLocale instead of Locale (java.util.Locale)

}

When executing the maven compile phase after the modifications in the bindings file and maven pom.xml file, you ended up with the following DelimitedDataSchema class (excerpt is shown):

package org.mylib.schema;

import java.util.Locale;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "DelimitedSchemaType", propOrder = {
    "locale"
})
public class DelimitedDataSchema {

    @XmlElement(required = true, type = XmlLocale.class)
    @XmlJavaTypeAdapter(LocaleXmlAdapter.class)
    protected Locale locale;

}   

The result is clear: the desired solution is accomplished by using the above mentioned solution. Notice that the @XmlElement now contains an extra parameter: type = XmlLocale.class. Notice that the @XmlJavaTypeAdapter only contains the LocaleXmlAdapter.class parameter. You can also write the same situation differently, like:

@XmlElement(required = true)
@XmlJavaTypeAdapter(type = XmlLocale, value = LocaleXmlAdapter.class)

Which accomplishes exactly the same. But remember that you need to specify the <jaxb:baseType name="java.util.Locale"/> because the object field must be defined as java.util.Locale. Currently, this is the ONLY way to accomplish this. You cannot just ONLY specify the @XmlJavaTypeAdapter(type = XmlLocale, value = LocaleXmlAdapter.class) annotation using the jaxb2-basics-annotate plugin because the object field property is then NOT changed into the java.util.Locale datatype.

In the near future I hope the Xml Java Compiler (XJC) will support complexTypes and distinguishes the datatypes being marshalled/unmarshalled itself by inspecting the custom written XmlAdapter parameter and return types.

Fourth, the evidence (Marshalling and Unmarshalling)

There is a saying that says: "The proof is in the eating of the pudding", which means something like this: if you have created something and it looks good, the only way to know if it really is good (i.e. that it works) is by testing is. In this case marshalling/unmarshalling it. In the case of the pudding, tasting it is the only way to be absolutely sure the recipe is great!

Main.java:

package org.mylib;

import org.mylib.schema.Dataschema;
import org.mylib.schema.DelimitedDataSchema;
import org.mylib.schema.FixedWidthDataSchema;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import java.io.Reader;
import java.io.StringReader;

public class Main {

    public static void main(String[] args) throws Exception {
        Dataschema ds = null;

        Reader xmlFileDelimited = new StringReader("<dataschema>\n" +
                "  <delimited>\n" +
                "    <locale language=\"en\" country=\"us\" />\n" +
                "  </delimited>\n" +
                "</dataschema>");

        try {
            JAXBContext jc = JAXBContext.newInstance(Dataschema.class);
            Unmarshaller um = jc.createUnmarshaller();
            ds = (Dataschema) um.unmarshal(new StreamSource(xmlFileDelimited));
        } catch (JAXBException e) {
            e.printStackTrace();
        }

        if (ds == null) {
            throw new NullPointerException("null literal as output of marshaller!");
        }

        DelimitedDataSchema delimited = ds.getDelimited();
        FixedWidthDataSchema fixedwidth = ds.getFixedwidth();

        if (((fixedwidth == null) && (delimited == null)) || ((fixedwidth != null) && (delimited != null))) {
            throw new IllegalStateException("schemas cannot be both absent or be both present at the same time!"); // (because of <choice> xml schema)!
        }

        if (delimited != null) {
            // very primitive code for proving correctness
            System.out.println(delimited.getLocale().toString());
        }

        if (fixedwidth != null) {
            // very primitive code for proving correctness
            System.out.println(fixedwidth.getLocale().toString());
        }
    }
}

Marshalling omitted, that is left to the reader to implement.

End of example.

Note that JAXB itself DOES marshal and unmarshal the org.mylib.schema.XmlLocale and java.util.Locale using the LocaleXmlAdapter pretty well. So, it is not the JAXB core that is the troublemaker in this case. The Xml Java Compiler is the one to blame for not accepting complexTypes (yet). These are the current limitations/shortcommings of the XJC and will hopefully be solved in the near future!

Last words to consider: If the beaten path doesn't get you there, then take the road that does. Slow and steady wins the race!

Some footnotes: Set the source and target compiler level to 1.8 instead of 1.5 (which is the default) using the maven-compiler-plugin.

Important note concerning the maven-compiler-plugin: "Merely setting the target option does not guarantee that your code actually runs on a JRE with the specified version. The pitfall is unintended usage of APIs that only exist in later JREs which would make your code fail at runtime with a linkage error. To avoid this issue, you can either configure the compiler's boot classpath to match the target JRE or use the Animal Sniffer Maven Plugin to verify your code doesn't use unintended APIs. In the same way, setting the source option does not guarantee that your code actually compiles on a JDK with the specified version. To compile your code with a specific JDK version, different than the one used to launch Maven, refer to the Compile Using A Different JDK example."

See: https://maven.apache.org/plugins/maven-compiler-plugin/examples/set-compiler-source-and-target.html

In other words: if you want to make sure your project is using the correct Java version and thus correct JAXB version, use the Animal Sniffer Maven Plugin. See: http://www.mojohaus.org/animal-sniffer/animal-sniffer-maven-plugin/

That's all ;)

Comments