ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


XML Data Binding with Castor

by Dion Almaer
10/24/2001

In this article, we will walk through marshalling data to and from XML, using a XML data-binding API. The first question is, why? Why not use SAX or DOM? Personally, when I sit down to work with XML, I get frustrated with the amount of code that you need to write to do simple things.

I came across JDOM and found it to be something I was looking for. DOM is built to be language-agnostic, and hence doesn't feel very "Java-like." JDOM does a great job in being DOM, in a way I would like to use it in Java.

For some applications, I don't want to even think about "parsing" data. It would be so nice if I could have a Java object to work with, and have it saved off as an XML representation to share, or store. This is exactly what XML data-binding can do for us. There are a few frameworks to help us do this, but we will walk through Castor, an open source framework from Exolab. Castor is a data-binding framework, which is a path between Java objects, XML documents, SQL tables, and LDAP directories. Today, we will work with Castor XML, the XML piece of the Castor project

We will discover the different aspects of Castor XML as we develop an Address Book XML document. We will create, read, and modify this XML document via the data-binding framework.

All of the code from this article should work with any JVM supporting the Java 2 platform.

We will walk through the following tasks:

  1. Create/modify/read a simple data-binding (Person representation)
  2. Develop the data-binding for the Address Book

Create/modify/read a simple data-binding

Let's get to work, building a "person" for the Address Book, which will sit between an XML representation and a Java object representation.

  1. Create an XML representation of a person
  2. Create a Java object representing a person
  3. Use Castor XML to read in XML to create a JavaBean
  4. Use Castor XML to create a "person"
  5. Use Castor XML to modify a "person"

Step One: Create an XML representation of a person

First of all, let us generate an XML representation of a person:

<person>
  <name>Michael Owen</name>
  <address>222 Bazza Lane, Liverpool, MN</address>
  <ssn>111-222-3333</ssn>
  <email>michael@owen.com</email>
  <home-phone>720.111.2222</home-phone>
  <work-phone>111.222.3333</work-phone>
</person>

Download the code for this article here. Code includes Person and Addressbook directories, as well as the castor.properties file, and a readme file. For the code to work, you must have either the castor.jar or the castor-xml.jar file.

Step Two: Create a Java object representing a person

Now that we have defined a person in XML, we need to generate a Java object that maps to this definition. To make things easy, we can follow a convention and create get and set methods which match the names of the elements in the XML document. If an element name is name, the corresponding methods in the Java object will be getName() and setName(). When elements have hyphens (such as home-phone), the corresponding method will uppercase the first letter after the hyphen. So, home-phone becomes getHomePhone(). With this in mind, let's look at Person.java:

public class Person {
  private String name;
  private String address;
  private String ssn;
  private String email;
  private String homePhone;
  private String workPhone;

// -- allows us to create a Person via the constructor
public Person(String name, String address, String ssn, 
     String email, String homePhone, String workPhone) {
    this.name = name;
    this.address = address;
    this.ssn = ssn;
    this.email = email;
    this.homePhone = homePhone;
    this.workPhone = workPhone;
  }

// -- used by the data-binding framework
  public Person() { }

// -- accessors
  public String getName() { return name; }

  public String getAddress() { return address; }

  public String getSsn() { return ssn; }

  public String getEmail() { return email; }

  public String getHomePhone() { return homePhone; }

  public String getWorkPhone() { return workPhone; }

// -- mutators
  public void setName(String name) { this.name = name; }

  public void setAddress(String address) {
    this.address = address;
  }

  public void setSsn(String ssn) { this.ssn = ssn; }

  public void setEmail(String email) { this.email = email;  }

  public void setHomePhone(String homePhone) {
    this.homePhone = homePhone;
  }

  public void setWorkPhone(String workPhone) {
    this.workPhone = workPhone;
  }
}

Step Three: Use Castor XML to create a JavaBean

It's pretty simple so far: a simple XML document, a simple JavaBean. Now we will use Castor to read in that XML and return a JavaBean. Here is where we see the power of the framework. We are reading in XML, yet to us, it looks like we are just working with a normal JavaBean.

Here is the code that reads the XML into the JavaBean, and displays information on the person:

import org.exolab.castor.xml.*;
import java.io.FileReader;

public class ReadPerson {
  public static void main(String args[])  {
    try {
     Person person = (Person)  
             Unmarshaller.unmarshal(Person.class,
               new FileReader("person.xml"));
     System.out.println("Person Attributes");
     System.out.println("-----------------");
     System.out.println("Name: " + person.getName() );
     System.out.println("Address: " + person.getAddress() );
     System.out.println("SSN: " + person.getSsn() );
     System.out.println("Email: " + person.getEmail() );
     System.out.println("Home Phone: " + 
                             person.getHomePhone() );
     System.out.println("Work Phone: " +  
                             person.getWorkPhone() );
    } catch (Exception e) {
      System.out.println( e );
    }
  }
}

