apply type deserialization even into maps and lists by default
diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslSerializationTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslSerializationTest.java
index f331bc5..a71b79c 100644
--- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslSerializationTest.java
+++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslSerializationTest.java
@@ -29,9 +29,12 @@
import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest;
import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.BrooklynDslCommon;
import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent;
+import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent.DslConfigSupplier;
import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent.Scope;
import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
import org.apache.brooklyn.core.resolve.jackson.*;
+import org.apache.brooklyn.core.resolve.jackson.BrooklynJacksonSerializationUtils.ConfigurableBeanDeserializerModifier;
+import org.apache.brooklyn.core.resolve.jackson.BrooklynRegisteredTypeJacksonSerializationTest.SampleBean;
import org.apache.brooklyn.core.resolve.jackson.WrappedValuesSerializationTest.ObjectWithWrappedValueString;
import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
import org.apache.brooklyn.core.workflow.WorkflowBasicTest;
@@ -77,7 +80,8 @@
Object stuff2 = mapper.readValue(out, Object.class);
Object stuff2I = ((Map<?, ?>) stuff2).get("stuff");
- Asserts.assertInstanceOf(stuff2I, Map.class);
+ Class<?> expectedDeserializedTypedMapNested = ConfigurableBeanDeserializerModifier.DEFAULT_APPLY_ONLY_TO_BEAN_DESERIALIZERS ? Map.class : DslConfigSupplier.class;
+ Asserts.assertInstanceOf(stuff2I, expectedDeserializedTypedMapNested);
}
private ObjectMapper newMapper() {
diff --git a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/BeanWithTypeUtils.java b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/BeanWithTypeUtils.java
index 2c85657..5df6c02 100644
--- a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/BeanWithTypeUtils.java
+++ b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/BeanWithTypeUtils.java
@@ -164,6 +164,7 @@
// so the former calls the latter, and the latter calls the former. but stop at some point!
if (stack.contains(mapOrListToSerializeThenDeserialize)) throw new IllegalStateException("Aborting recursive attempt to convert '"+mapOrListToSerializeThenDeserialize+"'");
+ T wrongTypeResult = null;
try {
stack.push(mapOrListToSerializeThenDeserialize);
@@ -184,14 +185,21 @@
// return result2;
// }
- return result;
+ if (result!=null && !type.getRawType().isInstance(result)) {
+ wrongTypeResult = result;
+ throw new IllegalStateException("Wrong type returned"); // will be ignored below
+ } else {
+ return result;
+ }
} catch (Exception e) {
try {
// needed for a few things, mainly where a bean has a type field that conflicts with the type here,
- // tryCoercer 81-wrong-bean uses this
+ // tryCoercer -20-wrong-bean uses this
return convertDeeply(mgmt, mapOrListToSerializeThenDeserialize, type, allowRegisteredTypes, loader, allowJavaTypes);
} catch (Exception e2) {
+ Exceptions.propagateIfFatal(e2);
+ if (wrongTypeResult!=null) return wrongTypeResult;
throw Exceptions.propagate(Arrays.asList(e, e2));
}
} finally {
diff --git a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/BrooklynJacksonSerializationUtils.java b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/BrooklynJacksonSerializationUtils.java
index 4134a71..e957c28 100644
--- a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/BrooklynJacksonSerializationUtils.java
+++ b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/BrooklynJacksonSerializationUtils.java
@@ -28,6 +28,12 @@
import com.fasterxml.jackson.databind.deser.DeserializerFactory;
import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.type.ArrayType;
+import com.fasterxml.jackson.databind.type.CollectionLikeType;
+import com.fasterxml.jackson.databind.type.CollectionType;
+import com.fasterxml.jackson.databind.type.MapLikeType;
+import com.fasterxml.jackson.databind.type.MapType;
+import com.fasterxml.jackson.databind.type.ReferenceType;
import com.fasterxml.jackson.databind.util.TokenBuffer;
import com.google.common.annotations.Beta;
@@ -126,8 +132,11 @@
}
public static class ConfigurableBeanDeserializerModifier extends BeanDeserializerModifier {
+ public static boolean DEFAULT_APPLY_ONLY_TO_BEAN_DESERIALIZERS = false;
+
List<Function<JsonDeserializer<?>,JsonDeserializer<?>>> deserializerWrappers = MutableList.of();
List<Function<Object,Object>> postConstructFunctions = MutableList.of();
+ public boolean applyOnlyToBeanDeserializers = DEFAULT_APPLY_ONLY_TO_BEAN_DESERIALIZERS;
public ConfigurableBeanDeserializerModifier addDeserializerWrapper(Function<JsonDeserializer<?>,JsonDeserializer<?>> ...f) {
for (Function<JsonDeserializer<?>,JsonDeserializer<?>> fi: f) deserializerWrappers.add(fi);
@@ -142,6 +151,10 @@
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription beanDesc,
JsonDeserializer<?> deserializer) {
+ return applyOurModifiers(deserializer);
+ }
+
+ protected JsonDeserializer<?> applyOurModifiers(JsonDeserializer<?> deserializer) {
for (Function<JsonDeserializer<?>,JsonDeserializer<?>> d: deserializerWrappers) {
deserializer = d.apply(deserializer);
}
@@ -151,6 +164,56 @@
return deserializer;
}
+ private JsonDeserializer<?> applyOurModifiersNonBean(JsonDeserializer<?> deserializer) {
+ if (applyOnlyToBeanDeserializers) {
+ // all the super's callers just return the input, so it is safe to do this, ignoring the supers of the calling method
+ return deserializer;
+ }
+ return applyOurModifiers(deserializer);
+ }
+
+ @Override
+ public JsonDeserializer<?> modifyMapDeserializer(DeserializationConfig config, MapType type, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
+ return applyOurModifiersNonBean(deserializer);
+ }
+
+ @Override
+ public JsonDeserializer<?> modifyArrayDeserializer(DeserializationConfig config, ArrayType valueType, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
+ return applyOurModifiersNonBean(deserializer);
+ }
+
+ @Override
+ public JsonDeserializer<?> modifyCollectionDeserializer(DeserializationConfig config, CollectionType type, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
+ return applyOurModifiersNonBean(deserializer);
+ }
+
+ @Override
+ public JsonDeserializer<?> modifyCollectionLikeDeserializer(DeserializationConfig config, CollectionLikeType type, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
+ return applyOurModifiersNonBean(deserializer);
+ }
+
+ @Override
+ public JsonDeserializer<?> modifyEnumDeserializer(DeserializationConfig config, JavaType type, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
+ return applyOurModifiersNonBean(deserializer);
+ }
+
+ @Override
+ public JsonDeserializer<?> modifyMapLikeDeserializer(DeserializationConfig config, MapLikeType type, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
+ return applyOurModifiersNonBean(deserializer);
+ }
+
+ @Override
+ public JsonDeserializer<?> modifyReferenceDeserializer(DeserializationConfig config, ReferenceType type, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
+ return applyOurModifiersNonBean(deserializer);
+ }
+
+ // not supported at key because it is a different hierarchy; our deserializers probably don't apply; no known use case so NBD
+// @Override
+// public KeyDeserializer modifyKeyDeserializer(DeserializationConfig config, JavaType type, KeyDeserializer deserializer) {
+// // super.modifyKeyDeserializer(config, type, deserializer);
+// return applyOurModifiersNonBean(deserializer);
+// }
+
public <T extends ObjectMapper> T apply(T mapper) {
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(this);
diff --git a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/ObjectReferencingSerialization.java b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/ObjectReferencingSerialization.java
index 6ad6990..8c0bfa6 100644
--- a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/ObjectReferencingSerialization.java
+++ b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/ObjectReferencingSerialization.java
@@ -178,7 +178,7 @@
// and if the expected type is overly strict, return the original object.
// if the token buffer is used too much we might have lost the alias reference and still end up with a string,
// but so long as this deserializer is preferred which it normally is, losing the alias reference is okay.
- Maybe resultCoerced = ((Maybe) TypeCoercions.tryCoerce(result, TypeToken.of(expected)));
+ Maybe resultCoerced = TypeCoercions.tryCoerce(result, TypeToken.of(expected));
if (resultCoerced.isAbsent()) {
// not uncommon when we are trying to deserialize in a few different ways, or if we are using a string deserializer because the json input is a string;
// however it can be useful, to explain why some things aren't coercing via BeanWithTypeUtils.convert.
diff --git a/core/src/test/java/org/apache/brooklyn/core/resolve/jackson/PerverseSerializationTest.java b/core/src/test/java/org/apache/brooklyn/core/resolve/jackson/PerverseSerializationTest.java
index 2c54369..cce46fa 100644
--- a/core/src/test/java/org/apache/brooklyn/core/resolve/jackson/PerverseSerializationTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/resolve/jackson/PerverseSerializationTest.java
@@ -21,6 +21,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Iterables;
import javassist.tools.rmi.Sample;
+import org.apache.brooklyn.core.resolve.jackson.BrooklynJacksonSerializationUtils.ConfigurableBeanDeserializerModifier;
import org.apache.brooklyn.core.resolve.jackson.BrooklynRegisteredTypeJacksonSerializationTest.SampleBean;
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.util.collections.MutableList;
@@ -96,9 +97,11 @@
// here, if it's an object we are reading, we get a list containing a map
x = deser(LIST_MAP_TYPE_SAMPLE_BEAN, Object.class);
- Asserts.assertInstanceOf(Iterables.getOnlyElement((List) x), Map.class);
+ // previously we did not deserialize types inside maps, but now we do
+ Class<?> expectedDeserializedTypedMapNested = ConfigurableBeanDeserializerModifier.DEFAULT_APPLY_ONLY_TO_BEAN_DESERIALIZERS ? Map.class : SampleBean.class;
+ Asserts.assertInstanceOf(Iterables.getOnlyElement((List) x), expectedDeserializedTypedMapNested);
- // but if we know it's a list then we are more aggressive about reading type information
+ // if we know it's a list then we are more aggressive about reading type information, we do not need our bean deserializer to modify non-bean deserializers
x = deser(LIST_MAP_TYPE_SAMPLE_BEAN, List.class);
Asserts.assertInstanceOf(Iterables.getOnlyElement((List) x), SampleBean.class);
}
@@ -122,8 +125,11 @@
Asserts.assertInstanceOf(Iterables.getOnlyElement(o.ls), SampleBean.class);
Asserts.assertInstanceOf(Iterables.getOnlyElement(o.lo), SampleBean.class);
Asserts.assertInstanceOf(Iterables.getOnlyElement(o.lr), SampleBean.class);
- Asserts.assertInstanceOf(Iterables.getOnlyElement((List)o.o), Map.class);
- Asserts.assertInstanceOf(Iterables.getOnlyElement((List)Iterables.getOnlyElement(o.lo2)), Map.class);
+
+ // previously we did not deserialize types inside maps, but now we do
+ Class<?> expectedDeserializedTypedMapNested = ConfigurableBeanDeserializerModifier.DEFAULT_APPLY_ONLY_TO_BEAN_DESERIALIZERS ? Map.class : SampleBean.class;
+ Asserts.assertInstanceOf(Iterables.getOnlyElement((List)o.o), expectedDeserializedTypedMapNested);
+ Asserts.assertInstanceOf(Iterables.getOnlyElement((List)Iterables.getOnlyElement(o.lo2)), expectedDeserializedTypedMapNested);
}
/* arrays - no surprises - always read and written as lists, and coerced if context requires */