XML JavaBeans, Part 2

Automatically convert JavaBeans to XML documents

1 2 3 4 Page 2
Page 2 of 4
sFilename. 320 * @param bean The JavaBean to write 321 * @param sFilename The name or path to the file to write. 322 * @exception java.io.FileNotFoundException 323 * @exception java.beans.IntrospectionException 324 */ 325 public static void writeXMLBean(Object bean, String sFilename) throws java.io.IOException, java.beans.IntrospectionException, 326 InstantiationException, IllegalAccessException { 327 writeXMLBean(bean, new FileWriter(sFilename)); 328 }

Figure 1. The writeXMLBean methods

The first version of the method (lines 287 to 290) takes a String filename, then simply creates a File object, and passes it to the third version of the method. The third version (lines 325 to 329) uses its File argument to construct a FileWriter, and passes the result to the second version.

It's the second version of writeXMLBean (lines 302 to 317) that actually does the work. This writeXMLBean class could very easily write its XML to the Writer, keeping track of indentation and so forth as it goes. But think about this for a moment: an XML document in a program can be represented as a tree of Document Object Model (DOM) nodes, right? And we want to flatten that tree of DOM nodes and write it to a text file. Well, why not have one method that actually builds the DOM tree representing the JavaBean, and then another that prints the DOM tree as XML? We could expose methods (which you'll see below) that convert a JavaBean to a tree, and then convert the tree to text, which is then written to a file.

For example, imagine we had a JavaBean that looked like Figure 2.

Figure 2. A JavaBean

The JavaBean of class Grade has two properties: a float called "average", and a JavaBean of class Course called "course". (Remember that the value of a JavaBean property may itself be a JavaBean.) The XMLBeanWriter class will internally build a DOM tree for this JavaBean that will look something like Figure 3. Figure 3 is a DOM tree that represents our JavaBean.

Figure 3. A DOM tree for the JavaBean in Figure 2

The XML corresponding to the DOM tree in Figure 3 appears in Figure 4.

 
<JavaBean CLASS="Grade">
  <Properties>
    <Property NAME="average">97.2</Property>
    <Property NAME="course">
      <JavaBean CLASS="Course">
        <Properties>
      <Property NAME="number">203</Property>
      <Property NAME="description">Basketry</Property>
    </Properties>
     </JavaBean>
    </Property>
  </Properties>
</JavaBean>

Figure 4. The XML representing the DOM tree in Figure 3

There are three excellent reasons for building a tree and then printing the tree, instead of just printing XML as we analyze the JavaBean.

  1. First, if we have a method that converts a JavaBean to a DOM tree, then any other "cut-and-paste" Java program that uses DOM trees can use our class to get a DOM representation of the JavaBean. It makes the XMLBeanWriter class more generally useful.

  2. The second reason to create a DOM tree and then print it is that most DOM implementations include a method that prints the DOM tree as XML, so that part of the work is already done.

  3. The third reason is that creating a DOM tree gives me something to write about, which gives you an opportunity to learn how to manipulate XML documents in Java.

Now that we have an idea of what the code's doing, take a look at the source code itself. The code for all versions of XMLBeanWriter appear in Figure 4 below.

Going back to the third version of writeXMLBean, notice that first we create a TXDocument called doc, like this:

    TXDocument doc = new TXDocument();

