Using the new Object/XML Mapping Support of Spring 3.0 with JiBX and Maven

The Object to XML mapping functionality (OXM) has been moved from Spring Web Services project to the core Spring Framework with version 3.0 of Spring.

Object/XML Mapping, or O/X mapping for short, is the act of converting an XML document to and from an object. This conversion process is also known as XML Marshalling, XML Serialization or Data Binding. A marshaller serializes an object to XML, and an unmarshaller deserializes XML stream to an object.

Similar to the Object/Relational support, the Spring O/X mapping support does not implement an O/X mapping. It just offers a consistent abstraction for the most popular O/X frameworks available for Java. By that it allows for easily switching from one O/X mapping framework to another. It currently supports JAXB, Castor, XMLBean, JiBX and XStream.

I have used the JAXB abstraction together with Spring Web Services before. So for this blog post I have decided to test the O/X support together with JiBX.

JiBX is a very flexible framework for O/X mapping

  • allowing you to start from existing Java code and generate an XML schema
  • or start from an XML schema and generate Java code
  • or bridge existing code to a schema that represents the same data.

Setting up Maven and JiBX to generate the source code

 

For this test, let’s start with the XML schema and generate the Java code from there, which matches the contract-first approach in a SOA.

<?xml version="1.0" encoding="windows-1252" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns:cus="http://www.trivadis.com/cdm/customer"
            targetNamespace="http://www.trivadis.com/cdm/customer"
            elementFormDefault="qualified">
  <xsd:element name="customer" type="cus:CustomerType"/>
  <xsd:complexType name="CustomerType">
    <xsd:sequence>
      <xsd:element name="id" type="xsd:integer"/>
      <xsd:element name="firstName" type="xsd:string"/>
      <xsd:element name="lastName" type="xsd:string"/>
      <xsd:element name="address" type="cus:AddressType"/>
    </xsd:sequence>
  </xsd:complexType>
  <xsd:complexType name="AddressType">
    <xsd:sequence>
      <xsd:element name="street" type="xsd:string"/>
      <xsd:element name="city" type="xsd:string"/>
      <xsd:element name="zipCode" type="xsd:integer"/>
      <xsd:element name="country" type="xsd:string"/>
    </xsd:sequence>
  </xsd:complexType>
</xsd:schema>

 

Starting from a schema and generating Java code and binding definitions is easy with JiBX. The CodeGen tool allows to use both automatic and customized generation for the XML schemas. CodeGen can be used directly from Java, from the Command Line, from an Ant build and there is also a Maven 2  Plugin for JiBX.

Personally I favor Maven over Ant, so let’s use Maven.

The first thing we have to do is add the JiBX Maven 2 Plugin to the project POM.

<plugin>
	<groupId>org.jibx</groupId>
	<artifactId>maven-jibx-plugin</artifactId>
	<version>1.2.1.1</version>
	<executions>
		<execution>
			<id>generate-java-code-from-schema</id>
			<goals>
				<goal>schema-codegen</goal>
			</goals>
			<configuration>
				<directory>src/main/xsd</directory>
				<includes>
					<include>customer.xsd</include>
				</includes>
			</configuration>
		</execution>
		<execution>
			<id>compile-binding</id>
			<goals>
				<goal>bind</goal>
			</goals>
			<configuration>
				<directory>target/generated-sources</directory>
				<load>true</load>
				<validate>true</validate>
				<verify>true</verify>
			</configuration>
		</execution>
	</executions>
</plugin>

 

The POM also needs to include jibx-run and optionally jibx-extras in its dependencies

<dependency>
	<groupId>org.jibx</groupId>
	<artifactId>jibx-run</artifactId>
	<version>1.2.1</version>
</dependency>
<dependency>
	<groupId>org.jibx</groupId>
	<artifactId>jibx-extras</artifactId>
	<version>1.2.1</version>
</dependency>

 

Now with customer.xsd under src/main/xsd we can generate the sources by running mvn generate-sources.

tmp

 

We can see from the output that a CustomerType and AddressType class has been generated. Here is the source code of the generated CustomerType class

package com.trivadis.cdm.customer;

import com.trivadis.cdm.customer.AddressType;
import java.math.BigInteger;

