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

Use config ordinal to determine which config style to use when populating list, between comma-separated and indexed #1266

Merged
merged 1 commit into from
Dec 6, 2024
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
5 changes: 3 additions & 2 deletions documentation/src/main/docs/config/indexed-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ The indexed property syntax uses the property name and square brackets with an i

A call to `Config#getValues("my.collection", String.class)`, will automatically create and convert a `List<String>`
that contains the values `dog`, `cat` and `turtle`. A call to `Config#getValues("my.indexed.collection", String.class)`
returns the exact same result. If SmallRye Config finds the same property name in their indexed and unindexed format,
the indexed value has priority.
returns the exact same result. The indexed property format is prioritized when both styles are found in the same
configuration source. When available in multiple sources, the higher ordinal source wins, like any other configuration
lookup.

The indexed property is sorted by its index before being added to the target `Collection`. Any gaps in the indexes do
not resolve to the target `Collection`, which means that the `Collection` result will store all values without empty
Expand Down
122 changes: 97 additions & 25 deletions implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,25 @@ public <T, C extends Collection<T>> C getValues(String name, Converter<T> conver
// Try legacy / MP comma separated values
return getValue(name, newCollectionConverter(converter, collectionFactory));
}
return getIndexedValues(indexedProperties, converter, collectionFactory);

// Check ordinality of indexed
int indexedOrdinality = Integer.MIN_VALUE;
C collection = collectionFactory.apply(indexedProperties.size());
for (String indexedProperty : indexedProperties) {
ConfigValue indexed = getConfigValue(indexedProperty);
if (indexed.getConfigSourceOrdinal() >= indexedOrdinality) {
indexedOrdinality = indexed.getConfigSourceOrdinal();
}
collection.add(convertValue(indexed, converter));
}

// Use indexed if comma separated empty or higher in ordinality
ConfigValue commaSeparated = getConfigValue(name);
if (commaSeparated == null || indexedOrdinality >= commaSeparated.getConfigSourceOrdinal()) {
return collection;
} else {
return getValue(name, newCollectionConverter(converter, collectionFactory));
}
}

