ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


XML-Java Data Binding Using XMLBeans XML-Java Data Binding Using XMLBeans

by Hetal C. Shah
07/28/2004

XMLBeans gives an object view of underlying XML data without losing access to the original XML info set, and delivers performance benefits via incremental unmarshalling and efficient methods to access XML schema built-in data types. These two features, along with nearly 100 percent support for XML schema, and provisions for on-time validation of XML data during data manipulation, make XMLBeans very useful for XML-Java data binding.

XMLBeans is currently in the Apache incubation process, and is proving very useful to Java developers for XML-Java binding.

The example code and other files mentioned in this article are available for download from the Resources section below. The examples have been tested with Apache XMLBeans Version 1.02, Java Version 1.4.2_02, and Microsoft Windows 2000.

Creating an XMLBean

Before getting started, download and install Apache XMLBeans Version 1.02 on your system. When you extract the files from the XMLBeans archive, you'll notice bin and lib directories in the distribution. Place the bin directory in your path, and xbean.jar from the lib directory in your classpath.

XMLBeans classes are created from an XML schema file (an XSD file). The resulting XMLBeans classes are able to parse any XML instance document that conforms to the schema. Also, an instance document can be created by manipulating these XMLBeans classes.

For example, the following listing of the weather_latlong.xsd schema describes XML documents containing either weather details or latitude and longitude ("latlong") details of a location, based on zip code.

<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<!-- This XML Schema describes xml documents
containing either weather details or latlong
details of a location based on Zipcode Two Global
elements Weather and Latlong, and one Global
Attribute Zipcode are declared.-->
 <xsd:element name="Weather">
  <xsd:complexType>
   <xsd:sequence>
    <xsd:element name="Temperature"
         type="xsd:float"/>
    <xsd:element name="Humidity"
         type="xsd:float"/>
    <xsd:element name="Visibility"
         type="xsd:float"/>
    <xsd:element name="Datetime"
         type="xsd:dateTime"/>
   </xsd:sequence>
  <xsd:attribute ref="Zipcode"/>
 </xsd:complexType>
 </xsd:element>
 <xsd:element name="Latlong">
  <xsd:complexType>
   <xsd:sequence>
    <xsd:element name="Latitude"
         type="xsd:string"/>
    <xsd:element name="Longitude"
         type="xsd:string"/>
    </xsd:sequence>
   <xsd:attribute ref="Zipcode"/>
  </xsd:complexType>
 </xsd:element>
 <xsd:attribute name="Zipcode"
         type="xsd:string"/>
</xsd:schema>

The next step is to generate a set of XMLBeans classes that represent the above XSD type schema. At a prompt in the working directory where you extracted the files from the example archive, type the following line:

scomp -out weather.jar weather_latlong.xsd

XMLBeans generates following five interfaces when it compiles the above schema: WeatherDocument, WeatherDocument$Weather, LatlongDocument, LatlongDocument$Latlong, and ZipcodeAttribute.

Here, the WeatherDocument interface represents the document element, and the WeatherDocument$Weather interface represents the global element Weather. Similarly, LatlongDocument, and the LatlongDocument$Latlong interface, are for the global element Latlong. The ZipcodeAttribute interface represents the global attribute Zipcode.

XMLBeans Classes

Let's discuss XMLBeans classes in more detail. XMLBeans provides 46 Java types that mirror the 46 built-in types defined by the XML schema specification. For example, where the W3C define an xsd:string, XMLBeans gives you an XmlString data type.

The Weather interface created from weather_latlong.xsd schema has following two methods declared for local element Visibility of type xsd:float:

float getVisibility();

and

org.apache.xmlbeans.XmlFloat xgetVisibility();

For each of these 46 Java types, XMLBeans provides two methods to access the data; here, one method returns an XmlFloat for xsd:float, and another method returns a natural Java type such as float for xsd:float.

The xget version of a function provides a performance benefit over the get version, as the get version has to convert the data to the most appropriate Java type.

Names for schema types become Java-friendly names when the schema is compiled. In other words, a name such as stock-quote becomes StockQuote. In addition, a schema's namespace URIs become package names for XMLBeans types generated from the schema. If the including schema does not have a target namespace declared, then the Java classes generated would all be in the noNamespace package. When there is a name collision of class names, the generated classes will have names with numerals appended -- for example, timeStamp3.

For global elements and attributes, the XMLBeans schema compiler generates an interface whose name ends with Document and Attribute, respectively.

For named types that are declared locally within the declaration of another element or type, XMLBeans generates an inner interface within the interface of element or type in which it is nested.

Consider the following listing of the employee.xsd schema:

