All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.micronaut.context.BeanDefinitionDelegate Maven / Gradle / Ivy

There is a newer version: 4.7.5
Show 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.EachBean;
import io.micronaut.context.annotation.Parameter;
import io.micronaut.context.annotation.Primary;
import io.micronaut.context.env.ConfigurationPath;
import io.micronaut.context.exceptions.BeanInstantiationException;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.naming.NameResolver;
import io.micronaut.core.type.Argument;
import io.micronaut.core.util.ObjectUtils;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.DelegatingBeanDefinition;
import io.micronaut.inject.DisposableBeanDefinition;
import io.micronaut.inject.InitializingBeanDefinition;
import io.micronaut.inject.InjectableBeanDefinition;
import io.micronaut.inject.InjectionPoint;
import io.micronaut.inject.InstantiatableBeanDefinition;
import io.micronaut.inject.ParametrizedInstantiatableBeanDefinition;
import io.micronaut.inject.ValidatedBeanDefinition;
import io.micronaut.inject.qualifiers.PrimaryQualifier;
import io.micronaut.inject.qualifiers.Qualifiers;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * A delegate bean definition.
 *
 * @param  The bean type
 * @author Graeme Rocher
 * @since 1.0
 */
@Internal
sealed class BeanDefinitionDelegate extends AbstractBeanContextConditional
    implements DelegatingBeanDefinition, InstantiatableBeanDefinition,
    InjectableBeanDefinition, NameResolver {

    protected final BeanDefinition definition;
    @Nullable
    protected final Qualifier qualifier;

    @Nullable
    private final ConfigurationPath configurationPath;

    private final Map>> typeArgumentsMap;

    private BeanDefinitionDelegate(BeanDefinition definition, @Nullable Qualifier qualifier,
                                   @Nullable ConfigurationPath configurationPath,
                                   @NonNull Map>> typeArgumentsMap) {
        this.definition = definition;
        this.qualifier = qualifier;
        this.configurationPath = configurationPath;
        this.typeArgumentsMap = typeArgumentsMap;
    }

    public Optional getConfigurationPath() {
        return Optional.ofNullable(configurationPath);
    }

    @Override
    public List> getTypeArguments(String type) {
        List> arguments = typeArgumentsMap.get(type);
        return arguments == null ? getTarget().getTypeArguments(type) : arguments;
    }

    @Override
    public Qualifier getDeclaredQualifier() {
        return qualifier;
    }

    /**
     * @return the qualifier
     */
    @Nullable
    public Qualifier getQualifier() {
        return qualifier;
    }

    @Nullable
    @Override
    public Qualifier resolveDynamicQualifier() {
        return qualifier;
    }

    /**
     * @return The bean definition type
     */
    BeanDefinition getDelegate() {
        return definition;
    }

    @Override
    public boolean isProxy() {
        return definition.isProxy();
    }

    @Override
    public boolean isIterable() {
        return definition.isIterable();
    }

    @Override
    public boolean isPrimary() {
        return isQualifiedAsPrimary(qualifier) || definition.isPrimary() || isPrimaryThroughAttribute();
    }

    private boolean isQualifiedAsPrimary(Qualifier q) {
        return q != null && (q == PrimaryQualifier.INSTANCE || q.contains(PrimaryQualifier.INSTANCE));
    }

    private boolean isPrimaryThroughAttribute() {
        if (configurationPath != null) {
            return configurationPath.isPrimary();
        }
        return false;
    }

    @Override
    public T inject(BeanContext context, T bean) {
        if (definition instanceof InjectableBeanDefinition injectableBeanDefinition) {
            return injectableBeanDefinition.inject(context, bean);
        }
        return bean;
    }

    @Override
    public T inject(BeanResolutionContext resolutionContext, BeanContext context, T bean) {
        if (definition instanceof InjectableBeanDefinition injectableBeanDefinition) {
            return injectableBeanDefinition.inject(resolutionContext, context, bean);
        }
        return bean;
    }

    @Override
    public T instantiate(BeanResolutionContext resolutionContext, BeanContext context) throws BeanInstantiationException {
        ConfigurationPath oldPath = null;
        if (configurationPath != null) {
            oldPath = resolutionContext.getConfigurationPath();
            resolutionContext.setConfigurationPath(configurationPath);
        }
        try {
            if (this.definition instanceof ParametrizedInstantiatableBeanDefinition parametrizedInstantiatableBeanDefinition) {
                Argument[] requiredArguments = parametrizedInstantiatableBeanDefinition.getRequiredArguments();
                Map fulfilled = getParametersValues(resolutionContext, (DefaultBeanContext) context, definition, requiredArguments);
                return parametrizedInstantiatableBeanDefinition.instantiate(resolutionContext, context, fulfilled);
            }
            if (this.definition instanceof InstantiatableBeanDefinition instantiatableBeanDefinition) {
                return instantiatableBeanDefinition.instantiate(resolutionContext, context);
            }
            throw new IllegalStateException("Cannot construct a dynamically registered singleton");
        } finally {
            resolutionContext.setConfigurationPath(oldPath);
        }
    }

    @Nullable
    private Map getParametersValues(BeanResolutionContext resolutionContext,
                                                    DefaultBeanContext context,
                                                    BeanDefinition definition,
                                                    Argument[] requiredArguments) {
        if (requiredArguments.length == 0) {
            return Collections.emptyMap();
        }
        Map fulfilled = new LinkedHashMap<>(requiredArguments.length, 1);
        ConfigurationPath configurationPath = resolutionContext.getConfigurationPath();
        for (Argument argument : requiredArguments) {
            String argumentName = argument.getName();
            if (argument.isAnnotationPresent(Parameter.class)) {
                Class type = argument.getWrapperType();
                boolean isEnum = Enum.class.isAssignableFrom(type);
                if (CharSequence.class.isAssignableFrom(type) || isEnum) {
                    String simpleName = configurationPath.simpleName();
                    if (simpleName != null) {
                        Object value = isEnum ? context.getConversionService().convertRequired(simpleName, type) : simpleName;
                        fulfilled.put(argumentName, value);
                    } else {
                        String name = findName(resolutionContext.getCurrentQualifier());
                        if (name != null) {
                            Object value = isEnum ? context.getConversionService().convertRequired(name, type) : name;
                            fulfilled.put(argumentName, value);
                        }
                    }
                } else if (Number.class.isAssignableFrom(type)) {
                    fulfilled.put(argumentName, context.getConversionService().convertRequired(configurationPath.index(), argument));
                } else if (qualifier != null && hasDeclaredAnnotation(EachBean.class) && String.class.equals(type) && "name".equals(argumentName)) {
                    String name = findName(qualifier);
                    if (name != null) {
                        fulfilled.put(argumentName, name);
                    }
                } else {
                    if (argument.isProvider()) {
                        Argument pt = argument.getFirstTypeVariable().orElse(null);
                        if (pt != null) {
                            type = pt.getType();
                        }
                    }

                    try (BeanResolutionContext.Path ignored = resolutionContext.getPath().pushConstructorResolve(definition, argument)) {
                        if (type.equals(configurationPath.configurationType())) {
                            Object bean = context.findBean(resolutionContext, argument, configurationPath.beanQualifier()).orElse(null);
                            fulfilled.put(argumentName, bean);
                        } else {
                            ConfigurationPath old = resolutionContext.setConfigurationPath(null);// reset
                            try {
                                Qualifier q = qualifier != null ? (Qualifier) qualifier : configurationPath.beanQualifier();
                                Object bean = context.findBean(resolutionContext, argument, q).orElse(null);
                                fulfilled.put(argumentName, bean);
                            } finally {
                                resolutionContext.setConfigurationPath(old);
                            }
                        }
                    }
                }
            }
        }
        return fulfilled;
    }

    @Nullable
    private String findName(@Nullable Qualifier q) {
        if (q == null) {
            return null;
        }
        String name = Qualifiers.findName(q);
        if (name != null) {
            return name;
        }
        if (isQualifiedAsPrimary(q)) {
            return Primary.SIMPLE_NAME;
        }
        return null;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        BeanDefinitionDelegate that = (BeanDefinitionDelegate) o;
        return Objects.equals(definition, that.definition) &&
            Objects.equals(qualifier, that.qualifier);
    }

    @Override
    public int hashCode() {
        return ObjectUtils.hash(definition, qualifier);
    }

    /**
     * @return The bean definition type
     */
    @Override
    public BeanDefinition getTarget() {
        return definition;
    }

    @Override
    public Optional resolveName() {
        return Optional.ofNullable(findName(qualifier));
    }

    @Override
    public String toString() {
        return definition.toString();
    }

    /**
     * @param definition The bean definition type
     * @param         The type
     * @return The new bean definition
     */
    static  BeanDefinitionDelegate create(BeanDefinition definition) {
        return create(definition, null);
    }

    /**
     * @param definition The bean definition type
     * @param qualifier The bean qualifier
     * @param         The type
     * @return The new bean definition
     */
    static  BeanDefinitionDelegate create(BeanDefinition definition, Qualifier qualifier) {
        return create(definition, qualifier, null, Map.of());
    }

    /**
     * @param definition       The bean definition type
     * @param qualifier        The bean qualifier
     * @param typeArgumentsMap The type arguments
     * @param               The type
     * @return The new bean definition
     * @since 4.6
     */
    static  BeanDefinitionDelegate create(BeanDefinition definition,
                                                Qualifier qualifier,
                                                @NonNull Map>> typeArgumentsMap) {
        return create(definition, qualifier, null, typeArgumentsMap);
    }

    /**
     * @param definition       The bean definition type
     * @param qualifier        The bean qualifier
     * @param path             The configuration path.
     * @param typeArgumentsMap The type arguments
     * @param               The type
     * @return The new bean definition
     */
    static  BeanDefinitionDelegate create(BeanDefinition definition,
                                                Qualifier qualifier,
                                                ConfigurationPath path,
                                                @NonNull Map>> typeArgumentsMap) {
        if (definition instanceof InitializingBeanDefinition || definition instanceof DisposableBeanDefinition) {
            if (definition instanceof ValidatedBeanDefinition) {
                return new LifeCycleValidatingDelegate<>(definition, qualifier, path, typeArgumentsMap);
            } else {
                return new LifeCycleDelegate<>(definition, qualifier, path, typeArgumentsMap);
            }
        } else if (definition instanceof ValidatedBeanDefinition) {
            return new ValidatingDelegate<>(definition, qualifier, path, typeArgumentsMap);
        }
        return new BeanDefinitionDelegate<>(definition, qualifier, path, typeArgumentsMap);
    }

    /**
     * @param definition The bean definition type
     * @param qualifier The bean qualifier
     * @param path The configuration path.
     * @param         The type
     * @return The new bean definition
     */
    static  BeanDefinitionDelegate create(BeanDefinition definition,
                                                Qualifier qualifier,
                                                ConfigurationPath path) {
        return create(definition, qualifier, path, Map.of());
    }

    @Override
    @NonNull
    public String getName() {
        return definition.getName();
    }

    /**
     * @param  The bean definition type
     */
    sealed interface ProxyInitializingBeanDefinition extends DelegatingBeanDefinition, InitializingBeanDefinition {
        @Override
        default T initialize(BeanResolutionContext resolutionContext, BeanContext context, T bean) {
            BeanDefinition definition = getTarget();
            if (definition instanceof InitializingBeanDefinition) {
                return ((InitializingBeanDefinition) definition).initialize(resolutionContext, context, bean);
            }
            return bean;
        }
    }

    /**
     * @param  The bean definition type
     */
    sealed interface ProxyDisposableBeanDefinition extends DelegatingBeanDefinition, DisposableBeanDefinition {
        @Override
        default T dispose(BeanResolutionContext resolutionContext, BeanContext context, T bean) {
            BeanDefinition definition = getTarget();
            if (definition instanceof DisposableBeanDefinition) {
                return ((DisposableBeanDefinition) definition).dispose(resolutionContext, context, bean);
            }
            return bean;
        }
    }

    /**
     * @param  The bean definition type
     */
    sealed interface ProxyValidatingBeanDefinition extends DelegatingBeanDefinition, ValidatedBeanDefinition {
        @Override
        default T validate(BeanResolutionContext resolutionContext, T instance) {
            BeanDefinition definition = getTarget();
            if (definition instanceof ValidatedBeanDefinition) {
                return ((ValidatedBeanDefinition) definition).validate(resolutionContext, instance);
            }
            return instance;
        }

        @Override
        default  void validateBeanArgument(@NonNull BeanResolutionContext resolutionContext, @NonNull InjectionPoint injectionPoint, @NonNull Argument argument, int index, @Nullable V value) {
            BeanDefinition definition = getTarget();
            if (definition instanceof ValidatedBeanDefinition) {
                ((ValidatedBeanDefinition) definition).validateBeanArgument(
                        resolutionContext,
                        injectionPoint,
                        argument,
                        index,
                        value
                );
            }
        }
    }

    /**
     * @param  The bean definition type
     */
    private static final class LifeCycleDelegate extends BeanDefinitionDelegate implements ProxyInitializingBeanDefinition, ProxyDisposableBeanDefinition {
        private LifeCycleDelegate(BeanDefinition definition, Qualifier qualifier, ConfigurationPath path, @NonNull Map>> typeArgumentsMap) {
            super(definition, qualifier, path, typeArgumentsMap);
        }
    }

    /**
     * @param  The bean definition type
     */
    private static final class ValidatingDelegate extends BeanDefinitionDelegate implements ProxyValidatingBeanDefinition {
        private ValidatingDelegate(BeanDefinition definition, Qualifier qualifier, ConfigurationPath path, @NonNull Map>> typeArgumentsMap) {
            super(definition, qualifier, path, typeArgumentsMap);
        }
    }

    /**
     * @param  The bean definition type
     */
    private static final class LifeCycleValidatingDelegate extends BeanDefinitionDelegate implements ProxyValidatingBeanDefinition, ProxyInitializingBeanDefinition, ProxyDisposableBeanDefinition {
        private LifeCycleValidatingDelegate(BeanDefinition definition, Qualifier qualifier, ConfigurationPath path, @NonNull Map>> typeArgumentsMap) {
            super(definition, qualifier, path, typeArgumentsMap);
        }
    }
}