IBM Skip to main content
Search for:   within 
      Search help  
     IBM home  |  Products & services  |  Support & downloads   |  My account

developerWorks > XML
developerWorks
XML and Java technologies: Data binding with Castor
Discuss120KBe-mail it!
Contents:
Document models versus data binding
The Castor framework
Default bindings
Changing the XML format
Handling collections
Object references
Working with the data
Conclusions
Resources
About the author
Rate this article
Related content:
XML document model performance in Java
Understanding SAX tutorial
Understanding DOM tutorial
Subscriptions:
dW newsletters
dW Subscription
(CDs and downloads)
A look at XML data binding for Java using the open source Castor project

Level: Intermediate

Dennis M. Sosnoski (mailto:dms@sosnoski.com?cc=&subject=Data binding with Castor)
President, Sosnoski Software Solutions, Inc.
1 April 2002

XML data binding for Java is a powerful alternative to XML document models for applications concerned mainly with the data content of documents. In this article, enterprise Java expert Dennis Sosnoski introduces data binding and discusses what makes it so appealing. He then shows readers how to handle increasingly complex documents using the open source Castor framework for Java data binding. If your application cares more about XML as data than as documents, you'll want to find out about this easy and efficient way of handling XML and Java technologies.

Most approaches to working with XML documents in applications put the emphasis on XML: You work with documents from an XML point of view and program in terms of XML elements, attributes, and character data content. This approach is great if your application is mainly concerned with the XML structure of documents. For many applications that care more about the data contained in documents than the documents themselves, data binding offers a much simpler approach to working with XML.

Document models versus data binding
The document models discussed in previous articles of this series (see Resources) are the closest alternatives to data binding. Both document models and data binding build document representations in memory, with two-way conversions between the internal representation and standard text XML. The difference between the two is that document models preserve the XML structure as closely as possible, while data binding is concerned only with the document data as used by your application.

To illustrate this point, Figure 1 shows the document model view of a simple XML document. The document components -- in this case just elements and text nodes -- are linked in a structure that mirrors the original XML document. It's easy to relate the resulting tree of nodes to the original document, but less easy to interpret the actual data present in the tree.

Figure 1. Document model view of document
Document model view of document

If your application program uses a document model approach for XML, you'll work with this type of tree. In this case, you'll use parent-child relationships between nodes to navigate up and down the tree, and sibling relationships between children of a common parent to navigate across the tree. You'll be able to manipulate the tree structure at a very detailed level, and when you serialize the tree as text, the generated XML document will reflect your changes (such as including comments).

Now contrast Figure 1 with Figure 2, which shows a data binding view of the same document. Here the structure of the original XML document is almost completely hidden by the conversion, but the actual data is much more easily seen and accessed through a pair of objects.

Figure 2. Data binding view of document
Data binding view of document

Working with this data structure is just normal Java programming -- you don't even need to know anything about XML! (Whoops, let's not go too far here -- we expert consultants still need to make a living...) Somebody on your project at least needs to understand how the mapping between the data structure and the XML document is set up, but this is still a big step in the direction of simplicity.

Data binding can provide other benefits beyond justprogramming simplicity. Since it abstracts many of the document details, data binding usually needs less memory than a document model approach. Consider, for instance, the two data structures shown in the earlier figures: The document model approach uses 10 separate objects, as compared to two for data binding. With a lot less to build, it may also be faster to construct the data binding representation for a document. Finally, access to the data within your program can be much faster with the data binding approach than with a document model, since you control how the data is represented and stored. I'll get back to these points later.

If data binding is such great stuff, when would you want to use a document model instead? The two cases that require a document model are:

  • Your application is really concerned with the details of the document structure. If you're writing an XML document editor, for instance, you'll want to stick to a document model rather than using data binding.
  • The documents you're processing don't follow fixed structures. For example, data binding wouldn't be a good approach for implementing a general XML document database.

Many applications use XML for data transfer, but otherwise don't care about the details of the document representation. These applications are ideal candidates for data binding. If your application fits this pattern, read on.

