TechTip | Monday, September 15, 2008

Exchanging Data With XML and JAXB, Part 2

by Jennie Hall

In Exchanging Data With XML and JAXB, Part 1, we saw
how the Java Architecture for XML Binding (JAXB) expedites
the exchange of data between systems. With JAXB 2.0's annotation
support, generating XML data for a business partner is as easy as
annotating your existing object model. 

In this tip, you'll learn how JAXB's binding customization
features can facilitate XML processing for the recipient of data as
well as for the sender. Binding customization offers a level of
control over the characteristics of the Java classes generated by
the JAXB schema compiler. This allows you to work with an object
model that makes sense for your business domain even when processing
XML instance documents based on a schema that is not of your own
design.

For a brief overview of the Java Architecture for XML Binding
(JAXB), see the "What's JAXB?" section of Exchanging Data With XML and JAXB, Part
1
.

The Sample Application

Let's continue with our scenario from Part 1.A veterinary office, NiceVet, wants to send upcoming
appointment reminders and pet birthday cards to its clients. NiceVet
contracts with a service provider, WePrintStuff, to do the printing
and mailing. In
Part 1, we assembled some data for WePrintStuff by annotating
NiceVet's existing object model and then using JAXB to marshal
application data from Java objects to an XML instance document. We
also used JAXB's schema generator to produce a corresponding schema
from the annotated Java classes.

On the receiving end, WePrintStuff will run the JAXB schema
compiler against the source schema to generate the schema-derived
classes. JAXB will create instances of these classes as it
unmarshals NiceVet's data from the XML instance document and binds
it into a Java content tree. In some cases, however, the default
manner in which JAXB generates the schema-derived classes and binds
the data is not exactly what is needed. JAXB's binding customization
features provide us with some nice ways to handle many of these
cases. We'll take a look at some of them in the next section.

Customizing the Source Schema

We can make some customizations right away to make life easier at
WePrintStuff. For example, we would like to work
with meaningful package and class names and avoid any naming
collisions. To accomplish this, we can add custom binding
declarations to the source schema itself -- this is known as inline
customization.

Here are the first few lines of the annotated schema:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"

<!-- JAXB namespace declaration required for inline customization -->

xmlns:jxb="http://java.sun.com/xml/ns/jaxb"

xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"

jxb:extensionBindingPrefixes="xjc"

<!-- JAXB version number required for inline customization -->

jxb:version="2.0">

<xs:annotation>

<xs:appinfo>
<!-- All collections in this schema will use implementation class java.util.ArrayList. -->

<jxb:globalBindings collectionType="java.util.ArrayList"/>

<jxb:schemaBindings>
<!-- Specify the package for the generated classes; avoid naming collisions. -->

<jxb:package name="weprintstuff.generated">
<!-- Specify some Javadoc that describes our package and its uses. -->

<jxb:javadoc><![CDATA[<body> The package weprintstuff.generated contains the
schema-derived classes that make up the object model used to process
print client orders.</body>]]>

</jxb:javadoc>

</jxb:package>

</jxb:schemaBindings>

</xs:appinfo>

</xs:annotation>

To enable inline customization, we must include a JAXB namespace
declaration and the JAXB version number. The JAXB namespace prefix
we'll use in our sample application is jxb:. We can
override the default JAXB bindings at different scopes. Lower-level,
more fine-grained scopes inherit declarations made at higher-level
scopes, but binding declarations made at lower-level scopes can
override these inherited declarations. The scopes in order from
highest to lowest level are global, schema, definition, and component.

In the preceding example, we've made a declaration at global
scope that specifies that all collections in this schema will use
the implementation class java.util.ArrayList. If our
source schema imported another schema, this declaration would apply
to the second schema as well. There can be only one
<globalBindings> declaration per schema. This
declaration is valid in the top-level schema only.

At schema scope, we've specified the package into which the JAXB
schema compiler will generate the schema-derived classes. We've also
included some package-level Javadoc that describes our package and
its uses. Note that we've wrapped our custom binding declarations in
<annotation><appinfo> tags.

