diff --git a/config/config-mp/pom.xml b/config/config-mp/pom.xml index 98090d26d..ca01905a3 100644 --- a/config/config-mp/pom.xml +++ b/config/config-mp/pom.xml @@ -61,4 +61,18 @@ test + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + mapped-env-value + + + + + \ No newline at end of file diff --git a/config/config-mp/src/main/java/io/helidon/config/mp/MpConfig.java b/config/config-mp/src/main/java/io/helidon/config/mp/MpConfig.java index dd5ed279d..6a64e7189 100644 --- a/config/config-mp/src/main/java/io/helidon/config/mp/MpConfig.java +++ b/config/config-mp/src/main/java/io/helidon/config/mp/MpConfig.java @@ -16,11 +16,8 @@ package io.helidon.config.mp; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - import io.helidon.config.ConfigSources; +import io.helidon.config.OverrideSources; import org.eclipse.microprofile.config.Config; @@ -49,30 +46,18 @@ public final class MpConfig { return (io.helidon.config.Config) mpConfig; } - io.helidon.config.Config.Builder builder = io.helidon.config.Config.builder() + io.helidon.config.Config mapper = io.helidon.config.Config.builder() + .sources(ConfigSources.empty()) + .overrides(OverrideSources.empty()) .disableEnvironmentVariablesSource() .disableSystemPropertiesSource() + .disableParserServices() + .disableFilterServices() .disableCaching() - .disableParserServices(); - - if (mpConfig instanceof MpConfigImpl) { - ((MpConfigImpl) mpConfig).converters() - .forEach((clazz, converter) -> { - Class cl = (Class) clazz; - builder.addStringMapper(cl, converter::convert); - }); - } - - Map allConfig = new HashMap<>(); - mpConfig.getPropertyNames() - .forEach(it -> { - // covering the condition where a config key disappears between getting the property names and requesting - // the value - Optional optionalValue = mpConfig.getOptionalValue(it, String.class); - optionalValue.ifPresent(value -> allConfig.put(it, value)); - }); - - return builder.addSource(ConfigSources.create(allConfig)) + .disableValueResolving() + .changesExecutor(command -> {}) .build(); + + return new SeConfig(mapper, mpConfig); } } diff --git a/config/config-mp/src/main/java/io/helidon/config/mp/MpConfigProviderResolver.java b/config/config-mp/src/main/java/io/helidon/config/mp/MpConfigProviderResolver.java index 3eb4245eb..9fb516b82 100644 --- a/config/config-mp/src/main/java/io/helidon/config/mp/MpConfigProviderResolver.java +++ b/config/config-mp/src/main/java/io/helidon/config/mp/MpConfigProviderResolver.java @@ -54,9 +54,12 @@ public class MpConfigProviderResolver extends ConfigProviderResolver { } @Override - public Config getConfig(ClassLoader loader) { - if (null == loader) { - loader = ClassLoader.getSystemClassLoader(); + public Config getConfig(ClassLoader classLoader) { + ClassLoader loader; + if (classLoader == null) { + loader = Thread.currentThread().getContextClassLoader(); + } else { + loader = classLoader; } Lock lock = RW_LOCK.readLock(); try { @@ -102,10 +105,17 @@ public class MpConfigProviderResolver extends ConfigProviderResolver { @Override public void registerConfig(Config config, ClassLoader classLoader) { + ClassLoader usedClassloader; + if (null == classLoader) { + usedClassloader = Thread.currentThread().getContextClassLoader(); + } else { + usedClassloader = classLoader; + } + Lock lock = RW_LOCK.writeLock(); try { lock.lock(); - doRegisterConfig(config, classLoader); + doRegisterConfig(config, usedClassloader); } finally { lock.unlock(); } diff --git a/config/config-mp/src/main/java/io/helidon/config/mp/SeConfig.java b/config/config-mp/src/main/java/io/helidon/config/mp/SeConfig.java new file mode 100644 index 000000000..801fc8682 --- /dev/null +++ b/config/config-mp/src/main/java/io/helidon/config/mp/SeConfig.java @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.config.mp; + +import java.time.Instant; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import io.helidon.common.GenericType; +import io.helidon.config.Config; +import io.helidon.config.ConfigMappingException; +import io.helidon.config.ConfigValue; +import io.helidon.config.ConfigValues; +import io.helidon.config.MissingValueException; + +/** + * Implementation of SE config backed by MP config. + */ +class SeConfig implements Config { + private static final Pattern SPLIT_PATTERN = Pattern.compile("(? children = new ConcurrentHashMap<>(); + private final io.helidon.config.Config mapper; + private final Key prefix; + private final Key key; + private final Key fullKey; + private final org.eclipse.microprofile.config.Config delegate; + private final MpConfigImpl delegateImpl; + private final String stringKey; + private final String stringPrefix; + + SeConfig(io.helidon.config.Config mapper, + Key prefix, + Key key, + Key fullKey, + org.eclipse.microprofile.config.Config delegate, + MpConfigImpl delegateImpl) { + this.mapper = mapper; + this.prefix = prefix; + this.key = key; + this.fullKey = fullKey; + this.delegate = delegate; + this.stringKey = fullKey.toString(); + this.stringPrefix = prefix.toString(); + this.delegateImpl = delegateImpl; + } + + SeConfig(io.helidon.config.Config mapper, org.eclipse.microprofile.config.Config delegate) { + this.mapper = mapper; + this.prefix = Key.create(""); + this.key = prefix; + this.fullKey = prefix; + this.delegate = delegate; + this.stringKey = prefix.child(key).toString(); + this.stringPrefix = prefix.toString(); + + if (delegate instanceof MpConfigImpl) { + this.delegateImpl = (MpConfigImpl) delegate; + } else { + this.delegateImpl = null; + } + } + + @Override + public Instant timestamp() { + return Instant.now(); + } + + @Override + public Key key() { + return key; + } + + @Override + public Config get(Key key) { + return children.computeIfAbsent(key, it -> new SeConfig(mapper, prefix, key, fullKey.child(key), delegate, delegateImpl)); + } + + @Override + public Config detach() { + return new SeConfig(mapper, fullKey, Key.create(""), fullKey, delegate, delegateImpl); + } + + @Override + public Type type() { + // check if there are any sub-nodes that have prefix with our key + boolean isObject = false; + + Iterator it = delegate.getPropertyNames().iterator(); + if (stringKey.isEmpty()) { + if (!it.hasNext()) { + return hasValue() ? Type.VALUE : Type.MISSING; + } + return Type.OBJECT; + } + + while (it.hasNext()) { + String name = it.next(); + if (name.equals(stringKey)) { + continue; + } + if (name.startsWith(stringKey + ".")) { + isObject = true; + break; + } + } + if (isObject) { + return Type.OBJECT; + } + if (hasValue()) { + return Type.VALUE; + } + + return Type.MISSING; + } + + @Override + public boolean hasValue() { + return currentValue().isPresent(); + } + + @Override + public Stream traverse(Predicate predicate) { + return asNodeList() + .map(list -> list.stream() + .filter(predicate) + .map(node -> traverseSubNodes(node, predicate)) + .reduce(Stream.empty(), Stream::concat)) + .orElseThrow(MissingValueException.createSupplier(key())); + + } + + private Stream traverseSubNodes(Config config, Predicate predicate) { + if (config.type().isLeaf()) { + return Stream.of(config); + } else { + return config.asNodeList() + .map(list -> list.stream() + .filter(predicate) + .map(node -> traverseSubNodes(node, predicate)) + .reduce(Stream.of(config), Stream::concat)) + .orElseThrow(MissingValueException.createSupplier(key())); + } + } + + @Override + public T convert(Class type, String value) throws ConfigMappingException { + try { + return impl().findConverter(type) + .convert(value); + } catch (Exception e) { + try { + return mapper.convert(type, value); + } catch (ConfigMappingException ignored) { + throw e; + } + } + } + + @SuppressWarnings("unchecked") + @Override + public ConfigValue as(GenericType genericType) { + if (genericType.isClass()) { + return (ConfigValue) as(genericType.rawType()); + } + throw new UnsupportedOperationException("MP Configuration does not support generic types."); + } + + @Override + public ConfigValue as(Class type) { + return delegate.getOptionalValue(stringKey, type) + .map(ConfigValues::simpleValue) + .orElseGet(ConfigValues::empty); + } + + @Override + public ConfigValue as(Function mapper) { + if (type() == Type.MISSING) { + return ConfigValues.empty(); + } + + return ConfigValues.simpleValue(mapper.apply(this)); + } + + @Override + public ConfigValue> asList(Class type) throws ConfigMappingException { + if (Config.class.equals(type)) { + return toNodeList(); + } + return asList(stringKey, type) + .map(ConfigValues::simpleValue) + .orElseGet(ConfigValues::empty); + } + + @Override + public ConfigValue> asList(Function mapper) throws ConfigMappingException { + return asNodeList() + .as(it -> it.stream() + .map(mapper) + .collect(Collectors.toList())); + } + + @Override + public ConfigValue> asNodeList() throws ConfigMappingException { + return asList(Config.class); + } + + @Override + public ConfigValue> asMap() throws MissingValueException { + Type nodeType = type(); + if (nodeType == Type.MISSING || nodeType == Type.VALUE) { + return ConfigValues.empty(); + } + + Map children = new HashMap<>(); + + for (String propertyName : delegate.getPropertyNames()) { + if (stringKey.isEmpty()) { + children.put(propertyName, delegate.getValue(propertyName, String.class)); + } else { + if (propertyName.equals(stringKey)) { + continue; + } + if (propertyName.startsWith(stringKey + ".")) { + String noPrefix = propertyName.substring(stringPrefix.length() + 1); + + children.put(noPrefix, delegate.getValue(propertyName, String.class)); + } + } + } + + return ConfigValues.simpleValue(children); + } + + @Override + public String toString() { + return type() + " " + stringKey + " = " + currentValue().orElse(null); + } + + @SuppressWarnings("unchecked") + private ConfigValue> toNodeList() { + Type nodeType = type(); + if (nodeType == Type.MISSING || nodeType == Type.VALUE) { + return ConfigValues.empty(); + } + + // this is an object or a list + List result = new LinkedList<>(); + Set children = new HashSet<>(); + + for (String propertyName : delegate.getPropertyNames()) { + if (stringKey.isEmpty()) { + String noSuffix = propertyName; + int dot = noSuffix.indexOf('.'); + if (dot > 0) { + noSuffix = noSuffix.substring(0, dot); + } + children.add(noSuffix); + } else { + if (propertyName.equals(stringKey)) { + continue; + } + if (propertyName.startsWith(stringKey + ".")) { + String noSuffix = propertyName.substring(stringKey.length() + 1); + int dot = noSuffix.indexOf('.'); + if (dot > 0) { + noSuffix = noSuffix.substring(0, dot); + } + children.add(noSuffix); + } + } + } + + for (String child : children) { + result.add((T) get(child)); + } + + return ConfigValues.simpleValue(result); + } + + private Optional currentValue() { + return delegate.getOptionalValue(stringKey, String.class); + } + + private MpConfigImpl impl() { + if (null == delegateImpl) { + throw new IllegalStateException("Cannot convert to arbitrary types when the MP Config is not a Helidon " + + "implementation"); + } + + return delegateImpl; + } + + private Optional> asList(String configKey, + Class typeArg) { + // first try to see if we have a direct value + Optional optionalValue = delegate.getOptionalValue(configKey, String.class); + if (optionalValue.isPresent()) { + return Optional.of(toList(configKey, optionalValue.get(), typeArg)); + } + + /* + we also support indexed value + e.g. for key "my.list" you can have both: + my.list=12,13,14 + or (not and): + my.list.0=12 + my.list.1=13 + */ + + String indexedConfigKey = configKey + ".0"; + optionalValue = delegate.getOptionalValue(indexedConfigKey, String.class); + if (optionalValue.isPresent()) { + List result = new LinkedList<>(); + + // first element is already in + result.add(convert(indexedConfigKey, optionalValue.get(), typeArg)); + + // hardcoded limit to lists of 1000 elements + for (int i = 1; i < 1000; i++) { + indexedConfigKey = configKey + "." + i; + optionalValue = delegate.getOptionalValue(indexedConfigKey, String.class); + if (optionalValue.isPresent()) { + result.add(convert(indexedConfigKey, optionalValue.get(), typeArg)); + } else { + // finish the iteration on first missing index + break; + } + } + return Optional.of(result); + } else { + return Optional.empty(); + } + } + + private List toList(String configKey, + String stringValue, + Class typeArg) { + if (stringValue.isEmpty()) { + return List.of(); + } + // we have a comma separated list + List result = new LinkedList<>(); + for (String value : toArray(stringValue)) { + result.add(convert(configKey, value, typeArg)); + } + return result; + } + + static String[] toArray(String stringValue) { + String[] values = SPLIT_PATTERN.split(stringValue, -1); + + for (int i = 0; i < values.length; i++) { + String value = values[i]; + values[i] = ESCAPED_COMMA_PATTERN.matcher(value).replaceAll(Matcher.quoteReplacement(",")); + } + return values; + } + + @SuppressWarnings("unchecked") + private T convert(String key, String value, Class type) { + if (null == value) { + return null; + } + if (String.class.equals(type)) { + return (T) value; + } + + try { + return impl().getConverter(type) + .orElseThrow(() -> new IllegalArgumentException("Did not find converter for type " + + type.getName() + + ", for key " + + key)) + .convert(value); + } catch (Exception e) { + try { + return mapper.convert(type, value); + } catch (ConfigMappingException ignored) { + throw e; + } + } + } +} diff --git a/config/config-mp/src/test/java/io/helidon/config/mp/MpConfigReferenceTest.java b/config/config-mp/src/test/java/io/helidon/config/mp/MpConfigReferenceTest.java index bcc5b4428..57a5979fa 100644 --- a/config/config-mp/src/test/java/io/helidon/config/mp/MpConfigReferenceTest.java +++ b/config/config-mp/src/test/java/io/helidon/config/mp/MpConfigReferenceTest.java @@ -17,7 +17,7 @@ package io.helidon.config.mp; import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -35,7 +35,10 @@ public class MpConfigReferenceTest { static void initClass() { System.setProperty("value2", VALUE_2); - config = ConfigProvider.getConfig(); + config = ConfigProviderResolver.instance() + .getBuilder() + .addDefaultSources() + .build(); } @Test diff --git a/config/config-mp/src/test/java/io/helidon/config/mp/MpConfigTest.java b/config/config-mp/src/test/java/io/helidon/config/mp/MpConfigTest.java index 0df8a6742..bb173362a 100644 --- a/config/config-mp/src/test/java/io/helidon/config/mp/MpConfigTest.java +++ b/config/config-mp/src/test/java/io/helidon/config/mp/MpConfigTest.java @@ -30,6 +30,7 @@ import io.helidon.config.spi.ConfigMapper; import io.helidon.config.spi.ConfigMapperProvider; import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.eclipse.microprofile.config.spi.ConfigSource; import org.junit.jupiter.api.BeforeAll; @@ -274,5 +275,31 @@ public class MpConfigTest { counter.set(0); } } + + @Test + void testEnvVar() { + ConfigProviderResolver instance = ConfigProviderResolver.instance(); + ClassLoader myCl = Thread.currentThread().getContextClassLoader(); + Config current = ConfigProvider.getConfig(myCl); + + try { + instance.registerConfig(instance.getBuilder() + .withSources(MpConfigSources.environmentVariables()) + .build(), + myCl); + Config myConfig = instance.getConfig(myCl); + // this must not throw an exception - path should be on any environment + // and the MP env var processing should make it available + String fooBar = myConfig.getValue("foo.bar", String.class); + assertThat(fooBar, is("mapped-env-value")); + + io.helidon.config.Config helidonConfig = (io.helidon.config.Config) myConfig; + // should work if we use it as SE as well + fooBar = helidonConfig.get("foo.bar").asString().get(); + assertThat(fooBar, is("mapped-env-value")); + } finally { + instance.registerConfig(current, myCl); + } + } } diff --git a/config/config/src/main/java/io/helidon/config/BuilderImpl.java b/config/config/src/main/java/io/helidon/config/BuilderImpl.java index ad7007235..cb50591d9 100644 --- a/config/config/src/main/java/io/helidon/config/BuilderImpl.java +++ b/config/config/src/main/java/io/helidon/config/BuilderImpl.java @@ -30,7 +30,6 @@ import java.util.concurrent.Executors; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; -import java.util.stream.Collectors; import io.helidon.common.GenericType; import io.helidon.common.Prioritized; @@ -53,8 +52,6 @@ class BuilderImpl implements Config.Builder { /* * Config sources */ - // sources to be sorted by priority - private final List prioritizedSources = new ArrayList<>(); // sources "pre-sorted" - all user defined sources without priority will be ordered // as added, as well as config sources from meta configuration private final List sources = new LinkedList<>(); @@ -118,7 +115,6 @@ class BuilderImpl implements Config.Builder { public Config.Builder sources(List> sourceSuppliers) { // replace current config sources with the ones provided sources.clear(); - prioritizedSources.clear(); sourceSuppliers.stream() .map(Supplier::get) @@ -414,7 +410,7 @@ class BuilderImpl implements Config.Builder { envVarAliasGeneratorEnabled = true; } - boolean nothingConfigured = sources.isEmpty() && prioritizedSources.isEmpty(); + boolean nothingConfigured = sources.isEmpty(); if (nothingConfigured) { // use meta configuration to load all sources @@ -429,29 +425,12 @@ class BuilderImpl implements Config.Builder { sources.stream() .map(context::sourceRuntimeBase) .forEach(targetSources::add); - - // prioritized sources are next - targetSources.addAll(mergePrioritized(context)); } // targetSources now contain runtimes correctly ordered for each config source return new ConfigSourcesRuntime(targetSources, mergingStrategy); } - private List mergePrioritized(ConfigContextImpl context) { - List allPrioritized = new ArrayList<>(); - prioritizedSources.stream() - .map(it -> new PrioritizedConfigSource(it, context)) - .forEach(allPrioritized::add); - - Priorities.sort(allPrioritized); - - return allPrioritized - .stream() - .map(it -> it.runtime(context)) - .collect(Collectors.toList()); - } - @SuppressWarnings("ParameterNumber") ProviderImpl createProvider(ConfigMapperManager configMapperManager, ConfigSourcesRuntime targetConfigSource, @@ -605,6 +584,7 @@ class BuilderImpl implements Config.Builder { .disableSystemPropertiesSource() .disableParserServices() .disableFilterServices() + .changesExecutor(command -> {}) .build(); } diff --git a/messaging/messaging/src/main/java/io/helidon/messaging/ConnectorConfigHelper.java b/messaging/messaging/src/main/java/io/helidon/messaging/ConnectorConfigHelper.java index 0e70a37d1..eaf3cf9ab 100644 --- a/messaging/messaging/src/main/java/io/helidon/messaging/ConnectorConfigHelper.java +++ b/messaging/messaging/src/main/java/io/helidon/messaging/ConnectorConfigHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,7 +65,7 @@ final class ConnectorConfigHelper { Config incomingChannelConfig = rootConfig.get("mp.messaging.incoming"); Config outgoingChannelConfig = rootConfig.get("mp.messaging.outgoing"); - Config channelsConfig = (Config) ConnectorConfigBuilder + Config channelsConfig = ConnectorConfigBuilder .create(incomingChannelConfig) .config(outgoingChannelConfig) .build(); diff --git a/messaging/messaging/src/test/java/io/helidon/messaging/TestConfigurableConnector.java b/messaging/messaging/src/test/java/io/helidon/messaging/TestConfigurableConnector.java index 737a470e8..d295fc4d5 100644 --- a/messaging/messaging/src/test/java/io/helidon/messaging/TestConfigurableConnector.java +++ b/messaging/messaging/src/test/java/io/helidon/messaging/TestConfigurableConnector.java @@ -17,9 +17,9 @@ package io.helidon.messaging; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; import io.helidon.config.mp.MpConfig; @@ -56,17 +56,25 @@ public class TestConfigurableConnector implements IncomingConnectorFactory, Outg @Override public SubscriberBuilder, Void> getSubscriberBuilder(final Config config) { - io.helidon.config.Config helidonConfig = MpConfig.toHelidonConfig(config); - printConfig(helidonConfig); + printConfig(config); return ReactiveStreams.>>>builder() .map(Message::getPayload) - .forEach(f -> f.complete(helidonConfig - .traverse() - .map(c -> Map.entry(c.key().name(), c.asString().get())) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))) - ); + .forEach(f -> f.complete(toMap(config))); } + private static Map toMap(Config config) { + Map result = new HashMap<>(); + + config.getPropertyNames() + .forEach(it -> { + config.getOptionalValue(it, String.class).ifPresent(value -> result.put(it, value)); + }); + + return result; + } + private static void printConfig(Config c) { + toMap(c).forEach((key, value) -> System.out.println(key + ": " + value)); + } private static void printConfig(io.helidon.config.Config c) { c.asMap().orElse(Map.of()).forEach((key, value) -> System.out.println(key + ": " + value)); } diff --git a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/AdHocConfigBuilderTest.java b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/AdHocConfigBuilderTest.java index 6088ea091..8df9dca43 100644 --- a/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/AdHocConfigBuilderTest.java +++ b/microprofile/messaging/src/test/java/io/helidon/microprofile/messaging/AdHocConfigBuilderTest.java @@ -21,12 +21,15 @@ import java.util.Map; import io.helidon.config.Config; import io.helidon.config.ConfigSources; +import io.helidon.config.mp.MpConfigSources; + +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import org.junit.jupiter.api.Test; - class AdHocConfigBuilderTest { private static final String TEST_TOPIC_CONFIG = "TEST_TOPIC_CONFIG"; @@ -38,6 +41,38 @@ class AdHocConfigBuilderTest { private static final String ADDITION_ATTR_2_VALUE = "addition-attr2-value"; private static final String TEST_CONNECTOR = "test-connector"; + private static ConfigProviderResolver resolver; + private static ClassLoader cl; + + @BeforeAll + static void initClass() { + resolver = ConfigProviderResolver.instance(); + cl = Thread.currentThread().getContextClassLoader(); + } + + @Test + void currentContextMp() { + // MP tests ensure that the MP Config -> SE Config casting works as expected + // when obtained from config resolver + Map propMap = Map.of( + "mp.messaging.outcoming.test-channel.key.serializer", AdHocConfigBuilderTest.class.getName() + ); + + resolver.registerConfig(resolver.getBuilder() + .withSources(MpConfigSources.create(propMap)) + .build(), cl); + + Config config = (Config) resolver.getConfig(cl); + + org.eclipse.microprofile.config.Config c = AdHocConfigBuilder + .from(config.get("mp.messaging.outcoming.test-channel")) + .put(TEST_KEY, TEST_TOPIC_CUSTOM) + .build(); + + assertThat(c.getValue(TEST_KEY, String.class), is(TEST_TOPIC_CUSTOM)); + assertThat(c.getValue("key.serializer", String.class), is(AdHocConfigBuilderTest.class.getName())); + } + @Test void currentContext() { Map propMap = Map.of( @@ -57,6 +92,27 @@ class AdHocConfigBuilderTest { assertThat(c.getValue("key.serializer", String.class), is(AdHocConfigBuilderTest.class.getName())); } + @Test + void customValueOverrideMp() { + Map propMap = Map.of( + "mp.messaging.outcoming.test-channel." + TEST_KEY, TEST_TOPIC_CONFIG, + "mp.messaging.outcoming.test-channel.key.serializer", AdHocConfigBuilderTest.class.getName() + ); + + resolver.registerConfig(resolver.getBuilder() + .withSources(MpConfigSources.create(propMap)) + .build(), cl); + + Config config = (Config) resolver.getConfig(cl); + + org.eclipse.microprofile.config.Config c = AdHocConfigBuilder + .from(config.get("mp.messaging.outcoming.test-channel")) + .put(TEST_KEY, TEST_TOPIC_CUSTOM) + .build(); + + assertThat(c.getValue(TEST_KEY, String.class), is(TEST_TOPIC_CUSTOM)); + } + @Test void customValueOverride() { Map propMap = Map.of( @@ -76,6 +132,38 @@ class AdHocConfigBuilderTest { assertThat(c.getValue(TEST_KEY, String.class), is(TEST_TOPIC_CUSTOM)); } + @Test + void putAllTestMp() { + Map propMap = Map.of( + "mp.messaging.outcoming.test-channel." + TEST_KEY, TEST_TOPIC_CONFIG + ); + + Map propMap2 = Map.of( + "mp.messaging.connector." + TEST_CONNECTOR + "." + ADDITION_ATTR_1, ADDITION_ATTR_1_VALUE, + "mp.messaging.connector." + TEST_CONNECTOR + "." + ADDITION_ATTR_2, ADDITION_ATTR_2_VALUE + ); + + resolver.registerConfig(resolver.getBuilder() + .withSources(MpConfigSources.create(propMap)) + .build(), cl); + + Config config = (Config) resolver.getConfig(cl); + + resolver.registerConfig(resolver.getBuilder() + .withSources(MpConfigSources.create(propMap2)) + .build(), cl); + + Config config2 = (Config) resolver.getConfig(cl); + + org.eclipse.microprofile.config.Config c = AdHocConfigBuilder + .from(config.get("mp.messaging.outcoming.test-channel")) + .putAll(config2.get("mp.messaging.connector." + TEST_CONNECTOR)) + .build(); + + assertThat(c.getValue(ADDITION_ATTR_1, String.class), is(ADDITION_ATTR_1_VALUE)); + assertThat(c.getValue(ADDITION_ATTR_2, String.class), is(ADDITION_ATTR_2_VALUE)); + } + @Test void putAllTest() { Map propMap = Map.of(