Skip to content

Commit

Permalink
Make REST Client a bean when the scope property is set in config
Browse files Browse the repository at this point in the history
Closes: #37007
  • Loading branch information
geoand committed Dec 7, 2023
1 parent 58834c2 commit b73fbe8
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.quarkus.restclient.config;

import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;

@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").
*/
@ConfigItem
public Optional<String> scope;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -70,14 +69,6 @@ public class RestClientConfig {
@ConfigItem
public Optional<String> 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<String> 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.
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, RestClientBuildConfig> configs = Collections.emptyMap();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -326,6 +327,10 @@ public void putClientConfig(Class<?> clientInterface, RestClientConfig clientCon
configs.put(clientInterface.getName(), clientConfig);
}

public Set<String> getConfigKeys() {
return configs.keySet();
}

public static RestClientsConfig getInstance() {
InstanceHandle<RestClientsConfig> configHandle;
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -410,10 +412,13 @@ void addRestClientBeans(Capabilities capabilities,
List<RestClientAnnotationsTransformerBuildItem> restClientAnnotationsTransformerBuildItem,
BuildProducer<GeneratedBeanBuildItem> generatedBeans,
RestClientReactiveConfig clientConfig,
RestClientsBuildTimeConfig clientsBuildConfig,
RestClientRecorder recorder) {

CompositeIndex index = CompositeIndex.create(combinedIndexBuildItem.getIndex());
Set<AnnotationInstance> registerRestClientAnnos = new HashSet<>(index.getAnnotations(REGISTER_REST_CLIENT));

Set<AnnotationInstance> registerRestClientAnnos = determineRegisterRestClientInstances(clientsBuildConfig, index);

Map<String, String> configKeys = new HashMap<>();
var annotationsStore = new AnnotationStore(restClientAnnotationsTransformerBuildItem.stream()
.map(RestClientAnnotationsTransformerBuildItem::getAnnotationsTransformer).collect(toList()));
Expand Down Expand Up @@ -574,6 +579,34 @@ && isImplementorOf(index, target.asClass(), RESPONSE_EXCEPTION_MAPPER, Set.of(AP
}
}

private Set<AnnotationInstance> determineRegisterRestClientInstances(RestClientsBuildTimeConfig clientsConfig,
CompositeIndex index) {
// these are the actual instances
Set<AnnotationInstance> registerRestClientAnnos = new HashSet<>(index.getAnnotations(REGISTER_REST_CLIENT));
// a set of the original target class
Set<ClassInfo> 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<String> configKeyNames = clientsConfig.configs.keySet();
for (String configKeyName : configKeyNames) {
ClassInfo classInfo = index.getClassByName(configKeyName);
if (classInfo == null) {
continue;
}
if (registerRestClientTargets.contains(classInfo)) {
continue;
}
Optional<String> 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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Bean<?>> 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down

0 comments on commit b73fbe8

Please sign in to comment.