Skip to content

Commit 4671b62

Browse files
elimelecfmbenhassine
authored andcommitted
Use Files.delete() for better error reporting
Signed-off-by: Elimelec Burghelea <[email protected]>
1 parent 6961571 commit 4671b62

File tree

6 files changed

+158
-15
lines changed

6 files changed

+158
-15
lines changed

spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/AbstractFileItemWriter.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2006-2023 the original author or authors.
2+
* Copyright 2006-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
2525
import java.nio.channels.FileChannel;
2626
import java.nio.charset.StandardCharsets;
2727
import java.nio.charset.UnsupportedCharsetException;
28+
import java.nio.file.Files;
2829

2930
import org.apache.commons.logging.Log;
3031
import org.apache.commons.logging.LogFactory;
@@ -61,6 +62,7 @@
6162
* @author Mahmoud Ben Hassine
6263
* @author Glenn Renfro
6364
* @author Remi Kaeffer
65+
* @author Elimelec Burghelea
6466
* @since 4.1
6567
*/
6668
public abstract class AbstractFileItemWriter<T> extends AbstractItemStreamItemWriter<T>
@@ -268,11 +270,9 @@ public void close() {
268270
state.close();
269271
if (state.linesWritten == 0 && shouldDeleteIfEmpty) {
270272
try {
271-
if (!resource.getFile().delete()) {
272-
throw new ItemStreamException("Failed to delete empty file on close");
273-
}
273+
Files.delete(resource.getFile().toPath());
274274
}
275-
catch (IOException e) {
275+
catch (IOException | SecurityException e) {
276276
throw new ItemStreamException("Failed to delete empty file on close", e);
277277
}
278278
}

spring-batch-infrastructure/src/main/java/org/springframework/batch/item/util/FileUtils.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2006-2024 the original author or authors.
2+
* Copyright 2006-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
1818

1919
import java.io.File;
2020
import java.io.IOException;
21+
import java.nio.file.Files;
2122

2223
import org.springframework.batch.item.ItemStreamException;
2324
import org.springframework.util.Assert;
@@ -28,6 +29,7 @@
2829
* @author Peter Zozom
2930
* @author Mahmoud Ben Hassine
3031
* @author Taeik Lim
32+
* @author Elimelec Burghelea
3133
*/
3234
public abstract class FileUtils {
3335

@@ -57,8 +59,11 @@ public static void setUpOutputFile(File file, boolean restarted, boolean append,
5759
if (!overwriteOutputFile) {
5860
throw new ItemStreamException("File already exists: [" + file.getAbsolutePath() + "]");
5961
}
60-
if (!file.delete()) {
61-
throw new IOException("Could not delete file: " + file);
62+
try {
63+
Files.delete(file.toPath());
64+
}
65+
catch (IOException | SecurityException e) {
66+
throw new IOException("Could not delete file: " + file, e);
6267
}
6368
}
6469

spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemWriter.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2006-2023 the original author or authors.
2+
* Copyright 2006-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@
2424
import java.io.UnsupportedEncodingException;
2525
import java.io.Writer;
2626
import java.nio.channels.FileChannel;
27+
import java.nio.file.Files;
2728
import java.util.Collections;
2829
import java.util.List;
2930
import java.util.Map;
@@ -75,6 +76,7 @@
7576
* @author Michael Minella
7677
* @author Parikshit Dutta
7778
* @author Mahmoud Ben Hassine
79+
* @author Elimelec Burghelea
7880
*/
7981
public class StaxEventItemWriter<T> extends AbstractItemStreamItemWriter<T>
8082
implements ResourceAwareItemWriterItemStream<T>, InitializingBean {
@@ -726,11 +728,9 @@ public void close() {
726728
}
727729
if (currentRecordCount == 0 && shouldDeleteIfEmpty) {
728730
try {
729-
if (!resource.getFile().delete()) {
730-
throw new ItemStreamException("Failed to delete empty file on close");
731-
}
731+
Files.delete(resource.getFile().toPath());
732732
}
733-
catch (IOException e) {
733+
catch (IOException | SecurityException e) {
734734
throw new ItemStreamException("Failed to delete empty file on close", e);
735735
}
736736
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.batch.item.support;
18+
19+
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertNotNull;
21+
import static org.junit.jupiter.api.Assertions.assertThrows;
22+
import static org.mockito.Mockito.when;
23+
24+
import java.io.File;
25+
26+
import org.junit.jupiter.api.Test;
27+
import org.mockito.Mockito;
28+
import org.springframework.batch.item.Chunk;
29+
import org.springframework.batch.item.ExecutionContext;
30+
import org.springframework.batch.item.ItemStreamException;
31+
import org.springframework.core.io.FileSystemResource;
32+
33+
/**
34+
* Tests for common methods from {@link AbstractFileItemWriter}.
35+
*
36+
* @author Elimelec Burghelea
37+
*/
38+
class AbstractFileItemWriterTests {
39+
40+
@Test
41+
void testFailedFileDeletionThrowsException() {
42+
File outputFile = new File("target/data/output.tmp");
43+
File mocked = Mockito.spy(outputFile);
44+
45+
TestFileItemWriter writer = new TestFileItemWriter();
46+
47+
writer.setResource(new FileSystemResource(mocked));
48+
writer.setShouldDeleteIfEmpty(true);
49+
writer.setName(writer.getClass().getSimpleName());
50+
writer.open(new ExecutionContext());
51+
52+
when(mocked.delete()).thenReturn(false);
53+
54+
ItemStreamException exception = assertThrows(ItemStreamException.class, writer::close,
55+
"Expected exception when file deletion fails");
56+
57+
assertEquals("Failed to delete empty file on close", exception.getMessage(), "Wrong exception message");
58+
assertNotNull(exception.getCause(), "Exception should have a cause");
59+
}
60+
61+
private static class TestFileItemWriter extends AbstractFileItemWriter<String> {
62+
63+
@Override
64+
protected String doWrite(Chunk<? extends String> items) {
65+
return String.join("\n", items);
66+
}
67+
68+
@Override
69+
public void afterPropertiesSet() {
70+
71+
}
72+
73+
}
74+
75+
}

spring-batch-infrastructure/src/test/java/org/springframework/batch/item/util/FileUtilsTests.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2008-2022 the original author or authors.
2+
* Copyright 2008-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@
2828
import org.springframework.util.Assert;
2929

3030
import static org.junit.jupiter.api.Assertions.assertEquals;
31+
import static org.junit.jupiter.api.Assertions.assertNotNull;
3132
import static org.junit.jupiter.api.Assertions.assertThrows;
3233
import static org.junit.jupiter.api.Assertions.assertTrue;
3334
import static org.junit.jupiter.api.Assertions.fail;
@@ -36,6 +37,7 @@
3637
* Tests for {@link FileUtils}
3738
*
3839
* @author Robert Kasanicky
40+
* @author Elimelec Burghelea
3941
*/
4042
class FileUtilsTests {
4143

@@ -178,6 +180,43 @@ public boolean exists() {
178180
}
179181
}
180182

183+
@Test
184+
void testCannotDeleteFile() {
185+
186+
File file = new File("new file") {
187+
188+
@Override
189+
public boolean createNewFile() {
190+
return true;
191+
}
192+
193+
@Override
194+
public boolean exists() {
195+
return true;
196+
}
197+
198+
@Override
199+
public boolean delete() {
200+
return false;
201+
}
202+
203+
};
204+
try {
205+
FileUtils.setUpOutputFile(file, false, false, true);
206+
fail("Expected ItemStreamException because file cannot be deleted");
207+
}
208+
catch (ItemStreamException ex) {
209+
String message = ex.getMessage();
210+
assertTrue(message.startsWith("Unable to create file"), "Wrong message: " + message);
211+
assertTrue(ex.getCause() instanceof IOException);
212+
assertTrue(ex.getCause().getMessage().startsWith("Could not delete file"), "Wrong message: " + message);
213+
assertNotNull(ex.getCause().getCause(), "Exception should have a cause");
214+
}
215+
finally {
216+
file.delete();
217+
}
218+
}
219+
181220
@BeforeEach
182221
void setUp() {
183222
file.delete();

spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemWriterTests.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2008-2023 the original author or authors.
2+
* Copyright 2008-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@
3030

3131
import org.springframework.batch.item.Chunk;
3232
import org.springframework.batch.item.ExecutionContext;
33+
import org.springframework.batch.item.ItemStreamException;
3334
import org.springframework.batch.item.UnexpectedInputException;
3435
import org.springframework.batch.item.WriterNotOpenException;
3536
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
@@ -47,16 +48,19 @@
4748

4849
import static org.junit.jupiter.api.Assertions.assertEquals;
4950
import static org.junit.jupiter.api.Assertions.assertFalse;
51+
import static org.junit.jupiter.api.Assertions.assertNotNull;
5052
import static org.junit.jupiter.api.Assertions.assertThrows;
5153
import static org.junit.jupiter.api.Assertions.assertTrue;
5254
import static org.mockito.Mockito.mock;
55+
import static org.mockito.Mockito.spy;
5356
import static org.mockito.Mockito.when;
5457

5558
/**
5659
* Tests for {@link StaxEventItemWriter}.
5760
*
5861
* @author Parikshit Dutta
5962
* @author Mahmoud Ben Hassine
63+
* @author Elimelec Burghelea
6064
*/
6165
class StaxEventItemWriterTests {
6266

@@ -831,6 +835,26 @@ void testOpenAndCloseTagsInComplexCallbacksRestart() throws Exception {
831835
+ "</ns:testroot>", content, "Wrong content: " + content);
832836
}
833837

838+
/**
839+
* Tests that if file.delete() returns false, an appropriate exception is thrown to
840+
* indicate the deletion attempt failed.
841+
*/
842+
@Test
843+
void testFailedFileDeletionThrowsException() throws IOException {
844+
File mockedFile = spy(resource.getFile());
845+
writer.setResource(new FileSystemResource(mockedFile));
846+
writer.setShouldDeleteIfEmpty(true);
847+
writer.open(executionContext);
848+
849+
when(mockedFile.delete()).thenReturn(false);
850+
851+
ItemStreamException exception = assertThrows(ItemStreamException.class, () -> writer.close(),
852+
"Expected exception when file deletion fails");
853+
854+
assertEquals("Failed to delete empty file on close", exception.getMessage(), "Wrong exception message");
855+
assertNotNull(exception.getCause(), "Exception should have a cause");
856+
}
857+
834858
private void initWriterForSimpleCallbackTests() throws Exception {
835859
writer = createItemWriter();
836860
writer.setHeaderCallback(writer -> {

0 commit comments

Comments
 (0)