XML JavaBeans, Part 3

Integrate the XMLBeans package with the Java core

After a hiatus of three months, this article completes the three-part series on the XMLBeans package. Simply put, XMLBeans can transform a JavaBean in memory into an XML document, or can transform an XML document (of a particular form) into a running JavaBean. Figure 1 shows an extremely simplified schematic of the concept.

Figure 1. The XMLBeans package

If you've experimented with XML and JavaBeans in this series, you'll have noticed that the XMLBeanWriter class we created in Part 2 is capable of creating XMLBeans XML files that the XMLBeanReader from Part 1 can't read. Some readers have also noted that the XMLBeans file format is somewhat complicated, and that controlling which properties are encoded in XML and which properties are ignored requires a lot of tedious coding. This month, we'll solve these problems by restructuring the XMLBeanReader and XMLBeanWriter classes. In the process, we'll simplify the file format while maintaining backward compatibility. Perhaps most interestingly, we'll simplify and generalize controlling XMLBeans properties by integrating XMLBeans with the core java.beans package.

Background reading

This article covers some intermediate-to-advanced JavaBeans topics, so you may need to do some background reading to get up to speed. The links to the articles described here appear in the Resources section.

TEXTBOX:

TEXTBOX_HEAD: XML JavaBeans: Read the whole series!

:END_TEXTBOX

First, I assume you've read and understand the first two articles (Parts 1 and 2) in this series. If you haven't read these articles, this month's article may not make much sense to you.

If you're new to XML, you might consider reading "XML for the absolute beginner," (JavaWorld April 1999). This article discusses JavaBeans property descriptors, property editor classes, and BeanInfo objects, topics previously covered in "The trick to controlling bean customization," and "Soup up your Java classes with customization." Once you have a passing understanding of these subjects, you'll be ready to tackle this final installment of the XML JavaBeans series.

As always, I shortened the code listings in this article by putting the code section under discussion into a scrolling list box. Since some readers dislike this convention, each code listing's caption includes a hyperlink to a printable HTML file. You can pop open a new browser and scroll this complete listing, if you'd like, or you can print the file and follow along on paper.

Improving XMLBeans

I find software to be in some ways like art: in practically every instance, as soon as I've completed a project, I'm aware of my work's weaknesses and how it could have been done better. The code we're going to discuss is a second attempt at creating an interface to read and write JavaBeans objects as XML documents. We won't spend a lot of time discussing the weaknesses of the previous design, but you'll see as we go along that the new implementation is cleaner and more flexible than the original.

This article introduces four major improvements to the XMLBeans package. The first involves general restructuring and simplification of the code, taking advantage of the concept that the value of any JavaBean property may be a primitive value, an object, or another JavaBean. The second improvement is an expansion of the customization of how XMLBeans read and write their properties in XML (or, more accurately, as DOM trees). Third, we give the programmer control over properties' visibility and behavior by integrating the XMLBeans package with the core java.beans package. And finally, we provide an alternate, less restrictive (and less well-defined) format for the XML produced and read by the package.

We'll start by going over the general restructuring improvement.

A value by any other name

The original XMLBeans implementation drew a distinction between JavaBeans properties that are "values" and those that are "beans." Value types were either Java primitive types (boolean, byte, char, short, int, long, float, or double) or any object type that is not primitive but can be constructed from a string (java.net.URL, for example). Bean types were explicitly encoded in XML as <JavaBean CLASS="<i>classname</i>">, and properties whose values were beans were processed differently from those that were not.

After writing the first version of the XMLBeans package, it became clear that there's not much of a difference between a property type that's a primitive value, one that's an object, or one that's a JavaBean. Primitive types require some special treatment, that's true, but the basic process for reading a JavaBean in terms of its properties is simple:

  • Create an instance of the JavaBean. The class name of the JavaBean appears in the Element that represents the bean.

  • For each property Element (an Element representing a property of the bean):
    • Determine the class of the property (by querying the PropertyDescriptor for the property)
    • Create an instance of the property class (we'll call this the property value since it will be the value to which the property is set)
    • Initialize the property-value instance using the contents of the property Element
    • Set the property using the property-value instance and the property-write accessor (the setter)

Likewise, the process for writing a JavaBean is straightforward:

  • For each property in the bean:

    • Get the property value from the bean by calling the property's read accessor (the getter for the property)
    • Convert the property value to a string
    • Add an element that represents the property and contains the string representation of it to the DOM tree you're creating
  • When all properties are encoded, write out the DOM tree as XML

You'll notice that, in the outline above, reference is made to property value without indicating whether the value is a bean, a primitive value, or an instance of some nonbean class. In the source code (which we'll discuss below), you'll see that these cases are handled separately and, in some cases, recursively. But the basic program structure reflects the concept that a property value is simply a property value, and the differences between the various kinds of properties (value, object, or bean) are just programming details.

The second major improvement -- customization -- is next.

Customization

Since XMLBeans is a utility package, meant to be used by a programmer, many of the features in the package are programming facilities, such as hook functions that allow a programmer to customize the package's behavior without doing violence to its operation.

