diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/AbstractRestClientConfigBuilder.java b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/AbstractRestClientConfigBuilder.java index f477e6d78e5f13..93a7292af3c297 100644 --- a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/AbstractRestClientConfigBuilder.java +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/AbstractRestClientConfigBuilder.java @@ -1,8 +1,16 @@ package io.quarkus.restclient.config; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.UndeclaredThrowableException; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import io.quarkus.runtime.configuration.ConfigBuilder; +import io.smallrye.config.ConfigMappingLoader; +import io.smallrye.config.ConfigMappingObject; import io.smallrye.config.SmallRyeConfigBuilder; /** @@ -25,16 +33,69 @@ public abstract class AbstractRestClientConfigBuilder implements ConfigBuilder { @Override public SmallRyeConfigBuilder configBuilder(final SmallRyeConfigBuilder builder) { List restClients = getRestClients(); - builder.withInterceptors(new RestClientNameFallbackConfigSourceInterceptor(restClients)); + Set ignoreNames = getIgnoreNames(); + builder.withInterceptors(new RestClientNameUnquotedFallbackInterceptor(restClients, ignoreNames)); + builder.withInterceptors(new RestClientNameFallbackInterceptor(restClients, ignoreNames)); for (RegisteredRestClient restClient : restClients) { builder.withDefaultValue("quarkus.rest-client.\"" + restClient.getFullName() + "\".force", "true"); builder.withDefaultValue("quarkus.rest-client." + restClient.getSimpleName() + ".force", "true"); if (restClient.getConfigKey() != null) { - builder.withDefaultValue("quarkus.rest-client." + restClient.getConfigKey() + ".force", "true"); + builder.withDefaultValue("quarkus.rest-client.\"" + restClient.getConfigKey() + "\".force", "true"); } } return builder; } public abstract List getRestClients(); + + /** + * Builds a list of base names from {@link RestClientsConfig} to ignore when rewriting the REST Client + * configuration. Only configuration from {@link RestClientsConfig#clients()} requires rewriting, but they share + * the same path of the base names due to {@link io.smallrye.config.WithParentName} in the member. + * + * @return a Set with the names to ignore. + */ + public Set getIgnoreNames() { + Class implementationClass = ConfigMappingLoader + .getImplementationClass(RestClientsConfig.class); + return configMappingNames(implementationClass).get(RestClientsConfig.class.getName()).get("") + .stream() + .filter(s -> s.charAt(0) != '*') + .map(s -> "quarkus.rest-client." + s) + .collect(Collectors.toSet()); + } + + /** + * TODO - Generate this in RestClientConfigUtils - The list can be collected during build time and generated + */ + @SuppressWarnings("unchecked") + @Deprecated(forRemoval = true) + static Map>> configMappingNames(final Class implementationClass) { + try { + Method getNames = implementationClass.getDeclaredMethod("getNames"); + return (Map>>) getNames.invoke(null); + } catch (NoSuchMethodException e) { + throw new NoSuchMethodError(e.getMessage()); + } catch (IllegalAccessException e) { + throw new IllegalAccessError(e.getMessage()); + } catch (InvocationTargetException e) { + try { + throw e.getCause(); + } catch (RuntimeException | Error e2) { + throw e2; + } catch (Throwable t) { + throw new UndeclaredThrowableException(t); + } + } + } + + static int indexOfRestClient(final String name) { + if (name.startsWith("quarkus.rest-client.")) { + return 20; + } + if (name.startsWith("quarkus.rest-client-reactive.")) { + return 29; + } + return -1; + } } diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RegisteredRestClient.java b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RegisteredRestClient.java index a350fcd9d8b9cd..f36550872ba2c5 100644 --- a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RegisteredRestClient.java +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RegisteredRestClient.java @@ -1,9 +1,12 @@ package io.quarkus.restclient.config; +import io.smallrye.config.NameIterator; + public class RegisteredRestClient { private final String fullName; private final String simpleName; private final String configKey; + private final boolean configKeySegments; public RegisteredRestClient(final String fullName, final String simpleName) { this(fullName, simpleName, null); @@ -13,6 +16,7 @@ public RegisteredRestClient(final String fullName, final String simpleName, fina this.fullName = fullName; this.simpleName = simpleName; this.configKey = configKey; + this.configKeySegments = configKey != null && new NameIterator(configKey).nextSegmentEquals(configKey); } public String getFullName() { @@ -26,4 +30,11 @@ public String getSimpleName() { public String getConfigKey() { return configKey; } + + public boolean isConfigKeySegments() { + if (configKey == null) { + throw new IllegalStateException("configKey is null"); + } + return !configKeySegments; + } } diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientNameFallbackConfigSourceInterceptor.java b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientNameFallbackConfigSourceInterceptor.java deleted file mode 100644 index 7ca19d8dbea6b2..00000000000000 --- a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientNameFallbackConfigSourceInterceptor.java +++ /dev/null @@ -1,87 +0,0 @@ -package io.quarkus.restclient.config; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.UndeclaredThrowableException; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; - -import io.smallrye.config.ConfigMappingLoader; -import io.smallrye.config.ConfigMappingObject; -import io.smallrye.config.FallbackConfigSourceInterceptor; - -/** - * Fallbacks REST Client FQN to Simple Name. - *

- * Ideally, this shouldn't be required. The old custom implementation allowed us to mix both names and merge them in a - * final configuration to use in the REST Client. The standard Config system does not support such a feature. If a - * configuration supports multiple names, the user has to use the same name across all configuration sources. No other - * Quarkus extension behaves this way because only the REST Client extension provides the custom code to make it work. - */ -public class RestClientNameFallbackConfigSourceInterceptor extends FallbackConfigSourceInterceptor { - public RestClientNameFallbackConfigSourceInterceptor(final List restClients) { - super(fallback(restClients)); - } - - private static Function fallback(final List restClients) { - Class implementationClass = ConfigMappingLoader - .getImplementationClass(RestClientsConfig.class); - Set ignoreNames = configMappingNames(implementationClass).get(RestClientsConfig.class.getName()).get("") - .stream() - .filter(s -> s.charAt(0) != '*') - .map(s -> "quarkus.rest-client." + s) - .collect(Collectors.toSet()); - - return new Function() { - @Override - public String apply(final String name) { - if (name.startsWith("quarkus.rest-client.")) { - if (ignoreNames.contains(name)) { - return name; - } - - for (RegisteredRestClient restClient : restClients) { - if (name.length() > 20 && name.charAt(20) == '"') { - String interfaceName = restClient.getFullName(); - if (name.regionMatches(21, interfaceName, 0, interfaceName.length())) { - if (name.length() > 21 + interfaceName.length() - && name.charAt(21 + interfaceName.length()) == '"') { - return "quarkus.rest-client." + restClient.getSimpleName() - + name.substring(21 + interfaceName.length() + 1); - } - } - } - } - } - return name; - } - }; - } - - /** - * Expose this as a public API in SmallRye Config - */ - @SuppressWarnings("unchecked") - @Deprecated(forRemoval = true) - private static Map>> configMappingNames(final Class implementationClass) { - try { - Method getNames = implementationClass.getDeclaredMethod("getNames"); - return (Map>>) getNames.invoke(null); - } catch (NoSuchMethodException e) { - throw new NoSuchMethodError(e.getMessage()); - } catch (IllegalAccessException e) { - throw new IllegalAccessError(e.getMessage()); - } catch (InvocationTargetException e) { - try { - throw e.getCause(); - } catch (RuntimeException | Error e2) { - throw e2; - } catch (Throwable t) { - throw new UndeclaredThrowableException(t); - } - } - } -} diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientNameFallbackInterceptor.java b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientNameFallbackInterceptor.java new file mode 100644 index 00000000000000..589fd204e42668 --- /dev/null +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientNameFallbackInterceptor.java @@ -0,0 +1,77 @@ +package io.quarkus.restclient.config; + +import static io.quarkus.restclient.config.AbstractRestClientConfigBuilder.indexOfRestClient; + +import java.util.List; +import java.util.Set; +import java.util.function.Function; + +import jakarta.annotation.Priority; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +import io.smallrye.config.FallbackConfigSourceInterceptor; +import io.smallrye.config.Priorities; +import io.smallrye.config.SmallRyeConfigBuilder; + +/** + * Fallbacks REST Client FQN to Simple Name and quoted config keys to unquoted + *

+ * Ideally, this shouldn't be required. The old custom implementation allowed us to mix both FQN and Simple Name in a + * merged configuration to use in the REST Client. The standard Config system does not support such a feature. If a + * configuration supports multiple names, the user has to use the same name across all configuration sources. No other + * Quarkus extension behaves this way because only the REST Client extension provides the custom code to make it work. + *

+ * In the case of {@link RegisterRestClient#configKey()}, users either use quoted or unquoted configuration names for + * single config key segments. Again, the Config system does not support such a feature (but could be implemented), so + * the interceptor also fallbacks to unquoted configuration names, due to the force property added by + * {@link AbstractRestClientConfigBuilder#configBuilder(SmallRyeConfigBuilder)}. + */ +@Priority(Priorities.LIBRARY + 610) +public class RestClientNameFallbackInterceptor extends FallbackConfigSourceInterceptor { + public RestClientNameFallbackInterceptor(final List restClients, + final Set ignoreNames) { + super(fallback(restClients, ignoreNames)); + } + + private static Function fallback(final List restClients, + final Set ignoreNames) { + return new Function() { + @Override + public String apply(final String name) { + int indexOfRestClient = indexOfRestClient(name); + if (indexOfRestClient != -1) { + if (ignoreNames.contains(name)) { + return name; + } + + int endOfRestClient = indexOfRestClient + 1; + for (RegisteredRestClient restClient : restClients) { + if (name.length() > indexOfRestClient && name.charAt(indexOfRestClient) == '"') { + String interfaceName = restClient.getFullName(); + if (name.regionMatches(endOfRestClient, interfaceName, 0, interfaceName.length())) { + if (name.length() > endOfRestClient + interfaceName.length() + && name.charAt(endOfRestClient + interfaceName.length()) == '"') { + return "quarkus.rest-client." + restClient.getSimpleName() + + name.substring(endOfRestClient + interfaceName.length() + 1); + } + } + + String configKey = restClient.getConfigKey(); + if (configKey == null || configKey.isEmpty() || restClient.isConfigKeySegments()) { + continue; + } + int endOfConfigKey = endOfRestClient + configKey.length(); + if (name.regionMatches(endOfRestClient, configKey, 0, configKey.length())) { + if (name.length() > endOfConfigKey && name.charAt(endOfConfigKey) == '"') { + return "quarkus.rest-client." + configKey + name.substring(endOfConfigKey + 1); + } + } + } + } + } + return name; + } + }; + } +} diff --git a/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientNameUnquotedFallbackInterceptor.java b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientNameUnquotedFallbackInterceptor.java new file mode 100644 index 00000000000000..1e2d497abdf8d1 --- /dev/null +++ b/extensions/resteasy-classic/rest-client-config/runtime/src/main/java/io/quarkus/restclient/config/RestClientNameUnquotedFallbackInterceptor.java @@ -0,0 +1,92 @@ +package io.quarkus.restclient.config; + +import static io.quarkus.restclient.config.AbstractRestClientConfigBuilder.indexOfRestClient; +import static io.smallrye.config.ProfileConfigSourceInterceptor.convertProfile; + +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.function.Function; + +import jakarta.annotation.Priority; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +import io.smallrye.config.ConfigValue; +import io.smallrye.config.FallbackConfigSourceInterceptor; +import io.smallrye.config.NameIterator; +import io.smallrye.config.Priorities; + +/** + * Relocates unquoted config keys to quoted + *

+ * In the case of {@link RegisterRestClient#configKey()}, users either use quoted or unquoted configuration names for + * single config key segments. Again, the Config system does not support such a feature (but could be implemented), so + * the interceptor also relocates to unquoted configuration names. + *

+ * We need a double-way relocation / fallback mapping between unquoted and quoted because SmallRye Config will use the + * first distict key it finds to populate {@link RestClientsConfig#clients()} in the list of property names. If quoted, + * it will search for all quoted. If unquoted, it will search for all unquoted. We cannot be sure how the user sets the + * configuration, especially considering that we may not be able to query the list directly if the config comes from a + * source that does not support listing property names. + */ +@Priority(Priorities.LIBRARY + 605) +public class RestClientNameUnquotedFallbackInterceptor extends FallbackConfigSourceInterceptor { + public RestClientNameUnquotedFallbackInterceptor(final List restClients, + final Set ignoreNames) { + super(relocate(restClients, ignoreNames)); + } + + private static Function relocate(final List restClients, + final Set ignoreNames) { + return new Function() { + @Override + public String apply(final String name) { + int indexOfRestClient = indexOfRestClient(name); + if (indexOfRestClient != -1) { + if (ignoreNames.contains(name)) { + return name; + } + + for (RegisteredRestClient restClient : restClients) { + String configKey = restClient.getConfigKey(); + if (configKey == null || configKey.isEmpty() || restClient.isConfigKeySegments()) { + continue; + } + + int endOfConfigKey = indexOfRestClient + configKey.length(); + if (name.regionMatches(indexOfRestClient, configKey, 0, configKey.length())) { + if (name.length() > endOfConfigKey && name.charAt(endOfConfigKey) == '.') { + return "quarkus.rest-client.\"" + configKey + "\"" + name.substring(endOfConfigKey); + } + } + } + } + return name; + } + }; + } + + private static final Comparator CONFIG_SOURCE_COMPARATOR = new Comparator() { + @Override + public int compare(ConfigValue original, ConfigValue candidate) { + int result = Integer.compare(original.getConfigSourceOrdinal(), candidate.getConfigSourceOrdinal()); + if (result != 0) { + return result; + } + result = Integer.compare(original.getConfigSourcePosition(), candidate.getConfigSourcePosition()) * -1; + if (result != 0) { + return result; + } + // If both properties are profiled, prioritize the one with the most specific profile. + if (original.getName().charAt(0) == '%' && candidate.getName().charAt(0) == '%') { + List originalProfiles = convertProfile( + new NameIterator(original.getName()).getNextSegment().substring(1)); + List candidateProfiles = convertProfile( + new NameIterator(candidate.getName()).getNextSegment().substring(1)); + return Integer.compare(originalProfiles.size(), candidateProfiles.size()) * -1; + } + return result; + } + }; +} 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 index 0d7c3c1f587078..7f6bcaf692571e 100644 --- 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 @@ -5,7 +5,6 @@ import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; -import io.quarkus.runtime.annotations.ConfigDocIgnore; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; import io.smallrye.config.ConfigMapping; @@ -62,12 +61,5 @@ interface RestClientBuildConfig { * */ Optional localProxyProvider(); - - /** - * Collects unmapped properties in the REST Client namespace, including available runtime properties. - */ - @WithParentName - @ConfigDocIgnore - Map properties(); } } 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 32695f92e8fbac..dab1aa762d3cc6 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 @@ -3,6 +3,7 @@ import java.util.Map; import java.util.Optional; +import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.rest.client.ext.QueryParamStyle; import io.quarkus.runtime.annotations.ConfigDocDefault; @@ -12,8 +13,11 @@ import io.quarkus.runtime.annotations.ConfigRoot; import io.quarkus.runtime.configuration.MemorySize; import io.smallrye.config.ConfigMapping; +import io.smallrye.config.ConfigValue; +import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.WithDefault; import io.smallrye.config.WithDefaults; +import io.smallrye.config.WithName; import io.smallrye.config.WithParentName; @ConfigMapping(prefix = "quarkus.rest-client") @@ -369,12 +373,40 @@ interface RestClientConfig { */ Optional url(); + /** + * Duplicate mapping of {@link RestClientConfig#url()} to keep a reference of the name used to retrieve the + * url. We need this to reload the url configuration in case it contains an expression + * to ${quarkus.http.port}, which is only set after we load the config. + */ + @ConfigDocIgnore + @WithName("url") + ConfigValue urlValue(); + + default Optional urlReload() { + SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class); + return config.getOptionalValue(urlValue().getName(), String.class); + } + /** * The base URI to use for this service. This property or the `url` property is considered required, unless * the `baseUri` attribute is configured in the `@RegisterRestClient` annotation. */ Optional uri(); + /** + * Duplicate mapping of {@link RestClientConfig#uri()} to keep a reference of the name used to retrieve the + * uri. We need this to reload the uri configuration in case it contains an expression + * to ${quarkus.http.port}, which is only set after we load the config. + */ + @ConfigDocIgnore + @WithName("uri") + ConfigValue uriValue(); + + default Optional uriReload() { + SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class); + return config.getOptionalValue(uriValue().getName(), String.class); + } + /** * This property is only meant to be set by advanced configurations to override whatever value was set for the uri or * url. 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 b15cfcb84cfa00..5c6ab2be091eb6 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 @@ -1,27 +1,46 @@ package io.quarkus.restclient.config; +import static java.util.stream.Collectors.toSet; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigInteger; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.stream.StreamSupport; import org.eclipse.microprofile.rest.client.ext.QueryParamStyle; import org.junit.jupiter.api.Test; +import io.quarkus.restclient.config.RestClientsConfig.RestClientConfig; import io.quarkus.runtime.configuration.ConfigUtils; import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; +import io.smallrye.config.SmallRyeConfigBuilderCustomizer; +import io.smallrye.config.common.MapBackedConfigSource; -public class RestClientConfigTest { - +class RestClientConfigTest { @Test - public void testLoadRestClientConfig() { + void loadRestClientConfig() { SmallRyeConfig config = ConfigUtils.emptyConfigBuilder() .withMapping(RestClientsConfig.class) - .withInterceptors(new RestClientNameFallbackConfigSourceInterceptor( - List.of(new RegisteredRestClient(RestClientConfigTest.class.getName(), - RestClientConfigTest.class.getSimpleName())))) + .withCustomizers(new SmallRyeConfigBuilderCustomizer() { + @Override + public void configBuilder(final SmallRyeConfigBuilder builder) { + new AbstractRestClientConfigBuilder() { + @Override + public List getRestClients() { + return List.of( + new RegisteredRestClient(RestClientConfigTest.class.getName(), + RestClientConfigTest.class.getSimpleName())); + } + }.configBuilder(builder); + } + }) .build(); Optional optionalValue = config.getOptionalValue("quarkus.rest-client.test-client.url", String.class); @@ -29,14 +48,14 @@ public void testLoadRestClientConfig() { RestClientsConfig restClientsConfig = config.getConfigMapping(RestClientsConfig.class); - RestClientsConfig.RestClientConfig configForKey = restClientsConfig.getClient("test-client"); + RestClientConfig configForKey = restClientsConfig.getClient("test-client"); verifyConfig(configForKey); - RestClientsConfig.RestClientConfig configForClassName = restClientsConfig.getClient(RestClientConfigTest.class); + RestClientConfig configForClassName = restClientsConfig.getClient(RestClientConfigTest.class); verifyConfig(configForClassName); } - private void verifyConfig(RestClientsConfig.RestClientConfig config) { + private void verifyConfig(RestClientConfig config) { assertTrue(config.url().isPresent()); assertThat(config.url().get()).isEqualTo("http://localhost:8080"); assertTrue(config.uri().isPresent()); @@ -62,4 +81,85 @@ private void verifyConfig(RestClientsConfig.RestClientConfig config) { assertTrue(config.maxChunkSize().isPresent()); assertThat(config.maxChunkSize().get().asBigInteger()).isEqualTo(BigInteger.valueOf(1024)); } + + @Test + void restClientConfigKey() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .addDiscoveredConverters() + .withMappingIgnore("quarkus.**") + .withMapping(RestClientsConfig.class) + .withSources(new MapBackedConfigSource("", Map.of( + "quarkus.rest-client.simple.uri", "http://localhost:8081", + "quarkus.rest-client.simple.url", "http://localhost:8081", + "quarkus.rest-client.\"quoted\".uri", "http://localhost:8081", + "quarkus.rest-client.\"quoted\".url", "http://localhost:8081", + "quarkus.rest-client.mixed.uri", "http://localhost:8081", + "quarkus.rest-client.\"mixed\".url", "http://localhost:8081", + "quarkus.rest-client.da-shed.uri", "http://localhost:8081", + "quarkus.rest-client.da-shed.url", "http://localhost:8081", + "quarkus.rest-client.\"segments.paths\".uri", "http://localhost:8081", + "quarkus.rest-client.\"segments.paths\".url", "http://localhost:8081")) { + }) + .withCustomizers(new SmallRyeConfigBuilderCustomizer() { + @Override + public void configBuilder(final SmallRyeConfigBuilder builder) { + new AbstractRestClientConfigBuilder() { + @Override + public List getRestClients() { + return List.of( + new RegisteredRestClient("dummy", "dummy", "simple"), + new RegisteredRestClient("dummy", "dummy", "quoted"), + new RegisteredRestClient("dummy", "dummy", "mixed"), + new RegisteredRestClient("dummy", "dummy", "da-shed"), + new RegisteredRestClient("dummy", "dummy", "segments.paths")); + } + }.configBuilder(builder); + } + }) + .build(); + + Set names = StreamSupport.stream(config.getPropertyNames().spliterator(), false).collect(toSet()); + assertTrue(names.contains("quarkus.rest-client.simple.uri")); + assertTrue(names.contains("quarkus.rest-client.\"simple\".uri")); + assertTrue(names.contains("quarkus.rest-client.quoted.uri")); + assertTrue(names.contains("quarkus.rest-client.\"quoted\".uri")); + assertTrue(names.contains("quarkus.rest-client.mixed.uri")); + assertTrue(names.contains("quarkus.rest-client.\"mixed\".uri")); + assertTrue(names.contains("quarkus.rest-client.da-shed.uri")); + assertTrue(names.contains("quarkus.rest-client.\"da-shed\".uri")); + assertTrue(names.contains("quarkus.rest-client.\"segments.paths\".uri")); + assertFalse(names.contains("quarkus.rest-client.segments.paths.uri")); + + RestClientsConfig restClientsConfig = config.getConfigMapping(RestClientsConfig.class); + + RestClientConfig simple = restClientsConfig.getClient("simple"); + assertTrue(simple.uri().isPresent()); + assertEquals("http://localhost:8081", simple.uri().get()); + assertTrue(simple.url().isPresent()); + assertEquals("http://localhost:8081", simple.url().get()); + + RestClientConfig quoted = restClientsConfig.getClient("quoted"); + assertTrue(quoted.uri().isPresent()); + assertEquals("http://localhost:8081", quoted.uri().get()); + assertTrue(quoted.url().isPresent()); + assertEquals("http://localhost:8081", quoted.url().get()); + + RestClientConfig mixed = restClientsConfig.getClient("mixed"); + assertTrue(mixed.uri().isPresent()); + assertEquals("http://localhost:8081", mixed.uri().get()); + assertTrue(mixed.url().isPresent()); + assertEquals("http://localhost:8081", mixed.url().get()); + + RestClientConfig dashed = restClientsConfig.getClient("da-shed"); + assertTrue(dashed.uri().isPresent()); + assertEquals("http://localhost:8081", dashed.uri().get()); + assertTrue(dashed.url().isPresent()); + assertEquals("http://localhost:8081", dashed.url().get()); + + RestClientConfig segments = restClientsConfig.getClient("segments.paths"); + assertTrue(segments.uri().isPresent()); + assertEquals("http://localhost:8081", segments.uri().get()); + assertTrue(segments.url().isPresent()); + assertEquals("http://localhost:8081", segments.url().get()); + } } diff --git a/extensions/resteasy-classic/resteasy-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientBase.java b/extensions/resteasy-classic/resteasy-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientBase.java index d399714f3b5abc..15c81408f6b282 100644 --- a/extensions/resteasy-classic/resteasy-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientBase.java +++ b/extensions/resteasy-classic/resteasy-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientBase.java @@ -302,9 +302,9 @@ protected void configureTimeouts(RestClientBuilder builder) { } protected void configureBaseUrl(RestClientBuilder builder) { - Optional baseUrlOptional = oneOf(clientConfigByClassName().uri(), clientConfigByConfigKey().uri()); + Optional baseUrlOptional = oneOf(clientConfigByClassName().uriReload(), clientConfigByConfigKey().uriReload()); if (baseUrlOptional.isEmpty()) { - baseUrlOptional = oneOf(clientConfigByClassName().url(), clientConfigByConfigKey().url()); + baseUrlOptional = oneOf(clientConfigByClassName().urlReload(), clientConfigByConfigKey().urlReload()); } if (((baseUriFromAnnotation == null) || baseUriFromAnnotation.isEmpty()) && baseUrlOptional.isEmpty()) { diff --git a/extensions/resteasy-classic/resteasy-client/runtime/src/test/java/io/quarkus/restclient/runtime/RestClientBaseTest.java b/extensions/resteasy-classic/resteasy-client/runtime/src/test/java/io/quarkus/restclient/runtime/RestClientBaseTest.java index 589bb7f3ebd05e..66eba198cd3ca4 100644 --- a/extensions/resteasy-classic/resteasy-client/runtime/src/test/java/io/quarkus/restclient/runtime/RestClientBaseTest.java +++ b/extensions/resteasy-classic/resteasy-client/runtime/src/test/java/io/quarkus/restclient/runtime/RestClientBaseTest.java @@ -106,7 +106,7 @@ public void testClientSpecificConfigs() throws Exception { // then - verify(restClientBuilderMock).baseUrl(new URL("http://localhost")); + verify(restClientBuilderMock).baseUrl(new URL("http://localhost:8080")); verify(restClientBuilderMock).proxyAddress("host1", 123); verify(restClientBuilderMock).connectTimeout(100, MILLISECONDS); verify(restClientBuilderMock).readTimeout(101, MILLISECONDS); diff --git a/extensions/resteasy-reactive/rest-client/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/devservices/DevServicesRestClientHttpProxyProcessor.java b/extensions/resteasy-reactive/rest-client/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/devservices/DevServicesRestClientHttpProxyProcessor.java index a1a4d5c8f8fb87..9a68ff95ae88d9 100644 --- a/extensions/resteasy-reactive/rest-client/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/devservices/DevServicesRestClientHttpProxyProcessor.java +++ b/extensions/resteasy-reactive/rest-client/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/devservices/DevServicesRestClientHttpProxyProcessor.java @@ -15,6 +15,7 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.exception.UncheckedException; +import org.eclipse.microprofile.config.ConfigProvider; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.IndexView; import org.jboss.logging.Logger; @@ -31,6 +32,7 @@ import io.quarkus.rest.client.reactive.spi.RestClientHttpProxyBuildItem; import io.quarkus.restclient.config.RestClientsBuildTimeConfig; import io.quarkus.restclient.config.RestClientsBuildTimeConfig.RestClientBuildConfig; +import io.smallrye.config.SmallRyeConfig; @BuildSteps(onlyIfNot = IsNormal.class) public class DevServicesRestClientHttpProxyProcessor { @@ -61,6 +63,7 @@ public void determineRequiredProxies(RestClientsBuildTimeConfig restClientsBuild IndexView index = combinedIndexBuildItem.getIndex(); + SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class); Map configs = restClientsBuildTimeConfig.clients(); for (var configEntry : configs.entrySet()) { if (!configEntry.getValue().enableLocalProxy()) { @@ -68,7 +71,9 @@ public void determineRequiredProxies(RestClientsBuildTimeConfig restClientsBuild break; } - String configKey = sanitizeKey(configEntry.getKey()); + String configKey = configEntry.getKey(); + Map restClientValues = config.getValues("quarkus.rest-client." + "\"" + configKey + "\"", + String.class, String.class); RegisteredRestClientBuildItem matchingBI = null; // check if the configKey matches one of the @RegisterRestClient values @@ -80,8 +85,8 @@ public void determineRequiredProxies(RestClientsBuildTimeConfig restClientsBuild } if (matchingBI != null) { Optional baseUri = oneOf( - Optional.of(configEntry.getValue().properties().get("url")), - Optional.of(configEntry.getValue().properties().get("url")), + Optional.ofNullable(restClientValues.get("uri")), + Optional.ofNullable(restClientValues.get("url")), matchingBI.getDefaultBaseUri()); if (baseUri.isEmpty()) { @@ -99,8 +104,8 @@ public void determineRequiredProxies(RestClientsBuildTimeConfig restClientsBuild break; } Optional baseUri = oneOf( - Optional.of(configEntry.getValue().properties().get("url")), - Optional.of(configEntry.getValue().properties().get("url"))); + Optional.ofNullable(restClientValues.get("uri")), + Optional.ofNullable(restClientValues.get("url"))); if (baseUri.isEmpty()) { log.debug("Unable to determine uri or url for config key '" + configKey + "'"); break; @@ -111,13 +116,6 @@ public void determineRequiredProxies(RestClientsBuildTimeConfig restClientsBuild } } - private String sanitizeKey(String key) { - if (key.startsWith("\"") && key.endsWith("\"")) { - return key.substring(1, key.length() - 1); - } - return key; - } - @BuildStep public void start(List restClientHttpProxyBuildItems, List restClientProxyProviderBuildItems, diff --git a/extensions/resteasy-reactive/rest-client/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java b/extensions/resteasy-reactive/rest-client/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java index e1d5eb53db6701..353c186c08d9c3 100644 --- a/extensions/resteasy-reactive/rest-client/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java +++ b/extensions/resteasy-reactive/rest-client/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java @@ -402,12 +402,12 @@ private void configureTimeouts(QuarkusRestClientBuilder builder) { } private void configureBaseUrl(QuarkusRestClientBuilder builder) { - Optional propertyOptional = oneOf(clientConfigByClassName().uri(), - clientConfigByConfigKey().uri()); + Optional propertyOptional = oneOf(clientConfigByClassName().uriReload(), + clientConfigByConfigKey().uriReload()); if (propertyOptional.isEmpty()) { - propertyOptional = oneOf(clientConfigByClassName().url(), - clientConfigByConfigKey().url()); + propertyOptional = oneOf(clientConfigByClassName().urlReload(), + clientConfigByConfigKey().urlReload()); } if (((baseUriFromAnnotation == null) || baseUriFromAnnotation.isEmpty()) && propertyOptional.isEmpty()) { diff --git a/extensions/resteasy-reactive/rest-client/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java b/extensions/resteasy-reactive/rest-client/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java index 95eaf821219d5d..ded303760cd50f 100644 --- a/extensions/resteasy-reactive/rest-client/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java +++ b/extensions/resteasy-reactive/rest-client/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java @@ -110,7 +110,7 @@ public void testClientSpecificConfigs() { // then - verify(restClientBuilderMock).baseUri(URI.create("http://localhost")); + verify(restClientBuilderMock).baseUri(URI.create("http://localhost:8080")); verify(restClientBuilderMock).property(QuarkusRestClientProperties.SHARED, true); verify(restClientBuilderMock).property(QuarkusRestClientProperties.NAME, "my-client"); verify(restClientBuilderMock).property(MULTIPART_ENCODER_MODE, HTML5);