Skip to content

Commit eb260eb

Browse files
committed
Add avaje-aws-appconfig module
1 parent 098b1bd commit eb260eb

File tree

8 files changed

+304
-0
lines changed

8 files changed

+304
-0
lines changed

avaje-aws-appconfig/pom.xml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>org.avaje</groupId>
8+
<artifactId>java11-oss</artifactId>
9+
<version>4.0</version>
10+
<relativePath/>
11+
</parent>
12+
13+
<artifactId>avaje-aws-appconfig</artifactId>
14+
<version>0.1-SNAPSHOT</version>
15+
16+
<properties>
17+
<surefire.useModulePath>false</surefire.useModulePath>
18+
</properties>
19+
20+
<dependencies>
21+
<dependency>
22+
<groupId>io.avaje</groupId>
23+
<artifactId>avaje-config</artifactId>
24+
<version>3.11-SNAPSHOT</version>
25+
<scope>provided</scope>
26+
</dependency>
27+
28+
<dependency>
29+
<groupId>io.avaje</groupId>
30+
<artifactId>junit</artifactId>
31+
<version>1.3</version>
32+
<scope>test</scope>
33+
</dependency>
34+
</dependencies>
35+
36+
</project>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package io.avaje.aws.appconfig;
2+
3+
public interface AppConfigFetcher {
4+
5+
static AppConfigFetcher.Builder builder() {
6+
return new DAppConfigFetcher.Builder();
7+
}
8+
9+
Result fetch() throws FetchException;
10+
11+
class FetchException extends Exception {
12+
13+
public FetchException(Exception e) {
14+
super(e);
15+
}
16+
}
17+
18+
interface Result {
19+
20+
String version();
21+
22+
String contentType();
23+
24+
String body();
25+
}
26+
27+
interface Builder {
28+
29+
AppConfigFetcher.Builder application(String application);
30+
31+
AppConfigFetcher.Builder environment(String environment);
32+
33+
AppConfigFetcher.Builder configuration(String configuration);
34+
35+
AppConfigFetcher.Builder port(int port);
36+
37+
AppConfigFetcher build();
38+
}
39+
40+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package io.avaje.aws.appconfig;
2+
3+
import io.avaje.config.ConfigParser;
4+
import io.avaje.config.Configuration;
5+
import io.avaje.config.ConfigurationSource;
6+
7+
import java.io.IOException;
8+
import java.io.StringReader;
9+
import java.util.Map;
10+
import java.util.Properties;
11+
12+
import static java.lang.System.Logger.Level.*;
13+
14+
public final class AppConfigPlugin implements ConfigurationSource {
15+
16+
private static final System.Logger log = System.getLogger("io.avaje.config.AppConfigPlugin");
17+
18+
@Override
19+
public void load(Configuration configuration) {
20+
if (!configuration.getBool("aws.appconfig.enabled", true)) {
21+
log.log(INFO, "AppConfigPlugin is not enabled");
22+
}
23+
24+
var loader = new Loader(configuration);
25+
loader.schedule();
26+
loader.reload();
27+
}
28+
29+
30+
static final class Loader {
31+
32+
private final Configuration configuration;
33+
private final AppConfigFetcher fetcher;
34+
private final ConfigParser yamlParser;
35+
private final long frequency;
36+
37+
private String currentVersion = "";
38+
39+
Loader(Configuration configuration) {
40+
this.configuration = configuration;
41+
this.frequency = configuration.getLong("aws.appconfig.frequency", 60L);
42+
this.yamlParser = configuration.parser("yaml").orElse(null);
43+
44+
var app = configuration.get("aws.appconfig.application");
45+
var env = configuration.get("aws.appconfig.environment");
46+
var con = configuration.get("aws.appconfig.configuration");
47+
48+
this.fetcher = AppConfigFetcher.builder()
49+
.application(app)
50+
.environment(env)
51+
.configuration(con)
52+
.build();
53+
}
54+
55+
void schedule() {
56+
configuration.schedule(frequency * 1000L, frequency * 1000L, this::reload);
57+
}
58+
59+
void reload() {
60+
try {
61+
AppConfigFetcher.Result result = fetcher.fetch();
62+
if (currentVersion.equals(result.version())) {
63+
log.log(TRACE, "AwsAppConfig unchanged, version", currentVersion);
64+
} else {
65+
String contentType = result.contentType();
66+
if (log.isLoggable(TRACE)) {
67+
log.log(TRACE, "AwsAppConfig fetched version:{0} contentType:{1} body:{2}", result.version(), contentType, result.body());
68+
}
69+
if (contentType.endsWith("yaml")) {
70+
if (yamlParser == null) {
71+
log.log(ERROR, "No Yaml Parser registered to parse AWS AppConfig");
72+
} else {
73+
Map<String, String> keyValues = yamlParser.load(new StringReader(result.body()));
74+
configuration.eventBuilder("AwsAppConfig")
75+
.putAll(keyValues)
76+
.publish();
77+
currentVersion = result.version();
78+
debugLog(result, keyValues.size());
79+
}
80+
} else {
81+
// assuming properties content
82+
Properties properties = new Properties();
83+
properties.load(new StringReader(result.body()));
84+
configuration.eventBuilder("AwsAppConfig")
85+
.putAll(properties)
86+
.publish();
87+
currentVersion = result.version();
88+
debugLog(result, properties.size());
89+
}
90+
}
91+
92+
} catch (AppConfigFetcher.FetchException | IOException e) {
93+
log.log(ERROR, "Error fetching or processing AppConfig", e);
94+
}
95+
}
96+
97+
private static void debugLog(AppConfigFetcher.Result result, int size) {
98+
if (log.isLoggable(DEBUG)) {
99+
log.log(DEBUG, "AwsAppConfig loaded version {0} with {1} properties", result.version(), size);
100+
}
101+
}
102+
}
103+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package io.avaje.aws.appconfig;
2+
3+
import java.io.IOException;
4+
import java.net.URI;
5+
import java.net.http.HttpClient;
6+
import java.net.http.HttpRequest;
7+
import java.net.http.HttpResponse;
8+
9+
final class DAppConfigFetcher implements AppConfigFetcher {
10+
11+
private final URI uri;
12+
private final HttpClient httpClient;
13+
14+
DAppConfigFetcher(String uri) {
15+
this.uri = URI.create(uri);
16+
this.httpClient = HttpClient.newBuilder()
17+
.build();
18+
}
19+
20+
@Override
21+
public AppConfigFetcher.Result fetch() throws FetchException {
22+
HttpRequest request = HttpRequest.newBuilder()
23+
.uri(uri)
24+
.GET()
25+
.build();
26+
27+
try {
28+
HttpResponse<String> res = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
29+
String version = res.headers().firstValue("Configuration-Version").orElse(null);
30+
String contentType = res.headers().firstValue("Content-Type").orElse("unknown");
31+
String body = res.body();
32+
return new DResult(version, contentType, body);
33+
34+
} catch (IOException | InterruptedException e) {
35+
throw new FetchException(e);
36+
}
37+
}
38+
39+
static class Builder implements AppConfigFetcher.Builder {
40+
41+
private int port = 2772;
42+
private String application;
43+
private String environment;
44+
private String configuration;
45+
46+
@Override
47+
public Builder application(String application) {
48+
this.application = application;
49+
return this;
50+
}
51+
52+
@Override
53+
public Builder environment(String environment) {
54+
this.environment = environment;
55+
return this;
56+
}
57+
58+
@Override
59+
public Builder configuration(String configuration) {
60+
this.configuration = configuration;
61+
return this;
62+
}
63+
64+
@Override
65+
public Builder port(int port) {
66+
this.port = port;
67+
return this;
68+
}
69+
70+
@Override
71+
public AppConfigFetcher build() {
72+
return new DAppConfigFetcher(uri());
73+
}
74+
75+
private String uri() {
76+
if (configuration == null) {
77+
configuration = environment + "-" + application;
78+
}
79+
return "http://localhost:" + port + "/applications/"
80+
+ application + "/environments/"
81+
+ environment + "/configurations/"
82+
+ configuration;
83+
}
84+
}
85+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package io.avaje.aws.appconfig;
2+
3+
final class DResult implements AppConfigFetcher.Result {
4+
5+
private final String version;
6+
private final String contentType;
7+
private final String body;
8+
public DResult(String version, String contentType, String body) {
9+
this.version = version;
10+
this.contentType = contentType;
11+
this.body = body;
12+
}
13+
14+
@Override
15+
public String version() {
16+
return version;
17+
}
18+
19+
@Override
20+
public String contentType() {
21+
return contentType;
22+
}
23+
24+
@Override
25+
public String body() {
26+
return body;
27+
}
28+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import io.avaje.aws.appconfig.AppConfigPlugin;
2+
3+
module io.avaje.aws.appconfig {
4+
5+
exports io.avaje.aws.appconfig;
6+
7+
requires io.avaje.config;
8+
requires java.net.http;
9+
provides io.avaje.config.ConfigurationSource with AppConfigPlugin;
10+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
io.avaje.aws.appconfig.AppConfigPlugin

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
<modules>
1717
<module>avaje-config</module>
18+
<module>avaje-aws-appconfig</module>
1819
</modules>
1920

2021
</project>

0 commit comments

Comments
 (0)