7 Mapping JPA to XML

You can use the Java Architecture for XML Binding (JAXB) 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 demonstrates some typical techniques for mapping JPA entities to XML. It contains the following sections:

7.1 Understanding JPA-to-XML Mapping Concepts

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:

7.1.1 XML Binding

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 representations are traversed.

You can find examples of XML binding with JAXB in Section 7.2, "Binding JPA Entities to XML".

7.1.2 JAXB

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 optimizing 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 Oracle TopLink API. For more information on these annotations, see Oracle Fusion Middleware Java API Reference for Oracle TopLink.

For more information about JAXB, see "Java Architecture for XML Binding (JAXB)" at:

http://www.eclipse.org/eclipselink/moxy.php

7.1.3 MOXy

MOXy implements JAXB for TopLink. It allows you to map a Plain Old Java Objects (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 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 schemas.

  • It allows you to combine MOXy mappings and the TopLink persistence framework to interact with your data through the J2EE connector architecture (JCA).

  • It offers superior performance in several mapping scenarios.

For more information on MOXy, see the MOXy FAQ at:

http://wiki.eclipse.org/EclipseLink/FAQ/WhatIsMOXy

7.1.4 XML Data Representation

JAXB/MOXy is 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 change the metadata for future versions.

    • You use the standard JAXB annotations, and use the XML metadata for the MOXy extensions. In this way you do not 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.

For more information about how to use XML data representation, see Section 7.4, "Main Tasks for Using XML Metadata Representation to Override JAXB Annotations".

7.2 Binding JPA Entities to XML

The following tasks demonstrate how to bind JPA entities to XML by using JAXB annotations. For more information about binding, see Section 7.1.1, "XML Binding"; for more information about JAXB, see Section 7.1.2, "JAXB"

7.2.1 Main Tasks for Binding JPA Relationships to XML

The following tasks demonstrate how to use JAXB to derive an XML representation from a set of JPA entities, a process called binding (read about XML binding in Section 7.2, "Binding JPA Entities to XML"). These tasks will show how to bind two common JPA relationships to map an Employee entity to that employee's telephone number, address, and department:

  • Privately-owned relationships

  • Shared reference relationships

The tasks for binding JPA relationships to XML are the following:

7.2.1.1 Task 1: Define the Accessor Type and Import Packages

Because 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 packages:

@XmlAccessorType(XmlAccessType.FIELD)
package com.example.model;
 
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;

7.2.1.2 Task 2: Map Privately Owned Relationships

A privately owned relationship occurs when the target object is referenced only 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 bidirectional mappings for both of these types of relationships between the Employee entity and the Address and PhoneNumber entities.

7.2.1.2.1 Mapping a One-to-One and Embedded Relationship

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 then back to the Employee entity. This is considered a one-to-one mapping because the employee can be associated with only one address. Because this relationship is bidirectional, that is, Employee points to Address, which must point back to Employee, it uses the TopLink extension @XmlInverseReference to represent the back-pointer.

To create the one-to-one and embedded mapping:

  1. Ensure that the accessor type FIELD has been defined at the package level, as described in Section 7.2.1.1, "Task 1: Define the Accessor Type and Import Packages".

  2. Map one direction of the relationship, in this case the employee property on the Address entity, 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.

  3. Map the return direction, that is, the address property on the Employee entity 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 7-1 and Example 7-2.

7.2.1.2.2 Mapping a One-to-Many Relationship

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 telephone 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 telephone numbers and back. Because the relationship between Employee and PhoneNumber is bidirectional, the example again uses the TopLink extension @XmlInverseReference to map the back-pointer.

To create a one-to-many mapping:

  1. Ensure that the accessor type FIELD has been defined at the package level, as described in Section 7.2.1.1, "Task 1: Define the Accessor Type and Import Packages".

  2. Map one direction of the relationship, in this case the employee property on the PhoneNumber entity, 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.

  3. Map the return direction, that is the telephone number property on the Employee entity, 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 7-1 and Example 7-3.

7.2.1.3 Task 3: Map the Shared Reference Relationship

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.

Because a shared reference relationship cannot be safely represented as nesting in XML, use key relationships. To specify the ID fields on JPA entities, use the TopLink JAXB @XmlID annotation on non-string fields and properties and @XmlIDREF on string fields and properties.

The following examples how to map a many-to-one shared reference relationship and a many-to-many shared reference relationship.

7.2.1.3.1 Mapping a Many-to-One 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 telephone numbers.

To map a many-to-one shared reference relationship:

  1. Ensure that the accessor type FIELD has been defined at the package level, as described in Section 7.2.1.1, "Task 1: Define the Accessor Type and Import Packages".

  2. Map one direction of the relationship, in this case the phone number property on the Employee entity, 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.

  3. Map the return direction, that is the employee property on the PhoneNumber entity, 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 7-1 and Example 7-3.

7.2.1.3.2 Mapping a Many-to-Many Shared Reference Relationship

The @ManyToMany annotation indicates that one or more instances of the source entity are able to refer to one or more target entity instances. Because the relationship between Department and Employee is bidirectional, this example again uses the TopLink @XmlInverseReference annotation to represent the back-pointer.

To map a many-to-many shared reference relationship, do the following:

  1. Ensure that the accessor type FIELD has been defined at the package level, as described in Section 7.2.1.1, "Task 1: Define the Accessor Type and Import Packages".

  2. Create a Department entity by inserting the following code:

    @Entity
    public class Department {
    
  3. 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 relationship.

  4. Complete the initial mapping, in this case the Department entity's employee property, and make it a foreign key for this entity by inserting the following code:

        @XmlIDREF
        private List<Employee> member;
    
  5. In the Employee entity created in Section 7.2.1.2.1, "Mapping a One-to-One and Embedded Relationship", specify that eId is the primary key for JPA (the @Id annotation), and for JAXB (the @XmlID annotation) by inserting the following code:

        @Id
        @Column(name="E_ID")
        @XmlID
        private BigDecimal eId;
     
    
  6. 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 7-1 and Example 7-4.

7.2.1.4 JPA Entities

After the mappings are created, the entities should look like those in the following examples:

Note:

To save space, package names, import statements, and the get and set methods have been omitted from the code examples. All examples use standard JPA annotations.

Example 7-1 Employee Entity

@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;
 
}

Example 7-2 Address Entity

@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 7-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 7-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;
 
}

