Skip to content

Commit

Permalink
Map injection behavior restored (3.x) (#4653)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
tomas-langer authored Aug 2, 2022
1 parent 00c4704 commit aaf0e0c
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -287,15 +296,19 @@ 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);
}
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();
Expand All @@ -307,6 +320,7 @@ private Object configValue(Config config, FieldTypes fieldTypes, String configKe
// generic declaration
return parameterizedConfigValue(config,
configKey,
defaultConfigKey,
defaultValue,
type0,
type1,
Expand Down Expand Up @@ -371,6 +385,7 @@ private static <T> T convert(String key, Config config, String value, Class<T> t

private static Object parameterizedConfigValue(Config config,
String configKey,
boolean defaultConfigKey,
String defaultValue,
Class<?> rawType,
Class<?> typeArg,
Expand All @@ -382,6 +397,7 @@ private static Object parameterizedConfigValue(Config config,
return Optional
.ofNullable(parameterizedConfigValue(config,
configKey,
defaultConfigKey,
defaultValue,
typeArg,
typeArg2,
Expand All @@ -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<String, String> 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)) {
Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -59,7 +61,29 @@ class MpConfigInjectionTest {
private SubBean subBean;

@Test
public void testImplicitConversion() {
void testInjectMapNoPrefix() {
Map<String, String> 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<String, String> 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"))),
Expand All @@ -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"))),
Expand All @@ -94,6 +118,14 @@ public static class Bean {
@Inject
@ConfigProperty(name = "inject.ctor")
public Ctor ctor;

@Inject
@ConfigProperty(name = "")
public Map<String, String> allProperties;

@Inject
@ConfigProperty(name = "inject")
public Map<String, String> injectProperties;
}

@Qualifier
Expand Down

0 comments on commit aaf0e0c

Please sign in to comment.