<?xml version="1.0" encoding="UTF-8"?>
<!-- This XML Schema describes Employee's
    Jobstatus -->
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <xsd:complexType name="Employee">
  <xsd:sequence>
   <xsd:element name="Jobstatus">
    <xsd:simpleType>
     <xsd:restriction base="xsd:NMTOKEN">
      <xsd:enumeration value="fullTime"/>
      <xsd:enumeration value="hourly"/>
     </xsd:restriction>
    </xsd:simpleType>
   </xsd:element>
  </xsd:sequence>
 </xsd:complexType>
</xsd:schema>

Given this, XMLBeans generates an inner interface Jobstatus within the interface of element Employee, in which it is nested.

public interface Employee
  extends org.apache.xmlbeans.XmlObject
{
...
 public interface Jobstatus
   extends org.apache.xmlbeans.XmlNMTOKEN
  {

  }

}

Here, the Employee class extends org.apache.xmlbeans.XmlObject, which is a base interface for all XMLBeans types. All built-in schema types, and user-defined, derived schema types, inherit from XmlObject.

Unmarshal XML Documents Using XMLBeans Classes

The following bit of code from weather_unmarshal.java illustrates how to use XMLBeans classes to get weather details from an XML document in a file called weatherInput.xml.

String filePath = "weatherInput.xml";
java.io.File inputXMLFile =
new java.io.File(filePath);

// Parse XML Document.

WeatherDocument weatherDoc =
WeatherDocument.Factory.parse(inputXMLFile);

// Get object reference of root element Weather.

WeatherDocument.Weather weatherElement =
weatherDoc.getWeather();

Here the XML document is parsed by calling the WeatherDocument.Factory.parse(File) method, which returns a WeatherDocument object. Then the getWeather() method is called on the weatherDocument object to get the object reference of the root element Weather.

To get the contents of the Weather element, simply call the appropriate get methods of the weatherElement that directly map to the element and attribute names defined in the schema:

// Call the appropriate 'get' methods of
// weatherElement that
// directly map to the element and attribute names
// defined in the schema.

Calendar timeStamp = weatherElement.getDatetime();

System.out.println("Weather details of zipcode "
 + weatherElement.getZipcode() + " at "
 + timeStamp);
System.out.println("Temperature is "
 + weatherElement.getTemperature());
System.out.println("Humidity is "
 + weatherElement.getHumidity());
System.out.println("Visibility is "
 + weatherElement.getVisibility());

This produces the output:

Weather details of zipcode 92834-2345 at 2003-11-13T05:29:27-03:01
Temperature is 85.3
Humidity is 50.0
Visibility is 5.5

Unmarshal When Multiple Global Elements are Declared in the Schema

In the above example, we assumed that the input XML document is always carrying weather details. However, in the real world, the input XML document might carry either weather details or latlong details, because weather_latlong.xsd describes both by declaring two global elements: Weather and Latlong.

There are two ways to parse an XML document and bind it to an instance of the corresponding XMLBeans type. In the above example, we parsed the XML document using WeatherDocument.Factory.parse(). Another way is to use XMLBeans' built-in class XmlObject.

The following bit of code from weather_unmarshal_xmlObject.java illustrates how to use XmlObject to get the weather or latlong details contained in an XML instance document.

public static void main(String args[]) {

try {

if (args.length < 1 ) {

System.out.println("Usage : java "
+"weather_unmarshal_xmlObject <<InputFilePath>>");

return;

}

 String filePath = args[0];
 java.io.File inputXMLFile
   = new java.io.File(filePath);

 XmlObject xmlObjExpected =
 XmlObject.Factory.parse(inputXMLFile);


// Check document type of the object returned by
// the call to XmlObject.Factory.parse() method.
// If type of object returned is of
//noNamespace.WeatherDocument, then input xml
//document carries weather details of a location.

 if (xmlObjExpected instanceof
 noNamespace.WeatherDocument) {


  WeatherDocument weatherDoc =
    (noNamespace.WeatherDocument)xmlObjExpected;
  WeatherDocument.Weather weatherElement =
	weatherDoc.getWeather();

  Calendar timeStamp =
    weatherElement.getDatetime();

  System.out.println
    ("Weather details of zipcode "
    + weatherElement.getZipcode() + " at "
    + timeStamp  + " : \n\n");
  System.out.println("Temperature is "
	+ weatherElement.getTemperature());
  System.out.println("Humidity is "
	+ weatherElement.getHumidity());
  System.out.println("Visibility is "
	+ weatherElement.getVisibility());

// else if type of object returned is of
// noNamespace.LatlongDocument, then input xml
//document carries latlong details of a location.

} else if(xmlObjExpected instanceof
    noNamespace.LatlongDocument) {

	LatlongDocument latLongDoc =
	(noNamespace.LatlongDocument)xmlObjExpected;
	LatlongDocument.Latlong latLongElement =
	latLongDoc.getLatlong();

    System.out.println
    ("Latlong details of zipcode "
      + latLongElement.getZipcode() + " : \n\n");
    System.out.println("Latitude is "
      + latLongElement.getLatitude());
    System.out.println("Longitude is "
      + latLongElement.getLongitude());


// else input xml document is well formed , but
// doesn't conform to weather_latlong.xsd schema
// file.

} else {
	System.out.println("Input xml document "
	  + "doesn't conform to weather_latlong.xsd");
}


} catch (Exception e) {


    e.printStackTrace();
  }

}

}

