diff --git a/pom.xml b/pom.xml
index 163f736dd..df781bafb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -105,6 +105,7 @@
1.0
1
1.2
+ 1.5.0
5.4.24.Final
diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml
index f95988aea..d5dda0941 100644
--- a/spring-batch-infrastructure/pom.xml
+++ b/spring-batch-infrastructure/pom.xml
@@ -211,6 +211,12 @@
${xmlunit.version}
test
+
+ org.skyscreamer
+ jsonassert
+ ${jsonassert.version}
+ test
+
commons-io
commons-io
diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/json/GsonJsonFileItemWriterFunctionalTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/json/GsonJsonFileItemWriterFunctionalTests.java
index bcd6b4e7a..da4ed68d4 100644
--- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/json/GsonJsonFileItemWriterFunctionalTests.java
+++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/json/GsonJsonFileItemWriterFunctionalTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 the original author or authors.
+ * Copyright 2018-2021 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.
@@ -44,4 +44,9 @@ public class GsonJsonFileItemWriterFunctionalTests extends JsonFileItemWriterFun
return "expected-trades-gson-pretty-print.json";
}
+ @Override
+ protected String getMarshallerName() {
+ return "gson";
+ }
+
}
diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/json/JacksonJsonFileItemWriterFunctionalTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/json/JacksonJsonFileItemWriterFunctionalTests.java
index d800bc04a..a4a16652a 100644
--- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/json/JacksonJsonFileItemWriterFunctionalTests.java
+++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/json/JacksonJsonFileItemWriterFunctionalTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 the original author or authors.
+ * Copyright 2018-2021 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.
@@ -45,4 +45,9 @@ public class JacksonJsonFileItemWriterFunctionalTests extends JsonFileItemWriter
return "expected-trades-jackson-pretty-print.json";
}
+ @Override
+ protected String getMarshallerName() {
+ return "jackson";
+ }
+
}
diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/json/JsonFileItemWriterFunctionalTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/json/JsonFileItemWriterFunctionalTests.java
index fb3730479..c6a915585 100644
--- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/json/JsonFileItemWriterFunctionalTests.java
+++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/json/JsonFileItemWriterFunctionalTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 the original author or authors.
+ * Copyright 2018-2021 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.
@@ -18,7 +18,9 @@ package org.springframework.batch.item.json;
import java.io.File;
import java.io.FileInputStream;
+import java.io.IOException;
import java.math.BigDecimal;
+import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -29,6 +31,7 @@ import java.util.List;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.UnexpectedInputException;
@@ -54,9 +57,6 @@ public abstract class JsonFileItemWriterFunctionalTests {
private static final String EXPECTED_FILE_DIRECTORY = "src/test/resources/org/springframework/batch/item/json/";
- private Resource resource;
- private List items;
- private ExecutionContext executionContext;
private Trade trade1 = new Trade("123", 5, new BigDecimal("10.5"), "foo");
private Trade trade2 = new Trade("456", 10, new BigDecimal("20.5"), "bar");
private Trade trade3 = new Trade("789", 15, new BigDecimal("30.5"), "foobar");
@@ -65,217 +65,274 @@ public abstract class JsonFileItemWriterFunctionalTests {
protected abstract JsonObjectMarshaller getJsonObjectMarshaller();
protected abstract JsonObjectMarshaller getJsonObjectMarshallerWithPrettyPrint();
protected abstract String getExpectedPrettyPrintedFile();
-
- private JsonFileItemWriter writer;
-
- @Before
- public void setUp() throws Exception {
- Path outputFilePath = Paths.get("target", "trades.json");
- Files.deleteIfExists(outputFilePath);
- this.resource = new FileSystemResource(outputFilePath.toFile());
- this.executionContext = new ExecutionContext();
- this.items = Arrays.asList(this.trade1, this.trade2);
- this.writer = new JsonFileItemWriterBuilder()
- .name("tradesItemWriter")
- .resource(this.resource)
- .jsonObjectMarshaller(getJsonObjectMarshaller())
- .build();
- }
+ protected abstract String getMarshallerName();
@Test
public void testJsonWriting() throws Exception {
+ //given
+ Path outputFilePath = Paths.get("target", "trades-" + getMarshallerName() + ".json");
+ FileSystemResource resource = new FileSystemResource(outputFilePath);
+ JsonFileItemWriter writer = new JsonFileItemWriterBuilder()
+ .name("tradesItemWriter")
+ .resource(resource)
+ .jsonObjectMarshaller(getJsonObjectMarshaller())
+ .build();
+
// when
- this.writer.open(this.executionContext);
- this.writer.write(this.items);
- this.writer.close();
+ writer.open(new ExecutionContext());
+ writer.write(Arrays.asList(this.trade1, this.trade2));
+ writer.close();
// then
assertFileEquals(
new File(EXPECTED_FILE_DIRECTORY + "expected-trades.json"),
- this.resource.getFile());
+ resource.getFile());
}
@Test
public void testJsonWritingWithMultipleWrite() throws Exception {
+ //given
+ Path outputFilePath = Paths.get("target", "testJsonWritingWithMultipleWrite-" + getMarshallerName() + ".json");
+ FileSystemResource resource = new FileSystemResource(outputFilePath);
+ JsonFileItemWriter writer = new JsonFileItemWriterBuilder()
+ .name("tradesItemWriter")
+ .resource(resource)
+ .jsonObjectMarshaller(getJsonObjectMarshaller())
+ .build();
+
// when
- this.writer.open(this.executionContext);
- this.writer.write(this.items);
- this.writer.write(Arrays.asList(trade3, trade4));
- this.writer.close();
+ writer.open(new ExecutionContext());
+ writer.write(Arrays.asList(this.trade1, this.trade2));
+ writer.write(Arrays.asList(this.trade3, this.trade4));
+ writer.close();
// then
assertFileEquals(
new File(EXPECTED_FILE_DIRECTORY + "expected-trades-with-multiple-writes.json"),
- this.resource.getFile());
+ resource.getFile());
}
@Test
public void testJsonWritingWithPrettyPrinting() throws Exception {
// given
- this.writer = new JsonFileItemWriterBuilder()
+ Path outputFilePath = Paths.get("target", "testJsonWritingWithPrettyPrinting-" + getMarshallerName() + ".json");
+ FileSystemResource resource = new FileSystemResource(outputFilePath);
+ JsonFileItemWriter writer = new JsonFileItemWriterBuilder()
.name("tradesItemWriter")
- .resource(this.resource)
+ .resource(resource)
.jsonObjectMarshaller(getJsonObjectMarshallerWithPrettyPrint())
.build();
// when
- this.writer.open(this.executionContext);
- this.writer.write(this.items);
- this.writer.close();
+ writer.open(new ExecutionContext());
+ writer.write(Arrays.asList(this.trade1, this.trade2));
+ writer.close();
// when
assertFileEquals(
new File(EXPECTED_FILE_DIRECTORY + getExpectedPrettyPrintedFile()),
- this.resource.getFile());
+ resource.getFile());
}
@Test
public void testJsonWritingWithEnclosingObject() throws Exception {
// given
- this.writer.setHeaderCallback(writer -> writer.write("{\"trades\":["));
- this.writer.setFooterCallback(writer -> writer.write(JsonFileItemWriter.DEFAULT_LINE_SEPARATOR + "]}"));
+ Path outputFilePath = Paths.get("target", "testJsonWritingWithEnclosingObject-" + getMarshallerName() + ".json");
+ FileSystemResource resource = new FileSystemResource(outputFilePath);
+ JsonFileItemWriter writer = new JsonFileItemWriterBuilder()
+ .name("tradesItemWriter")
+ .resource(resource)
+ .jsonObjectMarshaller(getJsonObjectMarshaller())
+ .headerCallback(headerWriter -> headerWriter.write("{\"trades\":["))
+ .footerCallback(footerWriter -> footerWriter.write(JsonFileItemWriter.DEFAULT_LINE_SEPARATOR + "]}"))
+ .build();
// when
- this.writer.open(this.executionContext);
- this.writer.write(this.items);
- this.writer.close();
+ writer.open(new ExecutionContext());
+ writer.write(Arrays.asList(this.trade1, this.trade2));
+ writer.close();
// then
assertFileEquals(
new File(EXPECTED_FILE_DIRECTORY + "expected-trades-with-wrapper-object.json"),
- this.resource.getFile());
+ resource.getFile());
}
@Test
public void testForcedWrite() throws Exception {
// given
- this.writer.setForceSync(true);
+ Path outputFilePath = Paths.get("target", "testForcedWrite-" + getMarshallerName() + ".json");
+ FileSystemResource resource = new FileSystemResource(outputFilePath);
+ JsonFileItemWriter writer = new JsonFileItemWriterBuilder()
+ .name("tradesItemWriter")
+ .resource(resource)
+ .jsonObjectMarshaller(getJsonObjectMarshaller())
+ .forceSync(true)
+ .build();
// when
- this.writer.open(this.executionContext);
- this.writer.write(Collections.singletonList(this.trade1));
- this.writer.close();
+ writer.open(new ExecutionContext());
+ writer.write(Collections.singletonList(this.trade1));
+ writer.close();
// then
assertFileEquals(
new File(EXPECTED_FILE_DIRECTORY + "expected-trades1.json"),
- this.resource.getFile());
+ resource.getFile());
}
@Test
public void testWriteWithDelete() throws Exception {
// given
- this.writer.setShouldDeleteIfExists(true);
+ ExecutionContext executionContext = new ExecutionContext();
+ Path outputFilePath = Paths.get("target", "testWriteWithDelete-" + getMarshallerName() + ".json");
+ FileSystemResource resource = new FileSystemResource(outputFilePath);
+ JsonFileItemWriter writer = new JsonFileItemWriterBuilder()
+ .name("tradesItemWriter")
+ .resource(resource)
+ .jsonObjectMarshaller(getJsonObjectMarshaller())
+ .shouldDeleteIfExists(true)
+ .build();
// when
- this.writer.open(this.executionContext);
- this.writer.write(Collections.singletonList(this.trade1));
- this.writer.close();
- this.writer.open(this.executionContext);
- this.writer.write(Collections.singletonList(this.trade2));
- this.writer.close();
+ writer.open(executionContext);
+ writer.write(Collections.singletonList(this.trade1));
+ writer.close();
+ writer.open(executionContext);
+ writer.write(Collections.singletonList(this.trade2));
+ writer.close();
// then
assertFileEquals(
new File(EXPECTED_FILE_DIRECTORY + "expected-trades2.json"),
- this.resource.getFile());
+ resource.getFile());
}
@Test
public void testRestart() throws Exception {
- this.writer.open(this.executionContext);
+ // given
+ ExecutionContext executionContext = new ExecutionContext();
+ Path outputFilePath = Paths.get("target", "testRestart-" + getMarshallerName() + ".json");
+ FileSystemResource resource = new FileSystemResource(outputFilePath);
+ JsonFileItemWriter writer = new JsonFileItemWriterBuilder()
+ .name("tradesItemWriter")
+ .resource(resource)
+ .jsonObjectMarshaller(getJsonObjectMarshaller())
+ .build();
+
+ // when
+ writer.open(executionContext);
// write some lines
- this.writer.write(Collections.singletonList(this.trade1));
+ writer.write(Collections.singletonList(this.trade1));
// get restart data
- this.writer.update(this.executionContext);
+ writer.update(executionContext);
// close template
- this.writer.close();
+ writer.close();
// init with correct data
- this.writer.open(this.executionContext);
+ writer.open(executionContext);
// write more lines
- this.writer.write(Collections.singletonList(this.trade2));
+ writer.write(Collections.singletonList(this.trade2));
// get statistics
- this.writer.update(this.executionContext);
+ writer.update(executionContext);
// close template
- this.writer.close();
+ writer.close();
// verify what was written to the file
assertFileEquals(
new File(EXPECTED_FILE_DIRECTORY+ "expected-trades.json"),
- this.resource.getFile());
+ resource.getFile());
// 2 lines were written to the file in total
- assertEquals(2, this.executionContext.getLong("tradesItemWriter.written"));
+ assertEquals(2, executionContext.getLong("tradesItemWriter.written"));
}
@Test
public void testTransactionalRestart() throws Exception {
- this.writer.open(this.executionContext);
+ // given
PlatformTransactionManager transactionManager = new ResourcelessTransactionManager();
+ ExecutionContext executionContext = new ExecutionContext();
+ Path outputFilePath = Paths.get("target", "testTransactionalRestart-" + getMarshallerName() + ".json");
+ FileSystemResource resource = new FileSystemResource(outputFilePath);
+ JsonFileItemWriter writer = new JsonFileItemWriterBuilder()
+ .name("tradesItemWriter")
+ .resource(resource)
+ .jsonObjectMarshaller(getJsonObjectMarshaller())
+ .build();
+
+ // when
+ writer.open(executionContext);
new TransactionTemplate(transactionManager).execute((TransactionCallback) status -> {
try {
// write some lines
- this.writer.write(Collections.singletonList(this.trade1));
+ writer.write(Collections.singletonList(this.trade1));
}
catch (Exception e) {
throw new UnexpectedInputException("Could not write data", e);
}
// get restart data
- this.writer.update(this.executionContext);
+ writer.update(executionContext);
return null;
});
// close template
- this.writer.close();
+ writer.close();
// init with correct data
- this.writer.open(this.executionContext);
+ writer.open(executionContext);
new TransactionTemplate(transactionManager).execute((TransactionCallback) status -> {
try {
// write more lines
- this.writer.write(Collections.singletonList(this.trade2));
+ writer.write(Collections.singletonList(this.trade2));
}
catch (Exception e) {
throw new UnexpectedInputException("Could not write data", e);
}
// get restart data
- this.writer.update(this.executionContext);
+ writer.update(executionContext);
return null;
});
// close template
- this.writer.close();
+ writer.close();
// verify what was written to the file
assertFileEquals(
new File(EXPECTED_FILE_DIRECTORY+ "expected-trades.json"),
- this.resource.getFile());
+ resource.getFile());
// 2 lines were written to the file in total
- assertEquals(2, this.executionContext.getLong("tradesItemWriter.written"));
+ assertEquals(2, executionContext.getLong("tradesItemWriter.written"));
}
@Test
public void testItemMarshallingFailure() throws Exception {
- this.writer.setJsonObjectMarshaller(item -> {
- throw new IllegalArgumentException("Bad item");
- });
- this.writer.open(this.executionContext);
+ // given
+ ExecutionContext executionContext = new ExecutionContext();
+ Path outputFilePath = Paths.get("target", "testItemMarshallingFailure-" + getMarshallerName() + ".json");
+ FileSystemResource resource = new FileSystemResource(outputFilePath);
+ JsonFileItemWriter writer = new JsonFileItemWriterBuilder()
+ .name("tradesItemWriter")
+ .resource(resource)
+ .jsonObjectMarshaller(item -> { throw new IllegalArgumentException("Bad item"); })
+ .build();
+
+ // when
+ writer.open(executionContext);
try {
- this.writer.write(Collections.singletonList(this.trade1));
+ writer.write(Collections.singletonList(this.trade1));
fail();
}
catch (IllegalArgumentException iae) {
assertEquals("Bad item", iae.getMessage());
}
finally {
- this.writer.close();
+ writer.close();
}
assertFileEquals(
new File(EXPECTED_FILE_DIRECTORY + "empty-trades.json"),
- this.resource.getFile());
+ resource.getFile());
}
@Test
@@ -283,29 +340,35 @@ public abstract class JsonFileItemWriterFunctionalTests {
* If append=true a new output file should still be created on the first run (not restart).
*/
public void testAppendToNotYetExistingFile() throws Exception {
- Resource toBeCreated = new FileSystemResource("target/FlatFileItemWriterTests.out");
+ // given
+ ExecutionContext executionContext = new ExecutionContext();
+ Path outputFilePath = Paths.get("target", "testAppendToNotYetExistingFile-" + getMarshallerName() + ".json");
+ FileSystemResource resource = new FileSystemResource(outputFilePath);
+ Files.deleteIfExists(outputFilePath);
+ JsonFileItemWriter writer = new JsonFileItemWriterBuilder()
+ .name("tradesItemWriter")
+ .resource(new FileSystemResource(outputFilePath))
+ .jsonObjectMarshaller(getJsonObjectMarshaller())
+ .append(true)
+ .build();
- File outputFile = toBeCreated.getFile(); //enable easy content reading and auto-delete the file
-
- assertFalse("output file does not exist yet", toBeCreated.exists());
- this.writer.setResource(toBeCreated);
- this.writer.setAppendAllowed(true);
- this.writer.afterPropertiesSet();
-
- this.writer.open(this.executionContext);
- assertTrue("output file was created", toBeCreated.exists());
-
- this.writer.write(Collections.singletonList(this.trade1));
- this.writer.close();
+ // when
+ writer.open(executionContext);
+ writer.write(Collections.singletonList(this.trade1));
+ writer.close();
+
+ // then
assertFileEquals(
new File(EXPECTED_FILE_DIRECTORY + "expected-trades1.json"),
- outputFile);
- outputFile.delete();
+ resource.getFile());
}
private void assertFileEquals(File expected, File actual) throws Exception {
- String expectedHash = DigestUtils.md5DigestAsHex(new FileInputStream(expected));
- String actualHash = DigestUtils.md5DigestAsHex(new FileInputStream(actual));
- Assert.assertEquals(expectedHash, actualHash);
+ JSONAssert.assertEquals(getContent(expected), getContent(actual), false);
}
+
+ private String getContent(File file) throws IOException {
+ return new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset());
+ }
+
}