Customizing JiBX binding behavior

# Customizing JiBX binding behavior |

JiBX Basics Overview Getting Started Binding Compiler Binding on Load Runtime Extras Building JiBX Maven Contributing License News and Status Support FAQ Mailing Lists Bugs User Comments Sponsors Start from Code Start from Code BindGen Example Code Example 1 Example 2 Example 3 Example 4 Customizations Reference SchemaGen Jibx2Wsdl Examples Customizations Start from Schema Start from Schema CodeGen Schema Datatype Handling Example Code Default Generation Simple Customizations More Customizations Modular Generation Customizations Reference Extensions Reference Binding Definition Binding Tutorial A basic binding Binding extras Structure mapping Collections and arrays Using mappings Advanced binding Method hooks Custom code Precompiled Bindings Definition details Definition contexts Conversions Date/time conversions XML summary <binding> element <include> element <format> element <namespace> element <mapping> element <value> element <structure> element <collection> element Attribute groups Usage API Runtime JavaDocs Subprojects Eclipse Plug-in Introduction Install Usage JiBX/WS Axis2 Usage JiBX/OTA Schema Library | ### Using custom code with JiBX For 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 `org.jibx.runtime` package) defined for normal user access. They're also not as well documented as the normal user interface - all you'll have to work with is this page, the associated example code, and the JavaDocs for the implementation classes (in the `org.jibx.runtime.impl` package). 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 unmarshallers The 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 `org.jibx.runtime.IMarshaller` (for a marshaller, used by an output binding) and/or `org.jibx.runtime.IUnmarshaller` (for an unmarshaller, used by an input binding) interfaces. 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/unmarshaller Figure 21 shows a pair of examples of using a a custom marshaller/unmarshaller. In this case the marshaller/unmarshaller involved handles `java.util.HashMap` instances. The Java class structure is based off a `Directory` that contains a `HashMap` (highlighted in green) using customer identifier strings as keys and the associated customer information (in the form of `Customer` class instances) as values. The custom marshaller code converts the hashmap into a map element wrapper with an attribute giving the number of key-value pairs, and the actual key-value pairs as contained entry elements (all highlighted in magenta). Each entry element gives the key as an attribute and the mapped value as the content. The custom unmarshaller converts this XML structure back into a hashmap instance. 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 `java.util.HashMap` instances referenced by the binding. Here the actual binding definition for the customerMap field (highlighted in green) automatically uses the custom code. In the bottom version, the custom marshaller and unmarshaller are instead referenced directly by the binding definition for the field (again highlighted in green). 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 `org.jibx.runtime.IAliasable` interface (which is only an indicator interface, with no actual methods defined). 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 `String` parameter. If the binding compiler finds a constructor of this type it will pass the name of the object class that the marshaller/unmarshaller is used with in the binding when calling the constructor. This feature can be used to implement polymorphic marshaller/unmarshallers. 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 `org.jibx.extras.HashMapperStringToComplex`. You can also find several other custom marshaller/unmarshaller classes in the `org.jibx.extras` package, including the `org.jibx.extras.TypedArrayMapper` that gives an example of marshaller/unmarshaller polymorphism. I'll also look into ways of making custom marshaller/unmarshallers even more configurable in the future. ### Controlling JiBX with front-end code Another 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 `BindingSelector` instance and set the document to be unmarshalled before finally calling `unmarshalVersioned` method to get back the unmarshalled root object:
    // 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 `org.jibx.extras.BindingSelector`, so if you just want to make use of it as-is you can easily do so.