2 EclipseLink MOXy Runtime
To specify EclipseLink MOXy as your JAXB provider:
-
Add the JAXB APIs (included in Java SE 8) and
eclipselink.jar
on your classpath. -
Use a
jaxb.properties
file (in the same package as your domain classes).
This chapter includes the following sections:
Specifying the EclipseLink Runtime
To use EclipseLink MOXy as your JAXB implementation, identify the EclipseLink JAXBContextFactory
in your jaxb.properties
file.
-
Create a text file named
jaxb.properties
, specifying EclipseLink'sJAXBContextFactory
as the factory used to build newJAXBContexts
:javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
-
Copy the file to the same package (directory) in which your model classes reside.
-
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".
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
Using the JAXBContext API
The methods on JAXBContext
(shown in Example 2-1) are used to create new instances.
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".
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
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.
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:
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:
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.
Example 2-2 Sample jaxb.index File
Employee PhoneNumber
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); } }
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
.
For information on using a MetadataSource, see "Using MetadataSource".
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);
}
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
:
For more information on the XML Bindings format, see "Using XML Bindings".
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);
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.
You can customize the Employee
to use an EclipseLink XMLAdapter for marshalling/unmarshalling PhoneNumbers
by using the XML Bindings in Example 2-7.
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-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; ... }
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>
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);
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.
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.
Table 2-1 Binding Document Attributes
Attribute | Description |
---|---|
|
The root of the XML Bindings document. This is also where you can define top-level properties for your JAXB system, such as the |
|
Defines properties related to the schema-level of your JAXB system. Corresponds to the JAXB |
|
Defines mapping information for each of your Java classes. |
|
Defines Java enumerations that can be used with your Java types. |
|
Defines an |
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>
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).
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.
Now, assume that we would like to make the following mapping changes:
-
Change the XML element name of
custId
tocustomer-id
-
Change the root element name of the class to
customer-info
-
Write the picture to XML as
picture-hex
in hexbinary
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.
The Bindings must then be provided during JAXB context creation. Bindings information is passed in via the properties
argument:
When providing Bindings, during JAXB context creation Oracle TopLink will:
-
Customer.class
will be analyzed and JAXB mappings will be generated as usual. -
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>
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>
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>
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);
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.
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-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); ...
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); ...
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 ofXmlRegistries
. -
For any duplicated
XmlRegistry
entries, a merged list ofXmlElementDecls
will be created.In the case that an
XmlElementDecl
for the sameXmlRegistry
class appears in multiple bindings files, thatXmlElementDecl
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.
-
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-complet
e:
-
Default JAXB mapping is generated for
custId
(note thatcustId
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
andsalary
-
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
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>
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>
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.
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);
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);
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);
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); } } }
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; } }
Generating an XML Schema
To generate an XML schema from a Java object model:
-
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; } }
-
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);
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
.
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-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>
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; } }
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; } }
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"));
}
}
Input (input.xml File)
<customer> <name>Jane Doe</name> <phone-number/> <phone-number/> <phone-number/> </customer>
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
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.
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()); } }
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.
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]
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()); } }
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);
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.
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>
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)); } }