2 EclipseLink MOXy Runtime

This chapter introduces and describes EclipseLink MOXy. EclipseLink MOXy is one implementation of the standard runtime defined by the JAXB specification.

To specify EclipseLink MOXy as your JAXB provider:

This chapter includes the following sections:

2.1 Specifying the EclipseLink Runtime

To use EclipseLink MOXy as your JAXB implementation, identify the EclipseLink JAXBContextFactory in your jaxb.properties file.

  1. Create a text file named jaxb.properties, specifying EclipseLink's JAXBContextFactory as the factory used to build new JAXBContexts:

    javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
    
  2. Copy the file to the same package (directory) in which your model classes reside.

  3. Use the standard JAXBContext.newInstance(Class... classesToBeBound) API to create a JAXBContext:

    JAXBContext jaxbContext = JAXBContext.newInstance(Customer.class);
    

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

For more information on different ways to create a JAXBContext, see "Bootstrapping".

2.2 Bootstrapping

EclipseLink MOXy offers several options when creating your JAXBContext. You have the option of bootstrapping from:

  • A list of one or more JAXB-annotated classes

  • A list of one or more EclipseLink XML bindings documents defining the mappings for your Java classes

  • A combination of classes and XML bindings

  • A list of context paths

  • A list of session names, referring to EclipseLink sessions defined in sessions.xml

2.2.1 Using the JAXBContext API

The methods on JAXBContext (shown in Example 2-1) are used to create new instances.

Example 2-1 JAXBContext Methods

public static JAXBContext newInstance(Class... classesToBeBound) throws JAXBException

public static JAXBContext newInstance(Class[] classesToBeBound, Map<String,?> properties) throws JAXBException

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

JAXBContext accepts the following options:

  • classesToBeBound – List of Java classes to be recognized by the new JAXBContext

  • contextPath – List of Java package names (or EclipseLink session names) that contain mapped classes

  • classLoader – The class loader used to locate the mapped classes

  • properties – A map of additional properties.

The APIs in Example 2-1 expect to find a jaxb.properties file in your Java package/context path. For more information see "Specifying the EclipseLink Runtime".

2.2.2 Bootstrapping from Classes

If you have a collection of Java classes annotated with JAXB annotations, you can provide a list of these classes directly:

JAXBContext context = JAXBContext.newInstance(Company.class, Employee.class);

Other classes that are reachable from the classes in the array (for example, referenced classes, super class) will automatically be recognized by the JAXBContext. Subclasses or classes marked as @XmlTransient will not be recognized.

2.2.3 Bootstrapping from a Context Path

Another way to bootstrap your JAXBContext is with a String, called the context path. This is a colon-delimited list of package names containing your mapped classes:

JAXBContext context = JAXBContext.newInstance("example");

Using this approach, there are a few different ways that EclipseLink will discover your model classes:

2.2.3.1 Using a jaxb.index File

The context path could contain a file named jaxb.index, which is a simple text file containing the class names from the current package that will be brought into the JAXBContext:

src/example/jaxb.index:

Example 2-2 Sample jaxb.index File

Employee
PhoneNumber

Other classes that are reachable from the classes in list (for example, referenced classes, super class) will automatically be recognized by the JAXBContext. Subclasses or classes marked as @XmlTransient will not be recognized.

2.2.3.2 Using an ObjectFactory

The context path could also contain a class called ObjectFactory, which is a special factory class that JAXB will look for. This class contains create() methods for each of the types in your model. Typically the ObjectFactory will be generated by the JAXB compiler, but one can be written by hand as well.

src/example/ObjectFactory.java:

Example 2-3 Sample ObjectFactory

@XmlRegistry
public class ObjectFactory {
private final static QName _Employee_QNAME = new QName("", "employee");
private final static QName _PhoneNumber_QNAME = new QName("", "phone-number");
public ObjectFactory() {
}
public EmployeeType createEmployeeType() {
return new EmployeeType();
}
@XmlElementDecl(namespace = "", name = "employee")
 public JAXBElement<EmployeeType> createEmployee(EmployeeType value) {
        return new JAXBElement<EmployeeType>(_Employee_QNAME, EmployeeType.class, null, value);
    }
     public PhoneNumberType createPhoneNumberType() {
        return new PhoneNumberType();
    }
 
    @XmlElementDecl(namespace = "", name = "phone-number")
    public JAXBElement<PhoneNumberType> createPhoneNumber(PhoneNumberType value) {
        return new JAXBElement<PhoneNumberType>(_PhoneNumber_QNAME, PhoneNumberType.class, null, value);
    }
 
}

2.2.3.3 Using MetadataSource

EclipseLink MOXy also has the ability to retrieve mapping information from an implementation of EclipseLink's MetadataSource. Using this approach, you are responsible for creating your own XmlBindings.

Example 2-4 Sample Metadata Source

package org.eclipse.persistence.jaxb.metadata;
public interface MetadataSource {
 
    /**
     * @param properties – The properties passed in to create the JAXBContext
     * @param classLoader – The ClassLoader passed in to create the JAXBContext
     * 
     * @return the XmlBindings object representing the metadata
     */
    XmlBindings getXmlBindings(Map<String, ?> properties, ClassLoader classLoader);
 
}

For information on using a MetadataSource, see "Using MetadataSource".

2.2.4 Bootstrapping from EclipseLink XML Bindings

To have more control over how your classes will be mapped to XML, you can bootstrap from an EclipseLink XML bindings document. 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.

Links to the actual documents are passed in via the properties parameter, using a special key, JAXBContextProperties.OXM_METADATA_SOURCE:

Example 2-5 Using an EclipseLink Bindings Document

InputStream iStream = myClassLoader.getResourceAsStream("example/xml-bindings.xml");
 
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, iStream);
 
