Abstract
One of the most powerful features of XMLBeans Version 1.0.3 is the ability to customize its code generation by supplying a configuration file. Configuring features of XMLBeans brings a number of benefits in terms of flexibility, reusability, simplified code, and maintenance. Because a substantial part of the cost of any enterprise application is its maintenance, the configuration features in XMLBeans can represent significant savings in cost and time in the long run.
This article illustrates these features with a series of examples. We assume you have a familiarity with XMLBeans. For a primer, see our article introducing XMLBeans, referenced below. The example code and other files mentioned in this article are available for download. The examples have been tested with Apache XMLBeans Version 1.0.3, Java Version 1.4.2_02, and Microsoft Windows 2000.
Introduction
When running the XMLBeans compiler, you can specify an optional configuration file that modifies the behavior of the XMLBeans generator. By default, this configuration file should have an .xsdconfig extension. The basic structure of the configuration file follows this pattern:
<?xml version="1.0" encoding="UTF-8"?> <xb:config xmlns:xb="http://xml.apache.org/xmlbeans/2004/02/xbean/config"> ... </xb:config>
As you can see, the root config element must be placed in the http://xml.apache.org/xmlbeans/2004/02/xbean/config namespace. The actual configuration features must be situated within the config element and can be placed in any order.
Once you have a schema file (say, someSchema.xsd) and a configuration file (say, someConfig.xsdconfig), then the next step is to generate a set of XMLBeans classes that represent the schema. The following is an example of the command-line usage:
scomp -out output_XmlBeans.jar someSchema.xsd someConfig.xsdconfig
Note that the configuration file is simply added as an additional parameter to scomp. This will ensure that the generator uses whatever extension features you have specified. The following sections examine each of these features in turn.
Adding a Prefix or Suffix to Generated Class Names
XMLBeans provides a simple configuration feature to add a prefix and suffix to the name of XMLBeans classes created for schema types from a specific namespace. This feature is useful to distinguish the XMLBeans-generated classes for schema types from a specific namespace, from other XMLBeans, and from JavaBeans used in the project.
This is the simplest of the features. We will also use this section to introduce the example schema. Listing 1 shows the localInfo.xsd schema used throughout this article. It describes XML documents containing weather information of a location, based on zip code.
Listing 1. localInfo.xsd
<xsd:schema
targetNamespace="http://www.localInfo.com/LI"
...
xmlns:ns1 = "http://www.helloWeather.com/weather"
>
<xsd:import
namespace="http://www.helloWeather.com/weather"
schemaLocation="weather.xsd"/>
<xsd:element name="localInfo">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="ns1:localWeather"/>
</xsd:sequence>
<xsd:attribute ref="Zipcode"/>
</xsd:complexType>
</xsd:element>
<xsd:attribute name="Zipcode" type="xsd:string"/>
</xsd:schema>
This localInfo.xsd schema imports weather.xsd, shown in Listing 2.
Listing 2. weather.xsd
<xsd:schema targetNamespace=
"http://www.helloWeather.com/weather"
....
>
<xsd:element name="localWeather">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="Temperature"/>
<xsd:element ref="Humidity" />
<xsd:element ref="Visibility" />
<xsd:element ref="Datetime" />
<xsd:element ref="image"
minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute ref="Zipcode"/>
</xsd:complexType>
</xsd:element>
<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:element name="image"
type="imageType"/>
<xsd:attribute name="Zipcode"
type="xsd:string"/>
<xsd:complexType name="imageType">
<xsd:simpleContent>
<xsd:extension base="xsd:base64Binary" >
<xsd:attribute name="type" use="optional"
type="xsd:string"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
Finally, Listing 3 shows a simple, complete configuration file (localInfo.xsdconfig) that illustrates usage of the prefix and suffix feature of XMLBeans.
Listing 3: localInfo.xsdconfig: Configuration for prefixes and suffixes
<xb:config xmlns:xb= "http://xml.apache.org/xmlbeans/2004/02/xbean/config" > <xb:namespace uri="##any"> <xb:suffix>XmlBean</xb:suffix> <xb:prefix>pre</xb:prefix> </xb:namespace> </xb:config>
The prefix and suffix elements specify the prefix and suffix, respectively, of the name of XMLBeans classes created for schema types from a namespace identified by the value of uri attribute in namespace element. In the above listing of localInfo.xsdconfig, we want to add the suffix "XmlBean" to the name of all XMLBeans classes created from an XML schema, irrespective of the namespace of the schema types they represent. For the purpose of this example, we won't use the prefix facility.
The next step is to generate a set of XMLBeans classes that represent localInfo.xsd. At a prompt in the working directory where you extracted the files from the example archive, type the following line:
scomp -out localinfo_XmlBeans.jar localInfo.xsd localInfo.xsdconfig
As a result of using the configuration file, the generated XMLBean classes will now have the appropriate suffix. For example, the XMLBean generated for the Zipcode attribute will be called ZipcodeAttributeXmlBean.java instead of ZipcodeAttribute, which would have been the default.
XML to Java Name Mapping and Namespace URI to Package Name Mapping
XMLBeans includes a configuration capability to map XML names to Java names, and to map a namespace URI to a Java package name. This allows developers to avoid rewriting Java code when XML names or namespace URIs change, and to make their XMLBeans-based applications flexible.
As an example, consider the following configuration file, which assigns a package name to each of the namespaces we used when creating the schema:
<xb:config xmlns:xb= "http://xml.apache.org/xmlbeans/2004/02/xbean/config" xmlns:NS1="http://www.helloWeather.com/weather"> ... <xb:namespace uri="http://www.localInfo.com/LI"> <xb:package>com.localInfo</xb:package> </xb:namespace> <xb:namespace uri="http://www.helloWeather.com/weather"> <xb:package>com.weather</xb:package> </xb:namespace> <xb:qname name="NS1:image" javaname="MapXmlBean"/> ... </xb:config>
Remember to declare the prefix for namespaces used to qualify elements in the configuration file. Here we have declared the NS1 prefix for the http://www.helloWeather.com/weather namespace in the config element.
As you can tell from this example, the namespace and package elements are used to map a namespace URI to the Java package name that should be generated. The following namespace element maps the http://www.localInfo.com/LI namespace to package name com.localInfo.
<xb:namespace uri="http://www.localInfo.com/LI"> <xb:package>com.localInfo</xb:package> </xb:namespace>
As a result of using this, the package name of the generated XMLBean classes for schema types belonging to the http://www.localInfo.com/LI namespace is com.localInfo instead of com.localInfo.li, which would have been the default.
The qname element is used to map a schema type name to the corresponding XMLBeans-generated Java class name. The name attribute of the qname element is a qualified name of a schema type, and the javaname attribute value represents the corresponding XMLBeans-generated class name.
The image element declared in the weather.xsd schema represents a map of a location. In the above example we use the qname extension to map the image element to the Java class named MapXmlBean, in order to make the Java name of the image element more intuitive. Note that qname takes precedence over namespace, therefore the Java name is specified as MapXmlBean instead of just Map, so that it is consistent with other XMLBeans class names.
Interface Extensions
The interface extension feature is useful for extending an XMLBean with methods unrelated to the structure and properties of the schema type it represents. For example, say the temperature data in the XML document carrying weather details is in Fahrenheit, but some client applications using the corresponding XMLBeans need temperature data in Celsius for further processing. Using the interface extension feature, a conversion function float getTemperatureInCelsius() can be added to the XMLBean, which will convert temperature data from Fahrenheit to Celsius.
Here is a snippet from a configuration file that does this by using the extension element:
<xb:extension for=
"com.weather.LocalWeatherDocumentXmlBean$LocalWeather"
>
<xb:interface
name="com.extension.weatherExtension">
<xb:staticHandler>
com.extension.weatherExtensionHandler
</xb:staticHandler>
</xb:interface>
</xb:extension>
The for attribute can accept a space-delimited list of XMLBeans Java interfaces, or *, to include all of them in the extension. As a result of this, the generated XMLBeans interface LocalWeatherDocumentXmlBean$LocalWeather will extend the indicated interface, which in this case will be com.extension.weatherExtension. For the example, we'll have the interface declare two methods:
public interface weatherExtension {
float getTemperatureInCelsius();
float getVisibilityInKilometers();
}
Implementation methods for the com.extension.weatherExtension interface will automatically be generated in the corresponding XMLBeans implementation class LocalWeatherDocumentXmlBeanImpl$LocalWeatherImpl. These implementation methods will delegate to the specified extension handler com.extension.weatherExtensionHandler's methods.
The extension handler class com.extension.weatherExtensionHandler must contain a public static method with the same name as the interface method but with the first parameter of type XmlObject, followed by the parameters of the interface method, like so:
public static float getTemperatureInCelsius
(XmlObject xo) {
.....
}
public static float getVisibilityInKilometers
(XmlObject xo) {
.....
}
The above two methods will be invoked every time the extended methods on an instance of LocalWeatherDocumentXmlBean$LocalWeather are called.
Since XMLBeans generates document classes based on a schema, these document classes must be built prior to compiling any extension classes that rely on them. Because of this circular dependency, building all the pieces is a bit tricky.
Building XMLBeans with the interface extension feature is divided into three steps.
-
Put an XML comment around the
extensionelement in the localInfo.xsdconfig file. Then, at a prompt in the working directory where you extracted the files from the example archive, type the following line:scomp -out localinfo_XmlBeans.jar localInfo.xsd localInfo.xsdconfig -
Compile the extension interface and handler class using localinfo_XmlBeans.jar, and xbean.jar.
SET CLASSPATH=%CLASSPATH%;localinfo_XmlBeans.jar;.; c:\xmlbeans-1.0.3\lib\xbean.jar javac -d . weatherExtension.java javac -d . weatherExtensionHandler.java -
Remove the XML comment around
extensionelement in the localInfo.xsdconfig file. Then runscompagain with the localInfo.xsd and localInfo.xsdconfig files, making sure that compiled extension classes are on theclasspath.scomp -out localinfo_XmlBeans.jar localInfo.xsd localInfo.xsdconfig
Voila! We are ready to compile and run the XMLBeans-based client application extensionClient.java, which is included in the example archive.
javac extensionClient.java java extensionClient localInfo_68154.xml
By using the interface extension feature, we have moved the additional logic from the client application to the XMLBean Java code. The code is now centralized and reusable. At the same time, the XMLBean's client application becomes simpler and thinner.
The PrePost Extension
When a prepost extension feature is used, pre and post calls to the extension handler will be generated at the beginning and at the end of all the setter methods of the specified XMLBeans.
Let's say we do not want our XMLBeans-based client applications to modify the map represented by content of the image element. To enforce this constraint, add the following extension element to the localInfo.xsdconfig file:
<xb:extension for="
com.weather.LocalWeatherDocumentXmlBean$LocalWeather
">
<xb:prePostSet>
<xb:staticHandler>
com.extension.MapPrePostSetHandler
</xb:staticHandler>
</xb:prePostSet>
</xb:extension>
The for attribute of extension element specifies a space-delimited list of XMLBeans in which setter methods call the preSet() and postSet() methods as shown in the following listing of LocalWeatherDocumentXmlBean$LocalWeather class:
/**
* Sets the "image" element
*/
public void setMapXmlBean
(com.weather.ImageTypeXmlBean mapXmlBean)
{
synchronized (monitor())
{
check_orphaned();
if ( com.extension.MapPrePostSetHandler.preSet
(1, this, MAPXMLBEAN$8, false, -1)
)
{
.... Code to set ImageTypeXmlBean instance
}
com.extension.MapPrePostSetHandler.postSet
(1, this, MAPXMLBEAN$8, false, -1);
}
}
/**
* Appends and returns a new empty "image" element
*/
public com.weather.ImageTypeXmlBean
addNewMapXmlBean()
{
synchronized (monitor())
{
check_orphaned();
com.weather.ImageTypeXmlBean target = null;
if ( com.extension.MapPrePostSetHandler.preSet
(2, this, MAPXMLBEAN$8, false, -1)
)
{
...
Code to create and return an empty instance
of ImageTypeXmlBean
...
}
com.extension.MapPrePostSetHandler.postSet
(2, this, MAPXMLBEAN$8, false, -1);
return target;
}
}
The above code is an example of what is generated when the PrePost extension is used. Note that the pre and post methods are invoked in both the set and append methods.
The following bit of code from MapPrePostSetHandler.java illustrates a possible implementation of the preSet() and postSet() methods to stop the client application from changing the content of the image element.
public static boolean preSet (int operationType,
XmlObject xo, QName propertyName,
boolean isAttr, int indexOfItem){
javax.xml.namespace.QName imageQName =
new javax.xml.namespace.QName
("http://www.helloWeather.com/weather","image");
if( (xo instanceof LocalWeather)
&& ( propertyName.equals(imageQName) ) )
{
return false;
}
return true;
}
...
public static void postSet(int operationType,
XmlObject xo, QName propertyName,
boolean isAttr, int indexOfItem)
{}
The preSet() method notifies the extension handler that a set, insert, or remove operation is about to be executed on an XMLBeans object. This is the last chance the extension handler class has to prevent that operation.
The preSet() method returns a boolean. If the returned value is:
true, the intended operation executes.false, the intended operation is skipped.
Our MapPrePostSetHandler's preSet() method returns false if the XMLBeans object is an instance of LocalWeather, and the property to be modified is an image element belonging to the http://www.helloWeather.com/weather namespace; otherwise, it returns true. XMLBeans does not provide a feature to specify whether a generated XMLBeans class should have add, remove, or set methods. As we have just seen, we can control the flow of these methods at runtime using the PrePost extension feature.
After an operation is completed or skipped, the XMLBeans class calls the postSet() method. The parameters of preSet() and postSet() methods are as follows:
int operationType-
Type of the operation. Possible values are
org.apache.xmlbeans.impl.config.PrePostExtension.OPERATION_SETfor set operations,org.apache.xmlbeans.impl.config.PrePostExtension.OPERATION_INSERTfor add operations, andorg.apache.xmlbeans.impl.config.PrePostExtension.OPERATION_REMOVEfor remove operations. XmlObject xo-
Instance of
XmlObjecton which the operation is called. QName propertyName-
Qualified name of the element or attribute to be operated on.
boolean isAttr-
trueif the property is an attribute, otherwisefalse. int indexOfItem-
Index of the item to be set, when an element identified by propertyName allows several values, that is, declares
maxOccurs> 1.
Building XMLBeans with both the interface extension and PrePost extension feature is bit trickier, and it is explained in the build.txt file of the example archive. An XMLBeans-based client application prePostClient.java demonstrating the effectiveness of the PrePost extension feature is also provided in the archive file available for download from the Additional Reading section below.
Conclusion
The configuration features of XMLBeans introduce a number of benefits that increase the flexibility of the code, while reducing the cost of maintaining the code. While the prefix/suffix extension is perhaps the least useful of these features, the package naming feature is almost essential. The interface extension feature is a great way to pull additional functionality into the XMLBeans framework, and the PrePost extension offers a poor man's AOP feature: wrapping operations that allow you to do things like optionally executing an operation based on some condition.
Additional Reading
- sample.zip - Sample schema, Java code, and XML files for this article
- XMLBeans - The Apache XMLBeans distribution
- XML-Java Data Binding Using XMLBeans - An introduction to XMLBeans





