Skip to content

Commit 3e9f521

Browse files
committed
Introduce SmallRyeConfig.subset
- Fixes #981
1 parent 6bd3669 commit 3e9f521

File tree

4 files changed

+188
-52
lines changed

4 files changed

+188
-52
lines changed

implementation/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@
8282
<groupId>io.smallrye.testing</groupId>
8383
<artifactId>smallrye-testing-utilities</artifactId>
8484
</dependency>
85+
<dependency>
86+
<groupId>org.assertj</groupId>
87+
<artifactId>assertj-core</artifactId>
88+
<scope>test</scope>
89+
</dependency>
8590
</dependencies>
8691

8792
<build>

implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java

Lines changed: 63 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@
1515
*/
1616
package io.smallrye.config;
1717

18-
import static io.smallrye.config.ConfigSourceInterceptor.EMPTY;
19-
import static io.smallrye.config.common.utils.StringUtil.replaceNonAlphanumericByUnderscores;
20-
import static io.smallrye.config.common.utils.StringUtil.toLowerCaseAndDotted;
18+
import io.smallrye.common.annotation.Experimental;
19+
import io.smallrye.config.SmallRyeConfigBuilder.InterceptorWithPriority;
20+
import org.eclipse.microprofile.config.Config;
21+
import org.eclipse.microprofile.config.ConfigProvider;
22+
import org.eclipse.microprofile.config.spi.ConfigSource;
23+
import org.eclipse.microprofile.config.spi.ConfigSourceProvider;
24+
import org.eclipse.microprofile.config.spi.Converter;
2125

2226
import java.io.ObjectStreamException;
2327
import java.io.Serializable;
@@ -39,14 +43,9 @@
3943
import java.util.concurrent.ConcurrentHashMap;
4044
import java.util.function.IntFunction;
4145

42-
import org.eclipse.microprofile.config.Config;
43-
import org.eclipse.microprofile.config.ConfigProvider;
44-
import org.eclipse.microprofile.config.spi.ConfigSource;
45-
import org.eclipse.microprofile.config.spi.ConfigSourceProvider;
46-
import org.eclipse.microprofile.config.spi.Converter;
47-
48-
import io.smallrye.common.annotation.Experimental;
49-
import io.smallrye.config.SmallRyeConfigBuilder.InterceptorWithPriority;
46+
import static io.smallrye.config.ConfigSourceInterceptor.EMPTY;
47+
import static io.smallrye.config.common.utils.StringUtil.replaceNonAlphanumericByUnderscores;
48+
import static io.smallrye.config.common.utils.StringUtil.toLowerCaseAndDotted;
5049

