From aaf0e0c9638ea93068158cff2b20d886b6ecd7bd Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Tue, 2 Aug 2022 23:31:48 +0200 Subject: [PATCH] Map injection behavior restored (3.x) (#4653) * Map injection behavior restored to original (pre #1721). Injecting without explicit key will inject all properties, injecting with a key will inject properties where keys have the prefix removed (and only containing keys that are prefixed with the key). Signed-off-by: Tomas Langer --- .../config/ConfigCdiExtension.java | 48 ++++++++++++++++--- .../config/MpConfigInjectionTest.java | 36 +++++++++++++- 2 files changed, 75 insertions(+), 9 deletions(-) diff --git a/microprofile/config/src/main/java/io/helidon/microprofile/config/ConfigCdiExtension.java b/microprofile/config/src/main/java/io/helidon/microprofile/config/ConfigCdiExtension.java index 07e0bced73a..5c82b631f12 100644 --- a/microprofile/config/src/main/java/io/helidon/microprofile/config/ConfigCdiExtension.java +++ b/microprofile/config/src/main/java/io/helidon/microprofile/config/ConfigCdiExtension.java @@ -70,7 +70,7 @@ /** * Extension to enable config injection in CDI container (all of {@link io.helidon.config.Config}, - * {@link org.eclipse.microprofile.config.Config} and {@link ConfigProperty}). + * {@link org.eclipse.microprofile.config.Config} and {@link ConfigProperty} and {@link ConfigProperties}). */ public class ConfigCdiExtension implements Extension { private static final Logger LOGGER = Logger.getLogger(ConfigCdiExtension.class.getName()); @@ -248,10 +248,19 @@ private Object produce(InjectionPoint ip) { + " container initialization. This will not work nicely with Graal native-image"); } - return produce(configKey, ip.getType(), defaultValue(annotation)); + return produce(configKey, ip.getType(), defaultValue(annotation), configKey.equals(fullPath.replace('$', '.'))); } - private Object produce(String configKey, Type type, String defaultValue) { + /* + * Produce configuration value from injection point. + * + * @param configKey actual configuration key to find + * @param type type of the injected field/parameter + * @param defaultValue default value to be used + * @param defaultConfigKey whether the configKey is constructed from class name and field + * @return produced value to be injected + */ + private Object produce(String configKey, Type type, String defaultValue, boolean defaultConfigKey) { /* Supported types group x: @@ -287,7 +296,7 @@ any java class (except for parameterized types) } } - Object value = configValue(config, fieldTypes, configKey, defaultValue); + Object value = configValue(config, fieldTypes, configKey, defaultValue, defaultConfigKey); if (value == null) { throw new NoSuchElementException("Cannot find value for key: " + configKey); @@ -295,7 +304,11 @@ any java class (except for parameterized types) return value; } - private Object configValue(Config config, FieldTypes fieldTypes, String configKey, String defaultValue) { + private Object configValue(Config config, + FieldTypes fieldTypes, + String configKey, + String defaultValue, + boolean defaultConfigKey) { Class type0 = fieldTypes.field0().rawType(); Class type1 = fieldTypes.field1().rawType(); Class type2 = fieldTypes.field2().rawType(); @@ -307,6 +320,7 @@ private Object configValue(Config config, FieldTypes fieldTypes, String configKe // generic declaration return parameterizedConfigValue(config, configKey, + defaultConfigKey, defaultValue, type0, type1, @@ -371,6 +385,7 @@ private static T convert(String key, Config config, String value, Class t private static Object parameterizedConfigValue(Config config, String configKey, + boolean defaultConfigKey, String defaultValue, Class rawType, Class typeArg, @@ -382,6 +397,7 @@ private static Object parameterizedConfigValue(Config config, return Optional .ofNullable(parameterizedConfigValue(config, configKey, + defaultConfigKey, defaultValue, typeArg, typeArg2, @@ -395,17 +411,23 @@ private static Object parameterizedConfigValue(Config config, } else { return (Supplier) () -> parameterizedConfigValue(config, configKey, + defaultConfigKey, defaultValue, typeArg, typeArg2, typeArg2); } } else if (Map.class.isAssignableFrom(rawType)) { + // config key we have should serve as a prefix, and the properties should have it removed + // similar to what the original io.helidon.config.Config.get(configKey).detach() Map result = new HashMap<>(); config.getPropertyNames() .forEach(name -> { - // workaround for race condition (if key disappears from source after we call getPropertyNames - config.getOptionalValue(name, String.class).ifPresent(value -> result.put(name, value)); + if (defaultConfigKey || name.startsWith(configKey)) { + String key = removePrefix(configKey, defaultConfigKey, name); + // workaround for race condition (if key disappears from source after we call getPropertyNames) + config.getOptionalValue(name, String.class).ifPresent(value -> result.put(key, value)); + } }); return result; } else if (Set.class.isAssignableFrom(rawType)) { @@ -416,6 +438,18 @@ private static Object parameterizedConfigValue(Config config, } } + private static String removePrefix(String prefix, boolean defaultConfigKey, String key) { + if (defaultConfigKey) { + return key; + } + + String intermediate = key.substring(prefix.length()); + if (intermediate.startsWith(".")) { + return intermediate.substring(1); + } + return intermediate; + } + static String[] toArray(String stringValue) { String[] values = SPLIT_PATTERN.split(stringValue, -1); diff --git a/microprofile/config/src/test/java/io/helidon/microprofile/config/MpConfigInjectionTest.java b/microprofile/config/src/test/java/io/helidon/microprofile/config/MpConfigInjectionTest.java index 1a3d618b709..ff6a671028f 100644 --- a/microprofile/config/src/test/java/io/helidon/microprofile/config/MpConfigInjectionTest.java +++ b/microprofile/config/src/test/java/io/helidon/microprofile/config/MpConfigInjectionTest.java @@ -18,6 +18,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; +import java.util.Map; import io.helidon.microprofile.config.Converters.Ctor; import io.helidon.microprofile.config.Converters.Of; @@ -37,6 +38,7 @@ import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertAll; @@ -59,7 +61,29 @@ class MpConfigInjectionTest { private SubBean subBean; @Test - public void testImplicitConversion() { + void testInjectMapNoPrefix() { + Map allProperties = bean.allProperties; + assertAll( + () -> assertThat(allProperties, hasEntry("inject.of", "of")), + () -> assertThat(allProperties, hasEntry("inject.valueOf", "valueOf")), + () -> assertThat(allProperties, hasEntry("inject.parse", "parse")), + () -> assertThat(allProperties, hasEntry("inject.ctor", "ctor")) + ); + } + + @Test + void testInjectMapWithPrefix() { + Map injectProperties = bean.injectProperties; + assertAll( + () -> assertThat(injectProperties, hasEntry("of", "of")), + () -> assertThat(injectProperties, hasEntry("valueOf", "valueOf")), + () -> assertThat(injectProperties, hasEntry("parse", "parse")), + () -> assertThat(injectProperties, hasEntry("ctor", "ctor")) + ); + } + + @Test + void testImplicitConversion() { assertAll("Implicit conversion injection", () -> assertThat("of", bean.of, is(Of.of("of"))), () -> assertThat("valueOf", bean.valueOf, is(ValueOf.valueOf("valueOf"))), @@ -69,7 +93,7 @@ public void testImplicitConversion() { } @Test - public void testImplicitConversionSubclass() { + void testImplicitConversionSubclass() { assertAll("Implicit conversion injection", () -> assertThat("of", subBean.of, is(Of.of("of"))), () -> assertThat("valueOf", subBean.valueOf, is(ValueOf.valueOf("valueOf"))), @@ -94,6 +118,14 @@ public static class Bean { @Inject @ConfigProperty(name = "inject.ctor") public Ctor ctor; + + @Inject + @ConfigProperty(name = "") + public Map allProperties; + + @Inject + @ConfigProperty(name = "inject") + public Map injectProperties; } @Qualifier