JAXBContext context = JAXBContext.newInstance(new Class[]{ Customer.class }, properties);

For more information on the XML Bindings format, see "Using XML Bindings".

2.2.5 Combining Annotated Classes and XML Bindings

When bootstrapping from annotated classes, additional mapping information can be provided with an EclipseLink XML bindings document. For instance, you might annotate your model classes with JAXB-spec-only annotations, and put your EclipseLink-specific mapping customizations into an XML bindings document (negating the need to import EclipseLink annotations in your model classes).

For example, review the annotated Employee class in Example 2-6.

Example 2-6 Sample Java Class

package example;
 
import javax.xml.bind.annotation.*;

@XmlRootElement

@XmlAccessorType(XmlAccessType.FIELD)
public class Employee {
   @XmlElement(name="phone-number")
   private PhoneNumber phoneNumber;
   ...
}

You can customize the Employee to use an EclipseLink XMLAdapter for marshalling/unmarshalling PhoneNumbers by using the XML Bindings in Example 2-7.

Example 2-7 Using an XML Bindings Document

<?xml version="1.0" encoding="US-ASCII"?>
<xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm">
  <java-types>
    <java-type name="example.Employee">
      <java-attributes>
        <xml-element java-attribute="phoneNumber">
          <xml-java-type-adapter value="example.util.PhoneNumberProcessor"/>
        </xml-element>
      </java-attributes>
    </java-type>
  </java-types>
</xml-bindings>

Finally, pass both the list of annotated classes and the link to the XML Bindings to the JAXBContext, as shown in Example 2-8.

Example 2-8 Sample Application Code

InputStream iStream = myClassLoader.getResourceAsStream("example/xml-bindings.xml");
 Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, iStream);
 
Class[] classes = new Class[] { Company.class, Employee.class };
JAXBContext context = JAXBContext.newInstance(classes, properties);

2.3 Using XML Bindings

In addition to standard JAXB annotations, EclipseLink offers another way of expressing your metadata: the EclipseLink XML Bindings document. Not only can XML Bindings separate your mapping information from your actual Java class, it can also be used for more advanced metadata tasks such as:

  • Augmenting or overriding existing annotations with additional mapping information

  • Specifying all mappings information externally, with no annotations in Java at all

  • Defining your mappings across multiple Bindings documentsSpecifying "virtual" mappings that do not correspond to concrete Java fieldsand more..

This section describes the XML Bindings format and demonstrates some basic use cases.

2.3.1 Understanding the XML Bindings Format

An XML Bindings document is XML that specifies Java type information, mapping information, context-wide properties – everything you need to define your JAXB system. An example Bindings document is shown in Example 2-9.

Example 2-9 Sample Bindings Document

<?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" xml-accessor-type="PUBLIC_MEMBER" xml-accessor-order="ALPHABETICAL"
    xml-mapping-metadata-complete="false" xml-name-transformer="example.NameGenerator"
    supported-versions="2.4" >
 
    <xml-schema element-form-default="QUALIFIED">
        <xml-ns prefix="ns1" namespace-uri="http://www.example.org/type" />
    </xml-schema>
     <java-types>
        <java-type name="Employee">
            <xml-type namespace="http://www.example.org/type" />
            <java-attributes>
                <xml-attribute java-attribute="empId" xml-path="@id" />
                <xml-element java-attribute="empName" name="name" />
                <xml-element java-attribute="salary" />
                <xml-element java-attribute="type" type="EmployeeType" />
            </java-attributes>
        </java-type>
        <java-type name="Company">
            <xml-root-element name="company" />
            <xml-attribute java-attribute="empId" xml-path="@id" />
            <xml-element java-attribute="empName" name="name" />
            <java-attributes>
                <xml-element java-attribute="employees" name="employee"
                    type="example.Employee" container-type="java.util.ArrayList" />
            </java-attributes>
        </java-type>
    </java-types>
 
    <xml-registries>
        <xml-registry name="example.ObjectFactory">
            <xml-element-decl java-method="createEmpleado"
                name="empleado" type="example.Employee" />
            <xml-element-decl java-method="createCorporacion"
                name="corporacion" type="example.Company" />
        </xml-registry>
    </xml-registries>
 
    <xml-enums>
        <xml-enum java-enum="EmployeeType" value="java.lang.String">
            <xml-enum-value java-enum-value="CONTRACT">CONTRACT</xml-enum-value>
            <xml-enum-value java-enum-value="PART_TIME">PART_TIME</xml-enum-value>
            <xml-enum-value java-enum-value="FULL_TIME">FULL_TIME</xml-enum-value>
        </xml-enum>
    </xml-enums>
 </xml-bindings>

Table 2-1 Binding Document Attributes

Attribute Description

<xml-bindings>

The root of the XML Bindings document. This is also where you can define top-level properties for your JAXB system, such as the package-name of your classes, specify a default xml-accessor-type, and so on.

<xml-schema>

Defines properties related to the schema-level of your JAXB system. Corresponds to the JAXB @XmlSchema annotation.

<java-types>

Defines mapping information for each of your Java classes.

<xml-enums>

Defines Java enumerations that can be used with your Java types.

<xml-registries>

Defines an ObjectFactory for use in your JAXB system.


2.3.2 Bootstrapping with XML Bindings

When instantiating a JAXBContext, links to Bindings documents 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 Bindings document, 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

To bootstrap from multiple XML Bindings documents:

  • Maps of the above inputs are supported, keyed on Java package name.

  • Lists of the above inputs are acceptable as well (<xml-bindings> must have package attribute).

2.3.3 Using XML Bindings with Annotations

The most typical use of an XML Bindings document is in conjunction with JAXB annotations. You may have situation where you are not permitted to edit your Java domain classes, but want to add additional mapping functionality. Or, you may wish to avoid importing any EclipseLink code into your domain model, but still take advantage of MOXy's advanced mapping features. When Bindings metadata is provided during context creation, its mapping information will be combined with any JAXB annotation information.