Now that we have the package we want, let's see if we can get
some class names that better reflect Java naming conventions and
WePrintStuff's business domain. The default bindings for the
original schema would generate classes with names like
ClassAType, ClassBType, and so
on.This is not how we typically name classes in the
Java programming language, so let's fix it:

    <xs:element name="printOrder" type="PrintOrderType"/>
<xs:complexType name="PrintOrderType">
<xs:annotation>
<xs:appinfo>
<!-- Name the generated class PrintOrder rather than PrintOrderType. -->
<jxb:class name="PrintOrder">
<!-- Provide some Javadoc for PrintOrder. -->
<jxb:javadoc><![CDATA[<code>PrintOrder</code> javadoc goes here]]>
</jxb:javadoc>
</jxb:class>
</xs:appinfo>
</xs:annotation>
<xs:sequence>
<xs:element name="notifications" type="notificationsType" minOccurs="0">
<xs:annotation>
<xs:appinfo>
<!-- PrintOrder's notifications property becomes printItems. -->
<jxb:property name="printItems"/>
</xs:appinfo>
</xs:annotation>
</xs:element>
</xs:sequence>

...
</xs:complexType>
<xs:complexType name="notificationsType">
<xs:annotation>
<xs:appinfo>
<!-- Name the generated class PrintItems rather than NotificationsType. -->
<jxb:class name="printItems" />
</xs:appinfo>
</xs:annotation>

...
</xs:complexType>

PrintOrderType becomes PrintOrder,
NotificationsType becomes PrintItems, and
so on. Dealing with class names is straightforward, but
unfortunately we're stuck with a class structure that is less than
ideal. There are too many wrappers around the data, which results in
a long chain of method calls. Here's what the calls would have looked
like with classes derived from the original schema. As you can see,
methods with singular method names return collections, which is misleading.

List<AppointmentType> appointments = printOrder.getNotifications().getAppointments().getAppointment();

List<BirthdayType> birthdays = printOrder.getNotifications().getBirthdays().getBirthday();

The following improved calls show the new names, which say what they
mean and are more relevant to WePrintStuff's business domain.

List<Appointment> appointments = printOrder.getPrintItems().getAppointmentHolder().getAppointments();

List<Birthday> birthdays = printOrder.getPrintItem().getBirthdayHolder().getBirthdays();

I tried to solve some of the class structure issues by using the
enhanced <jxb:javaType> customization
(<xjc:javaType>) and an adapter class derived
from XmlAdapter, but JAXB currently does not support
this customization for XML complex types. This issue is documented
in the JAXB issue tracker as issue number 209.
Meanwhile, we'll look at an example of the enhanced
<jxb:javaType> customization used with an XML
simple type in the upcoming paragraphs.

Moving down the source schema, we see that each print order
received will have an ID of type long. We know,
however, that WePrintStuff employs a natural key
strategy in its persistent store. For print-order records, the key
consists of a client name and an order ID. WePrintStuff uses the
class PrintOrderKey to encapsulate this key
information. We'll apply a binding customization that causes the
JAXB schema compiler to use an adapter class,
IdAdapter, to replace the original print-order ID with
WePrintStuff's PrintOrderKey class. Take a
look:

   <xs:element name="printOrder" type="PrintOrderType"/>
<xs:complexType name="PrintOrderType">
<xs:sequence>
<xs:element name="notifications" type="notificationsType" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="id" type="xs:long" use="required">
<xs:annotation>
<xs:appinfo>
<!-- Use an XmlAdapter-based adapter to create WePrintStuff's PrintOrderKey class. -->
<!-- Specify the name orderKey for this property. -->
<jxb:property name="orderKey">
<jxb:baseType>
<!-- Specify weprintstuff.print.PrintOrderKey as the type of the orderKey property. -->
<!-- Specify the adapter class that will map the schema type to the Java type. -->
<xjc:javaType name="weprintstuff.print.PrintOrderKey"
adapter="weprintstuff.print.IdAdapter"/>
</jxb:baseType>
</jxb:property>
</xs:appinfo>
</xs:annotation>
</xs:attribute>
</xs:complexType>

