diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java index 15b7d8e781f..5da79e1cd5f 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java @@ -17,21 +17,18 @@ package org.springframework.http.converter.xml; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.HashSet; +import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamReader; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMResult; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.sax.SAXSource; import javax.xml.transform.stream.StreamResult; @@ -43,7 +40,6 @@ import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.XMLReaderFactory; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; @@ -52,7 +48,6 @@ import org.springframework.http.converter.HttpMessageConversionException; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.util.StreamUtils; -import org.springframework.util.xml.StaxUtils; /** * Implementation of {@link org.springframework.http.converter.HttpMessageConverter} @@ -63,102 +58,100 @@ import org.springframework.util.xml.StaxUtils; */ public class SourceHttpMessageConverter extends AbstractHttpMessageConverter { - private final TransformerFactory transformerFactory = TransformerFactory.newInstance(); + private static final Set> SUPPORTED_CLASSES = new HashSet>(4); - private boolean processExternalEntities = false; - - /** - * Sets the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes} - * to {@code text/xml} and {@code application/xml}, and {@code application/*-xml}. - */ - public SourceHttpMessageConverter() { - super(MediaType.APPLICATION_XML, MediaType.TEXT_XML, new MediaType("application", "*+xml")); - } - - - /** - * Indicates whether external XML entities are processed when converting - * to a Source. - *

Default is {@code false}, meaning that external entities are not resolved. - */ - public void setProcessExternalEntities(boolean processExternalEntities) { - this.processExternalEntities = processExternalEntities; - } - - @Override - public boolean supports(Class clazz) { - return DOMSource.class.equals(clazz) || SAXSource.class.equals(clazz) - || StreamSource.class.equals(clazz) || Source.class.equals(clazz); + static { + SUPPORTED_CLASSES.add(DOMSource.class); + SUPPORTED_CLASSES.add(SAXSource.class); + SUPPORTED_CLASSES.add(StreamSource.class); + SUPPORTED_CLASSES.add(Source.class); } - @Override - protected T readInternal(Class clazz, HttpInputMessage inputMessage) - throws IOException, HttpMessageNotReadableException { - InputStream body = inputMessage.getBody(); - if (DOMSource.class.equals(clazz)) { - return (T) readDOMSource(body); - } - else if (StaxUtils.isStaxSourceClass(clazz)) { - return (T) readStAXSource(body); - } - else if (SAXSource.class.equals(clazz)) { - return (T) readSAXSource(body); - } - else if (StreamSource.class.equals(clazz) || Source.class.equals(clazz)) { - return (T) readStreamSource(body); - } - else { - throw new HttpMessageConversionException("Could not read class [" + clazz + - "]. Only DOMSource, SAXSource, and StreamSource are supported."); - } - } + private final TransformerFactory transformerFactory = TransformerFactory.newInstance(); - private DOMSource readDOMSource(InputStream body) throws IOException { - try { - DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); - documentBuilderFactory.setNamespaceAware(true); - documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", processExternalEntities); - DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); - Document document = documentBuilder.parse(body); - return new DOMSource(document); - } - catch (ParserConfigurationException ex) { - throw new HttpMessageNotReadableException("Could not set feature: " + ex.getMessage(), ex); - } - catch (SAXException ex) { - throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex); - } - } + private boolean processExternalEntities = false; - private SAXSource readSAXSource(InputStream body) throws IOException { - try { - XMLReader reader = XMLReaderFactory.createXMLReader(); - reader.setFeature("http://xml.org/sax/features/external-general-entities", processExternalEntities); - byte[] bytes = StreamUtils.copyToByteArray(body); - return new SAXSource(reader, new InputSource(new ByteArrayInputStream(bytes))); - } - catch (SAXException ex) { - throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex); - } - } - private Source readStAXSource(InputStream body) { - try { - XMLInputFactory inputFactory = XMLInputFactory.newFactory(); - inputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", processExternalEntities); - XMLStreamReader streamReader = inputFactory.createXMLStreamReader(body); - return StaxUtils.createStaxSource(streamReader); - } - catch (XMLStreamException ex) { - throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex); - } - } + /** + * Sets the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes} + * to {@code text/xml} and {@code application/xml}, and {@code application/*-xml}. + */ + public SourceHttpMessageConverter() { + super(MediaType.APPLICATION_XML, MediaType.TEXT_XML, new MediaType("application", "*+xml")); + } - private StreamSource readStreamSource(InputStream body) throws IOException { - byte[] bytes = StreamUtils.copyToByteArray(body); - return new StreamSource(new ByteArrayInputStream(bytes)); - } + + /** + * Indicates whether external XML entities are processed when converting to a Source. + *

Default is {@code false}, meaning that external entities are not resolved. + */ + public void setProcessExternalEntities(boolean processExternalEntities) { + this.processExternalEntities = processExternalEntities; + } + + + @Override + public boolean supports(Class clazz) { + return SUPPORTED_CLASSES.contains(clazz); + } + + @Override + @SuppressWarnings("unchecked") + protected T readInternal(Class clazz, HttpInputMessage inputMessage) + throws IOException, HttpMessageNotReadableException { + + InputStream body = inputMessage.getBody(); + if (DOMSource.class.equals(clazz)) { + return (T) readDOMSource(body); + } + else if (SAXSource.class.equals(clazz)) { + return (T) readSAXSource(body); + } + else if (StreamSource.class.equals(clazz) || Source.class.equals(clazz)) { + return (T) readStreamSource(body); + } + else { + throw new HttpMessageConversionException("Could not read class [" + clazz + + "]. Only DOMSource, SAXSource, and StreamSource are supported."); + } + } + + private DOMSource readDOMSource(InputStream body) throws IOException { + try { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + documentBuilderFactory.setFeature( + "http://xml.org/sax/features/external-general-entities", this.processExternalEntities); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document document = documentBuilder.parse(body); + return new DOMSource(document); + } + catch (ParserConfigurationException ex) { + throw new HttpMessageNotReadableException("Could not set feature: " + ex.getMessage(), ex); + } + catch (SAXException ex) { + throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex); + } + } + + private SAXSource readSAXSource(InputStream body) throws IOException { + try { + XMLReader reader = XMLReaderFactory.createXMLReader(); + reader.setFeature( + "http://xml.org/sax/features/external-general-entities", this.processExternalEntities); + byte[] bytes = StreamUtils.copyToByteArray(body); + return new SAXSource(reader, new InputSource(new ByteArrayInputStream(bytes))); + } + catch (SAXException ex) { + throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex); + } + } + + private StreamSource readStreamSource(InputStream body) throws IOException { + byte[] bytes = StreamUtils.copyToByteArray(body); + return new StreamSource(new ByteArrayInputStream(bytes)); + } @Override protected Long getContentLength(T t, MediaType contentType) { @@ -175,11 +168,11 @@ public class SourceHttpMessageConverter extends AbstractHttpMe return null; } - @Override - protected void writeInternal(T t, HttpOutputMessage outputMessage) - throws IOException, HttpMessageNotWritableException { + @Override + protected void writeInternal(T t, HttpOutputMessage outputMessage) + throws IOException, HttpMessageNotWritableException { try { - Result result = new StreamResult(outputMessage.getBody()); + Result result = new StreamResult(outputMessage.getBody()); transform(t, result); } catch (TransformerException ex) { @@ -187,28 +180,28 @@ public class SourceHttpMessageConverter extends AbstractHttpMe } } - private void transform(Source source, Result result) throws TransformerException { - this.transformerFactory.newTransformer().transform(source, result); - } + private void transform(Source source, Result result) throws TransformerException { + this.transformerFactory.newTransformer().transform(source, result); + } - private static class CountingOutputStream extends OutputStream { + private static class CountingOutputStream extends OutputStream { - private long count = 0; + long count = 0; @Override public void write(int b) throws IOException { - count++; + this.count++; } @Override public void write(byte[] b) throws IOException { - count += b.length; + this.count += b.length; } @Override public void write(byte[] b, int off, int len) throws IOException { - count += len; + this.count += len; } } diff --git a/spring-web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java index 8d47c221d1c..7362e880280 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java @@ -16,27 +16,24 @@ package org.springframework.http.converter.xml; -import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual; -import static org.junit.Assert.*; -import static org.junit.Assert.assertNotEquals; - import java.io.IOException; -import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringReader; import java.nio.charset.Charset; - import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamReader; import javax.xml.transform.Source; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.sax.SAXSource; -import javax.xml.transform.stax.StAXSource; import javax.xml.transform.stream.StreamSource; import org.junit.Before; import org.junit.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; @@ -44,12 +41,11 @@ import org.springframework.http.MediaType; import org.springframework.http.MockHttpInputMessage; import org.springframework.http.MockHttpOutputMessage; import org.springframework.util.FileCopyUtils; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.xml.sax.XMLReader; -import org.xml.sax.helpers.DefaultHandler; + +import static org.custommonkey.xmlunit.XMLAssert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; +import static org.junit.Assert.assertTrue; /** * @author Arjen Poutsma @@ -62,16 +58,17 @@ public class SourceHttpMessageConverterTests { private String bodyExternal; + @Before public void setUp() throws IOException { converter = new SourceHttpMessageConverter(); Resource external = new ClassPathResource("external.txt", getClass()); - bodyExternal = "\n" + " ]>&ext;"; } + @Test public void canRead() { assertTrue(converter.canRead(Source.class, new MediaType("application", "xml"))); @@ -131,38 +128,6 @@ public class SourceHttpMessageConverterTests { reader.parse(inputSource); } - @Test - public void readStAXSource() throws Exception { - MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8")); - inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); - StAXSource result = (StAXSource) converter.read(StAXSource.class, inputMessage); - XMLStreamReader streamReader = result.getXMLStreamReader(); - assertTrue(streamReader.hasNext()); - streamReader.nextTag(); - String s = streamReader.getLocalName(); - assertEquals("root", s); - s = streamReader.getElementText(); - assertEquals("Hello World", s); - streamReader.close(); - } - - @Test - public void readStAXSourceExternal() throws Exception { - MockHttpInputMessage inputMessage = new MockHttpInputMessage(bodyExternal.getBytes("UTF-8")); - inputMessage.getHeaders().setContentType(new MediaType("application", "xml")); - StAXSource result = (StAXSource) converter.read(StAXSource.class, inputMessage); - XMLStreamReader streamReader = result.getXMLStreamReader(); - assertTrue(streamReader.hasNext()); - streamReader.next(); - streamReader.next(); - String s = streamReader.getLocalName(); - assertEquals("root", s); - s = streamReader.getElementText(); - assertNotEquals("Foo Bar", s); - streamReader.close(); - } - - @Test public void readStreamSource() throws Exception { MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8")); @@ -203,7 +168,6 @@ public class SourceHttpMessageConverterTests { public void writeSAXSource() throws Exception { String xml = "Hello World"; SAXSource saxSource = new SAXSource(new InputSource(new StringReader(xml))); - MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); converter.write(saxSource, null, outputMessage); assertXMLEqual("Invalid result", "Hello World", @@ -216,7 +180,6 @@ public class SourceHttpMessageConverterTests { public void writeStreamSource() throws Exception { String xml = "Hello World"; StreamSource streamSource = new StreamSource(new StringReader(xml)); - MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); converter.write(streamSource, null, outputMessage); assertXMLEqual("Invalid result", "Hello World",