The Castor framework
Currently, a number of different frameworks support XML data binding for Java, but there's no standard interface. This will eventually change: JSR-031 in the Java Community Process (JCP) is working on defining a standard (see Resources). For now, pick one framework and learn to use its interface.

For this article, I've chosen to use the Castor data binding framework. The Castor project uses a BSD-style license, making it available for use in all types of applications (including completely proprietary ones). Castor actually goes well beyond just XML data binding by supporting SQL and LDAP bindings, though I'll ignore these other features for this article. It has been under development since early 2000 and is currently in an advanced beta state (generally usable, but you may need to update to the current CVS version if you need a bug fix). See the Resources section for links to the Castor site to get more details or to download the software.

Default bindings
Getting started with Castor's XML data binding is very simple. You don't even need to define an XML document format. As long as your data is represented in JavaBean-like objects, Castor generates a document format to represent the data automatically and later reconstruct the original data from that document.

Data binding dictionary
Here's a mini-dictionary for some terms I use in this article:

Marshalling is the process of generating an XML representation for an object in memory. Just like with Java serialization, the representation needs to include all dependent objects: objects referenced by our main object, objects referenced by those objects, and so on.

Unmarshalling is the reverse process, building an object (and dependent objects) in memory from an XML representation.

Mapping is the set of rules used for marshalling and unmarshalling. Castor has built-in rules defining the default mapping described in this section of the article. It also allows you to use a separate mapping file, as you'll see in the sections below.

So what does "JavaBean-like" mean? Real JavaBeans are visual components that can be configured within development environments for use in GUI layouts. Some practices that began with real JavaBeans have since become widespread in the Java community, especially for data classes. I call a class "JavaBean-like" if it follows these practices:

  • The class is public
  • It defines a public default (no argument) constructor
  • It defines public getX and setX methods for access to property (data) values

Now that the technical definition is out of the way, I'll skip all this when referring to one of these JavaBean-like classes and just call it a "bean" class.

I'll use an airline flight timetable for the example code throughout this article and start with a simple bean class representing a particular flight to illustrate how this works.This bean includes four items of information:

  • The carrier (airline company) identifier
  • The flight number
  • The departure time
  • The arrival time

Listing 1 below shows the code for handling the flight information.

Listing 1. Flight information bean


public class FlightBean
{
    private String m_carrier;
    private int m_number;
    private String m_departure;
    private String m_arrival;
    
    public FlightBean() {}
    public void setCarrier(String carrier) {
        m_carrier = carrier;
    }
    public String getCarrier() {
        return m_carrier;
    }
    public void setNumber(int number) {
        m_number = number;
    }
    public int getNumber() {
        return m_number;
    }
    public void setDepartureTime(String time) {
        m_departure = time;
    }
    public String getDepartureTime() {
        return m_departure;
    }
    public void setArrivalTime(String time) {
        m_arrival = time;
    }
    public String getArrivalTime() {
        return m_arrival;
    }
}

As you can see, on its own this bean's pretty boring, so I want to add a class and use it in a default XML binding, as shown in Listing 2.

Listing 2. Default data binding test


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

public class Test
{
    public static void main(String[] argv) {
        
        // build a test bean
        FlightBean bean = new FlightBean();
        bean.setCarrier("AR");
        bean.setNumber(426);
        bean.setDepartureTime("6:23a");
        bean.setArrivalTime("8:42a");
        try {
        
            // write it out as XML
            File file = new File("test.xml");
            Writer writer = new FileWriter(file);
            Marshaller.marshal(bean, writer);
            
            // now restore the value and list what we get
            Reader reader = new FileReader(file);
            FlightBean read = (FlightBean)
                Unmarshaller.unmarshal(FlightBean.class, reader);
            System.out.println("Flight " + read.getCarrier() + 
                read.getNumber() + " departing at " + 
                read.getDepartureTime() +
                " and arriving at " + read.getArrivalTime());
                
        } catch (IOException ex) {
            ex.printStackTrace(System.err);
        } catch (MarshalException ex) {
            ex.printStackTrace(System.err);
        } catch (ValidationException ex) {
            ex.printStackTrace(System.err);
        }
    }
}

