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!