7.2.2 Main Tasks for Binding Compound Primary Keys to XML

When a JPA entity has compound primary keys, you can bind the entity by using JAXB annotations and certain Oracle TopLink extensions, as shown in the following tasks:

7.2.2.1 Task1: Define the XML Accessor Type

Define the accessor type as FIELD, as described in Section 7.2.1.1, "Task 1: Define the Accessor Type and Import Packages".

7.2.2.2 Task 2: Create the Target Object

To create the target object, do the following:

  1. 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 {
    
  2. Specify the first primary key, eId, of the entity and map it to a column:

        @Id
        @Column(name="E_ID")
        @XmlID
        private BigDecimal eId;
    
  3. Specify the second primary key, country. In this instance, you need to use @XmlKey to identify the primary key because only one property—here, eId—can be annotated with the @XmlID annotation.

        @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 does not require the property be bound to the schema type ID. This is a typical application of the @XmlKey annotation.

  4. 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 that shown in Example 7-5.

Example 7-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;
 
}

7.2.2.3 Task 3: Create the Source Object

This task creates the source object, the PhoneNumber entity. Because the target object has a compound key, you must use the TopLink @XmlJoinNodes annotation to set up the mapping.

To create the source object:

  1. Create the PhoneNumber entity:

    @Entity
    public class PhoneNumber {
    
  2. 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")
       })
    
  3. Set up the mapping by using the TopLink @XmlJoinNodes annotation:

    @XmlJoinNodes( {
            @XmlJoinNode(xmlPath="contact/id/text()", referencedXmlPath="id/text()"),
            @XmlJoinNode(xmlPath="contact/country/text()", referencedXmlPath="country/text()")
        })
    
  4. Define the contact property:

    private Employee contact;
     
    }
    

The target object should look like that shown in Example 7-6.

Example 7-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;
 
}

7.2.3 Main Tasks for Binding Embedded ID Classes to XML

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. In the embedded Id, all attributes in its Embeddable class are assumed to be part of the primary key. The following tasks show how to derive an XML representation from a set of JPA entities using JAXB when a JPA entity has an embedded ID class:

7.2.3.1 Task 1: Define the XML Accessor Type

Define the XML accessor type as FIELD, as described in Section 7.2.1.1, "Task 1: Define the Accessor Type and Import Packages".

7.2.3.2 Task 2: Create the Target Object