Related Reading

Java and XML, 2nd Ed.Java and XML, 2nd Ed.
By Brett McLaughlin
Table of Contents
Index
Sample Chapter
Full Description
Read Online -- Safari

The magic is in the Unmarshaller class, and in this example, the static method unmarshal(). You simply give the method the XML document from which to read, and the Java object that will be instantiated. The framework then automatically goes through the XML, uses reflection to look for methods that match the conventions that we mentioned before, and set() the values in the instantiated Java object. Then we can talk to the Person class to call any methods that we wish. Notice that there are no SAX event handlers, and no need to walk the DOM tree. You don't even really know that you are using XML!

Step Four: Use Castor XML to create a person

We have discussed unmarshalling (reading) XML into a Java object. We can also create XML by simply marshalling a Person object out to a file. Ever had to go from a DOM to a file? This is a lot nicer!

Here is the code that will generate a person in the file bob_person.xml:

import org.exolab.castor.xml.*;
import java.io.FileWriter;

public class CreatePerson {
  public static void main(String args[]) {
  try {
// -- create a person to work with
    Person person = new Person("Bob Harris", 
     "123 Foo Street", "222-222-2222", 
      "bob@harris.org", "(123) 123-1234",
      "(123) 123-1234");

// -- marshal the person object out as a <person>
    FileWriter file = new FileWriter("bob_person.xml");
    Marshaller.marshal(person, file);
    file.close();
  } catch (Exception e) {
    System.out.println( e );
  }
  }
}

In this source file we instantiate a person passing in initial values. Then we use the Marshaller class to write out this person as an XML document to the bob_person.xml file.

Indentation of XML files

If you run this program and look at the file, you will see that it looks something like this:

<?xml version="1.0"?>
<person><name>Bob Harris</name><address>123 Foo Street</address><ssn>111-222-3333</ssn><email>bob@harris.org</email><home-phone>(123) 123-1234</home-phone><work-phone>(123) 123-1234</work-phone></person>

This isn't very pretty on the eyes is it? By default, Castor XML does not indent or format the XML representation, as that requires extra resources. For development purposes, you can make the output "pretty" by creating a castor.properties file that has the following property:

org.exolab.castor.indent=true

Step Five: Use Castor XML to modify a person

We now know how to read in a person, and how to create a person. Now we can put these two together to modify an existing person.

Here is the code that unmarshalls person.xml, makes a change to the name, and then marshals the Java object back into an XML file:

import org.exolab.castor.xml.*;
import java.io.FileWriter;
import java.io.FileReader;

public class ModifyPerson {
  public static void main(String args[]) {
  try {
// -- read in the person
    Person person = (Person)
      Unmarshaller.unmarshal(Person.class,
        new FileReader("person.xml"));
// -- change the name
    person.setName("David Beckham");

// -- marshal the changed person back to disk
    FileWriter file = new FileWriter("person.xml");
    Marshaller.marshal(person, file);
    file.close();
  } catch (Exception e) {
    System.out.println( e );
    }
  }
}

Just like that we modify a person, again without having to work with any XML-specific APIs. Now that we have a person, let's move on to setting up an Address Book.

Developing data-binding for Address Book

We define the address book as holding a collection of persons. To build this, we will have to create a mapping for the data-binding framework, and we will see how it will be able to set up a java.util.Collection of Persons. We will also learn about mapping attributes (as in <foo anAttribute="somevalue">).

Here are the steps we will follow:

  1. Create an XML representation of the address book
  2. Create a Java object representing the address book
  3. Create a mapping.xml file to allow Castor XML to marshal and unmarshal
  4. Use Castor XML to display the address book

Step One: Create an XML representation of the address book

Our address book will have an <addressbook> tag wrapping around the <person> elements that we defined earlier:

<addressbook name="Manchester United Address Book">
  <person name="Roy Keane">
    <address>23 Whistlestop Ave</address>
    <ssn>111-222-3333</ssn>
    <email>roykeane@manutd.com</email>
    <home-phone>720.111.2222</home-phone>
    <work-phone>111.222.3333</work-phone>
  </person>
  <person name="Juan Sebastian Veron">
    <address>123 Foobar Lane</address>
    <ssn>222-333-444</ssn>
    <email>juanveron@manutd.com</email>
    <home-phone>720.111.2222</home-phone>
    <work-phone>111.222.3333</work-phone>  
  </person>
</addressbook>

Step Two: Create a Java object representing the address book

