From 4c717befd80a3c6b50588700ac99757f4d4b4b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Szynkiewicz?= Date: Fri, 23 Jul 2021 10:19:31 +0200 Subject: [PATCH] Rest Client Reactive: globally register providers annotated with @Provider fixes #18560 --- .../RestClientReactiveProcessor.java | 59 ++++++++++----- .../provider/GlobalRequestFilter.java | 21 ++++++ .../provider/GlobalResponseFilter.java | 22 ++++++ .../provider/GlobalResponseFilterLowPrio.java | 23 ++++++ .../client/reactive/provider/HelloClient.java | 19 +++++ .../provider/HelloClientWithFilter.java | 21 ++++++ .../provider/ProviderPriorityTest.java | 64 ++++++++++++++++ .../reactive/provider/ProviderTest.java | 75 +++++++++++++++++++ .../provider/ResponseFilterLowestPrio.java | 21 ++++++ .../AnnotationRegisteredProviders.java | 15 +++- 10 files changed, 319 insertions(+), 21 deletions(-) create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/GlobalRequestFilter.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/GlobalResponseFilter.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/GlobalResponseFilterLowPrio.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/HelloClient.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/HelloClientWithFilter.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ProviderPriorityTest.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ProviderTest.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ResponseFilterLowestPrio.java diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java index b430fb8a37541..1e157164cff0f 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java @@ -1,9 +1,7 @@ package io.quarkus.rest.client.reactive.deployment; import static io.quarkus.arc.processor.MethodDescriptors.MAP_PUT; -import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_CLIENT_HEADERS; -import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_PROVIDER; -import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_PROVIDERS; +import static io.quarkus.rest.client.reactive.deployment.DotNames.*; import static org.jboss.resteasy.reactive.common.processor.EndpointIndexer.CDI_WRAPPER_SUFFIX; import static org.jboss.resteasy.reactive.common.processor.scanning.ResteasyReactiveScanner.BUILTIN_HTTP_ANNOTATIONS_TO_METHOD; @@ -22,6 +20,7 @@ import javax.enterprise.context.SessionScoped; import javax.enterprise.inject.Typed; import javax.inject.Singleton; +import javax.ws.rs.Priorities; import javax.ws.rs.core.MediaType; import org.eclipse.microprofile.config.Config; @@ -29,15 +28,7 @@ import org.eclipse.microprofile.rest.client.ext.QueryParamStyle; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import org.eclipse.microprofile.rest.client.inject.RestClient; -import org.jboss.jandex.AnnotationInstance; -import org.jboss.jandex.AnnotationTarget; -import org.jboss.jandex.AnnotationValue; -import org.jboss.jandex.ClassInfo; -import org.jboss.jandex.CompositeIndex; -import org.jboss.jandex.DotName; -import org.jboss.jandex.IndexView; -import org.jboss.jandex.MethodInfo; -import org.jboss.jandex.Type; +import org.jboss.jandex.*; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames; @@ -178,9 +169,14 @@ void registerHeaderFactoryBeans(CombinedIndexBuildItem index, } /** - * Creates an implementation of `AnnotationRegisteredProviders` class with a constructor that - * puts all the providers registered by the @RegisterProvider annotation in a - * map using the {@link AnnotationRegisteredProviders#addProviders(String, Map)} method + * Creates an implementation of `AnnotationRegisteredProviders` class with a constructor that: + * + * * * @param indexBuildItem index * @param generatedBeans build producer for generated beans @@ -217,14 +213,34 @@ void registerProvidersFromAnnotations(CombinedIndexBuildItem indexBuildItem, constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(AnnotationRegisteredProviders.class), constructor.getThis()); + for (AnnotationInstance instance : index.getAnnotations(ResteasyReactiveDotNames.PROVIDER)) { + // TODO: we may want to filter out stuff that is server side only + ClassInfo providerClass = instance.target().asClass(); + + int priority = getAnnotatedPriority(index, providerClass.name().toString(), Priorities.USER); + + constructor.invokeVirtualMethod( + MethodDescriptor.ofMethod(AnnotationRegisteredProviders.class, "addGlobalProvider", + void.class, Class.class, + int.class), + constructor.getThis(), constructor.loadClass(providerClass.name().toString()), + constructor.load(priority)); + } + for (Map.Entry> annotationsForClass : annotationsByClassName.entrySet()) { ResultHandle map = constructor.newInstance(MethodDescriptor.ofConstructor(HashMap.class)); for (AnnotationInstance value : annotationsForClass.getValue()) { String className = value.value().asString(); - AnnotationValue priority = value.value("priority"); + AnnotationValue priorityAnnotationValue = value.value("priority"); + int priority; + if (priorityAnnotationValue == null) { + priority = getAnnotatedPriority(index, className, Priorities.USER); + } else { + priority = priorityAnnotationValue.asInt(); + } constructor.invokeInterfaceMethod(MAP_PUT, map, constructor.loadClass(className), - constructor.load(priority == null ? -1 : priority.asInt())); + constructor.load(priority)); } constructor.invokeVirtualMethod( MethodDescriptor.ofMethod(AnnotationRegisteredProviders.class, "addProviders", void.class, String.class, @@ -238,6 +254,15 @@ void registerProvidersFromAnnotations(CombinedIndexBuildItem indexBuildItem, unremovableBeans.produce(UnremovableBeanBuildItem.beanClassNames(annotationRegisteredProvidersImpl)); } + private int getAnnotatedPriority(IndexView index, String className, int defaultPriority) { + ClassInfo providerClass = index.getClassByName(DotName.createSimple(className)); + AnnotationInstance priorityAnnoOnProvider = providerClass.classAnnotation(ResteasyReactiveDotNames.PRIORITY); + if (priorityAnnoOnProvider != null) { + defaultPriority = priorityAnnoOnProvider.value().asInt(); + } + return defaultPriority; + } + @BuildStep AdditionalBeanBuildItem registerProviderBeans(CombinedIndexBuildItem combinedIndex) { IndexView index = combinedIndex.getIndex(); diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/GlobalRequestFilter.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/GlobalRequestFilter.java new file mode 100644 index 0000000000000..57f2a95cf1657 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/GlobalRequestFilter.java @@ -0,0 +1,21 @@ +package io.quarkus.rest.client.reactive.provider; + +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.Provider; + +import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext; +import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestFilter; + +@Provider +public class GlobalRequestFilter implements ResteasyReactiveClientRequestFilter { + public static final int STATUS = 233; + + public static boolean abort = false; + + @Override + public void filter(ResteasyReactiveClientRequestContext requestContext) { + if (abort) { + requestContext.abortWith(Response.status(STATUS).build()); + } + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/GlobalResponseFilter.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/GlobalResponseFilter.java new file mode 100644 index 0000000000000..b26bac87f264b --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/GlobalResponseFilter.java @@ -0,0 +1,22 @@ +package io.quarkus.rest.client.reactive.provider; + +import javax.annotation.Priority; +import javax.ws.rs.client.ClientResponseContext; +import javax.ws.rs.ext.Provider; + +import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext; +import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientResponseFilter; + +@Provider +@Priority(20) +public class GlobalResponseFilter implements ResteasyReactiveClientResponseFilter { + + public static final int STATUS = 222; + + @Override + public void filter(ResteasyReactiveClientRequestContext requestContext, ClientResponseContext responseContext) { + if (responseContext.getStatus() != GlobalRequestFilter.STATUS) { + responseContext.setStatus(STATUS); + } + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/GlobalResponseFilterLowPrio.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/GlobalResponseFilterLowPrio.java new file mode 100644 index 0000000000000..64fc0ae57d4ea --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/GlobalResponseFilterLowPrio.java @@ -0,0 +1,23 @@ +package io.quarkus.rest.client.reactive.provider; + +import javax.annotation.Priority; +import javax.ws.rs.client.ClientResponseContext; +import javax.ws.rs.ext.Provider; + +import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext; +import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientResponseFilter; + +@Priority(10) // lower prio here means executed later +@Provider +public class GlobalResponseFilterLowPrio implements ResteasyReactiveClientResponseFilter { + + public static final int STATUS = 244; + public static boolean skip = false; + + @Override + public void filter(ResteasyReactiveClientRequestContext requestContext, ClientResponseContext responseContext) { + if (!skip) { + responseContext.setStatus(STATUS); + } + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/HelloClient.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/HelloClient.java new file mode 100644 index 0000000000000..d94255de56976 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/HelloClient.java @@ -0,0 +1,19 @@ +package io.quarkus.rest.client.reactive.provider; + +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@Path("/hello") +@Produces(MediaType.TEXT_PLAIN) +@Consumes(MediaType.TEXT_PLAIN) +@RegisterRestClient +public interface HelloClient { + @POST + Response echo(String name); +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/HelloClientWithFilter.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/HelloClientWithFilter.java new file mode 100644 index 0000000000000..6c4ab9f666425 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/HelloClientWithFilter.java @@ -0,0 +1,21 @@ +package io.quarkus.rest.client.reactive.provider; + +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@Path("/hello") +@Produces(MediaType.TEXT_PLAIN) +@Consumes(MediaType.TEXT_PLAIN) +@RegisterProvider(ResponseFilterLowestPrio.class) +@RegisterRestClient +public interface HelloClientWithFilter { + @POST + Response echo(String name); +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ProviderPriorityTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ProviderPriorityTest.java new file mode 100644 index 0000000000000..45e8178d2dee6 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ProviderPriorityTest.java @@ -0,0 +1,64 @@ +package io.quarkus.rest.client.reactive.provider; + +import static io.quarkus.rest.client.reactive.RestClientTestUtil.setUrlForClass; +import static org.assertj.core.api.Assertions.assertThat; + +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.rest.client.reactive.HelloResource; +import io.quarkus.test.QuarkusUnitTest; + +public class ProviderPriorityTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(HelloResource.class, + HelloClient.class, + HelloClientWithFilter.class, + ResponseFilterLowestPrio.class, + GlobalResponseFilter.class, + GlobalResponseFilterLowPrio.class) + .addAsResource( + new StringAsset(setUrlForClass(HelloClient.class) + + setUrlForClass(HelloClientWithFilter.class)), + "application.properties")); + + @RestClient + HelloClient helloClient; + + @RestClient + HelloClientWithFilter helloClientWithFilter; + + @AfterEach + void cleanUp() { + GlobalResponseFilterLowPrio.skip = false; + } + + @Test + void shouldApplyLocalLowestPrioFilterLast() { + Response response = helloClientWithFilter.echo("foo"); + assertThat(response.getStatus()).isEqualTo(ResponseFilterLowestPrio.STATUS); + } + + @Test + void shouldApplyLowPrioFilterLast() { + Response response = helloClient.echo("foo"); + assertThat(response.getStatus()).isEqualTo(GlobalResponseFilterLowPrio.STATUS); + } + + @Test + void shouldApplyHighPrioFilter() { + GlobalResponseFilterLowPrio.skip = true; + Response response = helloClient.echo("foo"); + assertThat(response.getStatus()).isEqualTo(GlobalResponseFilter.STATUS); + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ProviderTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ProviderTest.java new file mode 100644 index 0000000000000..4c4a472e5ef80 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ProviderTest.java @@ -0,0 +1,75 @@ +package io.quarkus.rest.client.reactive.provider; + +import static io.quarkus.rest.client.reactive.RestClientTestUtil.setUrlForClass; +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.URI; + +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.rest.client.reactive.HelloResource; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.common.http.TestHTTPResource; + +public class ProviderTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(HelloResource.class, HelloClient.class, GlobalRequestFilter.class, GlobalResponseFilter.class) + .addAsResource( + new StringAsset(setUrlForClass(HelloClient.class)), + "application.properties")); + + @RestClient + HelloClient helloClient; + + @TestHTTPResource + URI baseUri; + + @AfterEach + public void cleanUp() { + GlobalRequestFilter.abort = false; + } + + @Test + void shouldUseGlobalRequestFilterForInjectedClient() { + GlobalRequestFilter.abort = true; + Response response = helloClient.echo("Michał"); + assertThat(response.getStatus()).isEqualTo(GlobalRequestFilter.STATUS); + } + + @Test + void shouldUseGlobalResponseFilterForInjectedClient() { + Response response = helloClient.echo("Michał"); + assertThat(response.getStatus()).isEqualTo(GlobalResponseFilter.STATUS); + } + + @Test + void shouldUseGlobalRequestFilterForBuiltClient() { + GlobalRequestFilter.abort = true; + Response response = helloClient().echo("Michał"); + assertThat(response.getStatus()).isEqualTo(GlobalRequestFilter.STATUS); + } + + @Test + void shouldUseGlobalResponseFilterForBuiltClient() { + Response response = helloClient().echo("Michał"); + assertThat(response.getStatus()).isEqualTo(GlobalResponseFilter.STATUS); + } + + private HelloClient helloClient() { + return RestClientBuilder.newBuilder() + .baseUri(baseUri) + .build(HelloClient.class); + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ResponseFilterLowestPrio.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ResponseFilterLowestPrio.java new file mode 100644 index 0000000000000..d29961b978803 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ResponseFilterLowestPrio.java @@ -0,0 +1,21 @@ +package io.quarkus.rest.client.reactive.provider; + +import javax.annotation.Priority; +import javax.ws.rs.client.ClientResponseContext; + +import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext; +import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientResponseFilter; + +@Priority(1) +public class ResponseFilterLowestPrio implements ResteasyReactiveClientResponseFilter { + + public static final int STATUS = 266; + public static boolean skip = false; + + @Override + public void filter(ResteasyReactiveClientRequestContext requestContext, ClientResponseContext responseContext) { + if (!skip) { + responseContext.setStatus(STATUS); + } + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/AnnotationRegisteredProviders.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/AnnotationRegisteredProviders.java index 72d50cebc9fd0..2dd034ae78793 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/AnnotationRegisteredProviders.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/AnnotationRegisteredProviders.java @@ -1,19 +1,26 @@ package io.quarkus.rest.client.reactive.runtime; -import java.util.Collections; import java.util.HashMap; import java.util.Map; public abstract class AnnotationRegisteredProviders { private final Map, Integer>> providers = new HashMap<>(); + private final Map, Integer> globalProviders = new HashMap<>(); public Map, Integer> getProviders(Class clientClass) { - Map, Integer> providersForClass = providers.get(clientClass.getName()); - return providersForClass == null ? Collections.emptyMap() : providersForClass; + return providers.getOrDefault(clientClass.getName(), globalProviders); } // used by generated code + // MUST be called after addGlobalProvider public void addProviders(String className, Map, Integer> providersForClass) { - this.providers.put(className, providersForClass); + Map, Integer> providers = new HashMap<>(providersForClass); + providers.putAll(globalProviders); + this.providers.put(className, providers); + } + + // used by generated code + public void addGlobalProvider(Class providerClass, int priority) { + globalProviders.put(providerClass, priority); } }