Beck maps between Java objects and XML.
See:
Description
Beck maps between Java objects and XML.
The mapping is configurable (via an XML configuration file)
and extensible (by adding Java software to Beck's framework).
Beck is not a binding tool. It does not generate Java classes
from XML schema, nor generate XML schema from Java classes. Beck
assumes you already have Java classes to contain your data.
Getting Started
To map from XML to Java, call a read method of an
ObjectXMLReader
.
To map from DOM to Java, call the read method of an
ObjectDOMReader
.
To map from Java to XML, call a write method of an
ObjectXMLWriter
.
To customize the mapping, pass a customized mapper object
to the constructor of your ObjectXMLReader, ObjectDOMReader or ObjectXMLWriter.
For most applications, we recommend you construct a
DefaultMapper
from XML, like this:
DefaultMapper mapper = DefaultMapper.newInstance
(ObjectXMLReader.parseConfiguration
(Configuration.MINIMAL_XML));
Replace Configuration.MINIMAL_XML
with your mapping XML (a String).
You can customize the mapping by changing your mapping XML.
For directions, see
Configuration.MAP_FROM_XML
.
It's often convenient to store the mapping XML in a file or resource,
and use ObjectXMLReader.read (instead of parseConfiguration) to read it.
More ambitious customizers can construct a mapper in other ways.
You can extend the DefaultMapper class, overriding some of its methods
to
customize its behavior, and configure the name of your new class in the
mapping XML.
You can hand-craft a Configuration class, from which you construct a
DefaultMapper (or customized extension).
Or you can eliminate the Configuration entirely,
and hand-craft a class that implements
MapFromXML
and/or
MapToXML
.
DefaultMapper implements both MapToXML and MapFromXML.
DefaultMapper
contains a Configuration, which can affect most of its behaviors.
DefaultMapper also contains a copy of the configuration parameters that
it has used, in a structure optimized for speed (a map from
Configuration.Selector to the resulting value).
Mapping to XML
Here's how the Beck software modules work together, to copy data from
objects to XML. It's helpful to understand this before you
customize the software.
Beck copies data from a source object and the objects to which it
refers, the objects to which they refer, etc. As Beck traverses
these objects,
it copies their data through a MapToXML to an XMLWriter. For each
source object, the MapToXML chooses a Marshaller. The Marshaller
chooses an XML element or attribute and writes it to the
XMLWriter. If the source object contains child objects, they are
handled in the same way (recursively), so their XML elements or
attributes are contained in the XML of their parent.
A Marshaller can (and often does) delegate behaviors to its MapToXML:
- Choosing an XML element or attribute for a given source
object. DefaultMapper delegates to a Namer to choose the XML
name, and delegates to the Marshaller to convert the object's value to
XML text.
- Identifying other objects that can be contained in a given
object. DefaultMapper does this by searching for fields or
JavaBean-style getter methods (using reflection).
MapToXML and Marshaller use a Getter to follow a reference from one
object to another. One kind of Getter calls a method to get a
JavaBean property. Other kinds of getters get a field, an array
element or a collection member. You can configure getters to
access private fields or JavaBean properties. And you can
implement custom getters; for example, to call other methods, follow a
chain of references or compute data.
Here are some guidelines for customization:
- Consider configuring, instead of customizing. It's often
easier.
- To make Beck traverse from one object to another, add a custom
Getter.
- To change XML simple content (element text or an attribute
value), add a custom Marshaller.
- To systematically change the names of XML things, add a custom
Namer.
When implementing a custom class, extend a class that's similar.
Read the javadoc of the interfaces you implement and classes you
extend. The Beck interfaces impose some requirements on
you. For
example, most objects should be immutable, to support re-entrant usage.
Beck traverses the graph of objects depth-first, using a recursive
algorithm. The traversal stack is the method call stack.
The traversal state is not contained in the MapToXML, Marshaller or
Getter objects. Many of those objects are singletons, and it's
common for a single instance (e.g. a single Marshaller) to be in
simultaneous (re-entrant) use at several levels of a traversal stack.
Beck also traverses the tree of XML elements and attributes.
There's another stack for this, in XMLWriter. You can put state
in this stack, by constructing a MapKey and using it to access your own
private element of each stack frame.
A MapToXML is not required to be thread-safe, and DefaultMapper is not
thread-safe. (Its configuration parameter cache is not
synchronized.) So don't allow concurrent threads to use a single
MapToXML. A simple way to avoid concurrent usage is to construct
a
new MapToXML each time you copy an object to XML. But you'll get
better performance if you re-use a DefaultMapper (because its cache is
warm). So, you might want to allocate mappers from a thread-safe
pool, such as a
DefaultMapperPool
.
Mapping from XML
Here's how the Beck software modules work together, to copy data from
XML to objects. It's helpful to understand this before you
customize the software.
Beck copies data from XML via a MapFromXML to new Java objects.
Beck receives XML in the form of an event stream; that is a sequence of
calls to methods of an XMLReceiver
chosen by the MapFromXML.
Often these come from a SAX2 event stream via
SAXContentHandler
.
To read from an XML document, use a SAX2 parser
(such as Xerces
or Piccolo).
To read from a DOM tree, use ObjectDOMReader.copy.
A less efficient alternative is to serialize the DOM tree to XML
and parse the XML with a SAX2 parser.
For each XML element, the MapFromXML allocates an
Unmarshaller
,
which usually constructs an
object and copies the data into it. To construct an object,
an Unmarshaller usually delegates to its
MapFromXML, which usually delegates to an ObjectFactory, which is
configurable.
The Unmarshaller class for a given situation is configurable. If it's
not configured, the DefaultMapper tries to determine the target object
class (or base class or interface), and choose an Unmarshaller appropriate for
that class. The object class can be configured, or chosen by the
parent Unmarshaller (e.g. the class of a JavaBean property or mapped from
the name of the source element or attribute (by a Namer).
Beck traverses XML elements and attributes in XML document order (that
is the sequence of events passed to the XMLReceiver). The
traversal stack is DefaultXMLReceiver.unmarshallerStack. Each Unmarshaller
has a reference to the Unmarshaller one deeper in the traversal stack (that
is its 'parent').
After traversing an XML element (and its contained elements),
DefaultMapper passes that element's Unmarshaller to its parent's addChild
method. The parent usually creates a reference to the child's
object; for example, the parent may set a JavaBean property that refers
to the child object, or add the child object to a collection.
An Unmarshaller is not re-entrant; it handles only one XML source at a
time.
But an Unmarshaller is serially re-usable; that is, a given Unmarshaller will be
used to handle multiple XML sources that are not nested. The
Unmarshaller pools are kept in DefaultMapper.unmarshallerClass2Pool.
Here are some guidelines for customization:
- To change the class or property mapped from a specific XML
element, use configuration. Use a <when> clause to identify
the element or attribute, with a nested <choose class="name"> or
<choose property="name">.
- To change class or property names systematically, configure a custom Namer.
- To change how data are copied into an object, configure a custom Unmarshaller.
- To change how references between objects are created, configure a custom Setter.
I can't think of any reason to customize the XMLReceiver. If you
want to implement a systematic XML transformation, you're probably
better off implementing a filter whose output is a SAX2 event
stream. If you don't want to supply a SAX2 event stream, you can
call the Unmarshaller interface instead. But it's not much
different.
Non-public Java
Ordinarily, Beck accesses properties of Java objects using JavaBean style
methods getProperty() or setProperty(newValue).
If no such method is available, Beck will access a Java field directly,
assuming that the field name is the property name.
Non-public properties can be copied from XML.
Beck will call setter methods or mutate fields regardless of whether
they're public, private, protected or package access.
You can prevent this (if it's a problem) by overriding
DefaultMapper.findDefaultSetters or findNonPublicSetters.
Beck copies only public properties to XML, by default.
That is, by default Beck won't call non-public getProperty methods,
nor read from non-public fields.
To copy from a non-public property, use a mapping configuration file element
like <choose includeProperties="privateProperty protectedProperty"/>.
Properties that are explicitly included in this way will be copied to XML
regardless of whether they're public.
As usual for Java software, Beck can't access non-public classes unless they're
declared in the same package as the accessor.
If you need to copy to or from a non-public class, we recommend you create
a class in the same package that extends a Beck class, and
configure Beck to use your new class.
Your extension class can access the non-public class (because it's in the same package)
but inherit most of its behavior from Beck.
An alternative is to declare the non-public class in the same package as Beck.
But we recommend you don't do this: it surprises people, and risks a name conflict.
XML Schema
Beck has rudimentary support for mapping to XML using XML schema.
Beck uses the schema to choose the namespace of XML elements and
attributes (if they aren't specified by the mapping), to select which
attributes and elements are (and are not) output, and to determine the
order of output elements. This is fairly crude. It's not
guaranteed to produce XML that conforms to the schema.
Cross References
Back can support cross references within an XML document, if you
configure DefaultMapper to use an Identifier
implementation.
When copying from Java to XML, the first reference to a given object
maps to the complete XML data, and subsequent references map to a brief
element that refers to the first. When copying from XML to Java,
the structure is re-created, with multiple Java references to a single
object.
The code that maps from XML is fairly complicated. It stores
dangling references and potential referents in the documentState of the
Unmarshaller. Look for the code that refers to
Identifier.IDREF_TO_DANGLING_REFERENCES and IDREF_TO_REFERENT.