From f921b5c2fe20501a6550732e910774850bc63986 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 7 Dec 2023 13:49:40 +0200 Subject: [PATCH] Make REST Client a bean when the scope property is set in config Closes: #37007 --- .../config/RestClientBuildConfig.java | 22 +++++++ .../restclient/config/RestClientConfig.java | 11 ---- .../config/RestClientsBuildTimeConfig.java | 18 ++++++ .../restclient/config/RestClientsConfig.java | 5 ++ .../config/RestClientConfigTest.java | 2 - .../QuarkusConfigurationTest.java | 2 - .../RestClientReactiveProcessor.java | 35 ++++++++++- .../client/reactive/BeanFromConfigTest.java | 59 +++++++++++++++++++ .../client/reactive/ConfigurationTest.java | 2 - 9 files changed, 138 insertions(+), 18 deletions(-) create mode 100644 extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientBuildConfig.java create mode 100644 extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientsBuildTimeConfig.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/BeanFromConfigTest.java diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientBuildConfig.java b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientBuildConfig.java new file mode 100644 index 00000000000000..a0869e1472ea8c --- /dev/null +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientBuildConfig.java @@ -0,0 +1,22 @@ +package io.quarkus.restclient.config; + +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@ConfigGroup +public class RestClientBuildConfig { + + /** + * The CDI scope to use for injection. This property can contain either a fully qualified class name of a CDI scope + * annotation (such as "jakarta.enterprise.context.ApplicationScoped") or its simple name (such as + * "ApplicationScoped"). + * By default, this is not set which means the interface is not registered as a bean unless it is annotated with {@link RegisterRestClient}. + * If an interface is not annotated with {@link RegisterRestClient} and this property is set, then Quarkus will make the interface + * a bean of the configured scope. + */ + @ConfigItem + public Optional scope; +} diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientConfig.java b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientConfig.java index c838bb62dbaa8b..00d050a52db7dc 100644 --- a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientConfig.java +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientConfig.java @@ -21,7 +21,6 @@ public class RestClientConfig { EMPTY = new RestClientConfig(); EMPTY.url = Optional.empty(); EMPTY.uri = Optional.empty(); - EMPTY.scope = Optional.empty(); EMPTY.providers = Optional.empty(); EMPTY.connectTimeout = Optional.empty(); EMPTY.readTimeout = Optional.empty(); @@ -70,14 +69,6 @@ public class RestClientConfig { @ConfigItem public Optional uri; - /** - * The CDI scope to use for injection. This property can contain either a fully qualified class name of a CDI scope - * annotation (such as "jakarta.enterprise.context.ApplicationScoped") or its simple name (such as - * "ApplicationScoped"). - */ - @ConfigItem - public Optional scope; - /** * Map where keys are fully-qualified provider classnames to include in the client, and values are their integer * priorities. The equivalent of the `@RegisterProvider` annotation. @@ -280,7 +271,6 @@ public static RestClientConfig load(String configKey) { instance.url = getConfigValue(configKey, "url", String.class); instance.uri = getConfigValue(configKey, "uri", String.class); - instance.scope = getConfigValue(configKey, "scope", String.class); instance.providers = getConfigValue(configKey, "providers", String.class); instance.connectTimeout = getConfigValue(configKey, "connect-timeout", Long.class); instance.readTimeout = getConfigValue(configKey, "read-timeout", Long.class); @@ -320,7 +310,6 @@ public static RestClientConfig load(Class interfaceClass) { instance.url = getConfigValue(interfaceClass, "url", String.class); instance.uri = getConfigValue(interfaceClass, "uri", String.class); - instance.scope = getConfigValue(interfaceClass, "scope", String.class); instance.providers = getConfigValue(interfaceClass, "providers", String.class); instance.connectTimeout = getConfigValue(interfaceClass, "connect-timeout", Long.class); instance.readTimeout = getConfigValue(interfaceClass, "read-timeout", Long.class); diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientsBuildTimeConfig.java b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientsBuildTimeConfig.java new file mode 100644 index 00000000000000..16ac5b0776c4e8 --- /dev/null +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientsBuildTimeConfig.java @@ -0,0 +1,18 @@ +package io.quarkus.restclient.config; + +import java.util.Collections; +import java.util.Map; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "rest-client", phase = ConfigPhase.BUILD_TIME) +public class RestClientsBuildTimeConfig { + + /** + * Configurations of REST client instances. + */ + @ConfigItem(name = ConfigItem.PARENT) + public Map configs = Collections.emptyMap(); +} diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientsConfig.java b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientsConfig.java index 51a7aa1a377de2..fd79160d021c55 100644 --- a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientsConfig.java +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientsConfig.java @@ -2,6 +2,7 @@ import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import jakarta.enterprise.inject.CreationException; @@ -326,6 +327,10 @@ public void putClientConfig(Class clientInterface, RestClientConfig clientCon configs.put(clientInterface.getName(), clientConfig); } + public Set getConfigKeys() { + return configs.keySet(); + } + public static RestClientsConfig getInstance() { InstanceHandle configHandle; try { diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/RestClientConfigTest.java b/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/RestClientConfigTest.java index 051d4db37e3a10..03fd6335dd4309 100644 --- a/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/RestClientConfigTest.java +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/test/java/io/quarkus/restclient/config/RestClientConfigTest.java @@ -33,8 +33,6 @@ private void verifyConfig(RestClientConfig config) { assertThat(config.url.get()).isEqualTo("http://localhost:8080"); assertThat(config.uri).isPresent(); assertThat(config.uri.get()).isEqualTo("http://localhost:8081"); - assertThat(config.scope).isPresent(); - assertThat(config.scope.get()).isEqualTo("Singleton"); assertThat(config.providers).isPresent(); assertThat(config.providers.get()).isEqualTo("io.quarkus.restclient.configuration.MyResponseFilter"); assertThat(config.connectTimeout).isPresent(); diff --git a/extensions/resteasy-classic/rest-client/deployment/src/test/java/io/quarkus/restclient/configuration/QuarkusConfigurationTest.java b/extensions/resteasy-classic/rest-client/deployment/src/test/java/io/quarkus/restclient/configuration/QuarkusConfigurationTest.java index 8927496bdc37a9..a333943a45e003 100644 --- a/extensions/resteasy-classic/rest-client/deployment/src/test/java/io/quarkus/restclient/configuration/QuarkusConfigurationTest.java +++ b/extensions/resteasy-classic/rest-client/deployment/src/test/java/io/quarkus/restclient/configuration/QuarkusConfigurationTest.java @@ -55,8 +55,6 @@ void configurationsShouldBeLoaded() { void verifyClientConfig(RestClientConfig clientConfig, boolean verifyNonStandardProperties) { assertThat(clientConfig.url).isPresent(); assertThat(clientConfig.url.get()).contains("localhost"); - assertThat(clientConfig.scope).isPresent(); - assertThat(clientConfig.scope.get()).isEqualTo("Singleton"); assertThat(clientConfig.providers).isPresent(); assertThat(clientConfig.providers.get()) .isEqualTo("io.quarkus.restclient.configuration.MyResponseFilter"); 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 5f5a7c34939f22..839f1de001b1b1 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 @@ -34,6 +34,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import jakarta.enterprise.context.SessionScoped; import jakarta.enterprise.inject.Typed; @@ -103,6 +104,7 @@ import io.quarkus.rest.client.reactive.runtime.RestClientReactiveConfig; import io.quarkus.rest.client.reactive.runtime.RestClientRecorder; import io.quarkus.rest.client.reactive.spi.RestClientAnnotationsTransformerBuildItem; +import io.quarkus.restclient.config.RestClientsBuildTimeConfig; import io.quarkus.restclient.config.RestClientsConfig; import io.quarkus.restclient.config.deployment.RestClientConfigUtils; import io.quarkus.resteasy.reactive.spi.ContainerRequestFilterBuildItem; @@ -410,10 +412,13 @@ void addRestClientBeans(Capabilities capabilities, List restClientAnnotationsTransformerBuildItem, BuildProducer generatedBeans, RestClientReactiveConfig clientConfig, + RestClientsBuildTimeConfig clientsBuildConfig, RestClientRecorder recorder) { CompositeIndex index = CompositeIndex.create(combinedIndexBuildItem.getIndex()); - Set registerRestClientAnnos = new HashSet<>(index.getAnnotations(REGISTER_REST_CLIENT)); + + Set registerRestClientAnnos = determineRegisterRestClientInstances(clientsBuildConfig, index); + Map configKeys = new HashMap<>(); var annotationsStore = new AnnotationStore(restClientAnnotationsTransformerBuildItem.stream() .map(RestClientAnnotationsTransformerBuildItem::getAnnotationsTransformer).collect(toList())); @@ -574,6 +579,34 @@ && isImplementorOf(index, target.asClass(), RESPONSE_EXCEPTION_MAPPER, Set.of(AP } } + private Set determineRegisterRestClientInstances(RestClientsBuildTimeConfig clientsConfig, + CompositeIndex index) { + // these are the actual instances + Set registerRestClientAnnos = new HashSet<>(index.getAnnotations(REGISTER_REST_CLIENT)); + // a set of the original target class + Set registerRestClientTargets = registerRestClientAnnos.stream().map(ai -> ai.target().asClass()).collect( + Collectors.toSet()); + + // now we go through the keys and if any of them correspond to classes that don't have a @RegisterRestClient annotation, we fake that annotation + Set configKeyNames = clientsConfig.configs.keySet(); + for (String configKeyName : configKeyNames) { + ClassInfo classInfo = index.getClassByName(configKeyName); + if (classInfo == null) { + continue; + } + if (registerRestClientTargets.contains(classInfo)) { + continue; + } + Optional cdiScope = clientsConfig.configs.get(configKeyName).scope; + if (cdiScope.isEmpty()) { + continue; + } + registerRestClientAnnos.add(AnnotationInstance.builder(REGISTER_REST_CLIENT).add("configKey", configKeyName) + .buildWithTarget(classInfo)); + } + return registerRestClientAnnos; + } + /** * Based on a list of interfaces implemented by @Provider class, determine if registration * should be skipped or not. Server-specific types should be omitted unless implementation diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/BeanFromConfigTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/BeanFromConfigTest.java new file mode 100644 index 00000000000000..4fadb3d3ae01e1 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/BeanFromConfigTest.java @@ -0,0 +1,59 @@ +package io.quarkus.rest.client.reactive; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Set; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.inject.RestClient; +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 BeanFromConfigTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(HelloClient.class, HelloResource.class)) + .overrideConfigKey("quarkus.rest-client.\"io.quarkus.rest.client.reactive.HelloClient\".scope", "Dependent") + .overrideRuntimeConfigKey("quarkus.rest-client.\"io.quarkus.rest.client.reactive.HelloClient\".url", + "http://localhost:${quarkus.http.test-port:8081}"); + + @RestClient + HelloClient client; + + @Test + void shouldHello() { + assertThat(client.echo("w0rld")).isEqualTo("hello, w0rld"); + } + + @Test + void shouldHaveDependentScope() { + BeanManager beanManager = Arc.container().beanManager(); + Set> beans = beanManager.getBeans(HelloClient.class, RestClient.LITERAL); + Bean resolvedBean = beanManager.resolve(beans); + assertThat(resolvedBean.getScope()).isEqualTo(Dependent.class); + } + + @Path("/hello") + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.TEXT_PLAIN) + public static class HelloResource { + + @POST + public String echo(String name) { + return "hello, " + name; + } + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ConfigurationTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ConfigurationTest.java index 35c8f6e0f8c23d..eb6011ebd3ff07 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ConfigurationTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ConfigurationTest.java @@ -74,8 +74,6 @@ void emptyPathAnnotationShouldWork() { private void verifyClientConfig(RestClientConfig clientConfig, boolean checkExtraProperties) { assertThat(clientConfig.url).isPresent(); assertThat(clientConfig.url.get()).endsWith("/hello"); - assertThat(clientConfig.scope).isPresent(); - assertThat(clientConfig.scope.get()).isEqualTo("Singleton"); assertThat(clientConfig.providers).isPresent(); assertThat(clientConfig.providers.get()) .isEqualTo("io.quarkus.rest.client.reactive.HelloClientWithBaseUri$MyResponseFilter");