RuntimeBeanDefinition.java

/*
 * Copyright 2017-2022 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.Context;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.Experimental;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.reflect.GenericTypeUtils;
import io.micronaut.core.type.Argument;
import io.micronaut.inject.BeanContextConditional;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.BeanDefinitionReference;
import io.micronaut.inject.InstantiatableBeanDefinition;
import io.micronaut.inject.qualifiers.Qualifiers;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * Allow the construction for bean definitions programmatically that can be registered
 * via {@link BeanDefinitionRegistry} at runtime.
 *
 * <p>This differs from {@link BeanDefinitionRegistry#registerSingleton(Object)} in that
 * beans registered this way can be created lazily or not at all and participate
 * more completely in the life cycle of the {@link BeanContext} (for examples event listeners like {@link io.micronaut.context.event.BeanCreatedEventListener} will be fired).</p>
 *
 * <p>Note that it is generally not recommended to use this approach and build time bean computation is preferred. This type is
 * designed to support a few limited use cases where runtime bean registration is required.</p>
 *
 * @param <T> The bean type
 * @since 3.6.0
 * @author graemerocher
 * @see BeanDefinitionRegistry#registerBeanDefinition(RuntimeBeanDefinition)
 */
@Experimental
public interface RuntimeBeanDefinition<T> extends BeanDefinitionReference<T>, InstantiatableBeanDefinition<T>, BeanContextConditional {

    @Override
    @NonNull
    default AnnotationMetadata getAnnotationMetadata() {
        return AnnotationMetadata.EMPTY_METADATA;
    }

    @Override
    default boolean isEnabled(@NonNull BeanContext context, BeanResolutionContext resolutionContext) {
        return true;
    }

    @Override
    default List<Argument<?>> getTypeArguments(Class<?> type) {
        Class<T> beanType = getBeanType();
        if (type != null && type.isAssignableFrom(beanType)) {
            if (type.isInterface()) {
                return Arrays.stream(GenericTypeUtils.resolveInterfaceTypeArguments(beanType, type))
                    .map(Argument::of)
                    .collect(Collectors.toList());
            } else {
                return Arrays.stream(GenericTypeUtils.resolveSuperTypeGenericArguments(beanType, type))
                    .map(Argument::of)
                    .collect(Collectors.toList());
            }
        } else {
            return Collections.emptyList();
        }
    }

    @Override
    default boolean isContextScope() {
        return getAnnotationMetadata().hasDeclaredAnnotation(Context.class);
    }

    @Override
    default boolean isConfigurationProperties() {
        return BeanDefinitionReference.super.isConfigurationProperties();
    }

    @Override
    default BeanDefinition<T> load() {
        return this;
    }

    @Override
    default String getBeanDefinitionName() {
        return DefaultRuntimeBeanDefinition.generateBeanName(getBeanType());
    }

    @Override
    default BeanDefinition<T> load(BeanContext context) {
        return this;
    }

    @Override
    default boolean isPresent() {
        return true;
    }

    @Override
    default boolean isSingleton() {
        return BeanDefinitionReference.super.isSingleton();
    }

    /**
     * Creates a new effectively singleton bean definition that references the given bean.
     *
     * @param bean The bean
     * @return The {@link BeanDefinitionReference}
     * @param <B> The bean type
     * @since 3.6.0
     */
    @NonNull
    static <B> RuntimeBeanDefinition<B> of(@NonNull B bean) {
        Objects.requireNonNull(bean, "Bean cannot be null");
        @SuppressWarnings("unchecked") Class<B> t = (Class<B>) bean.getClass();
        return builder(t, () -> bean).singleton(true).build();
    }

    /**
     * Creates a new bean definition that will resolve the bean from the given supplier.
     *
     * <p>The bean is by default not singleton and the supplier will be invoked for each injection point.</p>
     * @param beanType The bean type
     * @param beanSupplier The bean supplier
     * @return The {@link BeanDefinitionReference}
     * @param <B> The bean type
     * @since 3.6.0
     */
    @NonNull
    static <B> RuntimeBeanDefinition<B> of(
        @NonNull Class<B> beanType,
        @NonNull Supplier<B> beanSupplier) {
        return builder(beanType, beanSupplier).build();
    }