For example, consider the simple JAXB domain class and its default JAXB XML representation shown in Example 2-10.

Example 2-10 Sample JAXB Domain Class and XML

package example;
 
import javax.xml.bind.annotation.*;
 
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
   @XmlAttribute
   private Integer custId;
   private String name;
   private Double salary;
   private byte[] picture;
   ...
}




<?xml version="1.0" encoding="UTF-8"?>
<customer custId="15">
   <name>Bob Dobbs</name>
   <salary>51727.61</salary>
   <picture>AgQIECBA</picture>
</customer>

Now, assume that we would like to make the following mapping changes:

  • Change the XML element name of custId to customer-id

  • Change the root element name of the class to customer-info

  • Write the picture to XML as picture-hex in hex binary format, and use our own custom converter, MyHexConverter.

We can specify these three customizations in an XML Bindings document as shown in Example 2-11.

Example 2-11 Customized XML Bindings

<?xml version="1.0" encoding="US-ASCII"?>
<xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="example">
 
    <java-types>
        <java-type name="Customer">
            <xml-root-element name="customer-info" />
            <java-attributes>
                <xml-attribute java-attribute="custId" name="customer-id" />
                <xml-element java-attribute="picture" name="picture-hex">
                    <xml-schema-type name="hexBinary" />
                    <xml-java-type-adapter
                        value="example.adapters.MyHexConverter" />
                </xml-element>
            </java-attributes>
        </java-type>
    </java-types>
 
</xml-bindings>

The Bindings must then be provided during JAXB context creation. Bindings information is passed in via the properties argument:

Example 2-12 Providing Bindings

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
InputStream iStream = classLoader.getResourceAsStream("metadata/xml-bindings.xml");
 
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, iStream);
 
JAXBContext ctx = JAXBContext.newInstance(new Class[] { Customer.class }, properties);

When providing Bindings, during JAXB context creation Oracle TopLink will:

  1. Customer.class will be analyzed and JAXB mappings will be generated as usual.

  2. The Bindings document is then analyzed, and the original JAXB mappings will be merged with the information in the Bindings document.

After applying the XML Bindings, we have the desired XML representation:

<?xml version="1.0" encoding="UTF-8"?>
<customer-info customer-id="15">
   <name>Bob Dobbs</name>
   <salary>51727.61</salary>
   <picture-hex>020408102040</picture-hex>
</customer-info>

2.3.4 Using Multiple Bindings Documents

Starting with version 2.3, EclipseLink allows you to use mapping information from multiple XML Bindings documents. Using this approach, you can split your metadata up as you wish.

Example 2-13 Using a List of XML Bindings:

...
FileReader file1 = new FileReader("base-bindings.xml");
FileReader file2 = new FileReader("override-bindings.xml");
 
List<Object> fileList = new ArrayList<Object>();
fileList.add(file1);
fileList.add(file2);
 
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, fileList);
 
JAXBContext ctx = JAXBContext.newInstance(new Class[] { Customer.class }, properties);

...

When using a List of Bindings documents, each one must define the package attribute of <xml-bindings>, to indicate the package for each set of Bindings.

Example 2-14 Using a Map for multiple packages:

...
 
FileReader fooFile1 = new FileReader("foo/base-bindings.xml");
FileReader fooFile2 = new FileReader("foo/override-bindings.xml");
 
List<Object> fooFileList = new ArrayList<Object>();
fooFileList.add(fooFile1);
fooFileList.add(fooFile2);
 
FileReader barFile1 = new FileReader("bar/base-bindings.xml");
FileReader barFile2 = new FileReader("bar/override-bindings.xml");
 
List<Object> barFileList = new ArrayList<Object>();
barFileList.add(barFile1);
barFileList.add(barFile2);
 
Map<String, List> metadataMap = new HashMap<String, List>();
metadataMap.put("foo", fooFileList);
metadataMap.put("bar", barFileList);
 
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, metadataMap);
 
JAXBContext ctx = JAXBContext.newInstance(new Class[] { Customer.class }, properties);
 
...

2.3.5 Understanding Override Rules

When multiple sources of metadata are encountered for the same package, a unified set of mappings will be created by merging the complete set of metadata. First, the annotations from the Java class will be processed, and then any XML Bindings information will be applied. The order that Bindings are specified is relevant; values in subsequent documents will override the ones defined in previous ones.

The following rules will be used for merging:

  • xml-schema

    • For values such as namespace, elementform, attributeform, the later file will override.

    • The list of namespace declarations from XmlNs will be merged into a single list containing all entries from all files.

      In the case of conflicting entries (the same prefix bound to multiple namespaces), the last file will override the declarations from previous files.

  • java-types

    • The merged bindings will contain all unique java-type entries from all bindings files.

    • If the same java-type occurs in multiple files, any values that are set in the later file will override values from the previous file.

    • Properties on each java-type will be merged into a unified list. If the same property is referenced in multiple files, this will be an exception case.

    • Class-level XmlJavaTypeAdpater entries will be overridden if specified in a later bindings file.

    • Class-level XmlSchemaTypes will create a merged list. If an entry for the same type is listed in multiple bindings files at this level, the last file's entry will override all previous ones.

  • xml-enums

    • The merged bindings will contain all unique xml-enum entries from all bindings files.

    • For any duplicated java-enums, a merged list of XmlEnumValues will be created. If an entry for the same enum facet occurs in multiple files, the last file will override the value for that facet.

  • xml-java-type-adapters

    • Package-level Java type adapters will be merged into a single list. In the case that an adapter is specified for the same class in multiple files, the last file's entry will win.

  • xml-registries

    • Each unique XmlRegistry entry will be added to the final merged list of XmlRegistries.

    • For any duplicated XmlRegistry entries, a merged list of XmlElementDecls will be created.

      In the case that an XmlElementDecl for the same XmlRegistry class appears in multiple bindings files, that XmlElementDecl will be replaced with the one from the later bindings.

  • xml-schema-types

    • XmlSchemaType entries will be merged into a unified list.

    • In the case that an XmlSchemaType entry for the same java-type appears at the package level in multiple bindings files, the merged bindings will only contain the entry for the last one specified.

