Skip to content

Commit

Permalink
Merge pull request #90 from avaje/feature/putAll-wildcard
Browse files Browse the repository at this point in the history
Add Configuration.Builder for manual construction
  • Loading branch information
rbygrave authored Sep 28, 2023
2 parents abeafff + f4ef01e commit 8fd6bff
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 28 deletions.
2 changes: 1 addition & 1 deletion avaje-config/src/main/java/io/avaje/config/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
*
* String topicName = Config.get("app.topic.name");
*
* List<Integer> codes = Config.getList().ofInt("my.codes", 42, 54);
* List<Integer> codes = Config.list().ofInt("my.codes", 42, 54);
*
* }</pre>
*/
Expand Down
68 changes: 65 additions & 3 deletions avaje-config/src/main/java/io/avaje/config/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
*
* String topicName = Config.get("app.topic.name");
*
* List<Integer> codes = Config.getList().ofInt("my.codes", 42, 54);
* List<Integer> codes = Config.list().ofInt("my.codes", 42, 54);
*
* }</pre>
*/
Expand Down Expand Up @@ -286,7 +286,7 @@ public interface Configuration {
*
* <pre>{@code
*
* List<Integer> codes = Config.getList().ofInt("my.codes", 97, 45);
* List<Integer> codes = Config.list().ofInt("my.codes", 97, 45);
*
* }</pre>
*/
Expand Down Expand Up @@ -485,7 +485,7 @@ interface ExpressionEval {
* <h3>Example</h3>
* <pre>{@code
*
* List<Integer> codes = Config.getList().ofInt("my.codes", 42, 54);
* List<Integer> codes = Config.list().ofInt("my.codes", 42, 54);
*
* }</pre>
*/
Expand Down Expand Up @@ -632,4 +632,66 @@ interface SetValue {
*/
<T> Set<T> ofType(String key, Function<String, T> mappingFunction);
}

/**
* Return a Builder for Configuration that is loaded manually (not via the normal resource loading).
*/
static Builder builder() {
return new CoreConfigurationBuilder();
}

/**
* Build Configuration manually explicitly loading all the configuration as key value pairs.
* <p>
* Building configuration this way does NOT automatically load resources like application.properties
* and also does NOT load ConfigurationSource. ALL configuration is explicitly loaded via calls
* to {@link Builder#put(String, String)}, {@link Builder#putAll(Map)}.
*/
interface Builder {

/**
* Put an entry into the configuration.
*/
Builder put(String key, String value);

/**
* Put entries into the configuration.
*/
Builder putAll(Map<String, ?> sourceMap);

/**
* Put entries into the configuration from properties.
*/
Builder putAll(Properties source);

/**
* Optionally set the event runner to use . If not specified a foreground runner will be used.
*/
Builder eventRunner(ModificationEventRunner eventRunner);

/**
* Optionally set the log to use. If not specified then a logger using System.Logger will be used.
*/
Builder log(ConfigurationLog log);

/**
* Optionally set the resource loader to use. If not specified then class path based resource loader is used.
*/
Builder resourceLoader(ResourceLoader resourceLoader);

/**
* Specify to include standard resource loading.
* <p>
* This includes the loading of application.properties, application.yaml etc.
*/
Builder includeResourceLoading();

/**
* Build and return the Configuration.
* <p>
* Performs evaluation of property values that contain expressions (e.g. {@code ${user.home}})
* and returns the configuration.
*/
Configuration build();
}
}
26 changes: 15 additions & 11 deletions avaje-config/src/main/java/io/avaje/config/CoreConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,22 @@ final class CoreConfiguration implements Configuration {
* Initialise the configuration which loads all the property sources.
*/
static Configuration initialise() {
final var runner = ServiceLoader.load(ModificationEventRunner.class).findFirst().orElseGet(ForegroundEventRunner::new);
final var log = ServiceLoader.load(ConfigurationLog.class).findFirst().orElseGet(DefaultConfigurationLog::new);
log.preInitialisation();
final var resourceLoader = ServiceLoader.load(ResourceLoader.class).findFirst().orElseGet(DefaultResourceLoader::new);
final var loader = new InitialLoader(log, resourceLoader);
final CoreConfiguration configuration = new CoreConfiguration(runner, log, loader.load());
configuration.loadSources(loader.loadedFrom());
loader.initWatcher(configuration);
configuration.initSystemProperties();
configuration.logMessage(loader);
return new CoreConfigurationBuilder()
.includeResourceLoading()
.build();
}

CoreConfiguration postLoad(@Nullable InitialLoader loader) {
if (loader != null) {
loadSources(loader.loadedFrom());
loader.initWatcher(this);
}
initSystemProperties();
if (loader != null) {
logMessage(loader);
}
log.postInitialisation();
return configuration;
return this;
}

ConfigurationLog log() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package io.avaje.config;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.ServiceLoader;

import static java.util.Objects.requireNonNull;

final class CoreConfigurationBuilder implements Configuration.Builder {

private final Map<String, String> sourceMap = new LinkedHashMap<>();
private ModificationEventRunner eventRunner;
private ConfigurationLog configurationLog;
private ResourceLoader resourceLoader;

private boolean includeResourceLoading;
private InitialLoader initialLoader;

@Override
public Configuration.Builder eventRunner(ModificationEventRunner eventRunner) {
this.eventRunner = eventRunner;
return this;
}

@Override
public Configuration.Builder log(ConfigurationLog configurationLog) {
this.configurationLog = configurationLog;
return this;
}

@Override
public Configuration.Builder resourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
return this;
}

@Override
public Configuration.Builder put(String key, String value) {
requireNonNull(key);
requireNonNull(value);
sourceMap.put(key, value);
return this;
}

@Override
public Configuration.Builder putAll(Map<String, ?> source) {
requireNonNull(source);
source.forEach((key, value) -> {
if (key != null && value != null) {
put(key, value.toString());
}
});
return this;
}

@Override
public Configuration.Builder putAll(Properties source) {
requireNonNull(source);
source.forEach((key, value) -> {
if (key != null && value != null) {
put(key.toString(), value.toString());
}
});
return this;
}

@Override
public Configuration.Builder includeResourceLoading() {
this.includeResourceLoading = true;
return this;
}

@Override
public Configuration build() {
final var runner = initRunner();
final var log = initLog();
if (includeResourceLoading) {
log.preInitialisation();
initialLoader = new InitialLoader(log, initResourceLoader());
}
return new CoreConfiguration(runner, log, initEntries()).postLoad(initialLoader);
}

private CoreEntry.CoreMap initEntries() {
final var entries = initEntryMap();
sourceMap.forEach((key, value) -> entries.put(key, value, "initial"));
return CoreExpressionEval.evalFor(entries);
}

private CoreEntry.CoreMap initEntryMap() {
return initialLoader == null ? CoreEntry.newMap() : initialLoader.load();
}

private ResourceLoader initResourceLoader() {
if (resourceLoader == null) {
resourceLoader = ServiceLoader.load(ResourceLoader.class)
.findFirst()
.orElseGet(DefaultResourceLoader::new);
}
return resourceLoader;
}

private ConfigurationLog initLog() {
if (configurationLog == null) {
configurationLog = ServiceLoader.load(ConfigurationLog.class)
.findFirst()
.orElseGet(DefaultConfigurationLog::new);
}
return configurationLog;
}

private ModificationEventRunner initRunner() {
if (eventRunner == null) {
eventRunner = ServiceLoader.load(ModificationEventRunner.class)
.findFirst()
.orElseGet(CoreConfiguration.ForegroundEventRunner::new);
}
return eventRunner;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ void put(String key, String val, String source) {
/**
* Evaluate all the expressions and return as a Properties object.
*/
CoreMap evalAll() {
CoreMap entryMap() {
log.log(Level.TRACE, "load from {0}", loadedResources);
return CoreExpressionEval.evalFor(map);
return map;
}

/**
Expand Down
6 changes: 3 additions & 3 deletions avaje-config/src/main/java/io/avaje/config/InitialLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ Set<String> loadedFrom() {
CoreMap load() {
loadEnvironmentVars();
loadLocalFiles();
return eval();
return entryMap();
}

void initWatcher(CoreConfiguration configuration) {
Expand Down Expand Up @@ -279,8 +279,8 @@ boolean loadWithExtensionCheck(String fileName) {
/**
* Evaluate all the configuration entries and return as properties.
*/
CoreMap eval() {
return loadContext.evalAll();
CoreMap entryMap() {
return loadContext.entryMap();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ class CoreConfigurationTest {
private final CoreConfiguration data = createSample();

private CoreMap basicProperties() {
return CoreEntry.newMap(properties(), "test");
}

private static Properties properties() {
Properties properties = new Properties();
properties.setProperty("a", "1");
properties.setProperty("foo.bar", "42");
Expand All @@ -27,8 +31,8 @@ private CoreMap basicProperties() {
properties.setProperty("someValues", "13,42,55");
properties.setProperty("1someValues", "13,42,55");
properties.setProperty("myEnum", "TWO");

return CoreEntry.newMap(properties, "test");
properties.setProperty("myHome", "my/${user.home}/home");
return properties;
}

private CoreConfiguration createSample() {
Expand Down Expand Up @@ -122,6 +126,42 @@ void toEnvKey() {
assertThat(CoreConfiguration.toEnvKey("BAR")).isEqualTo("BAR");
}

@Test
void builder() {
var conf = Configuration.builder()
.putAll(properties())
.putAll(Map.of("myExtraMap", "foo", "myExtraMap.b", "bar"))
.put("myExtraOne", "baz")
.build();

assertEquals(conf.get("a"), "1");
assertEquals(conf.get("doesNotExist", "something"), "something");
assertEquals(conf.get("myExtraMap"), "foo");
assertEquals(conf.get("myExtraMap.b"), "bar");
assertEquals(conf.get("myExtraOne"), "baz");

String userHome = System.getProperty("user.home");
assertEquals(conf.get("myHome"), "my/" + userHome + "/home");
}

@Test
void builder_withResources() {
var conf = Configuration.builder()
.putAll(properties())
.putAll(Map.of("myExtraMap", "foo", "myExtraMap.b", "bar"))
.put("myExtraOne", "baz")
.includeResourceLoading()
.build();

// loaded from application-test.yaml
assertThat(conf.get("myapp.activateFoo")).isEqualTo("true");
// loaded explicitly
assertThat(conf.get("a")).isEqualTo( "1");

String userHome = System.getProperty("user.home");
assertThat(conf.get("myHome")).isEqualTo("my/" + userHome + "/home");
}

@Test
void get() {
assertEquals(data.get("a", "something"), "1");
Expand Down Expand Up @@ -255,6 +295,7 @@ void onChangePutAll() {
@Test
void onChangeNew() {
// we will remove this entry
System.clearProperty("foo.bar");
assertThat(data.getOptional("foo.bar")).contains("42");

final List<ModificationEvent> capturedEvents = new ArrayList<>();
Expand Down
Loading

0 comments on commit 8fd6bff

Please sign in to comment.