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 5a75cfbd3da9d073eac370766b1d328ce2538e18
Author: Alex Heneveld <[email protected]>
AuthorDate: Thu Jul 28 13:10:03 2022 +0100

    better type inference in jackson deserialization
    
    always using @type if specified, in case where expected type is vague but 
actual type has a type field.
    also suppress nulls when WrappedValue is not collapsed.
---
 .../resolve/jackson/AsPropertyIfAmbiguous.java     | 108 ++++++++++++++++++---
 .../core/resolve/jackson/WrappedValue.java         |   2 +
 2 files changed, 96 insertions(+), 14 deletions(-)

diff --git 
a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/AsPropertyIfAmbiguous.java
 
b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/AsPropertyIfAmbiguous.java
index 84d84a393b..fc9b041a80 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/AsPropertyIfAmbiguous.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/AsPropertyIfAmbiguous.java
@@ -37,6 +37,7 @@ import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.javalang.Reflections;
 import org.apache.brooklyn.util.text.Strings;
+import org.apache.commons.lang3.tuple.Pair;
 
 import java.io.IOException;
 import java.lang.reflect.AccessibleObject;
@@ -136,27 +137,106 @@ public class AsPropertyIfAmbiguous {
 
         @Override
         public Object deserializeTypedFromObject(JsonParser p, 
DeserializationContext ctxt) throws IOException {
+            AsPropertyButNotIfFieldConflictTypeDeserializer target = this;
+            boolean mustUseConflictingTypePrefix = false;
+
             if (_idResolver instanceof HasBaseType) {
-                if (// object has field with same name as the type property - 
don't treat the type property supplied here as the type
-                        
presentAndNotJsonIgnored(Reflections.findFieldMaybe(((HasBaseType) 
_idResolver).getBaseType().getRawClass(), _typePropertyName))
-                                || // or object has getter with same name as 
the type property
-                                
presentAndNotJsonIgnored(Reflections.findMethodMaybe(((HasBaseType) 
_idResolver).getBaseType().getRawClass(), "get" + 
Strings.toInitialCapOnly(_typePropertyName)))
-                ) {
-                    // look for an '@' type
-                    return 
cloneWithNewTypePropertyName(CONFLICTING_TYPE_NAME_PROPERTY_PREFIX + 
_typePropertyName).deserializeTypedFromObject(p, ctxt);
-
-                    // previous behaviour:
+                JavaType baseType = ((HasBaseType) _idResolver).getBaseType();
+                if (baseType != null ) {
+                    if (// object has field with same name as the type 
property - don't treat the type property supplied here as the type
+                            
presentAndNotJsonIgnored(Reflections.findFieldMaybe(baseType.getRawClass(), 
_typePropertyName))
+                                    || // or object has getter with same name 
as the type property
+                                    
presentAndNotJsonIgnored(Reflections.findMethodMaybe(baseType.getRawClass(), 
"get" + Strings.toInitialCapOnly(_typePropertyName)))
+                    ) {
+                        // look for an '@' type
+//                    return 
cloneWithNewTypePropertyName(CONFLICTING_TYPE_NAME_PROPERTY_PREFIX + 
_typePropertyName).deserializeTypedFromObject(p, ctxt);
+                        // now we always look for @ first, in case the type is 
not known but that field is present; but if we know 'type' is a bean field, 
don't allow it to be used
+                        mustUseConflictingTypePrefix = true;
+
+                        // previous behaviour:
 //                    // don't read type id, just deserialize
 //                    JsonDeserializer<Object> deser = 
ctxt.findContextualValueDeserializer(((HasBaseType)_idResolver).getBaseType(), 
_property);
 //                    return deser.deserialize(p, ctxt);
+                    }
+
+                    // ? - MapperFeature.USE_BASE_TYPE_AS_DEFAULT_IMPL should 
do this
+                    if (!Objects.equals(_defaultImpl, baseType)) {
+                        // note: needed even if baseType is object
+                        target = new 
AsPropertyButNotIfFieldConflictTypeDeserializer(_baseType, _idResolver, 
_typePropertyName, _typeIdVisible, ((HasBaseType) _idResolver).getBaseType(), 
_inclusion);
+                    }
+                }
+            }
+            return target.deserializeTypedFromObjectSuper(p, ctxt, 
mustUseConflictingTypePrefix);
+        }
+
+        // copied from super class
+        private Object deserializeTypedFromObjectSuper(JsonParser p, 
DeserializationContext ctxt, boolean mustUseConflictingTypePrefix) throws 
IOException {
+//            return super.deserializeTypedFromObject(p, ctxt);
+
+            // 02-Aug-2013, tatu: May need to use native type ids
+            Object typeId;
+            if (p.canReadTypeId()) {
+                typeId = p.getTypeId();
+                if (typeId != null) {
+                    return _deserializeWithNativeTypeId(p, ctxt, typeId);
+                }
+            }
+
+            // but first, sanity check to ensure we have START_OBJECT or 
FIELD_NAME
+            JsonToken t = p.currentToken();
+            if (t == JsonToken.START_OBJECT) {
+                t = p.nextToken();
+            } else if (/*t == JsonToken.START_ARRAY ||*/ t != 
JsonToken.FIELD_NAME) {
+                /* This is most likely due to the fact that not all Java types 
are
+                 * serialized as JSON Objects; so if "as-property" inclusion 
is requested,
+                 * serialization of things like Lists must be instead handled 
as if
+                 * "as-wrapper-array" was requested.
+                 * But this can also be due to some custom handling: so, if 
"defaultImpl"
+                 * is defined, it will be asked to handle this case.
+                 */
+                return _deserializeTypedUsingDefaultImpl(p, ctxt, null, 
_msgForMissingId);
+            }
+            // Ok, let's try to find the property. But first, need token 
buffer...
+            TokenBuffer tb = null;
+            boolean ignoreCase = 
ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
+
+            // changed to look for conflicting property first
+            Pair<String, TokenBuffer> typeIdFindResult = 
findTypeIdOrUnambiguous(p, ctxt, t, tb, ignoreCase, 
mustUseConflictingTypePrefix);
+            tb = typeIdFindResult.getRight();
+//            if (typeIdFindResult.getLeft()==null && 
!mustUseConflictingTypePrefix) {
+//                p = tb.asParserOnFirstToken();
+//                tb = null;
+//                typeIdFindResult = findTypeId(p, ctxt, t, tb, ignoreCase, 
_typePropertyName);
+//            }
+//            tb = typeIdFindResult.getRight();
+
+            if (typeIdFindResult.getLeft()!=null) return 
_deserializeTypedForId(p, ctxt, tb, typeIdFindResult.getLeft());
+            else return _deserializeTypedUsingDefaultImpl(p, ctxt, tb, 
_msgForMissingId);
+        }
+
+        private Pair<String,TokenBuffer> findTypeIdOrUnambiguous(JsonParser p, 
DeserializationContext ctxt, JsonToken t, TokenBuffer tb, boolean ignoreCase, 
boolean mustUseConflictingTypePrefix) throws IOException {
+            String typeUnambiguous = CONFLICTING_TYPE_NAME_PROPERTY_PREFIX + 
_typePropertyName;
+
+            for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
+                final String name = p.currentName();
+                p.nextToken(); // to point to the value
+
+                // require unambiguous one is first if present; otherwise 
maintaining the parser and token buffer in the desired states is too hard
+                if (name.equals(typeUnambiguous) || 
(!mustUseConflictingTypePrefix && (name.equals(_typePropertyName)
+                        || (ignoreCase && 
name.equalsIgnoreCase(_typePropertyName))))) { // gotcha!
+                    // 09-Sep-2021, tatu: [databind#3271]: Avoid converting 
null to "null"
+                    String typeId = p.getValueAsString();
+                    if (typeId != null) {
+                        return Pair.of(typeId, tb);
+                    }
                 }
-                // ? - MapperFeature.USE_BASE_TYPE_AS_DEFAULT_IMPL should do 
this
-                if (!Objects.equals(_defaultImpl, ((HasBaseType) 
_idResolver).getBaseType())) {
-                    AsPropertyButNotIfFieldConflictTypeDeserializer delegate = 
new AsPropertyButNotIfFieldConflictTypeDeserializer(_baseType, _idResolver, 
_typePropertyName, _typeIdVisible, ((HasBaseType) _idResolver).getBaseType(), 
_inclusion);
-                    return delegate.deserializeTypedFromObject(p, ctxt);
+                if (tb == null) {
+                    tb = ctxt.bufferForInputBuffering(p);
                 }
+                tb.writeFieldName(name);
+                tb.copyCurrentStructure(p);
             }
-            return super.deserializeTypedFromObject(p, ctxt);
+            return Pair.of(null, tb);
         }
 
         private boolean presentAndNotJsonIgnored(Maybe<? extends 
AccessibleObject> fm) {
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/WrappedValue.java 
b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/WrappedValue.java
index 2da334be65..79b986615c 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/WrappedValue.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/resolve/jackson/WrappedValue.java
@@ -18,6 +18,7 @@
  */
 package org.apache.brooklyn.core.resolve.jackson;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
 import com.google.common.base.Preconditions;
 import java.util.Objects;
 import java.util.function.Supplier;
@@ -37,6 +38,7 @@ import org.apache.brooklyn.util.core.task.DeferredSupplier;
  *
  * When deserialized, this will parse Brooklyn DSL expressions.
  */
+@JsonInclude(JsonInclude.Include.NON_NULL)
 public class WrappedValue<T> implements Supplier<T>, 
com.google.common.base.Supplier<T>, DeferredSupplier<T> {
     final static WrappedValue<?> NULL_WRAPPED_VALUE = new WrappedValue<>(null, 
false);
     final T value;

Reply via email to