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 + "]"; - } - } - }