Support for SE mappers in MP. (#2091)

* Support for SE mappers in MP.
* Removed mandatory use of object mapping from security providers.
* Use direct APIs to retrieve booleans.
* Fixes to object mapping support in MP.

Signed-off-by: Tomas Langer <tomas.langer@oracle.com>
This commit is contained in:
Tomas Langer
2020-06-26 10:09:06 +02:00
committed by GitHub
parent 929c4c6874
commit 12cfae97d6
15 changed files with 383 additions and 55 deletions

View File

@@ -16,10 +16,13 @@
package io.helidon.config.mp;
import java.util.Iterator;
import io.helidon.config.ConfigSources;
import io.helidon.config.OverrideSources;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.spi.ConfigSource;
/**
* Utilities for Helidon MicroProfile Config implementation.
@@ -40,12 +43,21 @@ public final class MpConfig {
* @param mpConfig MP Config instance
* @return a new Helidon config using only the mpConfig as its config source
*/
@SuppressWarnings("unchecked")
public static io.helidon.config.Config toHelidonConfig(Config mpConfig) {
if (mpConfig instanceof io.helidon.config.Config) {
return (io.helidon.config.Config) mpConfig;
}
// If the mpConfig is based on an SE config (such as when we use meta configuration)
// we must reuse that se config instance
Iterator<ConfigSource> configSources = mpConfig.getConfigSources().iterator();
ConfigSource first = configSources.hasNext() ? configSources.next() : null;
if (!configSources.hasNext() && first instanceof MpHelidonConfigSource) {
// we only have Helidon SE config as a source - let's just use it
return ((MpHelidonConfigSource) first).unwrap();
}
// we use Helidon SE config to handle object mapping (and possible other mappers on classpath)
io.helidon.config.Config mapper = io.helidon.config.Config.builder()
.sources(ConfigSources.empty())
.overrides(OverrideSources.empty())
@@ -55,7 +67,8 @@ public final class MpConfig {
.disableFilterServices()
.disableCaching()
.disableValueResolving()
.changesExecutor(command -> {})
.changesExecutor(command -> {
})
.build();
return new SeConfig(mapper, mpConfig);

View File

@@ -33,6 +33,7 @@ import java.util.stream.Stream;
import io.helidon.common.GenericType;
import io.helidon.config.ConfigValue;
import io.helidon.config.MetaConfig;
import io.helidon.config.spi.ConfigMapper;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
@@ -297,6 +298,11 @@ public class MpConfigProviderResolver extends ConfigProviderResolver {
return getCurrent().asMap();
}
@Override
public ConfigMapper mapper() {
return getCurrent().mapper();
}
@Override
public <T> T getValue(String propertyName, Class<T> propertyType) {
return delegate.get().getValue(propertyName, propertyType);

View File

@@ -18,6 +18,8 @@ package io.helidon.config.mp;
import java.util.Map;
import io.helidon.config.Config;
import org.eclipse.microprofile.config.spi.ConfigSource;
final class MpHelidonConfigSource implements ConfigSource {
@@ -48,4 +50,8 @@ final class MpHelidonConfigSource implements ConfigSource {
public String getName() {
return "Helidon Config";
}
Config unwrap() {
return helidonConfig;
}
}

View File

@@ -39,6 +39,7 @@ import io.helidon.config.ConfigMappingException;
import io.helidon.config.ConfigValue;
import io.helidon.config.ConfigValues;
import io.helidon.config.MissingValueException;
import io.helidon.config.spi.ConfigMapper;
/**
* Implementation of SE config backed by MP config.
@@ -73,7 +74,8 @@ class SeConfig implements Config {
this.delegateImpl = delegateImpl;
}
SeConfig(io.helidon.config.Config mapper, org.eclipse.microprofile.config.Config delegate) {
SeConfig(Config mapper,
org.eclipse.microprofile.config.Config delegate) {
this.mapper = mapper;
this.prefix = Key.create("");
this.key = prefix;
@@ -101,7 +103,13 @@ class SeConfig implements Config {
@Override
public Config get(Key key) {
return children.computeIfAbsent(key, it -> new SeConfig(mapper, prefix, key, fullKey.child(key), delegate, delegateImpl));
return children.computeIfAbsent(key,
it -> new SeConfig(mapper,
prefix,
key,
fullKey.child(key),
delegate,
delegateImpl));
}
@Override
@@ -158,19 +166,6 @@ class SeConfig implements Config {
}
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 {
@@ -191,14 +186,22 @@ class SeConfig implements Config {
if (genericType.isClass()) {
return (ConfigValue<T>) as(genericType.rawType());
}
throw new UnsupportedOperationException("MP Configuration does not support generic types.");
return new SeConfigValue<>(key, () -> mapper.mapper().map(SeConfig.this, genericType));
}
@Override
public <T> ConfigValue<T> as(Class<T> type) {
return delegate.getOptionalValue(stringKey, type)
.map(ConfigValues::simpleValue)
.orElseGet(ConfigValues::empty);
if (type() == Type.MISSING) {
return ConfigValues.empty();
}
if (impl().getConverter(type).isPresent()) {
return delegate.getOptionalValue(stringKey, type)
.map(ConfigValues::simpleValue)
.orElseGet(ConfigValues::empty);
} else {
return new SeConfigValue<>(key, () -> mapper.mapper().map(SeConfig.this, type));
}
}
@Override
@@ -212,16 +215,20 @@ class SeConfig implements Config {
@Override
public <T> ConfigValue<List<T>> asList(Class<T> type) throws ConfigMappingException {
if (type() == Type.MISSING) {
return ConfigValues.empty();
}
if (Config.class.equals(type)) {
return toNodeList();
}
return asList(stringKey, type)
.map(ConfigValues::simpleValue)
.orElseGet(ConfigValues::empty);
return asList(stringKey, type);
}
@Override
public <T> ConfigValue<List<T>> asList(Function<Config, T> mapper) throws ConfigMappingException {
if (type() == Type.MISSING) {
return ConfigValues.empty();
}
return asNodeList()
.as(it -> it.stream()
.map(mapper)
@@ -230,6 +237,9 @@ class SeConfig implements Config {
@Override
public ConfigValue<List<Config>> asNodeList() throws ConfigMappingException {
if (type() == Type.MISSING) {
return ConfigValues.empty();
}
return asList(Config.class);
}
@@ -265,6 +275,27 @@ class SeConfig implements Config {
return type() + " " + stringKey + " = " + currentValue().orElse(null);
}
@Override
public ConfigMapper mapper() {
return mapper.mapper();
}
private Stream<Config> traverseSubNodes(Config config, Predicate<Config> predicate) {
if (type() == Type.MISSING) {
return Stream.of();
}
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()));
}
}
@SuppressWarnings("unchecked")
private <T> ConfigValue<List<T>> toNodeList() {
Type nodeType = type();
@@ -319,12 +350,15 @@ class SeConfig implements Config {
return delegateImpl;
}
private <T> Optional<List<T>> asList(String configKey,
Class<T> typeArg) {
private <T> ConfigValue<List<T>> asList(String configKey, Class<T> typeArg) {
return new SeConfigValue<>(key(), () -> toList(configKey, typeArg));
}
private <T> List<T> toList(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));
return valueToList(configKey, optionalValue.get(), typeArg);
}
/*
@@ -344,8 +378,9 @@ class SeConfig implements Config {
// 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++) {
// start from index 1, as 0 is already aded
int i = 1;
while (true) {
indexedConfigKey = configKey + "." + i;
optionalValue = delegate.getOptionalValue(indexedConfigKey, String.class);
if (optionalValue.isPresent()) {
@@ -354,16 +389,33 @@ class SeConfig implements Config {
// finish the iteration on first missing index
break;
}
i++;
}
return Optional.of(result);
return result;
} else {
return Optional.empty();
// and further still we may have a list of objects
if (get("0").type() == Type.MISSING) {
throw MissingValueException.create(key);
}
// there are objects here, let's do that
List<T> result = new LinkedList<>();
int i = 0;
while (true) {
Config config = get(String.valueOf(i));
if (config.type() == Type.MISSING) {
break;
}
result.add(config.as(typeArg).get());
i++;
}
return result;
}
}
private <T> List<T> toList(String configKey,
String stringValue,
Class<T> typeArg) {
private <T> List<T> valueToList(String configKey,
String stringValue,
Class<T> typeArg) {
if (stringValue.isEmpty()) {
return List.of();
}

View File

@@ -0,0 +1,121 @@
/*
* 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.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import io.helidon.config.Config;
import io.helidon.config.ConfigMappingException;
import io.helidon.config.ConfigValue;
import io.helidon.config.MissingValueException;
class SeConfigValue<T> implements ConfigValue<T> {
private final Config.Key key;
private final Supplier<T> valueSupplier;
SeConfigValue(Config.Key key, Supplier<T> valueSupplier) {
this.key = key;
this.valueSupplier = valueSupplier;
}
@Override
public Config.Key key() {
return key;
}
@Override
public Optional<T> asOptional() throws ConfigMappingException {
try {
return Optional.of(valueSupplier.get());
} catch (MissingValueException e) {
return Optional.empty();
}
}
@Override
public <N> ConfigValue<N> as(Function<T, N> mapper) {
return new SeConfigValue<>(key, () -> mapper.apply(valueSupplier.get()));
}
@Override
public Supplier<T> supplier() {
return valueSupplier;
}
@Override
public Supplier<T> supplier(T defaultValue) {
return () -> {
try {
return valueSupplier.get();
} catch (MissingValueException e) {
return defaultValue;
}
};
}
@Override
public Supplier<Optional<T>> optionalSupplier() {
return this::asOptional;
}
@Override
public int hashCode() {
try {
return Objects.hash(key, asOptional());
} catch (ConfigMappingException e) {
return Objects.hash(key);
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
SeConfigValue<?> that = (SeConfigValue<?>) o;
if (!key.equals(that.key)) {
return false;
}
Optional<?> myOptional;
try {
myOptional = asOptional();
} catch (ConfigMappingException e) {
try {
that.asOptional();
// same key, one failed -> different value
return false;
} catch (ConfigMappingException configMappingException) {
// same key, both failed -> same value
return true;
}
}
try {
Optional<?> thatOptional = that.asOptional();
return myOptional.equals(thatOptional);
} catch (ConfigMappingException e) {
// same key, one failed -> different value
return false;
}
}
}

View File

@@ -23,6 +23,8 @@ import java.util.Optional;
import java.util.function.Consumer;
import java.util.logging.Logger;
import io.helidon.config.spi.ConfigMapper;
/**
* Abstract common implementation of {@link Config} extended by appropriate Config node types:
* {@link ConfigListImpl}, {@link ConfigMissingImpl}, {@link ConfigObjectImpl}, {@link ConfigLeafImpl}.
@@ -153,6 +155,11 @@ abstract class AbstractConfigImpl implements Config {
});
}
@Override
public ConfigMapper mapper() {
return mapperManager;
}
/**
* Implementation of node specific context.
*/

View File

@@ -30,6 +30,7 @@ import java.util.stream.Stream;
import io.helidon.common.GenericType;
import io.helidon.config.spi.ConfigFilter;
import io.helidon.config.spi.ConfigMapper;
import io.helidon.config.spi.ConfigMapperProvider;
import io.helidon.config.spi.ConfigParser;
import io.helidon.config.spi.ConfigSource;
@@ -650,6 +651,13 @@ public interface Config {
*/
<T> T convert(Class<T> type, String value) throws ConfigMappingException;
/**
* The mapper used by this config instance.
*
* @return configuration mapper
*/
ConfigMapper mapper();
//
// accessors
//

View File

@@ -320,6 +320,11 @@ class ConfigMapperManager implements ConfigMapper {
public ConfigValue<Config> asNode() {
return as(Config.class);
}
@Override
public ConfigMapper mapper() {
return mapperManager;
}
}
// this class exists for debugging purposes - it is clearly seen that this mapper was not found