We've changed the name of the print-order property to
orderKey and specified the types of the adapter and the
order key. During the unmarshalling process, JAXB calls
IdAdapters unmarshal() method, which
receives the value of id and incorporates it into a new
PrintOrderKey containing the order ID and client name.
Here's the code for IdAdapter:

public class IdAdapter extends XmlAdapter<String, PrintOrderKey> {

// When marshalling a Java content tree to an XML instance document,

// move from the type that we work with in Java (PrintOrderKey)

// to the type that JAXB understands.

public String marshal(PrintOrderKey key) throws Exception {

return key.getOrderId().toString();

}

// When unmarshalling an XML instance document to a Java content tree,

// move from the type that JAXB understands (String) to the type

// we want to work with in Java technology.

public PrintOrderKey unmarshal(String id) throws Exception {

// WePrintStuff uses natural keys. Add client name and

// convert String ID to required Long.

return new PrintOrderKey("NICEVET", new Long(id));

}

}

The <xjc:javaType> customization we've just
discussed is an example of a vendor extension. The JAXB reference
implementation (RI) offers additional customizations that are not part of
the JAXB specification. To use these extensions, we must include a
declaration for the JAXB RI vendor extension namespace and specify a
namespace prefix:

<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"

xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
<!-- JAXB RI vendor extension namespace declaration is required. -->

xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
<!-- JAXB RI vendor extension namespace prefix is required. -->
jxb:extensionBindingPrefixes="xjc"

jxb:version="2.0">

Finally, vendor extensions require that we run the schema
compiler with the -extension switch. For an example of
the standard <jxb:javaType> customization, which
is not a vendor extension, check out niceVet.xsd and
weprintstuff.print.CustomDataTypeConverter. These files
are included with the sample code that comes with this tech
tip. In our example, we've used the
<jxb:javaType> customization to convert XML dateTime
types to nicely formatted String dates ready for output.

Things are looking better, but we'd also like to handle the
printing of appointment reminders and birthday cards as
polymorphically as we can. Take a look at the following:

   <xs:complexType name="AppointmentType">
<xs:annotation>
<xs:appinfo>
<!-- Name has generated class Appointment rather than AppointmentType. -->
<jxb:class name="Appointment">
<jxb:javadoc><![CDATA[<code>Appointment</code> javadoc goes here]]>
</jxb:javadoc>
</jxb:class>
</xs:appinfo>
</xs:annotation>
<xs:complexContent>
<!-- XML Schema extension element causes AppointmentType to derive from printItemType, a type we've added to the schema. -->
<xs:extension base="printItemType">
<xs:sequence>
...
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
...
<!-- Add this complex type to the schema so that Appointment class now derives from PrintItem, a base class that we've implemented. -->
<xs:complexType name="printItemType">
<xs:annotation>
<xs:appinfo>
<!-- Due to this customization, JAXB will not generate a class for printItemType; it will use the PrintItem class that we've written. -->
<jxb:class ref="weprintstuff.print.PrintItem"/>
</xs:appinfo>
</xs:annotation>
</xs:complexType>

Fortunately for us, we can modify the structure of the source
schema somewhat while retaining the schema's ability to validate the
same set of XML instance documents. In this case, we have added a
totally new complex type, printItemType, to the schema.
Then we modified the complex type AppointmentType with
the XML Schema extension element, causing
AppointmentType to derive from
printItemType.

At this point, we applied a JAXB customization to
printItemType that directs the schema compiler to use
the specified class that we've written rather than generating one.
Because we've implemented PrintItem ourselves, we can
include behavior: specifically, a print() method that
allows us to handle subclasses, such as Appointment and
Birthday, in a more polymorphic manner. Here's the code
for PrintItem:

