Skip to content

Commit

Permalink
ArC - support around invoke method declared on target class
Browse files Browse the repository at this point in the history
- and its superclasses
  • Loading branch information
mkouba committed Apr 18, 2023
1 parent 3e9b2e1 commit 4fdd98d
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ public class BeanInfo implements InjectionTargetInfo {

private final String targetPackageName;

private final List<MethodInfo> aroundInvokes;

BeanInfo(AnnotationTarget target, BeanDeployment beanDeployment, ScopeInfo scope, Set<Type> types,
Set<AnnotationInstance> qualifiers, List<Injection> injections, BeanInfo declaringBean, DisposerInfo disposer,
boolean alternative, List<StereotypeInfo> stereotypes, String name, boolean isDefaultBean, String targetPackageName,
Expand Down Expand Up @@ -143,6 +145,7 @@ public class BeanInfo implements InjectionTargetInfo {
this.lifecycleInterceptors = Collections.emptyMap();
this.forceApplicationClass = forceApplicationClass;
this.targetPackageName = targetPackageName;
this.aroundInvokes = isInterceptor() || isDecorator() ? List.of() : Beans.getAroundInvokes(implClazz, beanDeployment);
}

@Override
Expand Down Expand Up @@ -360,8 +363,10 @@ public boolean hasAroundInvokeInterceptors() {
}

boolean isSubclassRequired() {
return !interceptedMethods.isEmpty() || !decoratedMethods.isEmpty()
|| lifecycleInterceptors.containsKey(InterceptionType.PRE_DESTROY);
return !interceptedMethods.isEmpty()
|| !decoratedMethods.isEmpty()
|| lifecycleInterceptors.containsKey(InterceptionType.PRE_DESTROY)
|| !aroundInvokes.isEmpty();
}

/**
Expand Down Expand Up @@ -445,6 +450,18 @@ public List<DecoratorInfo> getBoundDecorators() {
return bound;
}

/**
*
* @return the list of around invoke interceptor methods declared in the hierarchy of a bean class
*/
List<MethodInfo> getAroundInvokes() {
return aroundInvokes;
}

boolean hasAroundInvokes() {
return !aroundInvokes.isEmpty();
}

public DisposerInfo getDisposer() {
return disposer;
}
Expand Down Expand Up @@ -603,7 +620,7 @@ private Map<MethodInfo, InterceptionInfo> initInterceptedMethods(List<Throwable>
}

Set<MethodInfo> finalMethods = Methods.addInterceptedMethodCandidates(beanDeployment, target.get().asClass(),
candidates, classLevelBindings, bytecodeTransformerConsumer, transformUnproxyableClasses);
candidates, classLevelBindings, bytecodeTransformerConsumer, transformUnproxyableClasses, aroundInvokes);
if (!finalMethods.isEmpty()) {
String additionalError = "";
if (finalMethods.stream().anyMatch(KotlinUtils::isNoninterceptableKotlinMethod)) {
Expand All @@ -620,7 +637,7 @@ private Map<MethodInfo, InterceptionInfo> initInterceptedMethods(List<Throwable>
for (Entry<MethodKey, Set<AnnotationInstance>> entry : candidates.entrySet()) {
List<InterceptorInfo> interceptors = beanDeployment.getInterceptorResolver()
.resolve(InterceptionType.AROUND_INVOKE, entry.getValue());
if (!interceptors.isEmpty()) {
if (!interceptors.isEmpty() || !aroundInvokes.isEmpty()) {
interceptedMethods.put(entry.getKey().method, new InterceptionInfo(interceptors, entry.getValue()));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ public List<Resource> generateResources(ReflectionRegistration reflectionRegistr
}

SubclassGenerator subclassGenerator = new SubclassGenerator(annotationLiterals, applicationClassPredicate,
generateSources, refReg, existingClasses);
generateSources, refReg, existingClasses, privateMembers);

ObserverGenerator observerGenerator = new ObserverGenerator(annotationLiterals, applicationClassPredicate,
privateMembers, generateSources, refReg, existingClasses, observerToGeneratedName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,29 @@ static List<MethodInfo> getCallbacks(ClassInfo beanClass, DotName annotation, In
return callbacks;
}

static List<MethodInfo> getAroundInvokes(ClassInfo beanClass, BeanDeployment deployment) {
List<MethodInfo> methods = new ArrayList<>();
AnnotationStore store = deployment.getAnnotationStore();
Set<String> processed = new HashSet<>();

ClassInfo aClass = beanClass;
while (aClass != null) {
for (MethodInfo method : aClass.methods()) {
if (Modifier.isStatic(method.flags())) {
continue;
}
if (store.hasAnnotation(method, DotNames.AROUND_INVOKE) && !processed.contains(method.name())) {
methods.add(method);
}
}
DotName superTypeName = aClass.superName();
aClass = superTypeName == null || DotNames.OBJECT.equals(superTypeName) ? null
: getClassByName(deployment.getBeanArchiveIndex(), superTypeName);
}
Collections.reverse(methods);
return methods.isEmpty() ? List.of() : List.copyOf(methods);
}

static void analyzeType(Type type, BeanDeployment beanDeployment) {
if (type.kind() == Type.Kind.PARAMETERIZED_TYPE) {
for (Type argument : type.asParameterizedType().arguments()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ public final class MethodDescriptors {
InvocationContexts.class,
"performAroundInvoke", Object.class, Object.class, Object[].class, InterceptedMethodMetadata.class);

public static final MethodDescriptor INVOCATION_CONTEXTS_PERFORM_TARGET_AROUND_INVOKE = MethodDescriptor.ofMethod(
InvocationContexts.class,
"performTargetAroundInvoke", Object.class, InvocationContext.class, List.class, BiFunction.class);

public static final MethodDescriptor INVOCATION_CONTEXTS_AROUND_CONSTRUCT = MethodDescriptor.ofMethod(
InvocationContexts.class,
"aroundConstruct",
Expand All @@ -179,8 +183,9 @@ public final class MethodDescriptors {
public static final MethodDescriptor INVOCATION_CONTEXTS_PRE_DESTROY = MethodDescriptor.ofMethod(InvocationContexts.class,
"preDestroy",
InvocationContext.class, Object.class, List.class, Set.class);

public static final MethodDescriptor INVOCATION_CONTEXTS_PERFORM_SUPERCLASS = MethodDescriptor.ofMethod(InvocationContexts.class,

public static final MethodDescriptor INVOCATION_CONTEXTS_PERFORM_SUPERCLASS = MethodDescriptor.ofMethod(
InvocationContexts.class,
"performSuperclassInterception",
Object.class, InvocationContext.class, List.class, Object.class, Object[].class);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,29 +152,33 @@ static boolean isObjectToString(MethodInfo method) {
static Set<MethodInfo> addInterceptedMethodCandidates(BeanDeployment beanDeployment, ClassInfo classInfo,
Map<MethodKey, Set<AnnotationInstance>> candidates,
List<AnnotationInstance> classLevelBindings, Consumer<BytecodeTransformer> bytecodeTransformerConsumer,
boolean transformUnproxyableClasses) {
boolean transformUnproxyableClasses, List<MethodInfo> aroundInvokes) {
return addInterceptedMethodCandidates(beanDeployment, classInfo, classInfo, candidates, Set.copyOf(classLevelBindings),
bytecodeTransformerConsumer, transformUnproxyableClasses,
new SubclassSkipPredicate(beanDeployment.getAssignabilityCheck()::isAssignableFrom,
beanDeployment.getBeanArchiveIndex(), beanDeployment.getObserverAndProducerMethods()),
false, new HashSet<>());
false, new HashSet<>(), aroundInvokes);
}

private static Set<MethodInfo> addInterceptedMethodCandidates(BeanDeployment beanDeployment, ClassInfo classInfo,
ClassInfo originalClassInfo,
Map<MethodKey, Set<AnnotationInstance>> candidates,
Set<AnnotationInstance> classLevelBindings, Consumer<BytecodeTransformer> bytecodeTransformerConsumer,
boolean transformUnproxyableClasses, SubclassSkipPredicate skipPredicate, boolean ignoreMethodLevelBindings,
Set<MethodKey> noClassInterceptorsMethods) {
Set<MethodKey> noClassInterceptorsMethods, List<MethodInfo> aroundInvokes) {

Set<NameAndDescriptor> methodsFromWhichToRemoveFinal = new HashSet<>();
Set<MethodInfo> finalMethodsFoundAndNotChanged = new HashSet<>();
skipPredicate.startProcessing(classInfo, originalClassInfo);

for (MethodInfo method : classInfo.methods()) {
if (aroundInvokes.contains(method)) {
// Around invoke method declared in the target class hierarchy
continue;
}
Set<AnnotationInstance> merged = mergeBindings(beanDeployment, originalClassInfo, classLevelBindings,
ignoreMethodLevelBindings, method, noClassInterceptorsMethods);
if (merged.isEmpty() || skipPredicate.test(method)) {
if ((merged.isEmpty() && aroundInvokes.isEmpty()) || skipPredicate.test(method)) {
continue;
}
boolean addToCandidates = true;
Expand Down Expand Up @@ -204,7 +208,7 @@ private static Set<MethodInfo> addInterceptedMethodCandidates(BeanDeployment bea
finalMethodsFoundAndNotChanged
.addAll(addInterceptedMethodCandidates(beanDeployment, superClassInfo, classInfo, candidates,
classLevelBindings, bytecodeTransformerConsumer, transformUnproxyableClasses, skipPredicate,
ignoreMethodLevelBindings, noClassInterceptorsMethods));
ignoreMethodLevelBindings, noClassInterceptorsMethods, aroundInvokes));
}
}

Expand All @@ -214,7 +218,7 @@ private static Set<MethodInfo> addInterceptedMethodCandidates(BeanDeployment bea
//interfaces can't have final methods
addInterceptedMethodCandidates(beanDeployment, interfaceInfo, originalClassInfo, candidates,
classLevelBindings, bytecodeTransformerConsumer, transformUnproxyableClasses,
skipPredicate, true, noClassInterceptorsMethods);
skipPredicate, true, noClassInterceptorsMethods, aroundInvokes);
}
}
return finalMethodsFoundAndNotChanged;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@
import org.jboss.jandex.Type.Kind;
import org.jboss.jandex.TypeVariable;

import io.quarkus.arc.ArcInvocationContext;
import io.quarkus.arc.ArcUndeclaredThrowableException;
import io.quarkus.arc.InjectableDecorator;
import io.quarkus.arc.InjectableInterceptor;
import io.quarkus.arc.Subclass;
import io.quarkus.arc.impl.InterceptedMethodMetadata;
import io.quarkus.arc.processor.BeanInfo.DecorationInfo;
import io.quarkus.arc.processor.BeanInfo.InterceptionInfo;
import io.quarkus.arc.processor.BeanProcessor.PrivateMembersCollector;
import io.quarkus.arc.processor.Methods.MethodKey;
import io.quarkus.arc.processor.ResourceOutput.Resource;
import io.quarkus.arc.processor.ResourceOutput.Resource.SpecialType;
Expand Down Expand Up @@ -80,6 +82,7 @@ public class SubclassGenerator extends AbstractGenerator {

private final Predicate<DotName> applicationClassPredicate;
private final Set<String> existingClasses;
private final PrivateMembersCollector privateMembers;

static String generatedName(DotName providerTypeName, String baseName) {
String packageName = DotNames.internalPackageNameWithTrailingSlash(providerTypeName);
Expand All @@ -90,11 +93,12 @@ static String generatedName(DotName providerTypeName, String baseName) {

public SubclassGenerator(AnnotationLiteralProcessor annotationLiterals, Predicate<DotName> applicationClassPredicate,
boolean generateSources, ReflectionRegistration reflectionRegistration,
Set<String> existingClasses) {
Set<String> existingClasses, PrivateMembersCollector privateMembers) {
super(generateSources, reflectionRegistration);
this.applicationClassPredicate = applicationClassPredicate;
this.annotationLiterals = annotationLiterals;
this.existingClasses = existingClasses;
this.privateMembers = privateMembers;
}

Collection<Resource> generate(BeanInfo bean, String beanClassName) {
Expand Down Expand Up @@ -325,6 +329,25 @@ public String apply(List<BindingKey> keys) {
}
}

// Initialize the "aroundInvokes" field if necessary
if (bean.hasAroundInvokes()) {
FieldCreator field = subclass.getFieldCreator("aroundInvokes", List.class)
.setModifiers(ACC_PRIVATE);
ResultHandle methodsList = constructor.newInstance(MethodDescriptor.ofConstructor(ArrayList.class));
for (MethodInfo method : bean.getAroundInvokes()) {
// BiFunction<Object,InvocationContext,Object>
FunctionCreator fun = constructor.createFunction(BiFunction.class);
BytecodeCreator funBytecode = fun.getBytecode();
ResultHandle ret = invokeInterceptorMethod(funBytecode, method,
applicationClassPredicate.test(bean.getBeanClass()),
funBytecode.getMethodParam(1),
funBytecode.getMethodParam(0));
funBytecode.returnValue(ret);
constructor.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, methodsList, fun.getInstance());
}
constructor.writeInstanceField(field.getFieldDescriptor(), constructor.getThis(), methodsList);
}

// Split initialization of InterceptedMethodMetadata into multiple methods
int group = 0;
int groupLimit = 30;
Expand Down Expand Up @@ -422,6 +445,7 @@ public String apply(List<BindingKey> keys) {
superParamHandles[i] = funcBytecode.readArrayValue(ctxParamsHandle, i);
}
}

// If a decorator is bound then invoke the method upon the decorator instance instead of the generated forwarding method
if (decorator != null) {
AssignableResultHandle funDecoratorInstance = funcBytecode.createVariable(Object.class);
Expand All @@ -445,10 +469,28 @@ public String apply(List<BindingKey> keys) {
funcBytecode.returnValue(superResult != null ? superResult : funcBytecode.loadNull());
}

ResultHandle aroundForwardFun = func.getInstance();

if (bean.hasAroundInvokes()) {
// Wrap the forwarding function with a function that calls around invoke methods declared in a hierarchy of the target class first
AssignableResultHandle methodsList = initMetadataMethod.createVariable(List.class);
initMetadataMethod.assign(methodsList, initMetadataMethod.readInstanceField(
FieldDescriptor.of(subclass.getClassName(), "aroundInvokes", List.class),
initMetadataMethod.getThis()));
FunctionCreator targetFun = initMetadataMethod.createFunction(BiFunction.class);
BytecodeCreator targetFunBytecode = targetFun.getBytecode();
ResultHandle ret = targetFunBytecode.invokeStaticMethod(
MethodDescriptors.INVOCATION_CONTEXTS_PERFORM_TARGET_AROUND_INVOKE,
targetFunBytecode.getMethodParam(1),
methodsList, aroundForwardFun);
targetFunBytecode.returnValue(ret);
aroundForwardFun = targetFun.getInstance();
}

// Now create metadata for the given intercepted method
ResultHandle methodMetadataHandle = initMetadataMethod.newInstance(
MethodDescriptors.INTERCEPTED_METHOD_METADATA_CONSTRUCTOR,
chainHandle, methodHandle, bindingsHandle, func.getInstance());
chainHandle, methodHandle, bindingsHandle, aroundForwardFun);

FieldDescriptor metadataField = FieldDescriptor.of(subclass.getClassName(), "arc$" + methodIdx++,
InterceptedMethodMetadata.class.getName());
Expand Down Expand Up @@ -513,6 +555,37 @@ public String apply(List<BindingKey> keys) {
return preDestroysField != null ? preDestroysField.getFieldDescriptor() : null;
}

private ResultHandle invokeInterceptorMethod(BytecodeCreator creator, MethodInfo interceptorMethod,
boolean isApplicationClass, ResultHandle invocationContext, ResultHandle targetInstance) {
ResultHandle ret;
// Check if interceptor method uses InvocationContext or ArcInvocationContext
Class<?> invocationContextClass;
if (interceptorMethod.parameterType(0).name().equals(DotNames.INVOCATION_CONTEXT)) {
invocationContextClass = InvocationContext.class;
} else {
invocationContextClass = ArcInvocationContext.class;
}
if (Modifier.isPrivate(interceptorMethod.flags())) {
privateMembers.add(isApplicationClass,
String.format("Interceptor method %s#%s()", interceptorMethod.declaringClass().name(),
interceptorMethod.name()));
// Use reflection fallback
ResultHandle paramTypesArray = creator.newArray(Class.class, creator.load(1));
creator.writeArrayValue(paramTypesArray, 0, creator.loadClass(invocationContextClass));
ResultHandle argsArray = creator.newArray(Object.class, creator.load(1));
creator.writeArrayValue(argsArray, 0, invocationContext);
reflectionRegistration.registerMethod(interceptorMethod);
ret = creator.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD,
creator.loadClass(interceptorMethod.declaringClass()
.name()
.toString()),
creator.load(interceptorMethod.name()), paramTypesArray, targetInstance, argsArray);
} else {
ret = creator.invokeVirtualMethod(interceptorMethod, targetInstance, invocationContext);
}
return ret;
}

private void processDecorator(DecoratorInfo decorator, BeanInfo bean, Type providerType,
String providerTypeName, ClassCreator subclass, ClassOutput classOutput, MethodCreator subclassConstructor,
int paramIndex, Map<String, ResultHandle> decoratorToResultHandle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,20 @@
*/
class AroundInvokeInvocationContext extends AbstractInvocationContext {

static Object perform(Object target, Object[] args, InterceptedMethodMetadata metadata) throws Exception {
if (metadata.chain.isEmpty()) {
return metadata.aroundInvokeForward.apply(target, new AroundInvokeInvocationContext(target, args, metadata));
}
return metadata.chain.get(0).invoke(new AroundInvokeInvocationContext(target, args, metadata));
}

private final InterceptedMethodMetadata metadata;

AroundInvokeInvocationContext(Object target, Object[] args, InterceptedMethodMetadata metadata) {
super(target, args, new ContextDataMap(metadata.bindings));
this.metadata = metadata;
}

static Object perform(Object target, Object[] args, InterceptedMethodMetadata metadata) throws Exception {
return metadata.chain.get(0).invoke(new AroundInvokeInvocationContext(target, args, metadata));
}

@Override
public Set<Annotation> getInterceptorBindings() {
return metadata.bindings;
Expand Down
Loading

0 comments on commit 4fdd98d

Please sign in to comment.