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 6748a03b4..62fd65f5e 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 @@ -469,7 +469,14 @@ InitializingBean { */ public void restoreFrom(ExecutionContext executionContext) { lastMarkedByteOffsetPosition = executionContext.getLong(getExecutionContextKey(RESTART_DATA_NAME)); - restarted = true; + linesWritten = executionContext.getLong(getExecutionContextKey(WRITTEN_STATISTICS_NAME)); + if (shouldDeleteIfEmpty && linesWritten == 0) { + // previous execution deleted the output file because no items were written + restarted = false; + lastMarkedByteOffsetPosition = 0; + } else { + restarted = true; + } } /** @@ -585,7 +592,6 @@ InitializingBean { } initialized = true; - linesWritten = 0; } public boolean isInitialized() { 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 4e8e351f3..c81c36250 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 @@ -142,6 +142,8 @@ ResourceAwareItemWriterItemStream, InitializingBean { private boolean forceSync; + private boolean shouldDeleteIfEmpty = false; + public StaxEventItemWriter() { setExecutionContextName(ClassUtils.getShortName(StaxEventItemWriter.class)); } @@ -203,6 +205,16 @@ ResourceAwareItemWriterItemStream, InitializingBean { this.forceSync = forceSync; } + /** + * Flag to indicate that the target file should be deleted if no items have + * been written (other than header and footer) on close. Defaults to false. + * + * @param shouldDeleteIfEmpty the flag value to set + */ + public void setShouldDeleteIfEmpty(boolean shouldDeleteIfEmpty) { + this.shouldDeleteIfEmpty = shouldDeleteIfEmpty; + } + /** * Get used encoding. * @@ -354,7 +366,15 @@ ResourceAwareItemWriterItemStream, InitializingBean { // otherwise start from beginning if (executionContext.containsKey(getExecutionContextKey(RESTART_DATA_NAME))) { startAtPosition = executionContext.getLong(getExecutionContextKey(RESTART_DATA_NAME)); + currentRecordCount = executionContext.getLong(getExecutionContextKey(WRITE_STATISTICS_NAME)); restarted = true; + if (shouldDeleteIfEmpty && currentRecordCount == 0) { + // previous execution deleted the output file because no items were written + restarted = false; + startAtPosition = 0; + } else { + restarted = true; + } } open(startAtPosition, restarted); @@ -656,6 +676,14 @@ ResourceAwareItemWriterItemStream, InitializingBean { } } } + if (currentRecordCount == 0 && shouldDeleteIfEmpty) { + try { + resource.getFile().delete(); + } + catch (IOException e) { + throw new ItemStreamException("Failed to delete empty file on close", e); + } + } } } 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 875587cba..22d125aba 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 @@ -323,8 +323,8 @@ public class FlatFileItemWriterTests { assertEquals("footer", readLine()); - // 3 lines were written to the file after restart - assertEquals(3, executionContext.getLong(ClassUtils.getShortName(FlatFileItemWriter.class) + ".written")); + // 8 lines were written to the file in total + assertEquals(8, executionContext.getLong(ClassUtils.getShortName(FlatFileItemWriter.class) + ".written")); } @@ -425,8 +425,8 @@ public class FlatFileItemWriterTests { assertEquals("footer", readLine()); - // 3 lines were written to the file after restart - assertEquals(3, executionContext.getLong(ClassUtils.getShortName(FlatFileItemWriter.class) + ".written")); + // 8 lines were written to the file in total + assertEquals(8, executionContext.getLong(ClassUtils.getShortName(FlatFileItemWriter.class) + ".written")); } @@ -595,6 +595,7 @@ public class FlatFileItemWriterTests { public void testDeleteOnExitReopen() throws Exception { writer.setShouldDeleteIfEmpty(true); writer.open(executionContext); + writer.update(executionContext); assertTrue(outputFile.exists()); writer.close(); assertFalse(outputFile.exists()); @@ -602,6 +603,43 @@ public class FlatFileItemWriterTests { writer.write(Collections.singletonList("test2")); assertEquals("test2", readLine()); } + + @Test + public void testWriteHeaderAndDeleteOnExitReopen() throws Exception { + writer.setHeaderCallback(new FlatFileHeaderCallback() { + + @Override + public void writeHeader(Writer writer) throws IOException { + writer.write("a\nb"); + } + + }); + writer.setShouldDeleteIfEmpty(true); + writer.open(executionContext); + writer.update(executionContext); + assertTrue(outputFile.exists()); + writer.close(); + assertFalse(outputFile.exists()); + + writer.open(executionContext); + writer.write(Collections.singletonList("test2")); + assertEquals("a", readLine()); + assertEquals("b", readLine()); + assertEquals("test2", readLine()); + } + + @Test + public void testDeleteOnExitNoRecordsWrittenAfterRestart() throws Exception { + writer.setShouldDeleteIfEmpty(true); + writer.open(executionContext); + writer.write(Collections.singletonList("test2")); + writer.update(executionContext); + writer.close(); + assertTrue(outputFile.exists()); + writer.open(executionContext); + writer.close(); + assertTrue(outputFile.exists()); + } @Test public void testWriteHeaderAfterRestartOnFirstChunk() throws Exception { 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 474935745..5b10370c1 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 @@ -353,6 +353,163 @@ public class StaxEventItemWriterTests { } } + /** + * 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. */