X2JB 2.0 Guide

Preface

This document will show you how to use XML 2 Java Binding (X2JB) tool and extend it via its extension mechanisms to fulfill your requirements and expectations.

Target Audience

X2JB is intended for Java developers that are dealing with static configuration of their system and are looking for very easy, flexible and extremely powerfull XML to Java mapping tool.

Important Note

X2JB comes with no XML Schema compiler, DTD compiler or other tool that would generate XML to Java mapping for you like XMLBeans or JAXB do. User will define XML to Java mapping interfaces on his own. This gives him the full control over the binding process - one of a best things X2JB provides you.

Architecture Overview

XML 2 Java Binding consists of three basic components:

  • X2JB Runtime - implements binding mechanism that is independent from used binding provider. It is located in x2jb-core.jar file.
  • X2JB Default Handlers - binding handlers for frequently used Java objects and primitives. They are placed in x2jb-default-handlers.jar file.
  • X2JB Providers - binding provider implementations. X2JB comes with two kind of providers. Properties based provider located in x2jb-properties-provider.jar and annotation based provider located in x2jb-annotation-provider.jar.

Default handlers and binding providers are part of X2JB extensibility mechanism - user can use delivered ones or he can write his own. We will explain all the three parts more detaily in next sections.

Getting Started

Now is the time for our first "Hello World" sample. Here is the sample configuration:

<?xml version="1.0" encoding="UTF-8"?>
<!-- file helloworld.xml -->
<messages message="A: Hello World!">
    <message>E: Hello World!</message>
</messages>

As mentioned earlier X2JB comes with two binding providers. Lets take a look to annotation based definition of mapping interface:

// file HelloWorld.java

package helloworld.ifaces;

import org.x2jb.bind.Binding;

public interface HelloWorld // will be associated with messages root element
{

    /*
     * Binds XML element 'message' to 'java.lang.String'
     */
    @Binding
    (
        nodeName = "message"
    )
    String getMessageFromElement();

    /*
     * Binds XML attribute 'message' to 'java.lang.String'
     */
    @Binding
    (
        nodeName = "message",
        isElementNode = false
    )
    String getMessageFromAttribute();

}

And here is the equivalent Java properties based mapping definition:

// file HelloWorld.java

package helloworld.ifaces;

public interface HelloWorld // will be associated with messages root element
{

    String getMessageFromElement();

    String getMessageFromAttribute();

}

and its associated properties definition file:

// file HelloWorld.binding

# Binds XML element 'message' to 'java.lang.String'
getMessageFromElement.nodeName=message

# Binds XML attribute 'message' to 'java.lang.String'
getMessageFromAttribute.nodeName=message
getMessageFromAttribute.isElementNode=false

We have defined HelloWorld.java mapping interface in both formats - Java annotations and Java properties format. These two formats are fully interchangeable but user can use only one binding format per Java Virtual Machine.

Users should notice one important thing. We have defined mapping interface and not abstract or instantiable class. This is the outstanding feature of X2JB - it is completely interface focused.

When defining mapping interfaces and their methods user can name them as he wants. However there are some X2JB restrictions that have to be satisfied.

So lets return back to our example. To properly package properties based mapping definition, each .binding file should be stored in the same jar and directory as associated .class file. Otherwise user will receive BindingException during the binding process.

Now is the time to write X2JB client code to demonstrate the API usage:

package helloworld;

import org.x2jb.bind.XML2Java;
import helloworld.ifaces.HelloWorld;
import org.w3c.dom.Document;
...

public final class Main
{

    ...

    public static void main( String[] args ) throws Exception
    {
        Document doc = getDocument( "/helloworld.xml" );
        HelloWorld helloWorld = XML2Java.bind( doc, HelloWorld.class );

        System.out.println( helloWorld.getMessageFromElement() );
        System.out.println( helloWorld.getMessageFromAttribute() );
    }

}

The whole binding mechanism is executed via XML2Java.bind() method call. This method returns Java proxy implementing the passed interface. X2JB runtime iterates through all Java beans (defined using set/get methods) of passed interface and does the following steps for each of them:

  • reads the bean mapping definition to discover which subelement(s)/attribute have to be found in enclosing document or element
  • finds all subelement(s)/attribute matching the mapping definition
  • if there are no such subelement(s)/attribute and they are/it is defined as required, BindingException is thrown, otherwise X2JB runtime will look up the default value for bean type
  • if subelement(s)/attribute are/is present in enclosing element X2JB runtime will bind them/it to the bean type object using X2JB default or user defined binding handlers
  • if bean type is interface and there is no user binding handler specified in bean mapping definition the whole process specified here is executed recursively for this bean type and found subelement(s). If user defined binding handler is specified in the binding, the construction of bean type is delegated to it.

