com.appcrossings.config.ConfigClient Maven / Gradle / Ivy
package com.appcrossings.config;
import java.net.URI;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicReference;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.EnvironmentStringPBEConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.appcrossings.config.discovery.ConfigDiscoveryStrategy;
import com.appcrossings.config.discovery.DefaultMergeStrategy;
import com.appcrossings.config.discovery.HostsFileDiscoveryStrategy;
import com.appcrossings.config.exception.InitializationException;
import com.appcrossings.config.processor.PropertiesProcessor;
import com.appcrossings.config.source.ConfigSource;
import com.appcrossings.config.util.CfgrdURI;
import com.appcrossings.config.util.StringUtils;
import com.appcrossings.config.util.UriUtil;
/**
*
* @author Krzysztof Karski
*
*/
public class ConfigClient implements Config {
public enum Method {
/**
* Fully qualified URI to the properites location
*/
ABSOLUTE_URI,
/**
* A cfgrd:// uri format pointing at a repo name and relative path with the repo
*/
CONFIGRD_URI,
/**
* Lookup properties location via hosts file
*/
HOST_FILE,
/**
* Lookup properties location via configrd server
*/
REDIRECT;
}
private class ReloadTask extends TimerTask {
@Override
public void run() {
try {
init();
} catch (Exception e) {
logger.error("Error refreshing configs", e);
}
}
}
private final static Logger logger = LoggerFactory.getLogger(ConfigClient.class);
private StandardPBEStringEncryptor encryptor = null;
public final Environment environment = new Environment();
private final AtomicReference loadedProperties =
new AtomicReference<>(new Properties());
private ConfigDiscoveryStrategy lookupStrategy = new HostsFileDiscoveryStrategy();
private final Method method;
protected final String REPO_DEF_PATH = "classpath:repo-defaults.yml";
protected String repoDefLocation;
protected final ConfigSourceResolver sourceResolver;
protected final URI startLocation;
private AtomicReference timer = new AtomicReference();
protected Integer timerTTL = 0;
/**
*
* @param uri - The path of the starting point for config exploration. Can be a default.properties
* or hosts.properties path
* @param refresh - The period in seconds at which the config properties should be refreshed. 0
* indicates no automated timer
* @throws Exception
*/
public ConfigClient(String uri) {
assert StringUtils.hasText(uri) : "Host or properties file path null or empty";
this.startLocation = URI.create(uri);
this.method = Method.ABSOLUTE_URI;
this.sourceResolver = new ConfigSourceResolver(REPO_DEF_PATH);
}
/**
*
* @param uri The path of the starting point for config exploration. Can be a default.properties
* or hosts.properties path
* @throws Exception
*/
public ConfigClient(String repoDefPath, String uri, Method method) {
assert StringUtils.hasText(repoDefPath) : "repo.def.path is null or empty";
assert StringUtils.hasText(uri) : "Host or properties file path null or empty";
assert method != null : "Method must be specified";
this.repoDefLocation = repoDefPath;
this.startLocation = URI.create(uri);
this.method = method;
this.sourceResolver = new ConfigSourceResolver(this.repoDefLocation);
}
public Environment getEnvironment() {
return environment;
}
public Properties getProperties() {
Properties props = new Properties();
props.putAll(loadedProperties.get());
return props;
}
public T getProperty(String key, Class clazz) {
String value = loadedProperties.get().getProperty(key);
if (StringUtils.hasText(value)) {
return StringUtils.cast(value, clazz);
}
return null;
}
public T getProperty(String key, Class clazz, T value) {
T val = getProperty(key, clazz);
if (val != null && val != "")
return val;
return value;
}
public ConfigSourceResolver getSourceResolver() {
return sourceResolver;
}
public void init() {
Optional startPath = Optional.empty();
if (this.method.equals(Method.ABSOLUTE_URI)) {
if (UriUtil.validate(startLocation).isAbsolute().invalid()) {
throw new IllegalArgumentException("Uri must be an absolute URI to the config location.");
}
startPath = Optional.of(this.startLocation);
} else {
/*
* Any of the below resolvers can return either an absolute URI or a cfgrd URI therefore a
* repo configuration is required. They SHOULD NOT return another redirect URI but there is
* nothing preventing it
*/
if (this.method.equals(Method.HOST_FILE)) {
startPath = resolveConfigPathFromHostFile(startLocation);
} else if (this.method.equals(Method.REDIRECT)) {
startPath = resolveConfigPathFromConfigrd(startLocation);
} else if (this.method.equals(Method.CONFIGRD_URI)) {
startPath = Optional.of(this.startLocation);
}
}
Optional configSource = Optional.empty();
if (startPath.isPresent()) {
configSource = resolveConfigSource(startPath.get());
} else {
logger.error(
"Unable to locate a config properties path using search location " + this.startLocation);
throw new InitializationException(
"Unable to locate a config properties path using search location " + this.startLocation);
}
final MergeStrategy merge = new DefaultMergeStrategy();
if (configSource.isPresent()) {
final String path = UriUtil.getPath(startPath.get());
Map p = configSource.get().get(path, new HashSet());
if (p.isEmpty()) {
logger.warn("Config location " + startPath.get()
+ " return an empty set of properties. Please check the location");
}
merge.addConfig(p);
} else {
logger.error("Unable to locate a config source for search location " + this.startLocation
+ " and config path " + startPath.get());
throw new InitializationException("Unable to locate a config source for search location "
+ this.startLocation + " and config path " + startPath.get());
}
merge.addConfig((Map) environment.getEnvironment());
Map merged = merge.merge();
if (merged.isEmpty()) {
logger.warn("Properties collection returned empty per search location " + this.startLocation
+ " and config path " + startPath.get()
+ ". If this is unexpected, please check your configuration.");
} else {
loadedProperties.set(PropertiesProcessor.asProperties(new StringUtils(merged).filled()));
}
logger.info("ConfigClient initialized.");
}
protected Optional resolveConfigPathFromConfigrd(URI serverPath) {
return Optional.empty();
}
protected Optional resolveConfigPathFromHostFile(URI hostFilePath) {
Map hosts = new HashMap<>();
logger.info("Loading hosts file at " + startLocation);
Optional startPath = Optional.empty();
Optional source = this.sourceResolver.buildAdHocConfigSource(hostFilePath);
if (source.isPresent()) {
String path = UriUtil.getPath(hostFilePath);
hosts = source.get().getRaw(path);
startPath = lookupStrategy.lookupConfigPath(hosts, (Map) environment.getEnvironment());
}
return startPath;
}
protected Optional resolveConfigSource(URI startPath) {
Optional cs = Optional.empty();
if (CfgrdURI.isCfgrdURI(startPath)) {
CfgrdURI cfgrd = new CfgrdURI(startPath);
cs = this.sourceResolver.findByRepoName(cfgrd.getRepoName());
} else {
cs = this.sourceResolver.buildAdHocConfigSource(startPath);
}
return cs;
}
/**
* Set password on the encryptor. If an encryptor isn't configured, a BasicTextEncryptor will be
* initialized and the password set on it. The basic assumed encryption algorithm is
* PBEWithMD5AndDES. This can be changed by setting the StandardPBEStringEncryptor.
*
* @param password
*/
public void setPassword(String password) {
this.encryptor = new StandardPBEStringEncryptor();
EnvironmentStringPBEConfig configurationEncryptor = new EnvironmentStringPBEConfig();
configurationEncryptor.setAlgorithm("PBEWithMD5AndDES");
configurationEncryptor.setPassword(password);
encryptor.setConfig(configurationEncryptor);
}
protected void setRefreshRate(Integer refresh) {
this.timerTTL = refresh;
if (timer.get() != null && (refresh == 0L || refresh == null)) {
timer.get().cancel();
return;
} else if (refresh > 0) {
Timer t2 = new Timer(true);
t2.schedule(new ReloadTask(), refresh * 1000, refresh * 1000);
Timer t1 = timer.getAndSet(t2);
if (t1 != null)
t1.cancel();
}
}
/**
* Override default text encryptor (StandardPBEStringEncryptor). Enables overriding both password
* and algorithm.
*
* @param encryptor
*/
public void setTextEncryptor(StandardPBEStringEncryptor config) {
this.encryptor = config;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy