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. Nested Classes
When using a public nested class (whether static or not) the naming of the base type
passed into the 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
Note: that since 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/> 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 Customized Mapping (Advanced)Caching and the XMLRegistry
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
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'> ...
More finely grained control over which primitive properties are mapped to elements and which
to attributes can be supplied by creating a custom 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 XMLIntrospector introspector = new XMLIntrospector(); introspector.getConfiguration().setSimpleTypeMapper( new StringsAsElementsSimpleTypeMapper()); ...
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. Introspection And Normalization
When an Object is introspected by the
Betwixt provides the
Betwixt supplies a second implementation called
For example, take a class
Ignoring Properties
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
The following example shows a beanWriter.getXMLIntrospector().getConfiguration().setPropertySuppressionStrategy( new PropertySuppressionStrategy() { public boolean suppressProperty(Class clazz, Class type, String name) { return false; } });
Here is another example making the choice dependant on what class contains the
property. This one shows the class property only for classes like
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; } }); Mixed Collections - Guessing Element Names
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
As an example, we assume a small <?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> Converting Objects And Primitives To Strings (And Back Again)Using ConvertUtils To Customize Conversion Of Primitives
Converting Dates (And Other Objects)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
There is one exception to this rule. If the class is a
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
For example, to delegate to Custom ObjectStringConverters (Advanced)
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
For example, to set a custom
Betwixt is distributed with a range of Flavours For Custom Converters
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 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.
For example, consider a bean representing a person with an attribute
giving that person's date of birth. Suppose that this is stored
as aa <?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. Multi MappingCustom Dot Betwixt Documents
There are occasions when it proves useful to be able to override the standard
loading behaviour for
Betwixt supports this by providing writing and reading methods which allow
an 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. Multi Mapping Document Format
This xml document format extends the For example: <?xml version="1.0"?> <betwixt-config> <!--name of the class to map --> <class name="org.some.package.MyClass"> <!-- standard definations (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); Multiple Mappings For The Same ObjectBetwixt 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 (Brief) Guide To Creating Custom Strategy PluginsIt 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. Using Options
Options provide an extensible way for extra mapping information to be communicated
from the binding to those components executing the mapping. Each
Setting option values through the <?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
The recommended convention for naming option's is to prefix with the reverse domain
name (the same convension that is used for the standard naming of packages).
In any case, all option names beginning with 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. |