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

Extract PropertiesParser (so that it is accessible to plugins) #117

Merged
merged 5 commits into from
Feb 3, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
3 changes: 2 additions & 1 deletion avaje-aws-appconfig/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
<relativePath/>
</parent>

<artifactId>avaje-aws-appconfig</artifactId>
<groupId>io.avaje</groupId>
<artifactId>avaje-config-aws-appconfig</artifactId>
<version>0.3-SNAPSHOT</version>

<properties>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.avaje.aws.appconfig;
package io.avaje.config.awsappconfig;

public interface AppConfigFetcher {
interface AppConfigFetcher {

static AppConfigFetcher.Builder builder() {
return new DAppConfigFetcher.Builder();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package io.avaje.config.awsappconfig;

import io.avaje.config.ConfigParser;
import io.avaje.config.Configuration;
import io.avaje.config.ConfigurationSource;

import java.io.StringReader;
import java.time.Instant;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;

import static java.lang.System.Logger.Level.*;

/**
* Plugin that loads AWS AppConfig as Yaml or Properties.
* <p>
* By default, will periodically reload the configuration if it has changed.
*/
public final class AwsAppConfigPlugin implements ConfigurationSource {

private static final System.Logger log = System.getLogger("io.avaje.config.AwsAppConfig");

private Loader loader;

@Override
public void load(Configuration configuration) {
if (!configuration.enabled("aws.appconfig.enabled", true)) {
log.log(INFO, "AwsAppConfig plugin is disabled");
return;
}
loader = new Loader(configuration);
loader.reload();
}

@Override
public void refresh() {
if (loader != null) {
loader.reload();
}
}

static final class Loader {

private final Configuration configuration;
private final AppConfigFetcher fetcher;
private final ConfigParser yamlParser;
private final ConfigParser propertiesParser;
private final ReentrantLock lock = new ReentrantLock();
private final AtomicReference<Instant> validUntil;
private final long nextRefreshSeconds;

private String currentVersion = "none";

Loader(Configuration configuration) {
this.validUntil = new AtomicReference<>(Instant.now().minusSeconds(1));
this.configuration = configuration;
this.propertiesParser = configuration.parser("properties").orElseThrow();
this.yamlParser = configuration.parser("yaml").orElse(null);
if (yamlParser == null) {
log.log(WARNING, "No Yaml parser registered");
}

var app = configuration.get("aws.appconfig.application");
var env = configuration.get("aws.appconfig.environment");
var con = configuration.get("aws.appconfig.configuration", env + "-" + app);

this.fetcher = AppConfigFetcher.builder()
.application(app)
.environment(env)
.configuration(con)
.build();

boolean pollEnabled = configuration.enabled("aws.appconfig.poll.enabled", true);
long pollSeconds = configuration.getLong("aws.appconfig.poll.seconds", 45L);
this.nextRefreshSeconds = configuration.getLong("aws.appconfig.refresh.seconds", pollSeconds - 1);
if (pollEnabled) {
configuration.schedule(pollSeconds * 1000L, pollSeconds * 1000L, this::reload);
}
}

void reload() {
if (reloadRequired()) {
performReload();
}
}

private boolean reloadRequired() {
return validUntil.get().isAfter(Instant.now());
}

private void performReload() {
lock.lock();
try {
if (!reloadRequired()) {
return;
}
AppConfigFetcher.Result result = fetcher.fetch();
if (currentVersion.equals(result.version())) {
log.log(TRACE, "AwsAppConfig unchanged, version {0}", currentVersion);
} else {
String contentType = result.contentType();
if (log.isLoggable(TRACE)) {
log.log(TRACE, "AwsAppConfig fetched version:{0} contentType:{1} body:{2}", result.version(), contentType, result.body());
}
Map<String, String> keyValues = parse(result);
configuration.eventBuilder("AwsAppConfig")
.putAll(keyValues)
.publish();
currentVersion = result.version();
debugLog(result, keyValues.size());
}
// move the next valid until time
validUntil.set(Instant.now().plusSeconds(nextRefreshSeconds));

} catch (Exception e) {
log.log(ERROR, "Error fetching or processing AwsAppConfig", e);
} finally {
lock.unlock();
}
}

private Map<String, String> parse(AppConfigFetcher.Result result) {
ConfigParser parser = parser(result.contentType());
return parser.load(new StringReader(result.body()));
}

private ConfigParser parser(String contentType) {
if (contentType.endsWith("yaml")) {
return yamlParser;
} else {
return propertiesParser;
}
}

private static void debugLog(AppConfigFetcher.Result result, int size) {
if (log.isLoggable(DEBUG)) {
log.log(DEBUG, "AwsAppConfig loaded version {0} with {1} properties", result.version(), size);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.avaje.aws.appconfig;
package io.avaje.config.awsappconfig;

import java.io.IOException;
import java.net.URI;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package io.avaje.aws.appconfig;
package io.avaje.config.awsappconfig;

final class DResult implements AppConfigFetcher.Result {

private final String version;
private final String contentType;
private final String body;
public DResult(String version, String contentType, String body) {

DResult(String version, String contentType, String body) {
this.version = version;
this.contentType = contentType;
this.body = body;
Expand Down
8 changes: 4 additions & 4 deletions avaje-aws-appconfig/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import io.avaje.aws.appconfig.AppConfigPlugin;
import io.avaje.config.awsappconfig.AwsAppConfigPlugin;

module io.avaje.aws.appconfig {
module io.avaje.config.awsappconfig {

exports io.avaje.aws.appconfig;
exports io.avaje.config.awsappconfig;

requires io.avaje.config;
requires java.net.http;
provides io.avaje.config.ConfigurationSource with AppConfigPlugin;
provides io.avaje.config.ConfigurationSource with AwsAppConfigPlugin;
}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
io.avaje.aws.appconfig.AppConfigPlugin
io.avaje.config.awsappconfig.AwsAppConfigPlugin
9 changes: 9 additions & 0 deletions avaje-config/src/main/java/io/avaje/config/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,15 @@ default boolean enabled(String key, boolean enabledDefault) {
*/
void loadIntoSystemProperties();

/**
* Trigger a refresh on all {@link ConfigurationSource}.
* <p>
* Generally configuration sources will schedule a periodic refresh of their
* configuration but there are cases like Lambda where it can be useful to
* trigger a refresh explicitly (e.g. on Lambda invocation).
*/
void refresh();

/**
* Return the number of configuration properties.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,15 @@ public interface ConfigurationSource {
* @param configuration The configuration with initially properties.
*/
void load(Configuration configuration);

/**
* Explicitly refresh the configuration source.
* <p>
* Generally the configuration source will schedule a periodic refresh of its
* configuration but there are cases like Lambda where it can be useful to
* trigger a refresh explicitly and manually (e.g. on Lambda invocation).
*/
default void refresh() {
// do nothing by default
}
}
42 changes: 42 additions & 0 deletions avaje-config/src/main/java/io/avaje/config/CoreComponents.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.avaje.config;

import java.util.Collections;
import java.util.List;

final class CoreComponents {

private final ModificationEventRunner runner;
private final ConfigurationLog log;
private final Parsers parsers;
private final List<ConfigurationSource> sources;

CoreComponents(ModificationEventRunner runner, ConfigurationLog log, Parsers parsers, List<ConfigurationSource> sources) {
this.runner = runner;
this.log = log;
this.parsers = parsers;
this.sources = sources;
}

CoreComponents() {
this.runner = new CoreConfiguration.ForegroundEventRunner();
this.log = new DefaultConfigurationLog();
this.parsers = new Parsers();
this.sources = Collections.emptyList();
}

Parsers parsers() {
return parsers;
}

ConfigurationLog log() {
return log;
}

ModificationEventRunner runner() {
return runner;
}

List<ConfigurationSource> sources() {
return sources;
}
}
Loading
Loading