Using custom code with JiBXFor the highest degree of control over JiBX binding behavior you can write custom code that ties directly into the JiBX framework. This gives you ways of handling special cases that are beyond the normal binding flexibility. It's not a technique to be used lightly, though. Working at this level takes you into the internals of the JiBX runtime
implementation classes. These implementation classes are considered to be
reasonably stable, but are likely to change more frequently then the interface
classes (in the Even with these limitations, the flexibility may be worth the pain involved if you have special binding requirements. I've supplied a pair of examples here which illustrate this flexibility. The JiBX custom code portions of these examples are included in the jibx-extras.jar in the distribution, so they're available for use without the need for you to personally dig into the JiBX implementation code (unless you want to change the way they work). Custom marshallers and unmarshallersThe principle behind custom marshallers and unmarshallers is simple: Instead
of letting JiBX build a marshaller and
unmarshaller for a class based on a mapping element in your binding
definition, you tell JiBX to use your own supplied class. This class must
implement the You can think of custom marshallers and unmarshallers as taking the simpler idea of custom serializers and deserializers to a whole new level. Serializers and deserializers give you control over the text representation of primitive or simple object values, where there's no XML structure involved. Custom marshallers and unmarshallers extend this control to complex objects with any number of attributes and/or child elements. The downside of this extended control is that marshallers and unmarshallers are considerably more complex to code than serializers and deserializers. Figure 21. Using a custom marshaller/unmarshallerFigure 21 shows a pair of examples of using a a custom
marshaller/unmarshaller. In this case the marshaller/unmarshaller involved handles
The binding definitions in Figure 21 show two
different ways of using a custom marshaller/unmarshaller class. In the top
version, the mapping definition highlighted in blue tells JiBX to
use the custom marshaller/unmarshaller by default for handling all
Hashmap handling makes an especially good example of the situations that require custom marshaller/unmarshallers. This particular implementation handles one particular form of hashmaps, with string keys and values of an object type with a mapping definition within the JiBX binding file. Other forms of hashmaps could be handled by modifying the basic marshaller/unmarshaller code, but it's very difficult to handle all possible types of hashmaps with a single implementation. Consider how the XML structure would need to be different if the key values in the hashmap were other mapped objects rather than simple string values, for instance (they couldn't be expressed in XML as attribute values, for starters). Below is a partial listing of the custom marshaller/unmarshaller class (with only the marshaller implementation shown - see the /tutorial/example21 directory of the distribution for the full source code and related files): public class HashMapper implements IMarshaller, IUnmarshaller, IAliasable { private static final String SIZE_ATTRIBUTE_NAME = "size"; private static final String ENTRY_ELEMENT_NAME = "entry"; private static final String KEY_ATTRIBUTE_NAME = "key"; private static final int DEFAULT_SIZE = 10; private String m_uri; private int m_index; private String m_name; public HashMapper() { m_uri = null; m_index = 0; m_name = "hashmap"; } public HashMapper(String uri, int index, String name) { m_uri = uri; m_index = index; m_name = name; } public boolean isExtension(int index) { return false; } public void marshal(Object obj, IMarshallingContext ictx) throws JiBXException { // make sure the parameters are as expected if (!(obj instanceof HashMap)) { throw new JiBXException("Invalid object type for marshaller"); } else if (!(ictx instanceof MarshallingContext)) { throw new JiBXException("Invalid object type for marshaller"); } else { // start by generating start tag for container MarshallingContext ctx = (MarshallingContext)ictx; HashMap map = (HashMap)obj; ctx.startTagAttributes(m_index, m_name). attribute(m_index, SIZE_ATTRIBUTE_NAME, map.size()). closeStartContent(); // loop through all entries in hashmap Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry)iter.next(); ctx.startTagAttributes(m_index, ENTRY_ELEMENT_NAME); if (entry.getKey() != null) { ctx.attribute(m_index, KEY_ATTRIBUTE_NAME, entry.getKey().toString()); } ctx.closeStartContent(); if (entry.getValue() instanceof IMarshallable) { ((IMarshallable)entry.getValue()).marshal(ctx); ctx.endTag(m_index, ENTRY_ELEMENT_NAME); } else { throw new JiBXException("Mapped value is not marshallable"); } } // finish with end tag for container element ctx.endTag(m_index, m_name); } } ... } At runtime, JiBX creates an instance of the class when needed using either a
default (no-argument) constructor or an optional aliased constructor that
uses element name information passed in from the JiBX binding. The difference
between the two is that the aliased constructor allows the element name to be
used by the marshaller/unmarshaller to be set by JiBX based on the information
in the binding definition file. For the Figure 21
example JiBX uses the aliased constructor behind the scenes, since the binding
definitions supply a name for the mapped element. If a custom marshaller or
unmarshaller class (which need not be the same class) supports setting the root
element name in this way it needs to implement the
This naming flexibility only applies at the top level, though. As you can see from the code, the local names used for the attributes and nested element name are fixed at compile time, while the namespaces are all set to match that of the aliased top-level element (which may not be what you want - often documents that use namespaces for elements do not use them for attributes, for instance). Besides the two constructor variations shown in this example, you can also
define constructors that take an additional If you want to use this custom marshaller/unmarshaller for hashmaps in your
own application you can find it included in jibx-extras.jar with the
name Controlling JiBX with front-end codeAnother interesting issue that's come up for several users in the past is the need to work with multiple versions of XML documents. JiBX has supported this from the beginning if the versions used different root element names, but this is not a convenient approach for XML versioning - it's much easier to keep the element names the same and instead just use an attribute of the root element to specify the document version. This makes a good example of controlling the high-level operation of JiBX from your own code. Below is a partial listing (with constructor and get/set methods left out) of code that first selects a binding for unmarshalling based on an attribute of the document root element, then creates an unmarshalling context for the specific version found and uses that context to unmarshal the document. On the marshalling side, this uses a supplied version string to select the binding: public class BindingSelector { /** URI of version selection attribute. */ private final String m_attributeUri; /** Name of version selection attribute. */ private final String m_attributeName; /** Array of version names. */ private final String[] m_versionTexts; /** Array of bindings corresponding to versions. */ private final String[] m_versionBindings; /** Basic unmarshalling context used to determine document version. */ private final UnmarshallingContext m_context; /** Stream for marshalling output. */ private OutputStream m_outputStream; /** Encoding for output stream. */ private String m_outputEncoding; /** Output writer for marshalling. */ private Writer m_outputWriter; /** Indentation for marshalling. */ private int m_outputIndent; ... /** * Marshal according to supplied version. * * @param obj root object to be marshalled * @param version identifier for version to be used in marshalling * @throws JiBXException if error in marshalling */ public void marshalVersioned(Object obj, String version) throws JiBXException { // look up version in defined list String match = (version == null) ? m_versionTexts[0] : version; for (int i = 0; i < m_versionTexts.length; i++) { if (match.equals(m_versionTexts[i])) { // version found, create marshaller for the associated binding IBindingFactory fact = BindingDirectory. getFactory(m_versionBindings[i], obj.getClass()); MarshallingContext context = (MarshallingContext)fact.createMarshallingContext(); // configure marshaller for writing document context.setIndent(m_outputIndent); if (m_outputWriter == null) { if (m_outputStream == null) { throw new JiBXException("Output not configured"); } else { context.setOutput(m_outputStream, m_outputEncoding); } } else { context.setOutput(m_outputWriter); } // output object as document context.startDocument(m_outputEncoding, null); ((IMarshallable)obj).marshal(context); context.endDocument(); return; } } // error if unknown version in document throw new JiBXException("Unrecognized document version " + version); } /** * Unmarshal according to document version. * * @param clas expected class mapped to root element of document (used only * to look up the binding) * @return root object unmarshalled from document * @throws JiBXException if error in unmarshalling */ public Object unmarshalVersioned(Class clas) throws JiBXException { // get the version attribute value (using first value as default) m_context.toStart(); String version = m_context.attributeText(m_attributeUri, m_attributeName, m_versionTexts[0]); // look up version in defined list for (int i = 0; i < m_versionTexts.length; i++) { if (version.equals(m_versionTexts[i])) { // version found, create unmarshaller for the associated binding IBindingFactory fact = BindingDirectory. getFactory(m_versionBindings[i], clas); UnmarshallingContext context = (UnmarshallingContext)fact.createUnmarshallingContext(); // return object unmarshalled using binding for document version context.setFromContext(m_context); return context.unmarshalElement(); } } // error if unknown version in document throw new JiBXException("Unrecognized document version " + version); } } To use this binding selection code you need to define a pair of arrays of
giving the text values for each version number and the corresponding binding
name, then create the // attribute text strings used for different document versions private static String[] VERSION_TEXTS = { "1.0", "1.1", "1.2" }; // binding names corresponding to text strings private static String[] VERSION_BINDINGS = { "binding0", "binding1", "binding2" }; public static void main(String[] args) { try { // process input file according to declared version BindingSelector select = new BindingSelector(null, "version", VERSION_TEXTS, VERSION_BINDINGS); IUnmarshallingContext context = select.getContext(); context.setDocument(new FileInputStream(args[0]), null); Customer customer = (Customer)select. unmarshalVersioned(Customer.class); ... You can find the full source code and related files in the
/tutorial/example22 directory of the distribution. A version of this
class is also included in the jibx-extras.jar as
|