Using and developing JAXB 2 Plugins with Jaxb2-Commons


Introduction

Java Architecture for XML Binding (JAXB) provides a convenient way to bind an XML schema to a representation in Java code. This makes it easy for you to incorporate XML data and processing functions in applications based on Java technology without having to know much about XML itself.

The following figure shows the components that make up a JAXB implementation:

Cannot resolve external resource into attachment.

The central role in this architecture plays the schema compiler, which binds a source schema to a set of schema derived program elements. Essentially, binding compiler accepts a set of schema and binding files as input and generates source code of Java classes (these are called schema-derived classes).

Although JAXB allows for customizing the schema-derived classes, these customizations are only possible to the certain extent. Fortunatelly, JAXB reference implementation schema compiler (XJC) can be extended through the mechanism of plugins. Plugins may augment schema-derived classes or influence the way XJC generates the code. For instance, you can use plugins to add value-based equals or hashCode methods, make your generated classes JMX-ready, add fluent-style with_MyProperty_ methods and so on.

There is already a number of useful plugins developed by JAXB 2 enthusiasts. This document intends to help end-users use JAXB 2 plugins and those who would like to develop an own plugin for JAXB 2.

Using plugins

Basic usage

Basically, in order to use the plugin, you have to do two things:

  1. Make your plugin avaliable to XJC.
  2. Turn your plugin on during the schema compilation.

The plugin is made available to XJC by simply including it (togther with all of its dependencies) into the XJC classpath. During the schema compilation, XJC will search its classpath for the available plugins, initialize them and run the enabled plugins.

The plugin is turned on (or enabled) by the corresponding command-line option name. Every plugin must have such an option name. For instance, ToString plugin is activated by the XtoString option; in order to turn on this plugin you have to append -XtoString command line argument to the XJC args.

The following sections explain how these two tasks (making plugins available to XJC and enabling the plugin) are done in popular build systems (Ant and Maven2).

Plugin dependencies

Before going on to Ant and Maven2 usage it is necessary to cover another important topic: plugin dependencies.

Dependencies are third-party libraries which are required for some code to function. In the case of plugins there are two types of dependencies to consider: dependencies of the plugin itself and dependencies of the generated code.

First type of dependency appears when your plugin relies on some third-party libraries. For instance, a plugin may use utilities from jaxb2-commons-tools for code generation. When adding plugin library to the XJC classpath, it is important to add all dependency libraries as well. Otherwise the plugin could be instantiated.

Second type of dependency arises when your plugin generates code that depends on some third-party libraries. For instance, basic plugins from Jaxb2-Commons generate code that uses Jakarta Commons Lang. Consequently, corresponding libraries must be added to the compilation and/or the runtime classpass so that generated code would actually compile and work.

Plugin dependencies are usually documented in plugin documentation.

Using plugins with Ant

Schema compilation in Ant is done with the special XJC Ant task, which is the part of JAXB RI. The following sections cover usage of JAXB plugins from the XJC task of Ant.

Exposing plugins to the XJC Ant task

In order to make you plugin available to the XJC Ant task, you have to include the plugin and all of its dependencies into the classpath of the declared task:

<taskdef name="xjc" classname="com.sun.tools.xjc.XJCTask">
	<classpath>
		<fileset dir="${basedir}/lib">
			<!-- JAXB dependencies -->
			<include name="activation-*.jar"/>
			<include name="jaxb-api-*.jar"/>
			<include name="jaxb-impl-*.jar"/>
			<include name="jsr173_api-*.jar"/>
			<include name="jaxb-xjc-*.jar"/>
			<!-- Plugins -->
			<include name="basic-*.jar"/>
			<!-- Plugin dependencies -->
			<include name="runtime-*.jar"/>
			<include name="tools-*.jar"/>
			<include name="commons-beanutils-*.jar"/>
			<include name="commons-lang-*.jar"/>
			<include name="commons-logging-*.jar"/>
		</fileset>
	</classpath>
</taskdef>

Do not forget to include plugin dependencies, otherwise the XJC will not be able to instantiate your plugins.

