diff --git a/docs/src/main/asciidoc/resteasy-reactive.adoc b/docs/src/main/asciidoc/resteasy-reactive.adoc index 9623731d82108d..33b9bfd39e3324 100644 --- a/docs/src/main/asciidoc/resteasy-reactive.adoc +++ b/docs/src/main/asciidoc/resteasy-reactive.adoc @@ -1812,3 +1812,19 @@ public class ResourceForApp1Only { ---- Please note that if a JAX-RS Application has been detected and the method `getClasses()` and/or `getSingletons()` has/have been overridden, Quarkus will ignore the build time conditions and consider only what has been defined in the JAX-RS Application. + + +== RESTEasy Reactive client +In addition to the Server side, RESTEasy Reactive comes with a new MicroProfile Rest Client implementation that is non-blocking at its core. + +To use it, add the `quarkus-rest-client-microprofile` extension to your project. +The client can be used as described in https://quarkus.io/guides/rest-client[Using the REST Client guide] and specified in the https://download.eclipse.org/microprofile/microprofile-rest-client-1.4.1/microprofile-rest-client-1.4.1.html[MicroProfile Rest Client specification], with a few exceptions: + +- the extension name +- the default scope of the client for the new extension is `@ApplicationScoped` while the `quarkus-rest-client` defaults to `@Dependent` +To change this behavior, set the `quarkus.rest.client.scope` property to the fully qualified scope name. +- it is not possible to set `HostnameVerifier` or `SSLContext` +- a few things that don't make sense for a non-blocking implementations, such as setting the `ExecutorService`, don't work + +It is important to note that the `quarkus-rest-client` extension may not work properly with RESTEasy Reactive. + diff --git a/extensions/resteasy-reactive/rest-client-microprofile/deployment/src/main/java/io/quarkus/resteasy/reactive/client/microprofile/deployment/ReactiveResteasyMpClientProcessor.java b/extensions/resteasy-reactive/rest-client-microprofile/deployment/src/main/java/io/quarkus/resteasy/reactive/client/microprofile/deployment/ReactiveResteasyMpClientProcessor.java index 1d462c29ecab8b..39f549a5f2f5bd 100644 --- a/extensions/resteasy-reactive/rest-client-microprofile/deployment/src/main/java/io/quarkus/resteasy/reactive/client/microprofile/deployment/ReactiveResteasyMpClientProcessor.java +++ b/extensions/resteasy-reactive/rest-client-microprofile/deployment/src/main/java/io/quarkus/resteasy/reactive/client/microprofile/deployment/ReactiveResteasyMpClientProcessor.java @@ -53,6 +53,7 @@ import io.quarkus.resteasy.reactive.client.deployment.RestClientDefaultProducesBuildItem; import io.quarkus.resteasy.reactive.client.microprofile.HeaderCapturingServerFilter; import io.quarkus.resteasy.reactive.client.microprofile.HeaderContainer; +import io.quarkus.resteasy.reactive.client.microprofile.ReactiveResteasyMpClientConfig; import io.quarkus.resteasy.reactive.client.microprofile.RestClientCDIDelegateBuilder; import io.quarkus.resteasy.reactive.client.microprofile.recorder.RestClientRecorder; import io.quarkus.resteasy.reactive.spi.ContainerRequestFilterBuildItem; @@ -128,7 +129,8 @@ void registerHeaderFactoryBeans(CombinedIndexBuildItem index, @BuildStep void addRestClientBeans(Capabilities capabilities, CombinedIndexBuildItem combinedIndexBuildItem, - BuildProducer generatedBeans) { + BuildProducer generatedBeans, + ReactiveResteasyMpClientConfig clientConfig) { CompositeIndex index = CompositeIndex.create(combinedIndexBuildItem.getIndex()); Set registerRestClientAnnos = new HashSet<>(index.getAnnotations(REGISTER_REST_CLIENT)); @@ -154,7 +156,7 @@ void addRestClientBeans(Capabilities capabilities, // CLASS LEVEL final String configPrefix = computeConfigPrefix(jaxrsInterface.name(), registerRestClient); final ScopeInfo scope = computeDefaultScope(capabilities, ConfigProvider.getConfig(), jaxrsInterface, - configPrefix); + configPrefix, clientConfig); // add a scope annotation, e.g. @Singleton classCreator.addAnnotation(scope.getDotName().toString()); classCreator.addAnnotation(RestClient.class); @@ -261,11 +263,19 @@ private String computeConfigPrefix(DotName interfaceName, AnnotationInstance reg private ScopeInfo computeDefaultScope(Capabilities capabilities, Config config, ClassInfo restClientInterface, - String configPrefix) { + String configPrefix, + ReactiveResteasyMpClientConfig mpClientConfig) { ScopeInfo scopeToUse = null; final Optional scopeConfig = config .getOptionalValue(String.format(RestClientCDIDelegateBuilder.REST_SCOPE_FORMAT, configPrefix), String.class); + BuiltinScope globalDefaultScope = BuiltinScope.from(DotName.createSimple(mpClientConfig.scope)); + if (globalDefaultScope == null) { + log.warnv("Unable to map the global rest client scope: '{}' to a scope. Using @ApplicationScoped", + mpClientConfig.scope); + globalDefaultScope = BuiltinScope.APPLICATION; + } + if (scopeConfig.isPresent()) { final DotName scope = DotName.createSimple(scopeConfig.get()); final BuiltinScope builtinScope = BuiltinScope.from(scope); @@ -278,9 +288,8 @@ private ScopeInfo computeDefaultScope(Capabilities capabilities, Config config, } if (scopeToUse == null) { - log.warn(String.format( - "Unsupported default scope %s provided for rest client %s. Defaulting to @Dependent.", - scope, restClientInterface.name())); + log.warnf("Unsupported default scope {} provided for rest client {}. Defaulting to {}", + scope, restClientInterface.name(), globalDefaultScope.getName()); scopeToUse = BuiltinScope.DEPENDENT.getInfo(); } } else { @@ -299,6 +308,6 @@ private ScopeInfo computeDefaultScope(Capabilities capabilities, Config config, } // Initialize a default @Dependent scope as per the spec - return scopeToUse != null ? scopeToUse : BuiltinScope.DEPENDENT.getInfo(); + return scopeToUse != null ? scopeToUse : globalDefaultScope.getInfo(); } } diff --git a/extensions/resteasy-reactive/rest-client-microprofile/deployment/src/test/java/io/quarkus/resteasy/reactive/client/BasicMpRestClientTest.java b/extensions/resteasy-reactive/rest-client-microprofile/deployment/src/test/java/io/quarkus/resteasy/reactive/client/BasicMpRestClientTest.java index 9bc13e207859b6..faecf794664b7f 100644 --- a/extensions/resteasy-reactive/rest-client-microprofile/deployment/src/test/java/io/quarkus/resteasy/reactive/client/BasicMpRestClientTest.java +++ b/extensions/resteasy-reactive/rest-client-microprofile/deployment/src/test/java/io/quarkus/resteasy/reactive/client/BasicMpRestClientTest.java @@ -2,15 +2,20 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.net.MalformedURLException; +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.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.arc.Arc; import io.quarkus.test.QuarkusUnitTest; public class BasicMpRestClientTest { @@ -29,7 +34,15 @@ void shouldHello() { } @Test - void shouldHelloThroughInjectedClient() throws MalformedURLException { + void shouldHelloThroughInjectedClient() { assertThat(testBean.helloViaInjectedClient("wor1d")).isEqualTo("hello, wor1d"); } + + @Test + void shouldHaveApplicationScopeByDefault() { + BeanManager beanManager = Arc.container().beanManager(); + Set> beans = beanManager.getBeans(HelloClient2.class, RestClient.LITERAL); + Bean resolvedBean = beanManager.resolve(beans); + assertThat(resolvedBean.getScope()).isEqualTo(ApplicationScoped.class); + } } diff --git a/extensions/resteasy-reactive/rest-client-microprofile/deployment/src/test/java/io/quarkus/resteasy/reactive/client/MpRestClientScopeOverrideTest.java b/extensions/resteasy-reactive/rest-client-microprofile/deployment/src/test/java/io/quarkus/resteasy/reactive/client/MpRestClientScopeOverrideTest.java new file mode 100644 index 00000000000000..51f4ec6d79d639 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-microprofile/deployment/src/test/java/io/quarkus/resteasy/reactive/client/MpRestClientScopeOverrideTest.java @@ -0,0 +1,34 @@ +package io.quarkus.resteasy.reactive.client; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Set; + +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; + +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.test.QuarkusUnitTest; + +public class MpRestClientScopeOverrideTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(HelloClient2.class)) + .withConfigurationResource("basic-test-application.properties"); + + @Test + void shouldHaveDependentScope() { + BeanManager beanManager = Arc.container().beanManager(); + Set> beans = beanManager.getBeans(HelloClient2.class, RestClient.LITERAL); + Bean resolvedBean = beanManager.resolve(beans); + assertThat(resolvedBean.getScope()).isEqualTo(Dependent.class); + } +} diff --git a/extensions/resteasy-reactive/rest-client-microprofile/deployment/src/test/resources/dependent-test-application.properties b/extensions/resteasy-reactive/rest-client-microprofile/deployment/src/test/resources/dependent-test-application.properties new file mode 100644 index 00000000000000..2418ae8329dd82 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-microprofile/deployment/src/test/resources/dependent-test-application.properties @@ -0,0 +1,3 @@ +hello2/mp-rest/url=http://localhost:${quarkus.http.test-port:8081}/hello + +quarkus.rest-client.scope=javax.enterprise.context.Dependent \ No newline at end of file diff --git a/extensions/resteasy-reactive/rest-client-microprofile/runtime/src/main/java/io/quarkus/resteasy/reactive/client/microprofile/ReactiveResteasyMpClientConfig.java b/extensions/resteasy-reactive/rest-client-microprofile/runtime/src/main/java/io/quarkus/resteasy/reactive/client/microprofile/ReactiveResteasyMpClientConfig.java new file mode 100644 index 00000000000000..b73ad10d51ad01 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-microprofile/runtime/src/main/java/io/quarkus/resteasy/reactive/client/microprofile/ReactiveResteasyMpClientConfig.java @@ -0,0 +1,15 @@ +package io.quarkus.resteasy.reactive.client.microprofile; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(phase = ConfigPhase.BUILD_TIME, name = "rest-client") +public class ReactiveResteasyMpClientConfig { + + /** + * Default scope for MicroProfile Rest Client. Use `javax.enterprise.context.Dependent` for spec-compliant behavior + */ + @ConfigItem(name = "scope", defaultValue = "javax.enterprise.context.ApplicationScoped") + public String scope; +} diff --git a/tcks/microprofile-rest-client-reactive/pom.xml b/tcks/microprofile-rest-client-reactive/pom.xml index 03850a86f5fb18..1101f10d593aa3 100644 --- a/tcks/microprofile-rest-client-reactive/pom.xml +++ b/tcks/microprofile-rest-client-reactive/pom.xml @@ -24,6 +24,7 @@ false true ${wiremock.server.port} + javax.enterprise.context.Dependent