2.3.6 Using Complete Metadata

If you would like to store all of your metadata in XML Bindings and ignore any JAXB annotations in your Java class, you can include the xml-mapping-metadata-complete attribute in the <xml-bindings> element of your Bindings document. Default JAXB mappings will still be generated (the same as if you were using a completely un-annotated class with JAXB), and then any mapping data defined in the XML Bindings will be applied.

This could be used, for example, to map the same Java class to two completely different XML representations: the annotations on the actual Java class would define the first XML representation, and then a second XML representation could be defined in an XML Bindings document with xml-mapping-metadata-complete="true". This would essentially give you a "blank canvas" to remap your Java class.

If you would like to ignore the default mappings that JAXB generates, you can specify xml-accessor-type="NONE" in your <java-type> element. Using this approach, only mappings that are explicitly defined in Bindings document will be applied.

Using the Customer example from above, the following examples demonstrate the XML representations that will be generated when using xml-mapping-metadata-complete:

Example 2-15 Sample Customer Class

package example;
 
import javax.xml.bind.annotation.*;
 
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
   @XmlAttribute
   private Integer custId;
   private String name;
   private Double salary;
   private byte[] picture;
   ...
}

Example 2-16 XML Bindings

<?xml version="1.0" encoding="US-ASCII"?>
<xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="example" xml-mapping-metadata-complete="true">
 
    <java-types>
        <java-type name="Customer">
            <xml-root-element />
            <java-attributes>
                <xml-attribute java-attribute="name" name="customer-name" />
            </java-attributes>
        </java-type>
    </java-types>
 
</xml-bindings>

Example 2-17 XML Representation

<?xml version="1.0" encoding="UTF-8"?>
<customer>
   <custId>15</custId>
   <customer-name>Bob Dobbs</customer-name>
   <picture>AgQIECBA</picture>
   <salary>51727.61</salary>
</customer>
  • Default JAXB mapping is generated for custId (note that custId is now an XML element, as if there were no annotation on the Java field)

  • The name element has been renamed to customer-name

  • Default JAXB mappings are generated for picture and salary

Example 2-18 XML Bindings (with xml-accessor-type="NONE")

<?xml version="1.0" encoding="US-ASCII"?>
<xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="example" xml-mapping-metadata-complete="true">
 
    <java-types>
        <java-type name="Customer" xml-accessor-type="NONE">
            <xml-root-element />
            <java-attributes>
                <xml-attribute java-attribute="name" name="customer-name" />
            </java-attributes>
        </java-type>
    </java-types>
 
</xml-bindings>

Example 2-19 XML Representation

<?xml version="1.0" encoding="UTF-8"?>
<customer>
   <customer-name>Bob Dobbs</customer-name>
</customer>
  • Specifying xml-accessor-type="NONE" will prevent any default mappings from being generated

  • The XML representation contains only the mappings defined in the XML Bindings document

2.3.7 Using Virtual Mappings

XML Bindings can also be used to specify virtual mappings – mappings that do not correspond to a concrete Java field. For example, you might want to use a HashMap as the underlying structure to hold data for certain mappings. For information on using Virtual Mappings, see "Using Virtual Access Methods".

2.4 Using MetadataSource

The MetadataSource, introduced in EclipseLink 2.3, is responsible for serving up EclipseLink metadata. This allows you to store mapping information outside of your application and have it retrieved when the application's JAXBContext is being created or refreshed.

2.4.1 Implementing a MetadataSource

To implement your own MetadataSource, you can:

  • Create a new class that implements the org.eclipse.persistence.jaxb.metadata.MetadataSource interface.

  • Create a new class that extends the org.eclipse.persistence.jaxb.metadata.MetadataSourceAdapter class. Using this method is preferred, as it will insulate you from future additions to the interface.

In either case, you will be responsible for implementing the following method:

Example 2-20 Implementing the XMlBindings Method

/**
 * Retrieve XmlBindings according to the JAXBContext bootstrapping information.
 *
 * @param properties - The properties passed in to create the JAXBContext
 * @param classLoader - The ClassLoader passed in to create the JAXBContext
 * @return the XmlBindings object representing the metadata
 */
XmlBindings getXmlBindings(Map<String, ?> properties, ClassLoader classLoader);

2.4.2 Using an XmlBindings Object

Internally, EclipseLink metadata is stored in an XmlBindings object, which itself is mapped with JAXB. This means that you can actually use a JAXB unmarshaller to read external metadata and create an XmlBindings from it:

Example 2-21 Sample XmlBindings Object

package example;
 
import org.eclipse.persistence.jaxb.xmlmodel.XmlBindings;
...
JAXBContext xmlBindingsContext = JAXBContext.newInstance("org.eclipse.persistence.jaxb.xmlmodel");
FileReader bindingsFile = new FileReader("xml-bindings.xml");
XmlBindings bindings = (XmlBindings) xmlBindingsContext.createUnmarshaller().unmarshal(bindingsFile);

2.4.3 Specifying the MetadataSource

To use a MetadataSource in creating a JAXBContext, add it to the properties map with the key JAXBContextProperties.OXM_METADATA_SOURCE:

Example 2-22 Adding MetadataSource to the Properties Map