Activating plugins

Plugins are activated by including the corresponding option (-XmyPlugin) into the XJC arguments. This is doe with the nested <arg/> element:

&lt;xjc target="${basedir}/target/generated-sources/xjc" extension="true">
	&lt;arg line="
		-Xequals
		-XhashCode
		-XtoString
		-Xcopyable"/>
	&lt;binding dir="${basedir}/src/main/resources">
	 	&lt;include name="**/*.xjb"/>
	&lt;/binding>
	&lt;schema dir="${basedir}/src/main/resources">
	 	&lt;include name="**/*.xsd"/>
	&lt;/schema>
&lt;/xjc>

Compile/runtime dependencies.

In case generated code has some additional compile or runtime dependencies, add these to the corresponding compilation, testing or runtime path.

For instance, hashCode() or equals(...) methods generated by basic plugins rely on Commons Lang and some interfaces from Jaxb2 Commons Runtime. Ant's compilation target would the look as follows:

&lt;!-- Compilation classpath -->
&lt;path id="compile.path">
	&lt;fileset dir="${basedir}/lib">
		&lt;!-- JAXB compile-time dependencies -->
		&lt;include name="jaxb-api-*.jar"/>
		&lt;!-- Additional compile-time dependencies of the generated code -->
		&lt;include name="commons-lang-*.jar"/>
		&lt;include name="runtime-*.jar"/>
	&lt;/fileset>
&lt;/path>
&lt;!-- Compilation target -->
&lt;target name="compile" depends="generate-sources">
	&lt;mkdir dir="${basedir}/target/classes"/>
	&lt;javac
		destdir="${basedir}/target/classes" 
		srcdir="${basedir}/src/main/java:${basedir}/target/generated-sources/xjc"
		classpathref="compile.path"
		source="1.5"
		target="1.5">
	&lt;/javac>
	&lt;copy todir="${basedir}/target/classes">
		&lt;fileset dir="${basedir}/src/main/resources"/>
		&lt;fileset dir="${basedir}/target/generated-sources/xjc">
			&lt;exclude name="**/*.java" />
		&lt;/fileset>
	&lt;/copy>
&lt;/target>

Using plugins with Maven2

Schema compilation in Maven2 is performed by specialized Maven2 plugins for JAXB2. There's a number of such plugins available. Jaxb2-Commons primarily uses the maven-jaxb2-plugin, so we'll refer to this Maven2 plugin in this document as well.

In order to use this plugin, you have to include the corresponding declaration into your build/plugins element:

&lt;build>
	&lt;plugins>
		&lt;plugin>
			&lt;groupId>org.jvnet.jaxb2.maven2&lt;/groupId>
			&lt;artifactId>maven-jaxb2-plugin&lt;/artifactId>
			&lt;executions>
				&lt;execution>
					&lt;goals>
						&lt;goal>generate&lt;/goal>
					&lt;/goals>
				&lt;/execution>
			&lt;/executions>
		&lt;/plugin>
	&lt;/plugins>
&lt;/build>

The definition above includes schema compilation performed by XJC into the generate-source phase of the build.

Exposing plugins to the Maven2 JAXB2 plugin

In order to make your plugins available to the Maven2 JAXB2 plugin, you have to include their dependencies into the plugin definition:

&lt;plugin>
	&lt;groupId>org.jvnet.jaxb2.maven2&lt;/groupId>
	&lt;artifactId>maven-jaxb2-plugin&lt;/artifactId>
	&lt;dependencies>
		&lt;dependency>
			&lt;groupId>org.jvnet.jaxb2_commons&lt;/groupId>
			&lt;artifactId>basic&lt;/artifactId>
			&lt;version>0.1&lt;/version>
		&lt;/dependency>
	&lt;/dependencies>
	&lt;executions>
		&lt;execution>
			&lt;goals>
				&lt;goal>generate&lt;/goal>
			&lt;/goals>
		&lt;/execution>
	&lt;/executions>
&lt;/plugin>

If dependencies of your plugin are well-managed, Maven will include them transitively. So, in example above we don't need to mind the dependencies of the basic plugins - they will be included by Maven automatically.

