SPR-5539: Add XML HttpMessageConverters
This commit is contained in:
parent
2a790ad25b
commit
ca01cb4df6
|
@ -59,6 +59,8 @@
|
|||
conf="compile->compile"/>
|
||||
<dependency org="org.springframework" name="org.springframework.core" rev="latest.integration"
|
||||
conf="compile->compile"/>
|
||||
<dependency org="org.springframework" name="org.springframework.oxm" rev="latest.integration"
|
||||
conf="compile->compile"/>
|
||||
<!-- test dependencies -->
|
||||
<dependency org="org.apache.taglibs" name="com.springsource.org.apache.taglibs.standard" rev="1.1.2"
|
||||
conf="test->runtime"/>
|
||||
|
@ -67,6 +69,8 @@
|
|||
<dependency org="org.easymock" name="com.springsource.org.easymock" rev="2.3.0" conf="test->compile"/>
|
||||
<dependency org="org.mortbay.jetty" name="com.springsource.org.mortbay.jetty.server" rev="6.1.9"
|
||||
conf="test->compile"/>
|
||||
<dependency org="org.custommonkey.xmlunit" name="com.springsource.org.custommonkey.xmlunit" rev="1.2.0"
|
||||
conf="test->compile"/>
|
||||
</dependencies>
|
||||
|
||||
</ivy-module>
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Copyright 2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.http.converter.xml;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.AbstractHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConversionException;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverters} that
|
||||
* convert from/to XML.
|
||||
*
|
||||
* <p>By default, subclasses of this converter support {@code text/xml} and {@code application/xml}. This can be
|
||||
* overridden by setting the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes} property.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
public abstract class AbstractXmlHttpMessageConverter<T> extends AbstractHttpMessageConverter<T> {
|
||||
|
||||
private final TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||
|
||||
/**
|
||||
* Protected constructor that sets the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes} to {@code
|
||||
* text/xml} and {@code application/xml}.
|
||||
*/
|
||||
protected AbstractXmlHttpMessageConverter() {
|
||||
super(new MediaType("application", "xml"), new MediaType("text", "xml"));
|
||||
}
|
||||
|
||||
/** Invokes {@link #readFromSource(Class, HttpHeaders, Source)}. */
|
||||
public final T read(Class<T> clazz, HttpInputMessage inputMessage) throws IOException {
|
||||
return readFromSource(clazz, inputMessage.getHeaders(), new StreamSource(inputMessage.getBody()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract template method called from {@link #read(Class, HttpInputMessage)}.
|
||||
*
|
||||
* @param clazz the type of object to return
|
||||
* @param headers the HTTP input headers
|
||||
* @param source the HTTP input body
|
||||
* @return the converted object
|
||||
* @throws IOException in case of I/O errors
|
||||
* @throws org.springframework.http.converter.HttpMessageConversionException in case of conversion errors
|
||||
*/
|
||||
protected abstract T readFromSource(Class<T> clazz, HttpHeaders headers, Source source) throws IOException;
|
||||
|
||||
@Override
|
||||
protected final void writeToInternal(T t, HttpOutputMessage outputMessage) throws IOException {
|
||||
writeToResult(t, outputMessage.getHeaders(), new StreamResult(outputMessage.getBody()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract template method called from {@link #writeToInternal(Object, HttpOutputMessage)}.
|
||||
*
|
||||
* @param t the object to write to the output message
|
||||
* @param headers the HTTP output headers
|
||||
* @param result the HTTP output body
|
||||
* @throws IOException in case of I/O errors
|
||||
* @throws HttpMessageConversionException in case of conversion errors
|
||||
*/
|
||||
protected abstract void writeToResult(T t, HttpHeaders headers, Result result) throws IOException;
|
||||
|
||||
/**
|
||||
* Transforms the given {@code Source} to the {@code Result}.
|
||||
*
|
||||
* @param source the source to transform from
|
||||
* @param result the result to transform to
|
||||
* @throws HttpMessageConversionException in case of transformation errors
|
||||
*/
|
||||
protected void transform(Source source, Result result) {
|
||||
try {
|
||||
Transformer transformer = transformerFactory.newTransformer();
|
||||
transformer.transform(source, result);
|
||||
}
|
||||
catch (TransformerException ex) {
|
||||
throw new HttpMessageConversionException("Could not transform XML", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package org.springframework.http.converter.xml;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.Source;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.oxm.Marshaller;
|
||||
import org.springframework.oxm.Unmarshaller;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter} that can read
|
||||
* and write XML using Spring's {@link Marshaller} and {@link Unmarshaller} abstractions.
|
||||
*
|
||||
* <p>This converter requires a {@code Marshaller} and {@code Unmarshaller} before it can be used. These can be injected
|
||||
* by the {@linkplain #MarshallingHttpMessageConverter(Marshaller) constructor} or {@linkplain
|
||||
* #setMarshaller(Marshaller) bean properties}.
|
||||
*
|
||||
* <p>By default, this converter supports {@code text/xml} and {@code application/xml}. This can be overridden by
|
||||
* setting the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes} property.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.0
|
||||
*/
|
||||
|
||||
public class MarshallingHttpMessageConverter extends AbstractXmlHttpMessageConverter<Object>
|
||||
implements InitializingBean {
|
||||
|
||||
private Marshaller marshaller;
|
||||
|
||||
private Unmarshaller unmarshaller;
|
||||
|
||||
/**
|
||||
* Construct a new {@code MarshallingHttpMessageConverter} with no {@link Marshaller} or {@link Unmarshaller} set. The
|
||||
* marshaller and unmarshaller must be set after construction by invoking {@link #setMarshaller(Marshaller)} and {@link
|
||||
* #setUnmarshaller(Unmarshaller)} .
|
||||
*/
|
||||
public MarshallingHttpMessageConverter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new {@code MarshallingMessageConverter} with the given {@link Marshaller} set. <p>If the given {@link
|
||||
* Marshaller} also implements the {@link Unmarshaller} interface, it is used for both marshalling and unmarshalling.
|
||||
* Otherwise, an exception is thrown. <p>Note that all {@code Marshaller} implementations in Spring also implement the
|
||||
* {@code Unmarshaller} interface, so that you can safely use this constructor.
|
||||
*
|
||||
* @param marshaller object used as marshaller and unmarshaller
|
||||
* @throws IllegalArgumentException when <code>marshaller</code> does not implement the {@link Unmarshaller} interface
|
||||
* as well
|
||||
*/
|
||||
public MarshallingHttpMessageConverter(Marshaller marshaller) {
|
||||
Assert.notNull(marshaller, "marshaller must not be null");
|
||||
if (!(marshaller instanceof Unmarshaller)) {
|
||||
throw new IllegalArgumentException("Marshaller [" + marshaller + "] does not implement the Unmarshaller " +
|
||||
"interface. Please set an Unmarshaller explicitely by using the " +
|
||||
"MarshallingHttpMessageConverter(Marshaller, Unmarshaller) constructor.");
|
||||
}
|
||||
else {
|
||||
this.marshaller = marshaller;
|
||||
this.unmarshaller = (Unmarshaller) marshaller;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new <code>MarshallingMessageConverter</code> with the given {@code Marshaller} and {@code
|
||||
* Unmarshaller}.
|
||||
*
|
||||
* @param marshaller the Marshaller to use
|
||||
* @param unmarshaller the Unmarshaller to use
|
||||
*/
|
||||
public MarshallingHttpMessageConverter(Marshaller marshaller, Unmarshaller unmarshaller) {
|
||||
Assert.notNull(marshaller, "marshaller must not be null");
|
||||
Assert.notNull(unmarshaller, "unmarshaller must not be null");
|
||||
this.marshaller = marshaller;
|
||||
this.unmarshaller = unmarshaller;
|
||||
}
|
||||
|
||||
/** Set the {@link Marshaller} to be used by this message converter. */
|
||||
public void setMarshaller(Marshaller marshaller) {
|
||||
this.marshaller = marshaller;
|
||||
}
|
||||
|
||||
/** Set the {@link Unmarshaller} to be used by this message converter. */
|
||||
public void setUnmarshaller(Unmarshaller unmarshaller) {
|
||||
this.unmarshaller = unmarshaller;
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() {
|
||||
Assert.notNull(this.marshaller, "Property 'marshaller' is required");
|
||||
Assert.notNull(this.unmarshaller, "Property 'unmarshaller' is required");
|
||||
}
|
||||
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return unmarshaller.supports(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object readFromSource(Class<Object> clazz, HttpHeaders headers, Source source) throws IOException {
|
||||
return unmarshaller.unmarshal(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeToResult(Object o, HttpHeaders headers, Result result) throws IOException {
|
||||
marshaller.marshal(o, result);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package org.springframework.http.converter.xml;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.dom.DOMResult;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.sax.SAXSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.converter.HttpMessageConversionException;
|
||||
|
||||
/** @author Arjen Poutsma */
|
||||
public class SourceHttpMessageConverter<T extends Source> extends AbstractXmlHttpMessageConverter<T> {
|
||||
|
||||
public boolean supports(Class<? extends T> clazz) {
|
||||
return Source.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected T readFromSource(Class<T> clazz, HttpHeaders headers, Source source) throws IOException {
|
||||
if (DOMSource.class.equals(clazz)) {
|
||||
DOMResult domResult = new DOMResult();
|
||||
transform(source, domResult);
|
||||
return (T) new DOMSource(domResult.getNode());
|
||||
}
|
||||
else if (SAXSource.class.equals(clazz)) {
|
||||
ByteArrayInputStream bis = transformToByteArray(source);
|
||||
return (T) new SAXSource(new InputSource(bis));
|
||||
}
|
||||
else if (StreamSource.class.equals(clazz) || Source.class.equals(clazz)) {
|
||||
ByteArrayInputStream bis = transformToByteArray(source);
|
||||
return (T) new StreamSource(bis);
|
||||
}
|
||||
else {
|
||||
throw new HttpMessageConversionException(
|
||||
"Could not read class [" + clazz + "]. Only DOMSource, SAXSource, and StreamSource are supported.");
|
||||
}
|
||||
}
|
||||
|
||||
private ByteArrayInputStream transformToByteArray(Source source) {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
transform(source, new StreamResult(bos));
|
||||
return new ByteArrayInputStream(bos.toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeToResult(T t, HttpHeaders headers, Result result) throws IOException {
|
||||
transform(t, result);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<html>
|
||||
<body>
|
||||
|
||||
Provides a HttpMessageConverter implementations for handling XML.
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,60 @@
|
|||
package org.springframework.http.converter.xml;
|
||||
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
|
||||
import static org.easymock.EasyMock.*;
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.MockHttpInputMessage;
|
||||
import org.springframework.http.MockHttpOutputMessage;
|
||||
import org.springframework.oxm.Marshaller;
|
||||
import org.springframework.oxm.Unmarshaller;
|
||||
|
||||
/** @author Arjen Poutsma */
|
||||
public class MarshallingHttpMessageConverterTest {
|
||||
|
||||
private MarshallingHttpMessageConverter converter;
|
||||
|
||||
private Marshaller marshaller;
|
||||
|
||||
private Unmarshaller unmarshaller;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
marshaller = createMock(Marshaller.class);
|
||||
unmarshaller = createMock(Unmarshaller.class);
|
||||
|
||||
converter = new MarshallingHttpMessageConverter(marshaller, unmarshaller);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void read() throws Exception {
|
||||
String body = "<root>Hello World</root>";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
|
||||
|
||||
expect(unmarshaller.unmarshal(isA(StreamSource.class))).andReturn(body);
|
||||
|
||||
replay(marshaller, unmarshaller);
|
||||
String result = (String) converter.read(Object.class, inputMessage);
|
||||
assertEquals("Invalid result", body, result);
|
||||
verify(marshaller, unmarshaller);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void write() throws Exception {
|
||||
String body = "<root>Hello World</root>";
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
|
||||
marshaller.marshal(eq(body), isA(StreamResult.class));
|
||||
|
||||
replay(marshaller, unmarshaller);
|
||||
converter.write(body, outputMessage);
|
||||
assertEquals("Invalid content-type", new MediaType("application", "xml"),
|
||||
outputMessage.getHeaders().getContentType());
|
||||
verify(marshaller, unmarshaller);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package org.springframework.http.converter.xml;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.sax.SAXSource;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
|
||||
import static org.custommonkey.xmlunit.XMLAssert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import org.junit.Test;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.MockHttpInputMessage;
|
||||
import org.springframework.http.MockHttpOutputMessage;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/** @author Arjen Poutsma */
|
||||
public class SourceHttpMessageConverterTest {
|
||||
|
||||
@Test
|
||||
public void readDOMSource() throws Exception {
|
||||
SourceHttpMessageConverter<DOMSource> converter = new SourceHttpMessageConverter<DOMSource>();
|
||||
String body = "<root>Hello World</root>";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
|
||||
DOMSource result = converter.read(DOMSource.class, inputMessage);
|
||||
Document document = (Document) result.getNode();
|
||||
assertEquals("Invalid result", "root", document.getDocumentElement().getLocalName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readSAXSource() throws Exception {
|
||||
SourceHttpMessageConverter<SAXSource> converter = new SourceHttpMessageConverter<SAXSource>();
|
||||
String body = "<root>Hello World</root>";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
|
||||
SAXSource result = converter.read(SAXSource.class, inputMessage);
|
||||
InputSource inputSource = result.getInputSource();
|
||||
String s = FileCopyUtils.copyToString(new InputStreamReader(inputSource.getByteStream()));
|
||||
assertXMLEqual("Invalid result", body, s);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readStreamSource() throws Exception {
|
||||
SourceHttpMessageConverter<StreamSource> converter = new SourceHttpMessageConverter<StreamSource>();
|
||||
String body = "<root>Hello World</root>";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
|
||||
StreamSource result = converter.read(StreamSource.class, inputMessage);
|
||||
String s = FileCopyUtils.copyToString(new InputStreamReader(result.getInputStream()));
|
||||
assertXMLEqual("Invalid result", body, s);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readSource() throws Exception {
|
||||
SourceHttpMessageConverter<Source> converter = new SourceHttpMessageConverter<Source>();
|
||||
String body = "<root>Hello World</root>";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
|
||||
converter.read(Source.class, inputMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void write() throws Exception {
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
documentBuilderFactory.setNamespaceAware(true);
|
||||
Document document = documentBuilderFactory.newDocumentBuilder().newDocument();
|
||||
Element rootElement = document.createElement("root");
|
||||
document.appendChild(rootElement);
|
||||
rootElement.setTextContent("Hello World");
|
||||
DOMSource domSource = new DOMSource(document);
|
||||
|
||||
SourceHttpMessageConverter<Source> converter = new SourceHttpMessageConverter<Source>();
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
converter.write(domSource, outputMessage);
|
||||
assertXMLEqual("Invalid result", "<root>Hello World</root>",
|
||||
outputMessage.getBodyAsString(Charset.forName("UTF-8")));
|
||||
assertEquals("Invalid content-type", new MediaType("application", "xml"),
|
||||
outputMessage.getHeaders().getContentType());
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -15,6 +15,7 @@
|
|||
<orderEntry type="module" module-name="beans" />
|
||||
<orderEntry type="module" module-name="context" />
|
||||
<orderEntry type="module" module-name="core" />
|
||||
<orderEntry type="module" module-name="oxm" />
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
|
@ -222,6 +223,17 @@
|
|||
</SOURCES>
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$IVY_CACHE$/org.custommonkey.xmlunit/com.springsource.org.custommonkey.xmlunit/1.2.0/com.springsource.org.custommonkey.xmlunit-1.2.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$IVY_CACHE$/org.custommonkey.xmlunit/com.springsource.org.custommonkey.xmlunit/1.2.0/com.springsource.org.custommonkey.xmlunit-sources-1.2.0.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</orderEntry>
|
||||
</component>
|
||||
<component name="copyright">
|
||||
<Base>
|
||||
|
|
Loading…
Reference in New Issue