The target object is an entity called Employee and contains the mapping for an employee's contact telephone number. Creating this target object requires implementing a DescriptorCustomizer interface, so you must include the TopLink @XmlCustomizer annotation. Also, because the relationship is bidirectional, you must implement the @XmlInverseReference annotation.

To create the target object:

  1. Create the Employee entity. Use the @IdClass annotation to specify that the EmployeeID class will be mapped to multiple properties of the entity and use the @XmlCustomizer annotation to indicate that the class EmployeeCustomizer will implement the DescriptorCustomizer interface (see Section 7.2.3.3, "Task 3: Implement DescriptorOrganizer as EmployeeCustomizer Class").

    @Entity
    @IdClass(EmployeeId.class)
    public class Employee {
    
  2. Define the id property and make it embeddable.

        @EmbeddedId
        @XmlPath(".");
        private EmployeeId id;
    
  3. Define a one-to-many mapping, in this case, the employee property on the PhoneNumber entity. Because the relationship is bidirectional, use @XmlInverseReference annotation 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 that shown in Example 7-7.

Example 7-7 Employee Entity as Target Object

@Entity
@IdClass(EmployeeId.class)
@XmlCustomizer(EmployeeCustomizer.class)
public class Employee {
 
    @EmbeddedId
    private EmployeeId id;
 
    @OneToMany(mappedBy="contact")
    @XmlInverseReference(mappedBy="contact")
    private List<PhoneNumber> contactNumber;
 
}

7.2.3.3 Task 3: Implement DescriptorOrganizer as EmployeeCustomizer Class

In Task 2: Create the Target Object, DescriptorCustomizer was implemented as the class EmployeeCustomizer. This allows changing the XML Path (XPath) on the mapping for the id property to either self or "." and then specifying the XPath to the XML nodes that represent the ID. To do this:

  1. Implement the DescriptorOrganizer class as EmployeeOrganizer.

    import org.eclipse.persistence.oxm.mappings.XMLCompositeObjectMapping;
     
    public class EmployeeCustomizer implements DescriptorCustomizer {
    
  2. Specify the XPath to the XML nodes that represent the ID:

    descriptor.addPrimaryKeyFieldName("eId/text()");
    descriptor.addPrimaryKeyFieldName("country/text()");
    

The EmployeeCustomizer class should look like Example 7-8.

Example 7-8 EmployeeCustomizer Class with Updated XPath Information

import org.eclipse.persistence.config.DescriptorCustomizer;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.oxm.mappings.XMLCompositeObjectMapping;
 
public class EmployeeCustomizer implements DescriptorCustomizer {
 
        descriptor.addPrimaryKeyFieldName("eId/text()");
        descriptor.addPrimaryKeyFieldName("country/text()");
    }
 
}

7.2.3.4 Task 4: Create the Source Object

The source object in this task has a compound key, so you must annotate the field with the @XmlTransient annotation to prevent a key from being mapped by itself. Use the TopLink @XmlCustomizer annotation to set up the mapping.

To create the source object, do the following:

  1. Create the PhoneNumber entity and specify another class, PhoneNumberCustomizer, to implement the DescriptorCustomizer interface.

    @Entity
    @XmlCustomizer(PhoneNumberCustomizer.class)
    public class PhoneNumber {
    
  2. 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")
        })
    
  3. Define the contact property. Use the @XmlTransient annotation to prevent this key from being mapped by itself.

    @XmlTransient
    private Employee contact;

The completed PhoneNumber class should look like Example 7-9.

Example 7-9 PhoneNumber Class as Source Object

@Entity
@XmlCustomizer(PhoneNumberCustomizer.class)
public class PhoneNumber {
 
    @ManyToOne
    @JoinColumns({
        @JoinColumn(name="E_ID", referencedColumnName = "E_ID"),
        @JoinColumn(name="E_COUNTRY", referencedColumnName = "COUNTRY")
    })
    @XmlTransient
    private Employee contact;
 
}

7.2.3.5 Task 5: Implement the DescriptorCustomizer as PhoneNumberCustomizer Class

Code added in Task 4 indicated the need to map the XMLObjectReferenceMapping class to the new values. This requires implementing the DescriptorCustomizer class as the PhoneNumberCustomizer class and adding the multiple key mappings. To do this:

  1. Implement DescriptorCustomizer as PhoneNumberCustomizer. Import org.eclipse.persistence.oxm.mappings.XMLObjectReferenceMapping:

    import org.eclipse.persistence.oxm.mappings.XMLObjectReferenceMapping;
     
    public class PhoneNumberCustomizer implements DescriptorCustomizer {
    
  2. 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()".

The PhoneNumberCustomizer should look like that shown in Example 7-10.

Example 7-10 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);
    }
 
}