Activating plugins

Activation of plugins is done via the configuration/args/arg elements in the plugin definition:

&lt;plugin>
	&lt;groupId>org.jvnet.jaxb2.maven2&lt;/groupId>
	&lt;artifactId>maven-jaxb2-plugin&lt;/artifactId>
	&lt;dependencies>
		&lt;dependency>
			&lt;groupId>org.jvnet.jaxb2_commons&lt;/groupId>
			&lt;artifactId>basic&lt;/artifactId>
			&lt;version>0.1&lt;/version>
		&lt;/dependency>
	&lt;/dependencies>
	&lt;configuration>
		&lt;args>
			&lt;arg>-XtoString&lt;/arg>
			&lt;arg>-Xequals&lt;/arg>
			&lt;arg>-XhashCode&lt;/arg>
			&lt;arg>-Xcopyable&lt;/arg>
		&lt;/args>
	&lt;/configuration>
	&lt;executions>
		&lt;execution>
			&lt;goals>
				&lt;goal>generate&lt;/goal>
			&lt;/goals>
		&lt;/execution>
	&lt;/executions>
&lt;/plugin>

Compile/runtime dependencies

Don't forget to include further compile/runtime/test dependencies into your project. For instance, the code generated by the basic plugins depends on the runtime module and uses testing module for unit-tests. Therefore we'll include these dependencies into our pom:

&lt;project xmlns="http://maven.apache.org/POM/4.0.0" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	&lt;!-- ... -->
	&lt;dependencies>
		&lt;dependency>
			&lt;groupId>org.jvnet.jaxb2_commons&lt;/groupId>
			&lt;artifactId>runtime&lt;/artifactId>
			&lt;version>0.1&lt;/version>
		&lt;/dependency>
		&lt;dependency>
			&lt;groupId>org.jvnet.jaxb2_commons&lt;/groupId>
			&lt;artifactId>testing&lt;/artifactId>
			&lt;version>0.1&lt;/version>
			&lt;scope>test&lt;/scope>
		&lt;/dependency>
	&lt;/dependencies>
	&lt;!-- ... -->
&lt;/project>

Troubleshooting

The first step to diagnose and resolve build problems is to turn on debugging information:

ant -d -v clean install

for Ant or

mvn -X clean install

for Maven2.

The most frequent problem reported by users is "unrecognized parameer -X_myPlugin_". There can be two reasons for this: either the plugin could not be found, or it cannot be instantiated.
To resolve this issue carefull check your XJC classpath. It must contain your plugin and all of is dependencies.
addon.AddOn).

If the problem persists, turn on tracing of the add-on instantiation in XJC. This is done by adding -Dcom.sun.tools.xjc.Options.findServices=true to your ANT_OPTS.

Examples

For your reference, Jaxb2-Commons include a number of ready-to-use sample projects for Maven2 and Ant.

Developing plugins

The following sections intend to help new developers start writing JAXB 2 plugins. We cover general questions of plugin development as well as illustrate how Jaxb2-Commons framework may ease the life of plugin developers.

Plugin basics

Plugins are simply Java classes that extend the base abstract class com.sun.tools.xjc.Plugin.

A minimalistic plugin must have a default public no-arg constructor and implement three abstract methods of the base class:

public abstract String getOptionName();
public abstract String getUsage();
public abstract boolean run(Outline outline, Options opt, ErrorHandler errorHandler);

Option name

Each plugin has an assigned option name. This option name is used by plugin end-users to activate the plugin. Option name must start with a "-". By convention, plugins also add "X" prefix to the option name. So typically the option name will be something like "-X_myPlugin_". For instance, "-XtoString.

Usage

Usage is simply a textual description of plugin functionality/configuration.

Run method

The run(...) method is the central method of the plugin class. This method will be invoked by XJC after the schema was processed and the code model was generated. Still, at this point the code model was not yet written to disk. This means it still gives a chance to be augment by the plugin.

This method has three arguments: outline, options and error handler.

