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

Add avaje-aws-appconfig module #116

Merged
merged 1 commit into from
Jan 31, 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
36 changes: 36 additions & 0 deletions avaje-aws-appconfig/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.avaje</groupId>
<artifactId>java11-oss</artifactId>
<version>4.0</version>
<relativePath/>
</parent>

<artifactId>avaje-aws-appconfig</artifactId>
<version>0.1-SNAPSHOT</version>

<properties>
<surefire.useModulePath>false</surefire.useModulePath>
</properties>

<dependencies>
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-config</artifactId>
<version>3.11-SNAPSHOT</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>io.avaje</groupId>
<artifactId>junit</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.avaje.aws.appconfig;

public interface AppConfigFetcher {

static AppConfigFetcher.Builder builder() {
return new DAppConfigFetcher.Builder();
}

Result fetch() throws FetchException;

class FetchException extends Exception {

public FetchException(Exception e) {
super(e);
}
}

interface Result {

String version();

String contentType();

String body();
}

interface Builder {

AppConfigFetcher.Builder application(String application);

AppConfigFetcher.Builder environment(String environment);

AppConfigFetcher.Builder configuration(String configuration);

AppConfigFetcher.Builder port(int port);

AppConfigFetcher build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package io.avaje.aws.appconfig;

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

import java.io.IOException;
import java.io.StringReader;
import java.util.Map;
import java.util.Properties;

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

public final class AppConfigPlugin implements ConfigurationSource {

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

@Override
public void load(Configuration configuration) {
if (!configuration.getBool("aws.appconfig.enabled", true)) {
log.log(INFO, "AppConfigPlugin is not enabled");
}

var loader = new Loader(configuration);
loader.schedule();
loader.reload();
}


static final class Loader {

private final Configuration configuration;
private final AppConfigFetcher fetcher;
private final ConfigParser yamlParser;
private final long frequency;

private String currentVersion = "";

Loader(Configuration configuration) {
this.configuration = configuration;
this.frequency = configuration.getLong("aws.appconfig.frequency", 60L);
this.yamlParser = configuration.parser("yaml").orElse(null);

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

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

void schedule() {
configuration.schedule(frequency * 1000L, frequency * 1000L, this::reload);
}

void reload() {
try {
AppConfigFetcher.Result result = fetcher.fetch();
if (currentVersion.equals(result.version())) {
log.log(TRACE, "AwsAppConfig unchanged, version", 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());
}
if (contentType.endsWith("yaml")) {
if (yamlParser == null) {
log.log(ERROR, "No Yaml Parser registered to parse AWS AppConfig");
} else {
Map<String, String> keyValues = yamlParser.load(new StringReader(result.body()));
configuration.eventBuilder("AwsAppConfig")
.putAll(keyValues)
.publish();
currentVersion = result.version();
debugLog(result, keyValues.size());
}
} else {
// assuming properties content
Properties properties = new Properties();
properties.load(new StringReader(result.body()));
configuration.eventBuilder("AwsAppConfig")
.putAll(properties)
.publish();
currentVersion = result.version();
debugLog(result, properties.size());
}
}

} catch (AppConfigFetcher.FetchException | IOException e) {
log.log(ERROR, "Error fetching or processing AppConfig", e);
}
}

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
@@ -0,0 +1,85 @@
package io.avaje.aws.appconfig;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

final class DAppConfigFetcher implements AppConfigFetcher {

private final URI uri;
private final HttpClient httpClient;

DAppConfigFetcher(String uri) {
this.uri = URI.create(uri);
this.httpClient = HttpClient.newBuilder()
.build();
}

@Override
public AppConfigFetcher.Result fetch() throws FetchException {
HttpRequest request = HttpRequest.newBuilder()
.uri(uri)
.GET()
.build();

try {
HttpResponse<String> res = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
String version = res.headers().firstValue("Configuration-Version").orElse(null);
String contentType = res.headers().firstValue("Content-Type").orElse("unknown");
String body = res.body();
return new DResult(version, contentType, body);

} catch (IOException | InterruptedException e) {
throw new FetchException(e);
}
}

static class Builder implements AppConfigFetcher.Builder {

private int port = 2772;
private String application;
private String environment;
private String configuration;

@Override
public Builder application(String application) {
this.application = application;
return this;
}

@Override
public Builder environment(String environment) {
this.environment = environment;
return this;
}

@Override
public Builder configuration(String configuration) {
this.configuration = configuration;
return this;
}

@Override
public Builder port(int port) {
this.port = port;
return this;
}

@Override
public AppConfigFetcher build() {
return new DAppConfigFetcher(uri());
}

private String uri() {
if (configuration == null) {
configuration = environment + "-" + application;
}
return "http://localhost:" + port + "/applications/"
+ application + "/environments/"
+ environment + "/configurations/"
+ configuration;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.avaje.aws.appconfig;

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) {
this.version = version;
this.contentType = contentType;
this.body = body;
}

@Override
public String version() {
return version;
}

@Override
public String contentType() {
return contentType;
}

@Override
public String body() {
return body;
}
}
10 changes: 10 additions & 0 deletions avaje-aws-appconfig/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import io.avaje.aws.appconfig.AppConfigPlugin;

module io.avaje.aws.appconfig {

exports io.avaje.aws.appconfig;

requires io.avaje.config;
requires java.net.http;
provides io.avaje.config.ConfigurationSource with AppConfigPlugin;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.avaje.aws.appconfig.AppConfigPlugin
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

<modules>
<module>avaje-config</module>
<module>avaje-aws-appconfig</module>
</modules>

</project>
Expand Down
Loading