This is a simple step-by-step Betwixt
tutorial focussing
on creating a mapping for an RSS 0.91 application.
Betwixt
is a dynamic, start-from-java XML-object binder.
For those unfamiliar with these terms, this means that it maps XML <-> objects
defining a binding from an existing group of java objects into xml (and back again)
without the use of code generation.
So, the place we'll start is with an object model for RSS 0.91. This was originally created for Digester by Craig R. McClanahan and Ted Husted with a few amendments for this tutorial. There are only four main objects in the model:
The application simply processes an xml file in RSS format and creates a plain text version. This isn't really very useful but is a good demonstration of bean reading.
RSS 0.91 is an XML vocabulary for describing metadata about websites commonly used to supply news channels. RSS 0.91 is a little elderly and has quite a simple - but elegant - DTD:
<!ELEMENT rss (channel)> <!ATTLIST rss version CDATA #REQUIRED> <!-- must be "0.91"> --> <!ELEMENT channel ( title | description | link | language | item+ | rating? | image? | textinput? | copyright? | pubDate? | lastBuildDate? | docs? | managingEditor? | webMaster? | skipHours? | skipDays?)*> <!ELEMENT title (#PCDATA)> <!ELEMENT description (#PCDATA)> <!ELEMENT link (#PCDATA)> <!ELEMENT image (title | url | link | width? | height? | description?)*> <!ELEMENT url (#PCDATA)> <!ELEMENT item (title | link | description)*> <!ELEMENT textinput (title | description | name | link)*> <!ELEMENT name (#PCDATA)> <!ELEMENT rating (#PCDATA)> <!ELEMENT language (#PCDATA)> <!ELEMENT width (#PCDATA)> <!ELEMENT height (#PCDATA)> <!ELEMENT copyright (#PCDATA)> <!ELEMENT pubDate (#PCDATA)> <!ELEMENT lastBuildDate (#PCDATA)> <!ELEMENT docs (#PCDATA)> <!ELEMENT managingEditor (#PCDATA)> <!ELEMENT webMaster (#PCDATA)> <!ELEMENT hour (#PCDATA)> <!ELEMENT day (#PCDATA)> <!ELEMENT skipHours (hour+)> <!ELEMENT skipDays (day+)>
Since Betwixt
is start-from-java, a DTD is not required.
This has many advantages during prototyping but this tutorial will
concentrate on a target specified by DTD (since it's a little more instructive).
What's probably needed in both cases is a good understanding of the xml
that the object model maps to.
Every xml document has a root node.
Every Betwixt
mapping starts from a root object.
The process of creating a mapping between the xml document and the object model
in Betwixt
typically begins by working out the structural relationship
between the elements in the xml and the objects in the property subgraph of that root object.
Channel
is the root object mapping to the <channel>
root element
of the xml. There are several ways that a mapping for an object can be defined in
Betwixt
. In this case, a .betwixt
xml file will be used.
This following file is built into the jar in the package containing Channel
:
<?xml version="1.0" encoding="UTF-8" ?> <info primitiveTypes="element"> <element name="rss"> <attribute name="version" property="version"/> <element name="channel"> <element name="title" property="title"/> <element name="item" property="items"/> <element name="textinput" property="textInput"/> <element name="skipDays"> <element name="day" property="skipDays"/> </element> <element name="skipHours"> <element name="hour" property="skipHours"/> </element> <addDefaults/> </element> </element> </info>
This describes how elements and attributes in the xml are mapped to properties
in a Channel object. Betwixt
uses the type of the properties to
determine how to map the sub-graph under an element mapped to a property.
So, for example the textInput
property gets and sets a TextInput
object so the mapping for TextInput
is used.
There are a number of collective properties: those that are mapped to a many
objects (rather than just one). For example, the
items
property is backed by an List
and is mapped to zero, one or more <item>
tags.
Betwixt
looks for an adder method (something like addItem
) which matches
the collective property (which should return something like a Collection)
and uses that to add instances of the object created from the xml contained.
This mechanism can be customized but the defaults are fine in this case.
The standard mapping for the class contained with the collection is used.
Notice the <addDefaults/>
tag. This tells Betwixt
to use it's introspection system to automagically determine the mappings
for all other properties. Betwixt
has a number of powerful
strategy plugins which allow the introspection process to be controlled
in a finely-grained manner but in this case, the default mappings of the
remaining properties work fine :)
See binding for more details.
Image uses a smaller .betwixt
file:
<?xml version="1.0" encoding="UTF-8" ?> <info primitiveTypes="element"> <element name="image"> <element name="url" property="URL"/> <addDefaults/> </element> </info>
Item
is an example of the other (main) way that mappings are created for objects.
There is no .betwixt
file so Betwixt
introspects the
class using reflection and determines a mapping based on the current introspector
configuration. In this case, it's exactly what's required so there's no need to change
TextInput is also mapped by using introspection.
org.apache.commons.betwixt.io.BeanReader
reads beans from xml documents.
The
tutorial application
reads an xml file (specified on the command line)
and prints a plain text version. The root object (in this case Channel
)
needs to be
registered
first. Then
parse
can be used to parse an XML document and produce a bean.