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 bc2583d402b8ac1e9cde1350904e6a73b0646e23 Author: Alex Heneveld <[email protected]> AuthorDate: Sat Aug 14 01:50:35 2021 +0100 serialization and deserialization that puts/gets objects to a backing map and writes references --- core/pom.xml | 4 + .../core/resolve/jackson/BeanWithTypeUtils.java | 19 ++- ...BrooklynRegisteredTypeJacksonSerialization.java | 10 ++ .../jackson/ObjectReferencingSerialization.java | 172 +++++++++++++++++++++ .../jackson/WrappedValuesSerialization.java | 11 +- .../brooklyn/util/core/flags/TypeCoercions.java | 8 +- .../BrooklynMiscJacksonSerializationTest.java | 165 +++++++++++++++++++- .../core/resolve/jackson/MapperTestFixture.java | 18 +++ .../util/core/internal/TypeCoercionsTest.java | 15 ++ 9 files changed, 414 insertions(+), 8 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index cd5a2c5..c03c64f 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -164,6 +164,10 @@ <artifactId>jackson-databind</artifactId> </dependency> <dependency> + <groupId>com.fasterxml.jackson.dataformat</groupId> + <artifactId>jackson-dataformat-yaml</artifactId> + </dependency> + <dependency> <groupId>com.fasterxml.jackson.jaxrs</groupId> <artifactId>jackson-jaxrs-json-provider</artifactId> </dependency> 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 7c31bf9..cf30094 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 @@ -18,9 +18,15 @@ */ package org.apache.brooklyn.core.resolve.jackson; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; import com.google.common.reflect.TypeToken; import java.util.*; import java.util.Map.Entry; @@ -50,8 +56,14 @@ public class BeanWithTypeUtils { /** also see {@link org.apache.brooklyn.util.core.json.BrooklynObjectsJsonMapper#newMapper(ManagementContext)} */ public static ObjectMapper newMapper(ManagementContext mgmt, boolean allowRegisteredTypes, BrooklynClassLoadingContext loader, boolean allowBasicJavaTypes) { - JsonMapper mapper = newSimpleMapper(); + return applyCommonMapperConfig(newSimpleMapper(), mgmt, allowRegisteredTypes, loader, allowBasicJavaTypes); + } + + public static ObjectMapper newYamlMapper(ManagementContext mgmt, boolean allowRegisteredTypes, BrooklynClassLoadingContext loader, boolean allowBasicJavaTypes) { + return applyCommonMapperConfig(newSimpleYamlMapper(), mgmt, allowRegisteredTypes, loader, allowBasicJavaTypes); + } + public static ObjectMapper applyCommonMapperConfig(ObjectMapper mapper, ManagementContext mgmt, boolean allowRegisteredTypes, BrooklynClassLoadingContext loader, boolean allowBasicJavaTypes) { BrooklynRegisteredTypeJacksonSerialization.apply(mapper, mgmt, allowRegisteredTypes, loader, allowBasicJavaTypes); WrappedValuesSerialization.apply(mapper, mgmt); mapper = new ConfigurableBeanDeserializerModifier() @@ -68,6 +80,11 @@ public class BeanWithTypeUtils { return JsonMapper.builder().build(); } + public static YAMLMapper newSimpleYamlMapper() { + // for use with json maps (no special type resolution, even the field "type" is ignored) + return YAMLMapper.builder().build(); + } + public static boolean isPureJson(Object o) { return isJsonAndOthers(o, oo -> false); } diff --git a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/BrooklynRegisteredTypeJacksonSerialization.java b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/BrooklynRegisteredTypeJacksonSerialization.java index e9724c8..b7dbad4 100644 --- a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/BrooklynRegisteredTypeJacksonSerialization.java +++ b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/BrooklynRegisteredTypeJacksonSerialization.java @@ -18,9 +18,12 @@ */ package org.apache.brooklyn.core.resolve.jackson; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo.As; +import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.*; @@ -122,6 +125,11 @@ public class BrooklynRegisteredTypeJacksonSerialization { return context.constructType(fromLoader.get()); } } + // TODO - this would be nice to support complex types +// if (type is present in a registered type) { +// get the bundle of registered type +// use that classloader to instantiate the type +// } if (allowPojoJavaTypes) { return super.typeFromId(context, id); } @@ -220,6 +228,8 @@ public class BrooklynRegisteredTypeJacksonSerialization { public static ObjectMapper apply(ObjectMapper mapper, ManagementContext mgmt, boolean allowRegisteredTypes, BrooklynClassLoadingContext loader, boolean allowPojoJavaTypes) { // the type resolver is extended to recognise brooklyn registered type names // and return a subtype of jackson JavaType + mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + mapper.setDefaultTyping(new BrtTypeResolverBuilder(mgmt, allowRegisteredTypes, loader, allowPojoJavaTypes)); SimpleModule module = new SimpleModule(); 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 new file mode 100644 index 0000000..27005c1 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/ObjectReferencingSerialization.java @@ -0,0 +1,172 @@ +package org.apache.brooklyn.core.resolve.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig; +import com.fasterxml.jackson.databind.jsontype.TypeSerializer; +import com.fasterxml.jackson.databind.ser.BeanSerializerFactory; +import com.fasterxml.jackson.databind.ser.SerializerFactory; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import java.io.IOException; +import java.util.Map; +import org.apache.brooklyn.core.resolve.jackson.BrooklynJacksonSerializationUtils.ConfigurableBeanDeserializerModifier; +import org.apache.brooklyn.util.text.Identifiers; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ObjectReferencingSerialization { + + private static final Logger LOG = LoggerFactory.getLogger(ObjectReferencingSerialization.class); + + public ObjectMapper useMapper(ObjectMapper mapper) { + BiMap<String,Object> backingMap = HashBiMap.create(); + mapper.setSerializerFactory(ObjectReferencingSerializerFactory.extending(mapper.getSerializerFactory(), new ObjectReferenceSerializer(backingMap))); + mapper = new ConfigurableBeanDeserializerModifier() + .addDeserializerWrapper( + d -> new ObjectReferencingJsonDeserializer(d, backingMap) + ).apply(mapper); + +// mapper.registerModule(new SimpleModule() +// .addSerializer(Object.class, new ObjectReferenceSerializer(backingMap)) +// .addDeserializer(Object.class, new ObjectReferenceDeserializer(backingMap)) +// ); + return mapper; + } + + + static class ObjectReference { + String id; + public ObjectReference() {} + public ObjectReference(String id) { this.id = id; } + } + + + static class ObjectReferenceSerializer extends StdSerializer<Object> { + private final BiMap<String, Object> backingMap; + + public ObjectReferenceSerializer(BiMap<String, Object> backingMap) { + super(Object.class); + this.backingMap = backingMap; + } + + @Override + public void serializeWithType(Object value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException { + serialize(value, gen, serializers); + } + + @Override + public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + String id = backingMap.inverse().get(value); + if (id==null) { + id = Identifiers.makeRandomId(12); + backingMap.put(id, value); + } + + gen.writeObjectRef(id); + +// serializers.findValueSerializer(Map.class, null).serializeWithType(MutableMap.of("@ref", id), gen, serializers, +// serializers.findTypeSerializer(serializers.constructType(Object.class))); + } + } + + static class ObjectReferenceDeserializer extends JsonDeserializer<Object> { + public ObjectReferenceDeserializer(Map<String, Object> backingMap) { + } + + @Override + public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { + return null; + } + } + + public static class ObjectReferencingSerializerFactory extends BeanSerializerFactory { + private final ObjectReferenceSerializer serializer; + + protected ObjectReferencingSerializerFactory(ObjectReferenceSerializer serializer, SerializerFactoryConfig config) { + super(config); + this.serializer = serializer; + } + + public static ObjectReferencingSerializerFactory extending(SerializerFactory factory, ObjectReferenceSerializer serializer) { + if (factory == null) return new ObjectReferencingSerializerFactory(serializer, null); + if (factory instanceof BeanSerializerFactory) return new ObjectReferencingSerializerFactory(serializer, ((BeanSerializerFactory) factory).getFactoryConfig() ); + throw new IllegalStateException("Cannot extend "+factory); + } + @Override + public ObjectReferencingSerializerFactory withConfig(SerializerFactoryConfig config) { + if (_factoryConfig == config) return this; + return new ObjectReferencingSerializerFactory(serializer, config); + } + + // --- our special behaviour + + @Override + protected JsonSerializer<Object> constructBeanOrAddOnSerializer(SerializerProvider prov, JavaType type, BeanDescription beanDesc, boolean staticTyping) throws JsonMappingException { + return serializer; + } + } + + static class ObjectReferencingJsonDeserializer extends JacksonBetterDelegatingDeserializer { + private final BiMap<String, Object> backingMap; + + public ObjectReferencingJsonDeserializer(JsonDeserializer<?> d, BiMap<String, Object> backingMap) { + super(d, (d2) -> new ObjectReferencingJsonDeserializer(d2, backingMap)); + this.backingMap = backingMap; + } + + @Override + protected Object deserializeWrapper(JsonParser jp, DeserializationContext ctxt, BiFunctionThrowsIoException<JsonParser, DeserializationContext, Object> nestedDeserialize) throws IOException { + String v = jp.getCurrentToken()== JsonToken.VALUE_STRING ? jp.getValueAsString() : null; + if (v!=null) { + Object result = backingMap.get(v); + if (result!=null) return result; + } + return nestedDeserialize.apply(jp, ctxt); + } + } +// +// public static class ObjectReferencingDeserializerFactory extends BeanDeserializerFactory { +// protected ObjectReferencingDeserializerFactory(DeserializerFactoryConfig config) { +// super(config); +// } +// +// public static ObjectReferencingDeserializerFactory extending(DeserializerFactory factory) { +// if (factory == null) return new ObjectReferencingDeserializerFactory(null); +// if (factory instanceof ObjectReferencingDeserializerFactory) return (ObjectReferencingDeserializerFactory) factory; +// if (factory instanceof BeanDeserializerFactory) return new ObjectReferencingDeserializerFactory( ((BeanDeserializerFactory) factory).getFactoryConfig() ); +// throw new IllegalStateException("Cannot extend "+factory); +// } +// @Override +// public ObjectReferencingDeserializerFactory withConfig(DeserializerFactoryConfig config) { +// if (_factoryConfig == config) return this; +// return new ObjectReferencingDeserializerFactory(config); +// } +// +// // --- our special behaviour +// +// +// @Override +// protected BeanDeserializerBuilder constructBeanDeserializerBuilder(DeserializationContext ctxt, BeanDescription beanDesc) { +// return new BeanDeserializerBuilder(beanDesc, ctxt) { +// { +// _objectIdReader = new ObjectIdReader() { +// +// }; +// } +// }; +// } +// } + +} diff --git a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/WrappedValuesSerialization.java b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/WrappedValuesSerialization.java index 49b42a8..d91ee70 100644 --- a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/WrappedValuesSerialization.java +++ b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/WrappedValuesSerialization.java @@ -199,16 +199,19 @@ public class WrappedValuesSerialization { if (factory.getClass() == BeanSerializerFactory.class) return new NullWrappedValueSuppressingBeanSerializerFactory( ((BeanSerializerFactory) factory).getFactoryConfig() ); throw new IllegalStateException("Cannot extend "+factory); } + @Override + public SerializerFactory withConfig(SerializerFactoryConfig config) { + if (_factoryConfig == config) return this; + return new NullWrappedValueSuppressingBeanSerializerFactory(config); + } + + // --- our special behaviour @Override protected PropertyBuilder constructPropertyBuilder(SerializationConfig config, BeanDescription beanDesc) { return new NullWrappedValueSuppressingPropertyBuilder(config, beanDesc); } - public SerializerFactory withConfig(SerializerFactoryConfig config) { - if (_factoryConfig == config) return this; - return new NullWrappedValueSuppressingBeanSerializerFactory(config); - } } public static <T> T ensureWrappedValuesInitialized(T x) { diff --git a/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java b/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java index b708869..f10e676 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java @@ -354,13 +354,19 @@ public class TypeCoercions { public void registerBeanWithTypeAdapter() { // if we want to do bean-with-type coercion ... probably nice to do if it doesn't already match registerAdapter("80-bean-with-type", new TryCoercer() { + @Override public <T> Maybe<T> tryCoerce(Object input, TypeToken<T> type) { if (!(input instanceof Map || input instanceof Collection || Boxing.isPrimitiveOrBoxedObject(input))) { return null; } if (BeanWithTypeUtils.isConversionRecommended(Maybe.of(input), type)) { - return BeanWithTypeUtils.tryConvertOrAbsentUsingContext(Maybe.of(input), type); + try { + Maybe<T> result = BeanWithTypeUtils.tryConvertOrAbsentUsingContext(Maybe.of(input), type); + return result; + } catch (Exception e) { + return Maybe.absent(e); + } } return null; } diff --git a/core/src/test/java/org/apache/brooklyn/core/resolve/jackson/BrooklynMiscJacksonSerializationTest.java b/core/src/test/java/org/apache/brooklyn/core/resolve/jackson/BrooklynMiscJacksonSerializationTest.java index a2297c7..1a609b9 100644 --- a/core/src/test/java/org/apache/brooklyn/core/resolve/jackson/BrooklynMiscJacksonSerializationTest.java +++ b/core/src/test/java/org/apache/brooklyn/core/resolve/jackson/BrooklynMiscJacksonSerializationTest.java @@ -18,16 +18,42 @@ */ package org.apache.brooklyn.core.resolve.jackson; +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.ObjectIdGenerator; +import com.fasterxml.jackson.annotation.ObjectIdGenerator.IdKey; +import com.fasterxml.jackson.annotation.ObjectIdGenerators.StringIdGenerator; +import com.fasterxml.jackson.annotation.ObjectIdResolver; +import com.fasterxml.jackson.databind.DeserializationConfig; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.KeyDeserializer; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationConfig; +import com.fasterxml.jackson.databind.cfg.HandlerInstantiator; +import com.fasterxml.jackson.databind.cfg.MapperConfig; +import com.fasterxml.jackson.databind.introspect.Annotated; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.jsontype.TypeIdResolver; +import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder; +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; +import com.google.common.reflect.TypeToken; +import java.util.Map; import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.javalang.JavaClassNames; import org.apache.brooklyn.util.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.testng.annotations.Test; public class BrooklynMiscJacksonSerializationTest implements MapperTestFixture { - public ObjectMapper mapper() { - ObjectMapper mapper = BeanWithTypeUtils.newMapper(null, false, null, true); + private static final Logger LOG = LoggerFactory.getLogger(BrooklynMiscJacksonSerializationTest.class); + + private ObjectMapper mapper; + public ObjectMapper mapper() { + if (mapper==null) mapper = BeanWithTypeUtils.newMapper(null, false, null, true); return mapper; } @@ -46,5 +72,140 @@ public class BrooklynMiscJacksonSerializationTest implements MapperTestFixture { Asserts.assertEquals(deser("\"1m\"", Duration.class), Duration.minutes(1)); } + static class ObjWithoutIdentityInfoAnnotation { + String foo; + + @Override + public String toString() { + return "Obj{" + + "foo='" + foo + '\'' + + "}@"+ System.identityHashCode(this); + } + } + + public static class AllBeansIdentityHandler extends HandlerInstantiator { + @Override + public JsonDeserializer<?> deserializerInstance(DeserializationConfig config, Annotated annotated, Class<?> deserClass) { + return null; + } + @Override + public KeyDeserializer keyDeserializerInstance(DeserializationConfig config, Annotated annotated, Class<?> keyDeserClass) { + return null; + } + @Override + public JsonSerializer<?> serializerInstance(SerializationConfig config, Annotated annotated, Class<?> serClass) { + return null; + } + @Override + public TypeResolverBuilder<?> typeResolverBuilderInstance(MapperConfig<?> config, Annotated annotated, Class<?> builderClass) { + return null; + } + @Override + public TypeIdResolver typeIdResolverInstance(MapperConfig<?> config, Annotated annotated, Class<?> resolverClass) { + return null; + } + + @Override + public ObjectIdGenerator<?> objectIdGeneratorInstance(MapperConfig<?> config, Annotated annotated, Class<?> implClass) { + return new StringIdGenerator(); + } + + @Override + public ObjectIdResolver resolverIdGeneratorInstance(MapperConfig<?> config, Annotated annotated, Class<?> implClass) { + return new MapBasedInstanceResolver(); + } + } + + static class MapBasedInstanceResolver implements ObjectIdResolver { + + Map<IdKey,Object> objectsById = MutableMap.of(); + + @Override + public void bindItem(IdKey id, Object pojo) { + objectsById.put(id, pojo); + } + + @Override + public Object resolveId(IdKey id) { + Object result = objectsById.get(id); + if (result!=null) return result; + // seems to happen for YAMLMapper, it doesn't call bindItem + LOG.warn("No object recorded for ID "+id+"; returning null during deserialization"); + return null; + } + + @Override + public ObjectIdResolver newForDeserialization(Object context) { + return this; + } + + @Override + public boolean canUseFor(ObjectIdResolver resolverType) { + return true; + } + } + + @JsonIdentityInfo(property="@object_id", generator=StringIdGenerator.class, resolver= MapBasedInstanceResolver.class) + static class ObjWithIdentityInfoAnnotation extends ObjWithoutIdentityInfoAnnotation {} + + @Test + public void testHowObjectIdAndReferences() throws Exception { + mapper = + BeanWithTypeUtils.applyCommonMapperConfig( + JsonMapper.builder().build() + + // YAML doesn't seem to call "bindItem" whereas JSON mapper does +// YAMLMapper.builder(). +//// configure(YAMLGenerator.Feature.USE_NATIVE_OBJECT_ID, true). +// build() + + , null, false, null, true) + ; +// mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + + ObjWithIdentityInfoAnnotation f1 = new ObjWithIdentityInfoAnnotation(); f1.foo = "1"; + ObjWithIdentityInfoAnnotation f2 = new ObjWithIdentityInfoAnnotation(); f2.foo = "2"; + String out = ser(MutableMap.of("a", f1, "b", f2, "c", f1)); + LOG.info("Result of "+ JavaClassNames.niceClassAndMethod()+": "+out); + + Map in = deser(out, +// Map.class + new TypeToken<Map<String, ObjWithIdentityInfoAnnotation>>() {} + ); + ObjWithIdentityInfoAnnotation a = (ObjWithIdentityInfoAnnotation)in.get("a"); + ObjWithIdentityInfoAnnotation b = (ObjWithIdentityInfoAnnotation)in.get("b"); + ObjWithIdentityInfoAnnotation c = (ObjWithIdentityInfoAnnotation)in.get("c"); + Asserts.assertTrue(a.foo.equals(c.foo), "expected same foo value for a and c - "+a+" != "+c); + Asserts.assertTrue(!b.foo.equals(c.foo), "expected different foo value for a and b"); + Asserts.assertTrue(a == c, "expected same instance for a and c - "+a+" != "+c); + Asserts.assertTrue(a != b, "expected different instance for a and b"); + } + + @Test + public void testCustomHandlerForReferences() throws Exception { + mapper = new ObjectReferencingSerialization().useMapper( + BeanWithTypeUtils.applyCommonMapperConfig( + YAMLMapper.builder() +// .handlerInstantiator(new AllBeansIdentityHandler()) + .build() + , null, false, null, true)); + + ObjWithoutIdentityInfoAnnotation f1 = new ObjWithoutIdentityInfoAnnotation(); f1.foo = "1"; + ObjWithoutIdentityInfoAnnotation f2 = new ObjWithoutIdentityInfoAnnotation(); f2.foo = "2"; + String out = ser(MutableMap.of("a", f1, "b", f2, "c", f1)); + LOG.info("Result of "+ JavaClassNames.niceClassAndMethod()+": "+out); + + Map in = deser(out, +// Map.class + new TypeToken<Map<String, ObjWithoutIdentityInfoAnnotation>>() {} + ); + ObjWithoutIdentityInfoAnnotation a = (ObjWithoutIdentityInfoAnnotation)in.get("a"); + ObjWithoutIdentityInfoAnnotation b = (ObjWithoutIdentityInfoAnnotation)in.get("b"); + ObjWithoutIdentityInfoAnnotation c = (ObjWithoutIdentityInfoAnnotation)in.get("c"); + Asserts.assertTrue(a.foo.equals(c.foo), "expected same foo value for a and c - "+a+" != "+c); + Asserts.assertTrue(!b.foo.equals(c.foo), "expected different foo value for a and b"); + Asserts.assertTrue(a == c, "expected same instance for a and c - "+a+" != "+c); + Asserts.assertTrue(a != b, "expected different instance for a and b"); + } } diff --git a/core/src/test/java/org/apache/brooklyn/core/resolve/jackson/MapperTestFixture.java b/core/src/test/java/org/apache/brooklyn/core/resolve/jackson/MapperTestFixture.java index c92ef37..387ee77 100644 --- a/core/src/test/java/org/apache/brooklyn/core/resolve/jackson/MapperTestFixture.java +++ b/core/src/test/java/org/apache/brooklyn/core/resolve/jackson/MapperTestFixture.java @@ -21,6 +21,7 @@ package org.apache.brooklyn.core.resolve.jackson; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Iterables; +import com.google.common.reflect.TypeToken; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.util.core.task.BasicExecutionContext; import org.apache.brooklyn.util.core.task.BasicExecutionManager; @@ -46,6 +47,14 @@ public interface MapperTestFixture { } } + default <T> String ser(T v, TypeToken<T> type) { + try { + return mapper().writerFor(BrooklynJacksonType.asTypeReference(type)).writeValueAsString(v); + } catch (JsonProcessingException e) { + throw Exceptions.propagate(e); + } + } + default <T> T deser(String v, Class<T> type) { try { return mapper().readValue(v, type); @@ -54,6 +63,15 @@ public interface MapperTestFixture { } } + + default <T> T deser(String v, TypeToken<T> type) { + try { + return mapper().readValue(v, BrooklynJacksonType.asTypeReference(type)); + } catch (JsonProcessingException e) { + throw Exceptions.propagate(e); + } + } + default <T> T deser(String v, RegisteredType type) { try { return mapper().readValue(v, BrooklynJacksonType.of(type)); diff --git a/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java b/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java index 5e07c1f..1a81ae9 100644 --- a/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java +++ b/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java @@ -18,6 +18,7 @@ */ package org.apache.brooklyn.util.core.internal; +import org.apache.brooklyn.test.Asserts; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; @@ -421,4 +422,18 @@ public class TypeCoercionsTest { public static class MyClazz implements MyInterface { } + + public static class ClassWithMap { + Map<String,Object> properties = MutableMap.of(); + } + + @Test + public void testObjectInMapCoercion() { + ClassWithMap r1 = TypeCoercions.coerce(MutableMap.of("properties", MutableMap.of("x", 1)), ClassWithMap.class); + Assert.assertEquals(r1.properties.get("x"), 1); + + r1 = TypeCoercions.coerce(MutableMap.of("properties", MutableMap.of("x", new MyClazz())), ClassWithMap.class); + Asserts.assertInstanceOf(r1.properties.get("x"), MyClazz.class); + } + }
