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 b5e88a47fc539..8a7f8143401a6 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,6 +1,8 @@ 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.CLIENT_HEADER_PARAM; +import static io.quarkus.rest.client.reactive.deployment.DotNames.CLIENT_HEADER_PARAMS; 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; @@ -45,6 +47,7 @@ import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.CustomScopeAnnotationsBuildItem; import io.quarkus.arc.deployment.GeneratedBeanBuildItem; import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; @@ -94,6 +97,14 @@ class RestClientReactiveProcessor { private static final String DISABLE_SMART_PRODUCES_QUARKUS = "quarkus.rest-client.disable-smart-produces"; private static final String KOTLIN_INTERFACE_DEFAULT_IMPL_SUFFIX = "$DefaultImpls"; + private static final Set SKIP_COPYING_ANNOTATIONS_TO_GENERATED_CLASS = Set.of( + REGISTER_REST_CLIENT, + REGISTER_PROVIDER, + REGISTER_PROVIDERS, + CLIENT_HEADER_PARAM, + CLIENT_HEADER_PARAMS, + REGISTER_CLIENT_HEADERS); + @BuildStep void announceFeature(BuildProducer features) { features.produce(new FeatureBuildItem(Feature.REST_CLIENT_REACTIVE)); @@ -331,6 +342,7 @@ AdditionalBeanBuildItem registerProviderBeans(CombinedIndexBuildItem combinedInd @Record(ExecutionTime.STATIC_INIT) void addRestClientBeans(Capabilities capabilities, CombinedIndexBuildItem combinedIndexBuildItem, + CustomScopeAnnotationsBuildItem scopes, BuildProducer generatedBeans, RestClientReactiveConfig clientConfig, RestClientRecorder recorder) { @@ -389,6 +401,19 @@ void addRestClientBeans(Capabilities capabilities, classCreator.addAnnotation(Typed.class.getName(), RetentionPolicy.RUNTIME) .addValue("value", new org.objectweb.asm.Type[] { asmType }); + for (AnnotationInstance annotation : jaxrsInterface.classAnnotations()) { + if (SKIP_COPYING_ANNOTATIONS_TO_GENERATED_CLASS.contains(annotation.name())) { + continue; + } + + // scope annotation is added to the generated class already, see above + if (scopes.isScopeIn(Set.of(annotation))) { + continue; + } + + classCreator.addAnnotation(annotation); + } + // CONSTRUCTOR: MethodCreator constructor = classCreator @@ -525,7 +550,7 @@ private ScopeInfo computeDefaultScope(Capabilities capabilities, Config config, if (scopeToUse == null) { log.warnf("Unsupported default scope {} provided for rest client {}. Defaulting to {}", scope, restClientInterface.name(), globalDefaultScope.getName()); - scopeToUse = BuiltinScope.DEPENDENT.getInfo(); + scopeToUse = globalDefaultScope.getInfo(); } } else { final Set annotations = restClientInterface.annotations().keySet(); diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientReactiveCDIWrapperBase.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientReactiveCDIWrapperBase.java index f490dc9a420b9..54b01a1450932 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientReactiveCDIWrapperBase.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientReactiveCDIWrapperBase.java @@ -7,6 +7,8 @@ import org.jboss.logging.Logger; +import io.quarkus.arc.NoClassInterceptors; + public abstract class RestClientReactiveCDIWrapperBase implements Closeable { private static final Logger log = Logger.getLogger(RestClientReactiveCDIWrapperBase.class); @@ -17,11 +19,13 @@ public RestClientReactiveCDIWrapperBase(Class jaxrsInterface, String baseUriF } @Override + @NoClassInterceptors public void close() throws IOException { delegate.close(); } @PreDestroy + @NoClassInterceptors public void destroy() { try { close(); @@ -32,6 +36,7 @@ public void destroy() { // used by generated code @SuppressWarnings("unused") + @NoClassInterceptors public Object getDelegate() { return delegate; } diff --git a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/FaultToleranceScanner.java b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/FaultToleranceScanner.java index 849d004c98b55..e6dbcce1a75f1 100644 --- a/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/FaultToleranceScanner.java +++ b/extensions/smallrye-fault-tolerance/deployment/src/main/java/io/quarkus/smallrye/faulttolerance/deployment/FaultToleranceScanner.java @@ -84,6 +84,11 @@ void forEachMethod(ClassInfo clazz, Consumer action) { // synthetic methods can't be intercepted continue; } + if (annotationStore.hasAnnotation(method, io.quarkus.arc.processor.DotNames.NO_CLASS_INTERCEPTORS) + && !annotationStore.hasAnyAnnotation(method, DotNames.FT_ANNOTATIONS)) { + // methods annotated @NoClassInterceptors and not annotated with an interceptor binding are not intercepted + continue; + } action.accept(method); } diff --git a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java index 0a9a432219503..617fd23c9e9f0 100644 --- a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java +++ b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java @@ -41,6 +41,9 @@ public class ClientCallingResource { @RestClient FaultToleranceClient faultToleranceClient; + @RestClient + FaultToleranceOnInterfaceClient faultToleranceOnInterfaceClient; + @Inject InMemorySpanExporter inMemorySpanExporter; @@ -139,6 +142,16 @@ void init(@Observes Router router) { router.route("/call-with-fault-tolerance").blockingHandler(rc -> { rc.end(faultToleranceClient.helloWithFallback()); }); + + router.route("/call-with-fault-tolerance-on-interface").blockingHandler(rc -> { + String exception = ""; + try { + faultToleranceOnInterfaceClient.hello(); + } catch (Exception e) { + exception = e.getClass().getSimpleName(); + } + rc.end(exception); + }); } private Future success(RoutingContext rc, String body) { diff --git a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/FaultToleranceOnInterfaceClient.java b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/FaultToleranceOnInterfaceClient.java new file mode 100644 index 0000000000000..83cdab622d461 --- /dev/null +++ b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/FaultToleranceOnInterfaceClient.java @@ -0,0 +1,22 @@ +package io.quarkus.it.rest.client.main; + +import java.time.temporal.ChronoUnit; + +import javax.ws.rs.Consumes; +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.faulttolerance.CircuitBreaker; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@Path("/unprocessable") +@RegisterRestClient(configKey = "w-fault-tolerance") +@CircuitBreaker(requestVolumeThreshold = 2, delay = 1, delayUnit = ChronoUnit.MINUTES) +public interface FaultToleranceOnInterfaceClient { + @GET + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.TEXT_PLAIN) + String hello(); +} diff --git a/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/BasicTest.java b/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/BasicTest.java index 5d53b33ea1634..6ce6783e86963 100644 --- a/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/BasicTest.java +++ b/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/BasicTest.java @@ -92,6 +92,21 @@ void shouldInterceptDefaultMethod() { .body(equalTo("Hello fallback!")); } + @Test + void shouldApplyInterfaceLevelInterceptorBinding() { + for (int i = 0; i < 2; i++) { + RestAssured.with().body(baseUrl).post("/call-with-fault-tolerance-on-interface") + .then() + .statusCode(200) + .body(equalTo("ClientWebApplicationException")); + } + + RestAssured.with().body(baseUrl).post("/call-with-fault-tolerance-on-interface") + .then() + .statusCode(200) + .body(equalTo("CircuitBreakerOpenException")); + } + @Test void shouldCreateClientSpans() { // Reset captured traces