JavaBeans

JavaBeans are the simpler of the two types and an excellent starting point for our foray into persistence via XML.

Note

JavaBeans and Enterprise JavaBeans are complex enough topics without involving XML. This chapter should not be considered an in-depth discussion of either topic but rather a simple introduction to the important features that facilitate persisting and restoring a bean of either type using XML.


JavaBeans Architecture

Just what is a JavaBean? A JavaBean is a Java class that exposes properties, methods, and events in a certain predefined fashion. JavaBeans can be either visual, in which case they must extend one of the AWT classes such as Canvas, or simple classes that follow a set of conventions defining what methods must exist. The only feature of JavaBeans we are really interested in are those responsible for setting and retrieving property values. Again, refer to one of the resource texts for a complete description of JavaBeans.

There are five method types defined by the JavaBeans specification:

  • Simple— Any property that is a single instance. Can be of any primitive type as well as most provided and user-defined classes.

  • Boolean— Similar to Simple but only supports Boolean values.

  • Indexed— Any property that has multiple instances indexed via some index. For example, an array of integer is an Index property.

  • Bound— Bound properties can be any of the three previous types and have similar get and set methods but are bound to zero or more event listeners that are informed whenever a value changes.

  • Constrained— Similar to Bound properties in that the value may be Simple, Boolean, or Indexed, but event listeners may optionally veto any change, causing the property to revert back to its previous value.

JavaBeans expose their properties through one of these five method signature patterns. The five pattern types are described in Table 8.1.

Table 8.1. JavaBean Method Signature Patterns
Property Type Signature
Simple public void setPropertyName(PropertyType newValue) public PropertyType getPropertyName()
Boolean public boolean isPropertyName() public void setPropertyName( boolean newValue)
Indexed public void setPropertyName(IndexType index, PropertyType newValue); public PropertyType getPropertyName(IndexType index)
Bound Any of the prior types set/get methods plus public void addPropertyChangeListener(PropertyChangeListener listener) public void removePropertyChangeListener (PropertyChangeListener listener)
Constrained Any of the first three types set/get methods plus public void addPropertyChangeListener(PropertyChangeListener listener) public void removePropertyChangeListener (PropertyChangeListener listener)

Listing 8.1 shows an incredibly simple bean that has three simple properties. get methods, such as on line 12, allow values to be inserted into the bean. set methods, such as line 16, allow values to be returned back out of the bean. The purpose of this bean is purely to exercise several set and get methods on string float and integer properties.

Code Listing 8.1. SimpleBean.java—A JavaBean with three properties
 1: public class SimpleBean implements java.io.Serializable {
 2:
 3:     protected String stringProperty;
 4:     protected int    intProperty;
 5:     protected float  floatProperty;
 6:     public SimpleBean()
 7:     {
 8:         stringProperty="";
 9:         intProperty=0;
10:         floatProperty = 0.0f;
11:     }
12:     public String getStringProperty()
13:     {
14:         return stringProperty;
15:     }
16:     public void setStringProperty(String newValue)
17:     {
18:         stringProperty = newValue;
19:     }
20:     public int getIntProperty()
21:     {
22:         return intProperty;
23:     }
24:     public void setIntProperty(int newValue)
25:     {
26:         intProperty = newValue;
27:     }
28:
29:     public float getFloatProperty()
30:     {
31:         return floatProperty;
32:     }
33:     public void setFloatProperty(float newValue)
34:     {
35:         floatProperty = newValue;
36:     }
37:
38:     public String toString()
39:     {
40:         return new String("stringProperty:" + stringProperty +
41:                           " intProperty:" + intProperty +
42:                           " floatProperty:" + floatProperty);
43:     }
44:     public void print()
45:     {
46:         System.out.println(this.toString());
47:     }
48: }

A DTD for JavaBeans

Although we could just jump in and start coding, it's best to understand the problem at hand. We need a document type definition for our JavaBean; we can determine what needs to be in our DTD by examining a JavaBean at a conceptual level and seeing what it's made of. First off, we need to know what class maps to our bean. We can start our list of items with the bean's class name. Each bean must have a set of properties that describe its content. So we need some sort of hierarchy of properties. For the set of properties, we need a value for each. One interesting point to consider is whether our JavaBean can contain non-primitive properties. That is, can we have JavaBeans within JavaBeans?

With the parts of a JavaBean in mind, we can start thinking about how to translate these parts into an appropriate DTD. Listing 8.2 shows a simple DTD that defines our JavaBean.