Outline is the complete model generated by XJC. It contains processed schema constructs (package, class and field outlines), generated code model and so on. Outline is the main object that your plugin will work with. You may use this object for analysis, but you may also modify it to change or extend the code that will finall make up the source code of the schema-derived classes.

Options describe the configuration of the environment in which XJC runs. From the options object you can retrieve source and target directories, included schemas and further XJC parameters.

Error handler is intended to be the error reporting mechanism. Theoretically, errors or problems shold be reported as SAX exceptions through the error handler. However, in practice almoust noone does this.

Further methods

The com.sun.tools.xjc.Plugin has a couple more methods which allow the plugin to be parameterized, enable proccessing of vendor-specific bindings and provide few more plugin hooks. This advanced plugin functionality is covered in the following sections.

Parameterizable plugins

Quite often plugin's behaviour must be customized by some externally provided parameter. This is done via the parseArgument method:

public int parseArgument( Options opions, String[] args, int i) throws BadCommandLineException, IOException;

The plugin must consider arguments (args) starting at position i and return the number of tokens it hase recognized and consumed. If nothing is consumed, simply return 0.

Arguments are passed to XJC exactly the same way plugins are activated. So you'll do something like

&lt;xjc target="${basedir}/target/generated-sources/xjc" extension="true">
	&lt;arg line="
		-XmyPlugin
		-XmyPlugin-myOption=value"/>
	&lt;!-- ... -->
&lt;/xjc>

By convention, plugin sub-options must start with plugin option name followed by "". So, "-X_myPlugin_myOption=value" would mean that the myOption parameter of the myPlugin plugin has have the certain "value". In this case, plugin must recognize this option and return 1.

Extending the AbstractParameterizablePlugin

I you feel fine with the proposed sub-option conventions, instead of implementing the parseArgument(...) method yourself, you may simply base your plugin on org.jvnet.jaxb2_commons.plugin.AbstractParameterizablePlugin from Jaxb2-Commons-Tools. This abstract plugin provides an implementation of the parseArgument(...) method which recognizes all "X_myPlugin_myOption=value" parameters and injects the value into the plugin via setter. Ex. "X_myPlugin_myOption=value" will result in myPlugin.setMyOption("value") invocation.
Injection is done with bean introspection implemented by Commons BeanUtils, so this will handle all basic datatype conversions and so on.
Essentially, you just need to define setters - and your plugin becomes parameterizable.

For example, the ToString plugin has an option to parameterize which string builder will be used when creating string representation of schema-derived object. This parameter is simply a class which can be passed from the outside.

Instead of implementing parseArgument(...), ToString plugin simply extends AbstractParameterizablePlugin and declares the toStringBuilder property:

public class ToStringPlugin extends AbstractParameterizablePlugin {

  @Override
  public String getOptionName() {
    return "XtoString";
  }

  @Override
  public String getUsage() {
    return ""
        + "-XtoString:  generates toString() method based on Jakarta Commons Lang"
        + "-XtoString-toStringBuilder:  toString builder class to use. Defaults to "
        + JAXBToStringBuilder.class.getName()
        + ".";
  }

  private Class toStringBuilder = JAXBToStringBuilder.class;

  public void setToStringBuilder(Class equalsBuilderClass) {
    if (!ToStringBuilder.class.isAssignableFrom(equalsBuilderClass))
      throw new IllegalArgumentException("The class must extend ["
          + ToStringBuilder.class.getName()
          + "].");
    this.toStringBuilder = equalsBuilderClass;
  }

  public Class getToStringBuilder() {
    return toStringBuilder;
  }

  // ...
}

In case the command line contains the -XtoString-toStringBuilder=com.acme.foo.MyToStringBuilder argument, the com.acme.foo.MyToStringBuilder.class will be passed to the plugin through the setToStringBuilder(...) setter.

Writing a Spring-configurable plugin

Using extension bindings

Writing a parameterizable plugin

Building the plugin

Jaxb2-Commons Maven2 build system

Testing the plugin

Programmatic execution of the plugin

Integrated testing

Documenting the plugin

Distributing the plugin