MetadataSource metadataSource = new MyMetadataSource();
 
Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, metadataSource);
 
JAXBContext jc = JAXBContext.newInstance(new Class[] { Customer.class }, properties);

2.4.4 MetadataSource Example

The following example creates an XmlBindings object by unmarshalling from a URL:

Example 2-23 Sample XmlBindings Object

package example;
 
import java.net.URL;
import java.util.Map;
 
import javax.xml.bind.JAXBContext;
 
import org.eclipse.persistence.jaxb.metadata.MetadataSourceAdapter;
import org.eclipse.persistence.jaxb.xmlmodel.XmlBindings;
 
public class MyMetadataSource extends MetadataSourceAdapter {
 
   private JAXBContext bindingsContext;
   private URL bindingsUrl;
 
   private final String XML_BINDINGS_PACKAGE = "org.eclipse.persistence.jaxb.xmlmodel";
   private final String METADATA_URL = "http://www.example.com/private/metadata/xml-bindings.xml"; 
   public MyMetadataSource() {
      try {
         bindingsContext = JAXBContext.newInstance(XML_BINDINGS_PACKAGE);
         bindingsUrl = new URL(METADATA_URL);
      } catch (Exception e) {
         throw new RuntimeException(e);
      }
   }
 
   @Override
   public XmlBindings getXmlBindings(Map<String, ?> properties, ClassLoader classLoader) {
      try {
         Unmarshaller u = bindingsContext.createUnmarshaller();
         XmlBindings bindings = (XmlBindings) u.unmarshal(bindingsUrl);
         return bindings;
      } catch (Exception e) {
         throw new RuntimeException(e);
      }
   }
 
}

2.4.5 Building XmlBindings Programatically

You also have the option of building your own XmlBindings object from scratch in code. The example below modifies the pCode field of the Address class to use a locale-specific name:

Example 2-24 Sample XmlBindings Object

package example;
 
import java.util.Locale;
import java.util.Map;
 
import org.eclipse.persistence.jaxb.metadata.MetadataSourceAdapter;
import org.eclipse.persistence.jaxb.xmlmodel.JavaType;
import org.eclipse.persistence.jaxb.xmlmodel.JavaType.JavaAttributes;
import org.eclipse.persistence.jaxb.xmlmodel.ObjectFactory;
import org.eclipse.persistence.jaxb.xmlmodel.XmlBindings;
import org.eclipse.persistence.jaxb.xmlmodel.XmlBindings.JavaTypes;
import org.eclipse.persistence.jaxb.xmlmodel.XmlElement;
 
public class AddressMetadataSource extends MetadataSourceAdapter {
 
    private ObjectFactory factory;
    private XmlBindings xmlBindings;
 
    public AddressMetadataSource() {
        factory = new ObjectFactory();
 
        xmlBindings = new XmlBindings();
        xmlBindings.setPackageName("example");
        xmlBindings.setJavaTypes(new JavaTypes());
    }
 
    @Override
    public XmlBindings getXmlBindings(Map<String, ?> properties, ClassLoader classLoader) {
        JavaType javaType = new JavaType();
        javaType.setName("Address");
        javaType.setJavaAttributes(new JavaAttributes());
 
        XmlElement pCodeElement = new XmlElement();
        pCodeElement.setJavaAttribute("pCode");
 
        String country = Locale.getDefault().getCountry(); 
        if (country.equals(Locale.US.getCountry())) {
            pCodeElement.setName("zip-code");
        } else if (country.equals(Locale.UK.getCountry())) {
            pCodeElement.setName("post-code");
        } else if (country.equals(Locale.CANADA.getCountry())) {
            pCodeElement.setName("postal-code");
        }
 
        javaType.getJavaAttributes().getJavaAttribute().add(factory.createXmlElement(pCodeElement));
 
        xmlBindings.getJavaTypes().getJavaType().add(javaType);
        return xmlBindings;
    }
 
}

2.5 Generating an XML Schema

To generate an XML schema from a Java object model:

  1. Create a class that extends javax.xml.bind.SchemaOutputResolver.

          private class MySchemaOutputResolver extends SchemaOutputResolver {
           
             public Result createOutput(String uri, String suggestedFileName) throws IOException {
                File file = new File(suggestedFileName);
                StreamResult result = new StreamResult(file);
                result.setSystemId(file.toURI().toURL().toString());
                return result;
             }
           
          }
    
  2. Use an instance of this class with JAXBContext to capture the generated XML Schema.

          Class[] classes = new Class[4]; 
          classes[0] = org.example.customer_example.AddressType.class; 
          classes[1] = org.example.customer_example.ContactInfo.class; 
          classes[2] = org.example.customer_example.CustomerType.class; 
          classes[3] = org.example.customer_example.PhoneNumber.class; 
          JAXBContext jaxbContext = JAXBContext.newInstance(classes);
           
          SchemaOutputResolver sor = new MySchemaOutputResolver();
          jaxbContext.generateSchema(sor);
    

2.6 Validating Against an XML Schema

If you would like to validate your objects against an XML Schema during marshalling and unmarshalling, you can make use of JAXB's ValidationEventHandler.

Example 2-25 Sample XML Schema

In this example we would like to validate our objects against the following XML schema:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
 
    <xs:element name="customer">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="name" type="stringMaxSize5"/>
                <xs:element ref="phone-number" maxOccurs="2"/>
             </xs:sequence>
        </xs:complexType>
    </xs:element>
 
    <xs:element name="phone-number">
        <xs:complexType>
            <xs:sequence/>
        </xs:complexType>
    </xs:element>
 
    <xs:simpleType name="stringMaxSize5">
        <xs:restriction base="xs:string">
            <xs:maxLength value="5"/>
        </xs:restriction>
    </xs:simpleType>
 
</xs:schema>