Beyond beans with Castor
Castor actually works with more than just the JavaBean-like classes discussed in this article. It can also access information from simple data object classes with public member variables. For instance, with some minor changes to the Test class, you could use the following definition for the flight data and still end up with the same XML format:


public class FlightData
{
    public String carrier;
    public int number;
    public String departure;
    public String arrival;
}

A class has to be all one way or the other for Castor to work with it properly. If the class defines any getX or setX methods, Castor treats it as a bean and uses only those methods for marshalling and unmarshalling.

In this code, you first construct a FlightBean bean and initialize it with some canned values. You then write the bean to an output file using Castor's default XML mapping for the bean. Finally, you read the generated XML back in to reconstruct the bean, using the same default mapping, and then print out the information from the reconstructed bean. Here's the result:

Flight AR426 departing at 6:23a and arriving at 8:42a

This output shows that you've successfully round-tripped the flight information (not bad for just two method calls). Now, I'll dig a little deeper than just the console output.

Behind the scenes



To see more of what's going on in this example, take a look at the XML generated by the Marshaller.marshal() call. Here's the document:

<?xml version="1.0"?>
<flight-bean number="426">
    <arrival-time>8:42a</arrival-time>
    <departure-time>6:23a</departure-time>
    <carrier>AR</carrier>
</flight-bean>

