9 Using Dynamic JAXB

This chapter includes the following sections:

9.1 Understanding Static and Dynamic Entities

There are two high-level ways to use EclipseLink JAXB: using pre-existing Java classes (Using Static MOXy), or using EclipseLink-generated in-memory Java classes (Using Dynamic MOXy).

9.1.1 Using Static MOXy

The most common way to use EclipseLink JAXB is with existing Java classes, mapped to XML using Java annotations and/or EclipseLink OXM metadata. These classes might be ones that you have written yourself, or they could be generated from an XML Schema using the XJC compiler tool.

Using this approach, you will be dealing with your actual domain objects when converting to and from XML. Example 9-1 shows a simple Java class that can be used with JAXB.

Example 9-1 Sample Java Class

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
 
@XmlRootElement
public class Customer {
   @XmlAttribute
   private long id;
 
   private String name;
 
   // ...
   // get() and set() methods
   // ...
}
 

Note:

When using static classes with JAXB, you can take advantage of JAXB's defaulting behavior and only annotate things which differ from the default. For example, all fields on a Java class will default to being mapped to an XML element, so no annotation is needed on the name field. We want the id field, however, to map to an XML attribute, so have annotated it as such.

Example 9-2 demonstrates how to unmarshal, modify, and marshal an object using static JAXB:

Example 9-2 Marshalling and Unmarshalling Example

JAXBContext jaxbContext = JAXBContext.newInstance(Customer.class, Address.class);
Customer customer = (Customer) jaxbContext.createUnmarshaller().unmarshal(instanceDoc);
 
Address address = new Address();
address.setStreet("1001 Fleet St.");
 
customer.setAddress(address);
 
jaxbContext.createMarshaller().marshal(customer, System.out);
 

9.1.2 Using Dynamic MOXy

With EclipseLink Dynamic MOXy, you can bootstrap a JAXBContext from a variety of metadata sources and use existing JAXB APIs to marshal and unmarshal data…without having compiled Java class files on the classpath. This allows you to alter the metadata as needed, without having to update and recompile the previously-generated Java source code.

You should consider using Dynamic MOXy when:

  • You want EclipseLink to generate mappings from an XML schema (XSD).

  • You do not want to deal with concrete Java domain classes.

9.1.2.1 Using Dynamic Entities

Instead of using actual Java classes (such as Customer.class or Address.class), Dynamic MOXy uses a simple get(propertyName)/set(propertyName, propertyValue) API to manipulate data. EclipseLink generates (in memory) a DynamicType associated with each DynamicEntity.

Note:

DynamicTypes are similar to Java classes; whereas DynamicEntities can be thought of as instances of a DynamicType.

Example 9-3 demonstrates how to unmarshal, modify, and marshal an object using dynamic JAXB:

Example 9-3 Marshalling and Unmarshalling Example

DynamicJAXBContext dynamicJAXBContext = DynamicJAXBContextFactory.createContextFromXSD(xsdInputStream, null, myClassLoader, null);
DynamicEntity customer = (DynamicEntity) dynamicJAXBContext.createUnmarshaller().unmarshal(instanceDoc);
 
String lastName = customer.get("lastName");
List orders = customer.get("orders");
...
DynamicEntity address = dynamicJAXBContext.newDynamicEntity("mynamespace.Address");
address.set("street", "1001 Fleet St.");
 
customer.set("lastName", lastName + "Jr.");
customer.set("address", address);
 
dynamicJAXBContext.createMarshaller().marshal(customer, System.out);
 

Note:

XML names found in the metadata (complex type names, element names, attribute names) will be translated to Java identifiers according to the algorithms described in "Appendix D: Binding XML Names to Java Identifiers" of the Java Architecture for XML Binding (JAXB) 2.2 Specification (http://jcp.org/en/jsr/detail?id=222).

In Example 9-3, last-name in XML was translated to lastName for the Java object.

9.2 Specifying the EclipseLink Runtime

In order to use EclipseLink Dynamic MOXy as your JAXB implementation, you must identify the EclipseLink DynamicJAXBContextFactory in your jaxb.properties file.

  1. Create a text file named jaxb.properties, specifying DynamicJAXBContextFactory as the factory used to build new JAXBContexts.

    javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContextFactory

  2. Copy the jaxb.properties file to the context path used to create the JAXBContext.

  3. Use the standard JAXBContext.newInstance(String contextPath) API to create a DynamicJAXBContext.

    DynamicJAXBContext jaxbContext = (DynamicJAXBContext) JAXBContext.newInstance("org.example.mypackage");

Because you do not need to change any application code, you can easily switch between different JAXB implementations.

9.2.1 Instantiating a DynamicJAXBContext

The following methods on JAXBContext can be used to create new instances of DynamicJAXBContexts:

public static JAXBContext newInstance(String contextPath) throws JAXBException
public static JAXBContext newInstance(String contextPath, ClassLoader classLoader) throws JAXBException
public static JAXBContext newInstance(String contextPath, ClassLoader classLoader, Map<String,?> properties) throws JAXBException
  • contextPath – Location of jaxb.properties file.

  • classLoader – The application's current class loader, which will be used to first lookup classes to see if they exist before new DynamicTypes are generated.

  • properties – A map of properties to use when creating a new DynamicJAXBContext. This map must contain one of the following two keys:

    • org.eclipse.persistence.jaxb.JAXBContextFactory.XML_SCHEMA_KEY, which can have several possible values:

      • One of the following, pointing to your XML Schema file:

        • java.io.InputStream

        • org.w3c.dom.Node

        • javax.xml.transform.Source

    • org.eclipse.persistence.jaxb.JAXBContextProperties.OXM_METADATA_SOURCE, which can have several possible values:

      • One of the following, pointing to your OXM file:

        • java.io.File

        • java.io.InputStream

        • java.io.Reader

        • java.net.URL

        • javax.xml.stream.XMLEventReader

        • javax.xml.stream.XMLStreamReader

        • javax.xml.transform.Source

        • org.w3c.dom.Node

        • org.xml.sax.InputSource

        Note:

        If using one of these options, a package-name element must be defined in the xml-bindings element of your OXM file.

      • A List of objects from the set above.

        Note:

        If using this option, a package-name element must be defined in the xml-bindings element of your OXM file.

      • A Map<String, Object>, where String is a package name, and Object is the pointer to the OXM file, from the set of possibilities above.

9.3 Bootstrapping from XML Schema (XSD)

With EclipseLink MOXy, you can provide an existing XML schema from which to create a DynamicJAXBContext. EclipseLink will parse the schema and generate DynamicTypes for each complex type. This is achieved by use of the DynamicJAXBContextFactory class. A DynamicJAXBContext cannot be instantiated directly; it must be created through the factory API.

You can pass the XML Schema to DynamicJAXBContextFactory by using:

  • java.io.InputStream

  • org.w3c.dom.Node

  • javax.xml.transform.Source

Note:

EclipseLink MOXy uses Sun's XJC (XML-to-Java Compiler) APIs to parse the schema into an in-memory representation and generate dynamic types and mappings. When bootstrapping from XSD, you will need to include jaxb-xjc.jar (from the JAXB reference implementation) on your CLASSPATH.

The APIs used to create a DynamicJAXBContext are as follows:

Example 9-4 Creating a DynamicJAXBContext

/**
 * Create a DynamicJAXBContext, using XML Schema as the metadata source.
 *
 * @param schemaStream
 *      java.io.InputStream from which to read the XML Schema.
 * @param resolver
 *      An org.xml.sax.EntityResolver, used to resolve schema imports.  Can be null.
 * @param classLoader
 *      The application's current class loader, which will be used to first lookup
 *      classes to see if they exist before new DynamicTypes are generated.  Can be
 *      null, in which case Thread.currentThread().getContextClassLoader() will be used.
 * @param properties
 *      Map of properties to use when creating a new DynamicJAXBContext.  Can be null.
 *
 * @return
 *      A new instance of DynamicJAXBContext.
 *
 * @throws JAXBException
 *      if an error was encountered while creating the DynamicJAXBContext.
 */
public static DynamicJAXBContext createContextFromXSD(java.io.InputStream schemaStream, EntityResolver resolver,
   ClassLoader classLoader, Map<String, ?> properties) throws JAXBException
 
public static DynamicJAXBContext createContextFromXSD(org.w3c.dom.Node schemaDOM, EntityResolver resolver,
   ClassLoader classLoader, Map<String, ?> properties) throws JAXBException
 
public static DynamicJAXBContext createContextFromXSD(javax.xml.transform.Source schemaSource, EntityResolver resolver,
   ClassLoader classLoader, Map<String, ?> properties) throws JAXBException
 

Note:

The classLoader parameter is your application's current class loader, and will be used to first lookup classes to see if they exist before new DynamicTypes are generated. The user may pass in null for this parameter, and Thread.currentThread().getContextClassLoader() will be used instead.

This example shows how to create and marshall a new object using Dynamic MOXy.

Example 9-5 Sample XML Schema

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="example" xmlns:myns="example" xmlns:xs="http://www.w3.org/2001/XMLSchema"
    attributeFormDefault="qualified" elementFormDefault="qualified">
 
    <xs:element name="customer" type="myns:customer"/>
 
    <xs:complexType name="customer">
        <xs:sequence>
            <xs:element name="first-name" type="xs:string"/>
            <xs:element name="last-name" type="xs:string"/>
            <xs:element name="address" type="myns:address"/>
        </xs:sequence>
    </xs:complexType>
 
    <xs:complexType name="address">
        <xs:sequence>
            <xs:element name="street" type="xs:string"/>
            <xs:element name="city" type="xs:string"/>
            <xs:element name="province" type="xs:string"/>
            <xs:element name="postal-code" type="xs:string"/>
        </xs:sequence>
    </xs:complexType>
 
</xs:schema>
 

The code snippet in Example 9-6:

  • Passes the XML Schema to DynamicJAXBContextFactory to create a DynamicJAXBContext

  • Creates new DynamicEntities and sets their properties

  • Creates a JAXBMarshaller and marshals the Java objects to XML

Example 9-6 Sample Application Code

InputStream inputStream = myClassLoader.getSystemResourceAsStream("example/resources/xsd/customer.xsd");
DynamicJAXBContext dContext = DynamicJAXBContextFactory.createContextFromXSD(inputStream, null, myClassLoader, null);
 
DynamicEntity newCustomer = dContext.newDynamicEntity("example.Customer");
newCustomer.set("firstName", "George");
newCustomer.set("lastName", "Jones");
 
DynamicEntity newAddress = dContext.newDynamicEntity("example.Address");
newAddress.set("street", "227 Main St.");
newAddress.set("city", "Toronto");
newAddress.set("province", "Ontario");
newAddress.set("postalCode", "M5V1E6");
 
newCustomer.set("address", newAddress);
 
dContext.createMarshaller().marshal(newCustomer, System.out);
 

9.3.1 Importing Other Schemas / EntityResolvers

If the XML schema that you use to bootstrap imports other schemas, you must configure an org.xml.sax.EntityResolver to resolve the locations of the imported schemas. You can then pass the EntityResolver to the DynamicJAXBContextFactory.

In Example 9-7, each type is defined in its own schema:

Example 9-7 Sample XML Schema

<!-- customer.xsd -->
 
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:myns="example" xmlns:add="addressNamespace"
   xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="example">
 
    <xs:import namespace="addressNamespace" schemaLocation="address.xsd"/>
 
    <xs:element name="customer" type="myns:customer"/>
 
    <xs:complexType name="customer">
        <xs:sequence>
            <xs:element name="first-name" type="xs:string"/>
            <xs:element name="last-name" type="xs:string"/>
            <xs:element name="address" type="add:address"/>
        </xs:sequence>
    </xs:complexType>
 
</xs:schema>
 

You must supply an EntityResolver implementation to resolve the location of the imported schema.

Example 9-8 illustrates the EntityResolver:

Example 9-8 Sample Application Code

class MyEntityResolver implements EntityResolver {
 
   public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
      // Imported schemas are located in ext\appdata\xsd\
 
      // Grab only the filename part from the full path
      String filename = new File(systemId).getName();
 
      // Now prepend the correct path
      String correctedId = "ext/appdata/xsd/" + filename;
 
      InputSource is = new InputSource(ClassLoader.getSystemResourceAsStream(correctedId));
      is.setSystemId(correctedId);
 
      return is;
   }
 
}
 