Code Listing 8.2. DTD for a JavaBean
 1: <!DOCTYPE JavaBean [
 2: <!ELEMENT JavaBean (Properties)>
 3: <!ATTLIST JavaBean ClassName CDATA #REQUIRED>
 4: <!ELEMENT Properties (Property*)>
 5: <!ELEMENT Property (#PCDATA | JavaBean)*>
 6: <!ATTLIST Property Name CDATA #REQUIRED >
 7: ]>

We won't go through an actual description of the syntax of this DTD (see Chapter 2, "Parsing XML," for that), but we should examine the construct itself.

We defined a special document type, JavaBean, for our bean. In reality we didn't need to do this; we could have used any DTD and determined the JavaBean name from the root element of the XML document. This method was chosen because it's somewhat clearer. The JavaBean root element contains a single attribute—the ClassName of the bean; again this was done for clarity, especially when dealing with beans that are part of packages. JavaBeans contain a number of properties, so it makes sense to define a Properties (note the plural) element that contains a number of Property elements. Again, each Property element contains a single attribute, Name, that specifies the name the Property applies to. We could have used the Element name as the Property name, but using an Attribute seemed clearer. Interestingly enough, Property elements can contain data; primitive values such as long, float, and so on; or embedded JavaBean elements!

When we put all these elements together, we define a simple DTD that serves our purposes well. Listing 8.3 shows the XML that represents a set of values for SimpleBean.java (refer to Listing 8.1). Let's move on now and examine how we can actually restore the state of a JavaBean using XML and our DTD.

Code Listing 8.3. SimpleBean.xml—XML Representing the State of SimpleBean
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE JavaBean [
<!ELEMENT JavaBean (Properties)>
<!ATTLIST JavaBean ClassName CDATA #REQUIRED>
<!ELEMENT Properties (Property*)>
<!ELEMENT Property (#PCDATA | JavaBean)*>
<!ATTLIST Property Name CDATA #REQUIRED >
]>

<JavaBean ClassName="SimpleBean">
  <Properties>
    <Property Name="stringProperty">This is a string!</Property>
    <Property Name="intProperty">1234</Property>
    <Property Name="floatProperty">4321.0</Property>
  </Properties>
</JavaBean>

XML to JavaBeans

To understand how to restore a bean's content from XML, we need to understand a little more about Java Reflection and Introspection. These two Java packages allow developers to interrogate a Java class to determine information about it—such as what fields the class contains, attributes of those fields, what methods the class contains, what their method signatures are, and a slew of other information. In addition to examining a class's contents we can, using Reflection, generate parameter lists and actually call methods on-the-fly. Before I developed the code to actually restore and persist JavaBeans to XML, I developed DumpBean.java, which exercises a number of the interfaces required to get information about a class. In fact, DumpBean will dump information about any class. Listing 8.4 shows the code for DumpBean.java. Deep analysis of this code is left to the reader. All the method calls are well documented in the Java online documentation for the Class object. Simply put, Listing 8.4 takes a Java class as input; creates a Class object representing the methods, interfaces, variables, and so on of that class, and then, using Reflection, lists the methods, variables, interfaces, and other characteristics of the class.

Code Listing 8.4. DumpBean.java—Dump a Classes Characteristics
  1: package sams.chp8;
  2: import java.lang.reflect.*;
  3: import java.beans.*;
  4:
  5: //
  6: // a simple class that dumps information about another class
  7: //
  8:
  9: public class DumpBean
 10: {
 11:
 12:     public static Object DumpObject(String className)
 13: s InstantiationException,IllegalAccessException
 14:     {
 15:         Class theClass = null;
 16:         try
 17:         {
 18:             theClass = Class.forName(className);
 19:             System.out.println("Information about:" +
 20:                   theClass.getName());
 21:             System.out.println("---
");
 22:
 23:             Package pkg  = theClass.getPackage();
 24:             System.out.println(" Package ");
 25:             if (null == pkg )
 26:                 System.out.println(
 27:                   package:not part of a package(default)");
 28:             else
 29:                 System.out.println(
 30:                   package:" + pkg.toString());
 31:             System.out.println("---
");
 32:
 33: 
 34:             Class[] interfaces = theClass.getInterfaces();
 35:             System.out.println(
 36:                    "Interfaces(" + interfaces.length + ")");
 37:             for (int i = 0; i < interfaces.length; i++)
 38:             {
 39:                 Class anInterface = interfaces[i];
 40:                 System.out.println("    interface["+i+"]" +
 41:                                    anInterface.toString());
 42:             }
 43:             System.out.println("---
");
 44:
 45:             Constructor[] ctors = theClass.getConstructors();
 46:             System.out.println(" Ctors(" + ctors.length + ")");
 47:             for (int i = 0; i < ctors.length; i++)
 48:             {
 49:                 Constructor ctor = ctors[i];
 50:                 System.out.println("    ctor["+i+"]" +
 51: toString());
 52:             }
 53:             System.out.println("---
");
 54:
 55:             Field[] fields = theClass.getDeclaredFields();
 56:             System.out.println(" Fields(" + fields.length + ")");
 57:             for (int i = 0; i < fields.length; i++)
 58:             {
 59:                 Field field = fields[i];
 60:                 System.out.println("    Field["+i+"]" +
 61:                               field.toString());
 62:             }
 63:             System.out.println("---
");
 64:
 65:
 66:             Method[] methods = theClass.getDeclaredMethods();
 67:
 68:             System.out.println(" Methods(" + methods.length + ")");
 69:             for (int i = 0; i < methods.length; i++)
 70:             {
 71:                 Class returnType = methods[i].getReturnType();
 72:                 Class[] parameterTypes =
 73:                               methods[i].getParameterTypes();
 74:                 System.out.println("    Method["+i+"]" +
 75:                               methods[i].getName());
 76:                 System.out.println("    " +
 77:                               methods[i].toString());
 78:                 System.out.println("      returns:"+
 79:                               parameterTypes.toString());
 80:                 System.out.println(
 81:                   "takes "+parameterTypes.length + " parameters");
 82:                 for (int j =0; j < parameterTypes.length;j++)
 83:                 {
 84:                     System.out.println("       Parameter[" + j +
 85:                               "]"+ parameterTypes[j].toString()
 86:                     + (parameterTypes[j].isPrimitive()?"
 87:                      "Primitive":" not Primitive")
 88:                     );
 89:                 }
 90:             }
 91:
 92:             System.out.println("

");
 93: 
 94:         }
 95:         catch (SecurityException e)
 96:         {
 97:             System.out.println(e);
 98:         }
 99:         catch (ClassNotFoundException e)
100:         {
101:             System.out.println("Class:"+className +
102:                                " not found!");
103:             System.out.println(e);
104:         }
105:
106:         return theClass!=null? theClass.newInstance():null;
107:
108:     }
109:
110:     public static void main (String argv [])
111:     {
112:         if (argv.length == 0)
113:         {
114: 
115:             System.err.println(
116:               "Usage: java sams.chp9.DumpBean someClass" );
117:             System.exit(1);
118:         }
119:
120:         try
121:         {
122:             Object bean = DumpObject(argv[0]);
123:             if ( null == bean)
124:             {
125:             System.out.println("Failed to open class from " + argv[0]);
126:             System.exit(1);
127:             }
128:         }
129:         catch (Exception e)
130:         {
131:             System.out.println(e);
132:
133:         }
134:     }
135: }

With the rudiments of Reflection and the Class object in mind, we can now detail the actual process of restoring a bean from an XML document. Listing 8.6 is an abbreviated version of XMLBean.java with the persist to XML portion removed. We will examine persisting to XML in the next section.

Restoring a bean's content from XML requires several steps:

1.
Open the document and find the root node.

2.
Find the Properties node and iterate over Property elements.

3.
For each Property element find the set method.

4.
Generate appropriate arguments to the set method.

5.
Using the arguments from step 4, store into the JavaBean the value from the Property element.

That sounds simple enough. Let's examine the code that performs this magic. Listing 8.5, XMLRead.java, is fairly self-explanatory: it takes an XML file that contains an XML JavaBean and uses XMLBean.newBeanInstance() to create the in-memory representation of the bean. All the real work happens in Listing 8.6, XMLBean.java.

Code Listing 8.5. XMLRead.java—Driver Application for Testing Creating XML Beans
 1: package sams.chp8;
 2: import java.lang.reflect.*;
 3: class XMLRead
 4: {
 5:     //
 6:     // Simple main for testing...
 7:     //
 8:     public static void main (String argv [])
 9:     {
10:         if (argv.length == 0)
11:         {
12:             System.err.println(
13:             "Usage: java sams.chp8.TestXMLRead somebean.xml" );
14:              System.exit(1);
15:
16:         }
17:         try
18:         {
19:             Object bean = XMLBean.newBeanInstance(argv[0]);
20:             if ( null == bean)
21:             {
22:                 System.out.println("Failed to create bean from "
23:                 + argv[0]);   
24:                 System.exit(1);
25:             }
26:              Method toString = bean.getClass().
27:                                    getMethod("toString", null);
28:              System.out.println("Java Bean type " +
29:                                     bean.getClass().getName());
30:              String result = (String)toString.invoke(bean, null);
31:             System.out.println("toString:"+ result);
32:         }
33:         catch (Exception e)
34:         {
35:             System.out.println(e);
36:         }
37:     }
38: }

Code Listing 8.6. XMLBean.java—Restoring from XML
  1: package sams.chp8;
  2: import java.lang.reflect.*;
  3: import java.beans.*;
  4: import java.io.*;
  5: import sams.chp7.*; // For XMLUtil.openDocument
  6: import org.w3c.dom.*;
  7:
  8: public class XMLBean
  9: {
 10:
 11:     //
 12:     // Convenience method to create a new Object that
 13:     // can be used as an argument to the setter method
 14:     //
 15:     static Object buildSimpleArgument(Class type, String value)
 16:     {
 17:         Object argument = null;
 18:         if (type == char.class)
 19:         {
 20:             char c = (char) (Integer.decode(value).intValue());
 21:             argument = new Character(c);
 22:         }
 23:         else if ( type == java.lang.String.class)
 24:         {
 25:             argument = value;
 26:         }
 27:         else if (type == boolean.class)
 28:             argument = new Boolean(value);
 29:         else if (type == byte.class)
 30:              argument = new Byte(value);
 31:         else if (type == int.class)
 32:             argument = new Integer(value);
 33:         else if (type == long.class)
 34:             argument = new Long(value);
 35:         else if (type == short.class)
 36:             argument = new Short(value);
 37:         else if (type == float.class)
 38:             argument = new Float(value);
 39:         else if (type == double.class)
 40:             argument = new Double(value);
 41:         else
 42:             return null;
 43:        return argument;
 44:     }
 45: 
 46:     //
 47:     // load a property
 48:     // This method does the bulk of the work
 49:     // of loading a JavaBean property
 50:     //
 51:     static void loadProperty(Node aProperty,Object object)
 52:                 throws NoSuchMethodException,
 53:                        IllFormedBeanException
 54:     {
 55:         if ( null == aProperty)
 56:         {
 57:             return; // something strange happened!
 58:         }
 59:         NamedNodeMap attributes = aProperty.getAttributes();
 60:         if ( null == attributes || attributes.getLength() == 0)
 61:         {
 62:             throw new IllFormedBeanException(
 63:                          "Cannot Name attributes for "
 64:                           + aProperty.getNodeName()
 65:                           + " in class "
 66:                           + object.getClass().getName());
 67:         }
 68:         String propertyName = null;
 69:         for ( int i = 0; i < attributes.getLength();i++ )
 70:         {
 71:             Node attribute = attributes.item(i);
 72:             if (attribute.getNodeName().equals("Name"))
 73:             {
 74:                 propertyName = attribute.getNodeValue();
 75:                 break;
 76:             }
 77:         }
 78:         if ( null == propertyName)
 79:         {
 80:             throw new IllFormedBeanException(
 81:                      "Cannot find Name attribute for "
 82:                      + propertyName + " in class "
 83:                      + object.getClass().getName());
 84:         }
 85: 
 86:         //
 87:         // get the class and find the method that
 88:         // has the same name
 89:         //
 90:         // We use property descriptors to find setter
 91:         // method and then use that method to
 92:         // store the value into the object
 93:         // We could use a hashmap to store all the
 94:         // desciptors and then access them
 95:         // by hash but this method, while slower,
 96:         // shows more what is going on with
 97:         // the property descriptors.
 98:         try
 99:         {
100:             Class    theClass = object.getClass();
101:             PropertyDescriptor propDescs[] =
102:               Introspector.getBeanInfo(theClass).
103:                     getPropertyDescriptors();
104:             PropertyDescriptor pd = null;
105:             Method setter = null;
106:             Class argumentType = null;
107:
108:             for (int i = 0;
109:                  setter == null && i < propDescs.length;
110:                  i++)
111:             {
112:                 if (propDescs[i].getName().equals(propertyName))
113:                 {
114:                     pd = propDescs[i];
115:                     setter = pd.getWriteMethod();
116:                     argumentType = pd.getPropertyType();
117:                 }
118:             }
119:             if ( null == setter )
120:             {
121:                 throw new IllFormedBeanException(
122:                      "Cannot find setter method for "
123:                      + propertyName + " in class "
124:                      + theClass.getName());
125:             }
126:             else if ( null == argumentType  )
127:             {
128:                 throw new IllFormedBeanException(
129:                      "Cannot determine argument type for "
130:                      + propertyName + " in class "
131:                      + theClass.getName());
132:             }
133:             //
134:             // Ok, one more step before we set the value.
135:             // We need to get the value of the property as
136:             // an argument that can be passed to
137:             // the setter method
138:             // there are two possiblities here:
139:             //  a: The object is a primitive type or string
140:             //     use buildSimpleArgument to create the
141:             //  b: the object is an embedded bean
142:             //     instantiate it and use the result
143:             // call the setter.
144:             //
145: 
146:             Object[] args = { null };
147:             NodeList propertyChildren =
148:                   aProperty.getChildNodes();
149:
150:             //
151:             // Is the argument a primitive type?
152:             // That is long, float, doublel etc?
153:             // Then look for the text node that represents it
154:             // and use buildSimpleArgument to
155:             // construct an appropriate object
156:             // to represent that value
157:             //
158:             if (argumentType.isPrimitive() ||
159:                 argumentType == java.lang.String.class)
160:             {
161:                 String value = null;
162:                 for (int i = 0;
163:                      i < propertyChildren.getLength();
164:                      i++)
165:                 {
166:                     Node aNode = propertyChildren.item(i);
167:                     if (aNode instanceof Text)
168:                     {
169:                         value = aNode.getNodeValue();
170:                         break;
171:                     }
172:                 }
173:                 if ( null == value)
174:                 {
175:                     throw new IllFormedBeanException(
176:                              "Value not found in XML Bean for"
177:                               + propertyName + " in class "
178:                               + theClass.getName());
179:                 }
180:                 args[0] = buildSimpleArgument(argumentType,
181:                                               value);
182:             }
183:             //
184:             // If it is not a primitive type
185:             //
186:             else
187:             {
188:                 //
189:                 // look for a child that says it is a javabean
190:                 //
191:                 for (int i = 0; i < propertyChildren.getLength(); i++)
192:                 {
193:                     Node aNode = propertyChildren.item(i);
194:                     if ( aNode.getNodeName().equals("JavaBean") )
195:                     {
196:                         args[0] = newBeanInstance(aNode);
197:                     }
198:                 }
199:             }
200:
201:             //
202:             // At this point if args[0] is still null
203:             // we were not able to build the argument list
204:             // throw an exception
205:             //
206:             if (null == args[0])
207:             {
208:                 throw new IllFormedBeanException(
209:                 "Cannot determine parameters to set method for "
210:                      + propertyName + " in class "
211:                      + theClass.getName());
212:             }
213:             //
214:             // We found the setter method
215:             // all we need to do is call it.
216:             //
217:             setter.invoke(object, args );
218:         }
219:         catch (Exception e)
220:         {
221:             return;
222:         }
223:     }
224: 
225:     //
226:     // Fill the bean based on the field names
227:     //
228:     static void loadBeanProperties(Object object,Node bean)
229:     {
230:         // get all the children of the bean
231:         // they should all be Properties children
232:         // later we will examine what we should
233:         // do if they are JavaBeans (beans within beans!)
234:         NodeList propertiesNL = bean.getChildNodes();
235:         for (int i = 0; i < propertiesNL.getLength(); i++)
236:         {
237:             Node propertiesNode = propertiesNL.item(i);
238:             if ( propertiesNode.getNodeName()
239:                 .equals("Properties"))
240:             {
241:                 DOMArray propertiesValues
242:                     = new DOMArray(propertiesNode,"Property");
243:                 for ( int j = 0;
244:                       j < propertiesValues.size();
245:                       j++)
246:                 {
247:                     try
248:                     {
249:                         loadProperty(
250:                           propertiesValues.getAsNode(j),object
251:                           );
252:                     }
253:                     catch (NoSuchMethodException e)
254:                     {
255:                         System.out.println(
256:          "Unexpected NoSuchMethodException loading a property");
257:
258:                     }
259:                     catch(IllFormedBeanException ifbe)
260:                     {
261:                         System.out.println(
262:           "Unexpected IllFormedBeanException:" + ifbe.getMessage());
263:                     }
264:                 }
265:             }
266:         }
267:     }
268:
269: 
270:     //
271:     // Create a JavaBean from a string representing
272:     // a uri to an XML file containing the JavaBean
273:      // description
274:     //
275:     public static Object newBeanInstance(String xmlJavaBean)
276:                  throws InstantiationException,
277:                         IllegalAccessException,
278:                         ClassNotFoundException
279:     {
280:         Document document =
281:              XMLUtil.openDocument(xmlJavaBean,false);
282:           // create a non-validating parser
283:         if ( null == document)
284:             return null;
285:             // Open failed cant create new instance.
286:         return newBeanInstance(document);
287:     }
288:
289:     //
290:     // Create a bean from a document
291:     // by finding the first bean instance
292:     // and returning a bean based on it.
293:     //
294:     public static Object newBeanInstance(Document document)
295:                          throws InstantiationException,
296:                                 IllegalAccessException,
297:                                 ClassNotFoundException
298:     {
299:         //
300:         // Find the first JavaBean node
301:         //
302:         NodeList beans =
303:              document.getElementsByTagName( "JavaBean" );
304:         if ( beans.getLength() < 1)
305:         {
306:             return null;
307:         }
308:         return newBeanInstance(beans.item(0));
309:     }
310: 
311:     //
312:     // Create a JavaBean starting from
313:     // the DOM Tree node that represents the bean
314:     // That is the <JavaBean Name=... element
315:     //
316:     public static Object newBeanInstance(Node aBean)
317:                          throws InstantiationException,
318:                          IllegalAccessException,
319:                          ClassNotFoundException
320:     {
321:         Class theClass = null;
322:         NamedNodeMap attributes = aBean.getAttributes();
323:         if ( null == attributes ||
324:              attributes.getLength() == 0)
325:         {
326:             return null;
327:         }
328:         String beanName = null;
329:         for ( int i = 0; i < attributes.getLength();i++ )
330:         {
331:             Node attribute = attributes.item(i);
332:             if (attribute.getNodeName().equals("ClassName"))
333:             {
334:                 beanName = attribute.getNodeValue();
335:                 break;
336:             }
337:         }
338:         if( null == beanName )
339:         {
340:             System.out.println(
341:               "Pretty weird Found a bean with no name!");
342:             return null;
343:         }
344:
345:         //
346:         // load the class
347:         //
348:         theClass = Class.forName(beanName);
349:         if ( null == theClass)
350:         {
351:             // most likely class not found
352:             return null;
353:         }
354:         //
355:         // If we are here we found the class
356:         // create a newInstance of the class
357:         // get all the properties and start setting
358:         // them one by one.
359:         //
360:         Object  object = theClass.newInstance();
361:         loadBeanProperties(object,aBean);
362:
363:         return object;
364:     }
365:
366:  . . . // Persist methods removed!
367:
368: }

Let's examine Listing 8.6 in terms of the numbered steps.

1.
We see that in lines 275, 294, and 316 we define several versions of newBeanInstance(): one for processing XML from a file, one for processing from a DOM document, and one for DOM elements. We load the JavaBean dynamically using Class.forName(), much like a class loader might do, and then carry this new Object along setting its property values as we go. When we examine EJBs, we will add several additional newBeanInstance() methods that work on a user-provided bean.

2.
On line 228, we see the loadBeanProperties() method that first finds the Properties DOM element and uses a DOMArray (lines 241–242) to iterate over all the Property elements.

3.
The loadProperty() method begins on line 51. This method is the actual meat and potatoes of creating a new bean from XML. It uses the Property DOM element and the JavaBean Object itself as follows:

  • Find the Property's name attribute (lines 59–77)

  • Using the Java Introspection engine, find the PropertyDescriptor object that represents the XML Property element we are processing (lines 100–118) and determine the argument type information and the setter method.

  • Examine the argument type. If it's primitive or a String, we can just set it directly using the buildSimpleArgument() method. Otherwise—and here's the trick for handling beans within beans—call newBeanInstance() on the current JavaBean child of the Property element to use as an argument to the setter method.

  • Call the setter method (line 217) to store the value into the bean itself.

The buildSimpleArgument() method still bears some review. In general, setter methods take arrays of Object elements as input. Since we are dealing with normal setPropertyName(Object value) methods, we need to build a one-element array that contains a single object representing the value. For primitive arguments, it's easy: buildSimpleArgument() constructs an instance of the Java class that represents the primitive element and uses the String-based constructor to create an object that represents the input String. For non-primitive arguments, it's a little harder, but we've already written a method to do it: newBeanInstance(). We take the return value from newBeanInstance() or buildSimpleArgument() and then pass it to the setter method; the method is magically called and the XML-persisted value is inserted into the bean instance.

The remainder of the code handles other processing and legwork necessary to make the process work, such as handling ill-formed beans, error testing, and so on.

The second half (with creating a bean from XML being the first half) of XML-based JavaBeans is persisting a JavaBean to XML. The next section examines this topic in detail and, as we shall see, uses many of the same techniques to achieve its goals.

Note

Throughout the XMLBean class, we use the getClass() method on various objects to obtain class information. Every Java object inherits the getClass() method from java.lang.Object. The class of an object is very different from an instance of an object. Instances contain an object's current state. The Class of an object contains information about the fields, method, and so forth and pertains to all objects of a given class.


JavaBean to XML

Persisting a JavaBean back to XML follows almost the exact reverse of the steps for restoring a bean's content from XML. Again, let's examine the steps first and then see how they are implemented.

1.
Open a stream for writing the XML. Note that we choose a PrintStream because it is simple and straightforward. However, we could have easily generalized the code to support writing to any stream. We could easily imagine a network-based stream that serializes printing over the network or a database-based stream that persisted our XML to a database.

2.
Use the Introspection engine to again examine our bean. This time, we look at the fields contained within the JavaBean so that we might save their values.

3.
Examine each field and obtain a reference to the getter method for each primitive type object within the JavaBean and with that getter method obtain the field's value and type information. Note that we skip static as well as volatile fields because they don't require persisting.

4.
If a given field is non-primitive, we recursively call our persistence methods to persist to XML the complex property; otherwise, we use the toString() method to obtain a string representation of the field and write it.

All of this is handled by Listing 8.8, XMLBean.java. Listing 8.7, XMLWrite.java, is a simple application that uses the XMLBean.newBeanInstance() to create a JavaBean in memory and the XMLBean.storeBeanInstance() to persist the bean back to a file.

Code Listing 8.7. XMLWrite.java—Driver for Peristing Beans as XML
 1: package sams.chp8;
 2: import java.lang.reflect.*;
 3: class XMLWrite
 4: {
 5:     //
 6:     // Simple main for testing...
 7:     //
 8:     public static void main (String argv [])
 9:     {
10:         if (argv.length < 1)
11:         {
12:             System.err.println(
13:           "Usage: java sams.chp8.XMLWrite frombean.xml tobean.xml" );
14:             System.exit(1);
15:         }
16:         try
17:         {
18:             // create an in-memory instance of the bean
19:             Object bean = XMLBean.newBeanInstance(argv[0]);
20:             if ( null == bean)
21:             {
22:                 System.out.println("Failed to create bean from "
23:                                    + argv[0]);
24:                 System.exit(1);
25:             }
26:             //
27:             // Now store it back
28:             //
29:             if ( argv.length == 2)
30:             {
31:                 if ( XMLBean.storeBeanInstance(argv[1],bean) != true)
32:                 {
33:                     System.out.println("Failed to export bean to"
34:                               + argv[1]);
35:                     System.exit(1);
36:                 }
37:             }
38:             else
39:             {
40:                 if ( XMLBean.storeBeanInstance(System.out,bean)
41:                      != true)
42:                 {
43:                     System.out.println("Failed to export bean");
44:                     System.exit(1);
45:                 }
46:             }
47:         }
48:         catch (Exception e)
49:         {
50:             System.out.println(e);
51:         }
52:     }
53: }

Code Listing 8.8. XMLBean.java—Persist to XML
  1: package sams.chp8;
  2: import java.lang.reflect.*;
  3: import java.beans.*;
  4: import java.io.*;
  5: import sams.chp7.*; // For XMLUtil.openDocument
  6: import org.w3c.dom.*;
  7:
  8: public class XMLBean
  9: {
 10:
 11: . . . // Restore portions removed.  See CD for complete sources
 12:     //
 13:     // The bulk of writing a bean is done
 14:     // in output properties We first find
 15:     // all the fields, skipping those that
 16:     // are transient, and get the associated
 17:     // property descriptor.
 18:     // We then use that property descriptor
 19:     // to get the reader method and then
 20:     // write the value.  We use each
 21:     // primitive types toString method to
 22:     // get a printable representation of
 23:     // that object. For non-primitive
 24:     // objects we simply call outputBean with the
 25:     // object and recurse through the beans within beans!
 26:     //
 27:     static boolean outputProperties(int level,
 28:                                     PrintStream ps,
 29:                                     Object bean,
 30:                                     Class theClass)
 31:     {
 32:
 33:         // go to the fields directly
 34:         Field[] fields = theClass.getDeclaredFields();
 35:         for (int i = 0; i < fields.length; i++)
 36:         {
 37:             Field field = fields[i];
 38:             Method getter = null;
 39:             //
 40:             // Skip transient fields
 41:             //
 42:             int modifiers = theClass.getModifiers();
 43:             if ( modifiers == java.lang.reflect.Modifier.TRANSIENT )
 44:             {
 45:                 continue;
 46:             }
 47:             Class type = null;
 48:             String fieldName = field.getName();
 49:             try
 50:             {
 51:                 PropertyDescriptor propDescs[] =
 52:                   Introspector.getBeanInfo(theClass)
 53:                   .getPropertyDescriptors();
 54:                 PropertyDescriptor pd = null;
 55:                 // find the property descriptor and
 56:                 // getter for this field
 57:                 for (int j = 0;
 58:                       pd == null && j < propDescs.length;
 59:                        j++)
 60:                 {
 61:                     if (propDescs[j].getName().equals(fieldName))
 62:                     {
 63:                         pd = propDescs[j];
 64:                         getter = pd.getReadMethod();
 65:                         type = pd.getPropertyType();
 66:                     }
 67:                 }
 68:
 69:             } catch ( IntrospectionException ie) {}
 70:             if ( null == getter)
 71:             {
 72:                 System.out.println("Cannot find getter for " +
 73:                                    field.getName());
 74:                 continue;  // no getter skip field
 75:             }
 76:             Object value = null;
 77:             try {
 78:                 value = getter.invoke(bean,null);
 79:             } catch (Exception e) {  value = null; };
 80:
 81:             if ( null == value ) continue; 
 82:                 // Couldn't get value skip it
 83:             //
 84:             // is it primitive or a string?
 85:             //
 86:             if ( type.isPrimitive() ||
 87:                  type == java.lang.String.class)
 88:             {
 89:                 //
 90:                 // just write it
 91:                 //
 92:                 prefix(ps,level);
 93:                 ps.print("<Property Name=""+fieldName + "">");
 94:                 try
 95:                 {
 96:                     Method toString =
 97:                      value.getClass().getMethod
 98:                      ("toString", null);
 99:                     String valueAsString =
100:                      (String)toString.invoke(value, null);
101:                     ps.print(valueAsString);
102:                 } catch (Exception e)
103:                 {
104:                     System.out.println(
105:                      "toString invoke failed" + e);
106:                 }
107:                 ps.println("</Property>");
108:             }
109:             else
110:             {
111:                 prefix(ps,level);
112:                 ps.println("<Property Name=""+
113:                           fieldName + "">");
114:                 outputBean(level+1,ps,value);
115:                 prefix(ps,level);ps.println("</Property>");
116:             }
117:         }
118:         return true;
119:
120:     }
121:
122:     //
123:     // Output bean is the start of writing
124:     // a bean object.  It prints the
125:     // appropriate class info and then calls
126:     // outputProperties to write the
127:     // underlying properties.
128:     //
129:     static boolean outputBean(int level,
130:                               PrintStream ps,
131:                               Object bean)
132:     {
133:         Class theClass = bean.getClass();
134:         prefix(ps,level);
135:          ps.println("<JavaBean ClassName="" +
136:                      theClass.getName() +
137:                      "">");
138:         prefix(ps,level+1);
139:         ps.println("<Properties>");
140: 
141:         boolean result = outputProperties(level+2,
142:                                           ps,
143:                                           bean,
144:                                           theClass);
145:
146:         prefix(ps,level+1);
147:         ps.println("</Properties>");
148:         prefix(ps,level);
149:         ps.println("</JavaBean>");
150:         return result;
151:     }
152:
153:     static void prefix(PrintStream ps,int level)
154:     {
155:         if ( level == 0) return;
156:         for ( int i = 0; i < level; i++)
157:             ps.print("    ");
158:     }
159:
160:     static void XMLBeanDTD(PrintStream ps)
161:     {
162:         ps.println("<?xml version="1.0" encoding="UTF-8" ?>");
163:         ps.println("<!DOCTYPE JavaBean [");
164:         ps.println("<!ELEMENT JavaBean (Properties)>");
165:         ps.println("<!ATTLIST JavaBean ClassName CDATA #REQUIRED>");
166:         ps.println("<!ELEMENT Properties (Property*)>");
167:         ps.println("<!ELEMENT Property (#PCDATA)>");
168:         ps.println("<!ATTLIST Property Name CDATA #REQUIRED >");
169:         ps.println("]>
");
170:
171:     }
172:     public static boolean storeBeanInstance(PrintStream ps, O
173:                                             bject bean)
174:     {
175:         XMLBeanDTD(ps);
176:         return outputBean(0,ps,bean);
177:     }
178:     public static boolean storeBeanInstance(String path,
179:                                             Object bean)
180:     {
181:         //
182:         // Open a stream to the file
183:         //
184:         boolean result = false;
185:         try
186:         {
187:             PrintStream pOut;
188:             pOut = new PrintStream (new FileOutputStream(path));
189:             result = storeBeanInstance(pOut,bean);
190:         }catch (IOException e){ }
191:
192:         return result;
193:     }
194:

As we did with Listing 8.6, let's examine the code and see how we handle each of the steps required to persist a JavaBean's state back to XML.

1.
On lines 172 and 178, we see two versions of XMLBean.storeBeanInstance(). One creates a stream to which to persist the bean's contents, and the other works on an existing stream, outputs our DTD, and then calls outputBean() to perform the real work.

2.
Our next step is to examine the bean's fields and output the non-transient/non-volatile fields to XML. The outputBean() method, which starts on line 129, outputs the necessary XML for the bean as a whole and then uses outputProperties() to do the real work of persisting the bean's underlying properties.

3.
outputProperties(), which starts on line 27, does the real work of persisting our bean to XML. Let's examine this method closely:

  • Obtain an instance of the class that represents the JavaBean we are persisting and use the getDeclaredFields() method of the class to get an array of all the fields in the JavaBean. See line 34.

  • For each field in the bean, examine the field's modifiers, skip any fields that are transient, and so forth. See line 42.

  • Use Introspection to obtain an array of the PropertiesDescriptor for this object and find the descriptor that describes the field we are processing. Use this descriptor to get a handle to the getter method and field's type information. See lines 51–66.

  • Use the getter method and the bean itself to get the fields value. See line 78.

  • Examine the field's type; for primitive types (and strings), use Introspection again to obtain a reference to the value's toString() method and use this method reference to obtain a string representation of the value. Then simply write the value to the output stream. See lines 86–100. If the value is not a primitive type, simply call outputBean using the new value recursively.

As with restoring the JavaBean's state from XML, the remainder of the code handles various legwork and housekeeping tasks to make the entire process work correctly. The complete code for XMLBean.java is available on the CD for your enjoyment.

Persisting and storing JavaBeans to and from XML is only half the story—the client-side Java half. What about server-side Java? In the next section, we examine Enterprise JavaBeans (EJBs), big brother or perhaps elder cousin to JavaBeans, and see how we can develop an Entity EJB that can read and write its state using Enterprise JavaBeans. In fact we'll enhance much of the code we've already written to handle persisting EJBs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.186.72