From e41763b391a456828867539a2fd0d04319cda2ed Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 1 Sep 2022 10:41:07 +0200 Subject: [PATCH 1/2] Use JUnit 5 assertions in AbstractExecutionContextSerializerTests --- ...stractExecutionContextSerializerTests.java | 47 +++++++++---------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractExecutionContextSerializerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractExecutionContextSerializerTests.java index 214046fd46..90242a5abb 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractExecutionContextSerializerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractExecutionContextSerializerTests.java @@ -15,20 +15,24 @@ */ package org.springframework.batch.core.repository.dao; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + import com.fasterxml.jackson.annotation.JsonTypeInfo; import org.junit.jupiter.api.Test; + import org.springframework.batch.core.JobParameter; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.repository.ExecutionContextSerializer; -import java.io.*; -import java.math.BigDecimal; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasEntry; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; /** @@ -54,7 +58,7 @@ void testSerializeAMap() throws Exception { Map m2 = serializationRoundTrip(m1); - compareContexts(m1, m2); + assertEquals(m1.entrySet(), m2.entrySet()); } @Test @@ -64,7 +68,7 @@ void testSerializeStringJobParameter() throws Exception { Map m2 = serializationRoundTrip(m1); - compareContexts(m1, m2); + assertEquals(m1.entrySet(), m2.entrySet()); } @Test @@ -74,7 +78,7 @@ void testSerializeDateJobParameter() throws Exception { Map m2 = serializationRoundTrip(m1); - compareContexts(m1, m2); + assertEquals(m1.entrySet(), m2.entrySet()); } @Test @@ -84,7 +88,7 @@ void testSerializeDoubleJobParameter() throws Exception { Map m2 = serializationRoundTrip(m1); - compareContexts(m1, m2); + assertEquals(m1.entrySet(), m2.entrySet()); } @Test @@ -94,7 +98,7 @@ void testSerializeLongJobParameter() throws Exception { Map m2 = serializationRoundTrip(m1); - compareContexts(m1, m2); + assertEquals(m1.entrySet(), m2.entrySet()); } @Test @@ -104,7 +108,7 @@ void testSerializeNonIdentifyingJobParameter() throws Exception { Map m2 = serializationRoundTrip(m1); - compareContexts(m1, m2); + assertEquals(m1.entrySet(), m2.entrySet()); } @Test @@ -117,7 +121,7 @@ void testSerializeJobParameters() throws Exception { Map m2 = serializationRoundTrip(m1); - compareContexts(m1, m2); + assertEquals(m1.entrySet(), m2.entrySet()); } @Test @@ -127,7 +131,7 @@ void testSerializeEmptyJobParameters() throws IOException { Map m2 = serializationRoundTrip(m1); - compareContexts(m1, m2); + assertEquals(m1.entrySet(), m2.entrySet()); } @Test @@ -149,7 +153,7 @@ void testComplexObject() throws Exception { Map m2 = serializationRoundTrip(m1); - compareContexts(m1, m2); + assertEquals(m1.entrySet(), m2.entrySet()); } @Test @@ -160,7 +164,7 @@ void testSerializeRecords() throws IOException { Map m2 = serializationRoundTrip(m1); - compareContexts(m1, m2); + assertEquals(m1.entrySet(), m2.entrySet()); } @Test @@ -169,13 +173,6 @@ void testNullSerialization() { assertThrows(IllegalArgumentException.class, () -> serializer.serialize(null, null)); } - protected void compareContexts(Map m1, Map m2) { - - for (Map.Entry entry : m1.entrySet()) { - assertThat(m2, hasEntry(entry.getKey(), entry.getValue())); - } - } - protected Map serializationRoundTrip(Map m1) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); getSerializer().serialize(m1, out); From 7b624f91aaa707f8504de49c7a40edace48138db Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 1 Sep 2022 16:47:43 +0200 Subject: [PATCH 2/2] [WIP] Add ExecutionContextSerializer implementation based on Gson This is work in progress. Gson seems to deserialize custom types to Map instead of the original type (seems like type info is not serialized). TODO: Need to figure out if this should be done in the implementation of the serializer or as a configuration of the gson instance --- spring-batch-core/pom.xml | 5 ++ .../GsonExecutionContextStringSerializer.java | 74 +++++++++++++++++++ ...ExecutionContextStringSerializerTests.java | 40 ++++++++++ 3 files changed, 119 insertions(+) create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/GsonExecutionContextStringSerializer.java create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/repository/GsonExecutionContextStringSerializerTests.java diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 84d52400be..9b28ff5d27 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -55,6 +55,11 @@ jackson-databind ${jackson.version} + + com.google.code.gson + gson + ${gson.version} + io.micrometer micrometer-core diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/GsonExecutionContextStringSerializer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/GsonExecutionContextStringSerializer.java new file mode 100644 index 0000000000..478b6afd20 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/GsonExecutionContextStringSerializer.java @@ -0,0 +1,74 @@ +/* + * Copyright 2022 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 + * + * https://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.core.repository.dao; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import org.springframework.batch.core.repository.ExecutionContextSerializer; +import org.springframework.util.Assert; + +/** + * This class is an implementation of {@link ExecutionContextSerializer} + * based on Google Gson library. + * + * @author Mahmoud Ben Hassine + * @since 5.0 + */ +public class GsonExecutionContextStringSerializer implements ExecutionContextSerializer { + + private Gson gson; + + /** + * Create a new {@link GsonExecutionContextStringSerializer}. + * + * @param gson the Gson instance to use + */ + public GsonExecutionContextStringSerializer(Gson gson) { + this.gson = gson; + } + + @Override + public Map deserialize(InputStream inputStream) throws IOException { + Assert.notNull(inputStream, "An InputStream is required"); + Type mapType = new TypeToken>(){}.getType(); + try (JsonReader jsonReader = new JsonReader(new InputStreamReader(inputStream))) { + return this.gson.fromJson(jsonReader, mapType); + } + } + + @Override + public void serialize(Map context, OutputStream outputStream) throws IOException { + Assert.notNull(context, "A context is required"); + Assert.notNull(outputStream, "An OutputStream is required"); + Type mapType = new TypeToken>(){}.getType(); + try (JsonWriter jsonWriter = new JsonWriter(new OutputStreamWriter(outputStream))) { + this.gson.toJson(context, mapType, jsonWriter); + } + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/GsonExecutionContextStringSerializerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/GsonExecutionContextStringSerializerTests.java new file mode 100644 index 0000000000..dfebb22437 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/GsonExecutionContextStringSerializerTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2022 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 + * + * https://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.core.repository; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.ToNumberPolicy; + +import org.springframework.batch.core.repository.dao.AbstractExecutionContextSerializerTests; +import org.springframework.batch.core.repository.dao.GsonExecutionContextStringSerializer; + +/** + * Test class for {@link GsonExecutionContextStringSerializer}. + * + * @author Mahmoud Ben Hassine + */ +public class GsonExecutionContextStringSerializerTests extends AbstractExecutionContextSerializerTests { + + @Override + protected ExecutionContextSerializer getSerializer() { + Gson gson = new GsonBuilder() + // TODO correctly configure gson to pass the test suite + .create(); + return new GsonExecutionContextStringSerializer(gson); + } + +}