Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API in the builder to register a mapping instance #1310

Merged
merged 1 commit into from
Feb 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.smallrye.config;

import static io.smallrye.config.ConfigMappingLoader.getConfigMappingClass;
import static io.smallrye.config.ConfigValidationException.Problem;
import static io.smallrye.config.ProfileConfigSourceInterceptor.activeName;
import static io.smallrye.config.common.utils.StringUtil.unindexed;
Expand All @@ -26,6 +27,8 @@
import org.eclipse.microprofile.config.spi.Converter;

import io.smallrye.config.ConfigMapping.NamingStrategy;
import io.smallrye.config.ConfigMappings.ConfigClass;
import io.smallrye.config.SmallRyeConfigBuilder.MappingBuilder;
import io.smallrye.config._private.ConfigMessages;
import io.smallrye.config.common.utils.StringUtil;

Expand All @@ -46,18 +49,26 @@ public final class ConfigMappingContext {

public ConfigMappingContext(
final SmallRyeConfig config,
final Map<Class<?>, Set<String>> mappings) {
final MappingBuilder mappingBuilder) {

this.config = config;

matchPropertiesWithEnv(mappings);
for (Map.Entry<Class<?>, Set<String>> mapping : mappings.entrySet()) {
Map<String, Object> mappingObjects = new HashMap<>();
for (String prefix : mapping.getValue()) {
applyPrefix(prefix);
mappingObjects.put(prefix, constructMapping(mapping.getKey(), prefix));
}
this.mappings.put(mapping.getKey(), mappingObjects);
matchPropertiesWithEnv(mappingBuilder);

for (Map.Entry<ConfigClass, Object> entry : mappingBuilder.getMappingsInstances().entrySet()) {
Class<?> type = getConfigMappingClass(entry.getKey().getType());
String prefix = entry.getKey().getPrefix();
Object instance = entry.getValue();
this.mappings.computeIfAbsent(type, k -> new HashMap<>(4)).put(prefix, instance);
this.usedProperties.addAll(entry.getKey().getDefaults().keySet());
}

for (ConfigClass configClass : mappingBuilder.getMappings()) {
Class<?> type = getConfigMappingClass(configClass.getType());
String prefix = configClass.getPrefix();
applyPrefix(configClass.getPrefix());
Object instance = constructMapping(type, prefix);
this.mappings.computeIfAbsent(type, k -> new HashMap<>(4)).put(prefix, instance);
}
}

Expand Down Expand Up @@ -90,11 +101,6 @@ public <T> T constructGroup(Class<T> interfaceType) {
return mappingObject;
}

@SuppressWarnings("unused")
public <T> ObjectCreator<T> constructObject(String path) {
return new ObjectCreator<>(path);
}

@SuppressWarnings("unchecked")
public <T> Converter<T> getConverterInstance(Class<? extends Converter<? extends T>> converterType) {
return (Converter<T>) converterInstances.computeIfAbsent(converterType, t -> {
Expand Down Expand Up @@ -217,12 +223,12 @@ void reportUnknown(final Set<String> ignoredPaths) {

// TODO - We shouldn't be mutating the EnvSource.
// We should do the calculation when creating the EnvSource, but right now mappings and sources are not well integrated.
private void matchPropertiesWithEnv(final Map<Class<?>, Set<String>> mappings) {
private void matchPropertiesWithEnv(final MappingBuilder mappings) {
// TODO - Should we match with instances?
Map<String, List<Class<?>>> prefixes = new HashMap<>();
for (Map.Entry<Class<?>, Set<String>> entry : mappings.entrySet()) {
for (String prefix : entry.getValue()) {
prefixes.computeIfAbsent(prefix, k -> new ArrayList<>()).add(entry.getKey());
}
for (ConfigClass configClass : mappings.getMappings()) {
Class<?> type = getConfigMappingClass(configClass.getType());
prefixes.computeIfAbsent(configClass.getPrefix(), k -> new ArrayList<>()).add(type);
}

StringBuilder sb = new StringBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,15 @@ private Map<Type, Converter<?>> buildConverters(final SmallRyeConfigBuilder buil
Map<Class<?>, Map<String, Object>> buildMappings(final SmallRyeConfigBuilder builder)
throws ConfigValidationException {
SmallRyeConfigBuilder.MappingBuilder mappingsBuilder = builder.getMappingsBuilder();
if (mappingsBuilder.getMappings().isEmpty()) {
if (mappingsBuilder.isEmpty()) {
return Collections.emptyMap();
}

// Perform the config mapping
ConfigMappingContext context = SecretKeys.doUnlocked(new Supplier<ConfigMappingContext>() {
@Override
public ConfigMappingContext get() {
return new ConfigMappingContext(SmallRyeConfig.this, mappingsBuilder.getMappings());
return new ConfigMappingContext(SmallRyeConfig.this, mappingsBuilder);
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package io.smallrye.config;

import static io.smallrye.config.ConfigMappingLoader.getConfigMappingClass;
import static io.smallrye.config.ConfigSourceInterceptorFactory.DEFAULT_PRIORITY;
import static io.smallrye.config.Converters.STRING_CONVERTER;
import static io.smallrye.config.Converters.newCollectionConverter;
Expand All @@ -37,6 +36,7 @@
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
Expand Down Expand Up @@ -773,41 +773,61 @@ public int compare(SmallRyeConfigBuilderCustomizer o1, SmallRyeConfigBuilderCust
}

public final class MappingBuilder {
private final Map<Class<?>, Set<String>> mappings = new HashMap<>();
private final Set<ConfigClass> mappings = new HashSet<>();
private final Map<ConfigClass, Object> mappingsInstances = new IdentityHashMap<>();
private final Set<String> ignoredPaths = new HashSet<>();

public void mapping(ConfigClass configClass) {
Assert.checkNotNullParam("configClass", configClass);

mappings.computeIfAbsent(getConfigMappingClass(configClass.getType()), k -> new HashSet<>(4))
.add(configClass.getPrefix());
mappings.add(configClass);
addDefaultsAndIgnores(configClass);
}

configClass.getDefaults().forEach(new BiConsumer<String, String>() {
@Override
public void accept(final String propertyName, final String value) {
// Do not override defaults set by the builder directly, which have priority over mapping defaults
defaultValues.putIfAbsent(propertyName, value);
}
});
public void mappingInstance(ConfigClass configClass, Object instance) {
Assert.checkNotNullParam("configClass", configClass);
Assert.checkNotNullParam("instance", instance);

// It is an MP ConfigProperties, so ignore unmapped properties
if (ConfigMappingLoader.ConfigMappingClass.getConfigurationClass(configClass.getType()) != null) {
ignoredPaths.add(configClass.getPrefix().isEmpty() ? "*" : configClass.getPrefix() + ".**");
}
// TODO - Validate that instance is an implementation of type?
mappingsInstances.put(configClass, instance);
addDefaultsAndIgnores(configClass);
}

public void ignoredPath(String ignoredPath) {
Assert.checkNotNullParam("ignoredPath", ignoredPath);
ignoredPaths.add(ignoredPath);
}

public Map<Class<?>, Set<String>> getMappings() {
public Set<ConfigClass> getMappings() {
return mappings;
}

public Map<ConfigClass, Object> getMappingsInstances() {
return mappingsInstances;
}

public Set<String> getIgnoredPaths() {
return ignoredPaths;
}

boolean isEmpty() {
return mappings.isEmpty() && mappingsInstances.isEmpty();
}

private void addDefaultsAndIgnores(ConfigClass configClass) {
configClass.getDefaults().forEach(new BiConsumer<String, String>() {
@Override
public void accept(final String propertyName, final String value) {
// Do not override defaults set by the builder directly, which have priority over mapping defaults
defaultValues.putIfAbsent(propertyName, value);
}
});

// It is an MP ConfigProperties, so ignore unmapped properties
if (ConfigMappingLoader.ConfigMappingClass.getConfigurationClass(configClass.getType()) != null) {
ignoredPaths.add(configClass.getPrefix().isEmpty() ? "*" : configClass.getPrefix() + ".**");
}
}
}

static class ConverterWithPriority {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,4 +323,31 @@ void properties() {
assertTrue(properties.containsKey("mapped.value"));
assertTrue(properties.containsKey("mapped.collection[*].value"));
}

@Test
void registerInstances() {
SmallRyeConfig config = new SmallRyeConfigBuilder()
.withSources(config("mapping.instance.value", "value"))
.withMapping(MappingInstance.class)
.build();

MappingInstance mapping = config.getConfigMapping(MappingInstance.class);

SmallRyeConfigBuilder builder = new SmallRyeConfigBuilder();
builder.getMappingsBuilder().mappingInstance(configClass(MappingInstance.class, "mapping.instance"), mapping);
SmallRyeConfig mappingConfig = builder.build();

MappingInstance mappingInstance = mappingConfig.getConfigMapping(MappingInstance.class);
assertEquals("value", mappingInstance.value());
assertEquals("default", mappingInstance.defaultValue());
assertEquals("default", mappingConfig.getRawValue("mapping.instance.default-value"));
}

@ConfigMapping(prefix = "mapping.instance")
interface MappingInstance {
String value();

@WithDefault("default")
String defaultValue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -46,7 +45,7 @@ void objectCreator() {
"optional-list-group[0].value", "value"))
.build();

ConfigMappingContext context = new ConfigMappingContext(config, new HashMap<>());
ConfigMappingContext context = new ConfigMappingContext(config, new SmallRyeConfigBuilder().getMappingsBuilder());
ObjectCreator mapping = new ObjectCreatorImpl(context);

assertEquals(2, mapping.unnamed().size());
Expand Down Expand Up @@ -271,7 +270,7 @@ void optionalGroup() {
"optional.value", "value"))
.build();

ConfigMappingContext context = new ConfigMappingContext(config, new HashMap<>());
ConfigMappingContext context = new ConfigMappingContext(config, new SmallRyeConfigBuilder().getMappingsBuilder());
OptionalGroup mapping = new OptionalGroupImpl(context);

assertTrue(mapping.optional().isPresent());
Expand Down Expand Up @@ -333,7 +332,7 @@ void unnamedKeys() {
"unnamed.key.value", "value"))
.build();

ConfigMappingContext context = new ConfigMappingContext(config, new HashMap<>());
ConfigMappingContext context = new ConfigMappingContext(config, new SmallRyeConfigBuilder().getMappingsBuilder());
context.applyPrefix("unnamed");

UnnamedKeys mapping = new UnnamedKeysImpl(context);
Expand Down Expand Up @@ -391,7 +390,7 @@ void mapDefaults() {
"map.defaults-list.one[0].value", "value"))
.build();

ConfigMappingContext context = new ConfigMappingContext(config, new HashMap<>());
ConfigMappingContext context = new ConfigMappingContext(config, new SmallRyeConfigBuilder().getMappingsBuilder());
context.applyPrefix("map");
MapDefaults mapping = new MapDefaultsImpl(context);

Expand Down Expand Up @@ -502,7 +501,7 @@ void namingStrategy() {
"naming.nested_value.value", "value"))
.build();

ConfigMappingContext context = new ConfigMappingContext(config, new HashMap<>());
ConfigMappingContext context = new ConfigMappingContext(config, new SmallRyeConfigBuilder().getMappingsBuilder());
context.applyPrefix("naming");
Naming naming = new NamingImpl(context);

Expand Down