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 b430fb8a37541d..1e157164cff0fa 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:
+ *
+ * - puts all the providers registered by the @RegisterProvider annotation in a
+ * map using the {@link AnnotationRegisteredProviders#addProviders(String, Map)} method
+ * - registers all the provider implementations annotated with @Provider using
+ * {@link AnnotationRegisteredProviders#addGlobalProvider(Class, int)}
+ *
+ *
*
* @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 00000000000000..57f2a95cf16571
--- /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 00000000000000..b26bac87f264b0
--- /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 00000000000000..64fc0ae57d4ea0
--- /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 00000000000000..d94255de569764
--- /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 00000000000000..6c4ab9f6664258
--- /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 00000000000000..45e8178d2dee69
--- /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 00000000000000..4c4a472e5ef800
--- /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 00000000000000..d29961b9788035
--- /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 72d50cebc9fd0b..2dd034ae787937 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);
}
}