package weprintstuff.print;

import java.util.\*;

public abstract class PrintItem {

private static final Map<String,Printer> printers;

static {

printers = new HashMap<String,Printer>();

printers.put("weprintstuff.generated.Birthday", new BirthdayPrinter());

printers.put("weprintstuff.generated.Appointment", new AppointmentPrinter());

}

public void print() {

Printer p = this.getPrinter();

p.print(this);

}

private Printer getPrinter() {

return printers.get(this.getClass().getName());

}

}

The Printer interface has a single method:

    public void print(PrintItem item);

Although we're still hampered by the structure of the
schema-derived classes as discussed earlier, the code to print the
items in the print order is now pretty clean. Here's an excerpt from
our main() method:

// Unmarshal the data in the XML instance document to a Java content tree

// made up of instances of the schema-derived classes.

JAXBElement printOrderElement = (JAXBElement)unmarshaller.unmarshal(new FileInputStream(input));

PrintOrder printOrder = (PrintOrder)printOrderElement.getValue();

List<Appointment> appointments = printOrder.getPrintItems().getAppointmentHolder().getAppointments();

for (Appointment a : appointments) {

a.print();

}

List<Birthday> birthdays = printOrder.getPrintItems().getBirthdayHolder().getBirthdays();

for (Birthday b : birthdays) {

b.print();

}

Another nice customization feature that JAXB provides is the
ability to map XML simple types to typesafe enumerations. This is
convenient because WePrintStuff currently has printing templates for
certain types of appointments only. We'll modify the schema to
include the supported appointment types and add a customization that
causes the schema compiler to map the elements of
ApptType to a Java typesafe enum class. XML instance
documents with appointment types that WePrintStuff does not support
will be rejected:

    <xs:simpleType name="ApptType">
<xs:annotation>
<xs:appinfo>
<!-- Map the elements of this simple type to a Java typesafe enum class. -->
<jxb:typesafeEnumClass/>
</xs:appinfo>
</xs:annotation>
<!-- Use XML Schema elements restriction and enumeration to define the supported appointment types. -->
<xs:restriction base="xs:string">
<xs:enumeration value="Yearly Checkup"/>
<xs:enumeration value="Well Mom Exam"/>
<xs:enumeration value="Teeth Cleaning"/>
<xs:enumeration value="Vaccination"/>
<xs:enumeration value="Senior Pet Checkup"/>
</xs:restriction>
</xs:simpleType>

Here is the resultant typesafe enum class, ApptType:

package weprintstuff.generated;

import javax.xml.bind.annotation.XmlEnum;

import javax.xml.bind.annotation.XmlEnumValue;

import javax.xml.bind.annotation.XmlType;

@XmlType(name = "ApptType")

@XmlEnum

public enum ApptType {

@XmlEnumValue("Yearly Checkup")

YEARLY_CHECKUP("Yearly Checkup"),

@XmlEnumValue("Well Mom Exam")

WELL_MOM_EXAM("Well Mom Exam"),

@XmlEnumValue("Teeth Cleaning")

TEETH_CLEANING("Teeth Cleaning"),

@XmlEnumValue("Vaccination")

VACCINATION("Vaccination"),

@XmlEnumValue("Senior Pet Checkup")

SENIOR_PET_CHECKUP("Senior Pet Checkup");

private final String value;

ApptType(String v) {

value = v;

}

public String value() {

return value;

}

public static ApptType fromValue(String v) {

for (ApptType c: ApptType.values()) {

if (c.value.equals(v)) {

return c;

}

}

throw new IllegalArgumentException(v);

}

}

So far we've made what are known as inline binding
customizations, but JAXB also accepts customizations in the form of
one or more external binding customization files. Inline and external
customizations can be used in combination, but they cannot be used
together on the same element. External binding files are useful when
you cannot modify a given schema or when you want to reuse
customizations across schemas. For example, WePrintStuff has its own
class for U.S. addresses, DomesticAddress, and WePrintStuff
would like to use this class to represent the concept of an address throughout the system.