When you create the DynamicJAXBContext, pass the EntityResolver to it, as shown here:

InputStream inputStream = ClassLoader.getSystemResourceAsStream("com/foo/sales/xsd/customer.xsd");
DynamicJAXBContext dContext = DynamicJAXBContextFactory.createContextFromXSD(inputStream, new MyEntityResolver(), null, null);

If you encounter the following exception when importing another schema:

Internal Exception: org.xml.sax.SAXParseException: schema_reference.4: Failed to read schema document '<imported-schema-name>', because 1) could not find the document; 2) the document could not be read; 3) the root element of the document is not <xsd:schema>.

You should disable XJC's schema correctness check option, either in code:

System.setProperty("com.sun.tools.xjc.api.impl.s2j.SchemaCompilerImpl.noCorrectnessCheck", "true")

or from the command line:

-Dcom.sun.tools.xjc.api.impl.s2j.SchemaCompilerImpl.noCorrect

9.3.2 Customizing Generated Mappings with XJC External Binding Customization Files

When bootstrapping from an XSD, you have the option to customize the mappings that will be generated through the use of XJC's External Binding Customization file format (.xjb). In the example below, the package name of the dynamic classes has been overridden, and the name attribute has been renamed to last-name-comma-first-name.

Example 9-9 custom1.xjb File

