diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index 6bb15d1a20b61..26f6c576fe68c 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -16,6 +16,7 @@ import java.util.Deque; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -733,7 +734,8 @@ private FilterClassIntrospector createFilterClassIntrospector() { public void transformEndpoints( ResourceScanningResultBuildItem resourceScanningResultBuildItem, ResourceInterceptorsBuildItem resourceInterceptorsBuildItem, - BuildProducer annotationsTransformer) { + BuildProducer annotationsTransformer, + BeanArchiveIndexBuildItem beanArchiveIndexBuildItem) { // all found resources and sub-resources Set allResources = new HashSet<>(); @@ -774,7 +776,7 @@ public void transform(TransformationContext context) { // check if the class is one of resources/sub-resources if (allResources.contains(clazz.name()) && clazz.declaredAnnotation(ResteasyReactiveDotNames.TYPED) == null) { - context.transform().add(createTypedAnnotationInstance(clazz)).done(); + context.transform().add(createTypedAnnotationInstance(clazz, beanArchiveIndexBuildItem)).done(); return; } // check if the class is one of providers, either explicitly declaring the annotation @@ -783,17 +785,52 @@ public void transform(TransformationContext context) { || filtersAndInterceptors.contains(clazz.name().toString())) && clazz.declaredAnnotation(ResteasyReactiveDotNames.TYPED) == null) { // Add @Typed(MyResource.class) - context.transform().add(createTypedAnnotationInstance(clazz)).done(); + context.transform().add(createTypedAnnotationInstance(clazz, beanArchiveIndexBuildItem)).done(); } } })); } - private AnnotationInstance createTypedAnnotationInstance(ClassInfo clazz) { + private AnnotationInstance createTypedAnnotationInstance(ClassInfo clazz, + BeanArchiveIndexBuildItem beanArchiveIndexBuildItem) { + Set interfaceNames = new HashSet<>(); + ClassInfo currentClazz = clazz; + while (!ResteasyReactiveDotNames.OBJECT.equals(currentClazz.name())) { + currentClazz.interfaceNames().forEach(iface -> interfaceNames.add(iface)); + // inspect super class + currentClazz = beanArchiveIndexBuildItem.getIndex().getClassByName(currentClazz.superName()); + } + Set allInterfaces = new HashSet<>(); + recursiveInterfaceSearch(interfaceNames, allInterfaces, beanArchiveIndexBuildItem); + AnnotationValue[] annotationValues = new AnnotationValue[allInterfaces.size() + 1]; + // always add the bean impl class + annotationValues[0] = AnnotationValue.createClassValue("value", + Type.create(clazz.name(), Type.Kind.CLASS)); + Iterator iterator = allInterfaces.iterator(); + for (int i = 1; i < annotationValues.length; i++) { + annotationValues[i] = AnnotationValue.createClassValue("value", + Type.create(iterator.next(), Type.Kind.CLASS)); + } return AnnotationInstance.create(ResteasyReactiveDotNames.TYPED, clazz, new AnnotationValue[] { AnnotationValue.createArrayValue("value", - new AnnotationValue[] { AnnotationValue.createClassValue("value", - Type.create(clazz.name(), Type.Kind.CLASS)) }) }); + annotationValues) }); + } + + private void recursiveInterfaceSearch(Set interfacesToProcess, Set allDiscoveredInterfaces, + BeanArchiveIndexBuildItem beanArchiveIndexBuildItem) { + allDiscoveredInterfaces.addAll(interfacesToProcess); + Set additionalInterfacesToProcess = new HashSet<>(); + for (DotName name : interfacesToProcess) { + ClassInfo clazz = beanArchiveIndexBuildItem.getIndex().getClassByName(name); + if (clazz != null) { + // get all interface that this interface extends + additionalInterfacesToProcess.addAll(clazz.interfaceNames()); + } + } + if (!additionalInterfacesToProcess.isEmpty()) { + // recursively process newly found interfaces + recursiveInterfaceSearch(additionalInterfacesToProcess, allDiscoveredInterfaces, beanArchiveIndexBuildItem); + } } private Collection additionalContextTypes(List contextTypeBuildItems) { diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/Alpha.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/Alpha.java new file mode 100644 index 0000000000000..2839dd5baa3fe --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/Alpha.java @@ -0,0 +1,4 @@ +package io.quarkus.rest.client.reactive.beanTypes; + +public interface Alpha extends Delta { +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/Beta.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/Beta.java new file mode 100644 index 0000000000000..27f08ec570146 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/Beta.java @@ -0,0 +1,4 @@ +package io.quarkus.rest.client.reactive.beanTypes; + +public interface Beta extends Delta { +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/Charlie.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/Charlie.java new file mode 100644 index 0000000000000..8b71425e41eb4 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/Charlie.java @@ -0,0 +1,4 @@ +package io.quarkus.rest.client.reactive.beanTypes; + +public interface Charlie extends Beta { +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/Client.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/Client.java new file mode 100644 index 0000000000000..a98eaaa3040d2 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/Client.java @@ -0,0 +1,17 @@ +package io.quarkus.rest.client.reactive.beanTypes; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@Path("/") +@RegisterRestClient(configKey = "hello2") +public interface Client extends Alpha { + @GET + @Produces(MediaType.TEXT_PLAIN) + String test(); + +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/ClientMock.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/ClientMock.java new file mode 100644 index 0000000000000..b6bb9b45b4a0a --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/ClientMock.java @@ -0,0 +1,20 @@ +package io.quarkus.rest.client.reactive.beanTypes; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Alternative; + +import org.eclipse.microprofile.rest.client.inject.RestClient; + +import io.quarkus.arc.Priority; + +@Alternative() +@Priority(1) +@ApplicationScoped +@RestClient +public class ClientMock implements Client, Charlie { + + @Override + public String test() { + return "hello from " + ClientMock.class; + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/Delta.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/Delta.java new file mode 100644 index 0000000000000..43e5505b63579 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/Delta.java @@ -0,0 +1,4 @@ +package io.quarkus.rest.client.reactive.beanTypes; + +public interface Delta { +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/MyBean.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/MyBean.java new file mode 100644 index 0000000000000..63b479b3061d9 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/MyBean.java @@ -0,0 +1,18 @@ +package io.quarkus.rest.client.reactive.beanTypes; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.eclipse.microprofile.rest.client.inject.RestClient; + +@ApplicationScoped +public class MyBean { + + @Inject + @RestClient + Client client; + + String test() { + return client.test(); + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/ResourceBeanTypeTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/ResourceBeanTypeTest.java new file mode 100644 index 0000000000000..fd4b9f80157af --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/beanTypes/ResourceBeanTypeTest.java @@ -0,0 +1,47 @@ +package io.quarkus.rest.client.reactive.beanTypes; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Set; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; +import javax.inject.Inject; + +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.test.QuarkusUnitTest; + +/** + * Tests that resources processed by RR have bean types equivalent to that of the impl class plus all interfaces in + * their hierarchy + */ +public class ResourceBeanTypeTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(Client.class, ClientMock.class, MyBean.class, Alpha.class, Beta.class, + Charlie.class, Delta.class)); + + @Inject + MyBean myBean; + + @Test + void shouldHaveAllInterfaceTypes() { + // firstly, sanity check + assertThat(myBean.test()).isEqualTo("hello from " + ClientMock.class); + + // now see what types does the Client bean have - the impl class and all interfaces should be in place + BeanManager beanManager = Arc.container().beanManager(); + Set> beans = beanManager.getBeans(Client.class, RestClient.LITERAL); + Bean resolvedBean = beanManager.resolve(beans); + assertThat(resolvedBean.getScope()).isEqualTo(ApplicationScoped.class); + assertThat(resolvedBean.getTypes()).contains(Client.class, ClientMock.class, Alpha.class, Beta.class, Charlie.class, + Delta.class); + } +}