Skip to content

Commit

Permalink
Update Jackson2ExecutionContextStringSerializer
Browse files Browse the repository at this point in the history
  • Loading branch information
fmbenhassine committed Jun 10, 2020
1 parent bbabe02 commit 9a79b45
Show file tree
Hide file tree
Showing 3 changed files with 319 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2008-2019 the original author or authors.
* Copyright 2008-2020 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.
Expand All @@ -18,28 +18,80 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.fasterxml.jackson.annotation.JacksonAnnotation;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DatabindContext;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
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.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;

/**
* Implementation that uses Jackson2 to provide (de)serialization.
* Implementation that uses Jackson2 to provide (de)serialization.
*
* By default, this implementation trusts a limited set of classes to be
* deserialized from the execution context. If a class is not trusted by default
* and is safe to deserialize, you can provide an explicit mapping using Jackson
* annotations, as shown in the following example:
*
* <pre class="code">
* &#064;JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
* public class MyTrustedType implements Serializable {
*
* }
* </pre>
*
* It is also possible to provide a custom {@link ObjectMapper} with a mixin for
* the trusted type:
*
* <pre class="code">
* ObjectMapper objectMapper = new ObjectMapper();
* objectMapper.addMixIn(MyTrustedType.class, Object.class);
* Jackson2ExecutionContextStringSerializer serializer = new Jackson2ExecutionContextStringSerializer();
* serializer.setObjectMapper(objectMapper);
* // register serializer in JobRepositoryFactoryBean
* </pre>
*
* If the (de)serialization is only done by a trusted source, you can also enable
* default typing:
*
* <pre class="code">
* PolymorphicTypeValidator polymorphicTypeValidator = .. // configure your trusted PolymorphicTypeValidator
* ObjectMapper objectMapper = new ObjectMapper();
* objectMapper.activateDefaultTyping(polymorphicTypeValidator);
* Jackson2ExecutionContextStringSerializer serializer = new Jackson2ExecutionContextStringSerializer();
* serializer.setObjectMapper(objectMapper);
* // register serializer in JobRepositoryFactoryBean
* </pre>
*
* @author Marten Deinum
* @author Mahmoud Ben Hassine
Expand All @@ -55,7 +107,8 @@ 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();
this.objectMapper.configure(MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES, true);
this.objectMapper.setDefaultTyping(createTrustedDefaultTyping());
this.objectMapper.registerModule(new JobParametersModule());
}

Expand Down Expand Up @@ -141,4 +194,158 @@ public JobParameter deserialize(JsonParser parser, DeserializationContext contex

}

/**
* Creates a TypeResolverBuilder that checks if a type is trusted.
* @return a TypeResolverBuilder that checks if a type is trusted.
*/
private static TypeResolverBuilder<? extends TypeResolverBuilder> createTrustedDefaultTyping() {
TypeResolverBuilder<? extends TypeResolverBuilder> result = new TrustedTypeResolverBuilder(ObjectMapper.DefaultTyping.NON_FINAL);
result = result.init(JsonTypeInfo.Id.CLASS, null);
result = result.inclusion(JsonTypeInfo.As.PROPERTY);
return result;
}

/**
* An implementation of {@link ObjectMapper.DefaultTypeResolverBuilder}
* that inserts an {@code allow all} {@link PolymorphicTypeValidator}
* and overrides the {@code TypeIdResolver}
* @author Rob Winch
*/
static class TrustedTypeResolverBuilder extends ObjectMapper.DefaultTypeResolverBuilder {

TrustedTypeResolverBuilder(ObjectMapper.DefaultTyping defaultTyping) {
super(
defaultTyping,
//we do explicit validation in the TypeIdResolver
BasicPolymorphicTypeValidator.builder()
.allowIfSubType(Object.class)
.build()
);
}

@Override
protected TypeIdResolver idResolver(MapperConfig<?> config,
JavaType baseType,
PolymorphicTypeValidator subtypeValidator,
Collection<NamedType> subtypes, boolean forSer, boolean forDeser) {
TypeIdResolver result = super.idResolver(config, baseType, subtypeValidator, subtypes, forSer, forDeser);
return new TrustedTypeIdResolver(result);
}
}