Castor examines the object that you pass in the Marshaller.marshal() call using Java introspection. In this case, it finds the four property values you've defined. Castor creates an element in the output XML (the root element of the document) to represent the object as a whole. The element name is derived from the object class name, flight-bean in this case. Castor then includes the property values for this object in one of two ways. It creates::

  • An attribute of the element for each primitive-valued property (in this case only the int-valued number property exposed by the getNumber() method)
  • A child element of the root element for each object-valued property (all the others here, since they're strings).

The result is the XML document shown immediately above.

Changing the XML format
If you don't like Castor's default mapping format, you can easily change the mapping. In the case of our flight information example, for instance, let's suppose we want a more compact representation of the data. Using attributes in place of child elements will help this, and we may even want to use shorter names than the defaults. A document similar to the one shown below would suit our needs nicely:

<?xml version="1.0"?>
<flight carrier="AR" depart="6:23a" arrive="8:42a" number="426"/>

Defining the mapping
To get Castor to use this format rather than the default, you first need to define a mapping describing this format. The mapping description itself is (big surprise) an XML document. Listing 3 shows the mapping to marshal the bean to the format shown before.

Listing 3. Mapping for compact format


<!DOCTYPE databases PUBLIC 
  "-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
  "http://castor.exolab.org/mapping.dtd">
<mapping>
  <description>Basic mapping example</description>
  <class name="FlightBean" auto-complete="true">
    <map-to xml="flight"/>
    <field name="carrier">
      <bind-xml name="carrier" node="attribute"/>
    </field>
    <field name="departureTime">
      <bind-xml name="depart" node="attribute"/>
    </field>
    <field name="arrivalTime">
      <bind-xml name="arrive" node="attribute"/>
    </field>
  </class>
</mapping>

The class element defines mappings for a named class, in this case FlightBean. You can tell Castor to use default mappings for any properties of the class not specifically listed within the body of the element by including the optional auto-complete attribute with a value of true on this element. This is convenient here, since the number property is already being handled in the wanted way.

The child map-to element tells Castor to map instances of the FlightBean class to flight elements in the XML document. This element is optional if you continue with the default element name of flight-bean seen in the example of default mapping output in Behind the scenes.

Finally, you can include a child field element for each property that you want to handle differently from the default. These all follow the same pattern: The name attribute gives the property name, and the child bind-xml element tells Castor how to map that property. In this case, you tell it to map each of the properties to an attribute with the given name.

Using the mapping

Now that you have a defined mapping, you need to tell the Castor framework to use that mapping when marshalling and unmarshalling the data. Listing 4 shows the changes you'll need to make to the earlier code to accomplish this.

Listing 4. Marshall and unmarshall with mapping

        ...
            // write it out as XML (if not already present)
            Mapping map = new Mapping();
            map.loadMapping("mapping.xml");
            File file = new File("test.xml");
            Writer writer = new FileWriter(file);
            Marshaller marshaller = new Marshaller(writer);
            marshaller.setMapping(map);
            marshaller.marshal(bean);
            
            // now restore the value and list what we get
            Reader reader = new FileReader(file);
            Unmarshaller unmarshaller = new Unmarshaller(map);
            FlightBean read = (FlightBean)unmarshaller.unmarshal(reader);
            ...
        } catch (MappingException ex) {
            ex.printStackTrace(System.err);
        ...

This code is a little more complicated than the earlier code using the default mapping shown in Listing 2. Before any other operations, create a Mapping object and load your mapping definition. The actual marshalling and unmarshalling are also different. To use the mapping, you need to create Marshaller and Unmarshaller objects, configure them with the mapping, and call methods on these objects rather than the static methods from the first example. Finally, you have to provide handling for another exception type generated by mapping errors.

With these changes completed, you can try the test program again. The console output is the same as the first example (shown in Listing 2), but now the XML document looks just like you wanted:

<?xml version="1.0"?>
<flight carrier="AR" depart="6:23a" arrive="8:42a" number="426"/>

Handling collections
Now that the individual flight data is in a form you like, you can define a higher level of structure: route data. This structure will include to and from airport identifiers, along with a collection of flights on that route. Listing 5 shows an example of a bean class that holds this information.

Listing 5. Route information bean


import java.util.ArrayList;

public class RouteBean
{
    private String m_from;
    private String m_to;
    private ArrayList m_flights;
    
    public RouteBean() {
        m_flights = new ArrayList();
    }
    public void setFrom(String from) {
        m_from = from;
    }
    public String getFrom() {
        return m_from;
    }
    public void setTo(String to) {
        m_to = to;
    }
    public String getTo() {
        return m_to;
    }
    public ArrayList getFlights() {
        return m_flights;
    }
    public void addFlight(FlightBean flight) {
        m_flights.add(flight);
    }
}

In this code, I've defined an addFlight() method to use for setting the flights on the route one at a time. This is a convenient approach for building the data structures in the test program, but contrary to what you might expect, Castor doesn't use this method for adding flights to the route when unmarshalling. Instead, it uses the getFlights() method to access the collection of flights, then adds directly to the collection.

Handling the collection of flights in the mapping just takes a variation of the field element used in the last example (shown in Listing 3). Listing 6 shows the modified mapping file.

Listing 6. Mapping for route with flight collection


<!DOCTYPE databases PUBLIC 
  "-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
  "http://castor.exolab.org/mapping.dtd">
<mapping>
  <description>Collection mapping example</description>
  <class name="RouteBean">
    <map-to xml="route"/>
    <field name="from">
      <bind-xml name="from" node="attribute"/>
    </field>
    <field name="to">
      <bind-xml name="to" node="attribute"/>
    </field>
    <field name="flights" collection="collection" type="FlightBean">
      <bind-xml name="flight"/>
    </field>
  </class>
  <class name="FlightBean" auto-complete="true">
    <field name="carrier">
      <bind-xml name="carrier" node="attribute"/>
    </field>
    <field name="departureTime">
      <bind-xml name="depart" node="attribute"/>
    </field>
    <field name="arrivalTime">
      <bind-xml name="arrive" node="attribute"/>
    </field>
  </class>
</mapping>

Everything's pretty much the same as the last mapping (shown in Listing 3), except for the field element defining the flights property of a RouteBean. This mapping uses a pair of attributes not needed before. The collection attribute with the value collection defines this property as a java.util.Collection (other values define arrays, java.util.Vectors, and so on). The type property defines the type of objects included in the collection, with a fully qualified classname as the value. Here the value is just FlightBean, since I didn't use a package for the classes.

The other difference is that I no longer need to use a map-to child element within the FlightBean class element to define the element name for the binding. The field element defining the flights property of the RouteBean does that through its child bind-xml element. Since the only way to marshal or unmarshal FlightBean objects is through this property, they'll always use the name set by this bind-xml element.

I won't bother to show the test program for this example, since the data binding part is the same as the last example. Here's what the generated XML document looks like for some sample data:

<?xml version="1.0"?>
<route from="SEA" to="LAX">
    <flight carrier="AR" depart="6:23a" arrive="8:42a" 
    number="426"/>
    <flight carrier="CA" depart="8:10a" arrive="10:52a" 
    number="833"/>
    <flight carrier="AR" depart="9:00a" arrive="11:36a" 
    number="433"/>
</route>

Object references
Now you're finally ready to tackle the full flight timetable. For this you'll add three more beans to the set:

  • AirportBean for airport information
  • CarrierBean for airline information
  • TimeTableBean to fit everything together

To keep it interesting, you'll also add some linkages between beans, besides the ownership relationship between RouteBean and FlightBean used in the last example (shown in Handling collections).

Linking the beans
For the first added relationship, change FlightBean to reference the carrier information directly instead of just using a code to identify the carrier. Here are the changes to FlightBean:

public class FlightBean
{
    private CarrierBean m_carrier;
    ...
    public void setCarrier(CarrierBean carrier) {
        m_carrier = carrier;
    }
    public CarrierBean getCarrier() {
        return m_carrier;
    }
    ...
}

Now, do the same thing for RouteBean referencing the airport information:

public class RouteBean
{
    private AirportBean m_from;
    private AirportBean m_to;
    ...
    public void setFrom(AirportBean from) {
        m_from = from;
    }
    public AirportBean getFrom() {
        return m_from;
    }
    public void setTo(AirportBean to) {
        m_to = to;
    }
    public AirportBean getTo() {
        return m_to;
    }
    ...
}

I won't include the code for the added beans themselves, since they don't show anything beyond what was done previously. You can download the full code for all the examples in the code.jar download file (see Resources).

Mapping references
You'll need to use some other features of the mapping document to support references between the objects that you are marshalling and unmarshalling. Listing 7 shows the complete mapping:

Listing 7. Mapping for full timetable


<!DOCTYPE databases PUBLIC 
  "-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
  "http://castor.exolab.org/mapping.dtd">
<mapping>
  <description>Reference mapping example</description>
  <class name="TimeTableBean">
    <map-to xml="timetable"/>
    <field name="carriers" type="CarrierBean" collection="collection">
      <bind-xml name="carrier"/>
    </field>
    <field name="airports" type="AirportBean" collection="collection">
      <bind-xml name="airport"/>
    </field>
    <field name="routes" type="RouteBean" collection="collection">
      <bind-xml name="route"/>
    </field>
  </class>
  <class name="CarrierBean" identity="ident" auto-complete="true">
    <field name="ident">
      <bind-xml name="ident" node="attribute"/>
    </field>
  </class>
  <class name="AirportBean" identity="ident" auto-complete="true">
    <field name="ident">
      <bind-xml name="ident" node="attribute"/>
    </field>
  </class>
  <class name="RouteBean">
    <field name="from" type="AirportBean">
      <bind-xml name="from" node="attribute" reference="true"/>
    </field>
    <field name="to" type="AirportBean">
      <bind-xml name="to" node="attribute" reference="true"/>
    </field>
    <field name="flights" type="FlightBean" collection="collection">
      <bind-xml name="flight"/>
    </field>
  </class>
  <class name="FlightBean" auto-complete="true">
    <field name="carrier">
      <bind-xml name="carrier" node="attribute" reference="true"/>
    </field>
    <field name="departureTime">
      <bind-xml name="depart" node="attribute"/>
    </field>
    <field name="arrivalTime">
      <bind-xml name="arrive" node="attribute"/>
    </field>
  </class>
</mapping>

Aside from the added beans, the important change here is the addition of identity and reference attributes. The identity attribute of a class element tells Castor that the named property is a unique identifier for an instance of that class. Here I have both CarrierBean and AirportBean define their ident properties as identifiers.

The reference attribute of a bind-xml element provides the other part of the linkage information Castor needs for the mapping. A mapping with reference set to true tells Castor to marshal the identifier for a referenced object, rather than a copy of the object itself. I've used this for the references from RouteBean to the linked AirportBeans for the two ends of the route, and from FlightBean to the linked CarrierBean.

When Castor unmarshals data using a mapping of this type, it automatically converts object identifiers into references to the actual objects. You need to make sure that the identifier values are truly unique, even among objects of different types. With the data in this example, that's not a problem: Carrier identifiers are two characters, and airport identifiers are three characters, so they can never be the same. You can easily avoid problems with this in cases where you do have the potential for conflicts by prefixing each identifier with a unique code for the type of object it represents.

The marshalled timetable
This example includes nothing new in the test code, except more setup of the sample data. Listing 8 shows the XML document generated by the marshalling:

Listing 8. The marshalled timetable


<?xml version="1.0"?>
<timetable>
    <carrier ident="AR" rating="9">
        <URL>http://www.arcticairlines.com</URL>
        <name>Arctic Airlines</name>
    </carrier>
    <carrier ident="CA" rating="7">
        <URL>http://www.combinedlines.com</URL>
        <name>Combined Airlines</name>
    </carrier>
    <airport ident="SEA">
        <location>Seattle, WA</location>
        <name>Seattle-Tacoma International Airport</name>
    </airport>
    <airport ident="LAX">
        <location>Los Angeles, CA</location>
        <name>Los Angeles International Airport</name>
    </airport>
    <route from="SEA" to="LAX">
        <flight carrier="AR" depart="6:23a" arrive="8:42a" number="426"/>
        <flight carrier="CA" depart="8:10a" arrive="10:52a" number="833"/>
        <flight carrier="AR" depart="9:00a" arrive="11:36a" number="433"/>
    </route>
    <route from="LAX" to="SEA">
        <flight carrier="CA" depart="7:45a" arrive="10:20a" number="311"/>
        <flight carrier="AR" depart="9:27a" arrive="12:04p" number="593"/>
        <flight carrier="AR" depart="12:30p" arrive="3:07p" number="102"/>
    </route>
</timetable>

Working with the data
Now that all the data for the timetable is finally set up, take a quick look at how you can manipulate it in a program. Using data binding, you've constructed a data structure for the timetable made up of several types of beans. The application code that works with the data can use these beans directly.

Suppose, for example, you want to find round-trip flight choices between Seattle and Los Angeles, but only for carriers with a specified minimum quality rating. Listing 9 shows the basic code to get this information using the data-binding bean structure (see the source download in Resources for full details).

Listing 9. Flight finder code


private static void listFlights(TimeTableBean top, String from,
        String to, int rating) {
        
        // find the routes for outbound and inbound flights
        Iterator r_iter = top.getRoutes().iterator();
        RouteBean in = null;
        RouteBean out = null;
        while (r_iter.hasNext()) {
            RouteBean route = (RouteBean)r_iter.next();
            if (route.getFrom().getIdent().equals(from) &&
                route.getTo().getIdent().equals(to)) {
                out = route;
            } else if (route.getFrom().getIdent().equals(to) &&
                route.getTo().getIdent().equals(from)) {
                in = route;
            }
        }
        
        // make sure we found the routes
        if (in != null && out != null) {
            
            // find outbound flights meeting carrier rating requirement
            Iterator o_iter = out.getFlights().iterator();
            while (o_iter.hasNext()) {
                FlightBean o_flight = (FlightBean)o_iter.next();
                if (o_flight.getCarrier().getRating() >= rating) {
                    
                    // find inbound flights meeting carrier rating 
                    //  requirement, and leaving after outbound arrives
                    int time = timeToMinute(o_flight.getArrivalTime());
                    Iterator i_iter = in.getFlights().iterator();
                    while (i_iter.hasNext()) {
                        FlightBean i_flight = (FlightBean)i_iter.next();
                        if (i_flight.getCarrier().getRating() >= rating 
          &&
                            timeToMinute(i_flight.getDepartureTime()) 
         > time) {
                            
                            // list the flight combination
                            printFlights(o_flight, i_flight, from, to);
                        }
                    }
                }
            }
        }
    }

You can try this out using the sample data shown earlier in Listing 8. If you ask for flights from Seattle (SEA) to Los Angeles (LAX) with carriers ranked 8 or higher, the results are:

Leave SEA on Arctic Airlines 426 at 6:23a
 return from LAX on Arctic Airlines 593 at 9:27a
Leave SEA on Arctic Airlines 426 at 6:23a
 return from LAX on Arctic Airlines 102 at 12:30p
Leave SEA on Arctic Airlines 433 at 9:00a
 return from LAX on Arctic Airlines 102 at 12:30p

Document model comparison
I won't try going through the equivalent code using one of the XML document models here; that is complicated enough to warrant a separate article. The simplest way of approaching the problem is probably to parse through the carrier elements first and build a map linking each identifier code to the corresponding element. Then, use logic similar to the example code in Listing 9. Each step is more complicated than the bean example, because the code works with XML components rather than the actual data values. Performance might also be much worse -- not an issue if you're just doing a few operations with the data, but a major concern if it's at the core of your application.

The difference (in terms of both code complexity and performance) is even greater if you use more data type conversions in the mapping between beans and XML. For instance, if you work with the flight times a lot, you'd probably want to convert the text times to a better internal representation (such as minute number in the day as shown in Listing 9). You could either define alternate get and set methods for text versus internal forms (setting the mapping to use only the text form) or define a custom org.exolab.castor.mapping.FieldHandler implementation for Castor to use with these values. Keeping the time values in an internal form allows you to skip the conversion when you try to match up flights in Listing 9 and makes the processing even faster.

Castor provides a lot of hooks for customization, beyond what I've discussed in this article: Special FieldHandlers are just one example. Ideally, the sample code and discussion have given you a feeling for the power and flexibility of the framework. I encourage you to experiment further with Castor on your own. I think you'll find Castor as useful (and as much fun) as I have.

Conclusions
Data binding is a great alternative to document models in applications that use XML for data exchange. It simplifies your programming because you no longer need to think in terms of XML. Instead, you can work directly with objects that represent the meaning of the data as used by your application. It also offers the potential for better memory and processor performance than document models.

I've gone through a series of increasingly complex examples of data binding using the Castor framework in this article. All these examples use what I call direct data binding: The developer defines the classes based on the data, then maps the data to an XML document structure. I'll explore another approach in the next article: schema data binding, which takes a document schema (such as DTD, XML Schema, or another flavor) and generates code corresponding to that schema.

Castor supports the schema approach as well as the direct binding you've seen in this article, so you'll see more of Castor in the follow up. I'll also look at the progress on JSR-031 for a Java Data Binding standard and compare the performance between approaches. Watch this space for more on XML data binding in Java, coming soon to an IBM developerWorks near you.

Resources

About the author
Dennis M. Sosnoski Dennis Sosnoski (dms@sosnoski.com) is the founder and lead consultant of Seattle-area Java consulting company Sosnoski Software Solutions, Inc. His professional software development experience spans over 30 years, with the last several years focused on server-side Java technologies including servlets, Enterprise JavaBeans, and XML. He's a frequent speaker on XML in Java and J2EE technologies at conferences nationwide, and chairs the Seattle Java-XML SIG.


Discuss120KBe-mail it!

What do you think of this document?
Killer! (5) Good stuff (4) So-so; not bad (3) Needs work (2) Lame! (1)

Send us your comments or click Discuss to share your comments with others.



developerWorks > XML
developerWorks
  About IBM  |  Privacy  |  Terms of use  |  Contact