Skip to content

Commit

Permalink
Allow to fetch multiple config parameters as a Map (#579)
Browse files Browse the repository at this point in the history
  • Loading branch information
essobedo authored May 31, 2021
1 parent 96a0972 commit f2a291e
Show file tree
Hide file tree
Showing 12 changed files with 603 additions and 51 deletions.
15 changes: 14 additions & 1 deletion cdi/src/main/java/io/smallrye/config/inject/ConfigExtension.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
Expand Down Expand Up @@ -173,9 +174,10 @@ protected void validate(@Observes AfterDeploymentValidation adv) {

// Check if the name is part of the properties first.
// Since properties can be a subset, then search for the actual property for a value.
// Check if it is a map
// Finally also check if the property is indexed (might be a Collection with indexed properties).
if ((!configNames.contains(name) && ConfigProducerUtil.getRawValue(name, config) == null)
&& !isIndexed(type, name, config)) {
&& !isMap(type) && !isIndexed(type, name, config)) {
if (configProperty.defaultValue().equals(ConfigProperty.UNCONFIGURED_VALUE)) {
adv.addDeploymentProblem(
InjectionMessages.msg.noConfigValue(name, formatInjectionPoint(injectionPoint)));
Expand Down Expand Up @@ -232,4 +234,15 @@ private static boolean isIndexed(Type type, String name, Config config) {
&&
!((SmallRyeConfig) config).getIndexedPropertiesIndexes(name).isEmpty();
}

/**
* Indicates whether the given type is a type of Map.
*
* @param type the type to check
* @return {@code true} if the given type is a type of Map, {@code false} otherwise.
*/
private static boolean isMap(final Type type) {
return type instanceof ParameterizedType &&
Map.class.isAssignableFrom((Class<?>) ((ParameterizedType) type).getRawType());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,13 @@ protected <T> List<T> producesListConfigProperty(InjectionPoint ip) {
return ConfigProducerUtil.getValue(ip, getConfig(ip));
}

@Dependent
@Produces
@ConfigProperty
protected <K, V> Map<K, V> producesMapConfigProperty(InjectionPoint ip) {
return ConfigProducerUtil.getValue(ip, getConfig(ip));
}

@Dependent
@Produces
@ConfigProperty
Expand Down
163 changes: 135 additions & 28 deletions cdi/src/main/java/io/smallrye/config/inject/ConfigProducerUtil.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.smallrye.config.inject;

import static io.smallrye.config.Converters.newCollectionConverter;
import static io.smallrye.config.Converters.newMapConverter;
import static io.smallrye.config.Converters.newOptionalConverter;

import java.lang.annotation.Annotation;
Expand All @@ -12,6 +13,7 @@
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
Expand All @@ -27,8 +29,10 @@
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.config.spi.Converter;

import io.smallrye.config.Converters;
import io.smallrye.config.SecretKeys;
import io.smallrye.config.SmallRyeConfig;
import io.smallrye.config.common.AbstractConverter;
import io.smallrye.config.common.AbstractDelegatingConverter;

/**
Expand All @@ -51,27 +55,15 @@ private ConfigProducerUtil() {
* @return the converted configuration value.
*/
public static <T> T getValue(InjectionPoint injectionPoint, Config config) {
String name = getName(injectionPoint);
if (name == null) {
return null;
}

String defaultValue = getDefaultValue(injectionPoint);
String rawValue = getRawValue(name, config);
if (hasCollection(injectionPoint.getType())) {
return convertValues(name, injectionPoint.getType(), rawValue, defaultValue, config);
}

return ((SmallRyeConfig) config).convertValue(name, resolveDefault(rawValue, defaultValue),
resolveConverter(injectionPoint, config));
return getValue(getName(injectionPoint), injectionPoint.getType(), getDefaultValue(injectionPoint), config);
}

/**
* Retrieves a converted configuration value from {@link Config}.
*
* @param name the name of the configuration property.
* @param type the {@link Type} of the configuration value to convert.
* @param defaultValue the defaut value to use if no configuration value is found.
* @param defaultValue the default value to use if no configuration value is found.
* @param config the current {@link Config} instance.
*
* @return the converted configuration value.
Expand All @@ -80,8 +72,30 @@ public static <T> T getValue(String name, Type type, String defaultValue, Config
if (name == null) {
return null;
}
String resolvedValue = resolveValue(name, defaultValue, config);
return ((SmallRyeConfig) config).convertValue(name, resolvedValue, resolveConverter(type, config));
if (hasCollection(type)) {
return convertValues(name, type, getRawValue(name, config), defaultValue, config);
} else if (hasMap(type)) {
return convertValues(name, type, defaultValue, config);
}

return ((SmallRyeConfig) config).convertValue(name, resolveDefault(getRawValue(name, config), defaultValue),
resolveConverter(type, config));
}

/**
* Converts the direct sub properties of the given parent property as a Map.
*
* @param name the name of the parent property for which we want the direct sub properties as a Map.
* @param type the {@link Type} of the configuration value to convert.
* @param defaultValue the default value to convert in case no sub properties could be found.
* @param config the configuration from which the values are retrieved.
* @param <T> the expected type of the configuration value to convert.
*
* @return the converted configuration value.
*/
private static <T> T convertValues(String name, Type type, String defaultValue, Config config) {
return ((SmallRyeConfig) config).convertValue(name, null,
resolveConverter(type, config, (kC, vC) -> new StaticMapConverter<>(name, defaultValue, config, kC, vC)));
}

private static <T> T convertValues(String name, Type type, String rawValue, String defaultValue, Config config) {
Expand All @@ -98,7 +112,7 @@ private static <T> T convertValues(String name, Type type, String rawValue, Stri
for (String indexedProperty : indexedProperties) {
// Never null by the rules of converValue
collection.add(
((SmallRyeConfig) config).convertValue(indexedProperty, resolveValue(indexedProperty, null, config),
((SmallRyeConfig) config).convertValue(indexedProperty, getRawValue(indexedProperty, config),
itemConverter));
}
return collection;
Expand Down Expand Up @@ -127,21 +141,17 @@ static String getRawValue(String name, Config config) {
return SecretKeys.doUnlocked(() -> config.getConfigValue(name).getValue());
}

private static String resolveValue(String name, String defaultValue, Config config) {
String rawValue = getRawValue(name, config);
return resolveDefault(rawValue, defaultValue);
}

private static String resolveDefault(String rawValue, String defaultValue) {
return rawValue != null ? rawValue : defaultValue;
}

private static <T> Converter<T> resolveConverter(final InjectionPoint injectionPoint, final Config config) {
return resolveConverter(injectionPoint.getType(), config);
private static <T> Converter<T> resolveConverter(final Type type, final Config config) {
return resolveConverter(type, config, Converters::newMapConverter);
}

@SuppressWarnings("unchecked")
private static <T> Converter<T> resolveConverter(final Type type, final Config config) {
private static <T> Converter<T> resolveConverter(final Type type, final Config config,
final BiFunction<Converter<Object>, Converter<Object>, Converter<Map<Object, Object>>> mapConverterFactory) {
Class<T> rawType = rawTypeOf(type);
if (type instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) type;
Expand All @@ -150,11 +160,18 @@ private static <T> Converter<T> resolveConverter(final Type type, final Config c
return (Converter<T>) newCollectionConverter(resolveConverter(typeArgs[0], config), ArrayList::new);
} else if (rawType == Set.class) {
return (Converter<T>) newCollectionConverter(resolveConverter(typeArgs[0], config), HashSet::new);
} else if (rawType == Map.class) {
return (Converter<T>) mapConverterFactory.apply(resolveConverter(typeArgs[0], config),
resolveConverter(typeArgs[1], config));
} else if (rawType == Optional.class) {
return (Converter<T>) newOptionalConverter(resolveConverter(typeArgs[0], config));
return (Converter<T>) newOptionalConverter(resolveConverter(typeArgs[0], config, mapConverterFactory));
} else if (rawType == Supplier.class) {
return resolveConverter(typeArgs[0], config);
return resolveConverter(typeArgs[0], config, mapConverterFactory);
}
} else if (rawType == Map.class) {
// No parameterized types have been provided so it assumes that a Map of String is expected
return (Converter<T>) mapConverterFactory.apply(resolveConverter(String.class, config),
resolveConverter(String.class, config));
}
// just try the raw type
return config.getConverter(rawType).orElseThrow(() -> InjectionMessages.msg.noRegisteredConverter(rawType));
Expand Down Expand Up @@ -208,6 +225,23 @@ private static <T> Class<T> rawTypeOf(final Type type) {
}
}

/**
* Indicates whether the given type is a type of Map or is a Supplier or Optional of Map.
*
* @param type the type to check
* @return {@code true} if the given type is a type of Map or is a Supplier or Optional of Map,
* {@code false} otherwise.
*/
private static boolean hasMap(final Type type) {
Class<?> rawType = rawTypeOf(type);
if (rawType == Map.class) {
return true;
} else if (type instanceof ParameterizedType) {
return hasMap(((ParameterizedType) type).getActualTypeArguments()[0]);
}
return false;
}

private static <T> boolean hasCollection(final Type type) {
Class<T> rawType = rawTypeOf(type);
if (type instanceof ParameterizedType) {
Expand Down Expand Up @@ -278,7 +312,7 @@ static String getConfigKey(InjectionPoint ip, ConfigProperty configProperty) {
throw InjectionMessages.msg.noConfigPropertyDefaultName(ip);
}

final static class IndexedCollectionConverter<T, C extends Collection<T>> extends AbstractDelegatingConverter<T, C> {
static final class IndexedCollectionConverter<T, C extends Collection<T>> extends AbstractDelegatingConverter<T, C> {
private static final long serialVersionUID = 5186940408317652618L;

private final IntFunction<Collection<T>> collectionFactory;
Expand All @@ -300,4 +334,77 @@ public C convert(final String value) throws IllegalArgumentException, NullPointe
return (C) indexedConverter.apply((Converter<T>) getDelegate(), collectionFactory);
}
}

/**
* A {@code Converter} of a Map that gives the same Map content whatever the value to convert. It actually relies on
* its parameters to convert the sub properties of a fixed parent property as a Map.
*
* @param <K> The type of the keys.
* @param <V> The type of the values.
*/
static final class StaticMapConverter<K, V> extends AbstractConverter<Map<K, V>> {
private static final long serialVersionUID = 402894491607011464L;

/**
* The name of the parent property for which we want the direct sub properties as a Map.
*/
private final String name;
/**
* The default value to convert in case no sub properties could be found.
*/
private final String defaultValue;
/**
* The configuration from which the values are retrieved.
*/
private final Config config;
/**
* The converter to use for the keys.
*/
private final Converter<K> keyConverter;
/**
* The converter to use the for values.
*/
private final Converter<V> valueConverter;

/**
* Construct a {@code StaticMapConverter} with the given parameters.
*
* @param name the name of the parent property for which we want the direct sub properties as a Map
* @param defaultValue the default value to convert in case no sub properties could be found
* @param config the configuration from which the values are retrieved
* @param keyConverter the converter to use for the keys
* @param valueConverter the converter to use the for values
*/
StaticMapConverter(String name, String defaultValue, Config config, Converter<K> keyConverter,
Converter<V> valueConverter) {
this.name = name;
this.defaultValue = defaultValue;
this.config = config;
this.keyConverter = keyConverter;
this.valueConverter = valueConverter;
}

/**
* {@inheritDoc}
*
* Gives the sub properties as a Map if they exist, otherwise gives the default value converted with a
* {@code MapConverter}.
*/
@Override
public Map<K, V> convert(String value) throws IllegalArgumentException, NullPointerException {
Map<K, V> result = getValues(name, config, keyConverter, valueConverter);
if (result == null && defaultValue != null) {
result = newMapConverter(keyConverter, valueConverter).convert(defaultValue);
}
return result;
}

/**
* @return the content of the direct sub properties as the requested type of Map.
*/
private static <K, V> Map<K, V> getValues(String name, Config config, Converter<K> keyConverter,
Converter<V> valueConverter) {
return SecretKeys.doUnlocked(() -> ((SmallRyeConfig) config).getValuesAsMap(name, keyConverter, valueConverter));
}
}
}
Loading

0 comments on commit f2a291e

Please sign in to comment.