Introduction

xjc is the tools provided by JAX-B that allows to generate Java classes from XSD files. The result of an xjc compilation can be extensively customized through JAX-B bindings, whether embedded or external.
Common customizations include :

  • Generate Serializable classes
  • Define the package for classes generated from a given XML namespace
  • Define custom handler for specific data type or XML elements

Standard xjc compilation takes in an XSD file and optionally a bindings file and generates Java classes for each complex or simple type defined in the xsd file.

In more complex situations, one needs to share common XML types across several independent XML schema. Handling this situation appropriately requires the use of JAX-B bindings and a deeper understanding of the behaviour of xjc

This article shows :

  • how to share common xsd accross independent XML schemas
  • how to handle common xsd libraries across different xjc compilations

Setting up the project

Project layout

Let us consider the following project:

<project-root>
├── build.xml
├── pom.xml
└── src
    └── main
        └── resources
            ├── xjb
            │   ├── bar.xjb
            │   ├── commons.xjb
            │   ├── foo.xjb
            │   └── global.xjb
            └── xsd
                ├── bar
                │   ├── bar-commons.xsd
                │   └── bar.xsd
                ├── commons
                │   └── commons.xsd
                └── foo
                    ├── foo-commons.xsd
                    └── foo.xsd

XML schemas

The XML schema files are located in src/main/resources
The files in the foo and bar directories represent two independent set of XSD files that share some types defined in commons
The files include one another as such :

  • bar.xsd -- includes --> bar-commons.xsd -- includes --> commons.xsd
  • foo.xsd -- includes --> foo-commons.xsd -- includes --> commons.xsd

JAX-B bindings

Customization of the xjc compilation is provided through external binding files in src/main/resource/xjb

  • global.xjb : contains the global bindings
  • commons.xjb : customisation for commons.xsd
  • bar.xjb : customisations for bar-commons.xsd and bar.xsd
  • foo.xjb : customisations for foo-commons.xsd and foo.xsd

Customizing pom.xml

The classes generated by xjc should never be committed to the source repository; they are generated at each compilation so as to insure that the latest version of these classes is always used.
As such, these classes should not be regarded as proper source files, but considered as generated resources and stored separately from the actual project sources.
To this end, the build-helper-maven-plugin plugin is used; it adds an extra source directory (target/src/main/generated) to the project where the generated classes will be stored.

<!-- Add source directory for generated sources -->
<plugin>
	<groupId>org.codehaus.mojo</groupId>
	<artifactId>build-helper-maven-plugin</artifactId>
	<executions>
		<execution>
			<id>add-source</id>
			<phase>generate-sources</phase>
			<goals>
				<goal>add-source</goal>
			</goals>
			<configuration>
				<sources>
					<source>${basedir}/target/src/main/generated</source>
				</sources>
			</configuration>
		</execution>
	</executions>
</plugin>

There are several ways to integrate the use of xjc in a Java project : maven plugins, ant tasks or direct command line invocations.
In this example, xjc is used using direct command line invocation from an ant build file. The reason for this is to insure that all xjc options are available.
Execution of the proper ant task is triggered from maven at the generate-sources phase by the maven-antrun-plugin.

<!-- generate sources -->
<plugin>
	<artifactId>maven-antrun-plugin</artifactId>
	<version>1.6</version>
	<executions>
		<execution>
			<phase>generate-sources</phase>
			<configuration>
				<target>
					<ant antfile="build.xml" target="generate-sources"></ant>
				</target>
			</configuration>
			<goals>
				<goal>run</goal>
			</goals>
		</execution>
	</executions>
</plugin>

Execution of xjc

As explained above, xjc is run calling directly the command-line tool from an ant task. In the build.xml file, this call is wrapped in the following private task:

<target name="-xjc">
	<fail unless="file" />
	<fail unless="bindings" />
		
	<exec executable="${xjc.exec}">
		<arg line="-d ${dir.project.root}/target/src/main/generated" />
		<arg line="-extension" />
		<arg line="-b ${bindings}" />
		<arg line="${file}" />
		<arg line="-nv" />
		<arg line="-npa" />
	</exec>
</target>

Generating the classes

Ant target

The following task is the meat and bones of the generation. This task generates successively in three independent run the classes associated to the common, foo and bar XML schemas.
For each run, the desired output is obtained following these rules :

  • Each XSD file should declare its own namespace. Failing to do so may generate errors with the external bindings.
  • All XML schema whose types should be generated in the same package should be grouped in the same xjc compilation. This induces the generation of a single ObjectFactory class with generation methods for all classes in this package.
  • Each xjc compilation should include the binding files for each included xsd file. Failure to do this will result in double generation of the classes corresponding to included XSD files but in a package whose name is automatically generated by xjc