    /**
     * A new builder for constructing and configuring runtime created beans.
     * @param bean The bean to use
     * @return The builder
     * @param <B> The bean type
     */
    @NonNull
    static <B> Builder<B> builder(@NonNull B bean) {
        Objects.requireNonNull(bean, "Bean cannot be null");
        @SuppressWarnings("unchecked")
        Argument<B> beanType = (Argument<B>) Argument.of(bean.getClass());
        return new DefaultRuntimeBeanDefinition.RuntimeBeanBuilder<>(
            beanType,
            () -> bean
        ).singleton(true);
    }

    /**
     * A new builder for constructing and configuring runtime created beans.
     * @param beanType The bean type
     * @param beanSupplier The bean supplier
     * @return The builder
     * @param <B> The bean type
     */
    @NonNull
    static <B> Builder<B> builder(@NonNull Class<B> beanType, @NonNull Supplier<B> beanSupplier) {
        return new DefaultRuntimeBeanDefinition.RuntimeBeanBuilder<>(
            Argument.of(beanType),
            beanSupplier
        );
    }

    /**
     * A new builder for constructing and configuring runtime created beans.
     * @param beanType The bean type
     * @param beanSupplier The bean supplier
     * @return The builder
     * @param <B> The bean type
     */
    @NonNull
    static <B> Builder<B> builder(@NonNull Argument<B> beanType, @NonNull Supplier<B> beanSupplier) {
        return new DefaultRuntimeBeanDefinition.RuntimeBeanBuilder<>(
            beanType,
            beanSupplier
        );
    }

    /**
     * A builder for constructing {@link RuntimeBeanDefinition} instances.
     * @param <B> The bean type
     */
    interface Builder<B> {
        /**
         * The qualifier to use.
         * @param qualifier The qualifier
         * @return This builder
         */
        @NonNull
        Builder<B> qualifier(@Nullable Qualifier<B> qualifier);

        /**
         * Adds this type as a bean replacement of the given type.
         * @param otherType The other type
         * @return This bean builder
         * @since 4.0.0
         */
        @NonNull
        Builder<B> replaces(@Nullable Class<? extends B> otherType);

        /**
         * The qualifier to use.
         * @param name The named qualifier to use.
         * @return This builder
         * @since 3.7.0
         */
        @NonNull
        default Builder<B> named(@Nullable String name) {
            if (name == null) {
                qualifier(null);
            } else {
                qualifier(Qualifiers.byName(name));
            }
            return this;
        }

        /**
         * The scope to use.
         * @param scope The scope
         * @return This builder
         */
        @NonNull
        Builder<B> scope(@Nullable Class<? extends Annotation> scope);

        /**
         * Is the bean singleton.
         * @param isSingleton True if it is singleton
         * @return This builder
         */
        @NonNull
        Builder<B> singleton(boolean isSingleton);

        /**
         * Limit the exposed types of this bean.
         * @param types The exposed types
         * @return This builder
         */
        @NonNull
        Builder<B> exposedTypes(Class<?>...types);

        /**
         * The type arguments for the type.
         * @param arguments The arguments
         * @return This builder
         */
        @NonNull
        Builder<B> typeArguments(Argument<?>... arguments);

        /**
         * The type arguments for an implemented type of this type.
         * @param implementedType The implemented type
         * @param arguments The arguments
         * @return This builder
         */
        @NonNull
        Builder<B> typeArguments(Class<?> implementedType, Argument<?>... arguments);

        /**
         * The annotation metadata for the bean.
         * @param annotationMetadata The annotation metadata
         * @return This builder
         */
        @NonNull
        Builder<B> annotationMetadata(@Nullable AnnotationMetadata annotationMetadata);

        /**
         * Builds the runtime bean.
         * @return The runtime bean
         */
        @NonNull
        RuntimeBeanDefinition<B> build();
    }
}