diff --git a/build-parent/pom.xml b/build-parent/pom.xml index aecfcfa96f848..03fbb4cb2d221 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -49,7 +49,7 @@ 0.21.0 - 1.7.0.Alpha13 + 1.7.0.Final jdt_apt diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index fa9dc00ae202f..720a01b64e47c 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -58,7 +58,7 @@ 1.6.4 5.3.1 - 1.7.0.Alpha14 + 1.7.0.Final 2.0.1 4.0.9 4.13.2 diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java index f5fb1364f1109..8af6b4fefca8d 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java @@ -104,6 +104,8 @@ public class BeanDeployment { private final Set removedBeans; + private final Set beansWithRuntimeDeferredUnproxyableError; + private final Map> customContexts; private final Map beanDefiningAnnotations; @@ -149,6 +151,7 @@ public class BeanDeployment { this.removeUnusedBeans = builder.removeUnusedBeans; this.unusedExclusions = removeUnusedBeans ? new ArrayList<>(builder.removalExclusions) : null; this.removedBeans = removeUnusedBeans ? new CopyOnWriteArraySet<>() : Collections.emptySet(); + this.beansWithRuntimeDeferredUnproxyableError = Collections.newSetFromMap(new ConcurrentHashMap<>()); this.customContexts = new ConcurrentHashMap<>(); this.excludeTypes = builder.excludeTypes != null ? new ArrayList<>(builder.excludeTypes) : Collections.emptyList(); @@ -506,6 +509,14 @@ public Collection getRemovedBeans() { return Collections.unmodifiableSet(removedBeans); } + boolean hasRuntimeDeferredUnproxyableError(BeanInfo bean) { + return beansWithRuntimeDeferredUnproxyableError.contains(bean); + } + + void deferUnproxyableErrorToRuntime(BeanInfo bean) { + beansWithRuntimeDeferredUnproxyableError.add(bean); + } + public Collection getQualifiers() { return Collections.unmodifiableCollection(qualifiers.values()); } @@ -1522,6 +1533,17 @@ private void validateBeans(List errors, Consumer Map> namedBeans = new HashMap<>(); Set classesReceivingNoArgsCtor = new HashSet<>(); + // this set is only used in strict compatible mode (see `Beans.validateBean()`), + // so no need to initialize it otherwise + Set injectedBeans = new HashSet<>(); + if (strictCompatibility) { + for (InjectionPointInfo injectionPoint : this.injectionPoints) { + if (injectionPoint.hasResolvedBean()) { + injectedBeans.add(injectionPoint.getResolvedBean()); + } + } + } + for (BeanInfo bean : beans) { if (bean.getName() != null) { List named = namedBeans.get(bean.getName()); @@ -1532,7 +1554,7 @@ private void validateBeans(List errors, Consumer named.add(bean); findNamespaces(bean, namespaces); } - bean.validate(errors, bytecodeTransformerConsumer, classesReceivingNoArgsCtor); + bean.validate(errors, bytecodeTransformerConsumer, classesReceivingNoArgsCtor, injectedBeans); } if (!namedBeans.isEmpty()) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java index 15af009794e8a..40f5469aa85b4 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java @@ -30,6 +30,7 @@ import jakarta.enterprise.context.spi.CreationalContext; import jakarta.enterprise.inject.CreationException; import jakarta.enterprise.inject.IllegalProductException; +import jakarta.enterprise.inject.UnproxyableResolutionException; import jakarta.enterprise.inject.literal.InjectLiteral; import jakarta.enterprise.inject.spi.InterceptionType; import jakarta.interceptor.InvocationContext; @@ -1927,7 +1928,10 @@ protected void implementGet(BeanInfo bean, ClassCreator beanCreator, ProviderTyp MethodCreator get = beanCreator.getMethodCreator("get", providerType.descriptorName(), CreationalContext.class) .setModifiers(ACC_PUBLIC); - if (BuiltinScope.DEPENDENT.is(bean.getScope())) { + if (bean.getDeployment().hasRuntimeDeferredUnproxyableError(bean)) { + get.throwException(UnproxyableResolutionException.class, "Bean not proxyable: " + bean); + get.returnValue(get.loadNull()); + } else if (BuiltinScope.DEPENDENT.is(bean.getScope())) { // @Dependent pseudo-scope // Foo instance = create(ctx) ResultHandle instance = get.invokeVirtualMethod( @@ -2214,6 +2218,10 @@ private ResultHandle wrapCurrentInjectionPoint(BeanInfo bean, } private void initializeProxy(BeanInfo bean, String baseName, ClassCreator beanCreator) { + if (bean.getDeployment().hasRuntimeDeferredUnproxyableError(bean)) { + return; + } + // Add proxy volatile field String proxyTypeName = getProxyTypeName(bean, baseName); beanCreator.getFieldCreator(FIELD_NAME_PROXY, proxyTypeName) 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 160e9cb46cba8..94b5567a1df75 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 @@ -582,8 +582,8 @@ public String getClientProxyPackageName() { } void validate(List errors, Consumer bytecodeTransformerConsumer, - Set classesReceivingNoArgsCtor) { - Beans.validateBean(this, errors, bytecodeTransformerConsumer, classesReceivingNoArgsCtor); + Set classesReceivingNoArgsCtor, Set injectedBeans) { + Beans.validateBean(this, errors, bytecodeTransformerConsumer, classesReceivingNoArgsCtor, injectedBeans); } void validateInterceptorDecorator(List errors, Consumer bytecodeTransformerConsumer) { @@ -797,7 +797,7 @@ private void putLifecycleInterceptors(Map li private void addClassLevelBindings(ClassInfo targetClass, Collection bindings) { List classLevelBindings = new ArrayList<>(); - doAddClassLevelBindings(targetClass, classLevelBindings, Set.of()); + doAddClassLevelBindings(targetClass, classLevelBindings, Set.of(), false); bindings.addAll(classLevelBindings); if (!stereotypes.isEmpty()) { // interceptor binding declared on a bean class replaces an interceptor binding of the same type @@ -808,22 +808,27 @@ private void addClassLevelBindings(ClassInfo targetClass, Collection bindings, Set skip) { + private void doAddClassLevelBindings(ClassInfo classInfo, Collection bindings, Set skip, + boolean onlyInherited) { beanDeployment.getAnnotations(classInfo).stream() .filter(a -> beanDeployment.getInterceptorBinding(a.name()) != null) .filter(a -> !skip.contains(a.name())) + .filter(a -> !onlyInherited + || beanDeployment.hasAnnotation(beanDeployment.getInterceptorBinding(a.name()), DotNames.INHERITED)) .forEach(bindings::add); if (classInfo.superClassType() != null && !classInfo.superClassType().name().equals(DotNames.OBJECT)) { ClassInfo superClass = getClassByName(beanDeployment.getBeanArchiveIndex(), classInfo.superName()); if (superClass != null) { - doAddClassLevelBindings(superClass, bindings, skip); + // proper interceptor binding inheritance only in strict mode, due to Quarkus expecting security + // annotations (such as `@RolesAllowed`) to be inherited, even though they are not `@Inherited` + doAddClassLevelBindings(superClass, bindings, skip, beanDeployment.strictCompatibility); } } } 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 f6200b874084f..31ba850b8f231 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 @@ -711,25 +711,34 @@ static void validateInterceptorDecorator(BeanInfo bean, List errors, } static void validateBean(BeanInfo bean, List errors, Consumer bytecodeTransformerConsumer, - Set classesReceivingNoArgsCtor) { + Set classesReceivingNoArgsCtor, Set injectedBeans) { + + // by default, we fail deployment due to unproxyability for all beans, but in strict mode, + // we only do that for beans that are injected somewhere -- and defer the error to runtime otherwise, + // due to CDI spec requirements + boolean failIfNotProxyable = bean.getDeployment().strictCompatibility ? injectedBeans.contains(bean) : true; + if (bean.isClassBean()) { ClassInfo beanClass = bean.getTarget().get().asClass(); String classifier = bean.getScope().isNormal() ? "Normal scoped" : null; if (classifier == null && bean.isSubclassRequired()) { classifier = "Intercepted"; + failIfNotProxyable = true; } if (Modifier.isFinal(beanClass.flags()) && classifier != null) { // Client proxies and subclasses require a non-final class if (bean.getDeployment().transformUnproxyableClasses) { bytecodeTransformerConsumer .accept(new BytecodeTransformer(beanClass.name().toString(), new FinalClassTransformFunction())); - } else { + } else if (failIfNotProxyable) { errors.add(new DeploymentException(String.format("%s bean must not be final: %s", classifier, bean))); + } else { + bean.getDeployment().deferUnproxyableErrorToRuntime(bean); } } if (bean.getDeployment().strictCompatibility && classifier != null) { - validateNonStaticFinalMethods(beanClass, bean.getDeployment().getBeanArchiveIndex(), - classifier, errors); + validateNonStaticFinalMethods(bean, beanClass, bean.getDeployment().getBeanArchiveIndex(), + classifier, errors, failIfNotProxyable); } MethodInfo noArgsConstructor = beanClass.method(Methods.INIT); @@ -754,13 +763,16 @@ static void validateBean(BeanInfo bean, List errors, Consumer errors, Consumer errors, Consumer errors, Consumer errors, Consumer errors) { + private static void validateNonStaticFinalMethods(BeanInfo bean, ClassInfo clazz, IndexView beanArchiveIndex, + String classifier, List errors, boolean failIfNotProxyable) { // see also Methods.skipForClientProxy() while (!clazz.name().equals(DotNames.OBJECT)) { for (MethodInfo method : clazz.methods()) { @@ -920,9 +936,13 @@ private static void validateNonStaticFinalMethods(ClassInfo clazz, IndexView bea } if (Modifier.isFinal(method.flags())) { - errors.add(new DeploymentException(String.format( - "%s bean must not declare non-static final methods with public, protected or default visibility: %s", - classifier, method))); + if (failIfNotProxyable) { + errors.add(new DeploymentException(String.format( + "%s bean must not declare non-static final methods with public, protected or default visibility: %s", + classifier, method))); + } else { + bean.getDeployment().deferUnproxyableErrorToRuntime(bean); + } } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java index ea0277af08f00..8421c9aed59ee 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java @@ -78,6 +78,12 @@ public ClientProxyGenerator(Predicate applicationClassPredicate, boolea Collection generate(BeanInfo bean, String beanClassName, Consumer bytecodeTransformerConsumer, boolean transformUnproxyableClasses) { + // see `BeanGenerator` -- if this bean is unproxyable and that error is deferred to runtime, + // we don't need to (and cannot, in fact) generate the client proxy class + if (bean.getDeployment().hasRuntimeDeferredUnproxyableError(bean)) { + return Collections.emptySet(); + } + ProviderType providerType = new ProviderType(bean.getProviderType()); ClassInfo providerClass = getClassByName(bean.getDeployment().getBeanArchiveIndex(), providerType.name()); String baseName = getBaseName(bean, beanClassName); 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 c6c3c7e4df94a..5f57163fe681e 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 @@ -183,18 +183,20 @@ private static Set addInterceptedMethodCandidates(BeanDeployment bea skipPredicate.startProcessing(classInfo, originalClassInfo); for (MethodInfo method : classInfo.methods()) { + MethodKey key = new MethodKey(method); + if (candidates.containsKey(key)) { + continue; + } + // 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; - } + boolean possiblyIntercepted = !bindings.isEmpty() || targetHasAroundInvokes; if (skipPredicate.test(method)) { continue; } boolean addToCandidates = true; - if (Modifier.isFinal(method.flags())) { + if (Modifier.isFinal(method.flags()) && possiblyIntercepted) { if (transformUnproxyableClasses && !isNoninterceptableKotlinMethod(method)) { methodsFromWhichToRemoveFinal.add(NameAndDescriptor.fromMethodInfo(method)); } else { @@ -203,7 +205,7 @@ private static Set addInterceptedMethodCandidates(BeanDeployment bea } } if (addToCandidates) { - candidates.computeIfAbsent(new Methods.MethodKey(method), key -> bindings); + candidates.putIfAbsent(key, bindings); } } skipPredicate.methodsProcessed(); diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceHandle.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceHandle.java index 63bce6bf5fd68..dbca61638c8d1 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceHandle.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceHandle.java @@ -64,6 +64,12 @@ default InjectableBean getBean() { */ @Override default void close() { + // https://github.com/quarkusio/quarkus/issues/33665 + if (Arc.container().strictCompatibility()) { + destroy(); + return; + } + InjectableBean bean = getBean(); if (bean == null || Dependent.class.equals(bean.getScope())) { destroy(); diff --git a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml index 844b4b621e59a..87e459298438e 100644 --- a/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml +++ b/independent-projects/arc/tcks/cdi-tck-runner/src/test/resources/testng.xml @@ -60,11 +60,7 @@ - - - - @@ -85,27 +81,12 @@ - - - - - - - - - - - - - - - diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java index 6855a1c35a9b0..05b799d33fde4 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.extension.ExtensionContext; import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcInitConfig; import io.quarkus.arc.ComponentsProvider; import io.quarkus.arc.ResourceReferenceProvider; import io.quarkus.arc.processor.AlternativePriorities; @@ -468,7 +469,7 @@ public void writeResource(Resource resource) throws IOException { .setContextClassLoader(testClassLoader); // Now we are ready to initialize Arc - Arc.initialize(); + Arc.initialize(ArcInitConfig.builder().setStrictCompatibility(strictCompatibility).build()); } catch (Throwable e) { if (shouldFail) { diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/finalmethod/FinalMethodIllegalTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/finalmethod/FinalMethodIllegalWhenInjectedTest.java similarity index 76% rename from independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/finalmethod/FinalMethodIllegalTest.java rename to independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/finalmethod/FinalMethodIllegalWhenInjectedTest.java index fc7da12739ff8..748735b30dbfe 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/finalmethod/FinalMethodIllegalTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/finalmethod/FinalMethodIllegalWhenInjectedTest.java @@ -5,17 +5,19 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; import jakarta.enterprise.inject.spi.DeploymentException; +import jakarta.inject.Inject; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import io.quarkus.arc.test.ArcTestContainer; -public class FinalMethodIllegalTest { +public class FinalMethodIllegalWhenInjectedTest { @RegisterExtension public ArcTestContainer container = ArcTestContainer.builder() - .beanClasses(Moo.class) + .beanClasses(Moo.class, MooConsumer.class) .strictCompatibility(true) .shouldFail() .build(); @@ -34,4 +36,11 @@ final int getVal() { return -1; } } + + // to trigger deployment-time error (in strict compatible mode) + @Dependent + static class MooConsumer { + @Inject + Moo moo; + } } diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/finalmethod/FinalMethodIllegalWhenNotInjectedTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/finalmethod/FinalMethodIllegalWhenNotInjectedTest.java new file mode 100644 index 0000000000000..d82d2a136e437 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/finalmethod/FinalMethodIllegalWhenNotInjectedTest.java @@ -0,0 +1,35 @@ +package io.quarkus.arc.test.clientproxy.finalmethod; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.UnproxyableResolutionException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class FinalMethodIllegalWhenNotInjectedTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Moo.class) + .strictCompatibility(true) + .shouldFail() + .build(); + + @Test + public void test() { + assertThrows(UnproxyableResolutionException.class, () -> { + Arc.container().instance(Moo.class).get(); + }); + } + + @ApplicationScoped + static class Moo { + final int getVal() { + return -1; + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/destroy/InstanceHandleDestroyTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/destroy/InstanceHandleDestroyTest.java new file mode 100644 index 0000000000000..4cd5c46fc9198 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/destroy/InstanceHandleDestroyTest.java @@ -0,0 +1,81 @@ +package io.quarkus.arc.test.instance.destroy; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.arc.test.ArcTestContainer; + +public class InstanceHandleDestroyTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer.Builder() + .beanClasses(MyDependentBean.class, MyAppScopedBean.class) + .strictCompatibility(true) + .build(); + + @Test + public void testDestroy() { + assertFalse(MyDependentBean.DESTROYED.get()); + try (InstanceHandle handle = Arc.container().instance(MyDependentBean.class)) { + assertNotNull(handle.get().toString()); + } + assertTrue(MyDependentBean.DESTROYED.get()); + + // normal-scoped + String oldId; + assertFalse(MyAppScopedBean.DESTROYED.get()); + try (InstanceHandle handle = Arc.container().instance(MyAppScopedBean.class)) { + assertNotNull(handle.get().toString()); + oldId = handle.get().getId(); + } + assertTrue(MyAppScopedBean.DESTROYED.get()); + + String newId = Arc.container().instance(MyAppScopedBean.class).get().getId(); + assertNotEquals(oldId, newId); + } + + @Dependent + static class MyDependentBean { + static final AtomicBoolean DESTROYED = new AtomicBoolean(false); + + @PreDestroy + void destroy() { + DESTROYED.set(true); + } + } + + @ApplicationScoped + static class MyAppScopedBean { + static final AtomicBoolean DESTROYED = new AtomicBoolean(false); + + String id; + + String getId() { + return id; + } + + @PostConstruct + void init() { + this.id = UUID.randomUUID().toString(); + } + + @PreDestroy + void destroy() { + DESTROYED.set(true); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/inherited/InheritedBindingOnBeanTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/inherited/InheritedBindingOnBeanTest.java new file mode 100644 index 0000000000000..6f08c6cf41389 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/inherited/InheritedBindingOnBeanTest.java @@ -0,0 +1,96 @@ +package io.quarkus.arc.test.interceptors.bindings.inherited; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +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.test.ArcTestContainer; + +public class InheritedBindingOnBeanTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer.Builder() + .beanClasses(MyBean.class, FooBinding.class, BarBinding.class, FooInterceptor.class, BarInterceptor.class) + .strictCompatibility(true) // correct interceptor binding inheritance + .build(); + + @Test + public void testInterception() { + MyBean bean = Arc.container().instance(MyBean.class).get(); + assertNotNull(bean); + bean.doSomething(); + assertTrue(FooInterceptor.intercepted); + assertFalse(BarInterceptor.intercepted); + } + + @FooBinding + @BarBinding + static class MySuperclass { + } + + @Singleton + static class MyBean extends MySuperclass { + void doSomething() { + } + } + + @Target({ TYPE, METHOD }) + @Retention(RUNTIME) + @Documented + @InterceptorBinding + @Inherited + @interface FooBinding { + } + + @Target({ TYPE, METHOD }) + @Retention(RUNTIME) + @Documented + @InterceptorBinding + // not @Inherited + @interface BarBinding { + } + + @FooBinding + @Interceptor + @Priority(1) + static class FooInterceptor { + static boolean intercepted = false; + + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + intercepted = true; + return ctx.proceed(); + } + } + + @BarBinding + @Interceptor + @Priority(1) + static class BarInterceptor { + static boolean intercepted = false; + + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + intercepted = true; + return ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/inherited/InheritedMethodsWithInterceptorBindingTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/inherited/InheritedMethodsWithInterceptorBindingTest.java new file mode 100644 index 0000000000000..590648eaba2de --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/inherited/InheritedMethodsWithInterceptorBindingTest.java @@ -0,0 +1,72 @@ +package io.quarkus.arc.test.interceptors.bindings.inherited; + +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.enterprise.context.Dependent; +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.test.ArcTestContainer; + +public class InheritedMethodsWithInterceptorBindingTest { + @RegisterExtension + ArcTestContainer container = new ArcTestContainer(MyBean.class, MyInterceptorBinding.class, MyInterceptor.class); + + @Test + public void test() { + MyBean bean = Arc.container().instance(MyBean.class).get(); + assertEquals("foobar", bean.foobar()); + assertEquals("intercepted: foobar", bean.foobarNotInherited()); + } + + static class MySuperclass { + @MyInterceptorBinding + String foobar() { + return "this should be ignored"; + } + + @MyInterceptorBinding + String foobarNotInherited() { + return "foobar"; + } + } + + @Dependent + static class MyBean extends MySuperclass { + @Override + String foobar() { + return "foobar"; + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Priority(1) + @Interceptor + static class MyInterceptor { + @AroundInvoke + public 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/noclassinterceptors/InheritedClassLevel.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/noclassinterceptors/InheritedClassLevel.java index a844883ff8152..299571408e9f1 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/noclassinterceptors/InheritedClassLevel.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/noclassinterceptors/InheritedClassLevel.java @@ -6,6 +6,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -15,5 +16,6 @@ @Retention(RUNTIME) @Documented @InterceptorBinding +@Inherited public @interface InheritedClassLevel { }