15 Mapping JPA to XML

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:

Use Case

Users need to map JPA entities to XML.

Solution

TopLink provides support for the JAXB standard through EclipseLink JAXB extensions.

Components

Sample

See the following EclipseLink and JAXB examples for related information:

15.1 Introduction to the Solution

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:

15.1.1 Understanding 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 relationships are traversed.

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

15.1.2 Understanding 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 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:

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

15.1.3 Understanding MOXy

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:

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

15.1.4 Understanding an XML Data Representation

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"

15.2 Binding JPA Entities to XML

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"

15.2.1 Binding JPA Relationships to XML

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.

15.2.1.1 Task 1: Define the Accessor Type and Import Classes

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;

15.2.1.2 Task 2: Map Privately-Owned Relationships

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.

15.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 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:

  1. 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".

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

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

15.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 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:

  1. 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".

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

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

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

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.

15.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 phone 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 15.2.1.1, "Task 1: Define the Accessor Type and Import Classes".

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

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

15.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. 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:

  1. 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".

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

  4. 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;
    
  5. 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;
     
    
  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 15-1 and Example 15-4.

15.2.1.4 JPA Entities

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.

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

15.2.2 Binding Compound Primary Keys to XML

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.

15.2.2.1 Task1: Define the XML Accessor Type

Define the accessor type as FIELD, as described in Section 15.2.1.1, "Task 1: Define the Accessor Type and Import Classes"

15.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— 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.

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

15.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, we need to use the EclipseLink's @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 EclipseLink's @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 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;
 
}

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

15.2.3.1 Task1: Define the XML Accessor Type

Define the XML accessor type as FIELD, as described in Section 15.2.1.1, "Task 1: Define the Accessor Type and Import Classes"

15.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 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:

  1. 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 {
    }
    
  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 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.

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

15.2.3.3 Task 3: Create the Source Object

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:

  1. Create the PhoneNumber entity.

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

15.2.3.4 Task 5: Implement the DescriptorCustomizer as PhoneNumberCustomizer Class

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:

  1. 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 {
    
  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()".

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

15.2.4 Using the EclipseLink XML Binding Document

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.

15.3 Mapping Simple Java Values to XML Text Nodes

This section demonstrates several ways to map simple Java values directly to XML text nodes. It includes the following examples:

15.3.1 Mapping a Value to an Attribute

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.

15.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 Example 15-11.

Example 15-11 Customer Object with Mapped id Property

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

15.3.1.2 Defining the Mapping in OXM Metadata Format

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

15.3.2 Mapping a Value to a Text Node

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:

15.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, alternately, by representing the mapping in EclipseLink's OXM metadata format.

15.3.2.1.1 Mapping by Using JAXB Annotations

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:

  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 Example 15-13.

Example 15-13 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;
 
   ...
}
15.3.2.1.2 Defining the Mapping in OXM Metadata Format

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.

Example 15-14 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>
...

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

15.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 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:

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

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

15.3.2.3 Mapping a Value to a Text Node in a Sub-element

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.

15.3.2.3.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 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:

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

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

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

15.3.2.4.1 Mapping by Using JAXB Annotations

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:

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

15.4 Using XML Metadata Representation to Override JAXB Annotations

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

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

15.4.2 Task 2: Configure Usage in JAXBContext

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

  1. 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);
    
  2. 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);
    
  3. Create the JAXBContext. For this example:

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

15.4.3 Task 3: Specify the 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.

15.5 Using XPath Predicates for Mapping

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:

15.5.1 Understanding XPath Predicates

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.

15.5.2 Mapping Based on Position

This mapping technique is described in Section 15.3.2.4, "Mapping Values to a Text Node by Position".

15.5.3 Mapping Based on an Attribute Value

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.

Example 15-21

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

15.5.3.1 Task 1: Create the Customer Entity

To create the Customer class entity:

  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 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()")
    
  4. 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>();
 
    ...
}

15.5.3.2 Task 2: Create the Address Entity

To create 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 the previous Tasks because the Address class is root not a root element in the XML document.

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

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

15.5.3.3 Task 3: Create the PhoneNumber Entity

To create the PhoneNumber entity:

  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 Example 15-24.

Example 15-24 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;
 
    ...
}

15.5.4 "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 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:

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

  2. Declare local to the Customer class these properties:

    • firstName (String type)

    • 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 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>

15.6 Using Dynamic JAXB/MOXy

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:

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

This example demonstrates how to bootstrap a dynamic JAXBContext from an XML Schema.

15.6.1.1 Bootstrapping 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.

15.6.1.2 The XML Schema

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>

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

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"/>

Example 15-28 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 15-29 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>

15.6.1.4 Implementing and Passing an EntityResolver

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

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

    Example 15-31 Passing in the Entityresolver

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

15.6.1.5 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 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
    

15.6.1.6 Specifying a ClassLoader

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.

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

This example shows how to create dynamic entities and marshal then to XML.

15.6.2.1 Creating the Dynamic Entities

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);

15.6.2.2 Marshalling the Dynamic Entities to XML

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:

Example 15-34 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>

15.6.3 Task 3: Unmarshal the Dynamic Entities from XML

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.

15.6.3.1 Unmarshal DynamicEntities from XML

The Unmarshaller obtained from the DynamicJAXBContext is a standard unmarshaller, and can be used normally to unmarshal instances of DynamicEntity.

Example 15-35 Standard Dynamic JAXB Unmarshaller

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

15.6.3.2 Get Data from the Dynamic Entity

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"));

15.6.3.3 Use DynamicType to Introspect Dynamic Entity

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

Example 15-36

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

15.7 Additional Resources

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