io.micronaut.context.DefaultApplicationContext Maven / Gradle / Ivy
 The newest version!
        
        /*
 * Copyright 2017-2020 original authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.micronaut.context;
import io.micronaut.context.annotation.BootstrapContextCompatible;
import io.micronaut.context.annotation.ConfigurationReader;
import io.micronaut.context.annotation.EachBean;
import io.micronaut.context.annotation.EachProperty;
import io.micronaut.context.env.BootstrapPropertySourceLocator;
import io.micronaut.context.env.CachedEnvironment;
import io.micronaut.context.env.ConfigurationPath;
import io.micronaut.context.env.DefaultEnvironment;
import io.micronaut.context.env.Environment;
import io.micronaut.context.env.PropertySource;
import io.micronaut.context.exceptions.ConfigurationException;
import io.micronaut.context.exceptions.DependencyInjectionException;
import io.micronaut.context.exceptions.NoSuchBeanException;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.convert.ArgumentConversionContext;
import io.micronaut.core.convert.DefaultMutableConversionService;
import io.micronaut.core.convert.MutableConversionService;
import io.micronaut.core.convert.TypeConverter;
import io.micronaut.core.convert.TypeConverterRegistrar;
import io.micronaut.core.io.scan.ClassPathResourceLoader;
import io.micronaut.core.naming.Named;
import io.micronaut.core.naming.conventions.StringConvention;
import io.micronaut.core.type.Argument;
import io.micronaut.core.util.ArgumentUtils;
import io.micronaut.core.util.StringUtils;
import io.micronaut.core.value.PropertyCatalog;
import io.micronaut.inject.BeanConfiguration;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.BeanDefinitionReference;
import io.micronaut.inject.qualifiers.EachBeanQualifier;
import io.micronaut.inject.qualifiers.PrimaryQualifier;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static io.micronaut.core.util.StringUtils.EMPTY_STRING_ARRAY;
/**
 * Creates a default implementation of the {@link ApplicationContext} interface.
 *
 * @author Graeme Rocher
 * @since 1.0
 */