/** 
 * Schema fragment(s) for this class:
 * <pre>
 * &lt;xs:complexType xmlns:ns="http://www.trivadis.com/cdm/customer" xmlns:xs="http://www.w3.org/2001/XMLSchema" name="CustomerType">
 *   &lt;xs:sequence>
 *     &lt;xs:element type="xs:integer" name="id"/>
 *     &lt;xs:element type="xs:string" name="firstName"/>
 *     &lt;xs:element type="xs:string" name="lastName"/>
 *     &lt;xs:element type="ns:AddressType" name="address"/>
 *   &lt;/xs:sequence>
 * &lt;/xs:complexType>
 * </pre>
 */
public class CustomerType
{
    private BigInteger id;
    private String firstName;
    private String lastName;
    private AddressType address;

    /** 
     * Get the 'id' element value.
     * 
     * @return value
     */
    public BigInteger getId() {
        return id;
    }

    /** 
     * Set the 'id' element value.
     * 
     * @param id
     */
    public void setId(BigInteger id) {
        this.id = id;
    }

    /** 
     * Get the 'firstName' element value.
     * 
     * @return value
     */
    public String getFirstName() {
        return firstName;
    }

    /** 
     * Set the 'firstName' element value.
     * 
     * @param firstName
     */
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    /** 
     * Get the 'lastName' element value.
     * 
     * @return value
     */
    public String getLastName() {
        return lastName;
    }

    /** 
     * Set the 'lastName' element value.
     * 
     * @param lastName
     */
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    /** 
     * Get the 'address' element value.
     * 
     * @return value
     */
    public AddressType getAddress() {
        return address;
    }

    /** 
     * Set the 'address' element value.
     * 
     * @param address
     */
    public void setAddress(AddressType address) {
        this.address = address;
    }
}

 

We can see that a POJO has been generated with a reference to another POJO, the AddressType class. This exactly represents the structure of the XML schema. The name of the classes are derived from the Complex Types and the Java data types used are derived from the XML types. Because we have used xs:integer for the id, a Java BigInteger is generated.

If you are not happy with this default generation, then a lot of things can be customized with JiBX CodeGen!

So let’s say that we would like to remove the suffix “Type” from the class names, so the generated classes are named Customer and Address and that we like to use a Java Integer instead of the BigInteger.

Customizations are normally supplied to JiBX CodeGen in the form of an XML document, although certain customizations can alternatively be set via command line parameters. Here is the XML needed for the customizations mentioned before.

<schema-set prefer-inline="true" generate-all="true"
	xmlns:xs="http://www.w3.org/2001/XMLSchema" type-substitutions="xs:integer xs:int xs:decimal xs:float">
	<name-converter strip-suffixes="Type AttributeGroup Group Attributes" />
</schema-set>

 

It specifies some type substitutions as well as suffixes to strip away from names through a name converter. If the suffix stripping is too general, then a more precise way by specifying the mapping from a complex type to the class name can be used:

<schema-set prefer-inline="true" generate-all="true"
	xmlns:xs="http://www.w3.org/2001/XMLSchema" type-substitutions="xs:integer xs:int xs:decimal xs:float">
	<name-converter strip-suffixes="Type AttributeGroup Group Attributes" />
	<schema name="customer.xsd">
		<complexType name="CustomerT" class-name="Customer" />
		<complexType name="AddressT" class-name="Address" />
	</schema>
</schema-set>

 

In order for Maven to use the customization, we need to change the configuration of the JiBX plugin in the POM and add the <customizations> element with the reference to the customization file.

 

<configuration>
	<directory>src/main/xsd</directory>
	<customizations>
		<customization>src/main/jibx/customization.xml</customization>
	</customizations>

 

With that in place we can again run mvn clean generate-sources and we should now get a Customer and an Address class.

Unfortunately the currently available version 1.2.1.1 of the JiBX Maven Plugin does not support the <customizations> element, even though it’s documented. The following error message will be produced: “Command line options must precede all other arguments: error on ‘-c’”. This is a known bug (JIBX-331) with a patch available but not yet applied!

Thanks to open source, I’ve just got the source code from the repository and applied the patch myself. You can download the patched version 1.2.1.1a from here. Just install it in your local maven repository by running mvn install from the project root.

