Skip to content

Commit cfad960

Browse files
committed
Adopt SmallRye Config
Ports stevespringett/Alpine#696 Signed-off-by: nscuro <nscuro@protonmail.com>
1 parent f674446 commit cfad960

31 files changed

+848
-325
lines changed

alpine/alpine-common/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@
3636
<groupId>org.apache.commons</groupId>
3737
<artifactId>commons-lang3</artifactId>
3838
</dependency>
39+
<dependency>
40+
<groupId>io.smallrye.config</groupId>
41+
<artifactId>smallrye-config-core</artifactId>
42+
</dependency>
3943
<dependency>
4044
<groupId>com.fasterxml.jackson.core</groupId>
4145
<artifactId>jackson-annotations</artifactId>

alpine/alpine-common/src/main/java/alpine/Config.java

Lines changed: 118 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,36 @@
2222
import alpine.common.util.ByteFormat;
2323
import alpine.common.util.PathUtil;
2424
import alpine.common.util.SystemUtil;
25+
import io.smallrye.config.ConfigSourceInterceptor;
26+
import io.smallrye.config.ConfigSourceInterceptorContext;
27+
import io.smallrye.config.ConfigSourceInterceptorFactory;
28+
import io.smallrye.config.ExpressionConfigSourceInterceptor;
29+
import io.smallrye.config.Priorities;
30+
import io.smallrye.config.ProfileConfigSourceInterceptor;
31+
import io.smallrye.config.RelocateConfigSourceInterceptor;
32+
import io.smallrye.config.SmallRyeConfig;
33+
import io.smallrye.config.SmallRyeConfigBuilder;
2534
import org.apache.commons.lang3.StringUtils;
2635

2736
import java.io.File;
28-
import java.io.FileNotFoundException;
2937
import java.io.IOException;
3038
import java.io.InputStream;
3139
import java.io.OutputStream;
3240
import java.nio.file.Files;
33-
import java.util.Arrays;
34-
import java.util.Collections;
3541
import java.util.HashMap;
3642
import java.util.List;
3743
import java.util.Map;
44+
import java.util.OptionalInt;
3845
import java.util.Properties;
3946
import java.util.UUID;
40-
47+
import java.util.stream.Collectors;
48+
49+
import static io.smallrye.config.PropertiesConfigSourceLoader.inFileSystem;
50+
import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_LOCATIONS;
51+
import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_LOG_VALUES;
52+
import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_MAPPING_VALIDATE_UNKNOWN;
53+
import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_PROFILE;
54+
import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_PROFILE_PARENT;
4155
import static java.util.function.Predicate.not;
4256

