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.
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.
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.
XML 2 Java Binding consists of three basic components:
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.
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:
To see the complete "Hello World" example, take a look to both versions of helloworld demo that is part of this distribution.
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:
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 user want to know the generated default name attribute value, here's the algorithm:
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 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.
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.
X2JB comes with two ready to be used binding providers.
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 is located in x2jb-properties-provider.jar file. User using properties driven binding provider have to follow the following steps:
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.
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.
User can always write his own binding handler. It is very easy, he just need to follow the following steps:
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.
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:
# 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
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.
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:
com.mycompany.myproject.FooBindingProvider
-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:
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 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.
The X2JB is well tested and is proven to be stable: