Monthly Archives: Juli 2012

Spring RestTemplate with schema less XML

Yesterday I faced a problem that took me a while to solve it. I wrote a client to consume a REST web service. That web service returns schema less XML documents. Let’s say a list of people to keep this example simple:

<people>
  <person>...</person>
  <person>...</person>
</people>

I wanted to map the received data without processing the XML by hand to Java beans and decided to use JAXB for that task.

Spring provides JAXB bindings and also something that is called RestTemplate. This class implements the RestOperations interface and is able to convert HTTP responses with help of HttpMessageConverters into concrete Java objects.

Sounds good, I thought and wired the components:

SpringConfig.java

@Configuration
public class SpringConfig {
	@Autowired
	@Bean(name = "restTemplate")
	public RestOperations restTemplate(Jaxb2Marshaller jaxb2Marshaller) throws Exception {
 
		final MarshallingHttpMessageConverter converter = new MarshallingHttpMessageConverter();
		converter.setMarshaller(jaxb2Marshaller);
		converter.setUnmarshaller(jaxb2Marshaller);
 
		final List<HttpMessageConverter<?>> converterList = new ArrayList<HttpMessageConverter<?>>();
		converterList.add(converter);
 
		RestTemplate restTemplate = new RestTemplate();
		restTemplate.setMessageConverters(converterList);
		return restTemplate;
	}
 
	@Bean
	public Jaxb2Marshaller jaxb2Marshaller() throws Exception {
 
		final Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
		jaxb2Marshaller.setClassesToBeBound(People.class);
		jaxb2Marshaller.setSchema(new ClassPathResource("people-schema.xsd"));
		return jaxb2Marshaller;
	}
 
	@Bean
	@Autowired
	public ClientHttpRequestFactory httpRequestFactory(HttpClient httpClient) {
 
		HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
		requestFactory.setHttpClient(httpClient);
		return requestFactory;
	}
 
	@Bean
	public HttpClient httpClient() {
 
		return new DefaultHttpClient();
	}
}

Actually JAXB doesn’t need a schema to marshal / unmarshal Java objects. But as I didn’t want to write and annotate the beans by hand, I wrote a schema and used the jaxb2-maven-plugin to generate the sources for me:

pom.xml

...
<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>jaxb2-maven-plugin</artifactId>
  <configuration>
    <outputDirectory>src/main/gen</outputDirectory>
    <packageName>my.package.name</packageName>
    <schemaDirectory>src/main/resources</schemaDirectory>
    <removeOldOutput>true</removeOldOutput>
  </configuration>
  <executions>
    <execution>
      <id>xjc</id>
      <goals>
        <goal>xjc</goal>
      </goals>
    </execution>
  </executions>
</plugin>

When I executed the RestTemplate restTemplate.getForObject(url, People.class) I received a strange error:

...
org.springframework.oxm.UnmarshallingFailureException: JAXB unmarshalling exception; nested exception is javax.xml.bind.UnmarshalException
 - with linked exception:
[java.io.FileNotFoundException: C:\......\users.dtd (Das System kann die angegebene Datei nicht finden)]
...

Here is a detailed explanation of that error.

It turned out that the Xerces parser tried to validate the received XML document. But there are no namespaces, no referenced DTD’s, nothing that explains how to validate it. And so the parser tries to load the users.dtd from classpath. It fails with an exception because the DTD does not exists. I cannot tell you why the parser tries to access the users.dtd of all but there is a solution for this…

It’s actually a Xerces feature, not a bug 😉 And the feature is called: http://apache.org/xml/features/nonvalidating/load-external-dtd

To prevent the validation of the received schema less XML documents I had to turn off this feature. Now I knew the solution but how I could access the parser instance to set the feature? The answer is: There is no way to access the parser.. But instead I can provide my own instance by overriding the readFromSource of the MarshallingHttpMessageConverter:

SpringConfig.java

	@Autowired
	@Bean(name = "restTemplate")
	public RestOperations restTemplate(Jaxb2Marshaller jaxb2Marshaller) throws Exception {
 
		// The wesbervice returns a schemaless XML. Therefore we have to turn off the "load external DTD" feature.
		final XMLReader xmlReader = XMLReaderFactory.createXMLReader();
		xmlReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
 
		// There is no intended way to get the XMLReader from the unmarshaler. Instead the received
		// source will be wrapped into a SAXSource with an own XMLReader instance.
		final MarshallingHttpMessageConverter converter = new MarshallingHttpMessageConverter() {
 
			@Override
			protected Object readFromSource(Class<?> clazz, HttpHeaders headers, Source source) throws IOException {
 
				SAXSource overridenSource = new SAXSource(xmlReader, SAXSource.sourceToInputSource(source));
				return super.readFromSource(clazz, headers, overridenSource);
			}
		};
		converter.setMarshaller(jaxb2Marshaller);
		converter.setUnmarshaller(jaxb2Marshaller);
 
		final List<HttpMessageConverter<?>> converterList = new ArrayList<HttpMessageConverter<?>>();
		converterList.add(converter);
 
		RestTemplate restTemplate = new RestTemplate();
		restTemplate.setMessageConverters(converterList);
		return restTemplate;
	}

Two things happened here:

  1. I’ve created an own instance of a XMLReader and turned off the „load external DTD“ feature
  2. I’ve wrapped the source to read from into a new SAXSource and also passed the previously created instance of the XMLReader

And the error is gone! Possible is this because of: http://docs.oracle.com/javase/6/docs/api/javax/xml/bind/Unmarshaller.html#unmarshal(javax.xml.transform.Source)

A client application can choose not to use the default parser mechanism supplied with their JAXB provider. Any SAX 2.0 compliant parser can be substituted for the JAXB provider’s default mechanism. To do so, the client application must properly configure a SAXSource containing an XMLReader implemented by the SAX 2.0 parser provider. If the XMLReader has an org.xml.sax.ErrorHandler registered on it, it will be replaced by the JAXB Provider so that validation errors can be reported via the ValidationEventHandler mechanism of JAXB. If the SAXSource does not contain an XMLReader
, then the JAXB provider’s default parser mechanism will be used.