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 index 6233cfeca..2b73a0126 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2008-2016 the original author or authors. + * Copyright 2008-2018 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,14 +18,23 @@ package org.springframework.batch.core.repository.dao; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.Date; import java.util.HashMap; import java.util.Map; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.repository.ExecutionContextSerializer; import org.springframework.util.Assert; @@ -33,6 +42,7 @@ import org.springframework.util.Assert; * Implementation that uses Jackson2 to provide (de)serialization. * * @author Marten Deinum + * @author Mahmoud Ben Hassine * @since 3.0.7 * * @see ExecutionContextSerializer @@ -46,6 +56,7 @@ public class Jackson2ExecutionContextStringSerializer implements ExecutionContex this.objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false); this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); this.objectMapper.enableDefaultTyping(); + this.objectMapper.registerModule(new JobParametersModule()); } public void setObjectMapper(ObjectMapper objectMapper) { @@ -66,4 +77,64 @@ public class Jackson2ExecutionContextStringSerializer implements ExecutionContex objectMapper.writeValue(out, context); } + + // BATCH-2680 + /** + * Custom Jackson module to support {@link JobParameter} and {@link JobParameters} + * deserialization. + */ + private class JobParametersModule extends SimpleModule { + + private JobParametersModule() { + super("Job parameters module"); + setMixInAnnotation(JobParameters.class, JobParametersMixIn.class); + addDeserializer(JobParameter.class, new JobParameterDeserializer()); + } + + private abstract class JobParametersMixIn { + @JsonIgnore + abstract boolean isEmpty(); + } + + private class JobParameterDeserializer extends StdDeserializer { + + private static final String IDENTIFYING_KEY_NAME = "identifying"; + private static final String TYPE_KEY_NAME = "type"; + private static final String VALUE_KEY_NAME = "value"; + + JobParameterDeserializer() { + super(JobParameter.class); + } + + @Override + public JobParameter deserialize(JsonParser parser, DeserializationContext context) throws IOException { + JsonNode node = parser.readValueAsTree(); + boolean identifying = node.get(IDENTIFYING_KEY_NAME).asBoolean(); + String type = node.get(TYPE_KEY_NAME).asText(); + JsonNode value = node.get(VALUE_KEY_NAME); + Object parameterValue; + switch (JobParameter.ParameterType.valueOf(type)) { + case STRING: { + parameterValue = value.asText(); + return new JobParameter((String) parameterValue, identifying); + } + case DATE: { + parameterValue = new Date(value.get(1).asLong()); + return new JobParameter((Date) parameterValue, identifying); + } + case LONG: { + parameterValue = value.get(1).asLong(); + return new JobParameter((Long) parameterValue, identifying); + } + case DOUBLE: { + parameterValue = value.asDouble(); + return new JobParameter((Double) parameterValue, identifying); + } + } + return null; + } + } + + } + } 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 fa1dfb56f..f4911a7de 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2018 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. @@ -16,6 +16,8 @@ package org.springframework.batch.core.repository.dao; import org.junit.Test; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.repository.ExecutionContextSerializer; import java.io.*; @@ -34,6 +36,7 @@ import static org.hamcrest.Matchers.hasEntry; * @author Thomas Risberg * @author Michael Minella * @author Marten Deinum + * @author Mahmoud Ben Hassine */ public abstract class AbstractExecutionContextSerializerTests { @@ -51,6 +54,79 @@ public abstract class AbstractExecutionContextSerializerTests { compareContexts(m1, m2); } + @Test + public void testSerializeStringJobParameter() throws Exception { + Map m1 = new HashMap<>(); + m1.put("name", new JobParameter("foo")); + + Map m2 = serializationRoundTrip(m1); + + compareContexts(m1, m2); + } + + @Test + public void testSerializeDateJobParameter() throws Exception { + Map m1 = new HashMap<>(); + m1.put("birthDate", new JobParameter(new Date(123456790123L))); + + Map m2 = serializationRoundTrip(m1); + + compareContexts(m1, m2); + } + + @Test + public void testSerializeDoubleJobParameter() throws Exception { + Map m1 = new HashMap<>(); + m1.put("weight", new JobParameter(80.5D)); + + Map m2 = serializationRoundTrip(m1); + + compareContexts(m1, m2); + } + + @Test + public void testSerializeLongJobParameter() throws Exception { + Map m1 = new HashMap<>(); + m1.put("age", new JobParameter(20L)); + + Map m2 = serializationRoundTrip(m1); + + compareContexts(m1, m2); + } + + @Test + public void testSerializeNonIdentifyingJobParameter() throws Exception { + Map m1 = new HashMap<>(); + m1.put("name", new JobParameter("foo", false)); + + Map m2 = serializationRoundTrip(m1); + + compareContexts(m1, m2); + } + + @Test + public void testSerializeJobParameters() throws Exception { + Map jobParametersMap = new HashMap<>(); + jobParametersMap.put("paramName", new JobParameter("paramValue")); + + Map m1 = new HashMap<>(); + m1.put("params", new JobParameters(jobParametersMap)); + + Map m2 = serializationRoundTrip(m1); + + compareContexts(m1, m2); + } + + @Test + public void testSerializeEmptyJobParameters() throws IOException { + Map m1 = new HashMap<>(); + m1.put("params", new JobParameters()); + + Map m2 = serializationRoundTrip(m1); + + compareContexts(m1, m2); + } + @Test public void testComplexObject() throws Exception { Map m1 = new HashMap();