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


The following commit(s) were added to refs/heads/master by this push:
     new f1a7df85da more cautious convertShallow object references
f1a7df85da is described below

commit f1a7df85da1dc128ea5c118dcbc836219ad5a1ba
Author: Alex Heneveld <[email protected]>
AuthorDate: Tue May 30 00:09:20 2023 +0100

    more cautious convertShallow object references
    
    fix errors where in some weird cases the reference could be accepted as the 
value (as a string),
    due to failure of jackson to preserve aliases through token buffer
---
 .../jackson/ObjectReferencingSerialization.java    | 56 +++++++++++++---------
 .../BrooklynMiscJacksonSerializationTest.java      | 41 +++++++++++++---
 2 files changed, 69 insertions(+), 28 deletions(-)

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 219e110178..e703f3a868 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
@@ -41,8 +41,11 @@ import com.google.common.reflect.TypeToken;
 import java.io.IOException;
 import java.io.StringReader;
 import java.lang.reflect.Type;
+import java.util.function.Supplier;
+
 import 
org.apache.brooklyn.core.resolve.jackson.BrooklynJacksonSerializationUtils.ConfigurableBeanDeserializerModifier;
 import org.apache.brooklyn.util.core.flags.TypeCoercions;
+import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.text.Identifiers;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -54,6 +57,8 @@ public class ObjectReferencingSerialization {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(ObjectReferencingSerialization.class);
 
+    public static final String PREFIX = 
"_"+ObjectReferencingSerialization.class.getSimpleName()+"_";
+
     final BiMap<String,Object> backingMap = HashBiMap.create();
     ObjectMapper mapper = null;
 
@@ -99,7 +104,7 @@ public class ObjectReferencingSerialization {
         public void serialize(Object value, JsonGenerator gen, 
SerializerProvider serializers) throws IOException {
             String id = backingMap.inverse().get(value);
             if (id==null) {
-                id = Identifiers.makeRandomId(12);
+                id = PREFIX+Identifiers.makeRandomId(12);
                 backingMap.put(id, value);
             }
 
@@ -146,30 +151,37 @@ public class ObjectReferencingSerialization {
         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) {
-                Type expected = _valueType!=null ? _valueType : _valueClass;
-
-                // not sure if we ever need to look at contextual type
-                Type expected2 = ctxt.getContextualType()==null ? null : 
ctxt.getContextualType();
-                if (expected2!=null) {
-                    if (expected==null) {
-                        expected = expected2;
-                    } else {
-                        // we have two expectations
-                        LOG.debug("Object reference deserialization ambiguity, 
expected "+expected+" and "+expected2);
+                // we need to exclude the case where a literal 'v' was 
expected;
+                // TokenBuffer does not preserve YAMLParser.isCurrentAlias() 
so we can't use that;
+                // instead we use a prefix to ensure our references are almost 
certainly unique
+                if (v.startsWith(PREFIX)) {
+                    Type expected = _valueType != null ? _valueType : 
_valueClass;
+
+                    // not sure if we ever need to look at contextual type
+                    Type expected2 = ctxt.getContextualType() == null ? null : 
ctxt.getContextualType();
+                    if (expected2 != null) {
+                        if (expected == null) {
+                            expected = expected2;
+                        } else {
+                            // we have two expectations
+                            LOG.debug("Object reference deserialization 
ambiguity, expected " + expected + " and " + expected2);
+                        }
+                    }
+                    if (expected == null) {
+                        expected = Object.class;
                     }
-                }
-                if (expected==null) {
-                    expected = Object.class;
-                }
 
-                Object result = backingMap.get(v);
-                if (result!=null) {
-                    // Because of how 
UntypedObjectDeserializer.deserializeWithType treats strings
-                    // we cannot trust string being expected (if we could, we 
could exclude backing map lookup!)
-                    if (!String.class.equals(expected)) {
-                        result = TypeCoercions.coerce(result, 
TypeToken.of(expected));
+                    Object result = backingMap.get(v);
+                    if (result != null) {
+                        // previously we excluded a string from being 
expected, since we wouldn't have converted that to a reference;
+                        // however there are contexts where a string is 
expected but other types are _accepted_ so just trust it if we come here,
+                        // 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.
+                        return ((Maybe)TypeCoercions.tryCoerce(result, 
TypeToken.of(expected))).or(result);
+                    } else {
+                        LOG.debug("Odd - what looks like a reference was 
received but not found: "+v);
                     }
-                    return result;
                 }
             }
             return nestedDeserialize.apply(jp, ctxt);
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 c25b3730b8..4e74a964ee 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
@@ -27,12 +27,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.databind.util.TokenBuffer;
 import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
-
-import java.io.IOException;
-import java.time.Instant;
-import java.util.*;
-import java.util.function.BiConsumer;
-
 import com.google.common.reflect.TypeToken;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
@@ -40,6 +34,7 @@ import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.core.flags.TypeCoercions;
+import org.apache.brooklyn.util.core.task.DeferredSupplier;
 import org.apache.brooklyn.util.core.units.ByteSize;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.javalang.JavaClassNames;
@@ -52,6 +47,11 @@ import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import java.io.IOException;
+import java.time.Instant;
+import java.util.*;
+import java.util.function.BiConsumer;
+
 public class BrooklynMiscJacksonSerializationTest implements MapperTestFixture 
{
 
     private static final Logger LOG = 
LoggerFactory.getLogger(BrooklynMiscJacksonSerializationTest.class);
@@ -126,6 +126,35 @@ public class BrooklynMiscJacksonSerializationTest 
implements MapperTestFixture {
         Asserts.assertTrue(f1==f2, "different instances for "+f1+" and "+f2);
     }
 
+    public static class ObjRefAcceptingStringSource {
+        String src;
+        ObjRefAcceptingStringSource bar;
+
+        public ObjRefAcceptingStringSource() {}
+        public ObjRefAcceptingStringSource(String src) {
+            this.src = src;
+        }
+    }
+
+    @Test
+    public void testConvertShallowDoesntAcceptIdsForStrings() throws 
JsonProcessingException {
+        ManagementContext mgmt = LocalManagementContextForTests.newInstance();
+
+        Asserts.assertFailsWith(() -> {
+                ObjRefAcceptingStringSource b = 
BeanWithTypeUtils.convertShallow(mgmt, MutableMap.of("bar", new 
DeferredSupplier() {
+                    @Override
+                    public Object get() {
+                        return "xxx";
+                    }
+                }), TypeToken.of(ObjRefAcceptingStringSource.class), false, 
null, true);
+                // ensure the ID of a serialized object isn't treated as a 
reference
+                Asserts.fail("Should have failed, instead got: " + b.bar.src);
+                return b;
+            }, e -> Asserts.expectedFailureContains(e, "Problem deserializing 
property 'bar'"));
+
+        ObjRefAcceptingStringSource b = BeanWithTypeUtils.convertShallow(mgmt, 
MutableMap.of("bar", new ObjRefAcceptingStringSource("good")), 
TypeToken.of(ObjRefAcceptingStringSource.class), false, null, true);
+        Asserts.assertEquals(b.bar.src, "good");
+    }
 
     @Test
     public void testDurationCustomSerialization() throws Exception {

Reply via email to