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.
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.
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.
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: } |
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.
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.
<?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> |
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.
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.
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: } |
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.
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.
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.
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.
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: } |
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.
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.
18.191.186.72