Notice the following constraints:

  • The customer's name cannot be longer than five (5) characters.

  • The customer cannot have more than two (2) phone numbers.

The Customer class is shown below. Notice that the class does not contain any validation code.

Example 2-26 Sample Customer Class

package example;
 
import java.util.ArrayList;
import java.util.List;
 
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
 
@XmlRootElement
public class Customer {
   private String name;
 
   private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>();
 
   public String getName() {
      return name;
   }
 
   public void setName(String name) {
      this.name = name;
   }
 
   @XmlElement(name="phone-number")
   public List<PhoneNumber> getPhoneNumbers() {
      return phoneNumbers;
   }
 
   public void setPhoneNumbers(List<PhoneNumber> phoneNumbers) {
      this.phoneNumbers = phoneNumbers;
   }
}
 

2.6.1 Using a ValidationEventHandler

You can receive JAXB validation events by providing your own subclass of ValidationEventHandler. The event is represented as an instance of ValidationEvent, and provides many details about the issue. The data is similar to what is available from a SAXParseException.

  • Returning false from the handleEvent method will cause the JAXB operation to stop.

  • Returning true will allow the method to continue, if possible.

In Example 2-27, we will simply print out an event's data when one is received:

Example 2-27 Sample ValidationEventHandler

package example;
 
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;
 
public class MyValidationEventHandler implements ValidationEventHandler {
 
    public boolean handleEvent(ValidationEvent event) {
        System.out.println("\nEVENT");
        System.out.println("SEVERITY:  " + event.getSeverity());
        System.out.println("MESSAGE:  " + event.getMessage());
        System.out.println("LINKED EXCEPTION:  " + event.getLinkedException());
        System.out.println("LOCATOR");
        System.out.println("    LINE NUMBER:  " + event.getLocator().getLineNumber());
        System.out.println("    COLUMN NUMBER:  " + event.getLocator().getColumnNumber());
        System.out.println("    OFFSET:  " + event.getLocator().getOffset());
        System.out.println("    OBJECT:  " + event.getLocator().getObject());
        System.out.println("    NODE:  " + event.getLocator().getNode());
        System.out.println("    URL:  " + event.getLocator().getURL());
        return true;
    }
 
}
 

2.6.2 Enabling Validation

In addition to providing an implementation of ValidationEventHandler, an instance of Schema must be set on the Marshaller or Unmarshaller.

Example 2-28 Sample Java Code

package example;
 
import java.io.File;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
 
public class UnmarshalDemo {
 
    public static void main(String[] args) throws Exception {
        SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        Schema schema = sf.newSchema(new File("customer.xsd"));
 
        JAXBContext jc = JAXBContext.newInstance(Customer.class);
 
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        unmarshaller.setSchema(schema);
        unmarshaller.setEventHandler(new MyValidationEventHandler());
        Customer customer = (Customer) unmarshaller.unmarshal(new File("input.xml"));
    }
 
}
 

2.6.2.1 Input (input.xml File)

<customer>
   <name>Jane Doe</name>
   <phone-number/>
   <phone-number/>
   <phone-number/>
</customer>

2.6.2.2 Output

The validation performed during the unmarshal raised three events. The first two events are related to the text value of the name element being too long. The third event is related to the extra phone-number element.

EVENT
SEVERITY:  1
MESSAGE:  cvc-maxLength-valid: Value 'Jane Doe' with length = '8' is not facet-valid with respect
          to maxLength '5' for type 'stringWithMaxSize5'.
LINKED EXCEPTION:  org.xml.sax.SAXParseException: cvc-maxLength-valid: Value 'Jane Doe' with length = '8'
                   is not facet-valid with respect to maxLength '5' for type 'stringWithMaxSize5'.
LOCATOR
    LINE NUMBER:  3
    COLUMN NUMBER:  25
    OFFSET:  -1
    OBJECT:  null
    NODE:  null
    URL:  null
 
EVENT
SEVERITY:  1
MESSAGE:  cvc-type.3.1.3: The value 'Jane Doe' of element 'name' is not valid.
LINKED EXCEPTION:  org.xml.sax.SAXParseException: cvc-type.3.1.3: The value 'Jane Doe' of element
                   'name' is not valid.
LOCATOR
    LINE NUMBER:  3
    COLUMN NUMBER:  25
    OFFSET:  -1
    OBJECT:  null
    NODE:  null
    URL:  null
 
EVENT
SEVERITY:  1
MESSAGE:  cvc-complex-type.2.4.d: Invalid content was found starting with element 'customer'. No child
          element '{phone-number}' is expected at this point.
LINKED EXCEPTION:  org.xml.sax.SAXParseException: cvc-complex-type.2.4.d: Invalid content was found starting
                   with element 'customer'. No child element '{phone-number}' is expected at this point.
LOCATOR
    LINE NUMBER:  7
    COLUMN NUMBER:  12
    OFFSET:  -1
    OBJECT:  null
    NODE:  null
    URL:  null

2.7 Understanding Events

JAXB offers several mechanisms to get event callbacks during the marshalling and unmarshalling processes. You can specify callback methods directly on your mapped objects, or define separate Listener classes and register them with the JAXB runtime.

2.7.1 Adding Event Listener Methods on JAXB Mapped Objects

On any of your objects you have mapped with JAXB, you have the option of specifying some special methods to allow you to receive event notification when that object is marshalled or unmarshalled. The methods must have the following signatures:

/**
 * Invoked by Marshaller after it has created an instance of this object.
 */
void beforeMarshal(Marshaller m);
 
/**
 * Invoked by Marshaller after it has marshalled all properties of this object.
 */
void afterMarshal(Marshaller m);
 
/**
 * This method is called immediately after the object is created and before the unmarshalling of this 
 * object begins. The callback provides an opportunity to initialize JavaBean properties prior to unmarshalling.
 */
