Betwixt is a Jakarta Commons component that provides XML introspection routines for mapping JavaBeans to XML and vice versa.. It uses concepts similar to the JavaBeans Introspector and BeanInfo to decipher information about XML data and map it to JavaBeans. Digester rules are used to create JavaBeans from XML internally.
Betwixt lies in the category commonly classed as data-binding tools Betwixt is a feature rich product that provides a lot of flexibility in its operation. This excerpt from Manning's Jakarta Commons Online Bookshelf gives an introduction to this component. Before we delve into Betwixt proper, let's take a brief look at data-binding concepts.
Understanding data binding
Data binding is the process by which data is transferred from one form to another. Although it's primarily used in conjunction with XML, data binding is a more generic concept and can be used to describe data in any format. The transformation from one form to another is inherently considered to be bidirectional.
Why is it called data binding? Well, one explanation is, because the two forms of data are considered bound to each other. Thus, technically, a change in the data in one form should automatically be reflected in the second form. Another explanation is that in business processes, the form of the data isn't important, what is important is the data itself, and data binding provides a mechanism for different forms of data to be seamlessly accessible.
Whatever the reasons for the name, the concept is straightforward. Data binding is the process of two-way transformations between data in different formats, with the emphasis on the actual data without regard to the structure of the data itself. Since we're concerned here with Java- and XML-based objects, we can further refine this concept. For our purposes, data binding is the two-way transformation between Java objects (JavaBeans) and XML data. The process by which Java objects are converted to corresponding XML representation is called marshalling, and the reverse conversion from XML to Java objects is called un-marshalling.
Finally, as we said before, although we're only concerned with Java and XML data binding, the concept can be extended to other formats as well. Thus, bidirectional transformations between XML data and a relational database tables/columns is also called data binding.
Betwixt closely follows the JavaBean structure for maintaining, accessing, and modifying information about a JavaBean and translating this information back and forth between its XML representations. The classes in the Sun package java.beans provide interfaces and concrete classes that give you information about a bean. Betwixt uses a similar mechanism: Specifically, it uses classes called XMLIntrospector and XMLBeanInfo that mirror the work done by the Introspector and BeanInfo classes from the java.beans package. Because these classes are so similar, let's look at those from the java.beans package first. This will help you better understand the process behind the Betwixt classes.
Java's BeanInfo and Introspector classes
Introspector is a concrete class in the java.beans package. It provides a standard way to learn more about a class, be it a JavaBean or not. If you call the static method getBeanInfo(Class targetClass), passing it the class that you want to learn more about, it returns a BeanInfo object that contains information about the target class.
Introspector works by first looking for an explicit BeanInfo class for your target class. Thus, as a conscientious developer, you may decide to provide a BeanInfo class for your target class that tells anyone who cares details about your target class over and above what the introspector can gather by itself using lowlevel reflection. If the introspector can locate such an explicit BeanInfo class about your target class, it merges this information with its own implicit reflection analysis on your target class and returns concrete BeanInfo information combining the two.
So, BeanInfo is information about your JavaBean packaged in a convenient class. However, BeanInfo is only an interface; if you decide to provide a BeanInfo class for your target class, you'll either need to implement this interface or use the SimpleBeanInfo class, extend it, and override the methods you want. The information about your target class should be provided using the MethodDescriptor, PropertyDescriptor, EventDescriptor, or BeanDescriptor class. Each of these class names should be self-describing. We recommend that you look up the java.beans package in the Java API for more information about these classes.
Figure 1 shows the relationship between the classes we just discussed.
Figure 1: Relationships between important classes of the java.beans package
Now that you know more about these base Java classes, let's look at how Betwixt uses the same principle in its XMLBeanInfo and XMLIntrospector classes to inspect information about Java objects.
Reading objects using XMLBeanInfo and XMLIntrospector
If we were to break down the whole bean --> XML Betwixt process into concrete steps, the first step would involve the XMLIntrospector class gathering information about the target class by searching for any java.beans.BeanInfo information, the second step would use this information to create an XMLBeanInfo object, and the final step would write this information out as XML using the BeanWriter and AbstractBeanWriter classes. These steps are shown in figure 2.
Figure 2: Steps in the bean --> XML process using Betwixt
In this section, we'll discuss the first two steps. The next section will look at the classes of step 3.
The XMLIntrospector class uses the java.beans.Introspector class to analyze the target class before it's written out as XML. The default introspection using java.beans.Introspector can be overridden by providing a .betwixt file, which contains information about the target class. This file must be named the same name as the target class (and an extension of .betwixt) and must be available in the CLASSPATH.
The XMLBeanInfo class uses org.apache.commons.betwixt.NodeDescriptor-based classes to encapsulate information. The org.apache.commons.betwixt.ElementDescriptor class defines information about the elements to be present in the XML, and org.apache.commons.betwixt. AttributeDescriptor defines information about the attributes. Both these classes derive from the NodeDescriptor class and are similar to the way MethodDescriptor and other classes define information about a JavaBean.
There is an internal process for caching preprocessed classes. Before a target class is introspected (a potentially time-consuming process), an internal registry is consulted. If this registry, defined by the interface org.apache.commons.betwixt.registry.XMLBeanInfoRegistry and implemented using org.apache.commons.betwixt.registry.DefaultXMLBeanInfoRegistry, doesn't contain XMLBeanInfo information about the target class, only then is the introspection continued. After the introspection is performed, this information is stored in the registry using the target class as the key. You can turn off this caching by using org.apache.commons.betwixt.registry. NoCacheRegistry instead of the default registry. This registry-indeed, any registry you create-can be set on the XMLIntrospector by calling setRegistry(XMLBeanInfoRegistry registry) method.
Even before the target class is looked up in the cache, the target class is normalized. Normalization is the process by which you can make changes to the target class before it's introspected. These changes can range from making changes to the superclass of your target class that should be introspected to supplying interface information that a class implements. This process allows you to make changes in the target class, thus restricting or enabling the class information before it's written out.
The default normalization, implemented by the class org.apache.commons.betwixt.strategy.ClassNormalizer, makes no changes to your class and returns it as it is. You can extend this class and enable your own normalization procedure by setting this class as the normalizer Betwixt should use by calling setClassNormalizer(ClassNormalizer classNormalizer) on the XMLIntrospector. Betwixt comes with another normalizer called ListedClassNormalizer, which allows you to substitute a target class for another class by calling the method addSubstitute(Class targetClass, Class substituteClass). This normalizer is useful for cases when you may want to restrict introspection of your classes to a standard set.
Betwixt writes out XML representations by using the classes AbstractBeanWriter and BeanWriter, which appear in the third step of the Betwixt bean --> XML process shown in figure 2. The next section looks at these classes.
Writing objects using AbstractBeanWriter and BeanWriter
AbstractBeanWriter is the superclass of BeanWriter. It contains concrete code to process the target class, whereas BeanWriter contains code to write the output as an XML stream. These classes, and the corresponding reader class called BeanReader, are in the org.apache.commons.betwixt.io package.
When Betwixt is writing your target bean as XML, it uses the org.apache.commons. betwixt.BindingConfiguration class to resolve three runtime issues. This class is a placeholder for resolving these issues and contains methods to set and get these issues as properties. This class can be set with the correct properties and attached to the AbstractBeanWriter (or the BeanReader, because the same configuration can be used for both reading and writing) by calling the method setBindingConfiguration(BindingConfiguration config).
The three issues in question are as follows:
- Matching cyclic elements
- Overriding the bean type
- Object to String conversions
The first issue involves using an ID for matching cyclic elements. When an XML file is being written out, elements that reference each other, leading to a cyclic check, can be referenced using ID and IDREF attributes. This is the default behavior of Betwixt. However, you can change it by setting setMapIDs(boolean mapIDs) to false in the BindingConfiguration class. As a side note, theUclasses in the package org.apache.commons.betwixt.io.id can be used to customize the value of the ID and IDREF attributes. By default, Betwixt uses a sequential ID generator, SequentialIDGenerator, which starts at zero. An ID generator must implement the org.apache.commons.betwixt.io.IDGenerator interface or extend the org.apache.commons.betwixt.io.id.AbstractIDGenerator class. Another ID generator, besides the sequential one, is RandomIDGenerator.
The second issue deals with the class name attribute used to override the type of a bean. By default, Betwixt uses className. You can set it to your actual class name, or to its superclass name, or to anything you desire. Note that you must use the same name (or the same BindingConfiguration class) when reading the XML back in, to ensure that your object is created exactly as it was originally.
The final issue that BindingConfiguration allows you to handle is the class that will be used for Object to String conversions. Such conversions, as may be obvious, are used to represent String versions of Objects. However, if you're scratching your head wondering how it's different from a toString() operation, hold on to your hats: We're talking about a round-trip conversion during which Objects are converted to String representations and String representations are converted to their original Object types. Betwixt's default mechanism for doing this uses the ConvertUtils package of the BeanUtils component.
So far, we've concentrated on writing beans as XML. In the next section, we'll cover the XML --> bean process, which is based on the Digester component.