BATCH-2680: add custom Jackson module to (de)serialize job parameters
Currently, the Jackson2ExecutionContextStringSerializer fails to deserialize json representations of: * empty JobParameters instances due to the presence of "empty":true in the serialized json String. In this case, Jackson is not able to find a property named "empty" in the target type (JobParameters) * JobParameter instances due to the presence of several constructors. In this case, Jackson does not know which constructor to use. This commit fixes these two issues by adding a custom Jackson module with: * a mixin to ignore the "isEmpty" getter in JobParameters type * a custom deserializer for JobParameter type Resolves BATCH-2680
This commit is contained in:
committed by
Michael Minella
parent
867fa5e2af
commit
b0ffe55113
@@ -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<JobParameter> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<String, Object> m1 = new HashMap<>();
|
||||
m1.put("name", new JobParameter("foo"));
|
||||
|
||||
Map<String, Object> m2 = serializationRoundTrip(m1);
|
||||
|
||||
compareContexts(m1, m2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializeDateJobParameter() throws Exception {
|
||||
Map<String, Object> m1 = new HashMap<>();
|
||||
m1.put("birthDate", new JobParameter(new Date(123456790123L)));
|
||||
|
||||
Map<String, Object> m2 = serializationRoundTrip(m1);
|
||||
|
||||
compareContexts(m1, m2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializeDoubleJobParameter() throws Exception {
|
||||
Map<String, Object> m1 = new HashMap<>();
|
||||
m1.put("weight", new JobParameter(80.5D));
|
||||
|
||||
Map<String, Object> m2 = serializationRoundTrip(m1);
|
||||
|
||||
compareContexts(m1, m2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializeLongJobParameter() throws Exception {
|
||||
Map<String, Object> m1 = new HashMap<>();
|
||||
m1.put("age", new JobParameter(20L));
|
||||
|
||||
Map<String, Object> m2 = serializationRoundTrip(m1);
|
||||
|
||||
compareContexts(m1, m2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializeNonIdentifyingJobParameter() throws Exception {
|
||||
Map<String, Object> m1 = new HashMap<>();
|
||||
m1.put("name", new JobParameter("foo", false));
|
||||
|
||||
Map<String, Object> m2 = serializationRoundTrip(m1);
|
||||
|
||||
compareContexts(m1, m2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializeJobParameters() throws Exception {
|
||||
Map<String, JobParameter> jobParametersMap = new HashMap<>();
|
||||
jobParametersMap.put("paramName", new JobParameter("paramValue"));
|
||||
|
||||
Map<String, Object> m1 = new HashMap<>();
|
||||
m1.put("params", new JobParameters(jobParametersMap));
|
||||
|
||||
Map<String, Object> m2 = serializationRoundTrip(m1);
|
||||
|
||||
compareContexts(m1, m2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializeEmptyJobParameters() throws IOException {
|
||||
Map<String, Object> m1 = new HashMap<>();
|
||||
m1.put("params", new JobParameters());
|
||||
|
||||
Map<String, Object> m2 = serializationRoundTrip(m1);
|
||||
|
||||
compareContexts(m1, m2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComplexObject() throws Exception {
|
||||
Map<String, Object> m1 = new HashMap<String, Object>();
|
||||
|
||||
Reference in New Issue
Block a user