
io.smallrye.config.SmallRyeConfig Maven / Gradle / Ivy
/*
* Copyright 2017 Red Hat, Inc.
*
* 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.smallrye.config;
import static io.smallrye.config.ConfigSourceInterceptor.EMPTY;
import static io.smallrye.config.SmallRyeConfigSourceInterceptor.configSourceInterceptor;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.UnaryOperator;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.spi.ConfigSource;
import org.eclipse.microprofile.config.spi.Converter;
import io.smallrye.common.annotation.Experimental;
import io.smallrye.config.SmallRyeConfigBuilder.InterceptorWithPriority;
/**
* @author Jeff Mesnil (c) 2017 Red Hat inc.
*/
public class SmallRyeConfig implements Config, Serializable {
private static final long serialVersionUID = 8138651532357898263L;
private final AtomicReference configSources;
private final Map> converters;
private final Map>> optionalConverters = new ConcurrentHashMap<>();
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
protected SmallRyeConfig(List configSources, Map> converters) {
this.configSources = new AtomicReference<>(
new ConfigSources(configSources, buildInterceptors(new SmallRyeConfigBuilder())));
this.converters = new ConcurrentHashMap<>(Converters.ALL_CONVERTERS);
this.converters.putAll(converters);
this.mappings = new ConfigMappings();
}
private List buildConfigSources(final SmallRyeConfigBuilder builder) {
final List sourcesToBuild = new ArrayList<>(builder.getSources());
if (builder.isAddDiscoveredSources()) {
sourcesToBuild.addAll(builder.discoverSources());
}
if (builder.isAddDefaultSources()) {
sourcesToBuild.addAll(builder.getDefaultSources());
}
sourcesToBuild.add(new DefaultValuesConfigSource(builder.getDefaultValues()));
// wrap all
final Function sourceWrappersToBuild = builder.getSourceWrappers();
final ListIterator it = sourcesToBuild.listIterator();
while (it.hasNext()) {
it.set(sourceWrappersToBuild.apply(it.next()));
}
return sourcesToBuild;
}
private List buildInterceptors(final SmallRyeConfigBuilder builder) {
final List interceptors = new ArrayList<>(builder.getInterceptors());
if (builder.isAddDiscoveredInterceptors()) {
interceptors.addAll(builder.discoverInterceptors());
}
if (builder.isAddDefaultInterceptors()) {
interceptors.addAll(builder.getDefaultInterceptors());
}
return interceptors;
}
private Map> buildConverters(final SmallRyeConfigBuilder builder) {
final Map convertersToBuild = new HashMap<>(builder.getConverters());
if (builder.isAddDiscoveredConverters()) {
for (Converter> converter : builder.discoverConverters()) {
Type type = Converters.getConverterType(converter.getClass());
if (type == null) {
throw ConfigMessages.msg.unableToAddConverter(converter);
}
SmallRyeConfigBuilder.addConverter(type, converter, convertersToBuild);
}
}
final ConcurrentHashMap> converters = new ConcurrentHashMap<>(Converters.ALL_CONVERTERS);
for (Map.Entry entry : convertersToBuild.entrySet()) {
converters.put(entry.getKey(), entry.getValue().getConverter());
}
converters.put(ConfigValue.class, ConfigValueConverter.CONFIG_VALUE_CONVERTER);
return converters;
}
// no @Override
public > C getValues(String name, Class itemClass, IntFunction collectionFactory) {
return getValues(name, requireConverter(itemClass), collectionFactory);
}
public > C getValues(String name, Converter converter, IntFunction collectionFactory) {
return getValue(name, Converters.newCollectionConverter(converter, collectionFactory));
}
@Override
public T getValue(String name, Class aClass) {
return getValue(name, requireConverter(aClass));
}
@SuppressWarnings("unchecked")
public T getValue(String name, Converter converter) {
ConfigValue configValue = getConfigValue(name);
if (ConfigValueConverter.CONFIG_VALUE_CONVERTER.equals(converter)) {
return (T) configValue;
}
if (converter instanceof Converters.OptionalConverter>) {
if (ConfigValueConverter.CONFIG_VALUE_CONVERTER.equals(
((Converters.OptionalConverter>) converter).getDelegate())) {
return (T) Optional.of(configValue);
}
}
String value = configValue.getValue();
final T converted;
if (value != null) {
converted = converter.convert(value);
} else {
try {
converted = converter.convert("");
} catch (IllegalArgumentException ignored) {
throw ConfigMessages.msg.propertyNotFound(name);
}
}
if (converted == null) {
throw ConfigMessages.msg.propertyNotFound(name);
}
return converted;
}
/**
* Determine whether the raw value of a configuration property is exactly equal to the expected given
* value.
*
* @param name the property name (must not be {@code null})
* @param expected the expected value (may be {@code null})
* @return {@code true} if the values are equal, {@code false} otherwise
*/
public boolean rawValueEquals(String name, String expected) {
return Objects.equals(expected, getRawValue(name));
}
@Experimental("Extension to the original ConfigSource to allow retrieval of additional metadata on config lookup")
public ConfigValue getConfigValue(String name) {
final ConfigValue configValue = configSources.get().getInterceptorChain().proceed(name);
return configValue != null ? configValue : ConfigValue.builder().withName(name).build();
}
/**
* Get the raw value of a configuration property.
*
* @param name the property name (must not be {@code null})
* @return the raw value, or {@code null} if no property value was discovered for the given property name
*/
public String getRawValue(String name) {
final ConfigValue configValue = getConfigValue(name);
return configValue != null && configValue.getValue() != null ? configValue.getValue() : null;
}
@Override
public Optional getOptionalValue(String name, Class aClass) {
return getValue(name, getOptionalConverter(aClass));
}
public Optional getOptionalValue(String name, Converter converter) {
return getValue(name, Converters.newOptionalConverter(converter));
}
public > Optional getOptionalValues(String name, Class itemClass,
IntFunction collectionFactory) {
return getOptionalValues(name, requireConverter(itemClass), collectionFactory);
}
public > Optional getOptionalValues(String name, Converter converter,
IntFunction collectionFactory) {
return getOptionalValue(name, Converters.newCollectionConverter(converter, collectionFactory));
}
public ConfigMappings getConfigMappings() {
return mappings;
}
@Experimental("ConfigMapping API to group configuration properties")
public T getConfigMapping(Class type) {
return mappings.getConfigMapping(type);
}
@Experimental("ConfigMapping API to group configuration properties")
public T getConfigMapping(Class type, String prefix) {
return mappings.getConfigMapping(type, prefix);
}
@Override
public Iterable getPropertyNames() {
final HashSet names = new HashSet<>();
final Iterator namesIterator = configSources.get().getInterceptorChain().iterateNames();
while (namesIterator.hasNext()) {
names.add(namesIterator.next());
}
return names;
}
@Override
public Iterable getConfigSources() {
return configSources.get().getSources();
}
/**
* Add a configuration source to the configuration object. The list of configuration sources is re-sorted
* to insert the new source into the correct position. Configuration source wrappers configured with
* {@link SmallRyeConfigBuilder#withWrapper(UnaryOperator)} will not be applied.
*
* @param configSource the new config source (must not be {@code null})
*/
@Deprecated
public void addConfigSource(ConfigSource configSource) {
configSources.updateAndGet(configSources -> new ConfigSources(configSources, configSource));
}
public T convert(String value, Class asType) {
return value != null ? requireConverter(asType).convert(value) : null;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Converter> getOptionalConverter(Class asType) {
return optionalConverters.computeIfAbsent(asType,
clazz -> Converters.newOptionalConverter(requireConverter((Class) clazz)));
}
@Deprecated // binary-compatibility bridge method for Quarkus
public Converter getConverter$$bridge(Class asType) {
return requireConverter(asType);
}
// @Override // in MP Config 2.0+
public Optional> getConverter(Class asType) {
return Optional.ofNullable(getConverterOrNull(asType));
}
public Converter requireConverter(final Class asType) {
final Converter conv = getConverterOrNull(asType);
if (conv == null) {
throw ConfigMessages.msg.noRegisteredConverter(asType);
}
return conv;
}
@SuppressWarnings("unchecked")
Converter getConverterOrNull(Class asType) {
final Converter> exactConverter = converters.get(asType);
if (exactConverter != null) {
return (Converter) exactConverter;
}
if (asType.isPrimitive()) {
return (Converter) getConverterOrNull(Converters.wrapPrimitiveType(asType));
}
if (asType.isArray()) {
final Converter> conv = getConverterOrNull(asType.getComponentType());
return conv == null ? null : Converters.newArrayConverter(conv, asType);
}
return (Converter) converters.computeIfAbsent(asType, clazz -> ImplicitConverters.getConverter((Class>) clazz));
}
@Override
public T unwrap(final Class type) {
if (Config.class.isAssignableFrom(type)) {
return type.cast(this);
}
throw ConfigMessages.msg.getTypeNotSupportedForUnwrapping(type);
}
@Experimental("To retrive active profiles")
public List getProfiles() {
return configSources.get().getProfiles();
}
private static class ConfigSources implements Serializable {
private static final long serialVersionUID = 3483018375584151712L;
private final List sources;
private final List interceptors;
private final ConfigSourceInterceptorContext interceptorChain;
/**
* Builds a representation of Config Sources, Interceptors and the Interceptor chain to be used in Config. Note
* that this constructor must be used when the Config object is being initialized, because interceptors also
* require initialization.
*
* Instances of the Interceptors are then kept in ConfigSources if the interceptor chain requires a reorder (in
* the case a new ConfigSource is addded to Config).
*
* @param sources the Config Sources to be part of Config.
* @param interceptors the Interceptors to be part of Config.
*/
ConfigSources(final List sources, final List interceptors) {
final List sortInterceptors = new ArrayList<>();
// Add all sources except for ConfigurableConfigSource types. These are initialized later
// Sources are converted to the interceptor API
sortInterceptors.addAll(mapSources(sources));
// Add all interceptors
sortInterceptors.addAll(mapInterceptors(interceptors));
sortInterceptors.sort(Comparator.comparingInt(ConfigSourceInterceptorWithPriority::getPriority));
// Create the initial chain and init each element with the current context
SmallRyeConfigSourceInterceptorContext current = new SmallRyeConfigSourceInterceptorContext(EMPTY, null);
for (ConfigSourceInterceptorWithPriority configSourceInterceptor : sortInterceptors) {
current = new SmallRyeConfigSourceInterceptorContext(configSourceInterceptor.getInterceptor(current), current);
}
// Init all late sources. Late sources are converted to the interceptor API and sorted again
sortInterceptors.addAll(mapLateSources(current, sources, getProfiles(sortInterceptors)));
sortInterceptors.sort(Comparator.comparingInt(ConfigSourceInterceptorWithPriority::getPriority));
// Rebuild the chain with the late sources and collect new instances of the interceptors
// The new instance will ensure that we get rid of references to factories and other stuff and keep only
// the resolved final source or interceptor to use.
final List initInterceptors = new ArrayList<>();
current = new SmallRyeConfigSourceInterceptorContext(EMPTY, null);
for (ConfigSourceInterceptorWithPriority configSourceInterceptor : sortInterceptors) {
ConfigSourceInterceptorWithPriority initInterceptor = configSourceInterceptor.initialized(current);
current = new SmallRyeConfigSourceInterceptorContext(initInterceptor.getInterceptor(), current);
initInterceptors.add(initInterceptor);
}
this.interceptorChain = current;
this.sources = Collections.unmodifiableList(getSources(initInterceptors));
this.interceptors = Collections.unmodifiableList(initInterceptors);
}
/**
* Builds a representation of Config Sources, Interceptors and the Interceptor chain to be used in Config. Note
* that this constructor is used in an already existent Config instance to reconstruct the chain if additional
* Config Sources are added to the Config.
*
* @param sources the Config Sources to be part of Config.
* @param configSource the new ConfigSource to add into the interceptor the chain.
*/
ConfigSources(final ConfigSources sources, final ConfigSource configSource) {
final int oldSize = sources.getInterceptors().size();
final List newInterceptors = Arrays
.asList(sources.getInterceptors().toArray(new ConfigSourceInterceptorWithPriority[oldSize + 1]));
newInterceptors.set(oldSize, new ConfigSourceInterceptorWithPriority(configSource));
newInterceptors.sort(Comparator.comparingInt(ConfigSourceInterceptorWithPriority::getPriority));
SmallRyeConfigSourceInterceptorContext current = new SmallRyeConfigSourceInterceptorContext(EMPTY, null);
for (ConfigSourceInterceptorWithPriority configSourceInterceptor : newInterceptors) {
current = new SmallRyeConfigSourceInterceptorContext(configSourceInterceptor.getInterceptor(current), current);
}
this.sources = Collections.unmodifiableList(getSources(newInterceptors));
this.interceptors = Collections.unmodifiableList(newInterceptors);
this.interceptorChain = current;
}
private static List mapSources(final List sources) {
final List sourcesWithPriority = new ArrayList<>();
for (ConfigSource source : sources) {
if (!(source instanceof ConfigurableConfigSource)) {
sourcesWithPriority.add(new ConfigSourceInterceptorWithPriority(source));
}
}
return sourcesWithPriority;
}
private static List mapInterceptors(
final List interceptors) {
final List sourcesWithPriority = new ArrayList<>();
for (InterceptorWithPriority interceptor : interceptors) {
sourcesWithPriority.add(new ConfigSourceInterceptorWithPriority(interceptor));
}
return sourcesWithPriority;
}
private static List getProfiles(final List interceptors) {
for (final ConfigSourceInterceptorWithPriority interceptor : interceptors) {
if (interceptor.getInterceptor() instanceof ProfileConfigSourceInterceptor) {
return Arrays.asList(((ProfileConfigSourceInterceptor) interceptor.getInterceptor()).getProfiles());
}
}
return Collections.emptyList();
}
private static List mapLateSources(
final SmallRyeConfigSourceInterceptorContext initChain,
final List sources,
final List profiles) {
final List lateSources = new ArrayList<>();
for (ConfigSource source : sources) {
if (source instanceof ConfigurableConfigSource) {
lateSources.add((ConfigurableConfigSource) source);
}
}
lateSources.sort(Comparator.comparingInt(ConfigurableConfigSource::getOrdinal));
final List sourcesWithPriority = new ArrayList<>();
for (ConfigurableConfigSource configurableSource : lateSources) {
final List configSources = configurableSource.getConfigSources(new ConfigSourceContext() {
@Override
public ConfigValue getValue(final String name) {
ConfigValue value = initChain.proceed(name);
return value != null ? value : ConfigValue.builder().withName(name).build();
}
@Override
public List getProfiles() {
return profiles;
}
@Override
public Iterator iterateNames() {
return initChain.iterateNames();
}
});
for (ConfigSource configSource : configSources) {
sourcesWithPriority.add(new ConfigSourceInterceptorWithPriority(configSource));
}
}
return sourcesWithPriority;
}
private static List getSources(final List interceptors) {
final List sources = new ArrayList<>();
for (ConfigSourceInterceptorWithPriority interceptor : interceptors) {
if (interceptor.getInterceptor() instanceof SmallRyeConfigSourceInterceptor) {
sources.add(((SmallRyeConfigSourceInterceptor) interceptor.getInterceptor()).getSource());
}
}
Collections.reverse(sources);
return sources;
}
List getSources() {
return sources;
}
List getInterceptors() {
return interceptors;
}
ConfigSourceInterceptorContext getInterceptorChain() {
return interceptorChain;
}
List getProfiles() {
for (final ConfigSourceInterceptorWithPriority interceptor : getInterceptors()) {
if (interceptor.getInterceptor() instanceof ProfileConfigSourceInterceptor) {
return Arrays.asList(((ProfileConfigSourceInterceptor) interceptor.getInterceptor()).getProfiles());
}
}
return Collections.emptyList();
}
}
static class ConfigSourceInterceptorWithPriority implements Comparable, Serializable {
private static final long serialVersionUID = 1637460029437579033L;
private final Function init;
private final int priority;
private final String name;
private ConfigSourceInterceptor interceptor;
ConfigSourceInterceptorWithPriority(final InterceptorWithPriority interceptor) {
this.init = interceptor::getInterceptor;
this.priority = interceptor.getPriority();
this.name = "undefined";
}
ConfigSourceInterceptorWithPriority(final ConfigSource configSource) {
this.init = context -> configSourceInterceptor(configSource);
this.priority = configSource.getOrdinal();
this.name = configSource.getName();
}
private ConfigSourceInterceptorWithPriority(final ConfigSourceInterceptor interceptor, final int priority,
final String name) {
this.init = null;
this.priority = priority;
this.name = name;
this.interceptor = interceptor;
}
ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorContext context) {
if (this.interceptor == null) {
this.interceptor = init.apply(context);
}
return this.interceptor;
}
ConfigSourceInterceptor getInterceptor() {
if (this.interceptor == null) {
throw new IllegalStateException();
}
return this.interceptor;
}
ConfigSourceInterceptorWithPriority initialized(final ConfigSourceInterceptorContext context) {
return new ConfigSourceInterceptorWithPriority(this.getInterceptor(context), this.priority, this.name);
}
int getPriority() {
return this.priority;
}
@Override
public int compareTo(final ConfigSourceInterceptorWithPriority other) {
int res = Integer.compare(this.priority, other.priority);
return res != 0 ? res : this.name.compareTo(other.name);
}
}
private Object writeReplace() throws ObjectStreamException {
return RegisteredConfig.instance;
}
/**
* Serialization placeholder which deserializes to the current registered config
*/
private static class RegisteredConfig implements Serializable {
private static final long serialVersionUID = 1L;
private static RegisteredConfig instance = new RegisteredConfig();
private Object readResolve() throws ObjectStreamException {
return ConfigProvider.getConfig();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy