ReferenceWithError validateResolve()

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