diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifier.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifier.java index 49af2bf4..16c9471a 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifier.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifier.java @@ -23,13 +23,23 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.ErrorListener; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; +import javax.xml.transform.sax.SAXSource; import javax.xml.transform.stream.StreamResult; -import javax.xml.transform.stream.StreamSource; import org.springframework.http.MediaType; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; @@ -47,12 +57,14 @@ public class PrettyPrintingContentModifier implements ContentModifier { @Override public byte[] modifyContent(byte[] originalContent, MediaType contentType) { - for (PrettyPrinter prettyPrinter : PRETTY_PRINTERS) { - try { - return prettyPrinter.prettyPrint(originalContent).getBytes(); - } - catch (Exception ex) { - // Continue + if (originalContent.length > 0) { + for (PrettyPrinter prettyPrinter : PRETTY_PRINTERS) { + try { + return prettyPrinter.prettyPrint(originalContent).getBytes(); + } + catch (Exception ex) { + // Continue + } } } return originalContent; @@ -74,10 +86,61 @@ public class PrettyPrintingContentModifier implements ContentModifier { "4"); transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "yes"); StringWriter transformed = new StringWriter(); - transformer.transform(new StreamSource(new ByteArrayInputStream(original)), + transformer.setErrorListener(new SilentErrorListener()); + transformer.transform(createSaxSource(original), new StreamResult(transformed)); return transformed.toString(); } + + private SAXSource createSaxSource(byte[] original) + throws ParserConfigurationException, SAXException { + SAXParserFactory parserFactory = SAXParserFactory.newInstance(); + SAXParser parser = parserFactory.newSAXParser(); + XMLReader xmlReader = parser.getXMLReader(); + xmlReader.setErrorHandler(new SilentErrorHandler()); + SAXSource xmlInput = new SAXSource(xmlReader, new InputSource( + new ByteArrayInputStream(original))); + return xmlInput; + } + + private static final class SilentErrorListener implements ErrorListener { + + @Override + public void warning(TransformerException exception) + throws TransformerException { + + } + + @Override + public void error(TransformerException exception) throws TransformerException { + + } + + @Override + public void fatalError(TransformerException exception) + throws TransformerException { + + } + + } + + private static final class SilentErrorHandler implements ErrorHandler { + + @Override + public void warning(SAXParseException exception) throws SAXException { + + } + + @Override + public void error(SAXParseException exception) throws SAXException { + + } + + @Override + public void fatalError(SAXParseException exception) throws SAXException { + + } + } } private static final class JsonPrettyPrinter implements PrettyPrinter { diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifierTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifierTests.java index 85e5edf9..a06c9400 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifierTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/PrettyPrintingContentModifierTests.java @@ -16,9 +16,12 @@ package org.springframework.restdocs.operation.preprocess; +import org.junit.Rule; import org.junit.Test; +import org.springframework.restdocs.test.OutputCapture; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.Matchers.isEmptyString; import static org.junit.Assert.assertThat; /** @@ -29,6 +32,9 @@ import static org.junit.Assert.assertThat; */ public class PrettyPrintingContentModifierTests { + @Rule + public OutputCapture outputCapture = new OutputCapture(); + @Test public void prettyPrintJson() throws Exception { assertThat(new PrettyPrintingContentModifier().modifyContent( @@ -56,7 +62,9 @@ public class PrettyPrintingContentModifierTests { @Test public void nonJsonAndNonXmlContentIsHandledGracefully() throws Exception { String content = "abcdefg"; + this.outputCapture.expect(isEmptyString()); assertThat(new PrettyPrintingContentModifier().modifyContent(content.getBytes(), null), equalTo(content.getBytes())); + } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/test/OutputCapture.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/test/OutputCapture.java new file mode 100644 index 00000000..4aab9668 --- /dev/null +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/test/OutputCapture.java @@ -0,0 +1,154 @@ +/* + * Copyright 2012-2015 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.restdocs.test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + +import org.hamcrest.Matcher; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import static org.hamcrest.Matchers.allOf; +import static org.junit.Assert.assertThat; + +/** + * JUnit {@code @Rule} to capture output from System.out and System.err. + * + * @author Phillip Webb + * @author Andy Wilkinson + */ +public class OutputCapture implements TestRule { + + private CaptureOutputStream captureOut; + + private CaptureOutputStream captureErr; + + private ByteArrayOutputStream capturedOutput; + + private List> matchers = new ArrayList>(); + + @Override + public Statement apply(final Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + captureOutput(); + try { + base.evaluate(); + } + finally { + try { + if (!OutputCapture.this.matchers.isEmpty()) { + assertThat(getOutputAsString(), + allOf(OutputCapture.this.matchers)); + } + } + finally { + releaseOutput(); + } + } + } + }; + } + + private void captureOutput() { + this.capturedOutput = new ByteArrayOutputStream(); + this.captureOut = new CaptureOutputStream(System.out, this.capturedOutput); + this.captureErr = new CaptureOutputStream(System.err, this.capturedOutput); + System.setOut(new PrintStream(this.captureOut)); + System.setErr(new PrintStream(this.captureErr)); + } + + private String getOutputAsString() { + flush(); + return this.capturedOutput.toString(); + } + + private void releaseOutput() { + System.setOut(this.captureOut.getOriginal()); + System.setErr(this.captureErr.getOriginal()); + this.capturedOutput = null; + } + + private void flush() { + try { + this.captureOut.flush(); + this.captureErr.flush(); + } + catch (IOException ex) { + // ignore + } + } + + /** + * Verify that the output is matched by the supplied {@code matcher}. Verification is + * performed after the test method has executed. + * + * @param matcher the matcher + */ + public final void expect(Matcher matcher) { + this.matchers.add(matcher); + } + + private static final class CaptureOutputStream extends OutputStream { + + private final PrintStream original; + + private final OutputStream copy; + + private CaptureOutputStream(PrintStream original, OutputStream copy) { + this.original = original; + this.copy = copy; + } + + @Override + public void write(int b) throws IOException { + this.copy.write(b); + this.original.write(b); + this.original.flush(); + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + this.copy.write(b, off, len); + this.original.write(b, off, len); + } + + private PrintStream getOriginal() { + return this.original; + } + + @Override + public void flush() throws IOException { + this.copy.flush(); + this.original.flush(); + } + + } + +}