diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/FlatFileItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/FlatFileItemWriter.java index a7835d8e3..ce8fbda86 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/FlatFileItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/FlatFileItemWriter.java @@ -80,6 +80,8 @@ public class FlatFileItemWriter extends ExecutionContextUserSupport implement private List headerLines = new ArrayList(); + private List footerLines = new ArrayList(); + private String lineSeparator = DEFAULT_LINE_SEPARATOR; public FlatFileItemWriter() { @@ -160,6 +162,16 @@ public class FlatFileItemWriter extends ExecutionContextUserSupport implement this.headerLines = Arrays.asList(headerLines); } + /** + * Public setter for footer lines. These will be output at the end of the + * file when the writer is closed. + * + * @param footerLines the footer lines to set + */ + public void setFooterLines(String[] footerLines) { + this.footerLines = Arrays.asList(footerLines); + } + /** * Writes out a string followed by a "new line", where the format of the new * line separator is determined by the underlying operating system. If the @@ -190,7 +202,6 @@ public class FlatFileItemWriter extends ExecutionContextUserSupport implement throw new FlushFailedException("Could not write data. The file may be corrupt.", e); } } - } /** @@ -198,8 +209,18 @@ public class FlatFileItemWriter extends ExecutionContextUserSupport implement */ public void close(ExecutionContext executionContext) { if (state != null) { - getOutputState().close(); - state = null; + try { + for (String line : footerLines) { + state.write(line + lineSeparator); + } + } + catch (IOException e) { + throw new ItemStreamException("Failed to writer footer before closing", e); + } + finally { + getOutputState().close(); + state = null; + } } } @@ -352,6 +373,7 @@ public class FlatFileItemWriter extends ExecutionContextUserSupport implement * Close the open resource and reset counters. */ public void close() { + initialized = false; restarted = false; try { 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 2efbd6650..00b6e2bd7 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 @@ -110,7 +110,9 @@ public class StaxEventItemWriter extends ExecutionContextUserSupport implemen private boolean saveState = true; - private List headers = new ArrayList(); + private List headerItems = new ArrayList(); + + private List footerItems = new ArrayList(); public StaxEventItemWriter() { setName(ClassUtils.getShortName(StaxEventItemWriter.class)); @@ -218,14 +220,25 @@ public class StaxEventItemWriter extends ExecutionContextUserSupport implemen } /** - * Setter for the headers. This list will be marshalled and output before - * any calls to {@link #write(List)}. Header item type is not restricted, - * but note the {@link #setMarshaller(Marshaller)} needs to support - * the type. - * @param headers + * Setter for the headerItems. This list will be marshalled and output + * before any calls to {@link #write(List)}. Header item type is not + * restricted, but note the {@link #setMarshaller(Marshaller)} needs to + * support the type. + * @param headerItems */ - public void setHeaderItems(List headers) { - this.headers = headers; + public void setHeaderItems(List headerItems) { + this.headerItems = headerItems; + } + + /** + * Setter for the footerItems. This list will be marshalled and output + * immediately before the writer is closed. Footer item type is not + * restricted, but note the {@link #setMarshaller(Marshaller)} needs to + * support the type. + * @param footerItems + */ + public void setFooterItems(List footerItems) { + this.footerItems = footerItems; } public void setSaveState(boolean saveState) { @@ -262,10 +275,10 @@ public class StaxEventItemWriter extends ExecutionContextUserSupport implemen if (startAtPosition == 0) { try { - doWrite(headers); + doWrite(headerItems); } catch (IOException e) { - throw new ItemStreamException("Failed to write headers", e); + throw new ItemStreamException("Failed to write headerItems", e); } } @@ -383,16 +396,33 @@ public class StaxEventItemWriter extends ExecutionContextUserSupport implemen } try { + doWrite(footerItems); delegateEventWriter.flush(); endDocument(delegateEventWriter); - eventWriter.close(); - channel.close(); } - catch (XMLStreamException xse) { - throw new DataAccessResourceFailureException("Unable to close file resource: [" + resource + "]", xse); + catch (IOException e) { + throw new ItemStreamException("Failed to write footer items", e); } - catch (IOException ioe) { - throw new DataAccessResourceFailureException("Unable to close file resource: [" + resource + "]", ioe); + catch (XMLStreamException e) { + throw new ItemStreamException("Failed to write end document tag", e); + } + finally { + + try { + eventWriter.close(); + } + catch (XMLStreamException xse) { + log.error("Unable to close file resource: [" + resource + "] " + xse); + } + finally { + try { + channel.close(); + } + catch (IOException ioe) { + log.error("Unable to close file resource: [" + resource + "] " + ioe); + } + + } } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/FlatFileItemWriterTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/FlatFileItemWriterTests.java index adcb811e2..918c17885 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/FlatFileItemWriterTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/FlatFileItemWriterTests.java @@ -203,11 +203,13 @@ public class FlatFileItemWriterTests { @Test public void testRestart() throws Exception { + writer.setFooterLines(new String[] { "footer" }); + writer.open(executionContext); // write some lines writer.write(Arrays.asList(new String[] { "testLine1", "testLine2", "testLine3" })); // write more lines - writer.write(Arrays.asList(new String[] {"testLine4", "testLine5"})); + writer.write(Arrays.asList(new String[] { "testLine4", "testLine5" })); // get restart data writer.update(executionContext); // close template @@ -216,7 +218,7 @@ public class FlatFileItemWriterTests { // init with correct data writer.open(executionContext); // write more lines - writer.write(Arrays.asList(new String[] {"testLine6","testLine7","testLine8"})); + writer.write(Arrays.asList(new String[] { "testLine6", "testLine7", "testLine8" })); // get statistics writer.update(executionContext); // close template @@ -226,6 +228,8 @@ public class FlatFileItemWriterTests { for (int i = 1; i <= 8; i++) { assertEquals("testLine" + i, readLine()); } + + assertEquals("footer", readLine()); // 3 lines were written to the file after restart assertEquals(3, executionContext.getLong(ClassUtils.getShortName(FlatFileItemWriter.class) + ".written")); @@ -303,6 +307,17 @@ public class FlatFileItemWriterTests { assertEquals(TEST_STRING, lineFromFile); } + @Test + public void testWriteFooter() throws Exception { + writer.setFooterLines(new String[] { "a", "b" }); + writer.open(executionContext); + writer.write(Collections.singletonList(TEST_STRING)); + writer.close(executionContext); + assertEquals(TEST_STRING, readLine()); + assertEquals("a", readLine()); + assertEquals("b", readLine()); + } + @Test public void testWriteHeader() throws Exception { writer.setHeaderLines(new String[] { "a", "b" }); 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 b7b6832be..5f1f495c4 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 @@ -95,8 +95,9 @@ public class StaxEventItemWriterTests { // check the output is concatenation of 'before restart' and 'after // restart' writes. String outputFile = outputFileContent(); - + System.out.println(outputFile); assertEquals(2, StringUtils.countOccurrencesOf(outputFile, TEST_STRING)); + assertTrue(outputFile.contains("" + TEST_STRING + TEST_STRING + "")); } /** @@ -106,7 +107,12 @@ public class StaxEventItemWriterTests { public void testWriteWithHeader() throws Exception { final Object header1 = new Object(); final Object header2 = new Object(); - writer.setHeaderItems(new ArrayList() {{add(header1); add(header2); }}); + writer.setHeaderItems(new ArrayList() { + { + add(header1); + add(header2); + } + }); writer.open(executionContext); writer.write(items); String content = outputFileContent();