This chapter describes how to use JPA with the Java Architecture for XML Binding (JAXB)—the Java EE standard for mapping POJOs (Plain Old Java Objects) to XML—and its Mapping Objects to XML (MOXy) extensions to map JPA entities to XML. Mapping JPA entities to XML is useful when you want to create a data access service with Java API for RESTful Web Services (JAX-RS), Java API for XML Web Services (JAX-WS), or Spring.
This chapter contains the following topics:
Section 15.3, "Mapping Simple Java Values to XML Text Nodes"
Section 15.4, "Using XML Metadata Representation to Override JAXB Annotations"
Users need to map JPA entities to XML.
TopLink provides support for the JAXB standard through EclipseLink JAXB extensions.
TopLink 12c Release 1 (12.1.2) or later.
Note:
TopLink's core functionality is provided by EclipseLink, the open source persistence framework from the Eclipse Foundation. EclipseLink implements Java Persistence API (JPA), Java Architecture for XML Binding (JAXB), and other standards-based persistence technologies, plus extensions to those standards. TopLink includes all of EclipseLink, plus additional functionality from Oracle.
XML document
See the following EclipseLink and JAXB examples for related information:
This chapter demonstrates some typical techniques for mapping JPA entities to XML. Working with the examples that follow requires some understanding of such high-level JPA-to-XML mapping concepts, such as JAXB, MOXy, XML binding, and how to override JAXB annotations. The following sections will give you a basic understanding of these concepts:
XML binding is how you represent information in an XML document as an object in computer memory. This allows applications to access the data in the XML from the object rather than using the Domain Object Model (DOM), the Simple API for XML (SAX) or the Streaming API for XML (StAX) to retrieve the data from a direct representation of the XML itself. When binding, JAXB applies a tree structure to the graph of JPA entities. Multiple tree representations of a graph are possible and will depend on the root object chosen and the direction the relationships are traversed.
You can find examples of XML binding with JAXB in Section 15.2, "Binding JPA Entities to XML".
JAXB is a Java API that allows a Java program to access an XML document by presenting that document to the program in a Java format. This process, called binding, represents information in an XML document as an object in computer memory. In this way, applications can access the data in the XML from the object rather than using the Domain Object Model (DOM) or the Streaming API for XML (SAX) to retrieve the data from a direct representation of the XML itself. Usually, an XML binding is used with JPA entities to create a data access service by leveraging a JAX-WS or JAX-RS implementation. Both of these Web Service standards use JAXB as the default binding layer. This service provides a means to access data exposed by JPA across computers, where the client computer might or might not be using Java.
JAXB uses an extended set of annotations to define the binding rules for Java-to-XML mapping. These annotations are subclasses of the javax.xml.bind.
*
packages in the EclipseLink API. For more information about these annotations, see Java API Reference for Oracle TopLink.
For more information about JAXB, see "Java Architecture for XML Binding (JAXB)" at:
MOXy is EclipseLink's JAXB implementation. It allows you to map a POJO model to an XML schema, greatly enhancing your ability to create JPA-to-XML mappings. MOXy supports all the standard JAXB annotations in the javax.xml.bind.annotation
package plus has its own extensions in the org.eclipse.persistence.oxm.annotations
package. You can use these latter annotations in conjunction with the standard annotations to extend the utility of JAXB. Because MOXy represents the optimal JAXB implementation, you still implement it whether or not you explicitly use any of its extensions. MOXy offers these benefits:
It allows you to map your own classes to your own XML schema, a process called "Meet in the Middle Mapping". This avoids static coupling of your mapped classes with a single XML schema,
It offers specific features, such as Xpath-based mapping, JSON binding, and compound key mapping and mapping relationships with back-pointers to address critical JPA-to-XML mapping issues.
It allows you to map your existing JPA models to industry standard schema.
It allows you to combine MOXy mappings and EclipseLink's persistence framework to interact with your data through JCA.
It offers superior performance in several scenarios.
For more information about MOXy, see the MOXy FAQ at:
Annotations are not always the most effective way to map JPA to XML. For example, you would not use JAXB if:
You want to specify metadata for a third-party class but do not have access to the source.
You want to map an object model to multiple XML schemas, because JAXB rules preclude applying more than one mapping by using annotations.
Your object model already contains too many annotations—for example, from such services as JPA, Spring, JSR-303, and so on—and you want to specify the metadata elsewhere.
Under these and similar circumstances, you can use an XML data representation by exposing the eclipselink_oxm.xml
file.
XML metadata works in two modes:
It adds to the metadata supplied by annotations. This is useful when:
Annotations define version one of the XML representation, and you use XML metadata to tweak the metadata for future versions.
You use the standard JAXB annotations, and use the XML metadata for the MOXy extensions. In this way you don't introduce new compile time dependencies in the object model.
It completely replaces the annotation metadata, which is useful when you want to map to different XML representations.
To see how to use XML data representation, see Section 15.4, "Using XML Metadata Representation to Override JAXB Annotations"
The following examples demonstrate how to bind JPA entities to XML by using JAXB annotations. For more information about binding, see Section 15.1.1, "Understanding XML Binding" for more information about JAXB, see Section 15.1.2, "Understanding JAXB"
The following exercise demonstrate show to use JAXB to derive an XML representation from a set of JPA entities, a process called "binding" (read about XML binding in Section 15.2, "Binding JPA Entities to XML"). These examples will show how to bind two common JPA relationships:
Privately-owned relationships
Shared reference relationships
to map an Employee entity to that employee's phone number, address, and department.
Since all of the following examples use the same accessor type, FIELD
, define it at the package level by using the JAXB annotation @XmlAccessorType
. At this point, you would also import the necessary classes:
@XmlAccessorType(XmlAccessType.FIELD) package com.example.model; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType;
A "privately-owned" relationship occurs when the target object is only referenced by a single source object. This type of relationship can be either one-to-one and embedded or one-to-many.
This Task shows how to create bi-directional mappings for both of these types of relationships between the Employee
entity and the Address
and PhoneNumber
entities.
The JPA @OneToOne
and @Embedded
annotations indicate that only one instance of the source entity is able to refer to the same target entity instance. This example shows how to map the Employee
entity to the Address
entity and back. This is considered a one-to-one mapping because the employee can be associated with only one address. Since this relationship is bi-directional—that is, Employee
points to Address
, which must point back to Employee
—it uses the EclipseLink extension @XmlInverseReference
to represent the back-pointer.
To create the one-to-one and embedded mapping:
Ensure that the accessor type FIELD
has been defined at the package level, as described in Section 15.2.1.1, "Task 1: Define the Accessor Type and Import Classes".
Map one direction of the relationship, in this case, the employee
property on Address
, by inserting the @OneToOne
annotation in the Employee
entity:
@OneToOne(mappedBy="resident") private Address residence;
The mappedBy
argument indicates that the relationship is owned by the resident
field.
Map the return direction—that is, the address
property on Employee
—by inserting the @OneToOne
and @XmlInverseMapping
annotations into the Address entity:
@OneToOne @JoinColumn(name="E_ID") @XmlInverseReference(mappedBy="residence") private Employee resident;
The mappedBy
field indicates that this relationship is owned by the residence
field. @JoinColumn
identifies the column that will contain the foreign key.
The entities should look like those shown in Example 15-1 and Example 15-2
.
The JPA @OneToMany
annotation indicates that a single instance of the source entity can refer to multiple instances of the same target entity. For example, one employee can have multiple phone numbers, such as a land line, a mobile number, a desired contact number, and an alternative workplace number. Each different number would be an instance of the PhoneNumber
entity and a single Employee
entity could point to each instance.
This Task maps the employee to one of that employee's phone numbers and back. Since the relationship between Employee
and PhoneNumber
is bi-directional, the example again uses the EclipseLink extension @XmlInverseReference
to map the back-pointer.
To create a one-to-many mapping:
Ensure that the accessor type FIELD
has been defined at the package level, as described in Section 15.2.1.1, "Task 1: Define the Accessor Type and Import Classes".
Map one direction of the relationship, in this case, the employee property on PhoneNumber
, by inserting the @OneToMany
annotation in the Employee
entity:
@OneToMany(mappedBy="contact") private List<PhoneNumber> contactNumber;
The mappedBy
field indicates that this relationship is owned by the contact
field.
Map the return direction—that is, the phone number property on Employee
—by inserting the @ManyToOne
and @XmlInverseMapping
annotations into the PhoneNumber
entity:
@ManyToOne @JoinColumn(name="E_ID", referencedColumnName = "E_ID") @XmlInverseReference(mappedBy="contactNumber") private Employee contact;
The mappedBy
field indicates that this relationship is owned by the contactNumber
field. The @JoinColumn
annotation identifies the column that will contain the foreign key (name="E_ID"
) and the column referenced by the foreign key (referencedColumnName = "E_ID"
).
The entities should look like those shown in Example 15-1 and Example 15-3
.
A shared reference relationship occurs when target objects are referenced by multiple source objects. For example, a business might be segregated into multiple departments, such as IT, human resources, finance, and so on. Each of these departments has multiple employees of differing job descriptions, pay grades, locations, and so on. Managing departments and employees requires shared reference relationships.
Since a shared reference relationship cannot be safely represented as nesting in XML, we use key relationships. In order to leverage the ID fields on JPA entities, you need to use the EclipseLink JAXB @XmlID
annotation on non-String fields and properties and @XmlIDREF
on string fields and properties.
This section contains examples that show how to map a many-to-one shared reference relationship and a many-to-many shared reference relationship.
In a many-to-one mapping, one or more instances of the source entity are able to refer to the same target entity instance. This example demonstrates how to map an employee to one of that employee's multiple phone numbers.
To map a many-to-one shared reference relationship:
Ensure that the accessor type FIELD
has been defined at the package level, as described in Section 15.2.1.1, "Task 1: Define the Accessor Type and Import Classes".
Map one direction of the relationship, in this case the phone number property on Employee
, by inserting the @ManyToOne
annotation in the PhoneNumber
entity:
@ManyToOne @JoinColumn(name="E_ID", referencedColumnName = "E_ID") @XmlIDREF private Employee contact;
The @JoinColumn
annotation identifies the column that will contain the foreign key (name="E_ID"
) and the column referenced by the foreign key (referencedColumnName = "E_ID"
). The @XmlIDREF
annotation indicates that this will be the primary key for the corresponding table.
Map the return direction—that is, the employee property on PhoneNumber —by inserting the @OneToMany
and @XmlInverseMapping
annotations into the Address entity:
@OneToMany(mappedBy="contact") @XmlInverseReference(mappedBy="contact") private List<PhoneNumber> contactNumber;
The mappedBy
field for both annotations indicates that this relationship is owned by the contact
field.
The entities should look like those shown in Example 15-1 and Example 15-3
.
The @ManyToMany
annotation indicates that one or more instances of the source entity are able to refer to one or more target entity instances. Since the relationship between Department
and Employee
is bi-directional, this example again uses the EclipseLink's @XmlInverseReference
annotation to represent the back-pointer.
To map a many-to-many shared reference relationship, do the following:
Ensure that the accessor type FIELD
has been defined at the package level, as described in Section 15.2.1.1, "Task 1: Define the Accessor Type and Import Classes".
Create a Department
entity by inserting the following code:
@Entity public class Department {
Under this entity define the many-to-many relationship and the entity's join table by inserting the following code:
@ManyToMany @JoinTable(name="DEPT_EMP", joinColumns = @JoinColumn(name="D_ID", referencedColumnName = "D_ID"), inverseJoinColumns = @JoinColumn(name="E_ID", referencedColumnName = "E_ID"))
This code creates a join table called DEPT_EMP
and identifies the column that will contain the foreign key (name="E_ID"
) and the column referenced by the foreign key (referencedColumnName = "E_ID"
). Additionally, it identifies the primary table on the inverse side of the association.
Complete the initial mapping—in this case, the Department
property employee
—and make it a foreign key for this entity by inserting the following code:
@XmlIDREF private List<Employee> member;
In the Employee
entity created in Section 15.2.1.2.1, "Mapping a One-to-One and Embedded Relationship", specifying that
eId
is the primary key for JPA (@Id
annotation), and for JAXB (@XmlID
annotation) by inserting the following code:
@Id @Column(name="E_ID") @XmlID private BigDecimal eId;
Still within the Employee
entity, complete the return mapping by inserting the following code:
@ManyToMany(mappedBy="member") @XmlInverseReference(mappedBy="member") private List<Department> team;
The entities should look like those shown in Example 15-1 and Example 15-4
.
Once the mappings are created, the entities should look like those in the following examples:
Note:
In order to save space, package names, import statements, and the get/set methods have been omitted from the code examples. All examples use standard JPA annotations.
@Entity
public class Employee {
@Id
@Column(name="E_ID")
private BigDecimal eId;
private String name;
@OneToOne(mappedBy="resident")
private Address residence;
@OneToMany(mappedBy="contact")
private List<PhoneNumber> contactNumber;
@ManyToMany(mappedBy="member")
private List<Department> team;
}
@Entity
public class Address {
@Id
@Column(name="E_ID", insertable=false, updatable=false)
private BigDecimal eId;
private String city;
private String street;
@OneToOne
@JoinColumn(name="E_ID")
private Employee resident;
}
Example 15-3 PhoneNumber Entity
@Entity
@Table(name="PHONE_NUMBER")
public class PhoneNumber {
@Id
@Column(name="P_ID")
private BigDecimal pId;
@ManyToOne
@JoinColumn(name="E_ID", referencedColumnName = "E_ID")
private Employee contact;
private String num;
}
Example 15-4 Department Entity
@Entity
public class Department {
@Id
@Column(name="D_ID")
private BigDecimal dId;
private String name;
@ManyToMany
@JoinTable(name="DEPT_EMP", joinColumns =
@JoinColumn(name="D_ID", referencedColumnName = "D_ID"),
inverseJoinColumns = @JoinColumn(name="E_ID",
referencedColumnName = "E_ID"))
private List<Employee> member;
}
When a JPA entity has compound primary keys, you can bind it by using JAXB annotations and certain EclipseLink extensions, as shown in the following example.
Define the accessor type as FIELD
, as described in Section 15.2.1.1, "Task 1: Define the Accessor Type and Import Classes"
To create the target object, do the following:
Create an Employee
entity with a composite primary key class called EmployeeID
to map to multiple fields or properties of the entity:
@Entity
@IdClass(EmployeeId.class)
public class Employee {
Specify the first primary key, eId, of the entity and map it to a column:
@Id @Column(name="E_ID") @XmlID private BigDecimal eId;
Specify the second primary key, country. In this instance, you need to use @XmlKey
to identify the primary key because only one property— eId
—can be annotated with the @XmlID
.
@Id @XmlKey private String country;
The @XmlKey
annotation marks a property as a key that will be referenced by using a key-based mapping via the @XmlJoinNode
annotation in the source object. This is similar to the @XmlKey
annotation except it doesn't require the property be bound to the schema type ID. This is a typical application of the @XmlKey
annotation.
Create a many-to-one mapping of the Employee
property on PhoneNumber
by inserting the following code:
@OneToMany(mappedBy="contact") @XmlInverseReference(mappedBy="contact") private List<PhoneNumber> contactNumber;
The Employee entity should look like Example 15-5
Example 15-5 Employee Entity with Compound Primary Keys
@Entity @IdClass(EmployeeId.class) public class Employee { @Id @Column(name="E_ID") @XmlID private BigDecimal eId; @Id @XmlKey private String country; @OneToMany(mappedBy="contact") @XmlInverseReference(mappedBy="contact") private List<PhoneNumber> contactNumber; } public class EmployeeId { public BigDecimal eId; public String country; public EmployeeId(BigDecimal eId, String country) { this.id = id; this.country = country;; } public boolean equals(Object other) { if (other instanceof EmployeeId) { final EmployeeId otherEmployeeId = (EmployeeId) other; return (otherEmployeeId.eId.equals(eId) && otherEmployeeId.country.equals(country)); } return false; } }
This Task creates the source object, the PhoneNumber
entity. Because the target object has a compound key, we need to use the EclipseLink's @XmlJoinNodes
annotation to set up the mapping.
To create the source object:
Create the PhoneNumber
entity:
@Entity public class PhoneNumber {
Create a many-to-one relationship and define the join columns:
@ManyToOne @JoinColumns({ @JoinColumn(name="E_ID", referencedColumnName = "E_ID"), @JoinColumn(name="E_COUNTRY", referencedColumnName = "COUNTRY") })
Set up the mapping by using the EclipseLink's @XmlJoinNodes
annotation
@XmlJoinNodes( { @XmlJoinNode(xmlPath="contact/id/text()", referencedXmlPath="id/text()"), @XmlJoinNode(xmlPath="contact/country/text()", referencedXmlPath="country/text()") })
Define the contact
property:
private Employee contact; }
The target object should look like Example 15-6.
Example 15-6 PhoneNumber Entity
@Entity public class PhoneNumber { @ManyToOne @JoinColumns({ @JoinColumn(name="E_ID", referencedColumnName = "E_ID"), @JoinColumn(name="E_COUNTRY", referencedColumnName = "COUNTRY") }) @XmlJoinNodes( { @XmlJoinNode(xmlPath="contact/id/text()", referencedXmlPath="id/text()"), @XmlJoinNode(xmlPath="contact/country/text()", referencedXmlPath="country/text()") }) private Employee contact; }
An embedded ID defines a separate Embeddable
Java class to contain the entity's primary key. It is defined through the @EmbeddedId
annotation.The embedded ID's Embeddable
class must define each id attribute for the entity using basic mappings. All attributes in the embedded Id's Embeddable
are assumed to be part of the primary key. This exercise shows how to derive an XML representation from a set of JPA entities using JAXB when a JPA entity has an embedded ID class.
Define the XML accessor type as FIELD
, as described in Section 15.2.1.1, "Task 1: Define the Accessor Type and Import Classes"
The target object is an entity called Employee
and contains the mapping for an employee's contact phone number. Creating this target object requires implementing a DescriptorCustomizer
interface, so you must include EclipseLink's @XmlCustomizer
annotation Also, since the relationship is bidirectional, you must also implement the @XmlInverseReference
. annotation.
To create the target object:
Create the Employee
entity. Use the @IdClass
annotation to specify that the EmployeeID
class will be mapped to multiple properties of the entity.
@Entity
@IdClass(EmployeeId.class)
public class Employee {
}
Define the id
property and make it embeddable.
@EmbeddedId @XmlPath("."); private EmployeeId id;
Define a one-to-many mapping—in this case, the employee
property on PhoneNumber
. Because the relationship is bi-directional, use @XmlInverseReference
to define the return mapping. Both of these relationships will be owned by the contact field, as indicated by the mappedBy
argument.
@OneToMany(mappedBy="contact") @XmlInverseReference(mappedBy="contact") private List<PhoneNumber> contactNumber;
The completed target object should look like Example 15-7.
The source object in this example has a compound key, so you must mark the field @XmlTransient
to prevent a key from being mapped by itself. Use EclipseLink's @XmlCustomizer
annotation to set up the mapping.
To create the source object, do the following:
Create the PhoneNumber
entity.
@Entity public class PhoneNumber { }
Create a many-to-one mapping and define the join columns.
@ManyToOne @JoinColumns({ @JoinColumn(name="E_ID", referencedColumnName = "E_ID"), @JoinColumn(name="E_COUNTRY", referencedColumnName = "COUNTRY") })
Define the XML nodes for the mapping, using the EclipseLink @XmlJoinNodes
annotation extension. If the target object had a single ID, you would use the @XmlIDREF
annotation.
@XmlJoinNodes( { @XmlJoinNode(xmlPath="contact/id/text()", referencedXmlPath="id/text()"), @XmlJoinNode(xmlPath="contact/country/text()", referencedXmlPath="country/text()") }) private Employee contact;
The completed PhoneNumber
class should look like Example 15-8.
Example 15-8 PhoneNumber Class as Source Object
@Entity public class PhoneNumber { @ManyToOne @JoinColumns({ @JoinColumn(name="E_ID", referencedColumnName = "E_ID"), @JoinColumn(name="E_COUNTRY", referencedColumnName = "COUNTRY") }) @XmlJoinNodes( { @XmlJoinNode(xmlPath="contact/id/text()", referencedXmlPath="id/text()"), @XmlJoinNode(xmlPath="contact/country/text()", referencedXmlPath="country/text()") }) private Employee contact; }
Code added in Task 4 indicated the need to create the XMLObjectReferenceMappings to the new values. This requires to implementing the DescriptorCustomizer
as the PhoneNumberCustomizer
and adding the multiple key mappings. To do this:
Implement DescriptorCustomizer
as PhoneNumberCustomizer
. Be sure to import org.eclipse.persistence.oxm.mappings.XMLObjectReferenceMapping
:
import org.eclipse.persistence.oxm.mappings.XMLObjectReferenceMapping;
public class PhoneNumberCustomizer implements DescriptorCustomizer {
In the customize
method, update the following mappings:
contactMapping.setAttributeName
to "contact"
.
contactMapping.addSourceToTargetKeyFieldAssociation
to "contact/@eID", "eId/text()"
.
contactMapping.addSourceToTargetKeyFieldAssociation
to "contact/@country", "country/text()"
.
PhoneNumberCustomizer
should look like Example 15-9.
Example 15-9 PhoneNumber Customizer with Updated Key Mappings
import org.eclipse.persistence.config.DescriptorCustomizer; import org.eclipse.persistence.descriptors.ClassDescriptor; import org.eclipse.persistence.oxm.mappings.XMLObjectReferenceMapping; public class PhoneNumberCustomizer implements DescriptorCustomizer { public void customize(ClassDescriptor descriptor) throws Exception { XMLObjectReferenceMapping contactMapping = new XMLObjectReferenceMapping(); contactMapping.setAttributeName("contact"); contactMapping.setReferenceClass(Employee.class); contactMapping.addSourceToTargetKeyFieldAssociation("contact/@eID", "eId/text()"); contactMapping.addSourceToTargetKeyFieldAssociation("contact/@country", "country/text()"); descriptor.addMapping(contactMapping); } }
As demonstrated in the preceding examples, EclipseLink implements the standard JAXB annotations to map JPA entities to an XML representation. You can also express metadata by using the EclipseLink XML Bindings document. Not only can you use XML bindings to separate your mapping information from your actual Java class but you can also use it for more advanced metadata tasks such as:
Augmenting or overriding existing annotations with additional mapping information.
Specifying all mapping information externally, without using any Java annotations.
Defining your mappings across multiple Bindings documents.
Specifying "virtual" mappings that do not correspond to concrete Java fields
For more information about using the XML Bindings document, see XML Bindings in the JAXB/MOXy documentation at http://wiki.eclipse.org/EclipseLink/UserGuide/MOXy/Runtime/XML_Bindings
.
This section demonstrates several ways to map simple Java values directly to XML text nodes. It includes the following examples:
This example maps the id
property in the Java object Customer
to its XML representation as an attribute of the <customer>
element. The XML will be based on the schema in Example 15-10.
Example 15-10 Example XML Schema
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:element name="customer" type="customer-type"/> <xsd:complexType name="customer-type"> <xsd:attribute name="id" type="xsd:integer"/> </xsd:complexType> </xsd:schema>
The following procedures demonstrate how to map the id
property from the Java object and, alternately, how to represent the value in EclipseLink's Object-to-XML Mapping (OXM) metadata format.
The key to creating this mapping from a Java object is the @XmlAttribute
JAXB annotation, which maps the field to the XML attribute. To create this mapping:
Create the object and import javax.xml.bind.annotation.*
:
package example; import javax.xml.bind.annotation.*;
Declare the Customer
class and use the @XmlRootElement
annotation to make it the root element. Set the XML accessor type to FIELD
:
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer {
Map the id
property in the Customer
class as an attribute:
@XmlAttribute
private Integer id;
The object should look like Example 15-11.
If you want to represent the mapping in EclipseLink's OXM metadata format, you need to use the XML tags defined in the eclipselink-oxm.xml
file and populate them with the appropriate values, as shown in Example 15-12.
Example 15-12 Mapping id as an Attribute in OXM Metadata Format
... <java-type name="Customer"> <xml-root-element name="customer"/> <java-attributes> <xml-attribute java-attribute="id"/> </java-attributes> </java-type> ...
For more information about the OXM metadata format, see Section 15.4, "Using XML Metadata Representation to Override JAXB Annotations".
EclipseLink makes it easy for you to map values from a Java object to various kinds of XML text nodes; for example, to simple text nodes, text nodes in a simple sequence, in a subset, or by position. These mappings are demonstrated in the following examples:
You can map a value from a Java object either by using JAXB annotations in the Java object or, alternately, by representing the mapping in EclipseLink's OXM metadata format.
Assuming the associated schema defines an element called <phone-number>
which accepts a string value, you can use the @XmlValue
annotation to map a string to the <phone-number>
node. Do the following:
Create the object and import javax.xml.bind.annotation.*
:
package example; import javax.xml.bind.annotation.*;
Declare the PhoneNumber
class and use the @XmlRootElement
annotation to make it the root element with the name phone-number. Set the XML accessor type to FIELD
:
@XmlRootElement(name="phone-number") @XmlAccessorType(XmlAccessType.FIELD) public class PhoneNumber {
Insert the @XmlValue
annotation on the line before the number
property in the Customer class to map this value as an attribute:
@XmlValue
private String number;
The object should look like Example 15-13.
If you want to represent the mapping in EclipseLink's OXM metadata format, you need to use the XML tags defined in the eclipselink-oxm.xml
file and populate them with the appropriate values, as shown in Example 15-14.
You can map a sequence of values, for example a customer's first and last name, as separate elements either by using JAXB annotations or by representing the mapping in EclipseLink's OXM metadata format. The following procedures illustrate how to map values for a customers' first names and last names
Assuming the associated schema defines the following elements:
<customer>
of the type customer-type, which itself is defined as a complexType
.
Sequential elements called <first-name>
and <last-name>
, both of the type string
.
you can use the @XmlElement
annotation to map values for a customer's first and last name to the appropriate XML nodes. To do so:
Create the object and import javax.xml.bind.annotation.*
:
package example; import javax.xml.bind.annotation.*;
Declare the Customer
class and use the @XmlRootElement
annotation to make it the root element. Set the XML accessor type to FIELD
:
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer {
Define the firstname
and lastname
properties and annotate them with the @XmlElement
annotation. Use the name=
argument to customize the XML element name (if you do not explicitly set the name with name=
, the XML element will match the Java attribute name; for example, here the <first-name>
element combination would be specified <firstName> </firstName>
in XML).
@XmlElement(name="first-name") private String firstName; @XmlElement(name="last-name") private String lastName;
The object should look like Example 15-15.
Example 15-15 Customer Object Mapping Values to a Simple Sequence
package example; import javax.xml.bind.annotation.*; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlElement(name="first-name") private String firstName; @XmlElement(name="last-name") private String lastName; ... }
If you want to represent the mapping in EclipseLink's OXM metadata format, you need to use the XML tags defined in the eclipselink-oxm.xml
file and populate them with the appropriate values, as shown in Example 15-16.
Example 15-16 Mapping Sequential Attributes in OXM Metadata Format
... <java-type name="Customer"> <xml-root-element name="customer"/> <java-attributes> <xml-element java-attribute="firstName" name="first-name"/> <xml-element java-attribute="lastName" name="last-name"/> </java-attributes> </java-type> ...
You can map values from a Java object to text nodes that are nested as a subelement in the XML document by using JAXB annotations or by representing the mapping in EclipseLink's OXM metadata format. For example, if you want to populate <first-name>
and <last-name>
elements, which are sub-elements of a <personal-info>
element under a <customer>
root, you could use the following procedures to achieve these mappings.
Assuming the associated schema defines the following elements:
<customer>
of the type customer-type, which itself is defined as a complexTpe.
<personal-info>
Sub-elements of <personal-info>
called <first-name>
and <last-name>
, both of the type string
you can use JAXB annotations to map values for a customer's first and last name to the appropriate XML sub-element nodes. Because this example goes beyond a simple element name customization and actually introduces new XML structure, it uses EclipseLink's @XmlPath
annotation. To achieve this mapping:
Create the object and import javax.xml.bind.annotation.*
and org.eclipse.persistence.oxm.annotations.*
.
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.*;
Declare the Customer
class and use the @XmlRootElement
annotation to make it the root element. Set the XML accessor type to FIELD
:
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer {
Define the firstName
and lastName
properties.
Map the firstName
and lastName
properties to the sub-elements defined by the XML schema by inserting the @XmlPath
annotation on the line immediately preceding the property declaration. For each annotation, define the mapping by specifying the appropriate XPath predicate:
@XmlPath("personal-info/first-name/text()") private String firstName; @XmlPath("personal-info/last-name/text()") private String lastName;
The object should look like Example 15-17.
Example 15-17 Customer Object Mapping Properties to Sub-elements
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.*; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlPath("personal-info/first-name/text()") private String firstName; @XmlPath("personal-info/last-name/text()") private String lastName; ... }
If you want to represent the mapping in EclipseLink's OXM metadata format, you need to use the XML tags defined in the eclipselink-oxm.xml
file and populate them with the appropriate values, as shown in Example 15-18.
Example 15-18 Mapping Attributes as Sub-elements in OXM Metadata Format
... <java-type name="Customer"> <xml-root-element name="customer"/> <java-attributes> <xml-element java-attribute="firstName" xml-path="personal-info/first-name/text()"/> <xml-element java-attribute="lastName" xml-path="personal-info/last-name/text()"/> </java-attributes> </java-type> ...
When multiple nodes have the same name, map their values from the Java object by specifying their position in the XML document. Do this by using mapping the values to the position of the attribute rather than the attribute's name. You can do this either by using JAXB annotations or by or by representing the mapping in EclipseLink's OXM metadata format. In the following example, XML contains two <name>
elements; the first occurrence of name should represent the Customer's first name, the second name their last name.
Assuming an XML schema that defines the following attributes:
<customer>
of the type customer-type, which itself is specified as a complexType
<name>
of the type String
this example again uses the JAXB @XmlPath
annotation to map a customer's first and last names to the appropriate <name>
element. It also uses the @XmlType(propOrder)
annotation to ensure that the elements are always in the proper positions. To achieve this mapping:
Create the object and import javax.xml.bind.annotation.*
and org.eclipse.persistence.oxm.annotations.XmlPath
.
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath;
Declare the Customer
class and insert the @XmlType(propOrder)
annotation with the arguments "firstName"
followed by "lastName"
. Insert the @XmlRootElement
annotation to make Customer
the root element and set the XML accessor type to FIELD
:
@XmlRootElement @XmlType(propOrder={"firstName", "lastName"}) @XmlAccessorType(XmlAccessType.FIELD) public class Customer {
Define the properties firstName
and lastName
with the type String
.
Map the properties firstName
and lastName
to the appropriate position in the XML document by inserting the @XmlPath
annotation with the appropriate XPath predicates.
@XmlPath("name[1]/text()") private String firstName; @XmlPath("name[2]/text()") private String lastName;
The predicates, "name[1]/text()"
and "name[2]/text()"
indicate the <name>
element to which that specific property will be mapped; for example, "name[1]/text"
will map the firstName
property to the first <name>
element.
The object should look like Example 15-19.
Example 15-19 Customer Object Mapping Values by Position
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath; @XmlRootElement @XmlType(propOrder={"firstName", "lastName"}) @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlPath("name[1]/text()") private String firstName; @XmlPath("name[2]/text()") private String lastName; ... }
For more information about using XPath predicates, see Section 15.5, "Using XPath Predicates for Mapping".
In addition to using Java annotations, EclipseLink provides an XML mapping configuration file called eclipselink-oxm.xml
that you can use in place of or to override JAXB annotations in the source with an XML representation of the metadata. In addition to allowing all of the standard JAXB mapping capabilities it also includes advanced mapping types and options.
An XML metadata representation is useful when:
You cannot modify the domain model because, for example, it come from a third party).
You do not want to introduce compile dependencies on JAXB APIs (if you are using a version of Java that predates Java SE 6).
You want to apply multiple JAXB mappings to a domain model (you are limited to one representation with annotations).
Your object model already contains so many annotations from other technologies that adding more would make the class unreadable.
This section demonstrates how to use eclipselink-oxm.xml
to override JAXB annotations
Note:
While using this mapping file enables many advanced features, it might prevent you from porting it to other JAXB implementations
First, update the XML mapping file to expose the eclipselink_oxm_2_3.xsd
. schema. Example 15-20 shows how to modify the
<xml-bindings>
element in the mapping file to point to the correct namespace and leverage the schema. Each Java package can have one mapping file.
Example 15-20 Updating XML Binding Information in the Mapping File
<?xml version="1.0"?> <xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eclipse.org/eclipselink/xsds/persistence/oxm http://www.eclipse.org/eclipselink/xsds/eclipselink_oxm_2_4.xsd" version="2.4"> </xml-bindings>
Next, pass the mapping file to JAXBContext
in your object:
Specify the externalized metadata by inserting this code:
Map<String, Object> properties = new HashMap<String, Object>(1); properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, "org/example/oxm.xml); JAXBContext.newInstance("org.example', aClassLoader, properties);
Create the properties object to pass to the JAXBContext
. For this example:
Map<String,Object> properties = new HashMap<String,Object>(); properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, metadata);
Create the JAXBContext
. For this example:
JAXBContext.newInstance("example.order:example.customer", aClassLoader, properties);
You must use MOXy as your JAXB implementation. To do so, do the following:
Open a jaxb.properties
file and add the following line:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Copy the jaxb.properties
file to the package that contains your domain classes.
This section demonstrates how the EclipseLink MOXy API uses XPath predicates to define an expression that specifiers the XML element's name. An XPath predicate is an expression that defines a specific object-to-XML mapping. As shown in previous examples, by default, JAXB will use the Java field name as the XML element name.
This section contains the following subsections:
As described above, an XPath predicate is an expression that defines a specific object-to-XML mapping when standard annotations
re not sufficient. For example, the following snippet of XML shows a <data>
element with two <node>
sub-elements. If you wanted to create this mapping in a Java object, you would need to specify an XPath predicate for each <node>
sub-element; for example, Node[2]
in the following Java:
<java-attributes>
<xml-element java-attribute="node" xml-path="node[1]/ABC"/>
<xml-element java-attribute="node" xml-path="node[2]/DEF"/>
</java-attributes>
would match the second occurrence of the node element ("DEF"
) in the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<node>ABC</node>
<node>DEF</node>
</data>
Thus, by using the XPath predicate, you can use the same attribute name for a different attribute value.
This mapping technique is described in Section 15.3.2.4, "Mapping Values to a Text Node by Position".
Beginning with EclipseLink MOXy 2.3, you can also map to an XML element based on an Attribute value. In this exercise, you will annotate the JPA entity to render the XML document shown in Example 15-21. Note that all of the XML elements are named node but are differentiated by the value of their name attribute.
<?xml version="1.0" encoding="UTF-8"?> <node> <node name="first-name">Bob</node> <node name="last-name">Smith</node> <node name="address"> <node name="street">123 A Street</node> </node> <node name="phone-number" type="work">555-1111</node> <node name="phone-number" type="cell">555-2222</node> </node>
To attain this mapping, you need to declare three classes, Name
, Address
, and PhoneNumber
and then use an XPath in the form of element-name
[@
attribute-name
='
value
']
to map each Java field.
To create the Customer
class entity:
Import the necessary JPA packages by adding the following code:
import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath;
Declare the Customer
class and use the @XmlRootElement
annotation to make it the root element. Set the XML accessor type to FIELD
:
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer {
Declare local to the Customer
class these properties:
firstName
(String type)
lastName
(String)
Address
(Address)
For each property, set the Xpath predicate by preceding the property declaration with the annotation @XmlPath(
element-name
[@
attribute-name
='
value
'])
; for example, for firstName
, you would set the XPath predicate with this statement:
@XmlPath("node[@name='first-name']/text()")
Also local to the Customer
class, declare the phoneNumber
property as a List<PhoneNumber>
type and assign it the value new ArrayList<PhoneNumber>()
.
The Customer
class should look like the snippet in Example 15-22.
Example 15-22 Customer Object Mapping to an Attribute Value
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath; @XmlRootElement(name="node") @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlPath("node[@name='first-name']/text()") private String firstName; @XmlPath("node[@name='last-name']/text()") private String lastName; @XmlPath("node[@name='address']") private Address address; @XmlPath("node[@name='phone-number']") private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>(); ... }
To create the Address
class, do the following:
Import the necessary JPA packages by adding the following code:
import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath;
Declare the Address
class and set the XML accessor type to FIELD
:
@XmlAccessorType(XmlAccessType.FIELD)
public class Address {
This instance does not require the @XmlRootElement
annotation as in the previous Tasks because the Address
class is root not a root element in the XML document.
Declare local to the Address
class the String
property street
. Set the XPath predicate by preceding the property declaration with the annotation @XmlPath("node[@name='street']/text()")
.
The Address
class should look like Example 15-23.
To create the PhoneNumber
entity:
Import the necessary JPA packages by adding the following code:
import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath;
Declare the PhoneNumber
class and use the @XmlRootElement
annotation to make it the root element. Set the XML accessor type to FIELD
:
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer {
Create the type and string properties and define their mapping as attributes under the PhoneNumber root element by using the @XmlAttribute
. annotation.
@XmlAttribute private String type; @XmlValue private String number;
The PhoneNumber
object should look like Example 15-24.
A "self" mapping occurs on one-to-one mappings when you set the target object's XPath to "." (dot) so the data from the target object appears inside the source object's XML element. This exercise uses the example in Section 15.5.3, "Mapping Based on an Attribute Value" to map the Address information to appear directly under the customer element and not wrapped in its own element.
To create the self mapping:
Repeat Tasks 1 and 2 in Section 15.5.3.1, "Task 1: Create the Customer Entity".
Declare local to the Customer
class these properties:
firstName
(String type)
lastName
(String)
Address
(Address)
For the firstName
and lastName
properties, set the XmlPath annotation by preceding the property declaration with the annotation @XmlPath(
element-name
[@
attribute-name
='
value
'])
; for example, for firstName
, you would set the XPath predicate with this statement:
@XmlPath("node[@name='first-name']/text()")
For the address
property, set @XmlPath
to "." (dot):
@XmlPath(".") private Address address;
Also local to the Customer
class, declare the phoneNumber
property as a List<PhoneNumber>
type and assign it the value new ArrayList<PhoneNumber>()
.
The rendered XML for the Customer entity would look like Example 15-25.
Example 15-25 XML Node with Self-Mapped Address Element
<?xml version="1.0" encoding="UTF-8"?> <node> <node name="first-name">Bob</node> <node name="last-name">Smith</node> <node name="street">123 A Street</node> <node name="phone-number" type="work">555-1111</node> <node name="phone-number" type="cell">555-2222</node> </node>
Dynamic JAXB/MOXy allows you to bootstrap a JAXBContext
from a variety of metadata sources and use familiar JAXB APIs to marshal and unmarshal data, without requiring compiled domain classes. This is an enhancement over static JAXB, because now you can update the metadata without having to update and recompile the previously-generated Java source code.
The benefits of using dynamic JAXB/MOXy entities are:
Instead of using actual Java classes (for example, Customer.class
, Address.class
, and so on), the domain objects are subclasses of the DynamicEntity
.
Dynamic entities offer a simple get(propertyName)
/set(propertyName propertyValue)
API to manipulate their data.
Dynamic entities have an associated DynamicType
, which is generated in-memory, when the metadata is parsed.
The following Tasks demonstrate how to use dynamic JAXB:
This example demonstrates how to bootstrap a dynamic JAXBContext
from an XML Schema.
Use the DynamicJAXBContextFactory
to create a dynamic JAXBContext
. Example 15-26 to bootstrap a
DynamicJAXBContext
from the customer.xsd
schema (Example 15-27) by using
createContextFromXSD()
.
Example 15-26 Specifying the Input Stream and Creating the DynamicJAXBContext
import java.io.FileInputStream; import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext; import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContextFactory; public class Demo { public static void main(String[] args) throws Exception { FileInputStream xsdInputStream = new FileInputStream("src/example/customer.xsd"); DynamicJAXBContext jaxbContext = DynamicJAXBContextFactory.createContextFromXSD(xsdInputStream, null, null, null);
The first parameter represents the XML schema itself and must be in one of the following forms: java.io.InputStream
, org.w3c.dom.Node
, or javax.xml.transform.Source
.
Example 15-27 shows the
customer.xsd
schema that represents the metadata for the dynamic JAXBContext you are bootstrapping.
Example 15-27 Sample XML Schema Document
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.example.org" targetNamespace="http://www.example.org" elementFormDefault="qualified"> <xsd:complexType name="address"> <xsd:sequence> <xsd:element name="street" type="xsd:string" minOccurs="0"/> <xsd:element name="city" type="xsd:string" minOccurs="0"/> </xsd:sequence> </xsd:complexType> <xsd:element name="customer"> <xsd:complexType> <xsd:sequence> <xsd:element name="name" type="xsd:string" minOccurs="0"/> <xsd:element name="address" type="address" minOccurs="0"/> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema>
To bootstrap DynamicJAXBContext
from an XML schema that contains imports of other schemas, you need to configure an org.xml.sax.EntityResolver
to resolve the locations of the imported schemas and pass the EntityResolver
to DynamicJAXBContextFactory
.
The following example shows two schema documents, customer.xsd
(Example 15-28) and
address.xsd
Example 15-29). You can see that
customer.xsd
imports address.xsd
by using the statement:
<xsd:import namespace="http://www.example.org/address" schemaLocation="address.xsd"/>
<?xml version="1.0" encoding="UTF-8"?>
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:add="http://www.example.org/address"
xmlns="http://www.example.org/customer"
targetNamespace="http://www.example.org/customer"
elementFormDefault="qualified">
<xsd:import namespace="http://www.example.org/address" schemaLocation="address.xsd"/>
<xsd:element name="customer">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="name" type="xsd:string" minOccurs="0"/>
<xsd:element name="address" type="add:address" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<?xml version="1.0" encoding="UTF-8"?> xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.example.org/address" targetNamespace="http://www.example.org/address" elementFormDefault="qualified"> <xsd:complexType name="address"> <xs:sequence> <xs:element name="street" type="xs:string"/> <xs:element name="city" type="xs:string"/> </xs:sequence> </xsd:complexType> </xsd:schema>
If you want to bootstrap DynamicJAXBContext
from the customer.xsd
schema, you need to pass an entity resolver. Do the following:
To resolve the locations of the imported schemas, you need to implement an entityResolver
by supplying the code shown in Example 15-30.
Example 15-30 Implementing an EntityResolver
class MyEntityResolver implements EntityResolver { public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { // Imported schemas are located in ext\appdata\xsd\ // Grab only the filename part from the full path String filename = new File(systemId).getName(); // Now prepend the correct path String correctedId = "ext/appdata/xsd/" + filename; InputSource is = new InputSource(ClassLoader.getSystemResourceAsStream(correctedId)); is.setSystemId(correctedId); return is; } }
After you implement your DynamicJAXBContext
, pass the EntityResolver
, as shown in Example 15-31.
You might see the following exception when importing another schema:
Internal Exception: org.xml.sax.SAXParseException: schema_reference.4: Failed to read schemadocument '<imported-schema-name>', because 1) could not find the document; 2) the document couldnot be read; 3) the root element of the document is not <xsd:schema>.
To work around this exception, disable XJC's schema correctness check by setting the noCorrectnessCheck
Java property. You can set this property one of two ways:
From within the code, by adding this line:
System.setProperty("com.sun.tools.xjc.api.impl.s2j.SchemaCompilerImpl.noCorrectnessCheck", "true")
From the command line, by using this command:
-Dcom.sun.tools.xjc.api.impl.s2j.SchemaCompilerImpl.noCorrectnessCheck=true
Use your application's current class loader as the classLoader
parameter. This parameter verifies that specified classes exist before new DynamicTypes
are generated. In most cases you can pass null
for this parameter and use Thread.currentThread().getContextClassLoader()
instead.
This example shows how to create dynamic entities and marshal then to XML.
Use the DynamicJAXBContext
to create instances of DynamicEntity
. The entity and property names correspond to the class and property names—in this case, the customer
and address
—that would have been generated if you had used static JAXB.
Example 15-32 Creating the Dynamic Entity
DynamicEntity customer = jaxbContext.newDynamicEntity("org.example.Customer"); customer.set("name", "Jane Doe"); DynamicEntity address = jaxbContext.newDynamicEntity("org.example.Address"); address.set("street", "1 Any Street").set("city", "Any Town"); customer.set("address", address);
The marshaller obtained from the DynamicJAXBContext
is a standard marshaller and can be used normally to marshal instances of DynamicEntity.
Example 15-33 Standard Dynamic JAXB Marshaller
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);marshaller.marshal(customer, System.out);
Example 15-34 shows the resultant XML document:
In this example shows how to unmarshal from XML the dynamic entities you created in Task 2: Create Dynamic Entities and Marshal Them to XML. The XML in reference is shown in Example 15-34
.
The Unmarshaller obtained from the DynamicJAXBContext
is a standard unmarshaller, and can be used normally to unmarshal instances of DynamicEntity
.
Next, specify which data in the dynamic entity to obtain. Specify this value by using System.out.println()
and passing in the entity name. DynamicEntity
offers property-based data access; for example, get("name")
instead of getName()
:
System.out.println(customer.<String>get("name"));
Instances of DynamicEntity
have a corresponding DynamicType
, which you can use to introspect the DynamicEntity
, as shown in Example 15-36.
See the following resources for more information about the technologies and tools used to implement the solutions in this chapter:
Developing JAXB Applications Using Oracle TopLink