Before running mvn clean generate-sources again, you need to switch the version for the JiBX plugin in the project POM from 1.2.1.1 to 1.2.1.1a

<plugin>
	<groupId>org.jibx</groupId>
	<artifactId>maven-jibx-plugin</artifactId>
	<version>1.2.1.1a</version>

 

Finally a Customer and Address class is generated and available to be used!

 

Combine the JiBX framework with Spring to marshal from Java objects to XML

To show the O/X mapping of Spring in Action, I just implemented a simple JUnit test class with one method showing the marshalling and unmarshalling from/to a Customer instance.

Spring abstracts all marshalling operations behind the org.springframework.oxm.Marshaller interface and all unmarshalling operations behind the org.springframework.oxm.Unmarshaller interface.

For each supported O/X mapping framework there is an implementation of theses two interfaces. In most cases, one class implements both interfaces. For the JiBX framework this is the org.springframework.oxm.jibx.JibxMarshaller class.

To simplify configuration, Spring also supports XML Schema-based configuration by the OXM namespace. To make the OXM tags available, the appropriate schema has to be referenced first in the preamble of the XML configuration file. Here is the Spring configuration for the JUnit test class.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:oxm="http://www.springframework.org/schema/oxm"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/oxm
    http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd">

	<oxm:jibx-marshaller id="marshaller" target-class="com.trivadis.cdm.customer.Customer"/>
	
</beans>

 

When using the XML Schema-bases configuration, adding the marshaller implementation is a simple one-liner.

Here is the source code of the JUnit test class using the Spring TestContext framework.

@RunWith(SpringJUnit4ClassRunner.class)
//ApplicationContext will be loaded from "classpath:/com/trivadis/soa/PublisherTest-context.xml"
@ContextConfiguration
public class MarshallingTest {
	
	@Autowired
	private Marshaller marshaller;
	@Autowired
	private Unmarshaller unmarshaller;
	
	private static final String FILE_NAME = "C:/temp/customer.xml";

	@Test
	public void testMarshalUnmarshalCustomer() throws Exception {
		Customer cust = null;
		Customer custRead = null;
		
		cust = new Customer();
		cust.setId(1);
		cust.setFirstName("Guido");
		cust.setLastName("Schmutz");
		
		Address adr = new Address();
		adr.setStreet("Papiermuehlestrasse 73");
		adr.setZipCode(3014);
		adr.setCity("Bern");
		adr.setCountry("Switzerland");
		
		cust.setAddress(adr);

		FileOutputStream os = null;
        try {
            os = new FileOutputStream(FILE_NAME);
            marshaller.marshal(cust, new StreamResult(os));
        } finally {
            if (os != null) {
                os.close();
            }
        }
        
        FileInputStream is = null;
        try {
            is = new FileInputStream(FILE_NAME);
            custRead = (Customer) this.unmarshaller.unmarshal(new StreamSource(is));
        } finally {
            if (is != null) {
                is.close();
            }
        }
        
        Assert.assertEquals(cust.getId(), custRead.getId());
        Assert.assertEquals(cust.getFirstName(), custRead.getFirstName());
        Assert.assertEquals(cust.getLastName(), custRead.getLastName());
        Assert.assertEquals(cust.getAddress().getStreet(), custRead.getAddress().getStreet());
        Assert.assertEquals(cust.getAddress().getZipCode(), custRead.getAddress().getZipCode());
        Assert.assertEquals(cust.getAddress().getCity(), custRead.getAddress().getCity());
        Assert.assertEquals(cust.getAddress().getCountry(), custRead.getAddress().getCountry());
	}
}

The JUnit test class defines a dependency on the Marshaller as well as the Unmarshaller. Using @Autowired, the instance defined in the Spring configuration is automatically injected when running the test. The only test method creates an instance of Customer, marshals it to XML and stores it in a file. Then this file is read, unmarshalled from XML to a new Customer instance and compared with the original one.

Before the test can be run, the project needs to be built by running mvn package.  This will also run the JiBX binding compiler which will enhance the Java byte code of the generated classes, i.e. Customer and Address, with the binding information necessary at runtime.

 

The example project can be downloaded from here.

The patched version (1.2.1.1a) of the Maven 2 JiBX Plugin project can be downloaded from here.