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

io.micronaut.inject.provider.AbstractProviderDefinition Maven / Gradle / Ivy

There is a newer version: 4.7.5
Show newest version
/*
 * Copyright 2017-2021 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.inject.provider;

import io.micronaut.context.BeanContext;
import io.micronaut.context.BeanResolutionContext;
import io.micronaut.context.Qualifier;
import io.micronaut.context.annotation.Any;
import io.micronaut.context.annotation.BootstrapContextCompatible;
import io.micronaut.context.exceptions.BeanInstantiationException;
import io.micronaut.context.exceptions.DisabledBeanException;
import io.micronaut.context.exceptions.NoSuchBeanException;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationUtil;
import io.micronaut.core.annotation.Indexes;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.naming.Named;
import io.micronaut.core.type.Argument;
import io.micronaut.core.type.ArgumentCoercible;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.BeanDefinitionReference;
import io.micronaut.inject.InjectionPoint;
import io.micronaut.inject.InstantiatableBeanDefinition;
import io.micronaut.inject.annotation.MutableAnnotationMetadata;
import io.micronaut.inject.qualifiers.AnyQualifier;
import io.micronaut.inject.qualifiers.Qualifiers;

import java.util.Collections;
import java.util.List;
import java.util.Optional;

/**
 * Abstract bean definition for other providers to extend from.
 *
 * @param  The generic type
 * @since 3.0.0
 * @author graemerocher
 */
public abstract class AbstractProviderDefinition implements InstantiatableBeanDefinition, BeanDefinitionReference {

    private static final Argument TYPE_VARIABLE = Argument.ofTypeVariable(Object.class, "T");
    private final AnnotationMetadata annotationMetadata;

    public AbstractProviderDefinition() {
        MutableAnnotationMetadata metadata = new MutableAnnotationMetadata();
        metadata.addDeclaredAnnotation(Any.class.getName(), Collections.emptyMap());
        metadata.addDeclaredStereotype(
                Collections.singletonList(Any.class.getName()),
                AnnotationUtil.QUALIFIER,
                Collections.emptyMap()
        );
        metadata.addDeclaredAnnotation(BootstrapContextCompatible.class.getName(), Collections.emptyMap());
        try {
            metadata.addDeclaredAnnotation(Indexes.class.getName(), Collections.singletonMap(AnnotationMetadata.VALUE_MEMBER, getBeanType()));
        } catch (NoClassDefFoundError e) {
            // ignore, might happen if jakarta.inject is not the classpath
        }
        annotationMetadata = metadata;
    }

    @Override
    public boolean isContainerType() {
        return false;
    }

    @Override
    public boolean isCandidateBean(Argument beanType) {
        return beanType.isAssignableFrom(getBeanType());
    }

    @Override
    public boolean isEnabled(@NonNull BeanContext context, @Nullable BeanResolutionContext resolutionContext) {
        return isPresent();
    }

    @Override
    public String getBeanDefinitionName() {
        return getClass().getName();
    }

    @Override
    public BeanDefinition load() {
        return this;
    }

    @Override
    public boolean isPresent() {
        return false;
    }

    /**
     * Builds a provider implementation.
     *
     * @param resolutionContext The resolution context
     * @param context The context
     * @param argument The argument
     * @param qualifier The qualifier
     * @param singleton Whether the bean is a singleton
     * @return The provider
     */
    protected abstract @NonNull T buildProvider(
            @NonNull BeanResolutionContext resolutionContext,
            @NonNull BeanContext context,
            @NonNull Argument argument,
            @Nullable Qualifier qualifier,
            boolean singleton);

    @Override
    public T instantiate(BeanResolutionContext resolutionContext, BeanContext context) throws BeanInstantiationException {
        final BeanResolutionContext.Segment segment = resolutionContext.getPath().currentSegment().orElse(null);
        if (segment != null) {
            final InjectionPoint injectionPoint = segment.getInjectionPoint();
            if (injectionPoint instanceof ArgumentCoercible argumentCoercible) {
                Argument injectionPointArgument = argumentCoercible.asArgument();
                Argument resolveArgument = injectionPointArgument;
                boolean isNullableProvider = injectionPointArgument.isNullable();
                boolean isOptionalProvider;
                if (resolveArgument.isOptional()) {
                    resolveArgument = resolveArgument.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
                    isOptionalProvider = true;
                } else {
                    isOptionalProvider = false;
                }
                @SuppressWarnings("unchecked") Argument argument =
                        (Argument) resolveArgument
                                .getFirstTypeVariable()
                                .orElse(null);
                if (argument != null) {
                    Qualifier qualifier = (Qualifier) resolutionContext.getCurrentQualifier();
                    if (qualifier == null && segment.getDeclaringType().isIterable()) {
                        final Object n = resolutionContext.getAttribute(Named.class.getName());
                        if (n != null) {
                            qualifier = Qualifiers.byName(n.toString());
                        }
                    }
                    if (isNullableProvider || isOptionalProvider || !(isAllowEmptyProviders(context) || qualifier instanceof AnyQualifier)) {
                        // Skip the contains bean for the providers that support an empty value and aren't nullable or optional
                        boolean hasBean = context.containsBean(argument, qualifier);
                        if (!hasBean) {
                            if (isNullableProvider) {
                                throw new DisabledBeanException("Nullable bean doesn't exist");
                            }
                            if (isOptionalProvider) {
                                return (T) Optional.empty();
                            }
                            throw new NoSuchBeanException(argument, qualifier);
                        }
                    }
                    return buildProvider(
                            resolutionContext,
                            context,
                            argument,
                            qualifier,
                            isSingleton()
                    );
                }
            }
        }
        throw new UnsupportedOperationException("Cannot inject provider for Object type");
    }

    /**
     * Return whether missing providers are allowed for this implementation. If {@code false} a {@link io.micronaut.context.exceptions.NoSuchBeanException} is thrown.
     * @param context The context
     * @return Returns {@code true} if missing providers are allowed
     */
    protected boolean isAllowEmptyProviders(BeanContext context) {
        return context.getContextConfiguration().isAllowEmptyProviders();
    }

    @Override
    public final boolean isAbstract() {
        return false;
    }

    @Override
    public final boolean isSingleton() {
        return false;
    }

    @Override
    public boolean isConfigurationProperties() {
        return false;
    }

    @Override
    @NonNull
    public final List> getTypeArguments(Class type) {
        if (type == getBeanType()) {
            return getTypeArguments();
        }
        return Collections.emptyList();
    }

    @Override
    @NonNull
    public final List> getTypeArguments() {
        return Collections.singletonList(TYPE_VARIABLE);
    }

    @Override
    public AnnotationMetadata getAnnotationMetadata() {
        return annotationMetadata;
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        return o != null && getClass() == o.getClass();
    }

    @Override
    public int hashCode() {
        return getClass().hashCode();
    }

}