4357
/**
@@ -54,10 +68,10 @@ public class Config {
5468
private static final String ALPINE_VERSION_PROP_FILE = "alpine.version";
5569
private static final String APPLICATION_VERSION_PROP_FILE = "application.version";
5670
private static final Config INSTANCE;
57-
private static Properties properties;
5871
private static Properties alpineVersionProperties;
5972
private static Properties applicationVersionProperties;
6073
private static String systemId;
74+
private static org.eclipse.microprofile.config.Config delegateConfig;
6175

6276
static {
6377
LOGGER.info(StringUtils.repeat("-", 80));
@@ -218,40 +232,71 @@ public static Config getInstance() {
218232
* Initialize the Config object. This method should only be called once.
219233
*/
220234
void init() {
221-
if (properties != null) {
235+
if (delegateConfig != null) {
222236
return;
223237
}
224238

225239
LOGGER.info("Initializing Configuration");
226-
properties = new Properties();
227-
240+
final var configBuilder = new SmallRyeConfigBuilder()
241+
.forClassLoader(Thread.currentThread().getContextClassLoader())
242+
// Enable default config sources:
243+
//
244+
// | Source | Priority |
245+
// | :--------------------------------------------------- | :------- |
246+
// | System properties | 400 |
247+
// | Environment variables | 300 |
248+
// | ${pwd}/.env file | 295 |
249+
// | ${pwd}/config/application.properties | 260 |
250+
// | ${classpath}/application.properties | 250 |
251+
// | ${classpath}/META-INF/microprofile-config.properties | 100 |
252+
//
253+
// https://smallrye.io/smallrye-config/3.10.2/config/getting-started/#config-sources
254+
.addDefaultSources()
255+
// Support expressions.
256+
// https://smallrye.io/smallrye-config/3.10.2/config/expressions/
257+
.withInterceptors(new ExpressionConfigSourceInterceptor())
258+
// Support profiles.
259+
// https://smallrye.io/smallrye-config/3.10.2/config/profiles/
260+
.withInterceptors(new ProfileConfigSourceInterceptor(List.of("prod", "dev", "test")))
261+
// Relocate SmallRye properties to the Alpine prefix for better framework "immersion".
262+
// https://smallrye.io/smallrye-config/3.10.2/extensions/relocate/
263+
.withInterceptorFactories(new ConfigSourceInterceptorFactory() {
264+
265+
@Override
266+
public ConfigSourceInterceptor getInterceptor(final ConfigSourceInterceptorContext context) {
267+
// Properties to be relocated are documented here:
268+
// https://smallrye.io/smallrye-config/3.10.2/config/configuration/
269+
return new RelocateConfigSourceInterceptor(Map.ofEntries(
270+
Map.entry(SMALLRYE_CONFIG_PROFILE, "alpine.config.profile"),
271+
Map.entry(SMALLRYE_CONFIG_PROFILE_PARENT, "alpine.config.profile.parent"),
272+
Map.entry(SMALLRYE_CONFIG_LOCATIONS, "alpine.config.locations"),
273+
Map.entry(SMALLRYE_CONFIG_LOG_VALUES, "alpine.config.log.values"),
274+
Map.entry(SMALLRYE_CONFIG_MAPPING_VALIDATE_UNKNOWN, "alpine.config.mapping.validate-unknown")));
275+
}
276+
277+
@Override
278+
public OptionalInt getPriority() {
279+
// Priority must be higher than that of ProfileConfigSourceInterceptor
280+
// in order for profile property relocations to work.
281+
return OptionalInt.of(Priorities.LIBRARY + 210);
282+
}
283+
284+
})
285+
.withDefaultValue("alpine.config.profile", "prod")
286+
// Allow applications to customize the Config via SPI.
287+
// https://smallrye.io/smallrye-config/3.10.2/config/customizer/
288+
.addDiscoveredCustomizers();
289+
290+
// If a custom properties file is specified via "alpine.application.properties" system property,
291+
// register it as additional config source. The file has a higher priority than any of the default
292+
// properties sources.
228293
final String alpineAppProp = PathUtil.resolve(System.getProperty(ALPINE_APP_PROP));
229294
if (StringUtils.isNotBlank(alpineAppProp)) {
230-
LOGGER.info("Loading application properties from " + alpineAppProp);
231-
try (InputStream fileInputStream = Files.newInputStream((new File(alpineAppProp)).toPath())) {
232-
properties.load(fileInputStream);
233-
} catch (FileNotFoundException e) {
234-
LOGGER.error("Could not find property file " + alpineAppProp);
235-
} catch (IOException e) {
236-
LOGGER.error("Unable to load " + alpineAppProp);
237-
}
238-
} else {
239-
LOGGER.info("System property " + ALPINE_APP_PROP + " not specified");
240-
LOGGER.info("Loading " + PROP_FILE + " from classpath");
241-
try (InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(PROP_FILE)) {
242-
if (in != null) {
243-
properties.load(in);
244-
} else {
245-
LOGGER.error("Unable to load (resourceStream is null) " + PROP_FILE);
246-
}
247-
} catch (IOException e) {
248-
LOGGER.error("Unable to load " + PROP_FILE);
249-
}
250-
}
251-
if (properties.size() == 0) {
252-
LOGGER.error("A fatal error occurred loading application properties. Please correct the issue and restart the application.");
295+
configBuilder.withSources(inFileSystem(alpineAppProp, 275, Thread.currentThread().getContextClassLoader()));
253296
}
254297

298+
delegateConfig = configBuilder.build();
299+
255300
alpineVersionProperties = new Properties();
256301
try (InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(ALPINE_VERSION_PROP_FILE)) {
257302
alpineVersionProperties.load(in);
@@ -313,6 +358,20 @@ void init() {
313358
}
314359
}
315360

361+
/**
362+
* @since 5.6.0
363+
*/
364+
public org.eclipse.microprofile.config.Config getDelegate() {
365+
return delegateConfig;
366+
}
367+
368+
/**
369+
* @since 5.6.0
370+
*/
371+
public <T> T getMapping(final Class<T> mappingClass) {
372+
return delegateConfig.unwrap(SmallRyeConfig.class).getConfigMapping(mappingClass);
373+
}
374+
316375
/**
317376
* Retrieves the path where the system.id is stored
318377
* @return a File representing the path to the system.id
@@ -432,15 +491,8 @@ public File getDataDirectorty() {
432491
* @since 1.0.0
433492
*/
434493
public String getProperty(Key key) {
435-
final String envVariable = getPropertyFromEnvironment(key);
436-
if (envVariable != null) {
437-
return envVariable;
438-
}
439-
if (key.getDefaultValue() == null) {
440-
return properties.getProperty(key.getPropertyName());
441-
} else {
442-
return properties.getProperty(key.getPropertyName(), String.valueOf(key.getDefaultValue()));
443-
}
494+
return delegateConfig.getOptionalValue(key.getPropertyName(), String.class).orElseGet(
495+
() -> key.getDefaultValue() != null ? String.valueOf(key.getDefaultValue()) : null);
444496
}
445497

446498
/**
@@ -455,21 +507,17 @@ public String getProperty(Key key) {
455507
* @since 1.7.0
456508
*/
457509
public String getPropertyOrFile(AlpineKey key) {
458-
final AlpineKey fileKey = AlpineKey.valueOf(key.toString()+"_FILE");
459-
final String filePath = getProperty(fileKey);
460-
final String prop = getProperty(key);
461-
if (StringUtils.isNotBlank(filePath)) {
462-
if (prop != null && !prop.equals(String.valueOf(key.getDefaultValue()))) {
463-
LOGGER.warn(fileKey.getPropertyName() + " overrides value from property " + key.getPropertyName());
464-
}
465-
try {
466-
return new String(Files.readAllBytes(new File(PathUtil.resolve(filePath)).toPath())).replaceAll("\\s+", "");
467-
} catch (IOException e) {
468-
LOGGER.error(filePath + " file doesn't exist or not readable.");
469-
return null;
470-
}
471-
}
472-
return prop;
510+
return delegateConfig.getOptionalValue(key.getPropertyName() + ".file", String.class)
511+
.map(filePath -> {
512+
try {
513+
return new String(Files.readAllBytes(new File(PathUtil.resolve(filePath)).toPath())).replaceAll("\\s+", "");
514+
} catch (IOException e) {
515+
LOGGER.error(filePath + " file doesn't exist or not readable.", e);
516+
return null;
517+
}
518+
})
519+
.or(() -> delegateConfig.getOptionalValue(key.getPropertyName(), String.class))
520+
.orElse(null);
473521
}
474522

475523
/**
@@ -520,15 +568,10 @@ public boolean getPropertyAsBoolean(Key key) {
520568
* @since 2.2.5
521569
*/
522570
public List<String> getPropertyAsList(Key key) {
523-
String property = getProperty(key);
524-
if (property == null) {
525-
return Collections.emptyList();
526-
} else {
527-
return Arrays.stream(property.split(","))
528-
.map(String::trim)
529-
.filter(not(String::isEmpty))
530-
.toList();
531-
}
571+
return delegateConfig.getValues(key.getPropertyName(), String.class).stream()
572+
.map(String::trim)
573+
.filter(not(String::isEmpty))
574+
.collect(Collectors.toList());
532575
}
533576

534577
/**
@@ -551,82 +594,25 @@ public List<String> getPropertyAsList(Key key) {
551594
*/
552595
public Map<String, String> getPassThroughProperties(final String prefix) {
553596
final var passThroughProperties = new HashMap<String, String>();
554-
try {
555-
for (final Map.Entry<String, String> envVar : System.getenv().entrySet()) {
556-
if (envVar.getKey().startsWith("ALPINE_%s_".formatted(prefix.toUpperCase().replace(".", "_")))) {
557-
final String key = envVar.getKey().replaceFirst("^ALPINE_", "").toLowerCase().replace("_", ".");
558-
passThroughProperties.put(key, envVar.getValue());
559-
}
560-
}
561-
} catch (SecurityException e) {
562-
LOGGER.warn("""
563-
Unable to retrieve pass-through properties for prefix "%s" \
564-
from environment variables. Using defaults.""".formatted(prefix), e);
565-
}
566-
for (final Map.Entry<Object, Object> property : properties.entrySet()) {
567-
if (property.getKey() instanceof String key
568-
&& key.startsWith("alpine.%s.".formatted(prefix))
569-
&& property.getValue() instanceof final String value) {
570-
key = key.replaceFirst("^alpine\\.", "");
571-
if (!passThroughProperties.containsKey(key)) { // Environment variables take precedence
572-
passThroughProperties.put(key, value);
573-
}
597+
for (final String propertyName : delegateConfig.getPropertyNames()) {
598+
if (!propertyName.startsWith("alpine.%s.".formatted(prefix))) {
599+
continue;
574600
}
601+
602+
final String key = propertyName.replaceFirst("^alpine\\.", "");
603+
passThroughProperties.put(key, delegateConfig.getValue(propertyName, String.class));
575604
}
576605
return passThroughProperties;
577606
}
578607

579-
static void reset() {
580-
properties = null;
581-
}
582-
583-
/**
584-
* Return the configured value for the specified Key.
585-
* @param key The Key to return the configuration for
586-
* @return a String of the value of the configuration
587-
* @since 1.0.0
588-
* @deprecated use {{@link #getProperty(Key)}}
589-
*/
590-
@Deprecated
591-
public String getProperty(String key) {
592-
return properties.getProperty(key);
593-
}
594-
595608
/**
596-
* Return the configured value for the specified Key.
597-
* @param key The String of the key to return the configuration for
598-
* @param defaultValue The default value if the key cannot be found
599-
* @return a String of the value of the configuration
600-
* @since 1.0.0
601-
* @deprecated use {{@link #getProperty(Key)}
602-
*/
603-
@Deprecated
604-
public String getProperty(String key, String defaultValue) {
605-
return properties.getProperty(key, defaultValue);
606-
}
607-
608-
/**
609-
* Attempts to retrieve the key via environment variable. Property names are
610-
* always upper case with periods replaced with underscores.
611-
*
612-
* alpine.worker.threads
613-
* becomes
614-
* ALPINE_WORKER_THREADS
609+
* Reload the configuration.
615610
*
616-
* @param key the key to retrieve from environment
617-
* @return the value of the key (if set), null otherwise.
618-
* @since 1.4.3
611+
* @since 5.6.0
619612
*/
620-
private String getPropertyFromEnvironment(Key key) {
621-
final String envVariable = key.getPropertyName().toUpperCase().replace(".", "_");
622-
try {
623-
return StringUtils.trimToNull(System.getenv(envVariable));
624-
} catch (SecurityException e) {
625-
LOGGER.warn("A security exception prevented access to the environment variable. Using defaults.");
626-
} catch (NullPointerException e) {
627-
// Do nothing. The key was not specified in an environment variable. Continue along.
628-
}
629-
return null;
613+
static void reload() {
614+
delegateConfig = null;
615+
INSTANCE.init();
630616
}
631617

632618
/**

0 commit comments

Comments
 (0)