The 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 WebApp
might need to be mapped to an element called web-app
.
Betwixt supports customization of these mappings through plug-in implementations of the
org.apache.commons.betwixt.strategy.NameMapper
interface. It is often useful to allow
different mappings for elements and attribute and so different implementations can be set for
elements and attributes. The default NameMapper implementation simply returns the type name
without modification.
Note that the attribute and element names given in a .betwixt file (as usual)
override the name mapping settings on the XMLIntrospector
.
Betwixt supports pluggable conversion of type names to element names. Setting the
ElementNameMapper
property on an XMLIntrospector
determines
how names from the bean will be converted into element names.
Betwixt supports pluggable conversion of type names to attribute names. Setting the
AttributeNameMapper
property on an XMLIntrospector
determines
how names from the bean will be converted into attribute names.
Here'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.
When using a public nested class (whether static or not) the naming of the base type
passed into the NameMapper
obeys the standard inner classes naming semantics
(using $). Typically, the mappers that ship with Betwixt
ignore this symbol
during type name processing. It will therefore appear in the name of the element.
For example, running the following application:
public class InnerClassWriter { 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; } } public static final void main(String args[]) throws Exception { // create write and set basic properties BeanWriter writer = new BeanWriter(); writer.getXMLIntrospector().getConfiguration() .setAttributesForPrimitives (true); writer.enablePrettyPrint(); writer.getBindingConfiguration().setMapIDs(false); // set a custom name mapper for attributes writer.getXMLIntrospector().getConfiguration() .setAttributeNameMapper(new HyphenatedNameMapper()); // set a custom name mapper for elements writer.getXMLIntrospector().getConfiguration() .setElementNameMapper(new DecapitalizeNameMapper()); // write out the bean writer.write(new InnerClassWriter().new TallTreeBean(15.1f)); } }
results in:
<innerClassWriter$TallTreeBean height-of-tree="15.1"/>
It is recommended that those that make extensive use of inner classes create
a custom NameMapper
implementation that processes inner class names
appropriately.
Note: that since Betwixt
uses reflection during
introspection, the usual rules concerning class scoping and visibility apply.
For example, if the scope of the nested class in the example above is changed from public to protected, friendly or private then the following result will be produced:
<innerClassWriter$TallTreeBean/>
This 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.
Betwixt allows this auto-detection of plurals from singulars to be customized.
Implementations of org.apache.commons.betwixt.PluralStemmer
allow different
strategies for this mapping to be plugged into XMLIntrospector
.
The strategy used by XMLIntrospector
to match singular properties and plural methods
is determined by the PluralStemmer
property value.
One important usage of custom plural stemmers is to support classes with non-english method names.
A custom PluralStemmer
implementation can be created containing the plural rules for
the language. Betwixt will then be able to recognize matching plural and singular methods.
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
FooBars
for a singular property called FooBar
.
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 <entry>
element. That element contains the key wrapped in a <key>
element and the entry value wrapped in a <value>
element.
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 add*
method that takes a single parameter, now Betwixt looks
(in a similar fashion) for such a method that takes two parameters.
Introspection is slow and so caching the results improves performance. Though the writers
and readers can - by nature - only be used safely in a single thread, a single
XMLIntrospector
instance can be safely shared amongst multiple threads. Sharing a
single instance will improve performance by sharing it's XMLBeanInfo
cache.
The XMLBeanInfoRegistry
interface allows finely grained, pluggable control over
the caching strategy used by a XMLIntrospector
. The implementation used can be set
by passing an instance to XMLIntrospector.setRegistry
.
Before using the standard introspection techniques to create a new XMLBeanInfo
instance
for a bean, the current XMLBeanInfoRegistry
is first checked. Only if the registry
does not return an XMLBeanInfo
will a new one be created. Once a new instance has been
created by introspection, the XMLBeanInfoRegistry
implementation will be called so that
the XMLBeanInfo
can (if required) be cached.
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 XMLBeanInfoRegistry
can also be used to override the standard introspection mechanism
on a per class basis. The safest way to do this is to create a custom XMLBeanInfoRegistry
implementation that pre-loads XMLBeanInfo
's for the required classes. If flush is called,
the cache should be reset that it contains only those that it contained at the start.
Here are discussed the important settings that haven't been covered already.
The AttributesForPrimitives
property determines whether a primitive
type (including strings) should be - by default - mapped to elements or attributes. For example, a
property called Age
of a bean called PersonBean
would be mapped to something
like:
<PersonBean> <age>21</age> ...
isAttributesForPrimitives
is false but to
<PersonBean age='21'> ...
More finely grained control over which primitive properties are mapped to elements and which
to attributes can be supplied by creating a custom SimpleTypeMapper
strategy
and plugging that into the introspection configuration.
Here is an example implementation (which maps strings to elements and everything else to attributes):
/** Implementation binds strings to elements but everything else to attributes */ public class StringsAsElementsSimpleTypeMapper extends SimpleTypeMapper { /** * Binds strings to elements but everything else to attributes */ public Binding bind( String propertyName, Class propertyType, IntrospectionConfiguration configuration) { if (String.class.equals(propertyType)) { return SimpleTypeMapper.Binding.ELEMENT; } return SimpleTypeMapper.Binding.ATTRIBUTE; } }
This class can be used to configure Betwixt
so that strings are bounds to
elements but other primitives are bound to attributes as follows:
XMLIntrospector introspector = new XMLIntrospector(); introspector.getConfiguration().setSimpleTypeMapper( new StringsAsElementsSimpleTypeMapper()); ...
The WrapCollectionsInElement
property determines whether the elements
for a composite property (i.e. one that returns a collection or iterator) should be wrapped in a parent
element. For example, if isWrapCollectionsInElement
is true then a property with signature
List getChildren()
would wrap a <children>
element around the elements
representing the contents of the list.
An element with mixed content contains child elements and text.
For example, element foo
has mixed content in the following:
<foo> Some text <bar/> </foo>
text
elements in the
.betwixt file.
A text
element can be mapped to a property in which case it must have
a property
attribute and may (optionally) have a type
attribute.
Otherwise, the text
element is mapped to a static value, in which case it
must have a value
attribute. If a text
element has both
value
and property
attributes then an exception will be thrown.
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.
When an Object is introspected by the XMLIntrospector
, the class is examined and
an XMLBeanInfo
created based upon it. But there are occasions when some variation
is needed - for example when reading or writing Entity Beans or Proxy implementations.
Betwixt provides the ClassNormalizer
strategy class as a way to allow users to customise
the process by which Betwixt works out which Class should be introspected for a given Object.
The default implementation simply supplies the Class of the Object.
But by setting the classNormalizer
property of XMLIntrospector
,
a custom implementation can be used instead.
Betwixt supplies a second implementation called ListedClassNormalizer
.
This contains a list of classes to match together with the Class which should be returned
when the Class is matched.
For example, take a class FaceImpl
that implements an interface IFace
.
Betwixt will introspect every instance of FaceImpl
as if it just implemented
IFace
by use of the following code:
XMLIntrospector introspector = ...;
ListedClassNormalizer classNormalizer = new ListedClassNormalizer();
classNormalizer.addSubstitution( IFace.class );
introspector.setClassNormalizer( classNormalizer );
As well as the finely grained tuning available when using dot betwixt files,
it is sometimes very useful to be able to ignore all properties with a certain name
or a certain type. For example, the default Betwixt configuration ignores all properties
called 'class'. The PropertySuppressionStrategy
pluggable strategy can
be set on the IntrospectionConfiguration
and allows course grained rules
concerning which properties are to be ignored to be set.
The following example shows a PropertySuppressionStrategy
that shows all
properties, including the class property:
beanWriter.getXMLIntrospector().getConfiguration().setPropertySuppressionStrategy( new PropertySuppressionStrategy() { public boolean suppressProperty(Class clazz, Class type, String name) { return false; } });
Here is another example making the choice dependent on what class contains the
property. This one shows the class property only for classes like
Throwable
, Exception
, Error
and so on:
beanWriter.getXMLIntrospector().getConfiguration().setPropertySuppressionStrategy( new PropertySuppressionStrategy() { public boolean suppressProperty(Class classContainingTheProperty, Class propertyType, String propertyName) { if (Class.class.equals(propertyType) && "class".equals(propertyName)) { if (!Throwable.class .isAssignableFrom(classContainingTheProperty)) { return true; } } return false; } });
A collection may contain objects of several different types. If you
specify a name for the collections elements in the dot-betwixt-files (for these
'mixed' collections), you can't say by its XML element what kind of object it was.
Therefore, by omitting the name
-attribute for a collection in
dot-betwixt-files, Betwixt will map each object contained in that collection separately.
As an example, we assume a small TestClass
having only one property
named col
of type java.util.Collection
. The collection
contains object of different types. With a usual dot-betwixt-file we would expect
to have all items in the collection be of the same element-type in the XML output.
Now we omit the name and let Betwixt guess the names for the items in the collection:
<?xml version="1.0" encoding="ISO-8859-15"?> <info primitiveTypes="element"> <element name="test-class"> <element name="mixed-collection"> <element property="col"/> <!-- without name-attribute! --> </element> </element> </info>
<test-class> <mixed-collection> <string>String x</string> <integer>5</integer> <another-class> ... </another-class> </mixed-collection> </test-class>
Betwixt finds the getter from the bean property. When a custom updater is specified, though,
Betwixt uses reflection to find a matching method name. By default, Betwixt will only search
for a public
method with the correct name. This behaviour can be controlled through
the dot betwixt file. The forceAccessible
attribute of the element
element
allows this behaviour to be configured.
For example, consider a bean such as:
public class SomeBean { ... public SomeClass getSomeProperty() { ... } void setSomeProperty(SomeClass value) { ... } ... }
By default, the setter would not be found. To ensure that the method is found, the following dot betwixt fragment could be used:
... <element name='SomeName' property='someProperty' updater='setSomeProperty' <!-- this attribute must be present --> forceAccessible="true"/> <!-- for this line to work --> ...
Note: when this attribute is set to true, Betwixt will conduct a search upwards through
the superclasses if no matching methods are found in the implementation class. Also note that
the updater
must be specified even if the method conforms to JavaBean
standards in order to override the accessibility. Overriding the accessibility of a
method is subject to the SecurityManager
currently in effect.
ConvertUtils
is part of commons-beanutils
and it can be used to flexibly convert strings to objects and back again. By default, Betwixt uses ConvertUtils to
perform these conversions and so standard ConvertUtils
methods can be called to customize these
conversions.
There are numerous situations when read beans from xml or writing beans to xml that String to Object or Object to String conversions are required. Betwixt uses a Strategy class to allow a convenient default which will work well for most basic users whilst allowing advanced users to fully hook in and customize this process.
The default strategy uses ConvertUtils
from
commons-beanutils
to perform these conversions.
This is a powerful component that allows flexible customization of the conversion process.
There is one exception to this rule. If the class is a java.util.Date
- or a subclass of java.util.Date
which is not a subclass of java.sql.Date
, java.sql.Time
or java.sql.Timestamp
- then this is converted to and from a string following this pattern:
(using the SimpleDateFormat notation). Observant readers will realise that this is the same pattern
that is returned by
EEE MMM dd HH:mm:ss zzz yyyy
java.util.Date.toString
- and that's why this pattern was chosen.
It provides a good default for casual users.
Advanced users will probably need a particular date format. The recommended way to do this is through
registering appropriate converters with ConvertUtils. The default conversion strategy must also be
replaced with an instance of
ConvertUtilsObjectStringConverter
. This is set though a BindingConfiguration
property.
For example, to delegate to ConvertUtils
for all conversions in a read:
and in a write:
BeanReader reader = new BeanReader();
reader.getBindingConfiguration()
.setObjectStringConverter(new ConvertUtilsObjectStringConverter());
reader.parse...
BeanWriter writer = new BeanWriter();
writer.getBindingConfiguration()
.setObjectStringConverter(new ConvertUtilsObjectStringConverter());
writer.write...
ConvertUtils
is flexible and powerful. It comes with a range of Converter
implementations which allow quick and easy customization. But, there are occasions where this will
not suit all the requirements of the user. Betwixt supports this need by allowing a custom
ObjectStringConverter
to be plugged in.
The strategy class
ObjectStringConverter
is simple: containing only two simple methods. For more information about creating subclasses, see the javadocs.
The implementation to be used is set through the BindingConfiguration
ObjectStringConverter
property.
For example, to set a custom ObjectStringConverter
for all conversions in a read:
and in a write:
ObjectStringConverter converter = new MyObjectStringConverter();
BeanReader reader = new BeanReader();
reader.getBindingConfiguration(converter);
reader.parse...
ObjectStringConverter converter = new MyObjectStringConverter();
BeanWriter writer = new BeanWriter();
writer.getBindingConfiguration(converter);
writer.write...
Betwixt is distributed with a range of ObjectStringConverter
's in the
org.apache.commons.betwixt.strategy
package. Examining the source code for these classes
is a good please to start when creating your own implementation.
One problem facing those who create custom converters is that objects of the same type
may (in different contexts) require different conversions. For example, a java.util.Date
may in one context have the semantic meaning of a day (and so the conversion needs to suppress the
time component) and in another may indicate a date time (and so the conversion in this case needs to
render the time components). The same type may come in different flavours.
The recommended way to solve this kind of problem is to added options to the betwixt file
which can then be picked up by the custom converter and used to determine the appropriate
conversion. Note that options are not directly inherited. Instead use
getInheritedOption
on Context
.
For example, consider a bean representing a person with an attribute
giving that person's date of birth. Suppose that this is stored
as a java.util.Date
. This needs to be rendered as
a date string (without a time component). A good approach would be
to add an option to the dot betwixt file as follows:
<?xml version='1.0'?> <info primitiveTypes="attribute"> <element name=''> <element name='birthday' property='birthday'> <option> <name>org.apache.commons.betwixt.flavour</name> <value>Day</value> </option> </element> <addDefaults/> </element> </info>
The following code snippet illustrates how a custom converter can use this information:
public String objectToString(Object object, Class type, Context context) { String flavour = null; Options options = context.getOptions(); if (options != null) { flavour = options.getValue("org.apache.commons.betwixt.flavour"); } if ("Day".equals(flavour)) { // render as date with no time component ... } else { // Do normal rendering ... } }
Of course, the choice of the option name is purely arbitrary.
There are occasions when it proves useful to be able to override the standard
loading behaviour for .betwixt
mapping file. For example, this is
one way in which multiple mapping for the same object can be supported (but see
later sections for more sophisticated solutions to this problem).
Betwixt supports this by providing writing and reading methods which allow
an InputSource
specifying a .betwixt
document to be passed in.
For example:
StringReader dotBetwixtDocument = new StringReader( "<?xml version='1.0' ?>" + "<info>" + " <element name='address'>" + ... " </element>" + "</info>"); BeanReader reader = new BeanReader(); reader.registerBeanClass(new InputSource(dotBetwixtDocument), Address.class); Address address = reader.parse(in);
parses the input document using the mapping specified in the string whilst:
StringReader reader = new StringReader( "<?xml version='1.0' ?>" + "<info>" + " <element name='address'>" + ... " </element>" + "</info>"); BeanWriter writer; ... writer.write(bean, new InputSource(reader));
writes out a bean according to the mapping given in the string.
This xml document format extends the .betwixt
vocabulary so that several
different classes mapping can be registered by a single document.
The mapping file format is an intuitive extension to the standard .betwixt format.
The root element is <betwixt-config>
which contains one or more
<class>
elements. Each of these specifies a class and contains
a mapping definition for that class (as found in a standard dot betwixt document).
For example:
<?xml version="1.0"?> <betwixt-config> <!--name of the class to map --> <class name="org.some.package.MyClass"> <!-- standard definitions (same as in standard .betwixt file) --> <element name="repository-registration-result"> <element name="repository-id" property="repositoryId"/> <element name="id-mapping" property="idMapping" class="org.some.package.SomeOtherClass"/> <element name="status" property="status"/> <element name="exception" property="exception"/> <element name="primary-luid" property="primaryLuid"/> <addDefaults add-properties='false'/> </element> </class> ... <!--additional class mappings --> <class> ... </class> ... </betwixt-config>
Multi mappings are used directly to register multiple mappings with a single introspector. For example,
String MAPPING = "<?xml version='1.0'?>" + " <betwixt-config>" + " <class name='org.apache.commons.betwixt.PartyBean'>" + " <element name='party'>" + ... " </class>" + ... " </betwixt-config>"; BeanReader beanReader = new BeanReader(); beanReader.registerMultiMapping(new InputSource(new StringReader(MAPPING))); ... PartyBean result = (PartyBean)beanReader.parse(xmlReader);
registers all mappings in the file then reads beans according to those settings. The following does something similar for writing:
String MAPPING = "<?xml version='1.0'?>" + " <betwixt-config>" + " <class name='org.apache.commons.betwixt.PartyBean'>" + " <element name='party'>" + ... " </class>" + ... " </betwixt-config>"; BeanWriter beanWriter = new BeanWriter(outputWriter); ... beanWriter.getXMLIntrospector() .register(new InputSource(new StringReader(MAPPING))); beanWriter.write(partyBean);
Betwixt maps an entire object graph. So, though it might see (at first) that specifying a custom dot betwixt is all that's required, for all (but the most simple cases) several mappings must be specified to map the graph corrected. This is where multi mapping documents become very useful.
A common usage pattern for this problem is to use one XMLIntrospector
for each of the different ways that the mappings should be done and register the mappings
through a multi mapping file. All the reader and writers for that particular type can
then share the same introspector.
It is common for users of Betwixt to need to develop their own custom strategies to handle some parts of the binding process. This section contains some information that may of of some use for those people. Help to create a more comprehensive guide would be appreciated.
Options provide an extensible way for extra mapping information to be communicated
from the binding to those components executing the mapping. Each Descriptor
exposes an
Options property
.
This contains a set of values indexed by name (both are strings). These options can be
set programmatically during the binding. They can also be set through the .betwixt
file.
Setting option values through the .betwixt
file is easy: just add an
<option>
child element to an <element>
element.
For example, the XMLBeanInfo
for the following betwixt file:
<?xml version='1.0'?> <info primitiveTypes="attribute"> <element name='some-bean'> <element name='some-property' property='someProperty'> <option> <name>org.apache.commons.betwixt.example-one</name> <value>value-one</value> </option> </element> <element name='another-property' property='anotherProperty'> <option> <name>org.apache.commons.betwixt.example-two</name> <value>value-two</value> </option> </element> </element> </info>
will have the value value-one associated with the option name
org.apache.commons.betwixt.example-one in the options for the
some-property
descriptor and value-two
associated with org.apache.commons.betwixt.example-two
for another-property
.
Note that the last value set for a particular option name is the one that
will be used.
The recommended convention for naming option's is to prefix with the reverse domain
name (the same convention that is used for the standard naming of packages).
In any case, all option names beginning with org.apache
should be avoided since
these may be used by ASF products in the future.
At the moment, there is no support for inheritance of options (from parent to child) through this may be considered later if there is sufficient demand.