To see the complete "Hello World" example, take a look to both versions of helloworld demo that is part of this distribution.

X2JB Restrictions

As mentioned earlier X2JB comes with few restrictions that must be satisfied. Lets call mapping interface binding interface and each method declared within it binding method. The following are the restrictions of X2JB:

  • Every binding interface must be Java interface
  • Every binding method cannot throw exceptions
  • Every binding method name must start with "is", "set" or "get" prefix
  • Every getter (getters start with "is" or "get" prefix) must follow the following rules"
    • if getter starts with "is" prefix its return type must be either boolean or Boolean
    • if getter starts with "get" prefix its return type can be any X2JB primitive type except void or Void
    • every getter must be without parameters
    • there can be only one getter for every bean type (isProperty() and getProperty() are not allowed to be declared on the same binding inteface)
  • Every setter (setters start with "set" prefix) must follow the following rules:
    • setter must return either void or Void
    • setter must have only one parameter of the same bean type like associated getter method
    • there must exist getter for every defined setter (but not vice versa)
    • there must exist only one setter for every bean (setProperty(int) and setProperty(Integer) are not allowed to be declared on the same binding interface)
  • If there are both setter and getter defined for particular bean, then these methods have to have identical binding definition.
  • There must exist type handler for each binding method bean type. The only exception to this rule is binding method returning binding interface. (X2JB default handlers will be used when user does not specify its own in bean mapping definition)

X2JB Default Binding

If there's no binding definition associated with particular binding method the default one is generated. The generated default binding has identical defaults like binding annotation defined for X2JB annotation provider, except the required name attribute. If binding definition is generated the name attribute is set to the following value:

  • if binding method starts with "set" prefix (e.g. setProperty), the name will hold value "property" (lowercased property name)
  • if binding method starts with "is" prefix (e.g. isProperty), the name will hold value "property"
  • if binding method starts with "get" prefix (e.g. getProperty), the name will hold value "property"

