From d9b62b339cdc7262a7385042dbe1556e2b59bb93 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 8 Jan 2024 16:34:39 -0600 Subject: [PATCH 1/2] Introduce `restart` method for interceptors First step in solving #1081 --- .../ConfigSourceInterceptorContext.java | 15 +++++++-- .../io/smallrye/config/SmallRyeConfig.java | 32 +++++++++++-------- ...mallRyeConfigSourceInterceptorContext.java | 10 +++++- .../SmallRyeConfigSourceInterceptors.java | 15 ++++++++- .../RelocateConfigSourceInterceptorTest.java | 8 +++++ 5 files changed, 62 insertions(+), 18 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/ConfigSourceInterceptorContext.java b/implementation/src/main/java/io/smallrye/config/ConfigSourceInterceptorContext.java index d679e33ef..ad3a2b59f 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigSourceInterceptorContext.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigSourceInterceptorContext.java @@ -11,16 +11,25 @@ public interface ConfigSourceInterceptorContext extends Serializable { /** * Proceeds to the next interceptor in the chain. * - * @param name the configuration name to lookup. Can be the original key. + * @param name the configuration name to look up (can be the original key) * @return a {@link ConfigValue} with information about the name, value, config source and ordinal, or {@code null} * if the value isn't present. */ ConfigValue proceed(String name); /** - * Proceeds to the next interceptor in the chain. + * Re-calls the first interceptor in the chain. + * If the original name is given, then it is possible to cause a recursive loop, so care must be taken. + * This method is intended to be used by relocating and other compatibility-related interceptors. * - * @return an Iterator of Strings with configuration names. + * @param name the configuration name to look up (can be the original key) + * @return a {@link ConfigValue} with information about the name, value, config source and ordinal, or {@code null} + * if the value isn't present. + */ + ConfigValue restart(String name); + + /** + * {@return an iterator over the configuration names known to this interceptor. */ Iterator iterateNames(); diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index 532c1a272..21b6e9fd2 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -80,7 +80,7 @@ public class SmallRyeConfig implements Config, Serializable { SmallRyeConfig(SmallRyeConfigBuilder builder) { // This needs to be executed before everything else to make sure that defaults from mappings are available to all sources ConfigMappingProvider mappingProvider = builder.getMappingsBuilder().build(); - this.configSources = new ConfigSources(builder); + this.configSources = new ConfigSources(builder, this); this.converters = buildConverters(builder); this.configValidator = builder.getValidator(); this.mappings = new ConcurrentHashMap<>(mappingProvider.mapConfiguration(this)); @@ -672,6 +672,10 @@ public ConfigSource getDefaultValues() { return configSources.defaultValues; } + ConfigSourceInterceptorContext interceptorChain() { + return configSources.interceptorChain; + } + private static class ConfigSources implements Serializable { private static final long serialVersionUID = 3483018375584151712L; @@ -686,7 +690,7 @@ private static class ConfigSources implements Serializable { * that this constructor must be used when the Config object is being initialized, because interceptors also * require initialization. */ - ConfigSources(final SmallRyeConfigBuilder builder) { + ConfigSources(final SmallRyeConfigBuilder builder, final SmallRyeConfig config) { // Add all sources except for ConfigurableConfigSource types. These are initialized later List sources = buildSources(builder); // Add the default values sources separately, so we can keep a reference to it and add mappings defaults @@ -698,27 +702,29 @@ private static class ConfigSources implements Serializable { List interceptorWithPriorities = buildInterceptors(builder); // Create the initial chain with initial sources and all interceptors - SmallRyeConfigSourceInterceptorContext current = new SmallRyeConfigSourceInterceptorContext(EMPTY, null); - current = new SmallRyeConfigSourceInterceptorContext(new SmallRyeConfigSources(mapSources(sources)), current); + SmallRyeConfigSourceInterceptorContext current = new SmallRyeConfigSourceInterceptorContext(EMPTY, null, config); + current = new SmallRyeConfigSourceInterceptorContext(new SmallRyeConfigSources(mapSources(sources)), current, + config); for (InterceptorWithPriority interceptorWithPriority : interceptorWithPriorities) { ConfigSourceInterceptor interceptor = interceptorWithPriority.getInterceptor(current); interceptors.add(interceptor); - current = new SmallRyeConfigSourceInterceptorContext(interceptor, current); + current = new SmallRyeConfigSourceInterceptorContext(interceptor, current, config); } // Init all late sources List profiles = getProfiles(interceptors); List sourcesWithPriorities = mapLateSources(sources, interceptors, current, profiles, - builder); + builder, config); List configSources = getSources(sourcesWithPriorities); // Rebuild the chain with the late sources and new instances of the interceptors // The new instance will ensure that we get rid of references to factories and other stuff and keep only // the resolved final source or interceptor to use. - current = new SmallRyeConfigSourceInterceptorContext(EMPTY, null); - current = new SmallRyeConfigSourceInterceptorContext(new SmallRyeConfigSources(sourcesWithPriorities), current); + current = new SmallRyeConfigSourceInterceptorContext(EMPTY, null, config); + current = new SmallRyeConfigSourceInterceptorContext(new SmallRyeConfigSources(sourcesWithPriorities), current, + config); for (ConfigSourceInterceptor interceptor : interceptors) { - current = new SmallRyeConfigSourceInterceptorContext(interceptor, current); + current = new SmallRyeConfigSourceInterceptorContext(interceptor, current, config); } this.profiles = profiles; @@ -785,7 +791,7 @@ private static List mapLateSources( final List interceptors, final ConfigSourceInterceptorContext current, final List profiles, - final SmallRyeConfigBuilder builder) { + final SmallRyeConfigBuilder builder, final SmallRyeConfig config) { ConfigSourceWithPriority.resetLoadPriority(); List currentSources = new ArrayList<>(); @@ -806,10 +812,10 @@ private static List mapLateSources( Collections.reverse(currentSources); // Rebuild the chain with the profiles sources, so profiles values are also available in factories - ConfigSourceInterceptorContext context = new SmallRyeConfigSourceInterceptorContext(EMPTY, null); - context = new SmallRyeConfigSourceInterceptorContext(new SmallRyeConfigSources(currentSources), context); + ConfigSourceInterceptorContext context = new SmallRyeConfigSourceInterceptorContext(EMPTY, null, config); + context = new SmallRyeConfigSourceInterceptorContext(new SmallRyeConfigSources(currentSources), context, config); for (ConfigSourceInterceptor interceptor : interceptors) { - context = new SmallRyeConfigSourceInterceptorContext(interceptor, context); + context = new SmallRyeConfigSourceInterceptorContext(interceptor, context, config); } // Init remaining sources, coming from SmallRyeConfig diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceInterceptorContext.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceInterceptorContext.java index db6eb1e40..1975e3f69 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceInterceptorContext.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceInterceptorContext.java @@ -7,12 +7,15 @@ class SmallRyeConfigSourceInterceptorContext implements ConfigSourceInterceptorC private final ConfigSourceInterceptor interceptor; private final ConfigSourceInterceptorContext next; + private final SmallRyeConfig config; SmallRyeConfigSourceInterceptorContext( final ConfigSourceInterceptor interceptor, - final ConfigSourceInterceptorContext next) { + final ConfigSourceInterceptorContext next, + final SmallRyeConfig config) { this.interceptor = interceptor; this.next = next; + this.config = config; } @Override @@ -20,6 +23,11 @@ public ConfigValue proceed(final String name) { return interceptor.getValue(next, name); } + @Override + public ConfigValue restart(final String name) { + return config.interceptorChain().proceed(name); + } + @Override public Iterator iterateNames() { return interceptor.iterateNames(next); diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceInterceptors.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceInterceptors.java index 18079d9e4..db8b0c397 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceInterceptors.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceInterceptors.java @@ -31,6 +31,16 @@ public ConfigValue proceed(final String name) { return interceptors.get(position++).getValue(this, name); } + public ConfigValue restart(final String name) { + int old = position; + position = 0; + try { + return proceed(name); + } finally { + position = old; + } + } + @Override public Iterator iterateNames() { return null; @@ -41,10 +51,13 @@ public Iterator iterateValues() { return null; } }; - return context.proceed(name); } + public ConfigValue restart(final String name) { + throw new UnsupportedOperationException(); + } + @Override public Iterator iterateNames() { return null; diff --git a/implementation/src/test/java/io/smallrye/config/RelocateConfigSourceInterceptorTest.java b/implementation/src/test/java/io/smallrye/config/RelocateConfigSourceInterceptorTest.java index cb0ae3889..fa4709eae 100644 --- a/implementation/src/test/java/io/smallrye/config/RelocateConfigSourceInterceptorTest.java +++ b/implementation/src/test/java/io/smallrye/config/RelocateConfigSourceInterceptorTest.java @@ -154,6 +154,10 @@ public ConfigValue proceed(final String name) { return null; } + public ConfigValue restart(final String name) { + return null; + } + @Override public Iterator iterateNames() { return null; @@ -197,6 +201,10 @@ public ConfigValue proceed(final String name) { return null; } + public ConfigValue restart(final String name) { + return null; + } + @Override public Iterator iterateNames() { return null; From a8d630a0c601b6afe9d7c375edc89da537a57653 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Tue, 9 Jan 2024 07:40:13 -0600 Subject: [PATCH 2/2] Add recursion limiter --- ...mallRyeConfigSourceInterceptorContext.java | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceInterceptorContext.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceInterceptorContext.java index 1975e3f69..4251835c6 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceInterceptorContext.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigSourceInterceptorContext.java @@ -9,6 +9,8 @@ class SmallRyeConfigSourceInterceptorContext implements ConfigSourceInterceptorC private final ConfigSourceInterceptorContext next; private final SmallRyeConfig config; + private static final ThreadLocal rcHolder = ThreadLocal.withInitial(RecursionCount::new); + SmallRyeConfigSourceInterceptorContext( final ConfigSourceInterceptor interceptor, final ConfigSourceInterceptorContext next, @@ -25,7 +27,13 @@ public ConfigValue proceed(final String name) { @Override public ConfigValue restart(final String name) { - return config.interceptorChain().proceed(name); + RecursionCount rc = rcHolder.get(); + rc.increment(); + try { + return config.interceptorChain().proceed(name); + } finally { + rc.decrement(); + } } @Override @@ -37,4 +45,20 @@ public Iterator iterateNames() { public Iterator iterateValues() { return interceptor.iterateValues(next); } + + static final class RecursionCount { + int count; + + void increment() { + int old = count; + if (old == 20) { + throw new IllegalStateException("Too many recursive interceptor actions"); + } + count = old + 1; + } + + void decrement() { + count--; + } + } }