io.helidon.integrations.micronaut.cdi.MicronautCdiExtension Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of helidon-integrations-micronaut-cdi Show documentation
Show all versions of helidon-integrations-micronaut-cdi Show documentation
Integration with Micronaut to be used in CDI environment.
The newest version!
/*
* Copyright (c) 2020, 2024 Oracle and/or its affiliates.
*
* 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
*
* http://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.helidon.integrations.micronaut.cdi;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import io.micronaut.aop.Around;
import io.micronaut.aop.MethodInterceptor;
import io.micronaut.context.ApplicationContext;
import io.micronaut.context.annotation.Type;
import io.micronaut.context.env.PropertySource;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.io.service.ServiceDefinition;
import io.micronaut.core.io.service.SoftServiceLoader;
import io.micronaut.inject.AdvisedBeanType;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.BeanDefinitionReference;
import io.micronaut.inject.ExecutableMethod;
import io.micronaut.inject.qualifiers.Qualifiers;
import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.BeforeDestroyed;
import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.context.Initialized;
import jakarta.enterprise.event.Observes;
import jakarta.enterprise.inject.spi.AfterBeanDiscovery;
import jakarta.enterprise.inject.spi.BeforeBeanDiscovery;
import jakarta.enterprise.inject.spi.Extension;
import jakarta.enterprise.inject.spi.ProcessAnnotatedType;
import jakarta.enterprise.inject.spi.configurator.BeanConfigurator;
import jakarta.inject.Qualifier;
import org.eclipse.microprofile.config.Config;
import static jakarta.interceptor.Interceptor.Priority.PLATFORM_AFTER;
import static jakarta.interceptor.Interceptor.Priority.PLATFORM_BEFORE;
/**
* Extension integrating CDI with Micronaut.
* This extensions adds Micronaut beans to be injectable into CDI beans (limited to {@link jakarta.inject.Singleton}
* scope), and adds support for invoking Micronaut interceptors.
*/
public class MicronautCdiExtension implements Extension {
private static final System.Logger LOGGER = System.getLogger(MicronautCdiExtension.class.getName());
private static final String MICRONAUT_BEAN_PREFIX = "micronaut-";
private final AtomicReference micronautContext = new AtomicReference<>();
private final Map> executableMethodCache = new HashMap<>();
private final Map methods = new HashMap<>();
// all bean definitions as seen by Micronaut
private final List beanDefinitions = new LinkedList<>();
// map of an actual class (user's source code) mapping to Micronaut bean definition
private final Map, List> mBeanToDefRef = new HashMap<>();
// Micronaut beans not yet processed by CDI
private final Map, List> unprocessedBeans = new HashMap<>();
/**
* Get the application context of Micronaut.
* This method can only be invoked once the server is started.
*
* @return Micronaut application context
* @throws java.lang.IllegalStateException when invoked when server (and hence the context) is not started
*/
public ApplicationContext context() {
ApplicationContext ctx = micronautContext.get();
if (ctx == null) {
throw new IllegalStateException(
"Micronaut application context can only be obtained after the ApplicationScoped is initialized");
}
return ctx;
}
MethodInterceptorMetadata getInterceptionMetadata(Method javaMethod) {
return methods.get(javaMethod);
}
/**
* Load all Micronaut bean definitions and register our interceptor binding, so we can
* execute Micronaut interceptors using a CDI interceptor.
*
* @param event CDI event
*/
void beforeBeanDiscovery(@Priority(PLATFORM_BEFORE) @Observes BeforeBeanDiscovery event) {
loadMicronautBeanDefinitions();
event.addAnnotatedType(MicronautInterceptor.class, "mcdi-MicronautInterceptor");
event.addInterceptorBinding(MicronautIntercepted.class);
}
/**
* Construct a list of Micronaut interceptors to execute on each CDI method.
* In case a Micronaut bean definition is available for the CDI bean (which should be for application, as
* the CDI annotation processor should be used, and it adds CDI beans as Micronaut beans), the information
* is combined from Micronaut and CDI bean definitions.
*
* @param event CDI event
*/
@SuppressWarnings("unchecked")
void processTypes(@Priority(PLATFORM_AFTER) @Observes ProcessAnnotatedType> event) {
Set> classInterceptors = new HashSet<>();
Map>> allMethodInterceptors = new HashMap<>();
List miBeans = unprocessedBeans.remove(event.getAnnotatedType().getJavaClass());
if (miBeans != null && miBeans.size() > 0) {
BeanDefinitionReference> miBean = findMicronautBeanDefinition(miBeans);
// add all interceptors that are seen based on Micronaut
findMicronautInterceptors(classInterceptors, allMethodInterceptors, miBean);
}
// find all annotations that have meta annotation Around and collect their Type list to add as interceptors
addMicronautInterceptors(classInterceptors, event.getAnnotatedType().getAnnotations());
// for each method, find the same (Around, collect Type), and add the interceptor binding for Micronaut interceptors
// CDI interceptors will be automatic
event.configureAnnotatedType()
.methods()
.forEach(method -> {
Method javaMethod = method.getAnnotated().getJavaMember();
Set> methodInterceptors = allMethodInterceptors.computeIfAbsent(javaMethod, it -> new HashSet<>());
methodInterceptors.addAll(classInterceptors);
addMicronautInterceptors(methodInterceptors, method.getAnnotated().getAnnotations());
if (!methodInterceptors.isEmpty()) {
// now I have a set of micronaut interceptors that are needed for this method
method.add(MicronautIntercepted.Literal.INSTANCE);
Set>> interceptors = new HashSet<>();
methodInterceptors.forEach(it -> interceptors.add((Class extends MethodInterceptor, ?>>) it));
methods.computeIfAbsent(javaMethod,
theMethod -> MethodInterceptorMetadata.create(
method.getAnnotated(),
executableMethodCache.get(theMethod)))
.addInterceptors(interceptors);
}
});
}
/**
* Add all (not yet added) Micronaut beans for injection as long as they are singletons.
*
* @param event CDI event
*/
@SuppressWarnings({"rawtypes", "unchecked"})
void afterBeanDiscovery(@Priority(PLATFORM_BEFORE) @Observes AfterBeanDiscovery event) {
event.addBean()
.addType(ApplicationContext.class)
.id(MICRONAUT_BEAN_PREFIX + "context")
.scope(ApplicationScoped.class)
.produceWith(instance -> micronautContext.get());
// add the remaining Micronaut beans
for (var entry : unprocessedBeans.entrySet()) {
Class> beanType = entry.getKey();
List beans = entry.getValue();
List extends BeanDefinitionReference>> refs = List.of();
if (beans.size() > 1) {
// first make sure these are singletons; if not, ignore
refs = beans.stream()
.map(MicronautBean::definitionRef)
.filter(it -> !it.getBeanType().getName().endsWith("$Intercepted"))
.filter(BeanDefinitionReference::isSingleton)
.collect(Collectors.toList());
}
// primary
event.addBean()
.addType(beanType)
.id(MICRONAUT_BEAN_PREFIX + beanType.getName())
// inject using dependent - manage scope by micronaut context
.scope(Dependent.class)
.produceWith(instance -> micronautContext.get().getBean(beanType));
if (refs.size() > 1) {
// we must care about qualifiers
for (var ref : refs) {
AnnotationMetadata annotationMetadata = ref.getAnnotationMetadata();
List> qualifiers = annotationMetadata
.getAnnotationTypesByStereotype(Qualifier.class);
Annotation[] synthesized = new Annotation[qualifiers.size()];
io.micronaut.context.Qualifier[] mq = new io.micronaut.context.Qualifier[qualifiers.size()];
for (int i = 0; i < qualifiers.size(); i++) {
Annotation annotation = annotationMetadata.synthesize(qualifiers.get(i));
synthesized[i] = annotation;
if (annotation != null) {
mq[i] = Qualifiers.byAnnotation(annotation);
}
}
io.micronaut.context.Qualifier composite = Qualifiers.byQualifiers(mq);
BeanConfigurator