From 22109eea84e4ea5ab773f58baad2b366b1858591 Mon Sep 17 00:00:00 2001 From: Marten Deinum Date: Mon, 7 Mar 2016 09:19:57 +0100 Subject: [PATCH] This commit adds a ExecutionContextSerializer based on Jackson2. It has been tested with Jackson 2.3.3 which is the Jackson version used with the Spring dependency. Tests have been updated to use Hamcrest matchers and made a bit more reusable. --- build.gradle | 2 + ...kson2ExecutionContextStringSerializer.java | 68 +++++++ ...stractExecutionContextSerializerTests.java | 185 ++++++++++++++++++ ...efaultExecutionContextSerializerTests.java | 152 +------------- ...ExecutionContextStringSerializerTests.java | 39 ++++ ...ExecutionContextStringSerializerTests.java | 159 +-------------- 6 files changed, 309 insertions(+), 296 deletions(-) create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/Jackson2ExecutionContextStringSerializer.java create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractExecutionContextSerializerTests.java create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/Jackson2ExecutionContextStringSerializerTests.java diff --git a/build.gradle b/build.gradle index ca403009e..e16bd59fd 100644 --- a/build.gradle +++ b/build.gradle @@ -70,6 +70,7 @@ allprojects { hsqldbVersion = '2.3.2' ibatisVersion = '2.3.4.726' jacksonVersion = '1.9.13' + jackson2Version = "2.3.3" javaMailVersion = '1.4.7' javaxBatchApiVersion = '1.0' javaxInjectVersion = '1' @@ -276,6 +277,7 @@ project('spring-batch-core') { optional "org.springframework:spring-jdbc:$springVersion" optional "org.slf4j:slf4j-log4j12:$slf4jVersion" optional "log4j:log4j:$log4jVersion" + optional "com.fasterxml.jackson.core:jackson-databind:${jackson2Version}" optional "javax.batch:javax.batch-api:$javaxBatchApiVersion" } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/Jackson2ExecutionContextStringSerializer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/Jackson2ExecutionContextStringSerializer.java new file mode 100644 index 000000000..753f990ec --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/Jackson2ExecutionContextStringSerializer.java @@ -0,0 +1,68 @@ +/* + * Copyright 2008-2016 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 + * + * http://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 com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.batch.core.repository.ExecutionContextSerializer; +import org.springframework.util.Assert; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * Implementation that uses Jackson2 to provide (de)serialization. + * + * @author Marten Deinum + * @since 3.0.7 + * + * @see ExecutionContextSerializer + */ +public class Jackson2ExecutionContextStringSerializer implements ExecutionContextSerializer { + + private ObjectMapper objectMapper; + + public Jackson2ExecutionContextStringSerializer() { + this.objectMapper = new ObjectMapper(); + this.objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false); + this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); + this.objectMapper.enableDefaultTyping(); + } + + public void setObjectMapper(ObjectMapper objectMapper) { + Assert.notNull(objectMapper, "ObjectMapper must not be null"); + this.objectMapper = objectMapper; + } + + public Map deserialize(InputStream in) throws IOException { + + TypeReference> typeRef = new TypeReference>() {}; + return objectMapper.readValue(in, typeRef); + } + + public void serialize(Map context, OutputStream out) throws IOException { + + Assert.notNull(context); + Assert.notNull(out); + + objectMapper.writeValue(out, context); + } +} 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 new file mode 100644 index 000000000..fa1dfb56f --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/AbstractExecutionContextSerializerTests.java @@ -0,0 +1,185 @@ +/* + * Copyright 2012-2016 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 + * + * http://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 org.junit.Test; +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; + +/** + * Abstract test class for {@code ExecutionContextSerializer} implementations. Provides a minimum on test methods + * that should pass for each {@code ExecutionContextSerializer} implementation. + * + * @author Thomas Risberg + * @author Michael Minella + * @author Marten Deinum + */ +public abstract class AbstractExecutionContextSerializerTests { + + @Test + public void testSerializeAMap() throws Exception { + Map m1 = new HashMap(); + m1.put("object1", Long.valueOf(12345L)); + m1.put("object2", "OBJECT TWO"); + // Use a date after 1971 (otherwise daylight saving screws up)... + m1.put("object3", new Date(123456790123L)); + m1.put("object4", new Double(1234567.1234D)); + + Map m2 = serializationRoundTrip(m1); + + compareContexts(m1, m2); + } + + @Test + public void testComplexObject() throws Exception { + Map m1 = new HashMap(); + ComplexObject o1 = new ComplexObject(); + o1.setName("02345"); + Map m = new HashMap(); + m.put("object1", Long.valueOf(12345L)); + m.put("object2", "OBJECT TWO"); + o1.setMap(m); + o1.setNumber(new BigDecimal("12345.67")); + ComplexObject o2 = new ComplexObject(); + o2.setName("Inner Object"); + o2.setMap(m); + o2.setNumber(new BigDecimal("98765.43")); + o1.setObj(o2); + m1.put("co", o1); + + Map m2 = serializationRoundTrip(m1); + + compareContexts(m1, m2); + } + + @Test (expected=IllegalArgumentException.class) + public void testNullSerialization() throws Exception { + getSerializer().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); + + String s = new String(out.toByteArray(), "ISO-8859-1"); + + InputStream in = new ByteArrayInputStream(s.getBytes("ISO-8859-1")); + Map m2 = getSerializer().deserialize(in); + return m2; + } + + + protected abstract ExecutionContextSerializer getSerializer(); + + public static class ComplexObject implements Serializable { + private static final long serialVersionUID = 1L; + private String name; + private BigDecimal number; + private ComplexObject obj; + private Map map; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public BigDecimal getNumber() { + return number; + } + + public void setNumber(BigDecimal number) { + this.number = number; + } + + public ComplexObject getObj() { + return obj; + } + + public void setObj(ComplexObject obj) { + this.obj = obj; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ComplexObject that = (ComplexObject) o; + + if (map != null ? !map.equals(that.map) : that.map != null) { + return false; + } + if (name != null ? !name.equals(that.name) : that.name != null) { + return false; + } + if (number != null ? !number.equals(that.number) : that.number != null) { + return false; + } + if (obj != null ? !obj.equals(that.obj) : that.obj != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result; + result = (name != null ? name.hashCode() : 0); + result = 31 * result + (number != null ? number.hashCode() : 0); + result = 31 * result + (obj != null ? obj.hashCode() : 0); + result = 31 * result + (map != null ? map.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "ComplexObject [name=" + name + ", number=" + number + "]"; + } + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/DefaultExecutionContextSerializerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/DefaultExecutionContextSerializerTests.java index db8e326c2..32b85ff40 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/DefaultExecutionContextSerializerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/DefaultExecutionContextSerializerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. @@ -15,26 +15,19 @@ */ package org.springframework.batch.core.repository.dao; -import static org.junit.Assert.assertEquals; - -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 org.junit.Before; import org.junit.Test; +import org.springframework.batch.core.repository.ExecutionContextSerializer; + +import java.io.ByteArrayOutputStream; +import java.util.HashMap; +import java.util.Map; /** * @author Michael Minella * */ -public class DefaultExecutionContextSerializerTests { +public class DefaultExecutionContextSerializerTests extends AbstractExecutionContextSerializerTests { private DefaultExecutionContextSerializer serializer; @@ -46,19 +39,6 @@ public class DefaultExecutionContextSerializerTests { serializer = new DefaultExecutionContextSerializer(); } - @Test - public void testSerializeAMap() throws Exception { - Map m1 = new HashMap(); - m1.put("object1", Long.valueOf(12345L)); - m1.put("object2", "OBJECT TWO"); - // Use a date after 1971 (otherwise daylight saving screws up)... - m1.put("object3", new Date(123456790123L)); - m1.put("object4", new Double(1234567.1234D)); - - Map m2 = serializationRoundTrip(m1); - - compareContexts(m1, m2); - } @Test(expected = IllegalArgumentException.class) public void testSerializeNonSerializable() throws Exception { @@ -68,120 +48,8 @@ public class DefaultExecutionContextSerializerTests { serializer.serialize(m1, new ByteArrayOutputStream()); } - @Test - public void testComplexObject() throws Exception { - Map m1 = new HashMap(); - ComplexObject o1 = new ComplexObject(); - o1.setName("02345"); - Map m = new HashMap(); - m.put("object1", Long.valueOf(12345L)); - m.put("object2", "OBJECT TWO"); - o1.setMap(m); - o1.setNumber(new BigDecimal("12345.67")); - ComplexObject o2 = new ComplexObject(); - o2.setName("Inner Object"); - o2.setMap(m); - o2.setNumber(new BigDecimal("98765.43")); - o1.setObj(o2); - m1.put("co", o1); - - Map m2 = serializationRoundTrip(m1); - - compareContexts(m1, m2); - } - - @Test (expected=IllegalArgumentException.class) - public void testNullSerialization() throws Exception { - serializer.serialize(null, null); - } - - private void compareContexts(Map m1, Map m2) { - for (String key : m1.keySet()) { - assertEquals("Bad key/value for " + key, m1.get(key), m2.get(key)); - } - } - - private Map serializationRoundTrip(Map m1) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - serializer.serialize(m1, out); - - String s = new String(out.toByteArray(), "ISO-8859-1"); - - InputStream in = new ByteArrayInputStream(s.getBytes("ISO-8859-1")); - Map m2 = serializer.deserialize(in); - return m2; - } - - @SuppressWarnings("unused") - private static class ComplexObject implements Serializable { - private static final long serialVersionUID = 1L; - private String name; - private BigDecimal number; - private ComplexObject obj; - private Map map; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public BigDecimal getNumber() { - return number; - } - - public void setNumber(BigDecimal number) { - this.number = number; - } - - public ComplexObject getObj() { - return obj; - } - - public void setObj(ComplexObject obj) { - this.obj = obj; - } - - public Map getMap() { - return map; - } - - public void setMap(Map map) { - this.map = map; - } - - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ComplexObject that = (ComplexObject) o; - - if (map != null ? !map.equals(that.map) : that.map != null) return false; - if (name != null ? !name.equals(that.name) : that.name != null) return false; - if (number != null ? !number.equals(that.number) : that.number != null) return false; - if (obj != null ? !obj.equals(that.obj) : that.obj != null) return false; - - return true; - } - - @Override - public int hashCode() { - int result; - result = (name != null ? name.hashCode() : 0); - result = 31 * result + (number != null ? number.hashCode() : 0); - result = 31 * result + (obj != null ? obj.hashCode() : 0); - result = 31 * result + (map != null ? map.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return "ComplexObject [name=" + name + ", number=" + number + "]"; - } - + @Override + protected ExecutionContextSerializer getSerializer() { + return this.serializer; } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/Jackson2ExecutionContextStringSerializerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/Jackson2ExecutionContextStringSerializerTests.java new file mode 100644 index 000000000..0e545826a --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/Jackson2ExecutionContextStringSerializerTests.java @@ -0,0 +1,39 @@ +/* + * Copyright 2008-2016 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 + * + * http://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 org.junit.Before; +import org.springframework.batch.core.repository.ExecutionContextSerializer; + +/** + * @author Marten Deinum + */ +public class Jackson2ExecutionContextStringSerializerTests extends AbstractExecutionContextSerializerTests { + + ExecutionContextSerializer serializer; + + @Before + public void onSetUp() throws Exception { + Jackson2ExecutionContextStringSerializer serializerDeserializer = new Jackson2ExecutionContextStringSerializer(); + + serializer = serializerDeserializer; + } + + @Override + protected ExecutionContextSerializer getSerializer() { + return this.serializer; + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/XStreamExecutionContextStringSerializerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/XStreamExecutionContextStringSerializerTests.java index 439b4c5ed..1bd4d322e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/XStreamExecutionContextStringSerializerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/XStreamExecutionContextStringSerializerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2014 the original author or authors. + * Copyright 2008-2016 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. @@ -15,25 +15,14 @@ */ package org.springframework.batch.core.repository.dao; -import static org.junit.Assert.assertEquals; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.math.BigDecimal; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - import org.junit.Before; -import org.junit.Test; import org.springframework.batch.core.repository.ExecutionContextSerializer; /** * @author Thomas Risberg * @author Michael Minella */ -public class XStreamExecutionContextStringSerializerTests { +public class XStreamExecutionContextStringSerializerTests extends AbstractExecutionContextSerializerTests { ExecutionContextSerializer serializer; @@ -45,146 +34,8 @@ public class XStreamExecutionContextStringSerializerTests { serializer = serializerDeserializer; } - @Test - public void testSerializeAMap() throws Exception { - Map m1 = new HashMap(); - m1.put("object1", Long.valueOf(12345L)); - m1.put("object2", "OBJECT TWO"); - // Use a date after 1971 (otherwise daylight saving screws up)... - m1.put("object3", new Date(123456790123L)); - m1.put("object4", new Double(1234567.1234D)); - - Map m2 = serializationRoundTrip(m1); - - compareContexts(m1, m2); + @Override + protected ExecutionContextSerializer getSerializer() { + return this.serializer; } - - @Test - public void testComplexObject() throws Exception { - Map m1 = new HashMap(); - ComplexObject o1 = new ComplexObject(); - o1.setName("02345"); - Map m = new HashMap(); - m.put("object1", Long.valueOf(12345L)); - m.put("object2", "OBJECT TWO"); - o1.setMap(m); - o1.setNumber(new BigDecimal("12345.67")); - ComplexObject o2 = new ComplexObject(); - o2.setName("Inner Object"); - o2.setMap(m); - o2.setNumber(new BigDecimal("98765.43")); - o1.setObj(o2); - m1.put("co", o1); - - Map m2 = serializationRoundTrip(m1); - - compareContexts(m1, m2); - } - - @Test (expected=IllegalArgumentException.class) - public void testNullSerialization() throws Exception { - serializer.serialize(null, null); - } - - private void compareContexts(Map m1, Map m2) { - for (String key : m1.keySet()) { - System.out.println("m1 = " + m1 + " m2 = " + m2); - assertEquals("Bad key/value for " + key, m1.get(key), m2.get(key)); - } - } - - private Map serializationRoundTrip(Map m1) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - serializer.serialize(m1, out); - - String s = out.toString(); - - ByteArrayInputStream in = new ByteArrayInputStream(s.getBytes()); - Map m2 = serializer.deserialize(in); - return m2; - } - - @SuppressWarnings("unused") - private static class ComplexObject { - private String name; - private BigDecimal number; - private ComplexObject obj; - private Map map; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public BigDecimal getNumber() { - return number; - } - - public void setNumber(BigDecimal number) { - this.number = number; - } - - public ComplexObject getObj() { - return obj; - } - - public void setObj(ComplexObject obj) { - this.obj = obj; - } - - public Map getMap() { - return map; - } - - public void setMap(Map map) { - this.map = map; - } - - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - ComplexObject that = (ComplexObject) o; - - if (map != null ? !map.equals(that.map) : that.map != null) { - return false; - } - if (name != null ? !name.equals(that.name) : that.name != null) { - return false; - } - if (number != null ? !number.equals(that.number) : that.number != null) { - return false; - } - if (obj != null ? !obj.equals(that.obj) : that.obj != null) { - return false; - } - - return true; - } - - @Override - public int hashCode() { - int result; - result = (name != null ? name.hashCode() : 0); - result = 31 * result + (number != null ? number.hashCode() : 0); - result = 31 * result + (obj != null ? obj.hashCode() : 0); - result = 31 * result + (map != null ? map.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return "ComplexObject [name=" + name + ", number=" + number + "]"; - } - } - }