7.2.4 Using the EclipseLink XML Binding Document

As demonstrated in the preceding tasks, TopLink 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 EclipseLink XML Bindings documents

  • Specifying virtual mappings that do not correspond to concrete Java fields

For more information on using the XML Bindings document, see XML Bindings in the JAXB/MOXy documentation at http://wiki.eclipse.org/EclipseLink/UserGuide/MOXy/Runtime/XML_Bindings.

7.3 Main Tasks for Mapping Simple Java Values to XML Text Nodes

There are several ways to map simple Java values directly to XML text nodes. It includes the following tasks:

7.3.1 Task 1: Mapping a Value to an Attribute

This task 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 7-11.

Example 7-11 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, alternatively, how to represent the value in the Oracle TopLink Object-to-XML Mapping (OXM) metadata format.

7.3.1.1 Mapping from the Java Object

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:

  1. Create the object and import javax.xml.bind.annotation.*:

    package example;
     
    import javax.xml.bind.annotation.*;
    
  2. 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 {
    
  3. Map the id property in the Customer class as an attribute:

       @XmlAttribute
       private Integer id;
    

The object should look like that shown in Example 7-12.

Example 7-12 Customer Object with Mapped id Property

package example;
 
import javax.xml.bind.annotation.*;
 
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
   @XmlAttribute
   private Integer id;
 
   ...
}

7.3.1.2 Defining the Mapping in OXM Metadata Format

If you want to represent the mapping in the TopLink OXM metadata format, use the XML tags defined in the eclipselink-oxm.xml file and populate them with the appropriate values, as shown in Example 7-13.

Example 7-13 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 7.4, "Main Tasks for Using XML Metadata Representation to Override JAXB Annotations".

7.3.2 Task 2: Mapping a Value to a Text Node

Oracle TopLink 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:

7.3.2.1 Mapping a Value to a Simple Text Node

You can map a value from a Java object either by using JAXB annotations in the Java object or, alternatively, by representing the mapping in the TopLink OXM metadata format.

7.3.2.1.1 Mapping by Using JAXB Annotations

