You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2021/08/14 01:54:54 UTC
[brooklyn-server] 02/06: serialization and deserialization that
puts/gets objects to a backing map and writes references
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 <al...@cloudsoftcorp.com>
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);
+ }
+
}