A TXDocument is a specific implementation of the interface org.w3c.dom.Document (or, just Document). Document is just an interface defined by the W3C. It has no particular implementation, but it does have defined behavior, which pretty much sums up what an interface is. A TXDocument is a class in IBM's xml4j package that implements the Document interface. It's the root of the DOM document tree. The Document interface (you can read about it at http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#i-Document) defines an API that allows a programmer to add, delete, and iterate on the child elements of the DOM document tree.

After we create the Document that we're going to print, we call the method getAsDOM(), which builds a DOM document tree based on the properties of the JavaBean.

309     DocumentFragment df = getAsDOM(doc, bean);
310     doc.appendChild(df);

There are two interesting things here. The first is that getAsDOM() returns a DocumentFragment, which requires a little explanation. Remember above, where I discussed cutting and pasting pieces of document trees? Well, a DocumentFragment is just what its name implies: it's a little piece of a document that can be added to the list of children of any DOM Node. A DocumentFragment is a lightweight container object that, when added to a DOM Node's children, gives all of its children to that Node, but doesn't appear as a child of the Node itself. So, in the second line above, when the DocumentFragment is appended to the list of the Document's children, the Document doesn't have a child that is a DocumentFragment; rather, it has whatever children the DocumentFragment had. (The DocumentFragment remains unchanged: it doesn't "lose" its children to the Document.) We use DocumentFragment objects extensively in WriteXMLBean.

The second interesting thing about the call to getAsDOM() is that we're passing the Document object to the method. We do this for a very specific reason: the only objects that can be added to a Document object are objects that the Document itself has created. You'll notice that a few lines ago, I created an instance of TXDocument. This is a specific implementation of a DOM object implemented by IBM. The other objects that may appear in a DOM tree -- Comment, Element, Text, and so forth -- must also be created. Instead of peppering new() statements everywhere, each one of which would have to be changed if the classes implementing DOM were changed, Document defines a list of methods that perform the creation of the sub-objects of the tree, on request. So, if you want a Text node object to add to some node of the DOM tree you're building, instead of saying:

    Text tx = new TXText("FiddleFaddle");

you ask the Document to create one for you:

    Text tx = doc.createTextNode("FiddleFaddle");

The other DOM object types are created similarly, and we'll see examples of that below.

The Document interface's create methods are an excellent example of the Factory design pattern, wherein creation of an object is deferred to another object. If we change to some other DOM implementation, only the new(TXDocument) will have to be changed. All of the other objects will be created by the new Document class. If you have trouble remembering which interface creates DOM objects, just remember the little-known rhyme:

Programs are made by fools like me,

But only

org.w3c.dom.Document

can make a tree

(See Resources below for an explanation of this pathetic stab at humor.)

Using this Factory design pattern provides other benefits that are powerful but that are also outside of the scope of this article.

The final line of the writeXMLBean method calls the method I told you about before, in which the TXDocument object writes its tree to the Writer in XML format:

    doc.printWithFormat(writer);

The basic framework is out of the way; now, let's have a look at how getAsDOM() creates a DOM tree.

Anatomy of a bean tree

There are two versions of getAsDOM(), the first of which appears in Figure 5 below:

Figure 5. getAsDOM() converts a bean to a DOM document

The first thing getAsDOM() does, other than get the bean's class for later use, is to check to see if the bean defines a method called DocumentFragment getAsDOM(org.w3c.Document) (lines 42 to 49). If so, it invokes that method on the bean, and sets the resulting DocumentFragment (the subdocument we're building) to whatever that method returns. It does this to allow a developer to override the standard way of representing this object instance as XML. This is a convention that writeXMLBean defines in order to add flexibility.

Essentially, WriteXMLBean.getAsDOM() is "asking" the JavaBean, "Hey, you! Do you know how to represent yourself as a DOM document?" The answer is "yes" if the bean class defines that method. This makes XMLBeanWriter more flexible. For example, if you're writing a JavaBean that you want to turn into XML, but you want some control over how that bean is represented in XML (you don't like how I did it, let's say), you can still use XMLBeanWriter. You simply define your own getAsDOM() method in your JavaBean class, and XMLBeanWriter.getAsDOM() returns whatever DocumentFragment you return. We'll see an example of this again in the section "Representing properties as XML" below.

As an example, I've modified the test class Player.java to define its own getAsDOM(). Let's say the hypothetical baseball player bean (in Player.java) from our example last month tracked a player's grade point average, but for privacy reasons, we want this number excluded from the resulting DOM document. The code that does that appears in Figure 7.

Figure 7. Class Player defines getAsDOM(), leaving out the new property "gradePointAverage"

1 2 3 4 Page 2
Page 2 of 4