Wednesday, April 04, 2007

Generate an XML Document from an Object Model with JAXB 2

Java offers various ways to manipulate XML, including the following:

  • Use simple file I/O or javax.xml.stream.XmlStreamWriter.

  • Use XML serialization with java.beans.XMLEncoder, which generates an XML representation of a Java Bean in the same way ObjectOutputStream can be used to create binary representation of Serializable objects.

  • Use a dedicated library like XStream Play directly with SAX (Simple API for XML) or DOM (Document Object Model) through the JAXP API.







Yet despite XML and Java technology being natural partners in data exchange, mapping a Java object model into XML and vice versa can be a mystery. Consider JAXB as your solution. JAXB (Java Architecture for XML Binding) enables you to perform XML-to-Java data binding and generate Java classes from XML schemas and vice-versa. It's portable, easy to use, and it offers functionalities such as XML validation and customization using annotation and adapters. (Figure 1 illustrates the uses of JAXB.)

Click to enlarge








Figure 1. JAXB Architecture


The JAXB API, defined in the javax.xml.bind package, is a set of interfaces and classes through which applications communicate with code generated from a schema. The center of the JAXB API is the javax.xml.bind.JAXBContext class. JAXBContext is an abstract class that manages the XML/Java binding. It can be seen as a factory because it provides:



  • An Unmarshaller class that deserializes XML into Java and optionally validates the XML (using the setSchema method).

  • A Marshaller class that serializes an object graph into XML data and optionally validates it.


First of all, JAXB can define a set of classes into an XML schema by using a schema generator. It also enables the reverse action, allowing you to generate a collection of Java classes from a given XML schema through the schema compiler. The schema compiler takes XML schemas as input and generates a package of Java classes and interfaces that reflect the rules defined in the source schema. These classes are annotated to provide the runtime framework with a customized Java-XML mapping.


JAXB also can generate a Java object hierarchy from an XML schema using a schema generator, or give an object Java hierarchy to describe the corresponding XML schema. The runtime framework provides the corresponding unmarshalling, marshalling, and validation functionalities. That is, it lets you transform an XML document into an object graph (unmarshalling) or transform an object graph into XML format (marshalling).


These capabilities are why JAXB is often associated with web services. Web services use the API to transform objects into messages that they then send through SOAP. This isn't a web services article, however. Using an example address book application for a fictional music company called Watermelon, it focuses on JAXB's XML binding features for round-tripping XML to Java.





















What You Need
JAXB (this article uses version 2.1.2 )
JUnit 4.1
Ant 1.7
MySQL 5 and TopLink Essentials as the implementation of JPA



Page 1 of 5




Let's Get Generate Some XML

Watermelon sells and delivers music products such as instruments, amplifiers, scores, books, and so on. In its address book, the company stores two types of customers: individuals and companies. Each has a home address and a set of delivery addresses. Each delivery address can be used on weekends and/or evenings and/or weekdays. I will include this information by adding tags to an address. Figure 2 shows the business model for Watermelon's address book.



Click to enlarge








Figure 2. Business Model for Watermelon's Address Book


Watermelon wants to send the details of some of its customers to a partner in XML format, so it needs an XML document of its object model for a given customer. With JAXB, this is really easy to do. The following code creates an instance of an Individual and then sets its attributes (first name, last name…), a home address, and two delivery addresses that are tagged. Once the objects are all set, it uses the javax.xml.bind.Marshaller to generate an XML representation of the Individual object, which is then displayed:



Listing 1: Creates an XML Representation of an Individual

// Instantiates Tag objects
Tag tag1 = new Tag("working hours");
Tag tag2 = new Tag("week-ends");
Tag tag3 = new Tag("mind the dog");

// Instantiates an individual object with home address
calendar.set(1940, 7, 7, 0, 0, 0);
Individual individual = new Individual(1L, "Ringo", "Starr", "+187445", "ringo@star.co.uk", calendar.getTime());
individual.setHomeAddress(new Address(2L, "Abbey Road", "London", "SW14", "UK"));

