ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Understanding JAXB: Java Binding Customization

by Sayed Hashimi
12/10/2003

Java Architecture for XML Binding (JAXB) is a specification (or standard) that automates the mapping between XML documents and Java objects and vice versa. One of the primary components of JAXB is the schema compiler. The schema compiler is the tool used to generate Java bindings from an XML schema document. If used in its default mode (for non-trivial applications), the compiler usually generates bindings that are awkward to work with. This article will look at various methods you can use to customize the generated bindings.

What is JAXB and Why Do We Need It?

More often than not, when a Java application consumes an XML document, it performs two non-application specific tasks: 1) it converts a request XML document to a set of Java objects and 2) creates a response XML document after it has consumed the request XML document.

An implementation of JAXB will autogenerate Java bindings for you. This means that you can avoid:

  1. Writing a bunch of classes that map to an XML document (binding).
  2. Writing code to take a request XML document and hydrate a set of Java objects (unmarshalling).
  3. Creating a response XML document from a set of Java objects (marshalling).

Related Reading

Java & XML Data Binding
By Brett McLaughlin

At a high level, JAXB is composed of three components: the binding compiler, the binding runtime framework, and the binding language. The binding compiler is essentially a .jar file that generates Java bindings from an XML schema document. The binding runtime framework is a Java API that handles transforming XML documents to Java objects and Java objects back to XML. The binding language is an XML-based language that allows for customizing the Java bindings. This article is about customizing the Java bindings generated by the binding compiler.

Why Do We Need to Consider Customization?

The binding compiler in the reference implementation of JAXB is the .jar file jaxb-xjc.jar. The responsibility of this .jar file is to take an XML schema document and to generate a Java representation. In generating the Java representation, the compiler uses a set of binding rules that dictate how the binding should be done. For example, as a rule, the binding compiler uses an algorithm to generate Java identifier names (e.g., an interface name) from XML names when a Java identifier name is not specified via the binding language. At times, the autogenerated names are not useful when manipulating Java objects. In non-trivial applications that make use of JAXB, one will need to customize the resulting Java representation, because the default binding rules alone will not suffice. Therefore, it is important to understand how we can control the Java bindings generated by the binding compiler. The JAXB specification mentions two mechanisms that we can use to customize the bindings. I, however, think that there are three. The specification lists:

  • Annotating the XML schema document (inline binding declaration).
  • Supplying the binding compiler an external binding document (external binding declaration).

A very powerful technique not mentioned is:

  • Modifying the XML schema so that you get the binding that you want.

Combing these methods allows us to truly take control of what the binding compiler generates. The sections that follow describe the methods mentioned above with examples. We will start by looking at how we can influence the Java representation of an XML file by modifying the XML schema.

Modify the XML Schema

The binding compiler takes an XSD and generates a Java representation. When we use the Java bindings in our code, we would like the Java objects to have names that make sense. For example, if my XML document has the following structure:

<Message>
    <Header>Message Header</Header>
    <Request data="Message Data"/></Message>

It would make sense for me to have a Message object (or interface), a Header object, and a Request object in my Java representation. A lot of times, however (depending on the XML schema), the Java representation (e.g., the names given to Java objects) is not semantically meaningful; using the Java representation is awkward. For example, instead of having an object of the type Request, you may have something called RequestType. When you say Message.getRequest(), you get back a RequestType and not a Request. Although we can get by with using the default Java bindings, we want to get a representation that makes sense. We want the binding compiler to generate a set of objects as if we were hand-coding the bindings ourselves. To that end, let's understand how we can modify the XML schema to get a better Java representation.

It is important to realize that more than one XSD can validate the same XML. For example, I can take an XSD and change how I define the XML components (e.g., an element), without changing the overall structure, and have the XSD validate the same XML. Similarly, I can take two XSDs that can validate the same XML and have the binding compiler generate two very different Java representations for very same XML. Just by changing how we define our XML components while writing the schema can be a savior when it comes to getting the correct Java representation. Let's see how.

We will start by taking an XSD and generating the Java representation of it. Then we will look at the generated classes and see how we interact with the Java bindings in our Java code, and then see if it makes programmatic sense. The XSD is shown in Listing 1, a sample XML file is shown in Listing 2, and the generated PurchaseOrder interface classes are shown in Listing 3.

