This is an automated email from the ASF dual-hosted git repository. heneveld pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/brooklyn-server.git
commit c8e7bbbedd729932c137b8fe755bdb96acf16849 Author: Alex Heneveld <a...@cloudsoft.io> AuthorDate: Fri May 10 15:09:02 2024 +0100 apply type deserialization even into maps and lists by default --- .../brooklyn/spi/dsl/DslSerializationTest.java | 6 ++- .../core/resolve/jackson/BeanWithTypeUtils.java | 12 ++++- .../jackson/BrooklynJacksonSerializationUtils.java | 63 ++++++++++++++++++++++ .../jackson/ObjectReferencingSerialization.java | 2 +- .../resolve/jackson/PerverseSerializationTest.java | 14 +++-- 5 files changed, 89 insertions(+), 8 deletions(-) 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 f331bc588e..a71b79ccad 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.api.internal.AbstractBrooklynObjectSpec; 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 @@ public class DslSerializationTest extends AbstractYamlTest implements MapperTest 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 2c85657c93..5df6c020f9 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 @@ public class BeanWithTypeUtils { // 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 @@ public class BeanWithTypeUtils { // 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 4134a718c3..e957c28cfe 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.BeanDeserializerModifier; 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 class BrooklynJacksonSerializationUtils { } 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 class BrooklynJacksonSerializationUtils { 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 @@ public class BrooklynJacksonSerializationUtils { 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 6ad6990ec4..8c0bfa6a52 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 @@ public class ObjectReferencingSerialization { // 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 2c543698f6..cce46facaf 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 @@ package org.apache.brooklyn.core.resolve.jackson; 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 @@ public class PerverseSerializationTest implements MapperTestFixture { // 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 @@ public class PerverseSerializationTest implements MapperTestFixture { 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 */