Bindings¶
A binding is what transforms XML into a Java object and vice versa. Bindings can be attached to the following types of XML “components”:
elements
attributes
simple types
complex types
More specifically, a binding is an instanceof
of org.geotools.xsd.Binding
:
interface Binding {
/**
* @return The qualified name of the target for the binding.
*/
QName getTarget();
/**
* @return The java type this binding maps to.
*/
Class getType();
/**
* @return The execution mode of the binding, one of the constants AFTER,
* BEFORE, or OVERRIDE.
*
*/
int getExecutionMode();
}
Binding Target¶
Bindings must declare the component they are attached to, known as the target for the binding.:
QName getTarget();
This is achieved with a qualified name, an instance of javax.xml.namespace.QName
. A qualified name is made up of:
a namespace URI, and
a local parse
Examples:
Namespace |
Local |
Component |
---|---|---|
|
|
Global |
|
|
Simple type |
|
|
Complex type |
Binding Type¶
The job of a binding is to transform between objects and XML. Along with the target XML component, a binding declares the type of the resulting java object.:
Class getType();
Examples:
Target |
Type (Java Class) |
---|---|
|
|
|
|
|
|
Binding Execution¶
When an element or attribute in an XML document is being parsed, a Binding Execution Chain is created for it under your control:
int getExecutionMode();
The bindings in the chain are executed one after another and the result is the final representation of the element / attribute as a java object. Each binding receives the value produced by the binding before it.
Consider the following XML snippet, in which an element named integerElement
which is of type xs:integer
is being parsed:
<integerElement>25</integerElement>
By default, a binding execution chain is made up of the following bindings:
A binding for the element or attribute itself ( optional )
A binding for the type of the element / attribute
Bindings for each super type in the type hierarchy of the element / attribute
So for the above example, the following bindings would form the binding execution chain:
Once the binding chain has been formed, it is executed in order, with the input to the chain being the “raw” text of the element. At each binding a transformation occurs and is passed onto the next binding in the chain. The end result of which is the final transformed value for the element.
The default execution behavior is to execute after its “parent” binding has executed. However a binding may also specify different execution behavior, called its Execution Mode:
After
Binding executes after its parent. The input to the binding is the output of its parent. This is the default execution mode for Simple Bindings
Before
Binding executes before its parent. The output of the binding is then the input to its parent.
Override
Binding completely overrides its immediate parent and all grandparents. The input to the binding is the “raw” value of the element or attribute.
The execution mode which should be used really depends on both the XML type hierarchy involved, and the Java object model it maps to. Continuing with the above example, in the end the desired result is to turn the string “25”, into the integer 25. Using four bindings as suggested above seems to be overkill.
A more logical approach would be to have the “integer” binding perform the transformation directly. This can be achieved by having the integer binding declare its execution mode to be “OVERRIDE”:
Encoding
Similar to parsing, encoding is executed via a binding chain. The input to the chain is the object itself and the output is XML. The formation of the chain for encoding is identical to that of parsing. So for the previous example the binding chain that would result is:
Simple Bindings¶
Types in XML schema fall into two categories: Simple and Complex. For this reason bindings also fall into the same two categories.
Simple bindings are used to parse and encode elements and attributes which have simple types. The API for simple bindings looks like:
/**
* Parses an instance component (element or attribute) into an object
* representation.
*/
Object parse(InstanceComponent instance, Object value) throws Exception;
/**
* Performs the encoding of the object as a String.
*
*
*/
String encode(Object object, String value) throws Exception;
Parsing:
Object parse(InstanceComponent instance, Object value) throws Exception;
The parse method for simple bindings takes two parameters:
instance: The instance component ( element or attribute ) that is being parsed as an instance of
org.geotools.xsd.InstanceComponent
value: The parsed value of the element / attribute created by the previous binding in the execution chain ( explained in greater detail above ). If the binding is the first in the chain, this value is the raw text of the element / attribute as a String.
The return value of the method is the transformed object. Continuing with the
xs:integer
example, an implementation could be:Object parse(InstanceComponent instance, Object value) throws Exception { return Integer.parseInt( (String) value ); }
Encoding:
String encode(Object object, String value) throws Exception;
The encode method for simple bindings must serialize the element or attribute as a string.
It takes two parameters:
object: The object to serialize or encode as XML
value: The string value as encoded by the previous binding in the execution chain. If the binding is the first in the chain, the value is the result of calling
toString()
on the object parameter.
The return value of the method is the serialized value for the object. The example of
xs:integer
can be implemented as:String encode(Object object, String value) throws Exception { Integer integer = (Integer) object; return integer.toString(); }
AbstractSimpleBinding
The class
org.geotools.xsd.AbstractSimpleBinding
is available for sub-classing by binding writers.
Complex Bindings¶
Complex bindings are used to parse and encode elements which have complex types.
The interface for complex bindings looks like:
/**
* Parses a complex element from an instance document into an object
* representation.
*
*/
Object parse(ElementInstance instance, Node node, Object value) throws Exception;
/**
* Performs the encoding of the object into its XML representation.
*
*/
Element encode(Object object, Document document, Element value) throws Exception;
/**
* Returns a property of a particular object which corresponds to the
* specified name.
*
*/
Object getProperty(Object object, QName name) throws Exception;
Parsing:
Object parse(ElementInstance instance, Node node, Object value) throws Exception;
The parse method for a complex binding takes three parameters:
instance
: The element instance being parsed, an instance oforg.geotools.xsd.ElementInstance
nocde
:: A node in the current “parse tree” ( explained below ) at the time the binding is being executed, an instance of Nodevalue
: The parsed value as produced by the previous binding in the execution chain. This value is null for the first binding in the chain.
Complex types by definition are XML elements which are composed of other XML elements and attributes. A complex object could be defined as an object which is composed of other objects. Parsing a complex object really just amounts to rounding up objects for child elements, and composing the resulting object accordingly.
Parsing of complex elements occurs on the trailing edge. At which point all child elements and attributes have been parsed and placed in the “parse tree”. The parse tree is an instance of Node. The Node interface contains methods for obtaining parsed values of child elements and attributes.
A complex binding must use the parse tree to obtain the values that it needs to compose the resulting object.
As an example, consider the binding for the
PurchaseOrderType
from the purchase order schema.:Object parse(ElementInstance instance, Node node, Object value) throws Exception { PurchaseOrder po = new PurchaseOrder(); //mandatory child elements po.setShipTo( (USAddress) node.getChildValue( "shipTo" ) ); po.setBillto( (USAddress) node.getChildValue( "billTo" ) ); po.setItems( (Items) node.getChildValue( "Items" ) ); //optinal child elements if ( node.hasChild( "comment" ) ) { po.setComment( (String) node.getChildValue( "comment" ) ); } //attributes po.setOrderDate( (Date) node.getAttributeValue( "orderDate" ) ); return po; }
Encoding:
Element encode(Object object, Document document, Element value) throws Exception;
The encode method for a complex binding must transform the object into a DOM element. It takes three parameters:
object: The object to encode
document: A document used to create DOM components
value: A value as an element which is the result of the previous binding in the execution chain. For the first binding in the chain this is an empty element (no attributes, children, or text)
The return value of the method is the encoded element. Often this is the same element passed in (the value parameter), with some content added to it.
The
getProperty
method for a complex binding is used to retrieve properties from an object being encoded.:Object getProperty(Object object, QName name) throws Exception;
The returned objects themselves are then encoded down the line. For an object being encoded as an element, each property corresponds to a child element or attribute.
The method takes two parameters:
object: The object being encoded, this is the same object as in the encode method
name: The qualified name of the property to retrieve
The return value of the method is the property itself, or null if it does not exist.
Note
A multi-valued property is an element declaration in which the
maxOccurs
attribute is greater then 1. In this case, thegetProperty
method may return a collection, an array, or an iterator for the property.The entire encoding process for a complex binding is split over these two methods.
Consider the
PurchaseOrderType
example:Element encode(Object object, Document document, Element value) throws Exception { return value; } Object getProperty(Object object, QName name) throws Exception { PurchaseOrder purchaseOrder = (PurchaseOrder) object; if ( "shipTo".equals( name.getLocalPart() ) ) { return purchaseOrder.getShipTo(); } if ( "billTo".equals( name.getLocalPart() ) ) { return purchaseOrder.getBillTo(); } ... }
In the above example all of the work is done in the
getProperty
method. This is often the case. However there are situations where the encode method is necessary.For types with “open-ended” or “extensible” content ( think AbstractFeatureType from the GML schema ). Since the content is open ended the schema does not contain the necessary information to retrieve the property
For types with “mixed” content ( i.e. can have child elements and text ). In this situation the child elements can be encoded with
getProperty
, and the text content can be be encoded in encode.
Note
While it is often not necessary to implement the encode method for a complex binding, the binding writer has the freedom to do so.
The above example would function exactly the same written as the following:
Element encode(Object object, Document document, Element value) throws Exception {
PurchaseOrder purchaseOrder = (PurchaseOrder) object;
USAddress shipTo = purchaseOrder.getShipTo();
Element shipToElement = document.createElement( "shipTo" );
value.appendChild( shipToElement );
Element name = document.createElement( "name" );
name.appendChild( document.createTextNode( shipTo.getName() ) );
shipToElement.appendChild( name );
Element street = document.createElement( "street" );
street.appendChild( document.createTextNode( shipTo.getStreet() ) );
shipToElement.appendChild( street );
...
Element billToElement = document.createElement( "billTo" );
value.appendChild( billToElement );
...
return value;
}
Object getProperty(Object object, QName name) throws Exception {
return null;
}
In this example, all the work is done in the encode method. However it is evident that the second example results in much more work for the binding implementer which is why the first method is often preferred.
AbstractComplexBinding
The class
org.geotools.xml.AbstractCompledxBinding
is available for sub-classing by binding writers.
Binding Context¶
Often bindings have dependencies on other types of objects. The most common case is a factory used to create objects. Bindings work with the concept of Constructor Injection, in which any dependencies a binding has on another object is listed as a parameter in its constructor.
Let us consider the PurchaseOrderType
example once again:
class PurchaseOrderTypeBinding extends AbstractComplexBinding {
PurchaseOrderFactory factory;
public PurchaseOrderTypeBinding( PurchaseOrderFactory factory ) }
this.factory = factory;
}
...
}
In the above, the binding declares a dependency on a factory which it will use to construct objects. You may be asking the question Where does this factory come from?. The answer is the Binding Context.
The Binding Context is used to create bindings. More specifically a binding is created within a binding context. Which means that all dependencies ( a PurchaseOrderFactory
in this case ) must also be present in the binding context. For those of you familiar with the concept of Inversion of Control it may not surprise you that the binding context is nothing more then a PicoContainer
instance.
The Binding Context is described further in the Configuration section.
Binding Testing¶
The XMLTestSupport
class is used as a base class for binding unit tests.
Subclasses of XMLTestSupport
need to provide the configuration the binding under test is part of. For instance:
public class POBindingTest extends XMLTestSupport {
protected Configuration createConfiguration() {
return new POConfiguration();
}
}
The class provides convenience methods for testing various aspects of all bindings such as:
Parse Testing
Each binding unit test has a member of type
org.w3c.dom.Document
. It is meant to be used to build up an instance document to be parsed by the parser. The instance document contains the content that the binding under test will parse. The document must be built up from a test method. Once built up, the parse method is called to parse the instance document into an object.:/** * Parses the built document. * <p> * This method should be called after building the entire document. * * </p> * @throws Exception */ protected Object parse() throws Exception;
The method takes no parameters. An example usage:
public class POBindingTest extends XMLTestSupport { ... public void testPurchaseOrderTypeParse() throws Exception { //build up the document Element purchaseOrderElement = document.createElementNS( "http://www.geotools.org/po", "PurchaseOrder" ); document.appendChild( purchaseOrderElement ); purchaseOrder.setAttribute( "orderDate", "2007-01-19" ); Element shipToElement = document.createElementNS( "http://www.geotools.org/po", "ShipTo" ); purchaseOrderElement.appendChild( shipToElement ); shipToElement.setAttribute( "country", "Canada") ); ... //call parse PurchaseOrder purchaseOrder = (PurchaseOrder) parse(); //make assertions assertEquals( new Date("2007-01-19"), purchaseOrder.getOrderDate() ); ... } }
Encode Testing
The encode method is used to encode an object into an instance document. It must be called after the object tree in which the binding will encode has been built up.:
/** * Encodes an object, element name pair. * * @param object The object to encode. * @param element The name of the element to encode. * * @return The object encoded. * @throws Exception */ protected Document encode( Object object, QName element ) throws Exception;
The method takes two parameters:
object: the object to be encoded
element: the qualified name of the element which maps to the object
The method returns a
org.w3c.dom.Document
object which is the root of the encoded document.An example usage:
public class POBindingTest extends XMLTestSupport { ... public void testPurchaseOrderTypeEncode() throws Exception { //build up the object PurchaseOrder purchaseOrder = new PurchaseOrder(); purchaseOrder.setOrderDate( new Date("2007-01-19") ); USAddress shipTo = new USAddress(); shipTo.setCountry( "Canada" ); .. purchaseOrder.setShipTo( shipTo); //encode the object Document document = encode( purchaseOrder, new QName( "http://www.geotools.org/po", "PurchaseOrder" ) ); //make assertions assertEquals( "2007-01-19", document.getDocuemntElement().getAttribute( "orderDate" ) ); ... } }
Type Mapping Testing
Part of testing a binding is being sure of which type of object it returns. To achieve this the binding method can be used to obtain an instance of a particular binding.:
/** * Convenience method for obtaining an instance of a binding. * * @param name The qualified name of the element,attribute,or type the * binding "binds" to, the key of the binding in the container. * * @return The binding. */ protected Binding binding( QName name );
The method takes a single parameter which is the qualified name of an element, attribute, or type. It returns the binding which is bound to the name. With the binding instance available, its mapped type can be asserted.
An example usage:
public class POBindingTest extends XMLTestSupport { ... public void testPurchaseOrderTypeType() { //get an instance of the binding Binding binding = binding( new QName( "http://www.geotools.org/po", "PurchaseOrderType" ) ); //assert the type assertEquals( PurchaseOrder.class, binding.getType() ): }
Execution Testing
In much the same way that the binding method shown above is used to assert type mapping, it can be used to assert execution mode as well.:
public class POBindingTest extends XMLTestSupport { ... public void testPurchaseOrderTypeExecutionMode() { //get an instance of the binding Binding binding = binding( new QName( "http://www.geotools.org/po", "PurchaseOrderType" ) ); //assert the type assertEquals( Binding.OVERRIDE, binding.getExecutionMode() ): }