Betwixt uses the type of the corresponding mapped property to determine the class to be instantiated when reading xml. But what happens when the type of the mapped property is an interface or an abstract class and so cannot be instantiated? Well - unless steps are taken to solve this problem, the read will fail.
Betwixt provides a number of different ways to solve this problem. One solution is to use derived beans . This is flexible but means coupling the xml to a java class structure. Another solution is to use custom bean creation to ensure that an appropriate class is created. Other solutions follow in this section.
The class to be instantiated when a (mapped) element is read can be specified via the class
attribute. When present, this should be a fully qualified java class name. A bean of this type
will then be instantiated when the element is read.
For example, here's a bean:
package org.apache.commons.betwixt.example; import java.util.ArrayList; import java.util.List; public class ExampleBean { private String name; private List examples = new ArrayList(); public ExampleBean() {} public String getName() { return name; } public void setName(String name) { this.name = name; } public List getExamples() { return examples; } public void addExample(IExample example) { examples.add(example); } }
IExample
interface:
package org.apache.commons.betwixt.example; public interface IExample { public int getId(); public void setId(int id); public String getName(); public void setName(String id); }
package org.apache.commons.betwixt.example; public class ExampleImpl implements IExample { private int id; private String name; public ExampleImpl() {} public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
example
element if the following ExampleBean.betwixt
file:
<?xml version="1.0" encoding="UTF-8" ?> <info> <element name='example-bean'> <element name='example' property='examples' class='org.apache.commons.betwixt.example.ExampleImpl'/> <addDefaults/> </element> </info>
Betwixt builds on Apache Digester
for bean reading.
BeanReader
extends Digester
and this makes a wide range of functionality available.
Digester uses Rule
's to specify the xml mapping (for more details see the
Digester documentation
).
Betwixt provides a custom ruleset (BeanRuleSet
). This creates Rule
's that
implement the standard Betwixt mapping for a class. BeanReader.registerBeanClass
uses this RuleSet
to add these standard betwixt mapping Rule
's
for the bean class.
These standard Betwixt mapping rules can be integrated with other Digester Rule
's.
Rule
's added before registerBeanClass
is called will come before (in a
Digester sense) the standard betwixt Rule
's. Those added after will come afterwards.
Note that care must be taken with the patterns for additional Rule
's.
The standard Betwixt mapping will only work if all it's Rule
's are matched.
Betwixt 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 updater
attribute of an <element>
element is set,
Betwixt will try to find a bean method with a matching name which takes a single parameter.
When such a matching method exists, this will be called to update the property value.
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>
Betwixt uses the Chain Of Responsibility pattern to decide the object which should be created
for a particular element. The standard chain contains ChainedBeanCreator
s which implement
functionality such as standard bean creation based on the expected type and the returning of beans by ID.
This allows users to easily insert their own ChainedBeanCreator
s into the chain - or to
replace the standard Betwixt chain completely.
The chain used by the BeanReader is part of the ReadConfiguration
and is accessed via the
BeanCreationChain property. For example the following sets a custom chain.
BeanCreationChain chain = MyBeanCreationChain(); BeanReader reader = new BeanReader(); ... reader.registerBeanClass("bean", Bean.class); reader.getReadConfiguration().setBeanCreationChain(chain); ... Bean bean = (Bean) reader.parse(in); ...
Betwixt provides a standard (list-backed) chain called BeanCreationList. This provides an easy methods to
register your own ChainedBeanCreator
. It also provides a factory method which creates an instance
with the standard betwixt chain already set. For example, the following inserts a custom in second place:
BeanCreationList chain = BeanCreationList.createStandardChain(); BeanCreator creator = MyBeanCreator(); chain.insertBeanCreator(1, creator);
Another useful class is ChainedBeanCreationFactory
. This contains factory methods for the
BeanCreator
s used by Betwixt. This allows a user to easily mix custom and standard creators.
Herein is contained a practical example demonstrating how custom bean creation may be used.
A common java pattern is the use of strongly typed Enum classes. Let's say that you have the following class:
public class CompassPoint { public static final CompassPoint NORTH = new CompassPoint("North"); public static final CompassPoint SOUTH = new CompassPoint("South"); public static final CompassPoint EAST = new CompassPoint("East"); public static final CompassPoint WEST = new CompassPoint("West"); private String name; private CompassPoint(String name) { this.name = name; } public String getName() { return name; } }
CompassPoint
objects are not beans and do not
have the empty constructors that Betwixt requires.
A good way to solve this problem is to create a custom BeanCreator which knows how to create an enum of the right type from the 'name' attribute value. For example:
public class CompassPointEnumCreator implements ChainedBeanCreator { public Object create(ElementMapping mapping, ReadContext context, BeanCreationChain chain) { if (CompassPoint.class.equals(mapping.getType())) { String value = mapping.getAttributes().getValue("name"); if ("North".equals(value)) { return CompassPoint.NORTH; } if ("South".equals(value)) { return CompassPoint.SOUTH; } if ("East".equals(value)) { return CompassPoint.EAST; } if ("West".equals(value)) { return CompassPoint.WEST; } } return chain.create(mapping, context); } }
Once this class has been created, all that remains is to add this into the chain. In this case, it's probably most convenient to use the factory method to create a standard chain and then insert the BeanCreator at a suitable position:
BeanCreationList chain = BeanCreationList.createStandardChain(); chain.insertBeanCreator(1, new EnumCreator()); ... BeanReader reader = new BeanReader(); reader.getXMLIntrospector().setAttributesForPrimitives(true); reader.getXMLIntrospector().setWrapCollectionsInElement(false); reader.getReadConfiguration().setBeanCreationChain(chain); ...
IncludeBeanCreator
is a ChainedBeanCreator
that implements a system
of 'includes' files. Adding this class to the bean creation chain will load and read the file
specified by the include-file
attribute whenever it is encountered.
The file will be loaded as a resource by the current ClassLoader
using the standard rules.
A similar effect could be accumplished through the using of xml entity's. There are occasions when this is inconvenient and so it's useful to have an alternative available.
A polymorphic mapping is one where the decision about the element type and name is postponed from introspection time to bind time. This allows reading of collections containing mixed types distinguished by element name. For example:
<?xml version="1.0" ?> <container> <elementA/> <elementB/> </container>
Polymorphic mappings should be set up using dot betwixt files. The name attribute for the element should be omitted and mappings registered for the contained object types. It is usually more convenient to use a single file containing all the mappings. For example:
<?xml version="1.0"?> <betwixt-config> <class name='SomeContainer'> <element name='container'> <!-- Polymorphic so no name attribute --> <element property='element'/> </element> </class> <!-- Need to register mappings for types of contained elements (when Betwixt default strategy is used) --> <class name='ContainedElementA'> <element name='elementA'/> </class> <class name='ContainedElementB'> <element name='elementB'/> </class> </betwixt-config>
By default, in this circumstance Betwixt will try to guess the correct resolution
by searching all registered XMLBeanInfo
's for an appropriate match.
If more than one is found, an arbitrary one is used.
In many cases, this accords well with intuition. There are
occasions when more finely grained control may be required. The resolution is
therefore factored into PolymorphicReferenceResolver
(a pluggable strategy) on XMLIntrospector
.
A custom resolver allows alternative algorithms for determining
type to be used which can (for example) ignore the mappings registered.
Note that the default implementation is provided by the default
XMLBeanInfoRegistry
implementation.
Therefore, when using a custom registry a custom resolver must also
be used.
Note: when using mixed collections with dot betwixt files containing
addDefaults
it may be necessary to set the guess-names
attribute to false.