public class DefaultApplicationContext extends DefaultBeanContext implements ConfigurableApplicationContext {
    private final ClassPathResourceLoader resourceLoader;
    private final ApplicationContextConfiguration configuration;
    private Environment environment;
    /**
     * True if the {@link #environment} was created by this context,
     * false if the {@link #environment} was provided by {@link #setEnvironment(Environment)}
     */
    private boolean environmentManaged;
    /**
     * Construct a new ApplicationContext for the given environment name.
     *
     * @param environmentNames The environment names
     */
    public DefaultApplicationContext(@NonNull String... environmentNames) {
        this(ClassPathResourceLoader.defaultLoader(DefaultApplicationContext.class.getClassLoader()), environmentNames);
    }
    /**
     * Construct a new ApplicationContext for the given environment name and classloader.
     *
     * @param environmentNames The environment names
     * @param resourceLoader   The class loader
     */
    public DefaultApplicationContext(@NonNull ClassPathResourceLoader resourceLoader, @NonNull String... environmentNames) {
        this(new ApplicationContextConfiguration() {
            @NonNull
            @Override
            public ClassLoader getClassLoader() {
                return getResourceLoader().getClassLoader();
            }
            @Override
            public @NonNull
            ClassPathResourceLoader getResourceLoader() {
                ArgumentUtils.requireNonNull("resourceLoader", resourceLoader);
                return resourceLoader;
            }
            @NonNull
            @Override
            public List getEnvironments() {
                ArgumentUtils.requireNonNull("environmentNames", environmentNames);
                return Arrays.asList(environmentNames);
            }
        });
    }
    /**
     * Construct a new ApplicationContext for the given environment name and classloader.
     *
     * @param configuration The application context configuration
     */
    public DefaultApplicationContext(@NonNull ApplicationContextConfiguration configuration) {
        super(configuration);
        ArgumentUtils.requireNonNull("configuration", configuration);
        this.configuration = configuration;
        this.resourceLoader = configuration.getResourceLoader();
    }
    @Override
    @Internal
    final void configureContextInternal() {
        super.configureContextInternal();
        configuration.getContextConfigurer().ifPresent(configurer ->
            configurer.configure(this)
        );
        if (traceMode != BeanResolutionTraceMode.NONE) {
            traceMode.getTracer().ifPresent(tracer -> {
                tracer.traceInitialConfiguration(
                    this.environment,
                    this.getBeanDefinitionReferences(),
                    this.getDisabledBeans()
                );
            });
        }
    }
    @Override
    public @NonNull
     ApplicationContext registerSingleton(@NonNull Class type, @NonNull T singleton, @Nullable Qualifier qualifier, boolean inject) {
        return (ApplicationContext) super.registerSingleton(type, singleton, qualifier, inject);
    }
    /**
     * Creates the default environment for the given environment name.
     *
     * @param configuration The application context configuration
     * @return The environment instance
     */
    protected @NonNull
    Environment createEnvironment(@NonNull ApplicationContextConfiguration configuration) {
        if (configuration.isEnableDefaultPropertySources()) {
            return new RuntimeConfiguredEnvironment(configuration, isBootstrapEnabled(configuration));
        } else {
            return new DefaultEnvironment(configuration);
        }
    }
    private boolean isBootstrapEnabled(ApplicationContextConfiguration configuration) {
        String bootstrapContextProp = System.getProperty(Environment.BOOTSTRAP_CONTEXT_PROPERTY);
        if (bootstrapContextProp != null) {
            return Boolean.parseBoolean(bootstrapContextProp);
        }
        Boolean configBootstrapEnabled = configuration.isBootstrapEnvironmentEnabled();
        return Objects.requireNonNullElseGet(configBootstrapEnabled, this::isBootstrapPropertySourceLocatorPresent);
    }
    private boolean isBootstrapPropertySourceLocatorPresent() {
        for (BeanDefinitionReference beanDefinitionReference : resolveBeanDefinitionReferences()) {
            if (BootstrapPropertySourceLocator.class.isAssignableFrom(beanDefinitionReference.getBeanType())) {
                return true;
            }
        }
        return false;
    }
    @Override
    @NonNull
    public MutableConversionService getConversionService() {
        return getEnvironment();
    }
    @Override
    @NonNull
    public Environment getEnvironment() {
        if (environment == null) {
            environment = createEnvironment(configuration);
            environmentManaged = true;
        }
        return environment;
    }
    /**
     * @param environment The environment
     */
    @Internal
    public void setEnvironment(Environment environment) {
        this.environment = environment;
        this.environmentManaged = false;
    }
    @Override
    @NonNull
    public synchronized ApplicationContext start() {
        startEnvironment();
        return (ApplicationContext) super.start();
    }
    @Override
    protected void registerConversionService() {
        // Conversion service is represented by the environment
    }
    @Override
    public synchronized @NonNull
    ApplicationContext stop() {
        ApplicationContext stop = (ApplicationContext) super.stop();
        if (environment != null && environmentManaged) {
            environment.stop();
        }
        environment = null;
        environmentManaged = false;
        return stop;
    }
    @Override
    public boolean containsProperty(String name) {
        return getEnvironment().containsProperty(name);
    }
    @Override
    public boolean containsProperties(String name) {
        return getEnvironment().containsProperties(name);
    }
    @Override
    public  Optional getProperty(String name, ArgumentConversionContext conversionContext) {
        return getEnvironment().getProperty(name, conversionContext);
    }
    @NonNull
    @Override
    public Collection getPropertyEntries(@NonNull String name) {
        return getEnvironment().getPropertyEntries(name);
    }
    @NonNull
    @Override
    public Collection getPropertyEntries(@NonNull String name, @NonNull PropertyCatalog propertyCatalog) {
        return getEnvironment().getPropertyEntries(name, propertyCatalog);
    }
    @NonNull
    @Override
    public Map getProperties(@Nullable String name, @Nullable StringConvention keyFormat) {
        return getEnvironment().getProperties(name, keyFormat);
    }
    @Override
    public Collection> getPropertyPathMatches(String pathPattern) {
        return getEnvironment().getPropertyPathMatches(pathPattern);
    }
    @Override
    protected synchronized void registerConfiguration(BeanConfiguration configuration) {
        if (getEnvironment().isActive(configuration)) {
            super.registerConfiguration(configuration);
        }
    }
    /**
     * Start the environment.
     */
    protected void startEnvironment() {
        Environment defaultEnvironment = getEnvironment();
        defaultEnvironment.start();
        RuntimeBeanDefinition.Builder extends Environment> definition;
        if (defaultEnvironment instanceof DefaultEnvironment de) {
            definition = RuntimeBeanDefinition
                .builder(DefaultEnvironment.class, () -> de);
        } else {
            definition = RuntimeBeanDefinition
                .builder(Environment.class, () -> defaultEnvironment);
        }
        //noinspection unchecked
        definition = definition
                        .singleton(true)
                        .qualifier(PrimaryQualifier.INSTANCE);
        //noinspection resource
        RuntimeBeanDefinition extends Environment> beanDefinition = definition.build();
        BeanDefinition extends Environment> existing = findBeanDefinition(beanDefinition.getBeanType()).orElse(null);
        if (existing instanceof RuntimeBeanDefinition> runtimeBeanDefinition) {
            removeBeanDefinition(runtimeBeanDefinition);
        }
        registerBeanDefinition(beanDefinition);
    }
    @Override
    protected void initializeContext(List contextScopeBeans, List processedBeans, List parallelBeans) {
        initializeTypeConverters(this);
        super.initializeContext(contextScopeBeans, processedBeans, parallelBeans);
    }
    @Override
    protected  NoSuchBeanException newNoSuchBeanException(@Nullable BeanResolutionContext resolutionContext,
                                                             Argument beanType,
                                                             @Nullable Qualifier qualifier,
                                                             @Nullable String message) {
        if (message == null) {
            StringBuilder stringBuilder = new StringBuilder();
            String ls = CachedEnvironment.getProperty("line.separator");
            appendBeanMissingMessage("", stringBuilder, ls, resolutionContext, beanType, qualifier);
            message = stringBuilder.toString();
        }
        return super.newNoSuchBeanException(resolutionContext, beanType, qualifier, message);
    }
    private  void appendBeanMissingMessage(String linePrefix,
                                              StringBuilder messageBuilder,
                                              String lineSeparator,
                                              @Nullable BeanResolutionContext resolutionContext,
                                              Argument beanType,
                                              @Nullable Qualifier qualifier) {
        if (linePrefix.length() == 10) {
            // Break possible cyclic dependencies
            return;
        }
        Collection> beanCandidates = findBeanCandidates(resolutionContext, beanType, false, definition -> !definition.isAbstract())
            .stream().sorted(Comparator.comparing(BeanDefinition::getName)).toList();
        for (BeanDefinition definition : beanCandidates) {
            if (definition != null && definition.isIterable()) {
                if (definition.hasDeclaredAnnotation(EachProperty.class)) {
                    appendEachPropertyMissingBeanMessage(linePrefix, messageBuilder, lineSeparator, resolutionContext, beanType, qualifier, definition);
                } else if (definition.hasDeclaredAnnotation(EachBean.class)) {
                    appendMissingEachBeanMessage(linePrefix, messageBuilder, lineSeparator, resolutionContext, beanType, qualifier, definition);
                }
            }
        }
        resolveDisabledBeanMessage(linePrefix, messageBuilder, lineSeparator, resolutionContext, beanType, qualifier);
    }
    private  void appendMissingEachBeanMessage(String linePrefix,
                                                  StringBuilder messageBuilder,
                                                  String lineSeparator,
                                                  @Nullable BeanResolutionContext resolutionContext,
                                                  Argument beanType,
                                                  @Nullable Qualifier qualifier,
                                                  BeanDefinition definition) {
        Class> dependentBean = definition.classValue(EachBean.class).orElseThrow();
        messageBuilder
            .append(lineSeparator)
            .append(linePrefix)
            .append("* [").append(beanType.getTypeString(true))
            .append("] requires the presence of a bean of type [")
            .append(dependentBean.getName())
            .append("]");
        if (qualifier != null) {
            messageBuilder.append(" with qualifier [").append(qualifier).append("]");
        }
        messageBuilder.append(".");
        appendBeanMissingMessage(linePrefix + " ",
            messageBuilder,
            lineSeparator,
            resolutionContext,
            Argument.of(dependentBean),
            (Qualifier) qualifier);
    }
    @Nullable
    private  BeanDefinition findAnyBeanDefinition(BeanResolutionContext resolutionContext, Argument beanType) {
        Collection> existing = super.findBeanCandidates(resolutionContext, beanType, false, definition -> !definition.isAbstract());
        BeanDefinition definition = null;
        if (existing.size() == 1) {
            definition = existing.iterator().next();
        }
        return definition;
    }
    private List> calculateEachPropertyChain(
        BeanResolutionContext resolutionContext,
        BeanDefinition> definition) {
        List> chain = new ArrayList<>();
        while (definition != null) {
            chain.add(definition);
            Class> declaringClass = definition.getBeanType().getDeclaringClass();
            if (declaringClass == null) {
                break;
            }
            BeanDefinition> dependent = findAnyBeanDefinition(resolutionContext, Argument.of(declaringClass));
            if (dependent == null || !dependent.isConfigurationProperties()) {
                break;
            }
            definition = dependent;
        }
        return chain;
    }
    @NonNull
    private  void appendEachPropertyMissingBeanMessage(String linePrefix,
                                                          StringBuilder messageBuilder,
                                                          String lineSeparator,
                                                          @Nullable BeanResolutionContext resolutionContext,
                                                          Argument beanType,
                                                          @Nullable Qualifier qualifier,
                                                          BeanDefinition> definition) {
        String prefix = calculatePrefix(resolutionContext, qualifier, definition);
        messageBuilder
            .append(lineSeparator)
            .append(linePrefix)
            .append("* [")
            .append(definition.asArgument().getTypeString(true));
        if (!definition.getBeanType().equals(beanType.getType())) {
            messageBuilder.append("] a candidate of [")
                .append(beanType.getTypeString(true));
        }
        messageBuilder.append("] is disabled because:")
            .append(lineSeparator);
        messageBuilder
            .append(linePrefix)
            .append(" - ")
            .append("Configuration requires entries under the prefix: [")
            .append(prefix)
            .append("]");
    }
    private  String calculatePrefix(BeanResolutionContext resolutionContext, Qualifier qualifier, BeanDefinition> definition) {
        List> chain = calculateEachPropertyChain(resolutionContext, definition);
        String prefix;
        if (chain.size() > 1) {
            Collections.reverse(chain);
            ConfigurationPath path = ConfigurationPath.of(chain.toArray(BeanDefinition[]::new));
            prefix = path.path();
        } else {
            prefix = definition.stringValue(EachProperty.class).orElse("");
            if (qualifier != null) {
                if (qualifier instanceof Named named) {
                    prefix += "." + named.getName();
                } else {
                    prefix += "." + "*";
                }
            } else {
                prefix += "." + definition.stringValue(EachProperty.class, "primary").orElse("*");
            }
        }
        return prefix;
    }
    @Override
    protected  void collectIterableBeans(@Nullable BeanResolutionContext resolutionContext,
                                            @NonNull BeanDefinition iterableBean,
                                            @NonNull Set> targetSet,
                                            @NonNull Argument beanType) {
        try (BeanResolutionContext rc = newResolutionContext(iterableBean, resolutionContext)) {
            if (iterableBean.hasDeclaredStereotype(EachProperty.class)) {
                transformEachPropertyBeanDefinition(rc, iterableBean, targetSet);
            } else if (iterableBean.hasDeclaredStereotype(EachBean.class)) {
                transformEachBeanBeanDefinition(rc, iterableBean, targetSet, beanType);
            } else {
                transformConfigurationReaderBeanDefinition(rc, iterableBean, targetSet);
            }
        }
    }
    private  void transformConfigurationReaderBeanDefinition(BeanResolutionContext resolutionContext,
                                                                BeanDefinition candidate,
                                                                Set> transformedCandidates) {
        try {
            final String prefix = candidate.stringValue(ConfigurationReader.class, ConfigurationReader.PREFIX).orElse(null);
            ConfigurationPath configurationPath = resolutionContext.getConfigurationPath();
            if (prefix != null) {
                if (configurationPath.isNotEmpty()) {
                    if (configurationPath.isWithin(prefix)) {
                        ConfigurationPath newPath = configurationPath.copy();
                        newPath.pushConfigurationReader(candidate);
                        newPath.traverseResolvableSegments(getEnvironment(), subPath ->
                            createAndAddDelegate(resolutionContext, candidate, transformedCandidates, subPath)
                        );
                    } else {
                        ConfigurationPath newPath = ConfigurationPath.newPath();
                        resolutionContext.setConfigurationPath(newPath);
                        try {
                            newPath.pushConfigurationReader(candidate);
                            newPath.traverseResolvableSegments(getEnvironment(), subPath ->
                                createAndAddDelegate(resolutionContext, candidate, transformedCandidates, subPath)
                            );
                        } finally {
                            resolutionContext.setConfigurationPath(configurationPath);
                        }
                    }
                } else if (prefix.indexOf('*') == -1) {
                    // doesn't require outer configuration
                    transformedCandidates.add(candidate);
                } else {
                    // if we have reached here we are likely in a nested a class being resolved directly from the context
                    // traverse and try to reformulate the path
                    @SuppressWarnings("unchecked")
                    Class                                   
              © 2015 - 2025 Weber Informatics LLC | Privacy Policy