One example of such a hook is the getAsDOM() method. In Part 2 of this series, I introduced a method in the XMLBeanWriter class to control how specific property types and JavaBeans are encoded as a DOM tree. The programmer has the option of specifying a method in a bean class called getAsDOM(). When XMLBeanWriter encounters a JavaBean with the getAsDOM() method, it defers to the bean itself to self-encode as a DOM tree. If a JavaBean class doesn't define a getAsDOM() method, the JavaBean is converted to XML in the standard way: by encoding all of its properties in XML.

The method DocumentFragment getAsDOM(Document d) by definition returns a DOM tree that represents its owner object. The XMLBeanWriter class recognizes this signature both for beans and for property-type classes.

As noted above, if a JavaBean contains a method called getAsDOM() (with the appropriate signature and return type), XMLBeanWriter will call that method and return whatever that method returned as the DOM representation of the object. If getAsDOM() doesn't exist, XMLBeanWriter loops through all of the bean's properties, encoding each property as a DOM tree. Each DOM tree that represents a property is added to the list of properties in the DOM tree representing the bean, until all properties are encoded. In addition to using getAsDOM to encode an entire bean, XMLBeanWriter always looks to see if each property type defines getAsDOM() and, if it does, uses that method to encode the property as a DOM tree.

So, for example, imagine we had a bean class called CustomerAccount, representing the information for a particular customer in an online sales system. The designer of the CustomerAccount class could choose to specify a method called getAsDOM(). When applied to a CustomerAccount object, the XMLBeanWriter will detect and call the getAsDOM() method. getAsDOM() will then return the custom DOM representation of the CustomerAccount JavaBean, instead of looking at all of the CustomerAccount's properties.

Furthermore, imagine that the CustomerAccount has a property called Address, of type CustomerAddress. If the CustomerAccount doesn't have a method getAsDOM(), but the CustomerAddress does, XMLBeanWriter will encode the Address property by calling CustomerAddress.getAsDOM().

Using getAsDOM() in either of these two places gives the bean designer a great deal of control over how the bean should be represented when the bean is converted to XML. In fact, at this point the designer has too much control because the existing version of XMLBeanReader doesn't know how to handle generic XML. As of the end of Part 2 of this series, it was possible to write JavaBeans with XMLBeanWriter that couldn't be read by XMLBeanReader.

Of course, the new version of the XMLBeans package removes this limitation, by allowing specification of a setAsDOM() method to match any getAsDOM() method. Just as XMLBeanWriter may use getAsDOM() to retrieve the DOM representation of a bean (for writing to an XML file), XMLBeanReader uses setAsDOM() to initialize a property value in a bean-object instance from the structure of a DOM subtree.

To provide more flexibility, an XMLBeans programmer may define getDOMSetterName() and/or getDOMGetterName(), which should return the name of a method to use in place of the names getAsDOM() or setAsDOM(). This allows more flexibility in naming, and also allows the object to decide at runtime which method to use for XML translation. This could be useful in a case in which there may be several alternate XML representations of an object, requiring several forms of getAsDOM() and setAsDOM(), and the application needs to be able to choose the translation method pair at runtime.

You may remember from Part 2 that it wasn't easy to prevent XMLBeanWriter from encoding properties. The only way to do so was to write a getAsDOM() method that encoded all properties as a DOM tree, excluding the properties not desired in the output. Though this technique produces the desired result, it requires a lot of unnecessary programming. The third major improvement to the XMLBeans package solves this problem (and others) by integrating XMLBeans with the java.beans package.

XML property descriptors and property editors

Typically, a JavaBean designer controls the visibility of bean properties to external agents by creating a BeanInfo object for the bean. One of the methods of the BeanInfo interface is PropertyDescriptor[] getPropertyDescriptors(), which returns an array of PropertyDescriptor objects, one for each property that the bean defines.

java.beans.Introspector, the class responsible for analyzing JavaBeans, identifies bean properties by first trying to find a BeanInfo object. If it finds one, it calls the BeanInfo's getPropertyDescriptors() method to get the list of properties. Only if this attempt fails does the Introspector itself analyze the class to determine properties.

For example, imagine a JavaBean ThingForSale with three properties: int weight, int age, and int price. A programmer could hide the price attribute by simply defining a getPropertyDescriptors() method in the BeanInfo class for the bean ThingForSaleBeanInfo that returns PropertyDescriptor objects only for age and weight. (For the fine details of how to do this, see my "The trick to controlling bean customization," (JavaWorld November 1997).)

A PropertyDescriptor indicates not only the property, but also the property accessor methods (setter and getter methods). As described above, when analyzing a bean, the Introspector first checks to see if it can get a list of PropertyDescriptors from the BeanInfo. If that fails, the Introspector analyzes the bean class, building a list of methods that match the signatures <i>Type</i> get<i>Property</i>() and <i>void</i> set<i>Property</i>(<i>Type</i> value). From this list of methods, the Introspector then creates its own PropertyDescriptor list.

If you think about it for a moment, it should be pretty clear to you that the getAsDOM() and setAsDOM() in a JavaBean could actually be used as setter and getter methods for bean properties. Instead of expressing the property in terms of its actual property type, though, these two methods get and set the property in terms of a DOM tree. In fact, each bean property could have its own DOM setter and DOM getter -- accessors for that property that set the property value as an Element, and get the property value as a DocumentFragment, respectively. We can even define a naming convention for them, just as we can for normal property accessors:

1 2 3 4 5 Page 1