<target name="generate-sources">

	<!-- compile commons -->			
	<antcall target="-xjc">
		<param name="bindings" value="
			${dir.project.xjb}/global.xjb
			${dir.project.xjb}/commons.xjb
			"/>
		<param name="file" value="
			${dir.project.resources}/xsd/commons/commons.xsd
			"/>    		
	</antcall>
		
	<!-- compile foo -->
	<antcall target="-xjc">
		<param name="bindings" value="
			${dir.project.xjb}/global.xjb
			${dir.project.xjb}/commons.xjb
			${dir.project.xjb}/foo.xjb
			"/>
		<param name="file" value="
			${dir.project.resources}/xsd/foo/foo-commons.xsd
			${dir.project.resources}/xsd/foo/foo.xsd
			"/>    		
	</antcall>

	<!-- compile bar -->
	<antcall target="-xjc">
		<param name="bindings" value="
			${dir.project.xjb}/global.xjb
			${dir.project.xjb}/commons.xjb
			${dir.project.xjb}/bar.xjb
			"/>
		<param name="file" value="
			${dir.project.resources}/xsd/bar/bar-commons.xsd
			${dir.project.resources}/xsd/bar/bar.xsd
			"/>    		
	</antcall>
</target>

Execution

Execution of this tasks gives the following output

-xjc:
     [exec] parsing a schema...
     [exec] compiling a schema...
     [exec] com/example/xsd/commons/CommonType1.java
     [exec] com/example/xsd/commons/CommonType2.java
     [exec] com/example/xsd/commons/ObjectFactory.java

-xjc:
     [exec] parsing a schema...
     [exec] compiling a schema...
     [exec] com/example/xsd/commons/CommonType1.java
     [exec] com/example/xsd/commons/CommonType2.java
     [exec] com/example/xsd/commons/ObjectFactory.java
     [exec] com/example/xsd/foo/AllFoo.java
     [exec] com/example/xsd/foo/Foo1.java
     [exec] com/example/xsd/foo/Foo2.java
     [exec] com/example/xsd/foo/ObjectFactory.java

-xjc:
     [exec] parsing a schema...
     [exec] compiling a schema...
     [exec] com/example/xsd/bar/AllBar.java
     [exec] com/example/xsd/bar/Bar1.java
     [exec] com/example/xsd/bar/Bar2.java
     [exec] com/example/xsd/bar/ObjectFactory.java
     [exec] com/example/xsd/commons/CommonType1.java
     [exec] com/example/xsd/commons/CommonType2.java
     [exec] com/example/xsd/commons/ObjectFactory.java

Generation result

And the directory target/src/main/generated contains:

com/
└── example/
    └── xsd/
        ├── bar/
        │   ├── AllBar.java
        │   ├── Bar1.java
        │   ├── Bar2.java
        │   └── ObjectFactory.java
        ├── commons/
        │   ├── CommonType1.java
        │   ├── CommonType2.java
        │   └── ObjectFactory.java
        └── foo/
            ├── AllFoo.java
            ├── Foo1.java
            ├── Foo2.java
            └── ObjectFactory.java

Source code of the xsd files

xsd/commons/commons.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema
	targetNamespace="http://www.example.com/xsd/commons/commons"
	xmlns="http://www.example.com/xsd/commons/commons" 	
	xmlns:xs="http://www.w3.org/2001/XMLSchema" 
	elementFormDefault="qualified">

	<xs:complexType name="CommonType1">
		<xs:sequence>
			<xs:element name="one" type="xs:string" />
			<xs:element name="two" type="xs:string" />
			<xs:element name="three" type="xs:string" />
		</xs:sequence>
	</xs:complexType>
	
	<xs:complexType name="CommonType2">
		<xs:sequence>
			<xs:element name="four" type="xs:string" />
			<xs:element name="five" type="xs:string" />
			<xs:element name="six" type="xs:string" />
		</xs:sequence>
	</xs:complexType>
</xs:schema>

xsd/foo/commons.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema
	targetNamespace="http://www.example.com/xsd/foo/foo-commons"
	xmlns="http://www.example.com/xsd/foo/foo-commons" 
	xmlns:commons="http://www.example.com/xsd/commons/commons"
	xmlns:xs="http://www.w3.org/2001/XMLSchema" 
	elementFormDefault="qualified">

	<xs:import schemaLocation="../commons/commons.xsd" namespace="http://www.example.com/xsd/commons/commons"  />
		
    <xs:complexType name="Foo1">
		<xs:sequence>
			<xs:element name="common1" type="commons:CommonType1" minOccurs="1" maxOccurs="1"/>
			<xs:element name="foo1" type="xs:string" minOccurs="1" maxOccurs="1" />
		</xs:sequence>
    </xs:complexType>
    
    <xs:complexType name="Foo2">
		<xs:sequence>
			<xs:element name="common2" type="commons:CommonType2" minOccurs="1" maxOccurs="1"/>
			<xs:element name="foo2" type="xs:string" minOccurs="1" maxOccurs="1" />
		</xs:sequence>
    </xs:complexType>

</xs:schema>

