in core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java [2022:2217]
ReferenceWithError<RegisteredType> validateResolve(RegisteredType typeToValidate, RegisteredTypeLoadingContext constraint, boolean allowChangingKind) {
Throwable inconsistentSuperTypesError=null, specError=null, beanError=null;
List<Throwable> guesserErrors = MutableList.of();
// collect supertype spec / most specific java
Set<Object> supers = typeToValidate.getSuperTypes();
BrooklynObjectType boType = null;
for (Object superI: supers) {
BrooklynObjectType boTypeI = null;
if (superI instanceof BrooklynObject) boTypeI = BrooklynObjectType.of((BrooklynObject)superI);
else if (superI instanceof Class) boTypeI = BrooklynObjectType.of((Class<?>)superI);
if (boTypeI!=null && boTypeI!=BrooklynObjectType.UNKNOWN) {
if (boType==null) boType = boTypeI;
else {
if (boTypeI!=boType) {
inconsistentSuperTypesError = new IllegalStateException("Inconsistent supertypes for "+typeToValidate+"; indicates "+boType+" and "+boTypeI);
}
}
}
}
Class<?> superJ = null;
for (Object superI: supers) {
if (superI instanceof Class) {
if (superJ==null) superJ = (Class<?>) superI;
else if (superJ.isAssignableFrom((Class<?>)superI)) superJ = (Class<?>) superI;
}
}
// could filter what we try based on kind; also could support itemType spec (generic) and
// more importantly bean to allow arbitrary types to be added to catalog
RegisteredType resultT = null;
boolean recheckNeededBecauseChangedOrUncertain = false;
Object resultO = null;
if (resultO==null && boType!=null) try {
// try spec instantiation if we know the BO Type (no point otherwise)
resultT = RegisteredTypes.copyResolved(RegisteredTypeKind.SPEC, typeToValidate, allowChangingKind);
try {
resultO = mgmt.getTypeRegistry().createSpec(resultT, constraint, boType.getSpecType());
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
specError = e;
resultT = null;
}
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
// ignore if we couldn't resolve as spec
}
if (resultO==null) try {
// try it as a bean
resultT = RegisteredTypes.copyResolved(RegisteredTypeKind.BEAN, typeToValidate, allowChangingKind);
try {
resultO = mgmt.getTypeRegistry().createBean(resultT, constraint, superJ);
if (resultO instanceof AbstractBrooklynObjectSpec) {
resultO = null;
throw new IllegalStateException("Dubious resolution of "+typeToValidate+" as "+resultO.getClass().getName()+" (spec not bean)");
}
if (isDubiousBeanType(resultO)) {
// 2022-05 previously we would always infer bean type, but now if it's a "dubious bean" you have the specify that it is a bean;
// if not, we mark it as dubious here, and we re-resolve later on.
// 2022-11 now we always try re-resolving later if it's a dubious bean type, so that we don't accept maps where caller has indicated a type,
// and that type might change (eg NestedRefsCatalogYamlTest)
if (typeToValidate.getKind()!=RegisteredTypeKind.BEAN) {
recheckNeededBecauseChangedOrUncertain = true;
resultO = null;
throw new IllegalStateException("Dubious resolution of " + typeToValidate + " as " + resultO.getClass().getName() + " " + resultO + "; if this is intended, specify kind as bean");
}
if (allowChangingKind) {
recheckNeededBecauseChangedOrUncertain = true;
resultO = null;
throw new IllegalStateException("Uncertain resolution of " + typeToValidate + " as " + resultO.getClass().getName() + " " + resultO + "; will try again");
}
}
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
beanError = e;
resultT = null;
}
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
// ignore if we couldn't resolve as spec
}
if (resultO==null && (constraint==null || constraint.getAlreadyEncounteredTypes().isEmpty())) try {
// try the messy but useful PlanInterpreterGuessingType
// (that is the only place where we will guess specs, so it handles most of our traditional catalog items in BOMs);
// but do not allow this to run if we are expanding a nested definition as that may fail to find recursive loops
// (the legacy routines this uses don't support that type of context)
String yaml = RegisteredTypes.getImplementationDataStringForSpec(typeToValidate);
log.trace("Validating {}: \n{}", typeToValidate, yaml);
CatalogBundle bundle = typeToValidate.getContainingBundle() != null ? CatalogItemDtoAbstract.parseLibraries(Arrays.asList(typeToValidate.getContainingBundle())).iterator().next() : null;
CatalogItemType itemType = null;
if (!allowChangingKind) {
itemType = boType!=null ? CatalogItemType.ofTargetClass(boType.getInterfaceType()) : null;
if (itemType==null && typeToValidate.getKind() == RegisteredTypeKind.BEAN) {
itemType = CatalogItemType.BEAN;
}
}
String format = typeToValidate.getPlan().getPlanFormat();
PlanInterpreterInferringType guesser = new PlanInterpreterInferringType(typeToValidate.getSymbolicName(), Iterables.getOnlyElement( Yamls.parseAll(yaml) ),
yaml, itemType, format, bundle, CatalogItemDtoAbstract.parseLibraries( typeToValidate.getLibraries() ), constraint, null);
guesser.resolve();
guesserErrors.addAll(guesser.getErrors());
if (guesser.isResolved()) {
// guesser resolved, but we couldn't create; did guesser change something?
CatalogItemType ciType = guesser.getCatalogItemType();
log.debug("Validated "+typeToValidate+" as "+ciType);
// try this even for templates; errors in them will be ignored by validator
// but might be interesting to someone calling resolve directly
// reset resultT and change things as needed based on guesser
resultT = typeToValidate;
if (boType==null) {
// guesser inferred a type
boType = BrooklynObjectType.of(ciType);
if (boType!=null && boType.getSpecType()!=null) {
supers = MutableSet.copyOf(supers);
supers.add(boType.getInterfaceType());
// didn't know type before, retry now that we know the type
resultT = RegisteredTypes.copyResolved(RegisteredTypeKind.SPEC, resultT, allowChangingKind);
RegisteredTypes.addSuperTypes(resultT, supers);
recheckNeededBecauseChangedOrUncertain = true;
}
}
if (!Objects.equal(guesser.getPlanYaml(), yaml)) {
RegisteredTypes.changePlanNotingEquivalent(resultT,
new BasicTypeImplementationPlan(typeToValidate.getPlan().getPlanFormat(), guesser.getPlanYaml()));
recheckNeededBecauseChangedOrUncertain = true;
}
if (recheckNeededBecauseChangedOrUncertain) {
log.debug("Re-resolving "+resultT+" following detection of change");
// try again with new plan or supertype info
return validateResolve(resultT, constraint, false);
} else if (Objects.equal(boType, BrooklynObjectType.of(ciType))) {
if (specError==null) {
throw new IllegalStateException("Guesser resolved but TypeRegistry couldn't create");
} else {
// do nothing; type was already known, prefer the spec error
}
} else {
throw new IllegalStateException("Guesser resolved as "+ciType+" but we expected "+boType);
}
} else {
throw new IllegalStateException("Guesser could not resolve "+typeToValidate);
}
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
guesserErrors.add(e);
resultT = null;
}
if (resultO!=null && resultT!=null) {
if (resultO instanceof BrooklynObject) {
// if it was a bean that points at a BO then switch it to a spec and try to re-validate
log.debug("Re-resolving "+resultT+" following detection of bean where spec was expected");
return validateResolve(RegisteredTypes.copyResolved(RegisteredTypeKind.SPEC, typeToValidate), constraint, false);
}
Class<?> resultS;
if (resultT.getKind() == RegisteredTypeKind.SPEC) {
resultS = ((AbstractBrooklynObjectSpec<?,?>)resultO).getType();
} else {
resultS = resultO.getClass();
}
RegisteredTypes.cacheActualJavaType(resultT, resultS);
Set<Object> newSupers = MutableSet.of();
// TODO collect registered type name supertypes, as strings
newSupers.add(resultS);
newSupers.addAll(supers);
newSupers.add(BrooklynObjectType.of(resultO.getClass()).getInterfaceType());
collectSupers(newSupers);
RegisteredTypes.addSuperTypes(resultT, newSupers);
log.trace("Resolved {} to java {}", resultT, resultS);
return ReferenceWithError.newInstanceWithoutError(resultT);
}
List<Throwable> errors = MutableList.<Throwable>of()
.appendIfNotNull(inconsistentSuperTypesError)
.appendAll(guesserErrors)
.appendIfNotNull(beanError)
.appendIfNotNull(specError);
log.trace("Failure resolving {} (informing caller): {}", resultT, errors);
return ReferenceWithError.newInstanceThrowingError(null, Exceptions.create("Could not resolve "+typeToValidate, errors));
}