Here's the external binding file, binding.xjb:

<!-- XML Schema namespace is required, as are the JAXB namespace and version. -->

<jxb:bindings version="2.0"

xmlns:jxb="http://java.sun.com/xml/ns/jaxb"

xmlns:xs="http://www.w3.org/2001/XMLSchema">

<!-- Specify the schema name and root schema node. -->

<jxb:bindings schemaLocation="niceVet.xsd" node="/xs:schema">

<!-- Specify the schema node to which this customization should apply with an XPath expression. -->

<jxb:bindings node="//xs:complexType[@name='AddressType']">
<!-- Direct the schema compiler to use the DomesticAddress class rather than generating a class for complex type AddressType. -->

<jxb:class ref="weprintstuff.print.DomesticAddress"/>

</jxb:bindings> <!-- node="//xs:complexType[@name='AddressType']" -->

</jxb:bindings> <!-- schemaLocation="niceVet.xsd" node="/xs:schema" -->

</jxb:bindings>

The binding customization file is
just an ASCII text file. A valid binding file must specify the
schema name and node. We identify nodes using XPath expressions.
In the previous listing, we've applied a customization that directs
the schema compiler to use WePrintStuff's
DomesticAddress class rather than generating a class
for the complex type AddressType. This is the same
customization illustrated earlier in our PrintItem base
class example, although in that situation, we declared the
customization inline. The syntax for supplying a binding
customization file to the schema compiler is the following:

xjc -b bindings schema

We can apply multiple binding files to one schema, one binding
file to multiple schemas, or multiple binding files to multiple
schemas. Each binding file must have its own -b switch.

Generating the Schema-Derived Classes

We generate our schema-derived classes at the command line with
the following command to the schema compiler, xjc:

xjc -d ./src -b binding.xjb -extension niceVet.xsd

The schema compiler informs us that it has generated our classes
with the following output:

parsing a schema...

compiling a schema...

weprintstuff\\generated\\Adapter1.java

weprintstuff\\generated\\Adapter2.java

weprintstuff\\generated\\Appointment.java

weprintstuff\\generated\\ApptType.java

weprintstuff\\generated\\Birthday.java

weprintstuff\\generated\\ObjectFactory.java

weprintstuff\\generated\\Owner.java

weprintstuff\\generated\\Pet.java

weprintstuff\\generated\\PrintItems.java

weprintstuff\\generated\\PrintOrder.java

weprintstuff\\generated\\package.html

The JAXB schema compiler has a number of options. For example, it
will mark generated classes as read only in response to
the -readOnly switch. Invoke xjc with no
options or with the -help switch for more
information.

The Payoff

We've modified our source schema, made our customizations, and
generated our schema-derived classes. All that's left to do is run
our printing program, let JAXB bind the XML data supplied by
NiceVet, and print out the appointment reminders and birthday cards.
We'll take a look at our XML instance document:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<printOrder id="1219271208522">
<notifications>
<appointments>
<appointment>
<apptType>Yearly Checkup</apptType>
<apptDate>2008-09-15T00:00:00-07:00</apptDate>
<owner>
<firstName>Joe</firstName>
<lastName>Outdoors</lastName>
<address>
<addressLine1>123 Whitewater Street</addressLine1>
<city>OurTown</city>
<state>CA</state>
<zip>90347</zip>
<zipExt>1234</zipExt>
</address>
</owner>
<pet>
<name>Honcho</name>
<species>Dog</species>
</pet>
</appointment>
<appointment>
<apptType>Well Mom Exam</apptType>
<apptDate>2008-09-12T00:00:00-07:00</apptDate>
<owner>
<firstName>Missy</firstName>
<lastName>Fairchild</lastName>
<address>
<addressLine1>456 Scenic Drive</addressLine1>
<city>West OurTown</city>
<state>CA</state>
<zip>90349</zip>
<zipExt>6789</zipExt>
</address>
</owner>
<pet>
<name>Miss Kitty</name>
<species>Cat</species>
</pet>
</appointment>
</appointments>
<birthdays>
<birthday>
<age>7</age>
<birthday>2000-09-07T00:00:00-07:00</birthday>
<owner>
<firstName>Violet</firstName>
<lastName>Flowers</lastName>
<address>
<addressLine1>22375 Willow Court</addressLine1>
<city>West OurTown</city>
<state>CA</state>
<zip>90349</zip>
<zipExt>6789</zipExt>
</address>
</owner>
<pet>
<name>Tom</name>
<species>Cat</species>
</pet>
</birthday>
</birthdays>
</notifications>
</printOrder>

