From 1adf1b7821508380d40eaa96ca9c0beedda203ba Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Wed, 19 Apr 2023 14:40:34 +0200 Subject: [PATCH] ArC: add tests for business method interceptors on target classes and fix some issues Co-authored-by: Martin Kouba --- .../io/quarkus/arc/processor/BeanInfo.java | 7 +- .../java/io/quarkus/arc/processor/Beans.java | 9 +- .../arc/processor/InterceptorInfo.java | 20 +-- .../io/quarkus/arc/processor/Methods.java | 68 ++++++---- .../processor/SubclassSkipPredicateTest.java | 2 +- ...sAndManySuperclassesWithOverridesTest.java | 64 ++++++++++ ...nvokeOnTargetClassAndSuperclassesTest.java | 45 +++++++ ...ClassAndSuperclassesWithOverridesTest.java | 53 ++++++++ .../AroundInvokeOnTargetClassTest.java | 38 ++++++ ...eAndManySuperclassesWithOverridesTest.java | 118 ++++++++++++++++++ ...getClassAndOutsideAndSuperclassesTest.java | 79 ++++++++++++ ...tsideAndSuperclassesWithOverridesTest.java | 80 ++++++++++++ ...oundInvokeOnTargetClassAndOutsideTest.java | 65 ++++++++++ 13 files changed, 604 insertions(+), 44 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndManySuperclassesWithOverridesTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndSuperclassesTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndSuperclassesWithOverridesTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndManySuperclassesWithOverridesTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndSuperclassesTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndSuperclassesWithOverridesTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideTest.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java index ead53d8a41703..ffa2a7995a2d6 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java @@ -619,8 +619,8 @@ private Map initInterceptedMethods(List } } - Set finalMethods = Methods.addInterceptedMethodCandidates(beanDeployment, target.get().asClass(), - candidates, classLevelBindings, bytecodeTransformerConsumer, transformUnproxyableClasses, aroundInvokes); + Set finalMethods = Methods.addInterceptedMethodCandidates(this, candidates, classLevelBindings, + bytecodeTransformerConsumer, transformUnproxyableClasses); if (!finalMethods.isEmpty()) { String additionalError = ""; if (finalMethods.stream().anyMatch(KotlinUtils::isNoninterceptableKotlinMethod)) { @@ -668,7 +668,8 @@ private Map initDecoratedMethods() { ClassInfo classInfo = target.get().asClass(); addDecoratedMethods(candidates, classInfo, classInfo, bound, new SubclassSkipPredicate(beanDeployment.getAssignabilityCheck()::isAssignableFrom, - beanDeployment.getBeanArchiveIndex(), beanDeployment.getObserverAndProducerMethods())); + beanDeployment.getBeanArchiveIndex(), beanDeployment.getObserverAndProducerMethods(), + beanDeployment.getAnnotationStore())); Map decoratedMethods = new HashMap<>(candidates.size()); for (Entry entry : candidates.entrySet()) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java index 141acb7620184..035ffb1ffd091 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java @@ -650,10 +650,10 @@ static List getCallbacks(ClassInfo beanClass, DotName annotation, In } static List getAroundInvokes(ClassInfo beanClass, BeanDeployment deployment) { - List methods = new ArrayList<>(); AnnotationStore store = deployment.getAnnotationStore(); - Set processed = new HashSet<>(); + List methods = new ArrayList<>(); + List allMethods = new ArrayList<>(); ClassInfo aClass = beanClass; while (aClass != null) { int aroundInvokesFound = 0; @@ -661,13 +661,14 @@ static List getAroundInvokes(ClassInfo beanClass, BeanDeployment dep if (Modifier.isStatic(method.flags())) { continue; } - if (store.hasAnnotation(method, DotNames.AROUND_INVOKE) && !processed.contains(method.name())) { - methods.add(method); + if (store.hasAnnotation(method, DotNames.AROUND_INVOKE)) { + InterceptorInfo.addInterceptorMethod(allMethods, methods, method); if (++aroundInvokesFound > 1) { throw new DefinitionException( "Multiple @AroundInvoke interceptor methods declared on class: " + aClass); } } + allMethods.add(method); } DotName superTypeName = aClass.superName(); aClass = superTypeName == null || DotNames.OBJECT.equals(superTypeName) ? null diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java index 21beeb93509ed..06eaf3ea49a58 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java @@ -247,28 +247,30 @@ public int compareTo(InterceptorInfo other) { return getTarget().toString().compareTo(other.getTarget().toString()); } - private void addInterceptorMethod(List allMethods, List interceptorMethods, MethodInfo method) { + static void addInterceptorMethod(List allMethods, List interceptorMethods, MethodInfo method) { validateSignature(method); if (!isInterceptorMethodOverriden(allMethods, method)) { interceptorMethods.add(method); } } - private boolean isInterceptorMethodOverriden(Iterable allMethods, MethodInfo method) { + static boolean isInterceptorMethodOverriden(Iterable allMethods, MethodInfo method) { for (MethodInfo m : allMethods) { - if (m.name().equals(method.name()) && m.parametersCount() == 1 - && (m.parameterType(0).name().equals(DotNames.INVOCATION_CONTEXT) - || m.parameterType(0).name().equals(DotNames.ARC_INVOCATION_CONTEXT))) { + if (m.name().equals(method.name()) && hasInterceptorMethodParameter(m)) { return true; } } return false; } - private MethodInfo validateSignature(MethodInfo method) { - List parameters = method.parameterTypes(); - if (parameters.size() != 1 || !(parameters.get(0).name().equals(DotNames.INVOCATION_CONTEXT) - || parameters.get(0).name().equals(DotNames.ARC_INVOCATION_CONTEXT))) { + static boolean hasInterceptorMethodParameter(MethodInfo method) { + return method.parametersCount() == 1 + && (method.parameterType(0).name().equals(DotNames.INVOCATION_CONTEXT) + || method.parameterType(0).name().equals(DotNames.ARC_INVOCATION_CONTEXT)); + } + + private static MethodInfo validateSignature(MethodInfo method) { + if (!hasInterceptorMethodParameter(method)) { throw new IllegalStateException( "An interceptor method must accept exactly one parameter of type jakarta.interceptor.InvocationContext: " + method + " declared on " + method.declaringClass()); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java index 1dc877ce6a2de..cff90da5a09d2 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java @@ -52,6 +52,8 @@ final class Methods { public static final String TO_STRING = "toString"; static final Set IGNORED_METHODS = Set.of(INIT, CLINIT); + static final List OBSERVER_PRODUCER_ANNOTATIONS = List.of(DotNames.OBSERVES, DotNames.OBSERVES_ASYNC, + DotNames.PRODUCES); private Methods() { } @@ -149,15 +151,18 @@ static boolean isObjectToString(MethodInfo method) { return method.declaringClass().name().equals(DotNames.OBJECT) && method.name().equals(TO_STRING); } - static Set addInterceptedMethodCandidates(BeanDeployment beanDeployment, ClassInfo classInfo, + static Set addInterceptedMethodCandidates(BeanInfo bean, Map> candidates, List classLevelBindings, Consumer bytecodeTransformerConsumer, - boolean transformUnproxyableClasses, List aroundInvokes) { + boolean transformUnproxyableClasses) { + BeanDeployment beanDeployment = bean.getDeployment(); + ClassInfo classInfo = bean.getTarget().get().asClass(); return addInterceptedMethodCandidates(beanDeployment, classInfo, classInfo, candidates, Set.copyOf(classLevelBindings), bytecodeTransformerConsumer, transformUnproxyableClasses, new SubclassSkipPredicate(beanDeployment.getAssignabilityCheck()::isAssignableFrom, - beanDeployment.getBeanArchiveIndex(), beanDeployment.getObserverAndProducerMethods()), - false, new HashSet<>(), aroundInvokes); + beanDeployment.getBeanArchiveIndex(), beanDeployment.getObserverAndProducerMethods(), + beanDeployment.getAnnotationStore()), + false, new HashSet<>(), bean.hasAroundInvokes()); } private static Set addInterceptedMethodCandidates(BeanDeployment beanDeployment, ClassInfo classInfo, @@ -165,20 +170,21 @@ private static Set addInterceptedMethodCandidates(BeanDeployment bea Map> candidates, Set classLevelBindings, Consumer bytecodeTransformerConsumer, boolean transformUnproxyableClasses, SubclassSkipPredicate skipPredicate, boolean ignoreMethodLevelBindings, - Set noClassInterceptorsMethods, List aroundInvokes) { + Set noClassInterceptorsMethods, boolean targetHasAroundInvokes) { Set methodsFromWhichToRemoveFinal = new HashSet<>(); Set 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 + // Note that we must merge the bindings first + Set bindings = mergeBindings(beanDeployment, originalClassInfo, classLevelBindings, + ignoreMethodLevelBindings, method, noClassInterceptorsMethods); + if (bindings.isEmpty() && !targetHasAroundInvokes) { + // No bindings found and target class does not declare around invoke interceptor methods continue; } - Set merged = mergeBindings(beanDeployment, originalClassInfo, classLevelBindings, - ignoreMethodLevelBindings, method, noClassInterceptorsMethods); - if ((merged.isEmpty() && aroundInvokes.isEmpty()) || skipPredicate.test(method)) { + if (skipPredicate.test(method)) { continue; } boolean addToCandidates = true; @@ -191,7 +197,7 @@ private static Set addInterceptedMethodCandidates(BeanDeployment bea } } if (addToCandidates) { - candidates.computeIfAbsent(new Methods.MethodKey(method), key -> merged); + candidates.computeIfAbsent(new Methods.MethodKey(method), key -> bindings); } } skipPredicate.methodsProcessed(); @@ -208,7 +214,7 @@ private static Set addInterceptedMethodCandidates(BeanDeployment bea finalMethodsFoundAndNotChanged .addAll(addInterceptedMethodCandidates(beanDeployment, superClassInfo, classInfo, candidates, classLevelBindings, bytecodeTransformerConsumer, transformUnproxyableClasses, skipPredicate, - ignoreMethodLevelBindings, noClassInterceptorsMethods, aroundInvokes)); + ignoreMethodLevelBindings, noClassInterceptorsMethods, targetHasAroundInvokes)); } } @@ -218,7 +224,7 @@ private static Set addInterceptedMethodCandidates(BeanDeployment bea //interfaces can't have final methods addInterceptedMethodCandidates(beanDeployment, interfaceInfo, originalClassInfo, candidates, classLevelBindings, bytecodeTransformerConsumer, transformUnproxyableClasses, - skipPredicate, true, noClassInterceptorsMethods, aroundInvokes); + skipPredicate, true, noClassInterceptorsMethods, targetHasAroundInvokes); } } return finalMethodsFoundAndNotChanged; @@ -274,9 +280,7 @@ private static Set mergeBindings(BeanDeployment beanDeployme } if (Modifier.isPrivate(method.flags()) - && !Annotations.contains(methodAnnotations, DotNames.PRODUCES) - && !Annotations.contains(methodAnnotations, DotNames.OBSERVES) - && !Annotations.contains(methodAnnotations, DotNames.OBSERVES_ASYNC)) { + && !Annotations.containsAny(methodAnnotations, OBSERVER_PRODUCER_ANNOTATIONS)) { String message; if (methodLevelBindings.size() == 1) { message = String.format("%s will have no effect on method %s.%s() because the method is private.", @@ -442,24 +446,28 @@ public MethodVisitor visitMethod(int access, String name, String descriptor, Str * This stateful predicate can be used to skip methods that should not be added to the generated subclass. *

* Don't forget to call {@link SubclassSkipPredicate#startProcessing(ClassInfo, ClassInfo)} before the methods are processed - * and - * {@link SubclassSkipPredicate#methodsProcessed()} afterwards. + * and {@link SubclassSkipPredicate#methodsProcessed()} afterwards. */ static class SubclassSkipPredicate implements Predicate { + private static final List INTERCEPTOR_ANNOTATIONS = List.of(DotNames.AROUND_INVOKE, DotNames.POST_CONSTRUCT, + DotNames.PRE_DESTROY); + private final BiFunction assignableFromFun; private final IndexView beanArchiveIndex; private final Set producersAndObservers; + private final AnnotationStore annotationStore; private ClassInfo clazz; private ClassInfo originalClazz; private List regularMethods; private Set bridgeMethods = new HashSet<>(); public SubclassSkipPredicate(BiFunction assignableFromFun, IndexView beanArchiveIndex, - Set producersAndObservers) { + Set producersAndObservers, AnnotationStore annotationStore) { this.assignableFromFun = assignableFromFun; this.beanArchiveIndex = beanArchiveIndex; this.producersAndObservers = producersAndObservers; + this.annotationStore = annotationStore; } void startProcessing(ClassInfo clazz, ClassInfo originalClazz) { @@ -487,7 +495,8 @@ public boolean test(MethodInfo method) { // Skip bridge methods that have a corresponding "implementation method" on the same class // The algorithm we use to detect these methods is best effort, i.e. there might be use cases where the detection fails return hasImplementation(method); - } else if (method.isSynthetic()) { + } + if (method.isSynthetic()) { // Skip non-bridge synthetic methods return true; } @@ -495,20 +504,25 @@ public boolean test(MethodInfo method) { // Skip a private method that is not and observer or producer return true; } - if (method.hasAnnotation(DotNames.POST_CONSTRUCT) || method.hasAnnotation(DotNames.PRE_DESTROY)) { - // @PreDestroy and @PostConstruct methods declared on the bean are NOT candidates for around invoke interception + if (Modifier.isStatic(method.flags())) { return true; } - if (isOverridenByBridgeMethod(method)) { + if (IGNORED_METHODS.contains(method.name())) { return true; } - if (Modifier.isStatic(method.flags())) { + if (method.declaringClass().name().equals(DotNames.OBJECT)) { return true; } - if (IGNORED_METHODS.contains(method.name())) { + if (annotationStore.hasAnyAnnotation(method, INTERCEPTOR_ANNOTATIONS)) { + // @AroundInvoke, @PreDestroy and @PostConstruct methods declared on the bean are NOT candidates for around invoke interception return true; } - if (method.declaringClass().name().equals(DotNames.OBJECT)) { + if (InterceptorInfo.hasInterceptorMethodParameter(method) + && InterceptorInfo.isInterceptorMethodOverriden(regularMethods, method)) { + // Has exactly one param InvocationContext/ArcInvocationContext and is overriden + return true; + } + if (isOverridenByBridgeMethod(method)) { return true; } if (Modifier.isInterface(clazz.flags()) && Modifier.isInterface(method.declaringClass().flags()) @@ -527,7 +541,7 @@ public boolean test(MethodInfo method) { } DotName typeName = type.name(); if (type.kind() == Kind.ARRAY) { - Type componentType = type.asArrayType().component(); + Type componentType = type.asArrayType().constituent(); if (componentType.kind() == Kind.PRIMITIVE) { continue; } diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/SubclassSkipPredicateTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/SubclassSkipPredicateTest.java index 5b73e11ba2f31..0035ce54cdb7b 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/SubclassSkipPredicateTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/SubclassSkipPredicateTest.java @@ -27,7 +27,7 @@ public void testPredicate() throws IOException { IndexView index = Index.of(Base.class, Submarine.class, Long.class, Number.class); AssignabilityCheck assignabilityCheck = new AssignabilityCheck(index, null); SubclassSkipPredicate predicate = new SubclassSkipPredicate(assignabilityCheck::isAssignableFrom, null, - Collections.emptySet()); + Collections.emptySet(), new AnnotationStore(Collections.emptyList(), null)); ClassInfo submarineClass = index.getClassByName(DotName.createSimple(Submarine.class.getName())); predicate.startProcessing(submarineClass, submarineClass); diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndManySuperclassesWithOverridesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndManySuperclassesWithOverridesTest.java new file mode 100644 index 0000000000000..179cff5fcd517 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndManySuperclassesWithOverridesTest.java @@ -0,0 +1,64 @@ +package io.quarkus.arc.test.interceptors.targetclass; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class AroundInvokeOnTargetClassAndManySuperclassesWithOverridesTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + MyBean bean = arc.instance(MyBean.class).get(); + assertEquals("super-intercepted: intercepted: foobar", bean.doSomething(42)); + } + + static class Alpha { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "this should not be called as the method is overridden in MyBean"; + } + } + + static class Bravo extends Alpha { + @AroundInvoke + Object specialIntercept(InvocationContext ctx) throws Exception { + return "this should not be called as the method is overridden in Charlie"; + } + } + + static class Charlie extends Bravo { + @AroundInvoke + Object superIntercept(InvocationContext ctx) throws Exception { + return "super-intercepted: " + ctx.proceed(); + } + + @Override + Object specialIntercept(InvocationContext ctx) { + return "this is not an interceptor method"; + } + } + + @Singleton + static class MyBean extends Charlie { + String doSomething(int param) { + return "foobar"; + } + + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "intercepted: " + ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndSuperclassesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndSuperclassesTest.java new file mode 100644 index 0000000000000..9f7e6e786b601 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndSuperclassesTest.java @@ -0,0 +1,45 @@ +package io.quarkus.arc.test.interceptors.targetclass; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class AroundInvokeOnTargetClassAndSuperclassesTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + MyBean bean = arc.instance(MyBean.class).get(); + assertEquals("super-intercepted: intercepted: foobar", bean.doSomething()); + } + + static class MyBeanSuperclass { + @AroundInvoke + Object superIntercept(InvocationContext ctx) throws Exception { + return "super-intercepted: " + ctx.proceed(); + } + } + + @Singleton + static class MyBean extends MyBeanSuperclass { + String doSomething() { + return "foobar"; + } + + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "intercepted: " + ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndSuperclassesWithOverridesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndSuperclassesWithOverridesTest.java new file mode 100644 index 0000000000000..a1712db87110a --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassAndSuperclassesWithOverridesTest.java @@ -0,0 +1,53 @@ +package io.quarkus.arc.test.interceptors.targetclass; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class AroundInvokeOnTargetClassAndSuperclassesWithOverridesTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + MyBean bean = arc.instance(MyBean.class).get(); + assertEquals("super-intercepted: intercepted: foobar", bean.doSomething()); + } + + static class Alpha { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "this should not be called as the method is overridden in MyBean"; + } + } + + static class Bravo extends Alpha { + @AroundInvoke + Object superIntercept(InvocationContext ctx) throws Exception { + return "super-intercepted: " + ctx.proceed(); + } + } + + @Singleton + static class MyBean extends Bravo { + String doSomething() { + return "foobar"; + } + + @Override + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "intercepted: " + ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassTest.java new file mode 100644 index 0000000000000..89587eab4c3ae --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/AroundInvokeOnTargetClassTest.java @@ -0,0 +1,38 @@ +package io.quarkus.arc.test.interceptors.targetclass; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class AroundInvokeOnTargetClassTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + MyBean bean = arc.instance(MyBean.class).get(); + assertEquals("intercepted: foobar", bean.doSomething()); + } + + @Singleton + static class MyBean { + String doSomething() { + return "foobar"; + } + + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "intercepted: " + ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndManySuperclassesWithOverridesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndManySuperclassesWithOverridesTest.java new file mode 100644 index 0000000000000..7dc4a90501ad5 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndManySuperclassesWithOverridesTest.java @@ -0,0 +1,118 @@ +package io.quarkus.arc.test.interceptors.targetclass.mixed; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class AroundInvokeOnTargetClassAndOutsideAndManySuperclassesWithOverridesTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + MyBean bean = arc.instance(MyBean.class).get(); + assertEquals("super-outside: outside: super-target: target: foobar", bean.doSomething()); + } + + static class Alpha { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "this should not be called as the method is overridden in MyBean"; + } + } + + static class Bravo extends Alpha { + @AroundInvoke + Object specialIntercept(InvocationContext ctx) { + return "this should not be called as the method is overridden in Charlie"; + } + } + + static class Charlie extends Bravo { + @AroundInvoke + Object superIntercept(InvocationContext ctx) throws Exception { + return "super-target: " + ctx.proceed(); + } + + @Override + Object specialIntercept(InvocationContext ctx) { + return "this is not an interceptor method"; + } + } + + @Singleton + @MyInterceptorBinding + static class MyBean extends Charlie { + String doSomething() { + return "foobar"; + } + + @Override + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "target: " + ctx.proceed(); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + @interface MyInterceptorBinding { + } + + static class Delta { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "this should not be called as the method is overridden in MyInterceptor"; + } + } + + static class Echo extends Delta { + @AroundInvoke + Object specialIntercept(InvocationContext ctx) throws Exception { + return "this should not be called as the method is overridden in Charlie"; + } + } + + static class Foxtrot extends Echo { + @AroundInvoke + Object superIntercept(InvocationContext ctx) throws Exception { + return "super-outside: " + ctx.proceed(); + } + + @Override + Object specialIntercept(InvocationContext ctx) { + return "this is not an interceptor method"; + } + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + static class MyInterceptor extends Foxtrot { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "outside: " + ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndSuperclassesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndSuperclassesTest.java new file mode 100644 index 0000000000000..a0dc9d112270a --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndSuperclassesTest.java @@ -0,0 +1,79 @@ +package io.quarkus.arc.test.interceptors.targetclass.mixed; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class AroundInvokeOnTargetClassAndOutsideAndSuperclassesTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + MyBean bean = arc.instance(MyBean.class).get(); + assertEquals("super-outside: outside: super-target: target: foobar", bean.doSomething()); + } + + static class MyBeanSuperclass { + @AroundInvoke + Object superIntercept(InvocationContext ctx) throws Exception { + return "super-target: " + ctx.proceed(); + } + } + + @Singleton + @MyInterceptorBinding + static class MyBean extends MyBeanSuperclass { + String doSomething() { + return "foobar"; + } + + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "target: " + ctx.proceed(); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + @interface MyInterceptorBinding { + } + + static class MyInterceptorSuperclass { + @AroundInvoke + Object superIntercept(InvocationContext ctx) throws Exception { + return "super-outside: " + ctx.proceed(); + } + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + static class MyInterceptor extends MyInterceptorSuperclass { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "outside: " + ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndSuperclassesWithOverridesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndSuperclassesWithOverridesTest.java new file mode 100644 index 0000000000000..d5b89eb78cb0c --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideAndSuperclassesWithOverridesTest.java @@ -0,0 +1,80 @@ +package io.quarkus.arc.test.interceptors.targetclass.mixed; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class AroundInvokeOnTargetClassAndOutsideAndSuperclassesWithOverridesTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + MyBean bean = arc.instance(MyBean.class).get(); + assertEquals("super-outside: outside: target: foobar", bean.doSomething()); + } + + static class MyBeanSuperclass { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "this should not be called as the method is overridden in MyBean"; + } + } + + @Singleton + @MyInterceptorBinding + static class MyBean extends MyBeanSuperclass { + String doSomething() { + return "foobar"; + } + + @Override + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "target: " + ctx.proceed(); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + @interface MyInterceptorBinding { + } + + static class MyInterceptorSuperclass { + @AroundInvoke + Object superIntercept(InvocationContext ctx) throws Exception { + return "super-outside: " + ctx.proceed(); + } + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + static class MyInterceptor extends MyInterceptorSuperclass { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "outside: " + ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideTest.java new file mode 100644 index 0000000000000..a21c8471834ab --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/targetclass/mixed/AroundInvokeOnTargetClassAndOutsideTest.java @@ -0,0 +1,65 @@ +package io.quarkus.arc.test.interceptors.targetclass.mixed; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.inject.Singleton; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.test.ArcTestContainer; + +public class AroundInvokeOnTargetClassAndOutsideTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + ArcContainer arc = Arc.container(); + MyBean bean = arc.instance(MyBean.class).get(); + assertEquals("outside: target: foobar", bean.doSomething()); + } + + @Singleton + @MyInterceptorBinding + static class MyBean { + String doSomething() { + return "foobar"; + } + + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "target: " + ctx.proceed(); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + public @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + public static class MyInterceptor { + @AroundInvoke + Object aroundInvoke(InvocationContext ctx) throws Exception { + return "outside: " + ctx.proceed(); + } + } +}