SE Config can be created from MP Config (#2060)

* MP to SE config now works including environment variables.
* Empty config now does not create an executor service.
* Using the same classloader when null is sent in MP config.
* Added env var test.
* Ensure the reference config uses its own configuration (intermitent failure fix)

Signed-off-by: Tomas Langer <tomas.langer@oracle.com>
Co-authored-by: Romain Grecourt <romain.grecourt@oracle.com>
This commit is contained in:
Tomas Langer
2020-06-19 16:07:20 +02:00
committed by GitHub
parent 3e19778657
commit af949542c8
10 changed files with 592 additions and 65 deletions

View File

@@ -61,4 +61,18 @@
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<environmentVariables>
<FOO_BAR>mapped-env-value</FOO_BAR>
</environmentVariables>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -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<Object> cl = (Class<Object>) clazz;
builder.addStringMapper(cl, converter::convert);
});
}
Map<String, String> 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<String> 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);
}
}

View File

@@ -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();
}

View File

@@ -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("(?<!\\\\),");
private static final Pattern ESCAPED_COMMA_PATTERN = Pattern.compile("\\,", Pattern.LITERAL);
private final Map<Key, SeConfig> 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<String> 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<Config> traverse(Predicate<Config> 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<Config> traverseSubNodes(Config config, Predicate<Config> 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> T convert(Class<T> 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 <T> ConfigValue<T> as(GenericType<T> genericType) {
if (genericType.isClass()) {
return (ConfigValue<T>) as(genericType.rawType());
}
throw new UnsupportedOperationException("MP Configuration does not support generic types.");
}
@Override
public <T> ConfigValue<T> as(Class<T> type) {
return delegate.getOptionalValue(stringKey, type)
.map(ConfigValues::simpleValue)
.orElseGet(ConfigValues::empty);
}
@Override
public <T> ConfigValue<T> as(Function<Config, T> mapper) {
if (type() == Type.MISSING) {
return ConfigValues.empty();
}
return ConfigValues.simpleValue(mapper.apply(this));
}
@Override
public <T> ConfigValue<List<T>> asList(Class<T> type) throws ConfigMappingException {
if (Config.class.equals(type)) {
return toNodeList();
}
return asList(stringKey, type)
.map(ConfigValues::simpleValue)
.orElseGet(ConfigValues::empty);
}
@Override
public <T> ConfigValue<List<T>> asList(Function<Config, T> mapper) throws ConfigMappingException {
return asNodeList()
.as(it -> it.stream()
.map(mapper)
.collect(Collectors.toList()));
}
@Override
public ConfigValue<List<Config>> asNodeList() throws ConfigMappingException {
return asList(Config.class);
}
@Override
public ConfigValue<Map<String, String>> asMap() throws MissingValueException {
Type nodeType = type();
if (nodeType == Type.MISSING || nodeType == Type.VALUE) {
return ConfigValues.empty();
}
Map<String, String> 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 <T> ConfigValue<List<T>> toNodeList() {
Type nodeType = type();
if (nodeType == Type.MISSING || nodeType == Type.VALUE) {
return ConfigValues.empty();
}
// this is an object or a list
List<T> result = new LinkedList<>();
Set<String> 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<String> 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 <T> Optional<List<T>> asList(String configKey,
Class<T> typeArg) {
// first try to see if we have a direct value
Optional<String> 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<T> 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 <T> List<T> toList(String configKey,
String stringValue,
Class<T> typeArg) {
if (stringValue.isEmpty()) {
return List.of();
}
// we have a comma separated list
List<T> 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> T convert(String key, String value, Class<T> 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;
}
}
}
}

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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<HelidonSourceWithPriority> 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<ConfigSource> sources = new LinkedList<>();
@@ -118,7 +115,6 @@ class BuilderImpl implements Config.Builder {
public Config.Builder sources(List<Supplier<? extends ConfigSource>> 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<ConfigSourceRuntimeImpl> mergePrioritized(ConfigContextImpl context) {
List<PrioritizedConfigSource> 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();
}

View File

@@ -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();

View File

@@ -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<? extends Message<?>, Void> getSubscriberBuilder(final Config config) {
io.helidon.config.Config helidonConfig = MpConfig.toHelidonConfig(config);
printConfig(helidonConfig);
printConfig(config);
return ReactiveStreams.<Message<CompletableFuture<Map<String, String>>>>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<String, String> toMap(Config config) {
Map<String, String> 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));
}

View File

@@ -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<String, String> 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<String, String> propMap = Map.of(
@@ -57,6 +92,27 @@ class AdHocConfigBuilderTest {
assertThat(c.getValue("key.serializer", String.class), is(AdHocConfigBuilderTest.class.getName()));
}
@Test
void customValueOverrideMp() {
Map<String, String> 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<String, String> propMap = Map.of(
@@ -76,6 +132,38 @@ class AdHocConfigBuilderTest {
assertThat(c.getValue(TEST_KEY, String.class), is(TEST_TOPIC_CUSTOM));
}
@Test
void putAllTestMp() {
Map<String, String> propMap = Map.of(
"mp.messaging.outcoming.test-channel." + TEST_KEY, TEST_TOPIC_CONFIG
);
Map<String, String> 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<String, String> propMap = Map.of(