io.helidon.integrations.micronaut.cdi.CdiExecutableMethod 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.
/*
* Copyright (c) 2020, 2021 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.annotation.Documented;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import io.micronaut.context.AbstractExecutableMethod;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.type.Argument;
import io.micronaut.core.type.DefaultArgument;
import io.micronaut.inject.ExecutableMethod;
import io.micronaut.inject.annotation.DefaultAnnotationMetadata;
import jakarta.enterprise.inject.spi.Annotated;
import jakarta.enterprise.inject.spi.AnnotatedMethod;
import jakarta.enterprise.inject.spi.AnnotatedParameter;
// Executable method used to invoke Micronaut interceptor when we need to merge CDI and Micronaut
// annotation metadata, or when we do not have Micronaut annotation metadata
@SuppressWarnings("rawtypes")
final class CdiExecutableMethod extends AbstractExecutableMethod {
private final AnnotationMetadata annotationMetadata;
private CdiExecutableMethod(AnnotationMetadata methodAnnotationMetadata,
Class> declaringType,
String methodName,
Argument> genericReturnType,
Argument... arguments) {
super(declaringType, methodName, genericReturnType, arguments);
this.annotationMetadata = methodAnnotationMetadata;
}
/**
* Create from CDI method and Micronaut executable method.
* Merges together annotations from both worlds.
*
* @param cdiMethod CDI annotated method
* @param micronautMethod Micronaut executable method
* @return a new combined executable method
*/
static ExecutableMethod, ?> create(AnnotatedMethod> cdiMethod, ExecutableMethod, ?> micronautMethod) {
return create(cdiMethod,
annotationMetadata(cdiMethod, micronautMethod.getAnnotationMetadata()),
arguments(cdiMethod.getParameters(), micronautMethod.getArguments()));
}
/**
* Create from CDI method.
*
* @param cdiMethod CDI annotated method
* @return Micronaut executable method
*/
static ExecutableMethod, ?> create(AnnotatedMethod> cdiMethod) {
return create(cdiMethod,
annotationMetadata(cdiMethod),
arguments(cdiMethod.getParameters()));
}
private static ExecutableMethod, ?> create(AnnotatedMethod> cdiMethod,
AnnotationMetadata annotationMetadata,
Argument... arguments) {
Class> declaringType = cdiMethod.getDeclaringType().getJavaClass();
Argument> returnType = Argument.of(cdiMethod.getJavaMember().getReturnType());
return new CdiExecutableMethod(annotationMetadata,
declaringType,
cdiMethod.getJavaMember().getName(),
returnType,
arguments);
}
@Override
protected AnnotationMetadata resolveAnnotationMetadata() {
return annotationMetadata;
}
@Override
protected Object invokeInternal(Object instance, Object[] arguments) {
throw new MicronautCdiException("invokeInternal should not be called in interceptor");
}
@SuppressWarnings("rawtypes")
private static Argument[] arguments(List extends AnnotatedParameter>> parameters, Argument[] miParameters) {
Argument[] result = new Argument[parameters.size()];
for (int i = 0; i < parameters.size(); i++) {
AnnotatedParameter> parameter = parameters.get(i);
result[i] = toArgument(parameter, miParameters[i]);
}
return result;
}
@SuppressWarnings("rawtypes")
private static Argument[] arguments(List extends AnnotatedParameter>> parameters) {
Argument[] result = new Argument[parameters.size()];
for (int i = 0; i < parameters.size(); i++) {
AnnotatedParameter> parameter = parameters.get(i);
result[i] = toArgument(parameter);
}
return result;
}
private static Argument> toArgument(AnnotatedParameter> parameter, Argument argument) {
Parameter javaParameter = parameter.getJavaParameter();
return new DefaultArgument(javaParameter.getParameterizedType(),
javaParameter.getName(),
annotationMetadata(parameter, argument.getAnnotationMetadata()));
}
private static Argument> toArgument(AnnotatedParameter> parameter) {
Parameter javaParameter = parameter.getJavaParameter();
return new DefaultArgument(javaParameter.getType(),
javaParameter.getName(),
annotationMetadata(parameter));
}
@SuppressWarnings("unchecked")
private static AnnotationMetadata annotationMetadata(Annotated annotated, AnnotationMetadata miAnnotated) {
Map, Annotation> annotations = new HashMap<>();
Set miAnnotationNames = miAnnotated.getAnnotationNames();
// add micronaut annotations
for (String miAnnotationName : miAnnotationNames) {
try {
Annotation annotation = miAnnotated.synthesize((Class extends Annotation>) Class.forName(miAnnotationName));
if (annotation != null) {
// annotation is present
annotations.put(annotation.annotationType(), annotation);
}
} catch (Throwable ignored) {
// this annotation is not on the classpath, we can ignore it
}
}
// then overwrite with CDI annotations (more significant for us)
annotated.getAnnotations()
.forEach(it -> annotations.put(it.annotationType(), it));
return annotationMetadata(annotations);
}
private static AnnotationMetadata annotationMetadata(Annotated annotated) {
Map, Annotation> annotations = new HashMap<>();
annotated.getAnnotations()
.forEach(it -> annotations.put(it.annotationType(), it));
return annotationMetadata(annotations);
}
private static AnnotationMetadata annotationMetadata(Map, Annotation> annotations) {
Map, Annotation> stereotypes = new HashMap<>();
Map> byStereotype = new HashMap<>();
Map> miAnnotations = new HashMap<>();
processAnnotations(annotations,
miAnnotations,
stereotypes,
byStereotype);
Map> miStereotypes = new HashMap<>();
for (var entry : stereotypes.entrySet()) {
miStereotypes.put(entry.getKey().getName(), annotationValues(entry.getValue()));
}
Map> byStereotypeWithList = new HashMap<>();
byStereotype
.forEach((stereotype, set) -> byStereotypeWithList.put(stereotype, new ArrayList<>(set)));
return new DefaultAnnotationMetadata(miAnnotations,
miStereotypes,
miStereotypes,
miAnnotations,
byStereotypeWithList);
}
private static void processAnnotations(Map, Annotation> declaredAnnotations,
Map> miAnnotations,
Map, Annotation> stereotypeMap,
Map> annotationsByStereotype) {
for (var entry : declaredAnnotations.entrySet()) {
if (stereotypeMap.containsKey(Repeatable.class)) {
// I need to ignore this (used only when there is just one repetition)
// this gets processed as part of the Repeatable container
continue;
}
String annotName = entry.getKey().getName();
miAnnotations.put(annotName, annotationValues(entry.getValue()));
Set stereotypes = getStereotypes(entry.getValue());
for (Annotation stereotype : stereotypes) {
if (Target.class.equals(stereotype.annotationType())) {
continue;
}
if (Documented.class.equals(stereotype.annotationType())) {
continue;
}
if (Retention.class.equals(stereotype.annotationType())) {
continue;
}
stereotypeMap.put(stereotype.annotationType(), stereotype);
String stereotypeName = stereotype.annotationType().getName();
annotationsByStereotype.computeIfAbsent(stereotypeName, it -> new HashSet<>())
.add(annotName);
}
}
}
private static Set getStereotypes(Annotation annotation) {
return Set.of(annotation.annotationType().getAnnotations());
}
private static Map annotationValues(Annotation annotation) {
Class extends Annotation> aClass = annotation.annotationType();
Method[] declaredMethods = aClass.getDeclaredMethods();
Map result = new HashMap<>();
for (Method declaredMethod : declaredMethods) {
int mod = declaredMethod.getModifiers();
if (Modifier.isPublic(mod) && !Modifier.isStatic(mod)) {
try {
Object value = declaredMethod.invoke(annotation);
// if the value is default, do not include
Object defaultValue = declaredMethod.getDefaultValue();
if (value.equals(defaultValue)) {
continue;
}
if (value.getClass().isArray() && Annotation.class.isAssignableFrom(value.getClass().getComponentType())) {
// this is a repeatable annotation
int len = Array.getLength(value);
List values = new ArrayList<>(len);
for (int i = 0; i < len; i++) {
Annotation element = (Annotation) Array.get(value, i);
values.add(new AnnotationValue(element.annotationType().getName(), annotationValues(element)));
}
result.put(declaredMethod.getName(), values);
} else {
result.put(declaredMethod.getName(), value);
}
} catch (Exception e) {
throw new MicronautCdiException(e);
}
}
}
return result;
}
}