To get the contents of the input XML document, we check the document type of the object returned by XmlObject.Factory.parse(), and then convert the returned object to the corresponding document type for further processing. Another interesting part of the code is the last else block, which handles the case where the XML document is well-formed but doesn't conform to the weather_latlong.xsd schema.

Create a New XML Document

The following bit of code from latlong_marshal.java illustrates how to use XMLBeans-generated classes to create a new XML instance document containing latlong details.

LatlongDocument latLongDoc;
LatlongDocument.Latlong latLongElement;
XmlOptions xmlOptions;

// LatlongDocument.Factory.newInstance() creates
// and returns a LatlongDocument object.

latLongDoc= LatlongDocument.Factory.newInstance();

// addNewLatlong() method is called on the
// document object to create and add a new
// LatLong Element to document.

latLongElement = latLongDoc.addNewLatlong();

LatlongDocument.Factory.newInstance() creates a LatlongDocument object and returns it. Then the addNewLatlong() method is called on the document object to create and add a new LatLong element to document.

To add data to the LatLong element, simply call the appropriate set methods of latLongElement that directly map to the element and attribute names defined in the schema.

latLongElement.setZipcode("91023");
latLongElement.setLatitude("33.8792");
latLongElement.setLongitude("117.8974");

The final block of code writes the current state of the LatLong element to a standard output stream.

xmlOptions = new XmlOptions();

// Requests use of whitespace for easier reading
xmlOptions.setSavePrettyPrint();

// Requests that nested levels of the xml
// document to be indented by multiple of 4
// whitespace characters
xmlOptions.setSavePrettyPrintIndent(4);

String xmlStr = latLongDoc.xmlText(xmlOptions);

// Writes the current state of the LatLong
// element to a standard output stream

System.out.println("XML Instance Document is : "
  + "\n\n\n " + xmlStr );

The xmlText method takes an optional xmlOptions object to control its behavior. setSavePrettyPrint() method requests use of white space for easier reading, and setSavePrettyPrintIndent(4) requests that nested levels of the XML document to be indented by multiple of four white-space characters.

This produces the output:

XML Instance Document is :

<Latlong Zipcode="91023">
    <Latitude>33.8792</Latitude>
    <Longitude>117.8974</Longitude>
</Latlong>

Performance Benefits

Unlike DOM, XMLBeans doesn't take the approach of unmarshalling an entire XML document and providing an object for each node in the XML document. With XMLBeans, unmarshalling and marshalling happens on demand, so if you never look at a piece of data, it's never unmarshalled or marshalled. This improves the performance of the XMLBeans solution.

XMLBeans also provides the efficient xget versions of functions to access XML schema built-in data types.

Validation

The value assigned to a built-in XMLBeans java type is validated as per the rules of the schema type it represents. So, for example, if a qualified name, whose prefix doesn't resolve to any URI, is assigned to an XmlQName data type, then an XmlValueOutOfRange exception will be thrown.

When an XML document is first parsed, the data contained can be validated based on the schema definition. More interestingly, any time the XML document is manipulated via the XMLBeans-generated Java classes, the XMLBeans system ensures that schema constraints are honored.

Other Features

XMLBeans objects are serializable, so they can be transmitted over RMI boundaries, and can be easily loaded from and saved to XML character streams and byte streams. XMLBeans also includes a configuration capability so that you can map XML names to your Java names. This will allow you to avoid rewriting Java code when XML names change. Space won't allow us to discuss them in more detail here.

Conclusion

XMLBeans gives an object view of the underlying XML data without losing access to the original XML structure, and delivers performance benefits via incremental unmarshalling and efficient xget...() methods to access XML schema built-in data types. These two features, along with nearly 100 percent support for XML schema, and provisions for on-time validation of XML data during data manipulation, make XMLBeans very useful for the kinds of XML-Java data binding used in implementations of web services, BPEL, BPML, rule-based XML data transformation engines, and so on.

Resources:

Hetal C. Shah is an IT consultant, specializing in Internet-related technologies


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.