Getting StartedProbably the best way to get started is to look at some examples. Here is some simple example code that reads and writes a bean (together with some comments). It's a good place to start. Mapping beans to XMLThere are various ways of mapping beans to an XML structure. For example consider a simple bean public class CustomerBean { public String getName(); public Order[] getOrders(); public String[] getEmailAddresses(); } This could be mapped to XML in various ways. A couple of examples of these different ways follow. Example 1This example uses attributes for primitive types. <CustomerBean name='James'> <order id='1'>...</order> <order id='2'>...</order> <emailAddress>jstrachan@apache.org</emailAddress> </CustomerBean> Example 2This example uses elements for all properties and wraps collections in an extra element (which can be quite common in XML schemas). Also note that some element names have been changed. <customer> <name>James</name> <orders> <order id='1'>...</order> <order id='2'>...</order> </orders> <email-addresses> <email-address>jstrachan@apache.org</email-address> </email-addresses> </customer> Betwixt aims to provide a diversity of possible mappings such that the developer can choose, if they wish, how their beans appear as XML to support whatever XML encoding scheme that is desired. If no particular mapping is provided then Betwixt will create a default mapping for you. Also the customization mechanism allows you to just override the parts you want to and let Betwixt default the rest. So if you just want to rename a property in XML for a certain type, thats all you need to do. No need to hand-code what Betwixt can deduce for itself. Customizing the mapping of a bean to XML (basic).betwixt filesThe XMLIntrospector will look for files of the form className.betwixt on the classpath using the same ClassLoader used to load the given class and use that document to specify the mapping to XML. If this file does not exist then the default introspection rules are used. The simplest possible file may just set the name of the element. e.g. <?xml version='1.0' encoding='UTF-8' ?> <info> <element name='channel'/> <addDefaults/> </info>
The above means to use the name 'channel' for the outer most element for the
given type. The
<?xml version='1.0' encoding='UTF-8' ?> <info primitiveTypes='attribute'> <hide property='something'/> <element name='channel'/> <element name='customerList'> <element name='customer' property='customers'/> </element> <element name='foo'> <attribute name='qqq' property='ppp'/> <element name='bar' property='xyz'/> <addDefaults/> </element> </info>
Note that the
The primitiveTypes attribute in the
Finally static text can be specified using a value attribute inside an
<?xml version='1.0' encoding='UTF-8' ?> <info primitiveTypes='element'> <element name='rss'/> <attribute name='version' value='0.91'/> <element name='channel'/> <addDefaults/> </element> </info> Bean naming conventionsThe Java Beans specification contains various naming conventions that should be used when writing beans that will allow the beans introspector to automatically guess the properties in a bean and their getters, their setter methods etc. Betwixt will use these same naming conventions to deduce how to make the beans appear as XML. There are some other naming conventions that you can use to make your beans easier to output as XML or parse.
One common requirement when mapping beans to xml is that the property and type names from the bean
must be processed (in some way) before they are used in the xml. For example, a property
called
Betwixt supports customization of these mappings through plug-in implementations of the
Note
that the attribute and element names given in a .betwixt file (as usual)
override the name mapping settings on the
Using a custom type name to element name mapping
Betwixt supports pluggable conversion of type names to element names. Setting the
Using a custom property name to attribute name mapping
Betwixt supports pluggable conversion of type names to attribute names. Setting the
Custom Mapping ExampleHere's a simple bean which will be mapped to xml: public class TallTreeBean { private float heightOfTree; public TallTreeBean(float height) { setHeightOfTree(height); } public float getHeightOfTree() { return heightOfTree; } public void setHeightOfTree(float heightOfTree) { this.heightOfTree = heightOfTree; } } Next is an application that writes that bean to xml. Custom name mappers for elements and attributes are set. import org.apache.commons.betwixt.io.BeanWriter; import org.apache.commons.betwixt.strategy.DecapitalizeNameMapper; import org.apache.commons.betwixt.strategy.HyphenatedNameMapper; public class NameMapperExampleApp { public static final void main(String args[]) throws Exception{ // create write and set basic properties BeanWriter writer = new BeanWriter(); writer.getXMLIntrospector().setAttributesForPrimitives(true); writer.enablePrettyPrint(); writer.setWriteIDs(false); // set a custom name mapper for attributes writer.getXMLIntrospector().setAttributeNameMapper(new HyphenatedNameMapper()); // set a custom name mapper for elements writer.getXMLIntrospector().setElementNameMapper(new DecapitalizeNameMapper()); // write out the bean writer.write(new TallTreeBean(15.1f)); System.out.println(""); } } The application should print out (to System.out) an xml fragment which looks like: <tallTreeBean height-of-tree="15.1"/> As you can see, the first letter of the element name has been decapitalized and the capitals in the property separated by hyphens after being converted to lower case. Using adder methods for composite propertiesThis naming convention is used to indicate the singular type of some composite property. To use: create an add method to match the getter method for 'composite properties'. public class SomeBean { public <CollectionType> getFoo*(); public void addFoo(<SingularType> foo); } Where CollectionType can be an array, a Collection, Enumeration, Iterator, Map. The [SinglularType] refers to the type of an item in the collection. The name of the getter property starts with 'Foo'. So 'Foo' is the singular name, the plural collection name could be Foos, FooArray, FooList, FooIterator or some other encoding, though the plural name should start with the singular name for auto-detection to work properly. Using a custom plural stemmer
Betwixt allows this auto-detection of plurals from singulars to be customized.
Implementations of
One important usage of custom plural stemmers is to support classes with non-english method names.
A custom
The default implementation supports common english plural patterns and then falls back to finding
any property that starts with the singular name. For example, it will match a plural property called
Reading And Writing Map Properties
Maps are a special kind of composite property. Each entry in a map has a key and a value.
Betwixt handles maps by adding extra elements which wrap each entry. Each entry is wrapped in
a
The structure is something like: ... <entry> <key>...</key> <value>...</value> <entry> <entry> <key>...</key> <value>...</value> <entry> ...
Reading map properties is an extension of the ways that Betwixt handles collections. Rather than
searching for an
Customizing the mapping of a bean to XML (advanced)Caching and XMLRegistry
Introspection is slow and so caching the results improves preformance. Though the writers
and readers can - by nature - only be used safely in a single thread, a single
The
Before using the standard introspection techniques to create a new
The default strategy caches everything and supports flushes. Betwixt contains an alternative implementation that does not cache anything. Users that require more sophisticated caching strategies should create custom implementations.
The
Other XMLIntrospector SettingsHere are discussed the important settings that haven't been covered already.
The
<PersonBean> <age>21</age> ...
isAttributesForPrimitives
is false but to
<PersonBean age='21'> ...
The
Using .betwixt files To Read And Write Mixed Content
An element with mixed content contains child elements and text.
For example, element
<foo> Some text <bar/> </foo>
text
elements in the
.betwixt file.
A
For example, a simple bean with the .betwixt file <?xml version="1.0" encoding="UTF-8" ?> <info primitiveTypes="attribute"> <element name='foo'> <attribute name='version' value='1.0'/> <element name='bar'> <attribute name='version' property='alpha'/> <text value='static text'/> <element name='baa' property='beta'/> <text property='gamma'/> </element> </element> </info> <foo version="1.0"> <bar version="One">static text<baa>Two</baa>Three</bar> </foo> Betwixt supports reading back mixed content in one special situation which happily is also a common use case. Betwixt will call a single property with all the mixed content text. So, only one mixed content property is specified then the bean can be written and then read back. Using ConvertUtils To Customize Conversion Of Primitives
Writing Beans (Advanced)Writing DynaBeansA DynaBean is a pseudo-bean whose properties may differ between instances of the same Class. Pseudo-introspection of the available properties uses a pseudo-class of type DynaClass. For more details see BeanUtils .
Betwixt now supports the (correct) writing of objects implementating DynaBean. Rather than using standard
introspection for these objects,
Reading Beans (Advanced)Adding Custom Digestion Rules
Betwixt builds on
Digester uses
These standard Betwixt mapping rules can be integrated with other Digester
Note
that care must be taken with the patterns for additional
Advanced UpdatersBetwixt by default uses the property write method for standard properties and matched stems for composite properties (for more details, see here ) to update property values when reading beans. These approaches should be sufficient for most cases. But this can be overruled on a per element basis.
By using a .betwixt file, the method used to update the bean can be controlled on a per element basis.
When the value of the
For example, the following betwixt file fragment: <?xml version="1.0" encoding="UTF-8" ?> <info primitiveTypes="element"> <element name="bean"> ... <element name='name' property='name' updater='nameSetter'/> ... </element> </info> ExamplesA Simple ExampleThis is a simple example to help those new to betwixt. It shows how a simple bean can be converted to xml and back again. A round trip, no less! In order to run these simple examples, the classpath needs to contain Betwixt and all it's dependencies . Note that any JAXP (1.1 or later) compliant parser can be used to replace xerces and xml-apis. JUnit is not required to run betwixt (but is needed if you want to run the unit tests in CVS). This example is based around a very simple bean representing a person: public class PersonBean { private String name; private int age; /** Need to allow bean to be created via reflection */ public PersonBean() {} public PersonBean(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String toString() { return "PersonBean[name='" + name + "',age='" + age + "']"; } }
The basic usage pattern for writing beans using Betwixt is to create a BeanWriter, configure it
and then pass a bean into the
Here's a simple application which converts a person bean to xml which is then sent to
import java.io.StringWriter; import org.apache.commons.betwixt.io.BeanWriter; public class WriteExampleApp { /** * Create an example bean and then convert it to xml. */ public static final void main(String [] args) throws Exception { // Start by preparing the writer // We'll write to a string StringWriter outputWriter = new StringWriter(); // Betwixt just writes out the bean as a fragment // So if we want well-formed xml, we need to add the prolog outputWriter.write("<?xml version='1.0' ?>"); // Create a BeanWriter which writes to our prepared stream BeanWriter beanWriter = new BeanWriter(outputWriter); // Configure betwixt // For more details see java docs or later in the main documentation beanWriter.getXMLIntrospector().setAttributesForPrimitives(false); beanWriter.setWriteIDs(false); beanWriter.enablePrettyPrint(); // Write example bean as base element 'person' beanWriter.write("person", new PersonBean("John Smith", 21)); // Write to System.out // (We could have used the empty constructor for BeanWriter // but this way is more instructive) System.out.println(outputWriter.toString()); } } Ths basic usage pattern for reading beans is only a little more complex. This time, you need to create a BeanReader, configure it, register the bean classes that the xml will be mapped to and then parse should be called.
Here's a simple application that converts some xml to a person bean. The bean is then converted
to string and the result sent to
import java.io.StringReader; import org.apache.commons.betwixt.io.BeanReader; public class ReadExampleApp { public static final void main(String args[]) throws Exception{ // First construct the xml which will be read in // For this example, read in from a hard coded string StringReader xmlReader = new StringReader( "<?xml version='1.0' ?><person><age>25</age><name>James Smith</name></person>"); // Now convert this to a bean using betwixt // Create BeanReader BeanReader beanReader = new BeanReader(); // Configure the reader // If you're round-tripping, make sure that the configurations are compatible! beanReader.getXMLIntrospector().setAttributesForPrimitives(false); beanReader.setMatchIDs(false); // Register beans so that betwixt knows what the xml is to be converted to // Since the element mapped to a PersonBean isn't called the same, // need to register the path as well beanReader.registerBeanClass("person", PersonBean.class); // Now we parse the xml PersonBean person = (PersonBean) beanReader.parse(xmlReader); // send bean to system out System.out.println(person); } } A Rich Site Summary MappingIn the RSS example from Digester there's a bean which matches this pattern. public class Channel public Item[] getItems(); public void addItem(Item item); } This means that the following bean does not match this naming convention, since the plural property name does not start with the singular name. public class Foo { public Collection getPeople(); public void addPerson(Person person); } Though these two beans do match public class Foo { public Collection getPersonCollection(); public void addPerson(Person person); } public class Foo { public Iterator getPersonIterator(); public void addPerson(Person person); } The following are other valid examples of composite-getter methods and their matching adder methods.
|