result = find(ni);
+ return result != null && result.hasRootValue();
+ }
+
+ public KeyMap map(P param, BiFunction conversion) {
+ return map(param, conversion, new IdentityHashMap<>());
+ }
+
+ public KeyMap map(Function conversion) {
+ return map(conversion, Functions.functionBiFunction());
+ }
+
+ KeyMap map(P param, BiFunction conversion, IdentityHashMap, KeyMap> cached) {
+ if (cached.containsKey(this)) {
+ return cached.get(this);
+ }
+ KeyMap newMap = new KeyMap<>(size());
+ cached.put(this, newMap);
+ Set>> entries = entrySet();
+ for (Entry> entry : entries) {
+ newMap.put(entry.getKey(), entry.getValue().map(param, conversion, cached));
+ }
+ KeyMap any = getAny();
+ if (any != null) {
+ newMap.putAny(any.map(param, conversion, cached));
+ }
+ if (hasRootValue()) {
+ newMap.putRootValue(conversion.apply(param, getRootValue()));
+ }
+ return newMap;
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public StringBuilder toString(StringBuilder b) {
+ b.append("KeyMap(");
+ V rootValue = this.rootValue;
+ if (rootValue == NO_VALUE) {
+ b.append("no value");
+ } else {
+ b.append("value=").append(rootValue);
+ }
+ b.append(") {");
+ final Iterator>> iterator = entrySet().iterator();
+ KeyMap any = this.any;
+ if (iterator.hasNext()) {
+ Entry> entry = iterator.next();
+ b.append(entry.getKey()).append("=>");
+ entry.getValue().toString(b);
+ while (iterator.hasNext()) {
+ entry = iterator.next();
+ b.append(',').append(entry.getKey()).append("=>");
+ entry.getValue().toString(b);
+ }
+ if (any != null) {
+ b.append(',').append("(any)=>");
+ any.toString(b);
+ }
+ } else {
+ if (any != null) {
+ b.append("(any)=>");
+ any.toString(b);
+ }
+ }
+ b.append('}');
+ return b;
+ }
+
+ public String toString() {
+ return toString(new StringBuilder()).toString();
+ }
+}
diff --git a/implementation/src/main/java/io/smallrye/config/KeyMapBackedConfigSource.java b/implementation/src/main/java/io/smallrye/config/KeyMapBackedConfigSource.java
new file mode 100644
index 000000000..b985d5f27
--- /dev/null
+++ b/implementation/src/main/java/io/smallrye/config/KeyMapBackedConfigSource.java
@@ -0,0 +1,34 @@
+package io.smallrye.config;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.eclipse.microprofile.config.spi.ConfigSource;
+
+import io.smallrye.config.common.AbstractConfigSource;
+
+public class KeyMapBackedConfigSource extends AbstractConfigSource {
+ private static final long serialVersionUID = 4378754290346888762L;
+
+ private final KeyMap properties;
+
+ public KeyMapBackedConfigSource(final String name, final KeyMap properties) {
+ super(name, ConfigSource.DEFAULT_ORDINAL);
+ this.properties = properties;
+ }
+
+ public KeyMapBackedConfigSource(final String name, final int ordinal, final KeyMap properties) {
+ super(name, ordinal);
+ this.properties = properties;
+ }
+
+ @Override
+ public Map getProperties() {
+ return Collections.emptyMap();
+ }
+
+ @Override
+ public String getValue(final String propertyName) {
+ return properties.findRootValue(propertyName);
+ }
+}
diff --git a/implementation/src/main/java/io/smallrye/config/NameIterator.java b/implementation/src/main/java/io/smallrye/config/NameIterator.java
new file mode 100644
index 000000000..5c450857b
--- /dev/null
+++ b/implementation/src/main/java/io/smallrye/config/NameIterator.java
@@ -0,0 +1,350 @@
+package io.smallrye.config;
+
+import java.util.NoSuchElementException;
+
+import io.smallrye.common.constraint.Assert;
+
+/**
+ * An iterator for property name strings.
+ */
+public final class NameIterator {
+ /**
+ * Configuration key maximum allowed length.
+ */
+ public static final int MAX_LENGTH = 2048;
+ private static final int POS_MASK = 0x0FFF;
+ private static final int POS_BITS = 12;
+ private static final int SE_SHIFT = 32 - POS_BITS;
+
+ private final String name;
+ private int pos;
+
+ public NameIterator(final String name) {
+ this(name, false);
+ }
+
+ public NameIterator(final String name, final boolean startAtEnd) {
+ this(name, startAtEnd ? name.length() : -1);
+ }
+
+ public NameIterator(final String name, final int pos) {
+ Assert.checkNotNullParam("name", name);
+ if (name.length() > MAX_LENGTH)
+ throw new IllegalArgumentException("Name is too long");
+ Assert.checkMinimumParameter("pos", -1, pos);
+ Assert.checkMaximumParameter("pos", name.length(), pos);
+ if (pos != -1 && pos != name.length() && name.charAt(pos) != '.')
+ throw new IllegalArgumentException("Position is not located at a delimiter");
+ this.name = name;
+ this.pos = pos;
+ }
+
+ public void goToEnd() {
+ this.pos = name.length();
+ }
+
+ public void goToStart() {
+ this.pos = -1;
+ }
+
+ /**
+ * Get the cursor position. It will be {@code -1} if the cursor is at the beginning of the string, or {@code name.length()}
+ * if it is at the end.
+ *
+ * @return the cursor position
+ */
+ public int getPosition() {
+ return pos;
+ }
+
+ /*
+ * next-iteration DFA
+ * → ## on EOI
+ * I → ## on '.'
+ * I → Q ## on '"'
+ * Q → I ## on '"'
+ * Q → QBS ## on '\'
+ * QBS → Q ## on any
+ * I → BS ## on '\'
+ * BS → I ## on any
+ */
+ private static final int FS_INITIAL = 0;
+ private static final int FS_QUOTE = 1;
+ private static final int FS_BACKSLASH = 2;
+ private static final int FS_QUOTE_BACKSLASH = 3;
+
+ //@formatter:off
+ /*
+ * Iteration cookie format
+ *
+ * Bit: 14...12 11 ... 0
+ * ┌───────┬────────────┐
+ * │ state │ position │
+ * │ │ (signed) │
+ * └───────┴────────────┘
+ */
+ //@formatter:on
+
+ /**
+ * Create a new iteration cookie at the current position.
+ *
+ * @return the new cookie
+ */
+ private int initIteration() {
+ return this.pos & POS_MASK;
+ }
+
+ private int cookieOf(int state, int pos) {
+ return state << POS_BITS | pos & POS_MASK;
+ }
+
+ private int getPosition(int cookie) {
+ return (cookie & POS_MASK) << SE_SHIFT >> SE_SHIFT;
+ }
+
+ private int getState(int cookie) {
+ return cookie >> POS_BITS;
+ }
+
+ /**
+ * Move to the next position.
+ *
+ * @param cookie the original cookie value
+ * @return the new cookie value
+ */
+ private int nextPos(int cookie) {
+ int pos = getPosition(cookie);
+ if (isEndOfString(cookie)) {
+ throw new NoSuchElementException();
+ }
+ int state = getState(cookie);
+ int ch;
+ for (;;) {
+ pos++;
+ if (pos == name.length()) {
+ return cookieOf(state, pos);
+ }
+ ch = name.charAt(pos);
+ if (state == FS_INITIAL) {
+ if (ch == '.') {
+ return cookieOf(state, pos);
+ } else if (ch == '"') {
+ state = FS_QUOTE;
+ } else if (ch == '\\') {
+ state = FS_BACKSLASH;
+ } else {
+ return cookieOf(state, pos);
+ }
+ } else if (state == FS_QUOTE) {
+ if (ch == '"') {
+ state = FS_INITIAL;
+ } else if (ch == '\\') {
+ state = FS_QUOTE_BACKSLASH;
+ } else {
+ return cookieOf(state, pos);
+ }
+ } else if (state == FS_BACKSLASH) {
+ state = FS_INITIAL;
+ return cookieOf(state, pos);
+ } else {
+ assert state == FS_QUOTE_BACKSLASH;
+ state = FS_QUOTE;
+ return cookieOf(state, pos);
+ }
+ }
+ }
+
+ private int prevPos(int cookie) {
+ int pos = getPosition(cookie);
+ if (isStartOfString(cookie)) {
+ throw new NoSuchElementException();
+ }
+ int state = getState(cookie);
+ int ch;
+ for (;;) {
+ pos--;
+ if (pos == -1) {
+ return cookieOf(state, pos);
+ }
+ ch = name.charAt(pos);
+ if (state == FS_INITIAL) {
+ if (pos >= 1 && name.charAt(pos - 1) == '\\') {
+ // always accept as-is
+ return cookieOf(state, pos);
+ } else if (ch == '.') {
+ return cookieOf(state, pos);
+ } else if (ch == '"') {
+ state = FS_QUOTE;
+ } else if (ch == '\\') {
+ // skip
+ } else {
+ // regular char
+ return cookieOf(state, pos);
+ }
+ } else if (state == FS_QUOTE) {
+ if (pos >= 1 && name.charAt(pos - 1) == '\\') {
+ // always accept as-is
+ return cookieOf(state, pos);
+ } else if (ch == '"') {
+ state = FS_INITIAL;
+ } else if (ch == '\\') {
+ // skip
+ } else {
+ return cookieOf(state, pos);
+ }
+ } else {
+ throw Assert.unreachableCode();
+ }
+ }
+ }
+
+ private boolean isSegmentDelimiter(int cookie) {
+ return isStartOfString(cookie) || isEndOfString(cookie) || getState(cookie) == FS_INITIAL && charAt(cookie) == '.';
+ }
+
+ private boolean isEndOfString(int cookie) {
+ return getPosition(cookie) == name.length();
+ }
+
+ private boolean isStartOfString(int cookie) {
+ return getPosition(cookie) == -1;
+ }
+
+ private int charAt(int cookie) {
+ return name.charAt(getPosition(cookie));
+ }
+
+ public int getPreviousStart() {
+ int cookie = initIteration();
+ do {
+ cookie = prevPos(cookie);
+ } while (!isSegmentDelimiter(cookie));
+ return getPosition(cookie) + 1;
+ }
+
+ public int getNextEnd() {
+ int cookie = initIteration();
+ do {
+ cookie = nextPos(cookie);
+ } while (!isSegmentDelimiter(cookie));
+ return getPosition(cookie);
+ }
+
+ public boolean nextSegmentEquals(String other) {
+ return nextSegmentEquals(other, 0, other.length());
+ }
+
+ public boolean nextSegmentEquals(String other, int offs, int len) {
+ int cookie = initIteration();
+ int strPos = 0;
+ for (;;) {
+ cookie = nextPos(cookie);
+ if (isSegmentDelimiter(cookie)) {
+ return strPos == len;
+ }
+ if (strPos == len) {
+ return false;
+ }
+ if (other.charAt(offs + strPos) != charAt(cookie)) {
+ return false;
+ }
+ strPos++;
+ }
+ }
+
+ public String getNextSegment() {
+ final StringBuilder b = new StringBuilder();
+ int cookie = initIteration();
+ for (;;) {
+ cookie = nextPos(cookie);
+ if (isSegmentDelimiter(cookie)) {
+ return b.toString();
+ }
+ b.append((char) charAt(cookie));
+ }
+ }
+
+ public boolean previousSegmentEquals(String other) {
+ return previousSegmentEquals(other, 0, other.length());
+ }
+
+ public boolean previousSegmentEquals(final String other, final int offs, final int len) {
+ int cookie = initIteration();
+ int strPos = len;
+ for (;;) {
+ strPos--;
+ cookie = prevPos(cookie);
+ if (isSegmentDelimiter(cookie)) {
+ return strPos == -1;
+ }
+ if (strPos == -1) {
+ return false;
+ }
+ if (other.charAt(offs + strPos) != charAt(cookie)) {
+ return false;
+ }
+ }
+ }
+
+ public String getPreviousSegment() {
+ final StringBuilder b = new StringBuilder();
+ int cookie = initIteration();
+ for (;;) {
+ cookie = prevPos(cookie);
+ if (isSegmentDelimiter(cookie)) {
+ return b.reverse().toString();
+ }
+ b.append((char) charAt(cookie));
+ }
+ }
+
+ public String getAllPreviousSegments() {
+ final int pos = getPosition();
+ if (pos == -1) {
+ return "";
+ }
+ return name.substring(0, pos);
+ }
+
+ public String getAllPreviousSegmentsWith(String suffix) {
+ final int pos = getPosition();
+ if (pos == -1) {
+ return suffix;
+ }
+ return name.substring(0, pos) + "." + suffix;
+ }
+
+ public boolean hasNext() {
+ return pos < name.length();
+ }
+
+ public boolean hasPrevious() {
+ return pos > -1;
+ }
+
+ public void next() {
+ pos = getNextEnd();
+ }
+
+ public void previous() {
+ pos = getPreviousStart() - 1;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String toString() {
+ if (pos == -1) {
+ return "*" + name;
+ } else if (pos == name.length()) {
+ return name + "*";
+ } else {
+ return name.substring(0, pos) + '*' + name.substring(pos + 1);
+ }
+ }
+
+ public void appendTo(final StringBuilder sb) {
+ sb.append(getAllPreviousSegments());
+ }
+}
diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java
index 44bff1143..eeca7c1b8 100644
--- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java
+++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java
@@ -45,7 +45,6 @@
import io.smallrye.common.annotation.Experimental;
import io.smallrye.config.SmallRyeConfigBuilder.InterceptorWithPriority;
-import io.smallrye.config.common.MapBackedConfigSource;
/**
* @author Jeff Mesnil (c) 2017 Red Hat inc.
@@ -57,9 +56,12 @@ public class SmallRyeConfig implements Config, Serializable {
private final Map> converters;
private final Map>> optionalConverters = new ConcurrentHashMap<>();
- SmallRyeConfig(SmallRyeConfigBuilder builder) {
+ private final ConfigMappings mappings;
+
+ SmallRyeConfig(SmallRyeConfigBuilder builder, ConfigMappings mappings) {
this.configSources = new AtomicReference<>(new ConfigSources(buildConfigSources(builder), buildInterceptors(builder)));
this.converters = buildConverters(builder);
+ this.mappings = mappings;
}
@Deprecated
@@ -68,6 +70,7 @@ protected SmallRyeConfig(List configSources, Map(Converters.ALL_CONVERTERS);
this.converters.putAll(converters);
+ this.mappings = new ConfigMappings();
}
private List buildConfigSources(final SmallRyeConfigBuilder builder) {
@@ -78,16 +81,6 @@ private List buildConfigSources(final SmallRyeConfigBuilder builde
if (builder.isAddDefaultSources()) {
sourcesToBuild.addAll(builder.getDefaultSources());
}
- if (!builder.getDefaultValues().isEmpty()) {
- sourcesToBuild.add(new MapBackedConfigSource("DefaultValuesConfigSource", builder.getDefaultValues()) {
- private static final long serialVersionUID = -2569643736033594267L;
-
- @Override
- public int getOrdinal() {
- return Integer.MIN_VALUE;
- }
- });
- }
// wrap all
final Function sourceWrappersToBuild = builder.getSourceWrappers();
@@ -211,6 +204,16 @@ public > Optional getOptionalValues(String name, C
return getOptionalValue(name, Converters.newCollectionConverter(converter, collectionFactory));
}
+ @Experimental("TODO")
+ public T getConfigMapping(Class type) {
+ return getConfigMapping(type, "");
+ }
+
+ @Experimental("TODO")
+ public T getConfigMapping(Class type, String prefix) {
+ return mappings.getConfigMapping(type, prefix);
+ }
+
@Override
public Iterable getPropertyNames() {
final HashSet names = new HashSet<>();
diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java
index d1823edbb..dce4a71a6 100644
--- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java
+++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java
@@ -56,6 +56,7 @@ public class SmallRyeConfigBuilder implements ConfigBuilder {
private final Set secretKeys = new HashSet<>();
private final List interceptors = new ArrayList<>();
private final Map defaultValues = new HashMap<>();
+ private final ConfigMappingProvider.Builder mappingsBuilder = ConfigMappingProvider.builder();
private ClassLoader classLoader = SecuritySupport.getContextClassLoader();
private boolean addDefaultSources = false;
private boolean addDefaultInterceptors = false;
@@ -224,6 +225,20 @@ public SmallRyeConfigBuilder withDefaultValues(Map defaultValues
return this;
}
+ public SmallRyeConfigBuilder withMapping(Class> klass) {
+ return withMapping(klass, "");
+ }
+
+ public SmallRyeConfigBuilder withMapping(Class> klass, String prefix) {
+ mappingsBuilder.addRoot(prefix, klass);
+ return this;
+ }
+
+ public SmallRyeConfigBuilder withMappingIgnore(String path) {
+ mappingsBuilder.addIgnored(path);
+ return this;
+ }
+
@Override
public SmallRyeConfigBuilder withConverters(Converter>[] converters) {
for (Converter> converter : converters) {
@@ -314,7 +329,22 @@ boolean isAddDiscoveredInterceptors() {
@Override
public SmallRyeConfig build() {
- return new SmallRyeConfig(this);
+ ConfigMappingProvider mappingProvider = mappingsBuilder.build();
+ if (!defaultValues.isEmpty() || !mappingProvider.getDefaultValues().isEmpty()) {
+ final KeyMap mappingProviderDefaultValues = mappingProvider.getDefaultValues();
+ defaultValues.forEach((key, value) -> mappingProviderDefaultValues.findOrAdd(key).putRootValue(value));
+ withSources(
+ new KeyMapBackedConfigSource("DefaultValuesConfigSource", Integer.MIN_VALUE, mappingProviderDefaultValues));
+ }
+
+ try {
+ ConfigMappings configMappings = new ConfigMappings();
+ SmallRyeConfig config = new SmallRyeConfig(this, configMappings);
+ mappingProvider.mapConfiguration(config, configMappings);
+ return config;
+ } catch (ConfigValidationException e) {
+ throw new IllegalStateException(e);
+ }
}
static class ConverterWithPriority {
diff --git a/implementation/src/main/java/io/smallrye/config/WithConverter.java b/implementation/src/main/java/io/smallrye/config/WithConverter.java
new file mode 100644
index 000000000..cf8a5b603
--- /dev/null
+++ b/implementation/src/main/java/io/smallrye/config/WithConverter.java
@@ -0,0 +1,24 @@
+package io.smallrye.config;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.eclipse.microprofile.config.spi.Converter;
+
+/**
+ * Specify the converter to use to convert the annotated type.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE_USE)
+public @interface WithConverter {
+ /**
+ * The converter class to use.
+ *
+ * @return the converter class
+ */
+ Class extends Converter>> value();
+}
diff --git a/implementation/src/main/java/io/smallrye/config/WithDefault.java b/implementation/src/main/java/io/smallrye/config/WithDefault.java
new file mode 100644
index 000000000..5051fe6cc
--- /dev/null
+++ b/implementation/src/main/java/io/smallrye/config/WithDefault.java
@@ -0,0 +1,17 @@
+package io.smallrye.config;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specify the default value of a property.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface WithDefault {
+ String value();
+}
diff --git a/implementation/src/main/java/io/smallrye/config/WithName.java b/implementation/src/main/java/io/smallrye/config/WithName.java
new file mode 100644
index 000000000..880188223
--- /dev/null
+++ b/implementation/src/main/java/io/smallrye/config/WithName.java
@@ -0,0 +1,22 @@
+package io.smallrye.config;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The name of the configuration property or group.
+ */
+@Documented
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface WithName {
+ /**
+ * The name of the property or group. Must not be empty.
+ *
+ * @return the name
+ */
+ String value();
+}
diff --git a/implementation/src/main/java/io/smallrye/config/WithParentName.java b/implementation/src/main/java/io/smallrye/config/WithParentName.java
new file mode 100644
index 000000000..a0769c30d
--- /dev/null
+++ b/implementation/src/main/java/io/smallrye/config/WithParentName.java
@@ -0,0 +1,16 @@
+package io.smallrye.config;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Use the parent's configuration name.
+ */
+@Documented
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface WithParentName {
+}
diff --git a/implementation/src/main/java/io/smallrye/config/inject/ConfigExtension.java b/implementation/src/main/java/io/smallrye/config/inject/ConfigExtension.java
index 2937880d5..38397f9f7 100644
--- a/implementation/src/main/java/io/smallrye/config/inject/ConfigExtension.java
+++ b/implementation/src/main/java/io/smallrye/config/inject/ConfigExtension.java
@@ -29,7 +29,9 @@
import java.util.function.Supplier;
import java.util.stream.StreamSupport;
+import javax.enterprise.context.Dependent;
import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Default;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.AfterDeploymentValidation;
@@ -38,7 +40,9 @@
import javax.enterprise.inject.spi.BeforeBeanDiscovery;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.enterprise.inject.spi.ProcessInjectionPoint;
+import javax.enterprise.inject.spi.WithAnnotations;
import javax.inject.Provider;
import org.eclipse.microprofile.config.Config;
@@ -46,6 +50,7 @@
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.config.spi.Converter;
+import io.smallrye.config.ConfigMapping;
import io.smallrye.config.ConfigValue;
import io.smallrye.config.SecretKeys;
import io.smallrye.config.SmallRyeConfig;
@@ -57,6 +62,7 @@
*/
public class ConfigExtension implements Extension {
private final Set injectionPoints = new HashSet<>();
+ private final Set> configMappings = new HashSet<>();
public ConfigExtension() {
}
@@ -66,6 +72,11 @@ protected void beforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd, BeanManage
bbd.addAnnotatedType(configBean, ConfigProducer.class.getName());
}
+ protected void processConfigMappings(
+ @Observes @WithAnnotations({ ConfigMapping.class }) ProcessAnnotatedType> processAnnotatedType) {
+ configMappings.add(processAnnotatedType.getAnnotatedType());
+ }
+
protected void collectConfigPropertyInjectionPoints(@Observes ProcessInjectionPoint, ?> pip) {
if (pip.getInjectionPoint().getAnnotated().isAnnotationPresent(ConfigProperty.class)) {
injectionPoints.add(pip.getInjectionPoint());
@@ -96,6 +107,22 @@ protected void registerCustomBeans(@Observes AfterBeanDiscovery abd, BeanManager
for (Class> customType : customTypes) {
abd.addBean(new ConfigInjectionBean<>(bm, customType));
}
+
+ SmallRyeConfig config = (SmallRyeConfig) ConfigProvider.getConfig(getContextClassLoader());
+ for (AnnotatedType> configMapping : configMappings) {
+ abd.addBean()
+ .id(configMapping.getJavaClass().toString())
+ .beanClass(configMapping.getJavaClass())
+ .types(configMapping.getJavaClass())
+ .qualifiers(Default.Literal.INSTANCE)
+ .scope(Dependent.class)
+ .createWith(creationalContext -> {
+ String prefix = Optional.ofNullable(configMapping.getAnnotation(ConfigMapping.class))
+ .map(ConfigMapping::value)
+ .orElse("");
+ return config.getConfigMapping(configMapping.getJavaClass(), prefix);
+ });
+ }
}
protected void validate(@Observes AfterDeploymentValidation adv) {
diff --git a/implementation/src/test/java/io/smallrye/config/ConfigMappingProviderTest.java b/implementation/src/test/java/io/smallrye/config/ConfigMappingProviderTest.java
new file mode 100644
index 000000000..a59fee94b
--- /dev/null
+++ b/implementation/src/test/java/io/smallrye/config/ConfigMappingProviderTest.java
@@ -0,0 +1,467 @@
+package io.smallrye.config;
+
+import static io.smallrye.config.KeyValuesConfigSource.config;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.StreamSupport.stream;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+import java.util.OptionalInt;
+import java.util.stream.Stream;
+
+import org.eclipse.microprofile.config.spi.Converter;
+import org.junit.jupiter.api.Test;
+
+import io.smallrye.config.common.MapBackedConfigSource;
+
+public class ConfigMappingProviderTest {
+ @Test
+ void configMapping() {
+ final SmallRyeConfig config = new SmallRyeConfigBuilder()
+ .withMapping(Server.class, "server")
+ .withSources(config("server.host", "localhost", "server.port", "8080")).build();
+ final Server configProperties = config.getConfigMapping(Server.class, "server");
+ assertEquals("localhost", configProperties.host());
+ assertEquals(8080, configProperties.port());
+ }
+
+ @Test
+ void noConfigMapping() {
+ final SmallRyeConfig config = new SmallRyeConfigBuilder()
+ .withSources(config("server.host", "localhost", "server.port", "8080")).build();
+ assertThrows(NoSuchElementException.class, () -> config.getConfigMapping(Server.class, "server"),
+ "Could not find a mapping for " + Server.class.getName() + " with prefix server");
+ }
+
+ @Test
+ void unregisteredConfigMapping() {
+ final SmallRyeConfig config = new SmallRyeConfigBuilder()
+ .withSources(config("host", "localhost", "port", "8080")).build();
+ assertThrows(NoSuchElementException.class, () -> config.getConfigMapping(Server.class),
+ "Could not find a mapping for " + Server.class.getName() + "with prefix");
+ }
+
+ @Test
+ void noPrefix() {
+ final SmallRyeConfig config = new SmallRyeConfigBuilder()
+ .withMapping(Server.class)
+ .withSources(config("host", "localhost", "port", "8080")).build();
+ final Server configProperties = config.getConfigMapping(Server.class);
+ assertEquals("localhost", configProperties.host());
+ assertEquals(8080, configProperties.port());
+ }
+
+ @Test
+ void configMappingBuilder() throws Exception {
+ final ConfigMappingProvider configMappingProvider = ConfigMappingProvider.builder().addRoot("server", Server.class)
+ .addIgnored("server.name").build();
+ final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources(
+ config("server.host", "localhost", "server.port", "8080", "server.name", "name")).build();
+
+ final Server server = configMappingProvider.mapConfiguration(config).getConfigMapping(Server.class, "server");
+ assertEquals("localhost", server.host());
+ assertEquals(8080, server.port());
+ }
+
+ @Test
+ void unknownConfigElement() {
+ assertThrows(IllegalStateException.class,
+ () -> new SmallRyeConfigBuilder().withMapping(Server.class, "server").build());
+ }
+
+ @Test
+ void ignorePropertiesInUnregisteredRoots() {
+ final SmallRyeConfig config = new SmallRyeConfigBuilder()
+ .withMapping(Server.class, "server")
+ .withSources(config("server.host", "localhost", "server.port", "8080", "client.name", "konoha"))
+ .build();
+ final Server configProperties = config.getConfigMapping(Server.class, "server");
+ assertEquals("localhost", configProperties.host());
+ assertEquals(8080, configProperties.port());
+ }
+
+ @Test
+ void ignoreSomeProperties() {
+ final SmallRyeConfig config = new SmallRyeConfigBuilder()
+ .withMapping(Server.class, "server")
+ .withMapping(Client.class, "client")
+ .withMappingIgnore("client.**")
+ .withSources(config("server.host", "localhost", "server.port", "8080", "client.host", "localhost",
+ "client.port", "8080", "client.name", "konoha"))
+ .build();
+
+ final Server server = config.getConfigMapping(Server.class, "server");
+ assertEquals("localhost", server.host());
+ assertEquals(8080, server.port());
+
+ final Client client = config.getConfigMapping(Client.class, "client");
+ assertEquals("localhost", client.host());
+ assertEquals(8080, client.port());
+ }
+
+ @Test
+ void ignoreProperties() {
+ final SmallRyeConfig config = new SmallRyeConfigBuilder()
+ .addDefaultSources()
+ .withMapping(Server.class, "server")
+ .withSources(config("server.host", "localhost", "server.port", "8080")).build();
+ final Server configProperties = config.getConfigMapping(Server.class, "server");
+ assertEquals("localhost", configProperties.host());
+ assertEquals(8080, configProperties.port());
+ }
+
+ @Test
+ void splitRoots() throws Exception {
+ final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources(
+ config("server.host", "localhost", "server.port", "8080", "server.name", "konoha"))
+ .build();
+
+ final ConfigMappingProvider configMappingProvider = ConfigMappingProvider.builder()
+ .addRoot("server", SplitRootServerHostAndPort.class)
+ .addRoot("server", SplitRootServerName.class)
+ .build();
+
+ final ConfigMappings result = configMappingProvider.mapConfiguration(config);
+ final SplitRootServerHostAndPort server = result.getConfigMapping(SplitRootServerHostAndPort.class, "server");
+ assertEquals("localhost", server.host());
+ assertEquals(8080, server.port());
+
+ final SplitRootServerName name = result.getConfigMapping(SplitRootServerName.class, "server");
+ assertEquals("konoha", name.name());
+ }
+
+ @Test
+ void splitRootsInConfig() {
+ final SmallRyeConfig config = new SmallRyeConfigBuilder()
+ .withSources(config("server.host", "localhost", "server.port", "8080", "server.name",
+ "konoha"))
+ .withMapping(SplitRootServerHostAndPort.class, "server")
+ .withMapping(SplitRootServerName.class, "server")
+ .build();
+ final SplitRootServerHostAndPort server = config.getConfigMapping(SplitRootServerHostAndPort.class, "server");
+ assertEquals("localhost", server.host());
+ assertEquals(8080, server.port());
+ }
+
+ @Test
+ void subGroups() {
+ final SmallRyeConfig config = new SmallRyeConfigBuilder()
+ .withSources(config("server.host", "localhost", "server.port", "8080", "server.name",
+ "konoha"))
+ .withMapping(ServerSub.class, "server")
+ .build();
+ final ServerSub server = config.getConfigMapping(ServerSub.class, "server");
+ assertEquals("localhost", server.subHostAndPort().host());
+ assertEquals(8080, server.subHostAndPort().port());
+ assertEquals("konoha", server.subName().name());
+ }
+
+ @Test
+ void types() {
+ final Map typesConfig = new HashMap() {
+ {
+ put("int", "9");
+ put("long", "9999999999");
+ put("float", "99.9");
+ put("double", "99.99");
+ put("char", "c");
+ put("boolean", "true");
+ }
+ };
+
+ final SmallRyeConfig config = new SmallRyeConfigBuilder()
+ .withSources(config(typesConfig))
+ .withMapping(SomeTypes.class)
+ .build();
+ final SomeTypes types = config.getConfigMapping(SomeTypes.class);
+
+ assertEquals(9, types.intPrimitive());
+ assertEquals(9, types.intWrapper());
+ assertEquals(9999999999L, types.longPrimitive());
+ assertEquals(9999999999L, types.longWrapper());
+ assertEquals(99.9f, types.floatPrimitive());
+ assertEquals(99.9f, types.floatWrapper());
+ assertEquals(99.99, types.doublePrimitive());
+ assertEquals(99.99, types.doubleWrapper());
+ assertEquals('c', types.charPrimitive());
+ assertEquals('c', types.charWrapper());
+ assertTrue(types.booleanPrimitive());
+ assertTrue(types.booleanWrapper());
+ }
+
+ @Test
+ void optionals() {
+ final Map typesConfig = new HashMap() {
+ {
+ put("server.host", "localhost");
+ put("server.port", "8080");
+ put("optional", "optional");
+ put("optional.int", "9");
+ }
+ };
+
+ final SmallRyeConfig config = new SmallRyeConfigBuilder()
+ .withSources(config(typesConfig))
+ .withMapping(Optionals.class)
+ .build();
+ final Optionals optionals = config.getConfigMapping(Optionals.class);
+
+ assertTrue(optionals.server().isPresent());
+ assertEquals("localhost", optionals.server().get().host());
+ assertEquals(8080, optionals.server().get().port());
+
+ assertTrue(optionals.optional().isPresent());
+ assertEquals("optional", optionals.optional().get());
+ assertTrue(optionals.optionalInt().isPresent());
+ assertEquals(9, optionals.optionalInt().getAsInt());
+ }
+
+ @Test
+ void collectionTypes() {
+ final Map typesConfig = new HashMap() {
+ {
+ put("strings", "foo,bar");
+ put("ints", "1,2,3");
+ }
+ };
+
+ final SmallRyeConfig config = new SmallRyeConfigBuilder()
+ .withSources(config(typesConfig))
+ .withMapping(CollectionTypes.class)
+ .build();
+ final CollectionTypes types = config.getConfigMapping(CollectionTypes.class);
+
+ assertEquals(Stream.of("foo", "bar").collect(toList()), types.listStrings());
+ assertEquals(Stream.of(1, 2, 3).collect(toList()), types.listInts());
+ }
+
+ @Test
+ void maps() {
+ final Map typesConfig = new HashMap() {
+ {
+ put("server.host", "localhost");
+ put("server.port", "8080");
+ put("another-server", "localhost");
+ }
+ };
+
+ final SmallRyeConfig config = new SmallRyeConfigBuilder()
+ .withSources(config(typesConfig))
+ .withMapping(Maps.class)
+ .build();
+ final Maps maps = config.getConfigMapping(Maps.class);
+
+ assertEquals("localhost", maps.server().get("server").host());
+ assertEquals("localhost", maps.anotherServer().get("another-server"));
+ }
+
+ @Test
+ void defaults() {
+ final SmallRyeConfig config = new SmallRyeConfigBuilder()
+ .withMapping(Defaults.class)
+ .build();
+ final Defaults defaults = config.getConfigMapping(Defaults.class);
+
+ assertEquals("foo", defaults.foo());
+ assertEquals("bar", defaults.bar());
+ assertEquals("foo", config.getRawValue("foo"));
+
+ final List propertyNames = stream(config.getPropertyNames().spliterator(), false).collect(toList());
+ assertFalse(propertyNames.contains("foo"));
+ }
+
+ @Test
+ void converters() {
+ final SmallRyeConfig config = new SmallRyeConfigBuilder()
+ .withSources(config("foo", "notbar"))
+ .withMapping(Converters.class)
+ .withConverter(String.class, 100, new FooBarConverter())
+ .build();
+ final Converters converters = config.getConfigMapping(Converters.class);
+
+ assertEquals("bar", converters.foo());
+ assertEquals("bar", config.getValue("foo", String.class));
+ }
+
+ @Test
+ void mix() {
+ final Map typesConfig = new HashMap() {
+ {
+ put("server.host", "localhost");
+ put("server.port", "8080");
+ put("server.name", "server");
+ put("client.host", "clienthost");
+ put("client.port", "80");
+ put("client.name", "client");
+ }
+ };
+
+ final SmallRyeConfig config = new SmallRyeConfigBuilder()
+ .withSources(config(typesConfig))
+ .withMapping(ComplexSample.class)
+ .build();
+
+ final ComplexSample sample = config.getConfigMapping(ComplexSample.class);
+ assertEquals("localhost", sample.server().subHostAndPort().host());
+ assertEquals(8080, sample.server().subHostAndPort().port());
+ assertTrue(sample.client().isPresent());
+ assertEquals("clienthost", sample.client().get().subHostAndPort().host());
+ assertEquals(80, sample.client().get().subHostAndPort().port());
+ }
+
+ @Test
+ void noDynamicValues() {
+ final SmallRyeConfig config = new SmallRyeConfigBuilder()
+ .withMapping(Server.class, "server")
+ .withSources(config("server.host", "localhost", "server.port", "8080"))
+ .withSources(new MapBackedConfigSource("test", new HashMap<>(), Integer.MAX_VALUE) {
+ private int counter = 1;
+
+ @Override
+ public String getValue(final String propertyName) {
+ return counter++ + "";
+ }
+ }).build();
+
+ final Server server = config.getConfigMapping(Server.class, "server");
+
+ assertNotEquals(config.getRawValue("server.port"), config.getRawValue("server.port"));
+ assertEquals(server.port(), server.port());
+ }
+
+ interface Server {
+ String host();
+
+ int port();
+ }
+
+ interface Client {
+ String host();
+
+ int port();
+ }
+
+ interface SplitRootServerHostAndPort {
+ String host();
+
+ int port();
+ }
+
+ interface SplitRootServerName {
+ String name();
+ }
+
+ interface ServerSub {
+ @WithParentName
+ ServerSubHostAndPort subHostAndPort();
+
+ @WithParentName
+ ServerSubName subName();
+ }
+
+ interface ServerSubHostAndPort {
+ String host();
+
+ int port();
+ }
+
+ interface ServerSubName {
+ String name();
+ }
+
+ public interface SomeTypes {
+ @WithName("int")
+ int intPrimitive();
+
+ @WithName("int")
+ Integer intWrapper();
+
+ @WithName("long")
+ long longPrimitive();
+
+ @WithName("long")
+ Long longWrapper();
+
+ @WithName("float")
+ float floatPrimitive();
+
+ @WithName("float")
+ Float floatWrapper();
+
+ @WithName("double")
+ double doublePrimitive();
+
+ @WithName("double")
+ Double doubleWrapper();
+
+ @WithName("char")
+ char charPrimitive();
+
+ @WithName("char")
+ Character charWrapper();
+
+ @WithName("boolean")
+ boolean booleanPrimitive();
+
+ @WithName("boolean")
+ Boolean booleanWrapper();
+ }
+
+ public interface Optionals {
+ Optional server();
+
+ Optional optional();
+
+ @WithName("optional.int")
+ OptionalInt optionalInt();
+ }
+
+ public interface CollectionTypes {
+ @WithName("strings")
+ List listStrings();
+
+ @WithName("ints")
+ List listInts();
+ }
+
+ public interface Maps {
+ @WithParentName
+ Map server();
+
+ Map anotherServer();
+ }
+
+ public interface Defaults {
+ @WithDefault("foo")
+ String foo();
+
+ @WithDefault("bar")
+ String bar();
+ }
+
+ public interface ComplexSample {
+ ServerSub server();
+
+ Optional client();
+ }
+
+ public interface Converters {
+ @WithConverter(FooBarConverter.class)
+ String foo();
+ }
+
+ public static class FooBarConverter implements Converter {
+ @Override
+ public String convert(final String value) {
+ return "bar";
+ }
+ }
+}
diff --git a/implementation/src/test/java/io/smallrye/config/KeyMapBackedConfigSourceTest.java b/implementation/src/test/java/io/smallrye/config/KeyMapBackedConfigSourceTest.java
new file mode 100644
index 000000000..6ce693a26
--- /dev/null
+++ b/implementation/src/test/java/io/smallrye/config/KeyMapBackedConfigSourceTest.java
@@ -0,0 +1,46 @@
+package io.smallrye.config;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.Map;
+
+import org.eclipse.microprofile.config.spi.ConfigSource;
+import org.junit.jupiter.api.Test;
+
+class KeyMapBackedConfigSourceTest {
+ @Test
+ void getProperties() {
+ KeyMap map = new KeyMap<>();
+ map.findOrAdd("root.foo").putRootValue("bar");
+ map.findOrAdd("root.foo.bar").putRootValue("baz");
+ map.findOrAdd("root.foo.bar.*").putRootValue("baz");
+ map.findOrAdd("root.foo.bar.*.baz").putRootValue("anything");
+
+ ConfigSource source = getSource(map);
+ Map properties = source.getProperties();
+ assertTrue(properties.isEmpty());
+ }
+
+ @Test
+ void getValue() {
+ KeyMap map = new KeyMap<>();
+ map.findOrAdd("root.foo").putRootValue("bar");
+ map.findOrAdd("root.foo.bar").putRootValue("baz");
+ map.findOrAdd("root.foo.bar.*").putRootValue("baz");
+ map.findOrAdd("root.foo.bar.*.baz").putRootValue("anything");
+
+ ConfigSource source = getSource(map);
+ assertEquals("bar", source.getValue("root.foo"));
+ assertEquals("baz", source.getValue("root.foo.bar"));
+ assertEquals("baz", source.getValue("root.foo.bar.x"));
+ assertEquals("baz", source.getValue("root.foo.bar.y"));
+ assertEquals("anything", source.getValue("root.foo.bar.x.baz"));
+ assertEquals("anything", source.getValue("root.foo.bar.y.baz"));
+ assertNull(source.getValue("root.bar"));
+ assertNull(source.getValue("root.foo.bar.y.baz.z"));
+ }
+
+ private ConfigSource getSource(final KeyMap properties) {
+ return new KeyMapBackedConfigSource("test", 0, properties);
+ }
+}
diff --git a/implementation/src/test/java/io/smallrye/config/KeyMapTest.java b/implementation/src/test/java/io/smallrye/config/KeyMapTest.java
new file mode 100644
index 000000000..0a93065c9
--- /dev/null
+++ b/implementation/src/test/java/io/smallrye/config/KeyMapTest.java
@@ -0,0 +1,120 @@
+package io.smallrye.config;
+
+import static java.util.stream.Collectors.toList;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.Test;
+
+public class KeyMapTest {
+ @Test
+ void find() {
+ KeyMap root = new KeyMap<>();
+ root.findOrAdd("root").findOrAdd("foo").putRootValue("foo");
+ root.findOrAdd("root").findOrAdd("bar").putRootValue("bar");
+
+ assertEquals("foo", root.findRootValue("root.foo"));
+ assertEquals("bar", root.findRootValue("root.bar"));
+ }
+
+ @Test
+ void findOrAddPath() {
+ KeyMap map = new KeyMap<>();
+ map.findOrAdd("root.foo").putRootValue("bar");
+ map.findOrAdd("root.foo.bar").putRootValue("baz");
+ map.findOrAdd("root.foo.bar.*").putRootValue("baz");
+ map.findOrAdd("root.foo.bar.*.baz").putRootValue("anything");
+
+ assertEquals("bar", map.findRootValue("root.foo"));
+ assertEquals("baz", map.findRootValue("root.foo.bar"));
+ assertEquals("baz", map.findRootValue("root.foo.bar.x"));
+ assertEquals("baz", map.findRootValue("root.foo.bar.y"));
+ assertEquals("anything", map.findRootValue("root.foo.bar.x.baz"));
+ assertEquals("anything", map.findRootValue("root.foo.bar.y.baz"));
+ assertNull(map.findRootValue("root.bar"));
+ assertNull(map.findRootValue("root.foo.bar.y.baz.z"));
+ }
+
+ @Test
+ void findOrAddVarArgs() {
+ KeyMap map = new KeyMap<>();
+ map.findOrAdd("root", "foo").putRootValue("bar");
+ map.findOrAdd("root", "foo", "bar").putRootValue("baz");
+ map.findOrAdd("root", "foo", "bar", "*").putRootValue("baz");
+ map.findOrAdd("root", "foo", "bar", "*", "baz").putRootValue("anything");
+
+ assertEquals("bar", map.findRootValue("root.foo"));
+ assertEquals("baz", map.findRootValue("root.foo.bar"));
+ assertEquals("baz", map.findRootValue("root.foo.bar.x"));
+ assertEquals("baz", map.findRootValue("root.foo.bar.y"));
+ assertEquals("anything", map.findRootValue("root.foo.bar.x.baz"));
+ assertEquals("anything", map.findRootValue("root.foo.bar.y.baz"));
+ assertNull(map.findRootValue("root.bar"));
+ assertNull(map.findRootValue("root.foo.bar.y.baz.z"));
+ }
+
+ @Test
+ void findOrAddIterator() {
+ KeyMap map = new KeyMap<>();
+ map.findOrAdd(Stream.of("root", "foo").collect(toList())).putRootValue("bar");
+ map.findOrAdd(Stream.of("root", "foo", "bar").collect(toList())).putRootValue("baz");
+ map.findOrAdd(Stream.of("root", "foo", "bar", "*").collect(toList())).putRootValue("baz");
+ map.findOrAdd(Stream.of("root", "foo", "bar", "*", "baz").collect(toList())).putRootValue("anything");
+
+ assertEquals("bar", map.findRootValue("root.foo"));
+ assertEquals("baz", map.findRootValue("root.foo.bar"));
+ assertEquals("baz", map.findRootValue("root.foo.bar.x"));
+ assertEquals("baz", map.findRootValue("root.foo.bar.y"));
+ assertEquals("anything", map.findRootValue("root.foo.bar.x.baz"));
+ assertEquals("anything", map.findRootValue("root.foo.bar.y.baz"));
+ assertNull(map.findRootValue("root.bar"));
+ assertNull(map.findRootValue("root.foo.bar.y.baz.z"));
+ }
+
+ @Test
+ void merge() {
+ KeyMap map = new KeyMap<>();
+ map.findOrAdd("root.foo").putRootValue("bar");
+ map.findOrAdd("root.foo.bar").putRootValue("baz");
+ Map flatMap = new HashMap<>();
+ flatMap.put("root.foo", "foo");
+ flatMap.put("root.foo.bar.*", "baz");
+ flatMap.put("root.foo.bar.*.baz", "anything");
+
+ flatMap.forEach((key, value) -> map.findOrAdd(key).putRootValue(value));
+
+ assertEquals("foo", map.findRootValue("root.foo"));
+ assertEquals("baz", map.findRootValue("root.foo.bar"));
+ assertEquals("baz", map.findRootValue("root.foo.bar.x"));
+ assertEquals("baz", map.findRootValue("root.foo.bar.y"));
+ assertEquals("anything", map.findRootValue("root.foo.bar.x.baz"));
+ assertEquals("anything", map.findRootValue("root.foo.bar.y.baz"));
+ assertNull(map.findRootValue("root.bar"));
+ assertNull(map.findRootValue("root.foo.bar.y.baz.z"));
+ }
+
+ @Test
+ void empty() {
+ KeyMap map = new KeyMap<>();
+ map.findOrAdd("", "foo").putRootValue("bar");
+
+ assertEquals("bar", map.findRootValue(".foo"));
+ }
+
+ @Test
+ void string() {
+ KeyMap map = new KeyMap<>();
+ map.findOrAdd("root.foo").putRootValue("bar");
+ map.findOrAdd("root.foo.bar").putRootValue("baz");
+ map.findOrAdd("root.foo.bar.*").putRootValue("baz");
+ map.findOrAdd("root.foo.bar.*.baz").putRootValue("anything");
+
+ assertEquals(
+ "KeyMap(no value) {root=>KeyMap(no value) {foo=>KeyMap(value=bar) {bar=>KeyMap(value=baz) {(any)=>KeyMap(value=baz) {baz=>KeyMap(value=anything) {}}}}}}",
+ map.toString());
+ }
+}
diff --git a/implementation/src/test/java/io/smallrye/config/KeyValuesConfigSource.java b/implementation/src/test/java/io/smallrye/config/KeyValuesConfigSource.java
index 546886f62..e498fba7f 100644
--- a/implementation/src/test/java/io/smallrye/config/KeyValuesConfigSource.java
+++ b/implementation/src/test/java/io/smallrye/config/KeyValuesConfigSource.java
@@ -45,6 +45,10 @@ public String getName() {
return "KeyValuesConfigSource";
}
+ public static ConfigSource config(Map properties) {
+ return new KeyValuesConfigSource(properties);
+ }
+
public static ConfigSource config(String... keyValues) {
if (keyValues.length % 2 != 0) {
throw new IllegalArgumentException("keyValues array must be a multiple of 2");
diff --git a/implementation/src/test/java/io/smallrye/config/inject/ConfigMappingInjectionTest.java b/implementation/src/test/java/io/smallrye/config/inject/ConfigMappingInjectionTest.java
new file mode 100644
index 000000000..c18cc6604
--- /dev/null
+++ b/implementation/src/test/java/io/smallrye/config/inject/ConfigMappingInjectionTest.java
@@ -0,0 +1,32 @@
+package io.smallrye.config.inject;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import javax.inject.Inject;
+
+import org.jboss.weld.junit5.WeldInitiator;
+import org.jboss.weld.junit5.WeldJunit5Extension;
+import org.jboss.weld.junit5.WeldSetup;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import io.smallrye.config.inject.InjectionTestConfigFactory.Server;
+
+@ExtendWith(WeldJunit5Extension.class)
+public class ConfigMappingInjectionTest extends InjectionTest {
+ @WeldSetup
+ public WeldInitiator weld = WeldInitiator.from(ConfigExtension.class, Server.class)
+ .inject(this)
+ .build();
+
+ @Inject
+ Server server;
+
+ @Test
+ void configMapping() {
+ assertNotNull(server);
+ assertEquals("localhost", server.host());
+ assertEquals(8080, server.port());
+ }
+}
diff --git a/implementation/src/test/java/io/smallrye/config/inject/InjectionTestConfigFactory.java b/implementation/src/test/java/io/smallrye/config/inject/InjectionTestConfigFactory.java
index dbac8b7b2..96b6c5d44 100644
--- a/implementation/src/test/java/io/smallrye/config/inject/InjectionTestConfigFactory.java
+++ b/implementation/src/test/java/io/smallrye/config/inject/InjectionTestConfigFactory.java
@@ -5,6 +5,7 @@
import org.eclipse.microprofile.config.spi.ConfigSource;
+import io.smallrye.config.ConfigMapping;
import io.smallrye.config.KeyValuesConfigSource;
import io.smallrye.config.SmallRyeConfig;
import io.smallrye.config.SmallRyeConfigFactory;
@@ -39,6 +40,17 @@ public String getName() {
.withSources(KeyValuesConfigSource.config("optional.int.value", "1", "optional.long.value", "2",
"optional.double.value", "3.3"))
.withSecretKeys("secret")
+ .withMapping(Server.class, "server")
+ .withDefaultValue("server.host", "localhost")
+ .withDefaultValue("server.port", "8080")
.build();
}
+
+ @ConfigMapping("server")
+ // TODO - radcortez - Add validation that interface has to be public.
+ public interface Server {
+ String host();
+
+ int port();
+ }
}
diff --git a/pom.xml b/pom.xml
index 3b22e0d48..799f585c2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -34,6 +34,7 @@
http://smallrye.io
+ 7.0
1.4
1.2.0
@@ -116,6 +117,12 @@