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);
}