And here's another look at the main() method:

// The XML instance document received from NiceVet contains NiceVet's print order.

File input = new File("niceVet.xml");

// Create a JAXBContext for the weprintstuff.generated package.

JAXBContext ctx = JAXBContext.newInstance("weprintstuff.generated");

// Create an unmarshaller.

Unmarshaller unmarshaller = ctx.createUnmarshaller();

// Unmarshal the data in the XML instance document to a Java content tree made up of instances of the schema-derived classes.

JAXBElement printOrderElement = (JAXBElement)unmarshaller.unmarshal(new FileInputStream(input));

PrintOrder printOrder = (PrintOrder)printOrderElement.getValue();

// Print out the print items.


List<Appointment> appointments = printOrder.getPrintItems().getAppointmentHolder().getAppointments();

for (Appointment a : appointments) {

a.print();

}

List<Birthday> birthdays = printOrder.getPrintItems().getBirthdayHolder().getBirthdays();

for (Birthday b : birthdays) {

b.print();

}

Check out our results: No one will ever miss an appointment or a
birthday again.

Hi Joe!

Our records show that your dog Honcho has a Yearly Checkup appointment scheduled on 09/15/2008.

Please call us 24 hours prior to your appointment if you need to reschedule.

Sincerely, NiceVet

Hi Missy! Our records show that your cat Miss Kitty has a Well Mom Exam appointment scheduled on 09/12/2008.

Please call us 24 hours prior to your appointment if you need to reschedule.

Sincerely, NiceVet

Hi Violet!

Our records show that your cat Tom will turn 7 years old on 09/07.

Happy Birthday, Tom, from all of us at NiceVet!

Running the Sample Application

To run the sample application, download the sample code and unzip
it. Navigate
to the directory
<sample-install-dir>/schema-to-java and generate
the schema-derived classes as described in the section "Generating
the Schema-Derived Classes."

Launch the NetBeans IDE and select File > Open Project.
In the Open Project dialog box, navigate to the directory where you
unzipped the sample code and select the folder schema-to-java.
Select the Open as Main Project check box. Click Open Project
Folder. Right-click the schema-to-java project and select Run
Project.

Conclusion

In Exchanging Data With XML and JAXB, Parts 1 and 2, we've learned how JAXB can facilitate the flow of data between
business partners. With features that expedite XML processing on
both ends of the exchange, JAXB can enable simpler, more productive
integration between systems.

References and Resources

Sample code for this tip
Java EE 5 Tutorial
Kohsuke Kawaguchi's Blog - Kohsuke Kawaguchi is a staff engineer at Sun Microsystems, where he works on JAXB, among other projects.
JSR 222: JAXB Specification

About the Author

Jennie Hall is a lead developer working in the financial sector.

Join the discussion

Comments ( 4 )
  • bob smith Thursday, September 18, 2008

    awesome!


  • Mike Jones Tuesday, September 23, 2008

    These tips are really useful. Thanks,


  • guest Saturday, September 27, 2008

    profe la verdad la informacion creo que no esta tan clara. Es mucha informacion y que hay que hacer con eso


  • wolf wayne robinson Wednesday, October 1, 2008

    i just won't to get started


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha
 

Visit the Oracle Blog

 

Contact Us

Oracle

Integrated Cloud Applications & Platform Services