void beforeUnmarshal(Unmarshaller u, Object parent);
 
/**
 * This method is called after all the properties (except IDREF) are unmarshalled for this object, 
 * but before this object is set to the parent object.
 */
void afterUnmarshal(Unmarshaller u, Object parent);
 

The following example shows how to write to a log every time a Company object is processed.

Example 2-29 Sample Event Listener

package example;
 
import java.util.Date;
import java.util.logging.Logger;
import javax.xml.bind.annotation.*;
 
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Company {
 
   @XmlAttribute
   private String id;
 
   void beforeMarshal(Marshaller m) {
      Logger.getLogger("example").info("COMPANY:[id=" + id + "] " + Thread.currentThread());
   }
 
   void afterMarshal(Marshaller m) {
      Logger.getLogger("example").info("COMPANY:[id=" + id + "] " + Thread.currentThread());
   }
 
   void beforeUnmarshal(Unmarshaller u, Object parent) {
      Logger.getLogger("example").info("COMPANY:[id=" + id + "] " + Thread.currentThread());
   }
 
   void afterUnmarshal(Unmarshaller u, Object parent) {
      Logger.getLogger("example").info("COMPANY:[id=" + id + "] " + Thread.currentThread());
   }
 
}

2.7.2 Registering Listeners on Marshallers and Unmarshallers

JAXB's Marshaller and Unmarshaller interfaces both define a setListener() method, which allows you to set your own custom Listener to intercept marshal and unmarshal events.

package javax.xml.bind;
 
public interface Marshaller {
   ...
   public static abstract class Listener {
     /**
      * Callback method invoked before marshalling from source to XML.
      *
      * This method is invoked just before marshalling process starts to marshal source.
      * Note that if the class of source defines its own beforeMarshal method,
      * the class specific callback method is invoked just before this method is invoked.
      *
      * @param source instance of JAXB mapped class prior to marshalling from it.
      */
      public void beforeMarshal(Object source) {}
 
     /**
      * Callback method invoked after marshalling source to XML.
      *
      * This method is invoked after source and all its descendants have been marshalled.
      * Note that if the class of source defines its own afterMarshal method,
      * the class specific callback method is invoked just before this method is invoked.
      *
      * @param source instance of JAXB mapped class after marshalling it.
      */
      public void afterMarshal(Object source) {}
   }
}
 
package javax.xml.bind;
 
public interface Unmarshaller {
   ...
   public static abstract class Listener {
 
     /**
      * Callback method invoked before unmarshalling into target.
      *
      * This method is invoked immediately after target was created and
      * before the unmarshalling of this object begins. Note that
      * if the class of target defines its own beforeUnmarsha method,
      * the class specific callback method is invoked before this method is invoked.
      *
      * @param target non-null instance of JAXB mapped class prior to unmarshalling into it.
      * @param parent instance of JAXB mapped class that will eventually reference target.
      *               null when target is root element.
      */
      public void beforeUnmarshal(Object target, Object parent) {}
 
     /**
      * Callback method invoked after unmarshalling XML data into target.
      *
      * This method is invoked after all the properties (except IDREF) are unmarshalled into target,
      * but before target is set into its parent object.
      * Note that if the class of target defines its own afterUnmarshal method,
      * the class specific callback method is invoked before this method is invoked.
      *
      * @param target non-null instance of JAXB mapped class prior to unmarshalling into it.
      * @param parent instance of JAXB mapped class that will reference target.
      *               null when target is root element.
      */
      public void afterUnmarshal(Object target, Object parent) {}
   }
}
 

This example performs the same logging as above, but using generic Listener classes. This makes it easier to log all JAXB objects in the system.

Example 2-30 Logging with the Listener Class

package example;
 
import java.util.logging.Logger;
 
private class MarshalLogger extends Marshaller.Listener {
   @Override
   public void afterMarshal(Object source) {
      Logger.getLogger("example").info(source + " "   + Thread.currentThread());
   }
 
   @Override
   public void beforeMarshal(Object source) {
      Logger.getLogger("example").info(source + " "   + Thread.currentThread());
   }
}
 
package example;
 
import java.util.logging.Logger;
 
private class UnmarshalLogger extends Unmarshaller.Listener {
   @Override
   public void afterUnmarshal(Object target, Object parent) {
      Logger.getLogger("example").info(target + " "   + Thread.currentThread());
   }
 
   @Override
   public void beforeUnmarshal(Object target, Object parent) {
      Logger.getLogger("example").info(target + " "   + Thread.currentThread());
   }
}

The following code sets up the listeners:

Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setListener(new MarshalLogger());
 
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setListener(new UnmarshalLogger());
 

An example of a typical marshal/unmarshal example, showing both the class-level and Marshaller/Unmarshaller-level event output:

Jun 2, 2011 6:31:59 PM example.Company beforeMarshal
INFO: COMPANY:[id=Zoltrix] Thread[main,5,main]
Jun 2, 2011 6:31:59 PM example.Tester$MarshalLogger beforeMarshal
INFO: example.Company@10e790c Thread[main,5,main]
Jun 2, 2011 6:31:59 PM example.Tester$MarshalLogger beforeMarshal
INFO: example.Employee@1db7df8 Thread[main,5,main]
Jun 2, 2011 6:31:59 PM example.Tester$MarshalLogger afterMarshal
INFO: example.Employee@1db7df8 Thread[main,5,main]
Jun 2, 2011 6:31:59 PM example.Tester$MarshalLogger beforeMarshal
INFO: example.Employee@3570b0 Thread[main,5,main]
Jun 2, 2011 6:31:59 PM example.Tester$MarshalLogger afterMarshal
INFO: example.Employee@3570b0 Thread[main,5,main]
Jun 2, 2011 6:31:59 PM example.Tester$MarshalLogger beforeMarshal
INFO: example.Employee@79717e Thread[main,5,main]
Jun 2, 2011 6:31:59 PM example.Tester$MarshalLogger afterMarshal
INFO: example.Employee@79717e Thread[main,5,main]
Jun 2, 2011 6:31:59 PM example.Company afterMarshal
INFO: COMPANY:[id=Zoltrix] Thread[main,5,main]
Jun 2, 2011 6:31:59 PM example.Tester$MarshalLogger afterMarshal
INFO: example.Company@10e790c Thread[main,5,main]
Jun 2, 2011 6:31:59 PM example.Company beforeUnmarshal
INFO: COMPANY:[id=null] Thread[main,5,main]
Jun 2, 2011 6:31:59 PM example.Tester$UnmarshalLogger beforeUnmarshal
INFO: example.Company@f0c0d3 Thread[main,5,main]
Jun 2, 2011 6:31:59 PM example.Tester$UnmarshalLogger beforeUnmarshal
INFO: example.Employee@4f80d6 Thread[main,5,main]
Jun 2, 2011 6:31:59 PM example.Tester$UnmarshalLogger afterUnmarshal
INFO: example.Employee@4f80d6 Thread[main,5,main]
Jun 2, 2011 6:31:59 PM example.Tester$UnmarshalLogger beforeUnmarshal
INFO: example.Employee@1ea0252 Thread[main,5,main]
Jun 2, 2011 6:31:59 PM example.Tester$UnmarshalLogger afterUnmarshal
INFO: example.Employee@1ea0252 Thread[main,5,main]
Jun 2, 2011 6:31:59 PM example.Tester$UnmarshalLogger beforeUnmarshal
INFO: example.Employee@3e89c3 Thread[main,5,main]
Jun 2, 2011 6:31:59 PM example.Tester$UnmarshalLogger afterUnmarshal
INFO: example.Employee@3e89c3 Thread[main,5,main]
Jun 2, 2011 6:31:59 PM example.Company afterUnmarshal
INFO: COMPANY:[id=Zoltrix] Thread[main,5,main]
Jun 2, 2011 6:31:59 PM example.Tester$UnmarshalLogger afterUnmarshal
INFO: example.Company@f0c0d3 Thread[main,5,main]

2.8 Querying Objects by XPath

In addition to using conventional Java access methods to get and set your object's values, EclipseLink MOXy also allows you to access values using an XPath statement. There are special APIs on EclipseLink's JAXBContext to allow you to get and set values by XPath.

For example, consider the following XML document:

<customer id="1141">
   <first-name>Jon</first-name>
   <last-name>Smith</last-name>
   <phone-number>
      <area-code>515</area-code>
      <number>2726652</number>
   </phone-number>
</customer>
 

Typical application code might look something like this:

Customer customer = (Customer) jaxbContext.createUnmarshaller().unmarshal(instanceDoc);
...
int customerId = customer.getId();
customer.setFirstName("Bob");
customer.getPhoneNumber().setAreaCode("555");
...
jaxbContext.createMarshaller().marshal(customer, System.out);
 

You could instead use XPath to access these values:

Customer customer = (Customer) jaxbContext.createUnmarshaller().unmarshal(instanceDoc);
...
int customerId = jaxbContext.getValueByXPath(customer, "@id", null, Integer.class);
jaxbContext.setValueByXPath(customer, "first-name/text()", null, "Bob");
jaxbContext.setValueByXPath(customer, "phone-number/area-code/text()", null, "555");
...
jaxbContext.createMarshaller().marshal(customer, System.out);

2.9 Binding to an Existing Document

The JAXB Binder interface (introduced in JAXB 2.0) allows you to preserve an entire XML document, even if only some of the items are mapped.

Normally, when using an Unmarshaller to load the XML document into objects, and a Marshaller to save the objects back to XML, the unmapped content will be lost.

A Binder can be created from the JAXBContext to interact with the XML in the form of a DOM. The Binder maintains an association between the Java objects and their corresponding DOM nodes.

Example 2-31 Using a Binder

import java.io.File;
import javax.xml.bind.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.*;
 
public class BinderDemo {
 
    public static void main(String[] args) throws Exception {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        File xml = new File("input.xml");
        Document document = db.parse(xml);
 
        JAXBContext jc = JAXBContext.newInstance(Customer.class);
 
        Binder<Node> binder = jc.createBinder();
        Customer customer = (Customer) binder.unmarshal(document);
        customer.getAddress().setStreet("2 NEW STREET");
        PhoneNumber workPhone = new PhoneNumber();
        workPhone.setType("work");
        workPhone.setValue("555-WORK");
        customer.getPhoneNumbers().add(workPhone);
        binder.updateXML(customer);
 
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer t = tf.newTransformer();
        t.transform(new DOMSource(document), new StreamResult(System.out));
    }
 
}
 

The Binder applies the changes to the original DOM instead of creating a new XML document, thereby preserving the entire XML infoset.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<customer>
    <UNMAPPED_ELEMENT_1/>
    <name>Jane Doe</name>
    <!-- COMMENT #1 -->
    <address>
        <UNMAPPED_ELEMENT_2/>
        <street>2 NEW STREET</street>
        <!-- COMMENT #2 -->
        <UNMAPPED_ELEMENT_3/>
        <city>Any Town</city>
    </address>
    <!-- COMMENT #3 -->
    <UNMAPPED_ELEMENT_4/>
    <phone-number type="home">555-HOME</phone-number>
    <!-- COMMENT #4 -->
    <phone-number type="cell">555-CELL</phone-number>
    <phone-number type="work">555-WORK</phone-number>
    <UNMAPPED_ELEMENT_5/>
    <!-- COMMENT #5 -->
</customer>