/**
* A {@link TypeIdResolver} that delegates to an existing implementation and throws an IllegalStateException if the
* class being looked up is not trusted, does not provide an explicit mixin, and is not annotated with Jackson
* mappings.
*/
static class TrustedTypeIdResolver implements TypeIdResolver {
private static final Set<String> TRUSTED_CLASS_NAMES = Collections.unmodifiableSet(new HashSet(Arrays.asList(
"java.util.ArrayList",
"java.util.LinkedList",
"java.util.Collections$EmptyList",
"java.util.Collections$EmptyMap",
"java.util.Collections$EmptySet",
"java.util.Collections$UnmodifiableRandomAccessList",
"java.util.Collections$UnmodifiableList",
"java.util.Collections$UnmodifiableMap",
"java.util.Collections$UnmodifiableSet",
"java.util.Collections$SingletonList",
"java.util.Collections$SingletonMap",
"java.util.Collections$SingletonSet",
"java.util.Date",
"java.time.Instant",
"java.time.Duration",
"java.time.LocalDate",
"java.time.LocalTime",
"java.time.LocalDateTime",
"java.net.URL",
"java.util.TreeMap",
"java.util.HashMap",
"java.util.LinkedHashMap",
"java.util.TreeSet",
"java.util.HashSet",
"java.util.LinkedHashSet",
"java.lang.Boolean",
"java.lang.Byte",
"java.lang.Short",
"java.lang.Integer",
"java.lang.Long",
"java.lang.Double",
"java.lang.Float",
"java.math.BigDecimal",
"java.math.BigInteger",
"java.lang.String",
"java.lang.Character",
"java.lang.CharSequence",
"java.util.Properties",
"[Ljava.util.Properties;",
"org.springframework.batch.core.JobParameter",
"org.springframework.batch.core.JobParameters",
"org.springframework.batch.core.jsr.partition.JsrPartitionHandler$PartitionPlanState"
)));

private final TypeIdResolver delegate;

TrustedTypeIdResolver(TypeIdResolver delegate) {
this.delegate = delegate;
}

@Override
public void init(JavaType baseType) {
delegate.init(baseType);
}

@Override
public String idFromValue(Object value) {
return delegate.idFromValue(value);
}

@Override
public String idFromValueAndType(Object value, Class<?> suggestedType) {
return delegate.idFromValueAndType(value, suggestedType);
}

@Override
public String idFromBaseType() {
return delegate.idFromBaseType();
}

@Override
public JavaType typeFromId(DatabindContext context, String id) throws IOException {
DeserializationConfig config = (DeserializationConfig) context.getConfig();
JavaType result = delegate.typeFromId(context, id);
String className = result.getRawClass().getName();
if (isTrusted(className)) {
return result;
}
boolean isExplicitMixin = config.findMixInClassFor(result.getRawClass()) != null;
if (isExplicitMixin) {
return result;
}
Class<?> rawClass = result.getRawClass();
JacksonAnnotation jacksonAnnotation = AnnotationUtils.findAnnotation(rawClass, JacksonAnnotation.class);
if (jacksonAnnotation != null) {
return result;
}
throw new IllegalArgumentException("The class with " + id + " and name of " + className + " is not trusted. " +
"If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or a custom ObjectMapper. " +
"If the serialization is only done by a trusted source, you can also enable default typing.");
}

private boolean isTrusted(String id) {
return TRUSTED_CLASS_NAMES.contains(id);
}

@Override
public String getDescForKnownTypeIds() {
return delegate.getDescForKnownTypeIds();
}

@Override
public JsonTypeInfo.Id getMechanism() {
return delegate.getMechanism();
}

}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2018 the original author or authors.
* Copyright 2012-2020 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.
Expand All @@ -15,6 +15,7 @@
*/
package org.springframework.batch.core.repository.dao;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.junit.Test;
import org.springframework.batch.core.JobParameter;
import org.springframework.batch.core.JobParameters;
Expand Down Expand Up @@ -175,6 +176,7 @@ protected Map<String, Object> serializationRoundTrip(Map<String, Object> m1) thr

protected abstract ExecutionContextSerializer getSerializer();

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public static class ComplexObject implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
Expand Down
Loading

0 comments on commit 9a79b45

Please sign in to comment.