xsd/foo/foo.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema
	targetNamespace="http://www.example.com/xsd/foo/foo"
	xmlns="http://www.example.com/xsd/foo/foo" 
	xmlns:commons="http://www.example.com/xsd/commons/commons"
	xmlns:fooco="http://www.example.com/xsd/foo/foo-commons"	
	xmlns:xs="http://www.w3.org/2001/XMLSchema" 
	elementFormDefault="qualified">

	<xs:import schemaLocation="../commons/commons.xsd" namespace="http://www.example.com/xsd/commons/commons"  />
	<xs:import schemaLocation="foo-commons.xsd" namespace="http://www.example.com/xsd/foo/foo-commons"  />
		
	<xs:element name="allFoo">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="foo1" type="fooco:Foo1" minOccurs="1" maxOccurs="1"/>
				<xs:element name="foo2" type="fooco:Foo2" minOccurs="1" maxOccurs="1"/>
			</xs:sequence>
		</xs:complexType>
    </xs:element>

</xs:schema>

xsd/bar/bar-commons.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema
	targetNamespace="http://www.example.com/xsd/bar/bar-commons"
	xmlns="http://www.example.com/xsd/bar/bar-commons" 
	xmlns:commons="http://www.example.com/xsd/commons/commons"
	xmlns:xs="http://www.w3.org/2001/XMLSchema" 
	elementFormDefault="qualified">

	<xs:import schemaLocation="../commons/commons.xsd" namespace="http://www.example.com/xsd/commons/commons"  />

    <xs:complexType name="Bar1">
		<xs:sequence>
			<xs:element name="common1" type="commons:CommonType1" minOccurs="1" maxOccurs="1"/>
			<xs:element name="bar1" type="xs:string" minOccurs="1" maxOccurs="1" />
		</xs:sequence>
    </xs:complexType>
    
    <xs:complexType name="Bar2">
		<xs:sequence>
			<xs:element name="common2" type="commons:CommonType2" minOccurs="1" maxOccurs="1"/>
			<xs:element name="bar2" type="xs:string" minOccurs="1" maxOccurs="1" />
		</xs:sequence>
    </xs:complexType>
	
</xs:schema>

xsd/bar/bar.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema
	targetNamespace="http://www.example.com/xsd/bar/bar"
	xmlns="http://www.example.com/xsd/bar/bar" 
	xmlns:barco="http://www.example.com/xsd/bar/bar-commons"
	xmlns:xs="http://www.w3.org/2001/XMLSchema" 
	elementFormDefault="qualified">

	<xs:import schemaLocation="bar-commons.xsd" namespace="http://www.example.com/xsd/bar/bar-commons" />
	
	<xs:element name="allBar">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="bar1" type="barco:Bar1" minOccurs="1" maxOccurs="1"/>
				<xs:element name="bar2" type="barco:Bar2" minOccurs="1" maxOccurs="1"/>
			</xs:sequence>
		</xs:complexType>
	</xs:element>

</xs:schema>

Sources for the XJB files

xjb/commons.xjb

<?xml version="1.0" encoding="UTF-8"?>
<bindings xmlns="http://java.sun.com/xml/ns/jaxb"
	xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd"
	version="2.1">
	
	<bindings schemaLocation="../xsd/commons/commons.xsd">
    	<schemaBindings>
    		<package name="com.example.xsd.commons"/>
    	</schemaBindings>
    </bindings>
</bindings>

xjb/global.xjb

<?xml version="1.0" encoding="UTF-8"?>
<bindings xmlns="http://java.sun.com/xml/ns/jaxb"
	xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance"	
	xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd"
	version="2.1">

    <globalBindings fixedAttributeAsConstantProperty="true">
    	<serializable uid="1"/>	   	
    </globalBindings>
    
</bindings>

xjb/foo.xjb

<?xml version="1.0" encoding="UTF-8"?>
<bindings xmlns="http://java.sun.com/xml/ns/jaxb"
	xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd"
	version="2.1">
    
    <bindings schemaLocation="../xsd/foo/foo-commons.xsd">
    	<schemaBindings>
    		<package name="com.example.xsd.foo"/>
    	</schemaBindings>
    </bindings>
    
    <bindings schemaLocation="../xsd/foo/foo.xsd">
    	<schemaBindings>
    		<package name="com.example.xsd.foo"/>
    	</schemaBindings>
    </bindings>
</bindings>

xjb/bar.xjb

<?xml version="1.0" encoding="UTF-8"?>
<bindings xmlns="http://java.sun.com/xml/ns/jaxb"
	xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd"
	version="2.1">
    
    <bindings schemaLocation="../xsd/bar/bar-commons.xsd">
    	<schemaBindings>
    		<package name="com.example.xsd.bar"/>
    	</schemaBindings>
    </bindings>
    
    <bindings schemaLocation="../xsd/bar/bar.xsd">
    	<schemaBindings>
    		<package name="com.example.xsd.bar"/>
    	</schemaBindings>
    </bindings>
    
</bindings>
Advertisements