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:
Add the JAXB APIs (included in Java SE 6) 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:
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's JAXBContextFactory
as the factory used to build new JAXBContexts
:
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".
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
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".
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.
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:
jaxb.index
FileThe 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.
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); } }
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".
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".
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.
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);
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.
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 |
---|---|
|
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 |
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).
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:
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>
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); ...
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.
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:
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; ... }
<?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
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".
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.
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);
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);
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);
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); } } }
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; } }
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);
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; } }
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; } }
In addition to providing an implementation of ValidationEventHandler
, an instance of Schema
must be set on the Marshaller
or Unmarshaller
.
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"));
}
}
<customer> <name>Jane Doe</name> <phone-number/> <phone-number/> <phone-number/> </customer>
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
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.
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()); } }
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]
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);
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.
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>