<jxb:bindings version="1.0" xmlns:jxb="http://java.sun.com/xml/ns/jaxb" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <jxb:bindings schemaLocation="employee.xsd" node="/xs:schema">
 
        <!-- Customize the package name that is generated for each schema -->
        <jxb:schemaBindings>
            <jxb:package name="com.acme.internal"/>
        </jxb:schemaBindings>
 
        <!-- Rename the 'name' element to 'last-name-comma-first-name' -->
        <jxb:bindings node="//xs:complexType[@name='person']">
            <jxb:bindings node=".//xs:element[@name='name']">
                <jxb:property name="last-name-comma-first-name"/>
            </jxb:bindings>
        </jxb:bindings>
 
    </jxb:bindings>
</jxb:bindings>
 

For complete information on the External Binding Customization file format, please see http://download.oracle.com/docs/cd/E17802_01/webservices/webservices/docs/2.0/tutorial/doc/JAXBUsing4.html.

Note:

If you wish to use External Binding Customization files, you will need to use Source objects to point to your XML Schema. Sources are used to load the .xjb files as well, and they must all have the same System ID set.

Example 9-10 illustrates bootstrapping from an XSD, and customizing the mapping generation using two separate .xjb files.

Example 9-10 Bootstrapping Example

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
String xsd = "example/resources/xsd/employee.xsd";
String xjb1 = "example/resources/xsd/custom1.xjb";
String xjb2 = "example/resources/xsd/custom2.xjb";
 
InputStream xsdStream = classLoader.getSystemResourceAsStream(xsd);
Source xsdSource = new StreamSource(xsdStream);
// Set SYSTEM_ID to the filename part of the XSD
xsdSource.setSystemId("employee.xsd");
 
InputStream xjbStream = classLoader.getResourceAsStream(xjb1);
Source xjbSource = new StreamSource(xjbStream);
// Set SYSTEM_ID to be the same as the XSD
xjbSource.setSystemId(xsdSource.getSystemId());
 
InputStream xjbStream2 = classLoader.getResourceAsStream(xjb2);
Source xjbSource2 = new StreamSource(xjbStream2);
// Set SYSTEM_ID to be the same as the XSD
xjbSource2.setSystemId(xsdSource.getSystemId());
 
ArrayList<Source> xjbFiles = new ArrayList<Source>(2);
xjbFiles.add(xjbSource);
xjbFiles.add(xjbSource2);
 
// Put XSD and XJBs into Properties
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(DynamicJAXBContextFactory.XML_SCHEMA_KEY, xsdSource);
properties.put(DynamicJAXBContextFactory.EXTERNAL_BINDINGS_KEY, xjbFiles);
 
// Create Context
DynamicJAXBContext jaxbContext = (DynamicJAXBContext) JAXBContext.newInstance("example", classLoader, properties);
 

The value of EXTERNAL_BINDINGS_KEY can be either a single Source or a List<Source>, pointing to your External Binding Customization file(s).

9.4 Bootstrapping from EclipseLink Metadata (OXM)

If you would like to have more control over how your DynamicEntities will be mapped to XML, you can instead bootstrap from an EclipseLink OXM Metadata file. Using this approach, you can take advantage of EclipseLink's robust mappings framework and customize how each complex type in XML maps to its Java counterpart. The following API on DynamicJAXBContextFactory can be used to bootstrap from an OXM file:

Example 9-11 Creating a DynamicJAXBContext