Assume the associated schema defines an element called <phone-number> that accepts a string value. You can use the @XmlValue annotation to map a string to the <phone-number> node. Do the following:

  1. Create the object and import javax.xml.bind.annotation.*:

    package example;
     
    import javax.xml.bind.annotation.*;
    
  2. 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 {
    
  3. 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 that shown in Example 7-14.

Example 7-14 PhoneNumber Object with Mapped number Property

package example;
 
import javax.xml.bind.annotation.*;
 
@XmlRootElement(name="phone-number")
@XmlAccessorType(XmlAccessType.FIELD)
public class PhoneNumber {
   @XmlValue
   private String number;
 
   ...
}
7.3.2.1.2 Defining the Mapping in OXM Metadata Format

If you want to represent the mapping in the TopLink OXM metadata format, then use the XML tags defined in the eclipselink-oxm.xml file and populate them with the appropriate values, as shown in Example 7-15.

Example 7-15 Mapping number as an Attribute in OXM Metadata Format

...
<java-type name="PhoneNumber">
   <xml-root-element name="phone-number"/>
   <java-attributes>
      <xml-value java-attribute="number"/>
   </java-attributes>
</java-type>
...

7.3.2.2 Mapping Values to a Text Node in a Simple Sequence

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 the TopLink OXM metadata format. The following procedures illustrate how to map values for customers' first names and last names

7.3.2.2.1 Mapping by Using JAXB Annotations

Assuming the associated schema defines the following elements:

  • <"customer"> of the type customer-type, which itself is defined as 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 names to the appropriate XML nodes. To do so:

  1. Create the object and import javax.xml.bind.annotation.*:

    package example;
     
    import javax.xml.bind.annotation.*;
    
  2. 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 {
    
  3. 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=, then 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 7-16.

Example 7-16 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;
 
   ...
}
7.3.2.2.2 Defining the Mapping in OXM Metadata Format

If you want to represent the mapping in the TopLink OXM metadata format, then use the XML tags defined in the eclipselink-oxm.xml file and populate them with the appropriate values, as shown in Example 7-17.

Example 7-17 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>
...

7.3.2.3 Mapping a Value to a Text Node in a Subelement

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 the TopLink OXM metadata format. For example, if you want to populate <first-name> and <last-name> elements, which are subelements of a <personal-info> element under a <customer> root element, you could use the following procedures to achieve these mappings.

7.3.2.3.1 Mapping by Using JAXB Annotations

Assume the associated schema defines the following elements:

  • <"customer"> of the type customer-type, which itself is defined as complexType

  • <personal-info>

  • Subelements 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 subelement nodes. Because this example goes beyond a simple element name customization and actually introduces a new XML structure, it uses the TopLink @XmlPath annotation. To achieve this mapping:

  1. 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.*;
    
  2. 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 {
    
  3. Define the firstName and lastName properties.

  4. Map the firstName and lastName properties to the subelements 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 that shown in Example 7-18.

Example 7-18 Customer Object Mapping Properties to Subelements

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;
 
   ...
}
7.3.2.3.2 Defining the Mapping in OXM Metadata Format

If you want to represent the mapping in the TopLink 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 7-19.

Example 7-19 Mapping Attributes as Subelements 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>
...

7.3.2.4 Mapping Values to a Text Node by Position

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 representing the mapping in the TopLink OXM metadata format. In the following example, XML contains two <name> elements; the first occurrence of name should represent the customer's first name and the second occurrence should represent the customer's last name.

Assume an XML schema defines the following attributes:

  • <customer> of the type customer-type, which itself is specified as a complexType

  • <name> of the type String

This example 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:

  1. 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;
    
  2. 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 {
    
  3. Define the properties firstName and lastName with the type String.

  4. 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 that shown in Example 7-20.

Example 7-20 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 on using XPath predicates, see Section 7.5, "Using XPath Predicates for Mapping".

7.4 Main Tasks for Using XML Metadata Representation to Override JAXB Annotations

In addition to using Java annotations, TopLink 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 comes from a third party.

  • You do not want to introduce compilation 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.

Use the eclipselink-oxm.xml configuration file to override JAXB annotations by performing the following tasks:

Caution:

While using this mapping file enables many advanced features, it might prevent you from porting it to other JAXB implementations.

7.4.1 Task 1: Define Advanced Mappings in the XML

First, update the XML mapping file to expose the eclipselink_oxm_2_3.xsd schema. Example 7-21 shows how to modify the <xml-bindings> element in the mapping file to point to the correct namespace and optimize the schema. Each Java package can have one mapping file.

Example 7-21 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_3.xsd"
       version="2.3">
</xml-bindings>

7.4.2 Task 2: Configure Usage in JAXBContext

Next, pass the mapping file to the JAXBContext class in your object:

  1. Specify the externalized metadata by inserting this code:

    Map<String, Source> metadata = new HashMap<String,Source>();
    metadata.put("example.order", new StreamSource("order-metadata.xml"));
    metadata.put("example.customer", new StreamSource("customer-metadata.xml"));
    
  2. Create the properties object to pass to JAXBContext. For this example:

    Map<String,Object> properties = new HashMap<String,Object>();
    properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, metadata);
    
  3. Create JAXBContext. For example:

    JAXBContext.newInstance("example.order:example.customer", aClassLoader, properties);
    

7.4.3 Task 3: Specify MOXy as the JAXB Implementation

You must use MOXy as your JAXB implementation. To do so, do the following:

  1. Open a jaxb.properties file and add the following line:

    javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
    
  2. Copy the jaxb.properties file to the package that contains your domain classes.

7.5 Using XPath Predicates for Mapping

The TopLink MOXy API uses XPath predicates to define an expression that specifies 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:

7.5.1 Understanding XPath Predicates

As described previously, an XPath predicate is an expression that defines a specific object-to-XML mapping when standard annotations are not sufficient. For example, the following XML code shows a <data> element with two <node> subelements. If you want to create this mapping in a Java object, then specify an XPath predicate for each <node> subelement, 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>

This 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.

In another example, if you wanted to map attributes based on position, you would follow the instructions described in Section 7.3.2.4, "Mapping Values to a Text Node by Position".

7.5.2 Main Tasks for Mapping Based on an Attribute Value

Beginning with TopLink MOXy 2.3, you can also map to an XML element based on an attribute value. In these tasks, you will annotate the JPA entity to render the XML document shown in Example 7-22. Note that all of the XML elements are named node but are differentiated by the value of their name attribute.

Example 7-22 JPA Entity

<?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, 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 entities from the Customer, Address, and PhoneNumber classes, perform the following tasks:

7.5.2.1 Task 1: Create the Customer Class Entity

To create an entity from the Customer class:

  1. Import the necessary JPA packages by adding the following code:

    import javax.xml.bind.annotation.*;
     
    import org.eclipse.persistence.oxm.annotations.XmlPath;
     
    
  2. 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 {
    
  3. Declare these properties as local to the Customer class:

    • 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()")
    
  4. Also declare local to the Customer class the phoneNumber property as a List<PhoneNumber> type and assign it the value new ArrayList<PhoneNumber>().

The Customer class should look like that shown in Example 7-23.

Example 7-23 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>();
 
    ...
}

7.5.2.2 Task 2: Create the Address Class Entity

To create an entity from the Address class, do the following:

  1. Import the necessary JPA packages by adding the following code:

    import javax.xml.bind.annotation.*;
     
    import org.eclipse.persistence.oxm.annotations.XmlPath;
     
    
  2. 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 Task 1: Create the Customer Class Entity because the Address class is not a root element in the XML document.

  3. Declare the String property street local to the Address class. Set the XPath predicate by preceding the property declaration with the annotation @XmlPath("node[@name='street']/text()").

The Address class should look like that shown in Example 7-24.

Example 7-24 Address Object Mapping to an Attribute Value

package example;
 
import javax.xml.bind.annotation.*;
 
import org.eclipse.persistence.oxm.annotations.XmlPath;
 
@XmlAccessorType(XmlAccessType.FIELD)
public class Address {
 
    @XmlPath("node[@name='street']/text()")
    private String street;
 
    ...
}

7.5.2.3 Task 3: Create the PhoneNumber Class Entity

To create an entity from the PhoneNumber class:

  1. Import the necessary JPA packages by adding the following code:

    import javax.xml.bind.annotation.*;
     
    import org.eclipse.persistence.oxm.annotations.XmlPath;
     
    
  2. 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 {
    
  3. 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 that shown in Example 7-25.

Example 7-25 PhoneNumber Object Mapping to an Attribute Value

package example;
 
import javax.xml.bind.annotation.*;
 
@XmlAccessorType(XmlAccessType.FIELD)
public class PhoneNumber {
 
    @XmlAttribute
    private String type;
 
    @XmlValue
    private String number;
 
    ...
}

7.5.3 Self-Mappings

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 7.5.2, "Main Tasks for 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:

  1. Repeat Tasks 1 and 2 in Section 7.5.2.1, "Task 1: Create the Customer Class Entity".

  2. Declare these properties local to the Customer class:

    • firstName (String)

    • lastName (String)

    • Address (Address)

  3. 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()")
    
  4. For the address property, set @XmlPath to "." (dot):

        @XmlPath(".")
        private Address address;
    
  5. Also declare the phoneNumber property local to the Customer class. Declare it as a List<PhoneNumber> type and assign it the value new ArrayList<PhoneNumber>().

The rendered XML for the Customer entity should look like that shown in Example 7-26.

Example 7-26 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>

7.6 Using Dynamic JAXB/MOXy

Dynamic JAXB/MOXy allows you to bootstrap a JAXBContext class 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 class.

  • Dynamic entities offer a simple get(propertyName)/set(propertyName propertyValue) API to manipulate their data.

  • Dynamic entities have an associated DynamicType class, which is generated in-memory, when the metadata is parsed.

7.6.1 Main Tasks for Using Dynamic JAXB/MOXy

The following Tasks demonstrate how to use dynamic JAXB/MOXy:

7.6.1.1 Task 1: Bootstrap a Dynamic JAXBContext from an XML Schema

Use the DynamicJAXBContextFactory class to create a dynamic JAXBContext object. Example 7-27 specifies the input stream and then bootstraps a DynamicJAXBContext class from the customer.xsd schema (Example 7-28) by using the createContextFromXSD()method.

Example 7-27 Specifying the Input Stream and Creating 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.

7.6.1.1.1 The XML Schema

Example 7-28 shows the customer.xsd schema that represents the metadata for the dynamic JAXBContext you are bootstrapping.

Example 7-28 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>
7.6.1.1.2 Handling Schema Import/Includes

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.

Example 7-29 shows the schema document customer.xsd and Example 7-30 shows the schema document address.xsd. You can see that customer.xsd imports address.xsd by using the statement:

<xsd:import namespace="http://www.example.org/address" schemaLocation="address.xsd"/>

Example 7-29 customer.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>

Example 7-30 address.xsd

<?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>
7.6.1.1.3 Implementing and Passing EntityResolver

If you want to bootstrap DynamicJAXBContext from the customer.xsd schema then pass an entity resolver. Do the following:

  1. To resolve the locations of the imported schemas, implement entityResolver by supplying the code shown in Example 7-31.

    Example 7-31 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;
       }
    
    }
    
  2. After you implement DynamicJAXBContext, pass the EntityResolver, as shown in Example 7-32.

    Example 7-32 Passing in the Entityresolver

    FileInputStream xsdInputStream = new FileInputStream("src/example/customer.xsd");
    DynamicJAXBContext jaxbContext = 
        DynamicJAXBContextFactory.createContextFromXSD(xsdInputStream, new MyEntityResolver(), null, null);
    
