Skip to content

Commit

Permalink
Check all used properties when required to create a Map entry (#1113)
Browse files Browse the repository at this point in the history
  • Loading branch information
radcortez authored Feb 7, 2024
1 parent a96c00a commit cd8d6ec
Show file tree
Hide file tree
Showing 2 changed files with 278 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,8 @@ public void accept(Function<String, Object> get) {
});
}

Set<String> mapKeys = new HashSet<>();
Map<String, String> mapKeys = new HashMap<>();
Map<String, List<String>> mapProperties = new HashMap<>();
for (String propertyName : config.getPropertyNames()) {
if (propertyName.length() > path.length() + 1 // only consider properties bigger than the map path
&& (path.isEmpty() || propertyName.charAt(path.length()) == '.') // next char must be a dot (for the key)
Expand All @@ -337,29 +338,49 @@ public void accept(Function<String, Object> get) {
mapProperty.next();

String mapKey = unindexed(mapProperty.getPreviousSegment());
// Nested property names use the same key for the same element, so track keys and skip if we already handled it
if (mapKeys.contains(mapKey)) {
continue;
}

mapKeys.add(mapKey);
String mapNext = unindexed(propertyName.substring(0, mapProperty.getPosition()));
mapKeys.computeIfAbsent(mapKey, new Function<String, String>() {
@Override
public String apply(final String s) {
return unindexed(propertyName.substring(0, mapProperty.getPosition()));
}
});

nestedCreators.add(new Consumer<>() {
mapProperties.computeIfAbsent(mapKey, new Function<String, List<String>>() {
@Override
public void accept(Function<String, Object> get) {
// The properties may have been used ih the unnamed key, which cause clashes, so we skip them
if (unnamedKey != null && usedProperties.contains(propertyName)) {
public List<String> apply(final String s) {
return new ArrayList<>();
}
});
mapProperties.get(mapKey).add(propertyName);
}
}

for (Map.Entry<String, String> mapKey : mapKeys.entrySet()) {
nestedCreators.add(new Consumer<>() {
@Override
public void accept(Function<String, Object> get) {
// The properties may have been used ih the unnamed key, which cause clashes, so we skip them
if (unnamedKey != null) {
boolean allUsed = true;
for (String mapProperty : mapProperties.get(mapKey.getKey())) {
if (!usedProperties.contains(mapProperty)) {
allUsed = false;
break;
}
}
if (allUsed) {
return;
}
}

V value = (V) get.apply(mapNext);
if (value != null) {
map.put(keyConverter.convert(mapKey), value);
}
// This is the full path plus the map key
V value = (V) get.apply(mapKey.getValue());
if (value != null) {
map.put(keyConverter.convert(mapKey.getKey()), value);
}
});
}
}
});

}

return map;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
package io.smallrye.config;

import static io.smallrye.config.KeyValuesConfigSource.config;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;

import org.junit.jupiter.api.Test;

public class ConfigMappingFullTest {
@Test
void ambiguousUnnamedKeysDefaults() {
SmallRyeConfig config = new SmallRyeConfigBuilder()
.withSources(config(
"datasource.postgresql.jdbc.url", "value",
"datasource.postgresql.password", "value"))
.withMapping(DataSourcesJdbcRuntimeConfig.class)
.withMapping(DataSourcesJdbcBuildTimeConfig.class)
.withMapping(DataSourcesRuntimeConfig.class)
.withMapping(DataSourcesBuildTimeConfig.class)
.build();

assertTrue(
config.getConfigMapping(DataSourcesRuntimeConfig.class).dataSources().get("postgresql").password().isPresent());

config = new SmallRyeConfigBuilder()
.withSources(config(
"datasource.postgresql.jdbc.url", "value",
"datasource.postgresql.password", "value"))
.withMapping(DataSourcesJdbcBuildTimeConfig.class)
.withMapping(DataSourcesJdbcRuntimeConfig.class)
.withMapping(DataSourcesRuntimeConfig.class)
.withMapping(DataSourcesBuildTimeConfig.class)
.build();

assertTrue(
config.getConfigMapping(DataSourcesRuntimeConfig.class).dataSources().get("postgresql").password().isPresent());

config = new SmallRyeConfigBuilder()
.withSources(config(
"datasource.postgresql.jdbc.url", "value",
"datasource.postgresql.password", "value"))
.withMapping(DataSourcesJdbcBuildTimeConfig.class)
.withMapping(DataSourcesJdbcRuntimeConfig.class)
.withMapping(DataSourcesBuildTimeConfig.class)
.withMapping(DataSourcesRuntimeConfig.class)
.build();

assertTrue(
config.getConfigMapping(DataSourcesRuntimeConfig.class).dataSources().get("postgresql").password().isPresent());

config = new SmallRyeConfigBuilder()
.withSources(config(
"datasource.postgresql.jdbc.url", "value",
"datasource.postgresql.password", "value"))
.withMapping(DataSourcesRuntimeConfig.class)
.withMapping(DataSourcesJdbcBuildTimeConfig.class)
.withMapping(DataSourcesJdbcRuntimeConfig.class)
.withMapping(DataSourcesBuildTimeConfig.class)
.build();

assertTrue(
config.getConfigMapping(DataSourcesRuntimeConfig.class).dataSources().get("postgresql").password().isPresent());
}

@ConfigMapping(prefix = "datasource")
interface DataSourcesRuntimeConfig {
@WithParentName
@WithDefaults
@WithUnnamedKey("<default>")
Map<String, DataSourceRuntimeConfig> dataSources();

interface DataSourceRuntimeConfig {
@WithDefault("true")
boolean active();

Optional<String> username();

Optional<String> password();

Optional<String> credentialsProvider();

Optional<String> credentialsProviderName();
}
}

@ConfigMapping(prefix = "datasource")
interface DataSourcesJdbcRuntimeConfig {
DataSourceJdbcRuntimeConfig jdbc();

@WithParentName
@WithDefaults
Map<String, DataSourceJdbcOuterNamedRuntimeConfig> namedDataSources();

interface DataSourceJdbcOuterNamedRuntimeConfig {
DataSourceJdbcRuntimeConfig jdbc();
}

interface DataSourceJdbcRuntimeConfig {
Optional<String> url();

OptionalInt initialSize();

@WithDefault("0")
int minSize();

@WithDefault("20")
int maxSize();

@WithDefault("2M")
String backgroundValidationInterval();

Optional<String> foregroundValidationInterval();

@WithDefault("5S")
Optional<String> acquisitionTimeout();

Optional<String> leakDetectionInterval();

@WithDefault("5M")
String idleRemovalInterval();

Optional<String> maxLifetime();

@WithDefault("false")
boolean extendedLeakReport();

@WithDefault("false")
boolean flushOnClose();

@WithDefault("true")
boolean detectStatementLeaks();

Optional<String> newConnectionSql();

Optional<String> validationQuerySql();

@WithDefault("true")
boolean poolingEnabled();

Map<String, String> additionalJdbcProperties();

@WithName("telemetry.enabled")
Optional<Boolean> telemetry();
}
}

@ConfigMapping(prefix = "datasource")
interface DataSourcesBuildTimeConfig {
@WithParentName
@WithDefaults
@WithUnnamedKey("<default>")
Map<String, DataSourceBuildTimeConfig> dataSources();

@WithName("health.enabled")
@WithDefault("true")
boolean healthEnabled();

@WithName("metrics.enabled")
@WithDefault("false")
boolean metricsEnabled();

@Deprecated
Optional<String> url();

@Deprecated
Optional<String> driver();

interface DataSourceBuildTimeConfig {
Optional<String> dbKind();

Optional<String> dbVersion();

DevServicesBuildTimeConfig devservices();

@WithDefault("false")
boolean healthExclude();

interface DevServicesBuildTimeConfig {
Optional<Boolean> enabled();

Optional<String> imageName();

Map<String, String> containerEnv();

Map<String, String> containerProperties();

Map<String, String> properties();

OptionalInt port();

Optional<String> command();

Optional<String> dbName();

Optional<String> username();

Optional<String> password();

Optional<String> initScriptPath();

Map<String, String> volumes();

@WithDefault("true")
boolean reuse();
}
}
}

@ConfigMapping(prefix = "datasource")
interface DataSourcesJdbcBuildTimeConfig {
@WithParentName
@WithDefaults
@WithUnnamedKey("<ddefault>")
Map<String, DataSourceJdbcOuterNamedBuildTimeConfig> dataSources();

interface DataSourceJdbcOuterNamedBuildTimeConfig {
DataSourceJdbcBuildTimeConfig jdbc();

interface DataSourceJdbcBuildTimeConfig {
@WithParentName
@WithDefault("true")
boolean enabled();

Optional<String> driver();

Optional<Boolean> enableMetrics();

@WithDefault("false")
boolean tracing();

@WithDefault("false")
boolean telemetry();
}
}
}
}

0 comments on commit cd8d6ec

Please sign in to comment.