Now that we have the address book defined in XML, we need to create the AddressBook object that represents it. We will make the AddressBook hold a java.util.List of Persons:

import java.util.List;
import java.util.ArrayList;

public class Addressbook {
  private String addressBookName;
  private List   persons = new ArrayList();

  public Addressbook() { }

// -- manipulate the List of Person objects
  public void addPerson(Person person) {
    persons.add(person);
  }

  public List getPersons() {
    return persons;
  }

// -- manipulate the name of the address book
  public String getName() {
    return addressBookName;
  }

  public void setName(String name) {
    this.addressBookName = name;
  }
}

Now we have the programming interface that we'll use to work with an address book, and we have defined the XML representation. We need to tie these together, and that is where the mapping file comes in.

Step Three: Create a mapping.xml file to allow Castor XML to marshal and unmarshal

In our simple example of a person, we didn't have to create a mapping file, because Castor could work out what to do based on the JavaBean conventions that we followed.

We have to use the mapping file now, however, as we need to instruct Castor that:

Here is the mapping file (mapping.xml):

<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Object Mapping DTD Version 1.0//EN" "http://castor.exolab.org/mapping.dtd">

<mapping>
<description>A mapping file for our Address Book application</description>

<class name="Person">
  <field name="name" type="string">
    <bind-xml name="name" node="attribute" />
  </field>
  <field name="address" type="string" />
  <field name="ssn" type="string" />
  <field name="email" type="string" />
  <field name="homePhone" type="string" />
  <field name="workPhone" type="string" />
</class>

<class name="Addressbook">
  <field name="name" type="string">
    <bind-xml name="name" node="attribute" />
  </field>
  <field name="persons" type="Person" collection="collection" />
</class>

</mapping>

Looking at the mapping file, you will see that it is built from the point of view of the Java class. We have Person.java and Addressbook.java, and both have a <class> element to describe them.

Each <class> has fields. This is where we tell Castor the name of the fields, their type, if they are an element or an attribute, and if there is one of them or a collection.

The following snippet describes the fact that the given tag has an attribute, of type String, with the name name. From this, Castor knows that the XML document will hold <addressbook name="the value">.

<field name="name" type="string">
    <bind-xml name="name" node="attribute" />
</field>

The next snippet gives the framework the ability to use a collection (in our case a java.util.List) of persons.

<field name="persons" type="Person" collection="collection" />

You can model many things in this mapping file, but let's not digress. For extensive coverage of this mapping file, see the Castor Web site.

Step Four: Use Castor XML to display the address book

To display the address book, we will want to unmarshal addressbook.xml. Unlike the simple person.xml, we will have to load the mapping file.

Here is the code for ViewAddressBook.java:

import org.exolab.castor.xml.*;
import org.exolab.castor.mapping.*;

import java.io.FileReader;
import java.util.List;
import java.util.Iterator;

public class ViewAddressbook {
  public static void main(String args[]) {
    try {
     // -- Load a mapping file
     Mapping mapping = new Mapping();
     mapping.loadMapping("mapping.xml");

     Unmarshaller un = new Unmarshaller(Addressbook.class);
     un.setMapping( mapping );

     // -- Read in the Addressbook using the mapping
     FileReader in = new FileReader("addressbook.xml");
     Addressbook book = (Addressbook) un.unmarshal(in);
     in.close();

     // -- Display the addressbook
     System.out.println( book.getName() );

     List persons = book.getPersons();
     Iterator iter = persons.iterator();

     while ( iter.hasNext() ) {
       Person person = (Person) iter.next();

       System.out.println("\n" + person.getName() );
       System.out.println("-----------------------------");
       System.out.println("Address = "+ person.getAddress());
         System.out.println("SSN = " + person.getSsn() );
       System.out.println("Home Phone = " +  
                             person.getHomePhone() );
       }
    } catch (Exception e) {
      System.out.println( e );
    }
  }
}

The only real difference here is that we are no longer using the static Unmarshaller.unmarshal() method. Now we instantiate an Unmarshaller object, and set the mapping to the newly created Mapping() object. The Mapping() object loads the XML mapping file that we just created:

Mapping mapping = new Mapping();
mapping.loadMapping("mapping.xml");

Unmarshaller un = new Unmarshaller(Addressbook.class);
un.setMapping( mapping );

Conclusion

We have shown that working with XML doesn't mean that you have to delve into the books to learn SAX, DOM, JAXP, and all the other TLAs. Castor's XML data-binding provides a simple but powerful mechanism to work with XML and Java objects.

Dion Almaer is a Principal Technologist for The Middleware Company, and Chief Architect of TheServerSide.Com J2EE Community.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.