5150
/**
5251
* @author <a href="http://jmesnil.net/">Jeff Mesnil</a> (c) 2017 Red Hat inc.
@@ -112,7 +111,7 @@ public <T, C extends Collection<T>> C getValues(String name, Converter<T> conver
112111
}
113112

114113
public <T, C extends Collection<T>> C getIndexedValues(String name, Converter<T> converter,
115-
IntFunction<C> collectionFactory) {
114+
IntFunction<C> collectionFactory) {
116115
List<String> indexedProperties = getIndexedProperties(name);
117116
if (indexedProperties.isEmpty()) {
118117
throw new NoSuchElementException(ConfigMessages.msg.propertyNotFound(name));
@@ -142,7 +141,7 @@ public List<Integer> getIndexedPropertiesIndexes(final String property) {
142141
if (propertyName.startsWith(property) && propertyName.length() > property.length()) {
143142
int index = property.length();
144143
if (propertyName.charAt(index) == '[') {
145-
for (;;) {
144+
for (; ; ) {
146145
if (propertyName.charAt(index) == ']') {
147146
try {
148147
indexes.add(Integer.parseInt(propertyName.substring(property.length() + 1, index)));
@@ -172,14 +171,14 @@ public <T> T getValue(String name, Class<T> aClass) {
172171
/**
173172
* Return the content of the direct sub properties as the requested type of Map.
174173
*
175-
* @param name The configuration property name
174+
* @param name The configuration property name
176175
* @param kClass the type into which the keys should be converted
177176
* @param vClass the type into which the values should be converted
178-
* @param <K> the key type
179-
* @param <V> the value type
177+
* @param <K> the key type
178+
* @param <V> the value type
180179
* @return the resolved property value as an instance of the requested Map (not {@code null})
181180
* @throws IllegalArgumentException if a key or a value cannot be converted to the specified types
182-
* @throws NoSuchElementException if no direct sub properties could be found.
181+
* @throws NoSuchElementException if no direct sub properties could be found.
183182
*/
184183
public <K, V> Map<K, V> getValues(String name, Class<K> kClass, Class<V> vClass) {
185184
final Map<K, V> result = getValuesAsMap(name, requireConverter(kClass), requireConverter(vClass));
@@ -192,11 +191,11 @@ public <K, V> Map<K, V> getValues(String name, Class<K> kClass, Class<V> vClass)
192191
/**
193192
* Return the content of the direct sub properties as the requested type of Map.
194193
*
195-
* @param name The configuration property name
196-
* @param keyConverter The converter to use for the keys.
194+
* @param name The configuration property name
195+
* @param keyConverter The converter to use for the keys.
197196
* @param valueConverter The converter to use for the values.
198-
* @param <K> The type of the keys.
199-
* @param <V> The type of the values.
197+
* @param <K> The type of the keys.
198+
* @param <V> The type of the values.
200199
* @return the resolved property value as an instance of the requested Map or {@code null} if it could not be found.
201200
* @throws IllegalArgumentException if a key or a value cannot be converted to the specified types
202201
*/
@@ -223,7 +222,6 @@ public <K, V> Map<K, V> getValuesAsMap(String name, Converter<K> keyConverter, C
223222
}
224223

225224
/**
226-
*
227225
* This method handles calls from both {@link Config#getValue} and {@link Config#getOptionalValue}.<br>
228226
*/
229227
@SuppressWarnings("unchecked")
@@ -246,17 +244,17 @@ public <T> T getValue(String name, Converter<T> converter) {
246244
/**
247245
* This method handles converting values for both CDI injections and programatical calls.<br>
248246
* <br>
249-
*
247+
* <p>
250248
* Calls for converting non-optional values ({@link Config#getValue} and "Injecting Native Values")
251249
* should throw an {@link Exception} for each of the following:<br>
252-
*
250+
* <p>
253251
* 1. {@link IllegalArgumentException} - if the property cannot be converted by the {@link Converter} to the specified type
254252
* <br>
255253
* 2. {@link NoSuchElementException} - if the property is not defined <br>
256254
* 3. {@link NoSuchElementException} - if the property is defined as an empty string <br>
257255
* 4. {@link NoSuchElementException} - if the {@link Converter} returns {@code null} <br>
258256
* <br>
259-
*
257+
* <p>
260258
* Calls for converting optional values ({@link Config#getOptionalValue} and "Injecting Optional Values")
261259
* should only throw an {@link Exception} for #1 ({@link IllegalArgumentException} when the property cannot be converted to
262260
* the specified type).
@@ -312,7 +310,7 @@ public <T> T convertValue(ConfigValue configValue, Converter<T> converter) {
312310
* Determine whether the <em>raw value</em> of a configuration property is exactly equal to the expected given
313311
* value.
314312
*
315-
* @param name the property name (must not be {@code null})
313+
* @param name the property name (must not be {@code null})
316314
* @param expected the expected value (may be {@code null})
317315
* @return {@code true} if the values are equal, {@code false} otherwise
318316
*/
@@ -344,11 +342,11 @@ public <T> Optional<T> getOptionalValue(String name, Class<T> aClass) {
344342
/**
345343
* Return the content of the direct sub properties as the requested type of Map.
346344
*
347-
* @param name The configuration property name
345+
* @param name The configuration property name
348346
* @param kClass the type into which the keys should be converted
349347
* @param vClass the type into which the values should be converted
350-
* @param <K> the key type
351-
* @param <V> the value type
348+
* @param <K> the key type
349+
* @param <V> the value type
352350
* @return the resolved property value as an instance of the requested Map (not {@code null})
353351
* @throws IllegalArgumentException if a key or a value cannot be converted to the specified types
354352
*/
@@ -365,12 +363,12 @@ public <T> Optional<List<T>> getOptionalValues(final String propertyName, final
365363
}
366364

367365
public <T, C extends Collection<T>> Optional<C> getOptionalValues(String name, Class<T> itemClass,
368-
IntFunction<C> collectionFactory) {
366+
IntFunction<C> collectionFactory) {
369367
return getOptionalValues(name, requireConverter(itemClass), collectionFactory);
370368
}
371369

372370
public <T, C extends Collection<T>> Optional<C> getOptionalValues(String name, Converter<T> converter,
373-
IntFunction<C> collectionFactory) {
371+
IntFunction<C> collectionFactory) {
374372
final Optional<C> optionalValue = getOptionalValue(name,
375373
Converters.newCollectionConverter(converter, collectionFactory));
376374
if (optionalValue.isPresent()) {
@@ -381,7 +379,7 @@ public <T, C extends Collection<T>> Optional<C> getOptionalValues(String name, C
381379
}
382380

383381
public <T, C extends Collection<T>> Optional<C> getIndexedOptionalValues(String name, Converter<T> converter,
384-
IntFunction<C> collectionFactory) {
382+
IntFunction<C> collectionFactory) {
385383
List<String> indexedProperties = getIndexedProperties(name);
386384
if (indexedProperties.isEmpty()) {
387385
return Optional.empty();
@@ -459,11 +457,42 @@ public Optional<ConfigSource> getConfigSource(final String name) {
459457
return Optional.empty();
460458
}
461459

460+
/**
461+
* Return a {@link Config} containing every key from the current {@link Config} that starts with the specified
462+
* prefix. The prefix is removed from the keys in the subset. For example, if the configuration contains the following
463+
* properties:
464+
*
465+
* <pre>
466+
* prefix.number = 1
467+
* prefix.string = Hello
468+
* prefixed.foo = bar
469+
* prefix = World
470+
* </pre>
471+
* <p>
472+
* the Configuration returned by {@code subset("prefix")} will contain the properties:
473+
*
474+
* <pre>
475+
* number = 1
476+
* string = Hello
477+
* = World
478+
* </pre>
479+
* <p>
480+
* (The key for the value "World" is an empty string)
481+
* <p>
482+
*
483+
* @param prefix The prefix used to select the properties.
484+
* @return a subset configuration
485+
*/
486+
@Experimental("Return a subset of the configuration")
487+
public Config subset(final String prefix) {
488+
return new SmallRyeSubsetConfig(prefix, this);
489+
}
490+
462491
public <T> T convert(String value, Class<T> asType) {
463492
return value != null ? requireConverter(asType).convert(value) : null;
464493
}
465494

466-
@SuppressWarnings({ "unchecked", "rawtypes" })
495+
@SuppressWarnings({"unchecked", "rawtypes"})
467496
private <T> Converter<Optional<T>> getOptionalConverter(Class<T> asType) {
468497
return optionalConverters.computeIfAbsent(asType,
469498
clazz -> Converters.newOptionalConverter(requireConverter((Class) clazz)));
@@ -733,7 +762,7 @@ private static List<ConfigurableConfigSource> getConfigurableSources(final List<
733762
* If <code>FOO_BAR</code> is present a property <code>foo.bar</code> is required.
734763
*/
735764
private static Set<String> generateDottedProperties(final List<ConfigSource> sources,
736-
final SmallRyeConfigSourceInterceptorContext current) {
765+
final SmallRyeConfigSourceInterceptorContext current) {
737766
// Collect all known properties
738767
Set<String> properties = new HashSet<>();
739768
Iterator<String> iterateNames = current.iterateNames();
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package io.smallrye.config;
2+
3+
import org.eclipse.microprofile.config.Config;
4+
import org.eclipse.microprofile.config.ConfigValue;
5+
import org.eclipse.microprofile.config.spi.ConfigSource;
6+
import org.eclipse.microprofile.config.spi.Converter;
7+
8+
import java.util.List;
9+
import java.util.Optional;
10+
import java.util.stream.Collectors;
11+
import java.util.stream.StreamSupport;
12+
13+
/**
14+
* @author George Gastaldi
15+
*/
16+
public class SmallRyeSubsetConfig implements Config {
17+
18+
private final String prefix;
19+
20+
private final Config delegate;
21+
22+
public SmallRyeSubsetConfig(String prefix, Config delegate) {
23+
this.prefix = prefix;
24+
this.delegate = delegate;
25+
}
26+
27+
@Override
28+
public <T> T getValue(String propertyName, Class<T> propertyType) {
29+
return delegate.getValue(toSubsetPropertyName(propertyName), propertyType);
30+
}
31+
32+
@Override
33+
public ConfigValue getConfigValue(String propertyName) {
34+
return delegate.getConfigValue(toSubsetPropertyName(propertyName));
35+
}
36+
37+
@Override
38+
public <T> List<T> getValues(String propertyName, Class<T> propertyType) {
39+
return delegate.getValues(toSubsetPropertyName(propertyName), propertyType);
40+
}
41+
42+
@Override
43+
public <T> Optional<T> getOptionalValue(String propertyName, Class<T> propertyType) {
44+
return delegate.getOptionalValue(toSubsetPropertyName(propertyName), propertyType);
45+
}
46+
47+
@Override
48+
public <T> Optional<List<T>> getOptionalValues(String propertyName, Class<T> propertyType) {
49+
return delegate.getOptionalValues(toSubsetPropertyName(propertyName), propertyType);
50+
}
51+
52+
@Override
53+
public Iterable<String> getPropertyNames() {
54+
return StreamSupport.stream(delegate.getPropertyNames().spliterator(), false)
55+
.map(this::chopSubsetPropertyName)
56+
.collect(Collectors.toSet());
57+
}
58+
59+
@Override
60+
public Iterable<ConfigSource> getConfigSources() {
61+
return delegate.getConfigSources();
62+
}
63+
64+
@Override
65+
public <T> Optional<Converter<T>> getConverter(Class<T> forType) {
66+
return delegate.getConverter(forType);
67+
}
68+
69+
@Override
70+
public <T> T unwrap(Class<T> type) {
71+
return delegate.unwrap(type);
72+
}
73+
74+
private String toSubsetPropertyName(String propertyName) {
75+
if (propertyName.isBlank()) {
76+
return prefix;
77+
} else {
78+
return prefix + "." + propertyName;
79+
}
80+
}
81+
82+
private String chopSubsetPropertyName(String propertyName) {
83+
if (propertyName.equalsIgnoreCase(prefix)) {
84+
return "";
85+
} else if (propertyName.startsWith(prefix)) {
86+
return propertyName.substring(prefix.length() + 1);
87+
} else {
88+
return propertyName;
89+
}
90+
}
91+
}

implementation/src/test/java/io/smallrye/config/SmallRyeConfigTest.java

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
11
package io.smallrye.config;
22

3-
import static io.smallrye.config.Converters.STRING_CONVERTER;
4-
import static io.smallrye.config.KeyValuesConfigSource.config;
5-
import static java.util.Collections.singletonList;
6-
import static java.util.Collections.singletonMap;
7-
import static java.util.stream.Collectors.toSet;
8-
import static java.util.stream.StreamSupport.stream;
9-
import static org.junit.jupiter.api.Assertions.assertEquals;
10-
import static org.junit.jupiter.api.Assertions.assertFalse;
11-
import static org.junit.jupiter.api.Assertions.assertNotNull;
12-
import static org.junit.jupiter.api.Assertions.assertNull;
13-
import static org.junit.jupiter.api.Assertions.assertThrows;
14-
import static org.junit.jupiter.api.Assertions.assertTrue;
3+
import io.smallrye.config.common.AbstractConfigSource;
4+
import io.smallrye.config.common.MapBackedConfigSource;
5+
import org.assertj.core.api.Assertions;
6+
import org.eclipse.microprofile.config.Config;
7+
import org.eclipse.microprofile.config.spi.ConfigSource;
8+
import org.junit.jupiter.api.Test;
159

1610
import java.util.ArrayList;
1711
import java.util.Arrays;
@@ -23,12 +17,14 @@
2317
import java.util.Optional;
2418
import java.util.Set;
2519

26-
import org.eclipse.microprofile.config.Config;
27-
import org.eclipse.microprofile.config.spi.ConfigSource;
28-
import org.junit.jupiter.api.Test;
29-
30-
import io.smallrye.config.common.AbstractConfigSource;
31-
import io.smallrye.config.common.MapBackedConfigSource;
20+
import static io.smallrye.config.Converters.STRING_CONVERTER;
21+
import static io.smallrye.config.KeyValuesConfigSource.config;
22+
import static java.util.Collections.singletonList;
23+
import static java.util.Collections.singletonMap;
24+
import static java.util.stream.Collectors.toSet;
25+
import static java.util.stream.StreamSupport.stream;
26+
import static org.assertj.core.api.Assertions.assertThat;
27+
import static org.junit.jupiter.api.Assertions.*;
3228

3329
class SmallRyeConfigTest {
3430
@Test
@@ -405,4 +401,19 @@ void emptyPropertyNames() {
405401

406402
assertEquals("value", config.getRawValue(""));
407403
}
404+
405+
@Test
406+
void subset() {
407+
SmallRyeConfig config = new SmallRyeConfigBuilder()
408+
.withSources(config(
409+
"app.foo", "bar",
410+
"app.foo.user", "guest",
411+
"app.foo.password", "apassword"))
412+
.build();
413+
Config subset = config.subset("app.foo");
414+
assertEquals("bar", subset.getValue("", String.class));
415+
assertEquals("guest", subset.getValue("user", String.class));
416+
assertEquals("apassword", subset.getValue("password", String.class));
417+
assertThat(subset.getPropertyNames()).containsAnyOf("", "user", "password");
418+
}
408419
}

0 commit comments

Comments
 (0)