If user want to know the generated default name attribute value, here's the algorithm:

  • remove "is", "set" or "get" prefix from method name (let's say its methodNameWithoutPrefix)
  • then java.beans.Introspector.decapitalize(methodNameWithoutPrefix) method is called for bean name construction

Users should take a look to both versions of defaultbindings sample that is part of this distribution to see how default bindings are generated from method names.

X2JB Modifiable Proxies

X2JB 1.0 introduced unmodifiable proxies. Since X2JB 2.0 there is also support for modifiable proxies. Modifiable proxies are Java proxies where user can change their values after the X2JB binding process. Users should take a look to both versions of mutability sample that is part of this distribution to see how generated X2JB Java proxies can be modified at runtime.

Built-in Type Handlers

X2JB comes with default type handlers for the following frequently used Java objects and primitives (we call them X2JB primitives):

Handled Java Type Default Value Accepted Values
java.lang.Boolean null true, false, 0, 1, yes, no (case unsensitive strings)
java.lang.Byte null every Java string accepted by java.lang.Byte.valueOf(String) method
java.lang.Character null unicode string containing only one unicode character
java.lang.Short null every Java string accepted by java.lang.Short.valueOf(String) method
java.lang.Integer null every Java string accepted by java.lang.Integer.valueOf(String) method
java.lang.Long null every Java string accepted by java.lang.Long.valueOf(String) method
java.lang.Float null every Java string accepted by java.lang.Float.valueOf(String) method
java.lang.Double null every Java string accepted by java.lang.Double.valueOf(String) method
java.math.BigInteger null every Java string accepted by java.math.BigInteger(String) constructor
java.math.BigDecimal null every Java string accepted by java.math.BigDecimal(String) constructor
java.lang.String null every Java string
javax.xml.namespace.QName null every Java string accepted by javax.xml.namespace.QName.valueOf(String) method
org.w3c.dom.Element null every org.w3c.dom.Element instance
X2JB Binding Interface null User defined binding interface
boolean false true, false, 0, 1, yes, no (case unsensitive strings)
byte 0 every Java string accepted by java.lang.Byte.valueOf(String) method
char 0 unicode string containing only one unicode character
short 0 every Java string accepted by java.lang.Short.valueOf(String) method
int 0 every Java string accepted by java.lang.Integer.valueOf(String) method
long 0 every Java string accepted by java.lang.Long.valueOf(String) method
float 0 every Java string accepted by java.lang.Float.valueOf(String) method
double 0 every Java string accepted by java.lang.Double.valueOf(String) method
array of any of above types zero length array

Default handler is used in case user does not specify his custom bean type handler in the bean mapping definition.

Users should take a look to both versions of supportedtypes and defaultvalues demos that are part of this distribution to see the aforementioned default handlers in action.

Binding Providers

X2JB comes with two ready to be used binding providers.

Annotation Based Binding Provider

Annotation based binding provider is located in x2jb-annotation-provider.jar file. There is also the associated org.x2jb.bind.Binding Java annotation used for mapping definition. This annotation consists of the following attributes:

Attribute Default Value Required Description
nodeName N/A yes Name of child node(s)
nodeNamespace "" no Namespace of child node(s)
isElementNode true no Indicates whether child node is XML element or attribute
isNodeUnique true no Specifies whether child node is unique. If not, multiple subelements are expected
isNodeMandatory true no Informs whether subnode must be present in enclosing element
typeHandler "" no Allows user to specify his own binding handler to be used for binding process
defaultValue "" no Allows user to specify default value to be used when node is not available.

nodeName represents XML element or attribute name. There are also two special strings that are accepted and have special meaning for X2JB runtime. The first is "." (point character) referencing enclosing element. The second is "*" (star character) referencing all subelements of enclosing element. They are intended to be used with org.w3c.dom.Element method bean type to enable e.g. dynamic configurations.

nodeNamespace is optional. However user must use it when he wants to bind XML document or element that consists of elements and/or attributes from multiple namespaces. If document or element content consists from elements or attributes just from one namespace then user does not need to specify nodeNamespace at all because it is autodetected by X2JB runtime. But if XML document or element content belongs to multiple namespaces user must specify nodeNamespace mapping attribute for each XML element or attribute from different namespace to force X2JB binding mechanism behave properly. The namespace of enclosing interface is always inherited so if the user do not specify the nodeNamespace attribute it is inherited from its enclosing interface during the binding process. Users should take a look to both version of namespaces demo that is part of this distribution to see how X2JB works with namespaces.

If isElementNode is set to true method bean type will be created from XML subelement. If it is set to false then method bean type will be created from XML attribute.

isNodeUnique attribute exists because of Java arrays. Without this attribute X2JB runtime would not be able to handle Java arrays properly. If value of this mapping attribute is true it means that only one subelement or attribute with specified nodeName and nodeNamespace can exist in enclosing element. Value false informs X2JB runtime that multiple XML elements are expected in enclosing element. User must always set the value of this attribute to false when he knows that multiple elements with defined nodeName and nodeNamespace are expected in enclosing element. Users should take a look to both versions of arrayssupport demo to see how X2JB works with Java arrays.

isNodeMandatory is some form of lightweight validation mechanism. It just specifies if XML element or attribute is mandatory in enclosing element. If yes and it was not found BindingException is thrown. This form of validation should be performed by DTD or XML Schema aware DOM parser. But there will be situations when user will work with XML documents that have no associated DTD or XML schema file, but user will know its structure and will want to use X2JB tool for mapping such XML documents to Java objects. This is the main reason why this attribute is included in X2JB mapping attributes.

typeHandler is very useful mapping attribute. There will be situations when user will want to customize the binding process for some Java type. When specifying typeHandler user must use fully qualified Java class name. If this attribute is specified it always overrides the default type handler that is associated with the method bean type (if exists). However user must always specify this attribute when method bean type is not present in the aforementioned default type handler list.

defaultValue allows user to specify custom default value per bean instead of using default X2JB runtime value. If this attribute is specified it always overrides default handlers value. Users should take a look to both versions of defaultvalues that are part of this distribution to see this attribute in action.

Properties Based Binding Provider

Properties based binding provider is located in x2jb-properties-provider.jar file. User using properties driven binding provider have to follow the following steps:

  • define properties based binding interface and its methods (i.e. without X2JB binding annotations)
  • create property file with the same name as compiled interface class file but with .binding suffix instead of .class
  • define mapping definition for each binding method defined in binding interface in this property file
  • put the created .binding file to the same directory where compiled interface .class file will be stored

Lets take a look back to above defined properties based version of HelloWorld interface.

To define the properties based mapping definition user must create HelloWorld.binding properties file. The name (except suffix) must be identical with compiled interface class name it belongs to (in our case HelloWorld.class). So what about nested interfaces if user has inner public interface e.g. somepackage.EnclosingClass.InnerClass then resulting .binding file will have name EnclosingClass$InnerClass.binding and will be located in package somepackage. To see the inner interface example, take a look to both versions of inneriface demo that is part of this distribution.

To define mapping for each method user must create key entries in this HelloWorld.binding file in the following format:

methodName.bindingAttribute=someValue

where methodName is the method we want to define mapping for, bindingAttribute is one of the attributes defined in Annotation Based Binding Provider table. For attributeValue applies the same rules as defined in the previous table.

Setting Classpath

Now, when user knows how to use both types of binding mechanisms is the time to explain how to properly set up the classpath to use the appropriate binding provider.

User must always have x2jb-core.jar and x2jb-default-handlers.jar on the classpath. Then he must have x2jb-annotation-provider.jar or x2jb-properties-provider.jar or custom binding provider on the classpath too. If there are multiple binding providers on the classpath then the first provider on the classpath is used. However user can always simply override this default behaviour. The trick is to specify -Dorg.x2jb.bind.spi.provider.BindingFactory JVM property.

Annotation Based Binding Provider is implemented by com.x2jb.bind.provider.AnnotationBindingFactoryImpl class. So if there are multiple binding providers on the classpath user can specify -Dorg.x2jb.bind.spi.provider.BindingFactory=com.x2jb.bind.provider.AnnotationBindingFactoryImpl and annotation based provider will be used.

Properties Based Binding Provider is implemented by com.x2jb.bind.provider.PropertiesBindingFactoryImpl class. So again if there are multiple binding providers on the classpath user can just specify -Dorg.x2jb.bind.spi.provider.BindingFactory=com.x2jb.bind.provider.PropertiesBindingFactoryImpl and properties provider will be used during the binding process.

Advanced Topics

Writing Custom Handler

User can always write his own binding handler. It is very easy, he just need to follow the following steps:

  • write his own implementation of org.x2jb.bind.spi.handler.AttributeHandler or org.x2jb.bind.spi.handler.ElementHandler interface
  • reference newly created binding handler class name in the method mapping definition (using typeHandler attribute)
  • put the handler implementation on the classpath

Users should take a look to both versions of customhandler sample that is part of this distribution to see how to write custom binding handler.

Writing Custom Default Handlers

There may be situations when user will not be satisfied with default X2JB handlers implementation. If this is the case user must follow the following steps to write and activate his custom default handlers implementations:

  • implement all the new default custom handlers (see previous chapter how to write it)
  • create new x2jb.handlers property file and specify his default handlers mapping there. Here is the sample how it looks like for x2jb-default-handlers.jar archive:
    # file x2jb.handlers - X2JB default handlers mapping definition
    #
    # the format of this file is:
    # javaType=handlerImplementation
    #
    java.lang.Boolean=com.x2jb.bind.handler.BooleanHandler
    java.lang.Character=com.x2jb.bind.handler.CharacterHandler
    java.lang.Byte=com.x2jb.bind.handler.ByteHandler
    java.lang.Short=com.x2jb.bind.handler.ShortHandler
    java.lang.Integer=com.x2jb.bind.handler.IntegerHandler
    java.lang.Long=com.x2jb.bind.handler.LongHandler
    java.lang.Float=com.x2jb.bind.handler.FloatHandler
    java.lang.Double=com.x2jb.bind.handler.DoubleHandler
    boolean=com.x2jb.bind.handler.PrimitiveBooleanHandler
    char=com.x2jb.bind.handler.PrimitiveCharacterHandler
    byte=com.x2jb.bind.handler.PrimitiveByteHandler
    short=com.x2jb.bind.handler.PrimitiveShortHandler
    int=com.x2jb.bind.handler.PrimitiveIntegerHandler
    long=com.x2jb.bind.handler.PrimitiveLongHandler
    float=com.x2jb.bind.handler.PrimitiveFloatHandler
    double=com.x2jb.bind.handler.PrimitiveDoubleHandler
    java.math.BigInteger=com.x2jb.bind.handler.BigIntegerHandler
    java.math.BigDecimal=com.x2jb.bind.handler.BigDecimalHandler
    java.lang.String=com.x2jb.bind.handler.StringHandler
    javax.xml.namespace.QName=com.x2jb.bind.handler.QNameHandler
    org.w3c.dom.Element=com.x2jb.bind.handler.ElementHandler
  • compile his newly created default handlers and package it to e.g. foo.jar including his newly created x2jb.handlers file to its META-INF directory.
  • put newly created default handlers archive foo.jar to the beginning of the classpath. It is not necessary to put it on the beginning of the classpath but in such case user must ensure x2jb-default-handlers.jar is not on the classpath.

The only important information user should know is the following fact. When X2JB runtime boots it lookups the first META-INF/x2jb.handlers property file on the classpath to instantiate all default handlers specified there. All other META-INF/x2jb.handlers classpath resources are completely ignored.

Writing Custom Binding Provider

As user already knows X2JB comes with two ready to be used binding providers. But he is not forced to use them. User can always write his own X2JB binding provider. Here are the steps how to accomplish this:

  • the new Binding Provider must implement org.x2jb.bind.spi.provider.BindingFactory interface. Users should read its API documentation very carefully to understand and fulfill the interface contract. Lets call this newly created BindingProvider e.g. com.mycompany.myproject.FooBindingProvider
  • user must create new org.x2jb.bind.spi.provider.BindingFactory text file and specify its new implementation class name there:
    com.mycompany.myproject.FooBindingProvider
  • user must compile and archive his newly created com.mycompany.myproject.FooBindingProvider to jar file including newly created org.x2jb.bind.spi.provider.BindingFactory text file in its META-INF/services directory.
  • put his custom binding provider jar file to the beginning of the classpath or just put it on the classpath and specify the following JVM property when running his application:
    -Dorg.x2jb.bind.spi.provider.BindingFactory=com.mycompany.myproject.FooBindingProvider

The only important information user should know again is the following fact. When X2JB runtime boots it lookups the binding provider to use in the following way:

  • it checks if the JVM property -Dorg.x2jb.bind.spi.provider.BindingFactory is specified. If yes its value is used to instantiate the binding provider
  • if there is no such JVM property specified X2JB runtime searches for the first META-INF/services/org.x2jb.bind.spi.provider.BindingFactory classpath resource and reads its content. The content of this text file representing the provider class name is used to instantiate the binding provider

Here is the complete source code of annotation binding provider that is part of this distribution and is implemented in x2jb-annotation-provider.jar archive:

//
// file com.x2jb.bind.provider.AnnotationBindingFactoryImpl.java
//
package com.x2jb.bind.provider;

import java.lang.reflect.Method;

import org.x2jb.bind.Binding;
import org.x2jb.bind.spi.provider.BindingDefinition;
import org.x2jb.bind.spi.provider.BindingFactory;

public final class AnnotationBindingFactoryImpl implements BindingFactory
{

    public AnnotationBindingFactoryImpl()
    {
        super();
    }

    public BindingDefinition getBinding( final Method method )
    {
        final Binding methodMetadata = ( Binding ) method.getAnnotation( Binding.class );
        return ( methodMetadata == null ) ? null : new AnnotationBindingImpl( methodMetadata );
    }

}

Here is the referenced AnnotationBindingImpl that translated data from Java annotation to BindingDefinition interface implementation:

//
// file com.x2jb.bind.provider.AnnotationBindingImpl.java
//
package com.x2jb.bind.provider;

import org.x2jb.bind.Binding;
import org.x2jb.bind.spi.provider.BindingDefinition;

final class AnnotationBindingImpl implements BindingDefinition
{

    private Binding binding;

    AnnotationBindingImpl( final Binding b )
    {
        this.binding = b;
    }

    public String getNodeName()
    {
        return this.binding.nodeName();
    }

    public String getNodeNamespace()
    {
        return this.binding.nodeNamespace();
    }

    public boolean isElementNode()
    {
        return this.binding.isElementNode();
    }

    public boolean isNodeUnique()
    {
        return this.binding.isNodeUnique();
    }

    public boolean isNodeMandatory()
    {
        return this.binding.isNodeMandatory();
    }

    public String getTypeHandler()
    {
        return this.binding.typeHandler();
    }

    public String getDefaultValue()
    {
        return this.binding.defaultValue();
    }

}

And at last the referenced Binding Java annotation source code:

//
// file org.x2jb.bind.Binding.java
//
package org.x2jb.bind;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention( RetentionPolicy.RUNTIME )
@Target( { ElementType.METHOD } )
public @interface Binding
{

    String nodeName();

    String nodeNamespace() default "";

    boolean isElementNode() default true;

    boolean isNodeUnique() default true;

    boolean isNodeMandatory() default true;

    String typeHandler() default "";

    String defaultValue() default "";

}

The Proof of Concept

The proof of concept X2JB tool was designed for is available in both versions of the webapp example that is part of this distribution. This webapp example demonstrates the power and flexibility of X2JB and provides some kind of overview of all the aforementioned features.

Tested Platforms

The X2JB is well tested and is proven to be stable:

  • on the following operating systems:
    • Windows XP
    • Linux
  • will work with the following Java versions:
    • jdk 1.5 and above
  • and with the following ANT versions:
    • ant 1.6.5 and above