diff --git a/extensions/oidc-client-reactive-filter/deployment/src/main/java/io/quarkus/oidc/client/reactive/filter/deployment/OidcClientReactiveFilterBuildStep.java b/extensions/oidc-client-reactive-filter/deployment/src/main/java/io/quarkus/oidc/client/reactive/filter/deployment/OidcClientReactiveFilterBuildStep.java index 6c5bfd560a1c88..9ccbdfbcce8728 100644 --- a/extensions/oidc-client-reactive-filter/deployment/src/main/java/io/quarkus/oidc/client/reactive/filter/deployment/OidcClientReactiveFilterBuildStep.java +++ b/extensions/oidc-client-reactive-filter/deployment/src/main/java/io/quarkus/oidc/client/reactive/filter/deployment/OidcClientReactiveFilterBuildStep.java @@ -4,6 +4,7 @@ import io.quarkus.deployment.Feature; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.AdditionalIndexedClassesBuildItem; import io.quarkus.deployment.builditem.EnableAllSecurityServicesBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; @@ -24,8 +25,11 @@ EnableAllSecurityServicesBuildItem security() { @BuildStep(onlyIf = IsEnabled.class) void registerProvider(BuildProducer additionalBeans, - BuildProducer reflectiveClass) { + BuildProducer reflectiveClass, + BuildProducer additionalIndexedClassesBuildItem) { additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf(OidcClientRequestReactiveFilter.class)); + additionalIndexedClassesBuildItem + .produce(new AdditionalIndexedClassesBuildItem(OidcClientRequestReactiveFilter.class.getName())); reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, OidcClientRequestReactiveFilter.class)); } } 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..5554b739186809 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 @@ -22,6 +22,8 @@ import javax.enterprise.context.SessionScoped; import javax.enterprise.inject.Typed; import javax.inject.Singleton; +import javax.ws.rs.Priorities; +import javax.ws.rs.RuntimeType; import javax.ws.rs.core.MediaType; import org.eclipse.microprofile.config.Config; @@ -178,9 +180,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 +224,42 @@ void registerProvidersFromAnnotations(CombinedIndexBuildItem indexBuildItem, constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(AnnotationRegisteredProviders.class), constructor.getThis()); + for (AnnotationInstance instance : index.getAnnotations(ResteasyReactiveDotNames.PROVIDER)) { + ClassInfo providerClass = instance.target().asClass(); + + // ignore providers annotated with `@ConstrainedTo(SERVER)` + AnnotationInstance constrainedToInstance = providerClass + .classAnnotation(ResteasyReactiveDotNames.CONSTRAINED_TO); + if (constrainedToInstance != null) { + if (RuntimeType.valueOf(constrainedToInstance.value().asEnum()) == RuntimeType.SERVER) { + continue; + } + } + + 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 +273,21 @@ 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)); + int priority = defaultPriority; + if (providerClass == null) { + log.warnv("Unindexed provider class {0}. The priority of the provider will be set to {1}. ", className, + defaultPriority); + } else { + AnnotationInstance priorityAnnoOnProvider = providerClass.classAnnotation(ResteasyReactiveDotNames.PRIORITY); + if (priorityAnnoOnProvider != null) { + priority = priorityAnnoOnProvider.value().asInt(); + } + } + return priority; + } + @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/GlobalRequestFilterConstrainedToServer.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/GlobalRequestFilterConstrainedToServer.java new file mode 100644 index 00000000000000..815a7c28f7155e --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/GlobalRequestFilterConstrainedToServer.java @@ -0,0 +1,17 @@ +package io.quarkus.rest.client.reactive.provider; + +import javax.ws.rs.ConstrainedTo; +import javax.ws.rs.RuntimeType; +import javax.ws.rs.ext.Provider; + +import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext; +import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestFilter; + +@Provider +@ConstrainedTo(RuntimeType.SERVER) +public class GlobalRequestFilterConstrainedToServer implements ResteasyReactiveClientRequestFilter { + @Override + public void filter(ResteasyReactiveClientRequestContext requestContext) { + throw new RuntimeException("Invoked filter that is constrained to server"); + } +} 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..f9ad62f57ce38d --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/provider/ProviderTest.java @@ -0,0 +1,76 @@ +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, GlobalRequestFilterConstrainedToServer.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); } } diff --git a/tcks/microprofile-rest-client-reactive/pom.xml b/tcks/microprofile-rest-client-reactive/pom.xml index 84a10536fb84e0..27e003c4f8430e 100644 --- a/tcks/microprofile-rest-client-reactive/pom.xml +++ b/tcks/microprofile-rest-client-reactive/pom.xml @@ -27,6 +27,8 @@ true true ${wiremock.server.port} + org.eclipse.microprofile.rest.client + microprofile-rest-client-tck