/**
 * Create a <tt>DynamicJAXBContext</tt>, using an EclipseLink OXM file as the metadata source.
 *
 * @param classLoader
 *      The application's current class loader, which will be used to first lookup classes to
 *      see if they exist before new <tt>DynamicTypes</tt> are generated. Can be <tt>null</tt>,
 *      in which case <tt>Thread.currentThread().getContextClassLoader()</tt> will be used.
 * @param properties
 *      Map of properties to use when creating a new <tt>DynamicJAXBContext</tt>.  This map must
 *      contain a key of JAXBContext.ECLIPSELINK_OXM_XML_KEY, with a value of... (see below)
 *
 * @return
 *      A new instance of <tt>DynamicJAXBContext</tt>.
 *
 * @throws JAXBException
 *      if an error was encountered while creating the <tt>DynamicJAXBContext</tt>.
 */
public static DynamicJAXBContext createContextFromOXM(ClassLoader classLoader, Map<String, ?> properties) throws JAXBException {
 

Links to the actual OXM files are passed in via the properties parameter, using a special key, JAXBContextProperties.OXM_METADATA_SOURCE. The value of this key will be a handle to the OXM metadata file, in the form of one of the following:

  • java.io.File

  • java.io.InputStream

  • java.io.Reader

  • java.net.URL

  • javax.xml.stream.XMLEventReader

  • javax.xml.stream.XMLStreamReader

  • javax.xml.transform.Source

  • org.w3c.dom.Node

  • org.xml.sax.InputSource

Lists of the above inputs are acceptable as well, to bootstrap from multiple OXM files. For more information, see the documentation on the DynamicJAXBContextFactory class.

In the following example, we will obtain our OXM file as a resource from our ClassLoader, and use the resulting InputStream to bootstrap a DynamicJAXBContext:

InputStream iStream = myClassLoader.getResourceAsStream("example/resources/eclipselink/eclipselink-oxm.xml");
 
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, iStream);
 
DynamicJAXBContext jaxbContext = DynamicJAXBContextFactory.createContextFromOXM(myClassLoader, properties);

9.4.1 Example

Using the sample OXM in Example 9-12, we will show an example of how to create and marshall a new object using Dynamic MOXy. It is important to note the type attributes. Because there is no underlying Java class, the types of each property must be explicitly specified.

Example 9-12 Sample XML Schema

<?xml version="1.0" encoding="US-ASCII"?>
<xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" package-name="example">
 
    <java-types>
        <java-type name="Customer">
            <xml-root-element name="customer"/>
            <java-attributes>
                <xml-element java-attribute="firstName" type="java.lang.String"/>
                <xml-element java-attribute="lastName" type="java.lang.String"/>
                <xml-element java-attribute="address" type="example.Address"/>
            </java-attributes>
        </java-type>
 
        <java-type name="Address">
            <java-attributes>
                <xml-element java-attribute="street" type="java.lang.String"/>
                <xml-element java-attribute="city" type="java.lang.String"/>
                <xml-element java-attribute="province" type="java.lang.String"/>
                <xml-element java-attribute="postalCode" type="java.lang.String"/>
            </java-attributes>
        </java-type>
    </java-types>
 
</xml-bindings>
 

The code in Example 9-13 demonstrates:

  • Passing the OXM file to DynamicJAXBContextFactory to create a DynamicJAXBContext

  • Creating new DynamicEntities and setting their properties

  • Creating a JAXBMarshaller and marshalling the Java objects to XML

Example 9-13 Sample Application Code

InputStream iStream = myClassLoader.getResourceAsStream("example/resources/eclipselink/eclipselink-oxm.xml");
 
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, iStream);
 
DynamicJAXBContext jaxbContext = DynamicJAXBContextFactory.createContextFromOXM(myClassLoader, properties);
 
DynamicEntity newCustomer = dContext.newDynamicEntity("example.Customer");
newCustomer.set("firstName", "George");
newCustomer.set("lastName", "Jones");
 
DynamicEntity newAddress = dContext.newDynamicEntity("example.Address");
newAddress.set("street", "227 Main St.");
newAddress.set("city", "Toronto");
newAddress.set("province", "Ontario");
newAddress.set("postalCode", "M5V1E6");
 
newCustomer.set("address", newAddress);
 
dContext.createMarshaller().marshal(newCustomer, System.out