/* * Copyright 2008-2014 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.batch.item.xml; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.List; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.stream.XMLEventFactory; import javax.xml.stream.XMLEventWriter; import javax.xml.stream.XMLStreamException; import javax.xml.transform.Result; import org.apache.commons.io.FileUtils; import org.junit.Before; import org.junit.Test; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.UnexpectedInputException; import org.springframework.batch.item.WriterNotOpenException; import org.springframework.batch.support.transaction.ResourcelessTransactionManager; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.oxm.Marshaller; import org.springframework.oxm.XmlMappingException; import org.springframework.oxm.jaxb.Jaxb2Marshaller; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * Tests for {@link StaxEventItemWriter}. */ public class StaxEventItemWriterTests { // object under test private StaxEventItemWriter<Object> writer; // output file private Resource resource; private ExecutionContext executionContext; // test item for writing to output private Object item = new Object() { @Override public String toString() { return ClassUtils.getShortName(StaxEventItemWriter.class) + "-testString"; } }; private JAXBItem jaxbItem = new JAXBItem(); // test item for writing to output with multi byte character private Object itemMultiByte = new Object() { @Override public String toString() { return ClassUtils.getShortName(StaxEventItemWriter.class) + "-téstStrïng"; } }; private List<? extends Object> items = Collections.singletonList(item); private List<? extends Object> itemsMultiByte = Collections.singletonList(itemMultiByte); private List<? extends Object> jaxbItems = Collections.singletonList(jaxbItem); private static final String TEST_STRING = "<" + ClassUtils.getShortName(StaxEventItemWriter.class) + "-testString/>"; private static final String TEST_STRING_MULTI_BYTE = "<" + ClassUtils.getShortName(StaxEventItemWriter.class) + "-téstStrïng/>"; private static final String NS_TEST_STRING = "<ns:" + ClassUtils.getShortName(StaxEventItemWriter.class) + "-testString/>"; private static final String FOO_TEST_STRING = "<foo:" + ClassUtils.getShortName(StaxEventItemWriter.class) + "-testString/>"; private SimpleMarshaller marshaller; private Jaxb2Marshaller jaxbMarshaller; @Before public void setUp() throws Exception { File directory = new File("build/data"); directory.mkdirs(); resource = new FileSystemResource(File.createTempFile("StaxEventWriterOutputSourceTests", ".xml", directory)); writer = createItemWriter(); executionContext = new ExecutionContext(); jaxbMarshaller = new Jaxb2Marshaller(); jaxbMarshaller.setClassesToBeBound(JAXBItem.class); } /** * Test setting writer name. */ @Test public void testSetName() throws Exception { writer.setName("test"); writer.open(executionContext); writer.write(items); writer.update(executionContext); writer.close(); assertTrue("execution context keys should be prefixed with writer name", executionContext.containsKey("test.position")); } @Test(expected = WriterNotOpenException.class) public void testAssertWriterIsInitialized() throws Exception { StaxEventItemWriter<String> writer = new StaxEventItemWriter<>(); writer.write(Collections.singletonList("foo")); } /** * Item is written to the output file only after flush. */ @Test public void testWriteAndFlush() throws Exception { writer.open(executionContext); writer.write(items); writer.close(); String content = getOutputFileContent(); assertTrue("Wrong content: " + content, content.contains(TEST_STRING)); } @Test public void testWriteAndForceFlush() throws Exception { writer.setForceSync(true); writer.open(executionContext); writer.write(items); writer.close(); String content = getOutputFileContent(); assertTrue("Wrong content: " + content, content.contains(TEST_STRING)); } /** * Restart scenario - content is appended to the output file after restart. */ @Test public void testRestart() throws Exception { writer.open(executionContext); // write item writer.write(items); writer.update(executionContext); writer.close(); // create new writer from saved restart data and continue writing writer = createItemWriter(); writer.open(executionContext); writer.write(items); writer.write(items); writer.close(); // check the output is concatenation of 'before restart' and 'after // restart' writes. String outputFile = getOutputFileContent(); assertEquals(3, StringUtils.countOccurrencesOf(outputFile, TEST_STRING)); assertEquals("<root>" + TEST_STRING + TEST_STRING + TEST_STRING + "</root>", outputFile.replace(" ", "")); } @Test public void testTransactionalRestart() throws Exception { writer.open(executionContext); PlatformTransactionManager transactionManager = new ResourcelessTransactionManager(); new TransactionTemplate(transactionManager).execute(new TransactionCallback<Void>() { @Override public Void doInTransaction(TransactionStatus status) { try { // write item writer.write(items); } catch (Exception e) { throw new UnexpectedInputException("Could not write data", e); } // get restart data writer.update(executionContext); return null; } }); writer.close(); // create new writer from saved restart data and continue writing writer = createItemWriter(); writer.open(executionContext); new TransactionTemplate(transactionManager).execute(new TransactionCallback<Void>() { @Override public Void doInTransaction(TransactionStatus status) { try { writer.write(items); } catch (Exception e) { throw new UnexpectedInputException("Could not write data", e); } // get restart data writer.update(executionContext); return null; } }); writer.close(); // check the output is concatenation of 'before restart' and 'after // restart' writes. String outputFile = getOutputFileContent(); assertEquals(2, StringUtils.countOccurrencesOf(outputFile, TEST_STRING)); assertTrue(outputFile.contains("<root>" + TEST_STRING + TEST_STRING + "</root>")); } @Test // BATCH-1959 public void testTransactionalRestartWithMultiByteCharacterUTF8() throws Exception { testTransactionalRestartWithMultiByteCharacter("UTF-8"); } @Test // BATCH-1959 public void testTransactionalRestartWithMultiByteCharacterUTF16BE() throws Exception { testTransactionalRestartWithMultiByteCharacter("UTF-16BE"); } private void testTransactionalRestartWithMultiByteCharacter(String encoding) throws Exception { writer.setEncoding(encoding); writer.open(executionContext); PlatformTransactionManager transactionManager = new ResourcelessTransactionManager(); new TransactionTemplate(transactionManager).execute(new TransactionCallback<Void>() { @Override public Void doInTransaction(TransactionStatus status) { try { // write item writer.write(itemsMultiByte); } catch (Exception e) { throw new UnexpectedInputException("Could not write data", e); } // get restart data writer.update(executionContext); return null; } }); writer.close(); // create new writer from saved restart data and continue writing writer = createItemWriter(); writer.setEncoding(encoding); writer.open(executionContext); new TransactionTemplate(transactionManager).execute(new TransactionCallback<Void>() { @Override public Void doInTransaction(TransactionStatus status) { try { writer.write(itemsMultiByte); } catch (Exception e) { throw new UnexpectedInputException("Could not write data", e); } // get restart data writer.update(executionContext); return null; } }); writer.close(); // check the output is concatenation of 'before restart' and 'after // restart' writes. String outputFile = getOutputFileContent(encoding); assertEquals(2, StringUtils.countOccurrencesOf(outputFile, TEST_STRING_MULTI_BYTE)); assertTrue(outputFile.contains("<root>" + TEST_STRING_MULTI_BYTE + TEST_STRING_MULTI_BYTE + "</root>")); } @Test public void testTransactionalRestartFailOnFirstWrite() throws Exception { PlatformTransactionManager transactionManager = new ResourcelessTransactionManager(); writer.open(executionContext); try { new TransactionTemplate(transactionManager).execute(new TransactionCallback<Void>() { @Override public Void doInTransaction(TransactionStatus status) { try { writer.write(items); } catch (Exception e) { throw new IllegalStateException("Could not write data", e); } throw new UnexpectedInputException("Could not write data"); } }); } catch (UnexpectedInputException e) { // expected } writer.close(); String outputFile = getOutputFileContent(); assertEquals("<root></root>", outputFile); // create new writer from saved restart data and continue writing writer = createItemWriter(); new TransactionTemplate(transactionManager).execute(new TransactionCallback<Void>() { @Override public Void doInTransaction(TransactionStatus status) { writer.open(executionContext); try { writer.write(items); } catch (Exception e) { throw new UnexpectedInputException("Could not write data", e); } // get restart data writer.update(executionContext); return null; } }); writer.close(); // check the output is concatenation of 'before restart' and 'after // restart' writes. outputFile = getOutputFileContent(); assertEquals(1, StringUtils.countOccurrencesOf(outputFile, TEST_STRING)); assertTrue(outputFile.contains("<root>" + TEST_STRING + "</root>")); assertEquals("<root><StaxEventItemWriter-testString/></root>", outputFile); } /** * Item is written to the output file only after flush. */ @Test public void testWriteWithHeader() throws Exception { writer.setHeaderCallback(new StaxWriterCallback() { @Override public void write(XMLEventWriter writer) throws IOException { XMLEventFactory factory = XMLEventFactory.newInstance(); try { writer.add(factory.createStartElement("", "", "header")); writer.add(factory.createEndElement("", "", "header")); } catch (XMLStreamException e) { throw new RuntimeException(e); } } }); writer.open(executionContext); writer.write(items); String content = getOutputFileContent(); assertTrue("Wrong content: " + content, content.contains(("<header/>"))); assertTrue("Wrong content: " + content, content.contains(TEST_STRING)); } /** * Count of 'records written so far' is returned as statistics. */ @Test public void testStreamContext() throws Exception { writer.open(executionContext); final int NUMBER_OF_RECORDS = 10; assertFalse(executionContext.containsKey(ClassUtils.getShortName(StaxEventItemWriter.class) + ".record.count")); for (int i = 1; i <= NUMBER_OF_RECORDS; i++) { writer.write(items); writer.update(executionContext); long writeStatistics = executionContext.getLong(ClassUtils.getShortName(StaxEventItemWriter.class) + ".record.count"); assertEquals(i, writeStatistics); } } /** * Open method writes the root tag, close method adds corresponding end tag. */ @Test public void testOpenAndClose() throws Exception { writer.setHeaderCallback(new StaxWriterCallback() { @Override public void write(XMLEventWriter writer) throws IOException { XMLEventFactory factory = XMLEventFactory.newInstance(); try { writer.add(factory.createStartElement("", "", "header")); writer.add(factory.createEndElement("", "", "header")); } catch (XMLStreamException e) { throw new RuntimeException(e); } } }); writer.setFooterCallback(new StaxWriterCallback() { @Override public void write(XMLEventWriter writer) throws IOException { XMLEventFactory factory = XMLEventFactory.newInstance(); try { writer.add(factory.createStartElement("", "", "footer")); writer.add(factory.createEndElement("", "", "footer")); } catch (XMLStreamException e) { throw new RuntimeException(e); } } }); writer.setRootTagName("testroot"); writer.setRootElementAttributes(Collections.<String, String> singletonMap("attribute", "value")); writer.open(executionContext); writer.close(); String content = getOutputFileContent(); assertTrue(content.contains("<testroot attribute=\"value\">")); assertTrue(content.contains("<header/>")); assertTrue(content.contains("<footer/>")); assertTrue(content.endsWith("</testroot>")); } @Test public void testNonExistantResource() throws Exception { Resource doesntExist = mock(Resource.class); when(doesntExist.getFile()).thenReturn(File.createTempFile("arbitrary", null)); when(doesntExist.exists()).thenReturn(false); writer.setResource(doesntExist); try { writer.open(executionContext); fail(); } catch (IllegalStateException e) { assertEquals("Output resource must exist", e.getMessage()); } } /** * Resource is not deleted when items have been written and shouldDeleteIfEmpty flag is set. */ @Test public void testDeleteIfEmptyRecordsWritten() throws Exception { writer.setShouldDeleteIfEmpty(true); writer.open(executionContext); writer.write(items); writer.close(); String content = getOutputFileContent(); assertTrue("Wrong content: " + content, content.contains(TEST_STRING)); } /** * Resource is deleted when no items have been written and shouldDeleteIfEmpty flag is set. */ @Test public void testDeleteIfEmptyNoRecordsWritten() throws Exception { writer.setShouldDeleteIfEmpty(true); writer.open(executionContext); writer.close(); assertFalse("file should be deleted" + resource, resource.getFile().exists()); } /** * Resource is deleted when items have not been written and shouldDeleteIfEmpty flag is set. */ @Test public void testDeleteIfEmptyNoRecordsWrittenHeaderAndFooter() throws Exception { writer.setShouldDeleteIfEmpty(true); writer.setHeaderCallback(new StaxWriterCallback() { @Override public void write(XMLEventWriter writer) throws IOException { XMLEventFactory factory = XMLEventFactory.newInstance(); try { writer.add(factory.createStartElement("", "", "header")); writer.add(factory.createEndElement("", "", "header")); } catch (XMLStreamException e) { throw new RuntimeException(e); } } }); writer.setFooterCallback(new StaxWriterCallback() { @Override public void write(XMLEventWriter writer) throws IOException { XMLEventFactory factory = XMLEventFactory.newInstance(); try { writer.add(factory.createStartElement("", "", "footer")); writer.add(factory.createEndElement("", "", "footer")); } catch (XMLStreamException e) { throw new RuntimeException(e); } } }); writer.open(executionContext); writer.close(); assertFalse("file should be deleted" + resource, resource.getFile().exists()); } /** * Resource is not deleted when items have been written and shouldDeleteIfEmpty flag is set. */ @Test public void testDeleteIfEmptyRecordsWrittenRestart() throws Exception { writer.setShouldDeleteIfEmpty(true); writer.open(executionContext); writer.write(items); writer.update(executionContext); writer.close(); writer = createItemWriter(); writer.setShouldDeleteIfEmpty(true); writer.open(executionContext); writer.close(); String content = getOutputFileContent(); assertTrue("Wrong content: " + content, content.contains(TEST_STRING)); } /** * Test that the writer can restart if the previous execution deleted empty file. */ @Test public void testDeleteIfEmptyRestartAfterDelete() throws Exception { writer.setShouldDeleteIfEmpty(true); writer.open(executionContext); writer.update(executionContext); writer.close(); assertFalse(resource.getFile().exists()); writer = createItemWriter(); writer.setShouldDeleteIfEmpty(true); writer.open(executionContext); writer.write(items); writer.update(executionContext); writer.close(); String content = getOutputFileContent(); assertTrue("Wrong content: " + content, content.contains(TEST_STRING)); } /** * Resource is not deleted when items have been written and shouldDeleteIfEmpty flag is set (restart after delete). */ @Test public void testDeleteIfEmptyNoRecordsWrittenHeaderAndFooterRestartAfterDelete() throws Exception { writer.setShouldDeleteIfEmpty(true); writer.setHeaderCallback(new StaxWriterCallback() { @Override public void write(XMLEventWriter writer) throws IOException { XMLEventFactory factory = XMLEventFactory.newInstance(); try { writer.add(factory.createStartElement("", "", "header")); writer.add(factory.createEndElement("", "", "header")); } catch (XMLStreamException e) { throw new RuntimeException(e); } } }); writer.setFooterCallback(new StaxWriterCallback() { @Override public void write(XMLEventWriter writer) throws IOException { XMLEventFactory factory = XMLEventFactory.newInstance(); try { writer.add(factory.createStartElement("", "", "footer")); writer.add(factory.createEndElement("", "", "footer")); } catch (XMLStreamException e) { throw new RuntimeException(e); } } }); writer.open(executionContext); writer.update(executionContext); writer.close(); assertFalse("file should be deleted" + resource, resource.getFile().exists()); writer.open(executionContext); writer.write(items); writer.update(executionContext); writer.close(); String content = getOutputFileContent(); assertTrue("Wrong content: " + content, content.contains(TEST_STRING)); } /** * Item is written to the output file with namespace. */ @Test public void testWriteRootTagWithNamespace() throws Exception { writer.setRootTagName("{http://www.springframework.org/test}root"); writer.afterPropertiesSet(); writer.open(executionContext); writer.write(items); writer.close(); String content = getOutputFileContent(); assertTrue("Wrong content: " + content, content .contains(("<root xmlns=\"http://www.springframework.org/test\">"))); assertTrue("Wrong content: " + content, content.contains(TEST_STRING)); assertTrue("Wrong content: " + content, content.contains(("</root>"))); } /** * Item is written to the output file with namespace and prefix. */ @Test public void testWriteRootTagWithNamespaceAndPrefix() throws Exception { writer.setRootTagName("{http://www.springframework.org/test}ns:root"); writer.afterPropertiesSet(); marshaller.setNamespace(writer.getRootTagNamespace()); marshaller.setNamespacePrefix(writer.getRootTagNamespacePrefix()); writer.open(executionContext); writer.write(items); writer.close(); String content = getOutputFileContent(); assertTrue("Wrong content: " + content, content .contains(("<ns:root xmlns:ns=\"http://www.springframework.org/test\">"))); assertTrue("Wrong content: " + content, content.contains(NS_TEST_STRING)); assertTrue("Wrong content: " + content, content.contains(("</ns:root>"))); assertTrue("Wrong content: " + content, content.contains(("<ns:root"))); } /** * Item is written to the output file with additional namespaces and prefix. */ @Test public void testWriteRootTagWithAdditionalNamespace() throws Exception { writer.setRootTagName("{http://www.springframework.org/test}ns:root"); marshaller.setNamespace("urn:org.test.foo"); marshaller.setNamespacePrefix("foo"); writer.setRootElementAttributes(Collections.singletonMap("xmlns:foo", "urn:org.test.foo")); writer.afterPropertiesSet(); writer.open(executionContext); writer.write(items); writer.close(); String content = getOutputFileContent(); assertTrue("Wrong content: " + content, content .contains(("<ns:root xmlns:ns=\"http://www.springframework.org/test\" " + "xmlns:foo=\"urn:org.test.foo\">"))); assertTrue("Wrong content: " + content, content.contains(FOO_TEST_STRING)); assertTrue("Wrong content: " + content, content.contains(("</ns:root>"))); assertTrue("Wrong content: " + content, content.contains(("<ns:root"))); } /** * Namespace prefixes are properly initialized on restart. */ @Test public void testRootTagWithNamespaceRestart() throws Exception { writer.setMarshaller(jaxbMarshaller); writer.setRootTagName("{http://www.springframework.org/test}root"); writer.afterPropertiesSet(); writer.open(executionContext); writer.write(jaxbItems); writer.update(executionContext); writer.close(); writer = createItemWriter(); writer.setMarshaller(jaxbMarshaller); writer.setRootTagName("{http://www.springframework.org/test}root"); writer.afterPropertiesSet(); writer.open(executionContext); writer.write(jaxbItems); writer.update(executionContext); writer.close(); String content = getOutputFileContent(); assertEquals("Wrong content: " + content, "<root xmlns=\"http://www.springframework.org/test\"><item/><item/></root>", content); } /** * Namespace prefixes are properly initialized on restart. */ @Test public void testRootTagWithNamespaceAndPrefixRestart() throws Exception { writer.setMarshaller(jaxbMarshaller); writer.setRootTagName("{http://www.springframework.org/test}ns:root"); writer.afterPropertiesSet(); writer.open(executionContext); writer.write(jaxbItems); writer.update(executionContext); writer.close(); writer = createItemWriter(); writer.setMarshaller(jaxbMarshaller); writer.setRootTagName("{http://www.springframework.org/test}ns:root"); writer.afterPropertiesSet(); writer.open(executionContext); writer.write(jaxbItems); writer.update(executionContext); writer.close(); String content = getOutputFileContent(); assertEquals("Wrong content: " + content, "<ns:root xmlns:ns=\"http://www.springframework.org/test\"><ns:item/><ns:item/></ns:root>", content); } /** * Namespace prefixes are properly initialized on restart. */ @Test public void testRootTagWithAdditionalNamespaceRestart() throws Exception { writer.setMarshaller(jaxbMarshaller); writer.setRootTagName("{urn:org.test.foo}foo:root"); writer.setRootElementAttributes(Collections.singletonMap("xmlns:ns", "http://www.springframework.org/test")); writer.afterPropertiesSet(); writer.open(executionContext); writer.write(jaxbItems); writer.update(executionContext); writer.close(); writer = createItemWriter(); writer.setMarshaller(jaxbMarshaller); writer.setRootTagName("{urn:org.test.foo}foo:root"); writer.setRootElementAttributes(Collections.singletonMap("xmlns:ns", "http://www.springframework.org/test")); writer.afterPropertiesSet(); writer.open(executionContext); writer.write(jaxbItems); writer.update(executionContext); writer.close(); String content = getOutputFileContent(); assertEquals("Wrong content: " + content, "<foo:root xmlns:foo=\"urn:org.test.foo\" xmlns:ns=\"http://www.springframework.org/test\"><ns:item/><ns:item/></foo:root>", content); } /** * Test with OXM Marshaller that closes the XMLEventWriter. */ // BATCH-2054 @Test public void testMarshallingClosingEventWriter() throws Exception { writer.setMarshaller(new SimpleMarshaller() { @Override public void marshal(Object graph, Result result) throws XmlMappingException, IOException { super.marshal(graph, result); try { StaxUtils.getXmlEventWriter(result).close(); } catch (Exception e) { throw new RuntimeException("Exception while writing to output file", e); } } }); writer.afterPropertiesSet(); writer.open(executionContext); writer.write(items); writer.write(items); } /** * Test opening and closing corresponding tags in header- and footer callback. */ @Test public void testOpenAndCloseTagsInCallbacks() throws Exception { initWriterForSimpleCallbackTests(); writer.open(executionContext); writer.write(items); writer.close(); String content = getOutputFileContent(); assertEquals("Wrong content: " + content, "<ns:testroot xmlns:ns=\"http://www.springframework.org/test\"><ns:group><StaxEventItemWriter-testString/></ns:group></ns:testroot>", content); } /** * Test opening and closing corresponding tags in header- and footer callback (restart). */ @Test public void testOpenAndCloseTagsInCallbacksRestart() throws Exception { initWriterForSimpleCallbackTests(); writer.open(executionContext); writer.write(items); writer.update(executionContext); initWriterForSimpleCallbackTests(); writer.open(executionContext); writer.write(items); writer.close(); String content = getOutputFileContent(); assertEquals("Wrong content: " + content, "<ns:testroot xmlns:ns=\"http://www.springframework.org/test\">" + "<ns:group><StaxEventItemWriter-testString/><StaxEventItemWriter-testString/></ns:group></ns:testroot>", content); } /** * Test opening and closing corresponding tags in complex header- and footer callback (restart). */ @Test public void testOpenAndCloseTagsInComplexCallbacksRestart() throws Exception { initWriterForComplexCallbackTests(); writer.open(executionContext); writer.write(items); writer.update(executionContext); initWriterForComplexCallbackTests(); writer.open(executionContext); writer.write(items); writer.close(); String content = getOutputFileContent(); assertEquals("Wrong content: " + content, "<ns:testroot xmlns:ns=\"http://www.springframework.org/test\">" + "<preHeader>PRE-HEADER</preHeader><ns:group><subGroup><postHeader>POST-HEADER</postHeader>" + "<StaxEventItemWriter-testString/><StaxEventItemWriter-testString/>" + "<preFooter>PRE-FOOTER</preFooter></subGroup></ns:group><postFooter>POST-FOOTER</postFooter>" + "</ns:testroot>", content); } private void initWriterForSimpleCallbackTests() throws Exception { writer = createItemWriter(); writer.setHeaderCallback(new StaxWriterCallback() { @Override public void write(XMLEventWriter writer) throws IOException { XMLEventFactory factory = XMLEventFactory.newInstance(); try { writer.add(factory.createStartElement("ns", "http://www.springframework.org/test", "group")); } catch (XMLStreamException e) { throw new RuntimeException(e); } } }); writer.setFooterCallback(new StaxWriterCallback() { @Override public void write(XMLEventWriter writer) throws IOException { XMLEventFactory factory = XMLEventFactory.newInstance(); try { writer.add(factory.createEndElement("ns", "http://www.springframework.org/test", "group")); } catch (XMLStreamException e) { throw new RuntimeException(e); } } }); writer.setRootTagName("{http://www.springframework.org/test}ns:testroot"); writer.afterPropertiesSet(); } // more complex callbacks, writing element before and after the multiple corresponding header- and footer elements private void initWriterForComplexCallbackTests() throws Exception { writer = createItemWriter(); writer.setHeaderCallback(new StaxWriterCallback() { @Override public void write(XMLEventWriter writer) throws IOException { XMLEventFactory factory = XMLEventFactory.newInstance(); try { writer.add(factory.createStartElement("", "", "preHeader")); writer.add(factory.createCharacters("PRE-HEADER")); writer.add(factory.createEndElement("", "", "preHeader")); writer.add(factory.createStartElement("ns", "http://www.springframework.org/test", "group")); writer.add(factory.createStartElement("", "", "subGroup")); writer.add(factory.createStartElement("", "", "postHeader")); writer.add(factory.createCharacters("POST-HEADER")); writer.add(factory.createEndElement("", "", "postHeader")); } catch (XMLStreamException e) { throw new RuntimeException(e); } } }); writer.setFooterCallback(new StaxWriterCallback() { @Override public void write(XMLEventWriter writer) throws IOException { XMLEventFactory factory = XMLEventFactory.newInstance(); try { writer.add(factory.createStartElement("", "", "preFooter")); writer.add(factory.createCharacters("PRE-FOOTER")); writer.add(factory.createEndElement("", "", "preFooter")); writer.add(factory.createEndElement("", "", "subGroup")); writer.add(factory.createEndElement("ns", "http://www.springframework.org/test", "group")); writer.add(factory.createStartElement("", "", "postFooter")); writer.add(factory.createCharacters("POST-FOOTER")); writer.add(factory.createEndElement("", "", "postFooter")); } catch (XMLStreamException e) { throw new RuntimeException(e); } } }); writer.setRootTagName("{http://www.springframework.org/test}ns:testroot"); writer.afterPropertiesSet(); } /** * Writes object's toString representation as XML comment. */ private static class SimpleMarshaller implements Marshaller { private String namespacePrefix = ""; private String namespace = ""; public void setNamespace(String namespace) { this.namespace = namespace; } public void setNamespacePrefix(String namespacePrefix) { this.namespacePrefix = namespacePrefix; } @Override public void marshal(Object graph, Result result) throws XmlMappingException, IOException { Assert.isInstanceOf( Result.class, result); try { StaxUtils.getXmlEventWriter( result ).add( XMLEventFactory.newInstance().createStartElement(namespacePrefix, namespace, graph.toString())); StaxUtils.getXmlEventWriter( result ).add( XMLEventFactory.newInstance().createEndElement(namespacePrefix, namespace, graph.toString())); } catch ( Exception e) { throw new RuntimeException("Exception while writing to output file", e); } } @Override public boolean supports(Class<?> clazz) { return true; } } /** * @return output file content as String */ private String getOutputFileContent() throws IOException { return getOutputFileContent("UTF-8"); } /** * @param encoding the encoding * @return output file content as String */ private String getOutputFileContent(String encoding) throws IOException { String value = FileUtils.readFileToString(resource.getFile(), encoding); value = value.replace("<?xml version='1.0' encoding='" + encoding + "'?>", ""); return value; } /** * @return new instance of fully configured writer */ private StaxEventItemWriter<Object> createItemWriter() throws Exception { StaxEventItemWriter<Object> source = new StaxEventItemWriter<Object>(); source.setResource(resource); marshaller = new SimpleMarshaller(); source.setMarshaller(marshaller); source.setEncoding("UTF-8"); source.setRootTagName("root"); source.setVersion("1.0"); source.setOverwriteOutput(true); source.setSaveState(true); source.afterPropertiesSet(); return source; } @XmlRootElement(name="item", namespace="http://www.springframework.org/test") private static class JAXBItem { } }