/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: MIT-0
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.amazon.sample.appconfig.quarkus.config.source;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.eclipse.microprofile.config.spi.ConfigSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class AwsAppConfigSource implements ConfigSource, AutoCloseable {
private static final Logger LOGGER = LoggerFactory.getLogger(AwsAppConfigSource.class);
/**
* Quarkus uses 300 for ENV variables and 250 for application.properties.
*
*
We want to be higher precedence than the packaged application.properties but not higher than
* any supplied ENV variables.
*/
private static final int ORDINAL = 275;
private ConcurrentHashMap props;
private final ObjectMapper mapper;
private final TypeReference> typeRef;
private AppConfigDataClient dataClient;
private String token = null;
private ScheduledExecutorService execSvc;
public AwsAppConfigSource() {
props = new ConcurrentHashMap<>();
mapper = new ObjectMapper();
typeRef = new TypeReference<>() {};
try {
dataClient = AppConfigDataClient.builder().build();
} catch (Exception ex) {
// Quarkus plugin runs this code path if you run ./mvnw package.
// Hence we need to account for the probability the no AWS credentials can be obtained
LOGGER.error(
"AWS AppConfig client creation failed. This sample will not work. "
+ "Check the nested exception for details and reconfigure your environment",
ex);
return;
}
execSvc = new ScheduledThreadPoolExecutor(1);
token = startConfigurationSession();
LOGGER.info("fetched initialConfigToken");
fetchConfig();
LOGGER.info("initialized with AppConfig provided interestRate = {}", props.get("interestRate"));
// we poll in 30 seconds intervals for demonstration purposes
execSvc.scheduleAtFixedRate(this::fetchConfig, 30, 30, TimeUnit.SECONDS);
}
private String startConfigurationSession() {
var applicationName = "ConfigSourceDemo";
var environmentName = "Sandbox";
var configProfileName = "json-profile";
var res =
dataClient.startConfigurationSession(
req -> {
req.applicationIdentifier(applicationName);
req.environmentIdentifier(environmentName);
req.configurationProfileIdentifier(configProfileName);
});
return res.initialConfigurationToken();
}
private void fetchConfig() {
var res = dataClient.getLatestConfiguration(req -> req.configurationToken(token));
try {
if (res.configuration() == null) {
LOGGER.info("unexpected null value for configuration. aborting config update");
return;
}
var configString = res.configuration().asUtf8String();
if (configString.isEmpty()) {
LOGGER.info("No changes on the configuration received");
return;
}
props = mapper.readValue(configString, typeRef);
token = res.nextPollConfigurationToken();
LOGGER.debug(
"now using config version = {}\nand interestRate = {}", token, props.get("interestRate"));
} catch (Exception ex) {
LOGGER.error("unexpected failure in reading the config", ex);
}
}
@Override
public Map getProperties() {
return props;
}
@Override
public Set getPropertyNames() {
return props.keySet();
}
@Override
public int getOrdinal() {
return ORDINAL;
}
@Override
public String getValue(String s) {
return props.get(s);
}
@Override
public String getName() {
return "AwsAppConfigSource";
}
@Override
public void close() throws Exception {
execSvc.shutdown();
}
}