// Instantiates a first delivery address
Address deliveryAddress1 = new Address(3L, "Findsbury Avenue", "London", "CE451", "UK");
deliveryAddress1.addTag(tag1);
deliveryAddress1.addTag(tag3);
individual.addDeliveryAddress(deliveryAddress1);

// Instantiates a second delivery address
Address deliveryAddress2 = new Address(4L, "Camden Street", "Brighton", "NW487", "UK");
deliveryAddress2.addTag(tag1);
deliveryAddress2.addTag(tag2);
individual.addDeliveryAddress(deliveryAddress2);

// Generates XML representation of an individual
StringWriter writer = new StringWriter();
JAXBContext context = JAXBContext.newInstance(Customer.class);
Marshaller m = context.createMarshaller();
m.marshal(individual, writer);


System.out.println(writer);

Author's note: The Marshaller.marshal() method takes an object and marshals it into several supports. The example uses a StringWriter to have a string representation of the XML document, but you could use a FileOutputStream to store the XML into a file, as follows:


File file = new File("customer.xml");
FileOutputStream outputFile = new FileOutputStream(file);
m.marshal(individual, outputFile);

 


The code creates a new instance of JAXBContext by using the static method newInstance, to which it passes the root class that the application needs to manipulate (Customer). From the created Marshaller object, it then calls the marshal method that generates the XML representation of an individual into a StringWriter.


The only thing left to do is add an @XmlRootElement annotation to the Customer class. The @XmlRootElement annotation notifies JAXB that the annotated class is the root element of the XML document. If this annotation is missing, JAXB will throw an exception. If you add it and run the code, you will get the following XML document:



Listing 2: XML Representation of an Individual




London
UK
3
Findsbury Avenue

working hours


mind the dog

CE451


Brighton
UK
4
Camden Street

working hours


week-ends

NW487

ringo@star.co.uk

London
UK
2
Abbey Road
SW14

1

+187445

With just one annotation (@XmlRootElement), a Marshaller object, and the coding-by-exception approach that guides JAXB (you need to add custom code only when the default is inadequate), you can easily get an XML representation of your object graph. The root element represents the Customer object, and it includes all the attributes (one home address, two delivery addresses, an ID, a phone number, and so on).



Page 2 of 5



Customize the XML Document

Watermelon and its business partner are not completely happy with the structure of the previous XML document (Listing 2). They would like to get rid of some information (address identifiers, tags), format the date of birth, order some attributes, and rename others. Thanks to the annotations of the javax.xml.bind.annotation package, JAXB provides a way to customize and control the XML structure.


First of all, you don't really want to map customers. Rather, you want to map individuals and companies (that means getting rid of the element and instead using or as root elements). To notify JAXB not to take the abstract Customer class into account, you have to get rid of the @XmlRootElement annotation and make the class transient with @XmlTransient. You can use this annotation on a class or an attribute. It informs JAXB not to take it into account when creating the XML representation.


An XML document is made of elements (value) and attributes (). JAXB uses two annotations to differentiate them: @XmlAttribute and @XmlElement. Each annotation has a set of parameters that allows you to rename an attribute, allow null value or not, give it a default value, etc. The following code uses these annotations to turn id into an XML attribute (instead of an element) and to rename the delivery address element (address instead of deliveryAddresses):



@XmlTransient
public abstract class Customer {

@XmlAttribute
protected Long id;
protected String telephone;
protected String email;
protected Address homeAddress;
@XmlElementWrapper(name = "delivery")
@XmlElement(name = "address")

protected List
deliveryAddresses = new ArrayList
();
// Constructors, getters, setters
}


This code uses another interesting annotation, @XmlElementWrapper. It generates a wrapper element around the collections of delivery addresses. If you look back at Listing 2, you'll see two elements. With the code above, you get one element that wraps two



elements.


Talking about addresses, you want to get rid of the identifier and the tags from the XML document. For that, use the @XmlTransient annotation. To rename an element, just use the name property of the @XmlElement annotation. The following code renames the attribute zipcode into a element:



@XmlType(propOrder = {"street", "zipcode", "city", "country"})
@XmlAccessorType(XmlAccessType.FIELD)

