Mar 05 2010

JAXB, XmlRootElement, and XML case sensitivity

Category: Java,JAXB,Spring Framework,XMLRussell Pitre @ 1:51 pm

Today I started to incorporate our organization’s canonical data model (a SOA design pattern) defined in XSD into a Java web app with the use of JAXB annotations. JAXB is a framework for binding Java objects to XML elements and vice versa through the use of JAXB annotations. The domain layer within our web application closely matches the canonical data model already so refactoring the web application was not necessary. The purpose of this exercise is to prepare for the development of soap-based web services.

I ran into a 2 hour wall while developing a unit test for the domain object. I want the unit test to ensure that my JAXB annotations are setup correctly and that they should be honoring the xsd document.

Here’s a look at a xsd snippet.

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
	targetNamespace="http://acme.com/schema/type/project/v1" 
	xmlns:project="http://acme.com/schema/type/project/v1" 
	xmlns:company="http://acme.com/schema/type/company/v1"
	elementFormDefault="qualified">

	<xsd:import namespace="http://acme.com/schema/type/company/v1" schemaLocation="../company/company.xsd"/>

	<xsd:element name="Project" type="project:ProjectType"/>
	
	<xsd:complexType name="ProjectType">
		<xsd:annotation>
			<xsd:documentation>Represents a project that acme manages.  This data is stored in the Prolog database.</xsd:documentation>
		</xsd:annotation>
		<xsd:sequence>
			<xsd:element name="name" type="xsd:string"/>
			<xsd:element name="number" type="xsd:string"/>
			<xsd:element name="status" type="xsd:string"/>
		</xsd:sequence>
		<xsd:attribute name="projectId" type="xsd:int"/>
	</xsd:complexType>
	
</xsd:schema>

Here’s a look at the domain java object.

package com.acme.model.project;

// imports removed for brevitiy

@XmlAccessorType(XmlAccessType.NONE)
@XmlType(name = "ProjectType", propOrder = {
		"name",
		"number",
		"status"
})
@XmlRootElement
public class Project extends BaseObject {

	private static final long serialVersionUID = 7189638916879376326L;

	@XmlAttribute
	private Integer projectId;
	
	@XmlElement(required = true)
	private String name;
	
	@XmlElement(required = true)
	private String number;
	
	@XmlElement(required = true)
	private String status;
	
	// getters/setters removed for brevitiy
}

Here’s a look at the unit test

package com.acme.model.project;

// imports removed for brevitiy

public class ProjectTest {

	private JAXBContext jAXBContext;
	
	@Before
	public void setup() throws Exception {
		jAXBContext = JAXBContext.newInstance(Project.class);
	}

	/**
	 * This test requires an accompying Project.xml located in
	 * src/test/resources/com/acme/model/project
	 * 
	 */
	@Test
	public void testJAXBAnnotations() throws Exception {
		Resource schemaResource = new ClassPathResource("com/acme/schema/type/project/project.xsd");
		File schema = schemaResource.getFile();
		
		Unmarshaller unmarshaller = jAXBContext.createUnmarshaller();
		SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
		unmarshaller.setSchema(sf.newSchema(schema));

		Project project = (Project) unmarshaller.unmarshal(new File("src/test/resources/com/acme/model/project/Project.xml"));

		Assert.assertNotNull(project);	
	}
}

So, lets take a look at the unit test to see what’s going on. In the unit test we’re are loading the schema definition using the resource abtraction provided by the Spring Framework on lines 21 & 22. We then create an Unmarshaller and give it the schema on lines 24-26. Setting the schema effectively tells the Unmarshaller to validate the xml file; specificying a null schema disables validation. The last step is the meat of the test, which is to unmarshall a xml file. If we have any problems with our JAXB annotations they will surely show up here.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Project xmlns="http://acme.com/schema/type/project/v1" projectId="1234">
    <name>Sample Project</name>
    <number>091234</number>
    <status>Closed</status>
</Project>

Run the test.

…and this is what we get.

javax.xml.bind.UnmarshalException: unexpected element (uri:"http://acme.com/schema/type/project/v1", local:"Project"). Expected elements are <{http://acme.com/schema/type/project/v1}project>
	at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.handleEvent(UnmarshallingContext.java:556)
	at com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader.reportError(Loader.java:199)
	at com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader.reportError(Loader.java:194)
	at com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader.reportUnexpectedChildElement(Loader.java:71)
	at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext$DefaultRootLoader.childElement(UnmarshallingContext.java:962)
	at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext._startElement(UnmarshallingContext.java:399)
	at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.startElement(UnmarshallingContext.java:380)
	at com.sun.xml.internal.bind.v2.runtime.unmarshaller.ValidatingUnmarshaller.startElement(ValidatingUnmarshaller.java:74)
.
.
.
.
.

The error is barking at us saying the element is not understood by the JAXB framework. This is where i hit a wall for about two hours today, scratching my head wondering what i had wrong. Both the xsd Project element definition and root element matched, yet still i have this problem. Everything matches up except for the @XmlRootElement on the Project class. By default, JAXB uses the camelcase version of the class name as the xml element name.

A simple configuration change from @XmlRootElement to @XmlRootElement (name = “Project”) fixed the problem and now we see green bars!


Jul 29 2009

maven-jaxb-plugin

Category: Java,JAXB,Maven,XMLRussell Pitre @ 8:25 pm

I’ve recently started an extranet based webapp that will be consuming a web service hosted on an intranet webapp.   On both the server and client sides i’m using Spring Web Services.  Spring WS provides a nice Object/XML (OXM) mapping abstraction layer that supports a few different oxm frameworks like JAXB, Castor, and XStream.  I haven’t had experience with any of them before this project so I chose JAXB only because it seems to be the most widely used.

A best practice is top-down, or contract first development when designing web services.  A really good place to start with SOA development is Web Service Contract Design and Versioning for SOA by Thomas Erl.

The first step i took was to define the data model for the messages with xml schema. I discovered a great Maven plugin to generate source code from the XSDs for the web service.  Here’s the snippet that i used in my pom.xml

<plugin>
	<groupId>com.sun.tools.xjc.maven2</groupId>
	<artifactId>maven-jaxb-plugin</artifactId>
	<executions>
		<execution>
			<phase>generate-sources</phase>
			<goals>
				<goal>generate</goal>
			</goals>
		</execution>
	</executions>
	<configuration>
		<schemaDirectory>src/main/resources/com/mycompany/xml/schema</schemaDirectory>
		<includeSchemas>
			<includeSchema>**/*.xsd</includeSchema>
		</includeSchemas>
		<includeBindings>
			<includeBinding>**/*.xjb</includeBinding>
		</includeBindings>
		<verbose>true</verbose>
		<removeOldOutput>true</removeOldOutput>
	</configuration>
</plugin>