diff --git a/build.gradle b/build.gradle index c28746f77f..0a1817b55d 100644 --- a/build.gradle +++ b/build.gradle @@ -94,7 +94,7 @@ allprojects { servletApiVersion = '4.0.1' sqlfireclientVersion = '1.0.3' sqliteVersion = '3.32.3.2' - woodstoxVersion = '6.2.0' + woodstoxVersion = '6.2.1' xmlunitVersion = '2.7.0' xstreamVersion = '1.4.12' jrubyVersion = '1.7.27' diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemWriter.java index b5f9f8947b..d2672347f6 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2017 the original author or authors. + * Copyright 2006-2020 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. @@ -72,7 +72,7 @@ * @author Peter Zozom * @author Robert Kasanicky * @author Michael Minella - * + * @author Parikshit Dutta */ public class StaxEventItemWriter extends AbstractItemStreamItemWriter implements ResourceAwareItemWriterItemStream, InitializingBean { @@ -85,6 +85,9 @@ public class StaxEventItemWriter extends AbstractItemStreamItemWriter impl // default encoding public static final String DEFAULT_XML_VERSION = "1.0"; + // default standalone document declaration, value not set + public static final Boolean DEFAULT_STANDALONE_DOCUMENT = null; + // default root tag name public static final String DEFAULT_ROOT_TAG_NAME = "root"; @@ -109,6 +112,9 @@ public class StaxEventItemWriter extends AbstractItemStreamItemWriter impl // XML version private String version = DEFAULT_XML_VERSION; + // standalone header attribute + private Boolean standalone = DEFAULT_STANDALONE_DOCUMENT; + // name of the root tag private String rootTagName = DEFAULT_ROOT_TAG_NAME; @@ -270,6 +276,29 @@ public void setVersion(String version) { this.version = version; } + /** + * Get used standalone document declaration. + * + * @return the standalone document declaration used + * + * @since 4.3 + */ + public Boolean getStandalone() { + return standalone; + } + + /** + * Set standalone document declaration to be used for output XML. If not set, + * standalone document declaration will be omitted. + * + * @param standalone the XML standalone document declaration to be used + * + * @since 4.3 + */ + public void setStandalone(Boolean standalone) { + this.standalone = standalone; + } + /** * Get the tag name of the root element. * @@ -606,7 +635,12 @@ protected void startDocument(XMLEventWriter writer) throws XMLStreamException { XMLEventFactory factory = createXmlEventFactory(); // write start document - writer.add(factory.createStartDocument(getEncoding(), getVersion())); + if (getStandalone()==null) { + writer.add(factory.createStartDocument(getEncoding(), getVersion())); + } + else { + writer.add(factory.createStartDocument(getEncoding(), getVersion(), getStandalone())); + } // write root tag writer.add(factory.createStartElement(getRootTagNamespacePrefix(), getRootTagNamespace(), getRootTagName())); diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/builder/StaxEventItemWriterBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/builder/StaxEventItemWriterBuilder.java index d155298b53..0a4a531ab6 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/builder/StaxEventItemWriterBuilder.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/builder/StaxEventItemWriterBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2017-2020 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. @@ -27,6 +27,7 @@ * A builder for the {@link StaxEventItemWriter}. * * @author Michael Minella + * @author Parikshit Dutta * @since 4.0 * @see StaxEventItemWriter */ @@ -50,6 +51,8 @@ public class StaxEventItemWriterBuilder { private String version = StaxEventItemWriter.DEFAULT_XML_VERSION; + private Boolean standalone = StaxEventItemWriter.DEFAULT_STANDALONE_DOCUMENT; + private String rootTagName = StaxEventItemWriter.DEFAULT_ROOT_TAG_NAME; private Map rootElementAttributes; @@ -188,7 +191,7 @@ public StaxEventItemWriterBuilder encoding(String encoding) { * * @param version XML version * @return the current instance of the builder - * @see StaxEventItemWriter#version + * @see StaxEventItemWriter#setVersion(String) */ public StaxEventItemWriterBuilder version(String version) { this.version = version; @@ -196,6 +199,21 @@ public StaxEventItemWriterBuilder version(String version) { return this; } + /** + * Standalone document declaration for the output document. Defaults to null. + * + * @param standalone Boolean standalone document declaration + * @return the current instance of the builder + * @see StaxEventItemWriter#setStandalone(Boolean) + * + * @since 4.3 + */ + public StaxEventItemWriterBuilder standalone(Boolean standalone) { + this.standalone = standalone; + + return this; + } + /** * The name of the root tag for the output document. * @@ -278,6 +296,7 @@ public StaxEventItemWriter build() { writer.setTransactional(this.transactional); writer.setVersion(this.version); writer.setName(this.name); + writer.setStandalone(this.standalone); return writer; } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemWriterTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemWriterTests.java index 1b0169bc16..047711ada2 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemWriterTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemWriterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2014 the original author or authors. + * Copyright 2008-2020 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. @@ -55,6 +55,8 @@ /** * Tests for {@link StaxEventItemWriter}. + * + * @author Parikshit Dutta */ public class StaxEventItemWriterTests { @@ -137,6 +139,35 @@ public void testAssertWriterIsInitialized() throws Exception { writer.write(Collections.singletonList("foo")); } + @Test + public void testStandaloneDeclarationInHeaderWhenNotSet() throws Exception { + writer.open(executionContext); + writer.write(items); + writer.close(); + String content = getOutputFileContent(writer.getEncoding(), false); + assertFalse(content.contains("standalone=")); + } + + @Test + public void testStandaloneDeclarationInHeaderWhenSetToTrue() throws Exception { + writer.setStandalone(true); + writer.open(executionContext); + writer.write(items); + writer.close(); + String content = getOutputFileContent(writer.getEncoding(), false); + assertTrue(content.contains("standalone='yes'")); + } + + @Test + public void testStandaloneDeclarationInHeaderWhenSetToFalse() throws Exception { + writer.setStandalone(false); + writer.open(executionContext); + writer.write(items); + writer.close(); + String content = getOutputFileContent(writer.getEncoding(), false); + assertTrue(content.contains("standalone='no'")); + } + /** * Item is written to the output file only after flush. */ @@ -982,12 +1013,28 @@ private String getOutputFileContent() throws IOException { * @return output file content as String */ private String getOutputFileContent(String encoding) throws IOException { + return getOutputFileContent(encoding, true); + } + + /** + * @param encoding the encoding + * @param discardHeader the flag to strip XML header + * @return output file content as String + */ + private String getOutputFileContent(String encoding, boolean discardHeader) throws IOException { String value = FileUtils.readFileToString(resource.getFile(), encoding); - value = value.replace("", ""); + if (discardHeader) { + // standalone is omitted if not explicitly set, meaning it will be 'yes'/'no' or no standalone attribute + if (value.contains("standalone")) { + boolean standalone = value.contains("standalone='yes'"); + return value.replace("", ""); + } + return value.replace("", ""); + } return value; } - /** * @return new instance of fully configured writer */ diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/builder/StaxEventItemWriterBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/builder/StaxEventItemWriterBuilderTests.java index 1b7ae7dcb1..0ac218defc 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/builder/StaxEventItemWriterBuilderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/builder/StaxEventItemWriterBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2017-2020 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. @@ -46,6 +46,7 @@ /** * @author Michael Minella + * @author Parikshit Dutta */ public class StaxEventItemWriterBuilderTests { @@ -224,9 +225,85 @@ public void testMissingNameValidation() { .build(); } + @Test + public void testStandaloneDeclarationInHeaderWhenNotSet() throws Exception { + StaxEventItemWriter staxEventItemWriter = new StaxEventItemWriterBuilder() + .name("fooWriter") + .marshaller(marshaller) + .resource(this.resource) + .build(); + + staxEventItemWriter.afterPropertiesSet(); + + ExecutionContext executionContext = new ExecutionContext(); + staxEventItemWriter.open(executionContext); + staxEventItemWriter.write(this.items); + staxEventItemWriter.close(); + + String output = getOutputFileContent(staxEventItemWriter.getEncoding(), false); + assertFalse(output.contains("standalone=")); + } + + @Test + public void testStandaloneDeclarationInHeaderWhenSetToTrue() throws Exception { + StaxEventItemWriter staxEventItemWriter = new StaxEventItemWriterBuilder() + .name("fooWriter") + .marshaller(marshaller) + .resource(this.resource) + .standalone(true) + .build(); + + staxEventItemWriter.afterPropertiesSet(); + + ExecutionContext executionContext = new ExecutionContext(); + staxEventItemWriter.open(executionContext); + staxEventItemWriter.write(this.items); + staxEventItemWriter.close(); + + String output = getOutputFileContent(staxEventItemWriter.getEncoding(), false); + assertTrue(output.contains("standalone='yes'")); + } + + @Test + public void testStandaloneDeclarationInHeaderWhenSetToFalse() throws Exception { + StaxEventItemWriter staxEventItemWriter = new StaxEventItemWriterBuilder() + .name("fooWriter") + .marshaller(marshaller) + .resource(this.resource) + .standalone(false) + .build(); + + staxEventItemWriter.afterPropertiesSet(); + + ExecutionContext executionContext = new ExecutionContext(); + staxEventItemWriter.open(executionContext); + staxEventItemWriter.write(this.items); + staxEventItemWriter.close(); + + String output = getOutputFileContent(staxEventItemWriter.getEncoding(), false); + assertTrue(output.contains("standalone='no'")); + } + private String getOutputFileContent(String encoding) throws IOException { + return getOutputFileContent(encoding, true); + } + + /** + * @param encoding the encoding + * @param discardHeader the flag to strip XML header + * @return output file content as String + */ + private String getOutputFileContent(String encoding, boolean discardHeader) throws IOException { String value = FileUtils.readFileToString(resource.getFile(), encoding); - value = value.replace("", ""); + if (discardHeader) { + // standalone is omitted if not explicitly set, meaning it will be 'yes'/'no' or no standalone attribute + if (value.contains("standalone")) { + boolean standalone = value.contains("standalone='yes'"); + return value.replace("", ""); + } + return value.replace("", ""); + } return value; }