public class Address {

@XmlTransient
private Long id;
private String street;
private String city;
@XmlElement(name = "zip")
private String zipcode;
private String country;
@XmlTransient
private List tags = new ArrayList();
// Constructors, getters, setters
}


Notice the @XmlType annotation on the top of the class. It allows JAXB to map a class or an enum to a XML schema type. You can use it to specify a namespace or to order attributes using the propOrder property, which takes a list of names of attributes and generates the XML document following this order.









Author's note: The Address class is annotated with @XmlAccessorType. It controls whether fields or Java bean properties are serialized. XmlAccessType represents the possible values (FIELD, NONE, PROPERTY, PUBLIC_MEMBER). This example uses the FIELD value, which notifies JAXB to use the attributes for the XML mapping (and not getters).


Table 1 below shows three different extracts of XML documents:



  1. The default customer XML document that you would get with coding-by-exceptions (with no annotation except @XmlRootElement)

  2. The customizations done to the customer class (the identifier becomes an attribute and the delivery addresses are wrapped into a element)

  3. Taken into account the annotations of the Address class (no identifier and no tags are displayed; elements are in a certain order)
















Default XML Representation Annotated Customer Class Annotated Address Class




London

UK

3

Findsbury



working hours





mind the dog



CE451





Brighton

UK

4

Camden



working hours





week-ends



NW487



(...)









London

UK

3

Findsbury



working hours





mind the dog



CE451









Brighton

UK

4

Camden



working hours





week-ends



NW487







(...)









Findsbury

CE451

London

UK









Camden

NW487

Brighton

UK







(...)








Table 1. Customizations of the XML Representation of a Customer


You also can annotate the concrete classes Company and Individual to customize the mapping (see table below). First of all, being the root of the XML document now, both have to use the @XmlRootElement annotation where they can specify the XML namespace http://www.watermelon.example/customer. Like the Address, the example uses the @XmlType.propOrder to order the attributes. Note that in the list of attributes, you can use the ones inherited by the super class Customer, such as id, email, telephone, homeAddress, and so on.














Annotated Company Class Annotated Individual Class
@XmlRootElement(name = "company", namespace =

"http://www.watermelon.example/customer")

@XmlType(propOrder = {"id", "name", "contactName",

"telephone", "email", "numberOfEmployees",

"homeAddress", "deliveryAddresses"})

@XmlAccessorType(XmlAccessType.FIELD)


public class Company extends Customer {



@XmlAttribute

private String name;

private String contactName;

private Integer numberOfEmployees;

// Constructors, getters, setters

}
@XmlRootElement(name = "individual", namespace =

"http://www.watermelon.example/customer")

@XmlType(propOrder = {"id", "lastname",

"firstname", "dateOfBirth", "telephone",

"email", "homeAddress",

"deliveryAddresses"})

@XmlAccessorType(XmlAccessType.FIELD)


public class Individual extends Customer {



private String firstname;

@XmlAttribute

private String lastname;

@XmlJavaTypeAdapter(DateAdapter.class)

private Date dateOfBirth;

// Constructors, getters, setters

}


There is still one thing that Watermelon is not happy about: the date format. JAXB maps java.util.Date attributes into the default value. For example, the date of birth of an individual will be displayed as follow:



1940-08-07T00:00:00.781+02:00


To format this date (e.g., 07/08/1940), you have two choices:



  1. Use the datatype javax.xml.datatype.XMLGregorianCalendar instead of java.util.Date. The XMLGregorianCalendar gives a W3C XML representation for date/time datatypes and allows certain manipulations.

  2. Use an adapter. As you can see in the code above, the Individual class uses a @XmlJavaTypeAdapter annotation. @XmlJavaTypeAdapter(DateAdapter.class) notifies JAXB to use the custom adapter called DateAdapter when marshalling/unmarshalling the dateOfBirth attribute. Adapters are used when Java types do not map naturally to a XML representation. You can then adapt a bound type to a value type or vice versa.


To format the date, you have to write a class (DateAdpater) that extends XmlAdapter. It has to override the marshal and unmarshal methods, which the JAXB binding framework invokes during marshalling and unmarshalling. This way, you can marshal a date into a formatted string and vice versa. The following code just uses a java.text.SimpleDateFormat object to format the date into a string and vice-versa:



public class DateAdapter extends XmlAdapter {

DateFormat df = new SimpleDateFormat("dd/MM/yyyy");

public Date unmarshal(String date) throws Exception {
return df.parse(date);
}

public String marshal(Date date) throws Exception {
return df.format(date);
}
}


Now if you go back to Listing 1, when you call the Marshaller.marshal() method, the DateAdapter.marshal() is also called (@XmlJavaTypeAdapter(DateAdapter.class)) and the date of birth is then formatted. Here is the XML document you obtain:


















Individual XML Document




Ringo

07/08/1940

+187445

ringo@star.co.uk



Abbey Road

SW14

London

UK









Findsbury Avenue

CE451

London

UK









Camden Street

NW487

Brighton

UK







Company XML Document




Mr Father

+14519454

contact@sony.com

25000



General Alley

75011

Paris

FR









St James St

SW14

London

UK









Central Side Park

7845

New York

US










Page 3 of 5



Unmarshal and Generate a Schema

As Figure 1 showed, JAXB can also be used to unmarshal, generate, and compile a schema. So let's quickly talk about that. From what you've learned so far, you should understand unmarshalling. The idea is to take the previously obtained XML document and generate an object graph. For that, you get a JAXBContext, from which you create an Unmarshaller object. You then call the unmarshal method, passing an XML document (either from a String or a file), and it returns an instance of an Individual with its attributes:

// xmlString contains the XML document of an individual
StringReader reader = new StringReader(xmlString);
JAXBContext context = JAXBContext.newInstance(Individual.class);
Unmarshaller u = context.createUnmarshaller();

Individual individual = (Individual) u.unmarshal(reader);

System.out.println(individual.getFirstname());




An XML schema describes the structure of an XML document and it is written in XML syntax. Even if you don't know much about XML schema, you can generate one using the schemaGen tool provided by Sun's JAXB implementation (command line or Ant task). For example, schemaGen can take all the classes developed for this article and generate the XML schema in Listing 3. In that schema, you can see the Address, Company, Individual, and Tag classes described as complex types (download the code and you will see how schemaGen is invoked from Ant):






address">








company">


















individual">


















tag">






dateAdapter">













When you download JAXB, another tool comes along with it: the schema compiler (xjc). schemaGen allows to generate an XML schema from Java classes, and xjc does the opposite: from an XML schema, it creates annotated Java files. You can use it as a command line or as an Ant task. Take Listing 3, run xjc, and that will generate the class hierarchy (not the objects, the classes) that describes the Watermelon business model.



Page 4 of 5




Annotations for Persistence

Some of you might already know of the fictional music company called Watermelon. I've used it in two previous articles about JPA ("Master the New Persistence Paradigm with JPA" and "Persistence Pays Offs: Advanced Mapping with JPA"). If you think of JAXB as a way to persist data into XML, JPA is its counterpart in terms of relational databases. In fact, both technologies rely heavily on annotations. That means that the same class can be annotated by JPA and JAXB annotations and, in this way, can give an XML representation as well as be persisted in a database.


For those of you interested in this double use of annotations, you will find an example in the code download. But just to give you a taste, here is what the Address class would look like:



@Entity
@Table(name = "t_address")

@XmlType(propOrder = {"street", "zipcode", "city", "country"})
@XmlAccessorType(XmlAccessType.FIELD)
public class Address {

@XmlTransient
@Id @GeneratedValue
private Long id;

private String street;

@Column(length = 100)
private String city;

@Column(name = "zip_code", length = 10)
@XmlElement(name = "zip")
private String zipcode;

@Column(length = 50)
private String country;

@XmlTransient
@ManyToMany(cascade = CascadeType.PERSIST)
@JoinTable(name = "t_address_tag",
joinColumns = {@JoinColumn(name = "address_fk")},
inverseJoinColumns = {@JoinColumn(name = "tag_fk")})

private List tags = new ArrayList();

// Constructors, getters, setters
}



Page 5 of 5