public <T, C extends Collection<T>> C getIndexedValues(String name, Converter<T> converter,
Expand Down Expand Up @@ -384,19 +402,32 @@ <K, V, C extends Collection<V>> Map<K, C> getMapIndexedValues(
@SuppressWarnings("unchecked")
public <T> T getValue(String name, Class<T> propertyType) {
if (propertyType.isArray()) {
ConfigValue configValue = getConfigValue(name);
if (configValue.getValue() != null) {
List<String> indexedProperties = getIndexedProperties(name);
if (indexedProperties.isEmpty()) {
// Try legacy / MP comma separated values
return getValue(name, requireConverter(propertyType));
}

List<?> values = getValues(name, propertyType.getComponentType());
Object array = Array.newInstance(propertyType.getComponentType(), values.size());
for (int i = 0, valuesSize = values.size(); i < valuesSize; i++) {
Array.set(array, i, values.get(i));
// Check ordinality of indexed
int indexedOrdinality = Integer.MIN_VALUE;
Object array = Array.newInstance(propertyType.getComponentType(), indexedProperties.size());
for (int i = 0; i < indexedProperties.size(); i++) {
final String indexedProperty = indexedProperties.get(i);
ConfigValue indexed = getConfigValue(indexedProperty);
if (indexed.getConfigSourceOrdinal() >= indexedOrdinality) {
indexedOrdinality = indexed.getConfigSourceOrdinal();
}
Array.set(array, i, convertValue(indexed, requireConverter(propertyType.getComponentType())));
}
return (T) array;
}

// Use indexed if comma separated empty or higher in ordinality
ConfigValue commaSeparated = getConfigValue(name);
if (commaSeparated == null || indexedOrdinality >= commaSeparated.getConfigSourceOrdinal()) {
return (T) array;
} else {
return convertValue(commaSeparated, requireConverter(propertyType));
}
}
return getValue(name, requireConverter(propertyType));
}

Expand Down Expand Up @@ -502,8 +533,36 @@ public String getRawValue(String name) {
}

@Override
public <T> Optional<T> getOptionalValue(String name, Class<T> aClass) {
return getValue(name, getOptionalConverter(aClass));
@SuppressWarnings("unchecked")
public <T> Optional<T> getOptionalValue(String name, Class<T> propertyType) {
if (propertyType.isArray()) {
List<String> indexedProperties = getIndexedProperties(name);
if (indexedProperties.isEmpty()) {
// Try legacy / MP comma separated values
return getValue(name, getOptionalConverter(propertyType));
}

// Check ordinality of indexed
int indexedOrdinality = Integer.MIN_VALUE;
Object array = Array.newInstance(propertyType.getComponentType(), indexedProperties.size());
for (int i = 0; i < indexedProperties.size(); i++) {
final String indexedProperty = indexedProperties.get(i);
ConfigValue indexed = getConfigValue(indexedProperty);
if (indexed.getConfigSourceOrdinal() >= indexedOrdinality) {
indexedOrdinality = indexed.getConfigSourceOrdinal();
}
Array.set(array, i, convertValue(indexed, requireConverter(propertyType.getComponentType())));
}

// Use indexed if comma separated empty or higher in ordinality
ConfigValue commaSeparated = getConfigValue(name);
if (commaSeparated == null || indexedOrdinality >= commaSeparated.getConfigSourceOrdinal()) {
return (Optional<T>) Optional.of(array);
} else {
return getValue(name, getOptionalConverter(propertyType));
}
}
return getValue(name, getOptionalConverter(propertyType));
}

public <T> Optional<T> getOptionalValue(String name, Converter<T> converter) {
Expand All @@ -521,11 +580,29 @@ public <T, C extends Collection<T>> Optional<C> getOptionalValues(String name, C

public <T, C extends Collection<T>> Optional<C> getOptionalValues(String name, Converter<T> converter,
IntFunction<C> collectionFactory) {
Optional<C> optionalValue = getOptionalValue(name, newCollectionConverter(converter, collectionFactory));
if (optionalValue.isPresent()) {
return optionalValue;
List<String> indexedProperties = getIndexedProperties(name);
if (indexedProperties.isEmpty()) {
// Try legacy / MP comma separated values
return getOptionalValue(name, newCollectionConverter(converter, collectionFactory));
}

// Check ordinality of indexed
int indexedOrdinality = Integer.MIN_VALUE;
C collection = collectionFactory.apply(indexedProperties.size());
for (String indexedProperty : indexedProperties) {
ConfigValue indexed = getConfigValue(indexedProperty);
if (indexed.getValue() != null && indexed.getConfigSourceOrdinal() >= indexedOrdinality) {
indexedOrdinality = indexed.getConfigSourceOrdinal();
}
convertValue(indexed, newOptionalConverter(converter)).ifPresent(collection::add);
}

// Use indexed if comma separated empty or higher in ordinality
ConfigValue commaSeparated = getConfigValue(name);
if (commaSeparated == null || indexedOrdinality >= commaSeparated.getConfigSourceOrdinal()) {
return collection.isEmpty() ? Optional.empty() : Optional.of(collection);
} else {
return getIndexedOptionalValues(name, converter, collectionFactory);
return getOptionalValue(name, newCollectionConverter(converter, collectionFactory));
}
}

Expand All @@ -536,17 +613,13 @@ public <T, C extends Collection<T>> Optional<C> getIndexedOptionalValues(String
return Optional.empty();
}

final C collection = collectionFactory.apply(indexedProperties.size());
C collection = collectionFactory.apply(indexedProperties.size());
for (String indexedProperty : indexedProperties) {
final Optional<T> optionalValue = getOptionalValue(indexedProperty, converter);
Optional<T> optionalValue = getOptionalValue(indexedProperty, converter);
optionalValue.ifPresent(collection::add);
}

if (!collection.isEmpty()) {
return Optional.of(collection);
}

return Optional.empty();
return collection.isEmpty() ? Optional.empty() : Optional.of(collection);
}

/**
Expand Down Expand Up @@ -839,7 +912,7 @@ private static class ConfigSources implements Serializable {
// Init all late sources
List<String> profiles = getProfiles(positiveInterceptors);
List<ConfigSourceWithPriority> sourcesWithPriorities = mapLateSources(sources, negativeInterceptors,
positiveInterceptors, current, profiles, builder, config);
positiveInterceptors, current, profiles, builder);
List<ConfigSource> configSources = getSources(sourcesWithPriorities);

// Rebuild the chain with the late sources and new instances of the interceptors
Expand Down Expand Up @@ -929,8 +1002,7 @@ private static List<ConfigSourceWithPriority> mapLateSources(
final List<ConfigSourceInterceptor> positiveInterceptors,
final ConfigSourceInterceptorContext current,
final List<String> profiles,
final SmallRyeConfigBuilder builder,
final SmallRyeConfig config) {
final SmallRyeConfigBuilder builder) {

ConfigSourceWithPriority.resetLoadPriority();
List<ConfigSourceWithPriority> currentSources = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
Expand All @@ -28,6 +29,7 @@
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Consumer;
import java.util.function.IntFunction;

import org.eclipse.microprofile.config.Config;
Expand Down Expand Up @@ -212,11 +214,7 @@ void getOptionalValuesIndexed() {
@Test
void getOptionalValuesNotIndexed() {
SmallRyeConfig config = new SmallRyeConfigBuilder()
.withSources(config(
"server.environments", "dev,qa",
"server.environments[0]", "dev",
"server.environments[1]", "qa",
"server.environments[2]", "prod"))
.withSources(config("server.environments", "dev,qa"))
.build();

Optional<List<String>> environments = config.getOptionalValues("server.environments", String.class);
Expand All @@ -232,12 +230,18 @@ void getOptionalValuesEmpty() {
.withSources(config("server.environments", ""))
.build();

assertFalse(
config.getIndexedOptionalValues("server.environments", config.requireConverter(String.class), ArrayList::new)
.isPresent());
assertFalse(config.getOptionalValues("server.environments", String.class).isPresent());

SmallRyeConfig configIndexed = new SmallRyeConfigBuilder()
.withSources(config("server.environments[0]", ""))
.build();

assertFalse(configIndexed
.getIndexedOptionalValues("server.environments", config.requireConverter(String.class), ArrayList::new)
.isPresent());
assertFalse(configIndexed.getOptionalValues("server.environments", String.class).isPresent());
}

Expand Down Expand Up @@ -415,7 +419,7 @@ void getValuesMap() {

Converter<String> stringConverter = config.requireConverter(String.class);
Map<String, String> treeMap = config.getValues("my.prop", stringConverter, stringConverter, t -> new TreeMap<>());
assertTrue(treeMap instanceof TreeMap);
assertInstanceOf(TreeMap.class, treeMap);

Optional<Map<String, String>> optionalMap = config.getOptionalValues("my.prop", String.class, String.class);
assertTrue(optionalMap.isPresent());
Expand All @@ -428,7 +432,7 @@ void getValuesMap() {
Optional<Map<String, String>> optionalTreeMap = config.getOptionalValues("my.prop", stringConverter, stringConverter,
t -> new TreeMap<>());
assertTrue(optionalTreeMap.isPresent());
assertTrue(optionalTreeMap.get() instanceof TreeMap);
assertInstanceOf(TreeMap.class, optionalTreeMap.get());

assertTrue(config.getOptionalValues("my.optional", String.class, String.class).isEmpty());
}
Expand Down Expand Up @@ -477,7 +481,7 @@ void getValuesMapList() {
Converter<String> stringConverter = config.requireConverter(String.class);
Map<String, List<String>> treeMap = config.getValues("my.prop", stringConverter, stringConverter, t -> new TreeMap<>(),
ArrayList::new);
assertTrue(treeMap instanceof TreeMap);
assertInstanceOf(TreeMap.class, treeMap);

Optional<Map<String, List<String>>> optionalMap = config.getOptionalValues("my.prop", String.class, String.class,
ArrayList::new);
Expand All @@ -493,7 +497,7 @@ void getValuesMapList() {
Optional<Map<String, List<String>>> optionalTreeMap = config.getOptionalValues("my.prop", stringConverter,
stringConverter, t -> new TreeMap<>(), ArrayList::new);
assertTrue(optionalTreeMap.isPresent());
assertTrue(optionalTreeMap.get() instanceof TreeMap);
assertInstanceOf(TreeMap.class, optionalTreeMap.get());

assertTrue(config.getOptionalValues("my.optional", String.class, String.class, ArrayList::new).isEmpty());
}
Expand Down Expand Up @@ -603,4 +607,102 @@ void propertiesSources(@TempDir Path tempDir) throws Exception {
assertFalse(config.getConfigSources(EnvConfigSource.class).iterator().hasNext());
assertEquals("1234", config.getRawValue("my.prop"));
}

@Test
void overrideIndexed() {
SmallRyeConfig config = new SmallRyeConfigBuilder()
.withSources(config("list[0]", "one", "list[1]", "two"))
.build();

String[] listArray = config.getValue("list", String[].class);
assertEquals(2, listArray.length);
assertEquals("one", listArray[0]);
assertEquals("two", listArray[1]);
List<String> listList = config.getValues("list", String.class);
assertEquals(2, listList.size());
assertEquals("one", listList.get(0));
assertEquals("two", listList.get(1));
Optional<String[]> listOptionalArray = config.getOptionalValue("list", String[].class);
assertTrue(listOptionalArray.isPresent());
listOptionalArray.ifPresent(list -> {
assertEquals(2, list.length);
assertEquals("one", list[0]);
assertEquals("two", list[1]);
});
Optional<List<String>> listOptionalList = config.getOptionalValues("list", String.class);
assertTrue(listOptionalList.isPresent());
listOptionalList.ifPresent(new Consumer<List<String>>() {
@Override
public void accept(final List<String> list) {
assertEquals(2, list.size());
assertEquals("one", list.get(0));
assertEquals("two", list.get(1));
}
});

config = new SmallRyeConfigBuilder()
.withSources(config("list[0]", "one", "list[1]", "two"))
.withSources(new PropertiesConfigSource(Map.of("list", "three,four"), "", 1000))
.build();

listArray = config.getValue("list", String[].class);
assertEquals(2, listArray.length);
assertEquals("three", listArray[0]);
assertEquals("four", listArray[1]);
listList = config.getValues("list", String.class);
assertEquals(2, listList.size());
assertEquals("three", listList.get(0));
assertEquals("four", listList.get(1));
listOptionalArray = config.getOptionalValue("list", String[].class);
assertTrue(listOptionalArray.isPresent());
listOptionalArray.ifPresent(list -> {
assertEquals(2, list.length);
assertEquals("three", list[0]);
assertEquals("four", list[1]);
});
listOptionalList = config.getOptionalValues("list", String.class);
assertTrue(listOptionalList.isPresent());
listOptionalList.ifPresent(new Consumer<List<String>>() {
@Override
public void accept(final List<String> list) {
assertEquals(2, list.size());
assertEquals("three", list.get(0));
assertEquals("four", list.get(1));
}
});
}

@Test
void overrideCommaSeparated() {
SmallRyeConfig config = new SmallRyeConfigBuilder()
.withSources(config("list", "one,two"))
.withSources(new PropertiesConfigSource(Map.of("list[0]", "three", "list[1]", "four"), "", 1000))
.build();

String[] listArray = config.getValue("list", String[].class);
assertEquals(2, listArray.length);
assertEquals("three", listArray[0]);
assertEquals("four", listArray[1]);
List<String> listList = config.getValues("list", String.class);
assertEquals(2, listList.size());
assertEquals("three", listList.get(0));
assertEquals("four", listList.get(1));
Optional<String[]> listOptionalArray = config.getOptionalValue("list", String[].class);
assertTrue(listOptionalArray.isPresent());
listOptionalArray.ifPresent(list -> {
assertEquals(2, list.length);
assertEquals("three", list[0]);
assertEquals("four", list[1]);
});
Optional<List<String>> listOptionalList = config.getOptionalValues("list", String.class);
assertTrue(listOptionalList.isPresent());
listOptionalList.ifPresent(new Consumer<List<String>>() {
@Override
public void accept(final List<String> list) {
assertEquals(2, list.size());
assertEquals("three", list.get(0));
assertEquals("four", list.get(1));
}
});
}
}
Loading
Loading