private DiscoveredTypeAndCachedTokenBuffer findTypeIdOrUnambiguous()

in core/src/main/java/org/apache/brooklyn/core/resolve/jackson/AsPropertyIfAmbiguous.java [312:425]


        private DiscoveredTypeAndCachedTokenBuffer findTypeIdOrUnambiguous(JsonParser p, DeserializationContext ctxt, JsonToken t, TokenBuffer tb, boolean ignoreCase, boolean mustUseConflictingTypePrefix) throws IOException {
            String typeUnambiguous1 = CONFLICTING_TYPE_NAME_PROPERTY_TRANSFORM.apply(_typePropertyName);
            String typeUnambiguous2 = CONFLICTING_TYPE_NAME_PROPERTY_TRANSFORM_ALT.apply(_typePropertyName);

            int fieldsRead = 0;
            for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
                final String name = p.currentName();
                p.nextToken(); // to point to the value

                // unambiguous property should precede ambiguous property name in cases where property name is required
                // maintaining the parser and token buffer in the desired states to allow either anywhere is too hard
                boolean unambiguousName = name.equals(typeUnambiguous1) || name.equals(typeUnambiguous2);
                boolean ambiguousName = !unambiguousName && (!mustUseConflictingTypePrefix && (name.equals(_typePropertyName)
                        || (ignoreCase && name.equalsIgnoreCase(_typePropertyName))));
                if (ambiguousName || unambiguousName) { // gotcha!
                    // 09-Sep-2021, tatu: [databind#3271]: Avoid converting null to "null"
                    String typeId = p.getValueAsString();
                    if (typeId != null) {
                        boolean disallowed = false;
                        if (ambiguousName) {
                            JavaType tt = _idResolver.typeFromId(ctxt, typeId);
                            if (BrooklynObject.class.isAssignableFrom(tt.getRawClass()) && !Feed.class.isAssignableFrom(tt.getRawClass())) {
                                Boolean wantsSpec = null;
                                Boolean wantsBO = null;

                                JavaType baseType = null;
                                if (_idResolver instanceof HasBaseType) {
                                    baseType = ((HasBaseType) _idResolver).getBaseType();
                                    if (baseType != null) {
                                        wantsSpec = AbstractBrooklynObjectSpec.class.isAssignableFrom(baseType.getRawClass());
                                        wantsBO = BrooklynObject.class.isAssignableFrom(baseType.getRawClass());
                                    }
                                }

                                if (Boolean.TRUE.equals(wantsSpec)) {
                                    if (tt instanceof BrooklynJacksonType && BrooklynTypeRegistry.RegisteredTypeKind.SPEC.equals(((BrooklynJacksonType)tt).getRegisteredType().getKind())) {
                                        // if it's a spec registered type, we should load it, like normal
                                        // (no-op)
                                    } else {
                                        // if it's a class then we need to (1) infer the BOSpec type, then (2) re-read the type and set that as the field
                                        typeId = BrooklynObjectType.of(tt.getRawClass()).getSpecType().getName();
                                        tt = null;
                                        if (tb == null) {
                                            tb = ctxt.bufferForInputBuffering(p);
                                        }
                                        tb.writeFieldName(name);
                                        tb.copyCurrentStructure(p);
                                    }
                                } else if (Boolean.TRUE.equals(wantsBO)) {
                                    // if caller wants a BO we just read it normally, whether loading from an ID or created a (non-entity) instance such as a feed
                                    // no-op

                                } else if (!(tt instanceof BrooklynJacksonType) && BrooklynObjectType.of(tt.getRawClass()).getInterfaceType().equals(tt.getRawClass())) {
                                    // if caller hasn't explicitly asked for a BO, and a base BO type (eg Entity) is specified, probably we are loading from an ID
                                    // by specifying Entity class exactly (not a sub-type interface and not registered type) we allow re-instantiation using ID
                                    // no-op

                                } else {
                                    // caller hasn't explicitly asked for a BO, and it isn't a recognized pattern, so in this case we do not load the type;
                                    // will probably remain as a map, unless (type) is specified

                                    if (LOG.isTraceEnabled()) LOG.trace("Ambiguous request for "+baseType+" / "+tt+"; allowing");
                                    tt = null;
                                    disallowed = true;
                                }
                            }

                            if (tt!=null && hasTypePropertyNameAsField(tt)) {
                                // if there is a property called 'type' then caller should use @type.
                                disallowed = true;
                                // unless we need a type to conform to coercion.
                                if (_idResolver instanceof HasBaseType) {
                                    JavaType baseType = ((HasBaseType) _idResolver).getBaseType();
                                    if (baseType==null || baseType.getRawClass().equals(Object.class)) {
                                        if (fieldsRead==0) {
                                            // 'type' should be treated as a normal key when an object is expected, if type it references has a field 'type',
                                            // except if it is the first key in the definition, to facilitate messy places where we say 'type: xxx' as the definition
                                            if (warnedAmbiguousTypeProperty.add(typeId)) {
                                                LOG.warn("Ambiguous type property '" + _typePropertyName + "' used for '" + typeId + "' as first entry in definition; this looks like a type specification but this could also refer to the property; " +
                                                        "using for the former, but specification should have used '" + typeUnambiguous1 + "' as key earlier in the map, " +
                                                        "or if setting the field is intended put an explicit '" + typeUnambiguous1 + "' before it");
                                            }
                                            disallowed = false;
                                        } else {
                                            // leave disallowed
                                        }
                                    } else if (baseType.isMapLikeType()) {
                                        // leave disalloed
                                    } else {
                                        if (warnedAmbiguousTypeProperty.add(typeId)) {
                                            LOG.warn("Ambiguous type property '" + _typePropertyName + "' used for '" + typeId + "'; a type specification is needed to comply with expectations, but this could also refer to the property; " +
                                                    "using for the former, but specification should have used " + typeUnambiguous1 + " as key earlier in the map");
                                        }
                                        disallowed = false;
                                    }
                                }
                            }
                        }
                        if (!disallowed) {
                            return new DiscoveredTypeAndCachedTokenBuffer(typeId, tb, unambiguousName);
                        }
                    }
                }
                if (tb == null) {
                    tb = ctxt.bufferForInputBuffering(p);
                }
                tb.writeFieldName(name);
                tb.copyCurrentStructure(p);

                // advance so we no longer think we are at the beginning
                fieldsRead++;
            }
            return new DiscoveredTypeAndCachedTokenBuffer(null, tb, true);
        }