Listing 1. Purchase Order schema

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  elementFormDefault="qualified" 
  attributeFormDefault="unqualified">
    <xs:element name="PurchaseOrder">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="BillTo"/>
                <xs:element ref="Items"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="BillTo">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="Name" type="xs:string"/>
                <xs:element name="Street" type="xs:string"/>
                <xs:element name="City" type="xs:string"/>
                <xs:element name="State" type="xs:string"/>
                <xs:element name="Zip" type="xs:string"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="Items">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="Item" minOccurs="1" 
                   maxOccurs="unbounded"/>
             </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:element name="Item">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="ProductName" type="xs:string"/>
                <xs:element name="Quantity" type="xs:positiveInteger"/>
                <xs:element name="Price" type="xs:decimal"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

Listing 2. Purchase Order XML Document

<?xml version="1.0" encoding="UTF-8"?>
<PurchaseOrder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:noNamespaceSchemaLocation="C:\example\po.xsd">
    <BillTo>
        <Name>Sayed Hashimi</Name>
        <Street>1234 Main Street</Street>
        <City>Jacksonville</City>
        <State>FL</State>
        <Zip>32216</Zip>
    </BillTo>
    <Items>
        <Item>
            <ProductName>GodFather</ProductName>
            <Quantity>5</Quantity>
            <Price>15.99</Price>
        </Item>
    </Items>
</PurchaseOrder>

Listing 3. Purchase Order Java Representation

public interface PurchaseOrderType {
    generated.ItemsType getItems();
    generated.BillToType getBillTo();
}
public interface PurchaseOrder
    extends javax.xml.bind.Element, generated.PurchaseOrderType
{
}

First, let's look at the generated Java bindings. PurchaseOrder extends PurchaseOrderType and has two accessors; one for Items and one for BillTo. We would use the bindings as:

PurchaseOrder po = factory.createPurchaseOrder();
BillToType billTo = po.getBillTo();
ItemsType items = po.getItems();

Note that above you are dealing with objects whose names are suffixed with "Type". This clearly does not make programmatic sense. Have another look at the XML document. Just by looking at the XML structure, we can see that it is natural to say:

PurchaseOrder po = factory.createPurchaseOrder();
BillTo billTo    = po.getBillTo();
Items items      = po.getItems();

It is not meaningful to interact with a BillToType object; we should be dealing with a BillTo object. It is important to understand why the binding compiler generated types whose names were suffixed with "Type". The reason for the "Type" extension has to do with how we defined the XML components in the schema document. For example, in Listing 1, the PurchaseOrder element is an anonymous complex type with two elements (BillTo and Items). Note that the anonymous complex type refers (ref) to the other elements. The schema compiler generates an interface with a suffix of "Type" when it encounters a ref or an anonymous complex type defined as part of an element definition. Observing, this we modify our XSD to that shown in Listing 4.

Listing 4. XSD Without the refs

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  elementFormDefault="qualified" 
  attributeFormDefault="unqualified">
    <xs:element name="PurchaseOrder">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="BillTo" type="BillTo"/>
                <xs:element name="Items" type="Items"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    <xs:complexType name="BillTo">
        <xs:sequence>
            <xs:element name="Name" type="xs:string"/>
            <xs:element name="Street" type="xs:string"/>
            <xs:element name="City" type="xs:string"/>
            <xs:element name="State" type="xs:string"/>
            <xs:element name="Zip" type="xs:string"/>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="Items">
        <xs:sequence>
            <xs:element name="Item" maxOccurs="unbounded" type="Item"/>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="Item">
        <xs:sequence>
            <xs:element name="ProductName" type="xs:string"/>
            <xs:element name="Quantity" type="xs:positiveInteger"/>
            <xs:element name="Price" type="xs:decimal"/>
        </xs:sequence>
    </xs:complexType>
</xs:schema>

We have made three very important changes to the initial XSD.

  1. Removed the refs.
  2. Changed the internal PurchaseOrder elements to named complex types.
  3. When using a complex type within another element, we use set the name and type of the element to the name of the complex type.

Now let's see the resulting PurchaseOrder interfaces.

public interface PurchaseOrder
    extends javax.xml.bind.Element, generated.PurchaseOrderType
{
}
public interface PurchaseOrderType
{
    generated.Items getItems();
    generated.BillTo getBillTo();
}

Note that now we get a binding that is an accurate representation of the XML. The key to getting this binding is to stick to using named complex types and the trick of giving the name and type of an element the same name as the complex type's name (see Listing 4).

Pages: 1, 2

Next Pagearrow