From 0b3310cc9f27e974c30da0b033c25312f48e5945 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Wed, 20 May 2020 11:33:53 +1000 Subject: [PATCH] Make ArC intercept default methods Fixes #7188 --- .../io/quarkus/arc/processor/Methods.java | 10 ++++- .../java/io/quarkus/arc/impl/Reflections.java | 38 +++++++++++++++---- .../interceptors/defaultmethod/ABinding.java | 18 +++++++++ .../defaultmethod/DefaultMethodBean.java | 12 ++++++ .../DefaultMethodInterceptorTest.java | 27 +++++++++++++ .../defaultmethod/DefaultMethodInterface.java | 8 ++++ .../defaultmethod/MessageInterceptor.java | 15 ++++++++ .../main/java/io/quarkus/it/panache/Beer.java | 11 ++++++ .../io/quarkus/it/panache/BeerRepository.java | 16 ++++++++ .../panache/TransactionalRepositoryTest.java | 25 ++++++++++++ 10 files changed, 171 insertions(+), 9 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/defaultmethod/ABinding.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/defaultmethod/DefaultMethodBean.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/defaultmethod/DefaultMethodInterceptorTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/defaultmethod/DefaultMethodInterface.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/defaultmethod/MessageInterceptor.java create mode 100644 integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Beer.java create mode 100644 integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/BeerRepository.java create mode 100644 integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TransactionalRepositoryTest.java 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 28fa7717bcecf..1ace72fdb7763 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 @@ -28,7 +28,7 @@ import org.objectweb.asm.Opcodes; /** - * + * * @author Martin Kouba * @author Michal Szynkiewicz, michal.l.szynkiewicz@gmail.com */ @@ -186,6 +186,14 @@ public MethodVisitor visitMethod(int access, String name, String descriptor, Str classLevelBindings, bytecodeTransformerConsumer, transformUnproxyableClasses)); } } + for (DotName i : classInfo.interfaceNames()) { + ClassInfo interfaceInfo = getClassByName(beanDeployment.getIndex(), i); + if (interfaceInfo != null) { + //interfaces can't have final methods + addInterceptedMethodCandidates(beanDeployment, interfaceInfo, candidates, + classLevelBindings, bytecodeTransformerConsumer, transformUnproxyableClasses); + } + } return finalMethodsFoundAndNotChanged; } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Reflections.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Reflections.java index 6011c934bfa86..e2fbca8dd587b 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Reflections.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/Reflections.java @@ -10,8 +10,12 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; +import java.util.ArrayDeque; import java.util.Arrays; +import java.util.Deque; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; import java.util.function.Function; /** @@ -46,7 +50,7 @@ private Reflections() { } /** - * + * * @param clazz * @param fieldName * @return the field declared in the class hierarchy @@ -67,7 +71,7 @@ private static Field findFieldInternal(Class clazz, String fieldName) { } /** - * + * * @param clazz * @param methodName * @param parameterTypes @@ -78,14 +82,32 @@ public static Method findMethod(Class clazz, String methodName, Class... p } private static Method findMethodInternal(Class clazz, String methodName, Class... parameterTypes) { - try { - return clazz.getDeclaredMethod(methodName, parameterTypes); - } catch (NoSuchMethodException e) { - if (clazz.getSuperclass() != null) { - return findMethodInternal(clazz.getSuperclass(), methodName, parameterTypes); + Class theClass = clazz; + Deque> interfaces = new ArrayDeque<>(); + while (theClass != null) { + try { + return theClass.getDeclaredMethod(methodName, parameterTypes); + } catch (NoSuchMethodException e) { + interfaces.addAll(Arrays.asList(theClass.getInterfaces())); + theClass = theClass.getSuperclass(); + } + } + //look for default methods on interfaces + Set> seen = new HashSet<>(interfaces); + while (!interfaces.isEmpty()) { + Class iface = interfaces.pop(); + try { + return iface.getDeclaredMethod(methodName, parameterTypes); + } catch (NoSuchMethodException ex) { + //ignore + } + for (Class extra : iface.getInterfaces()) { + if (seen.add(extra)) { + interfaces.add(extra); + } } - throw new IllegalArgumentException(e); } + throw new IllegalArgumentException("Cannot find method " + methodName + Arrays.asList(parameterTypes) + " on " + clazz); } public static Constructor findConstructor(Class clazz, Class... parameterTypes) { diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/defaultmethod/ABinding.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/defaultmethod/ABinding.java new file mode 100644 index 0000000000000..7935a1b82826b --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/defaultmethod/ABinding.java @@ -0,0 +1,18 @@ +package io.quarkus.arc.test.interceptors.defaultmethod; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import javax.interceptor.InterceptorBinding; + +@Target({ TYPE, METHOD }) +@Retention(RUNTIME) +@Documented +@InterceptorBinding +public @interface ABinding { + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/defaultmethod/DefaultMethodBean.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/defaultmethod/DefaultMethodBean.java new file mode 100644 index 0000000000000..50959ad2eb16e --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/defaultmethod/DefaultMethodBean.java @@ -0,0 +1,12 @@ +package io.quarkus.arc.test.interceptors.defaultmethod; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +@ABinding +public class DefaultMethodBean implements DefaultMethodInterface { + + public String hello() { + return "hello"; + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/defaultmethod/DefaultMethodInterceptorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/defaultmethod/DefaultMethodInterceptorTest.java new file mode 100644 index 0000000000000..fc7dc5ec40223 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/defaultmethod/DefaultMethodInterceptorTest.java @@ -0,0 +1,27 @@ +package io.quarkus.arc.test.interceptors.defaultmethod; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.arc.test.ArcTestContainer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class DefaultMethodInterceptorTest { + + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(ABinding.class, DefaultMethodBean.class, + DefaultMethodInterface.class, MessageInterceptor.class); + + @Test + public void testInterception() { + ArcContainer arc = Arc.container(); + + InstanceHandle handle = arc.instance(DefaultMethodBean.class); + DefaultMethodBean simpleBean = handle.get(); + Assertions.assertEquals("intercepted:hello", simpleBean.hello()); + Assertions.assertEquals("intercepted:default method", simpleBean.defaultMethod()); + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/defaultmethod/DefaultMethodInterface.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/defaultmethod/DefaultMethodInterface.java new file mode 100644 index 0000000000000..a5e1d1d207bb7 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/defaultmethod/DefaultMethodInterface.java @@ -0,0 +1,8 @@ +package io.quarkus.arc.test.interceptors.defaultmethod; + +public interface DefaultMethodInterface { + + default String defaultMethod() { + return "default method"; + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/defaultmethod/MessageInterceptor.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/defaultmethod/MessageInterceptor.java new file mode 100644 index 0000000000000..661fa2b83acb0 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/defaultmethod/MessageInterceptor.java @@ -0,0 +1,15 @@ +package io.quarkus.arc.test.interceptors.defaultmethod; + +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; + +@ABinding +@Interceptor +public class MessageInterceptor { + + @AroundInvoke + public Object invoke(InvocationContext context) throws Exception { + return "intercepted:" + context.proceed(); + } +} diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Beer.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Beer.java new file mode 100644 index 0000000000000..8a2e74cf0bc9f --- /dev/null +++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Beer.java @@ -0,0 +1,11 @@ +package io.quarkus.it.panache; + +import javax.persistence.Entity; + +import io.quarkus.hibernate.orm.panache.PanacheEntity; + +@Entity +public class Beer extends PanacheEntity { + + public String name; +} diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/BeerRepository.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/BeerRepository.java new file mode 100644 index 0000000000000..973e1d20f8b60 --- /dev/null +++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/BeerRepository.java @@ -0,0 +1,16 @@ +package io.quarkus.it.panache; + +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; +import javax.transaction.Transactional; + +import io.quarkus.hibernate.orm.panache.PanacheRepository; + +@ApplicationScoped +@Transactional +public class BeerRepository implements PanacheRepository { + public List findOrdered() { + return find("ORDER BY name").list(); + } +} diff --git a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TransactionalRepositoryTest.java b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TransactionalRepositoryTest.java new file mode 100644 index 0000000000000..10a9f3e5f5798 --- /dev/null +++ b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TransactionalRepositoryTest.java @@ -0,0 +1,25 @@ +package io.quarkus.it.panache; + +import javax.inject.Inject; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class TransactionalRepositoryTest { + + @Inject + BeerRepository beerRepository; + + @Test + public void testTransactionalRepository() { + Beer b = new Beer(); + b.name = "IPA"; + beerRepository.persist(b); + + Assertions.assertEquals(1, beerRepository.count()); + } + +}