7.6.1.1.4 Error Handling

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 the XJC schema correctness check by setting the noCorrectnessCheck Java property. You can set this property in 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
    
7.6.1.1.5 Specifying a Class Loader

Use your application's current class loader as the classLoader parameter. This parameter verifies that specified classes exist before a new DynamicType is generated. In most cases you can pass null for this parameter and use Thread.currentThread().getContextClassLoader() method instead.

7.6.1.2 Task 2: Create Dynamic Entities and Marshal Them to XML

Use the DynamicJAXBContext class to create instances of a DynamicEntity object. 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 7-33 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, as shown in Example 7-34.

Example 7-34 Standard Dynamic JAXB Marshaller

Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);marshaller.marshal(customer, System.out);

Example 7-35 show resultant XML document:

Example 7-35 Updated XML Document Showing <address> Element and Its Attributes

<?xml version="1.0" encoding="UTF-8"?>
<customer xmlns="www.example.org">
   <name>Jane Doe</name>
   <address>
      <street>1 Any Street</street>
      <city>Any Town</city>
   </address>
</customer>

7.6.1.3 Task 3: Unmarshal the Dynamic Entities from XML

This task 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 7-35.

The Unmarshaller obtained from the DynamicJAXBContext object is a standard unmarshaller, and can be used to unmarshal instances of DynamicEntity, as shown in Example 7-36.

Example 7-36 Standard Dynamic JAXB Unmarshaller

FileInputStream xmlInputStream = new FileInputStream("src/example/dynamic/customer.xml");
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
DynamicEntity customer = (DynamicEntity) unmarshaller.unmarshal(xmlInputStream);
7.6.1.3.1 Get Data from the DynamicEntity

Specify which data in the dynamic entity to obtain. Specify this value by using the System.out.println() method 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"));
7.6.1.3.2 Use DynamicType to Introspect Dynamic Entity

Instances of DynamicEntity have a corresponding DynamicType, which you can use to introspect the DynamicEntity object, as shown in Example 7-37.

Example 7-37 Introspecting the DynamicEntity

DynamicType addressType = jaxbContext.getDynamicType("org.example.Address"); DynamicEntity address = customer.<DynamicEntity>get("address");for(String propertyName: addressType.getPropertiesNames()) {    System.out.println(address.get(propertyName));}

7.7 Additional Resources

The following additional resourcs are available:

7.7.1 Code Samples

Numerous code samples and tutorials can be found at the EclipseLink MOXy wiki site:

http://www.eclipse.org/eclipselink/moxy.php

7.7.2 Related Javadoc

The following Javadoc is available:

7.7.2.1 Java Architecture for XML Binding (JAXB) Specification

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 Oracle TopLink API. Javadoc is for these annotations is at:

http://docs.oracle.com/javase/6/docs/api/javax/xml/bind/package-summary.html

7.7.2.2 Mapping Objects to XML (MOXy) Specification

MOXy supports all the standard JAXB annotations in the javax.xml.bind.annotation package:

http://www.eclipse.org/eclipselink/api/2.3/org/eclipse/persistence/oxm/annotations/package-summary.html

MOXy has its own extensions in the org.eclipse.persistence.oxm.annotations package:

http://www.eclipse.org/eclipselink/api/2.3/org/eclipse/persistence/oxm/annotations/package-summary.html