Skip to content

Instantly share code, notes, and snippets.

@jhannes
Last active October 19, 2016 13:17
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save jhannes/b8b143e0e5b287d73038 to your computer and use it in GitHub Desktop.
Save jhannes/b8b143e0e5b287d73038 to your computer and use it in GitHub Desktop.
Dead simple configuration
package com.johannesbrodwall.infrastructure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
import javax.sql.DataSource;
public abstract class AppConfiguration {
private static Logger log = LoggerFactory.getLogger(AppConfiguration.class);
private long nextCheckTime = 0;
private long lastLoadTime = 0;
private Properties properties = new Properties();
private final File configFile;
protected final String propertyPrefix;
private DataSource dataSource;
public AppConfiguration(String filename, String propertyPrefix) {
this.configFile = new File(filename);
this.propertyPrefix = propertyPrefix;
dataSource = new ConfiguredDataSource(this, propertyPrefix);
}
public String getProperty(String propertyName, String defaultValue) {
String result = getProperty(propertyName);
if (result == null) {
log.trace("Missing property {} in {}", propertyName, properties.keySet());
return defaultValue;
}
return result;
}
public String getRequiredProperty(String propertyName) {
String result = getProperty(propertyName);
if (result == null) {
throw new RuntimeException("Missing property " + propertyName);
}
return result;
}
private String getProperty(String propertyName) {
if (System.getProperty(propertyName) != null) {
log.trace("Reading {} from system properties", propertyName);
return System.getProperty(propertyName);
}
if (System.getenv(propertyName.replace('.', '_')) != null) {
log.trace("Reading {} from environment", propertyName);
return System.getenv(propertyName.replace('.', '_'));
}
ensureConfigurationIsFresh();
return properties.getProperty(propertyName);
}
private synchronized void ensureConfigurationIsFresh() {
if (System.currentTimeMillis() < nextCheckTime) return;
nextCheckTime = System.currentTimeMillis() + 10000;
log.trace("Rechecking {}", configFile);
if (!configFile.exists()) {
log.error("Missing configuration file {}", configFile);
}
if (lastLoadTime >= configFile.lastModified()) return;
lastLoadTime = configFile.lastModified();
log.debug("Reloading {}", configFile);
try (FileInputStream inputStream = new FileInputStream(configFile)) {
properties.clear();
properties.load(inputStream);
} catch (IOException e) {
throw new RuntimeException("Failed to load " + configFile, e);
}
}
public DataSource getDataSource() {
return dataSource;
}
public int getHttpPort(int defaultPort) {
if (System.getenv("PORT") != null) {
return Integer.parseInt(System.getenv("PORT"));
}
return getIntProperty(propertyPrefix + ".http.port", defaultPort);
}
public boolean getFlag(String property, boolean defaultValue) {
return Boolean.parseBoolean(getProperty(property, String.valueOf(defaultValue)));
}
public int getIntProperty(String propertyName, int defaultValue) {
return Integer.parseInt(getProperty(propertyName, String.valueOf(defaultValue)));
}
}
package com.johannesbrodwall.infrastructure;
import com.mchange.v2.c3p0.DriverManagerDataSource;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import javax.sql.DataSource;
class ConfiguredDataSource implements DataSource {
private DriverManagerDataSource dataSource = new DriverManagerDataSource();
private AppConfiguration config;
private final String propertyPrefix;
public ConfiguredDataSource(AppConfiguration config, String propertyPrefix) {
this.config = config;
this.propertyPrefix = propertyPrefix;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return getDataSource().getConnection(username, password);
}
@Override
public Connection getConnection() throws SQLException {
return getDataSource().getConnection();
}
private DriverManagerDataSource getDataSource() {
updateProperties();
return dataSource;
}
private void updateProperties() {
String databaseUrl = System.getenv("DATABASE_URL");
if (databaseUrl != null) {
URI dbUri = getDatabaseUri(databaseUrl);
dataSource.setUser(dbUri.getUserInfo().split(":")[0]);
dataSource.setPassword(dbUri.getUserInfo().split(":")[1]);
dataSource.setJdbcUrl(getJdbcUrlFromDbUrl(dbUri));
dataSource.setDriverClass(getJdbcDriverFromDbUrl(dbUri));
} else {
dataSource.setUser(config.getRequiredProperty(propertyPrefix + ".db.username"));
dataSource.setPassword(config.getProperty(propertyPrefix + ".db.password", dataSource.getUser()));
dataSource.setJdbcUrl(config.getRequiredProperty(propertyPrefix + ".db.url"));
dataSource.setDriverClass(config.getRequiredProperty(propertyPrefix + ".db.driverClassName"));
}
}
private URI getDatabaseUri(String databaseUrl) {
try {
return new URI(databaseUrl);
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Invalid database URL " + databaseUrl);
}
}
String getJdbcUrlFromDbUrl(URI dbUri) {
Map<String, String> formats = new HashMap<>();
formats.put("postgres", "jdbc:postgresql://%2$s:%3$d/%4$s");
formats.put("oracle", "jdbc:%1$s:thin:@%2$s:%3$d:%4$s");
String format = formats.get(dbUri.getScheme());
if (format == null && dbUri.getPort() == -1) {
format = "jdbc:%1$s://%2$s/%4$s";
} else if (format == null) {
format = "jdbc:%1$s://%2$s:%3$d/%4$s";
}
return String.format(format,
dbUri.getScheme(), dbUri.getHost(), dbUri.getPort(), dbUri.getPath().substring(1));
}
String getJdbcDriverFromDbUrl(URI dbUri) {
Map<String, String> drivers = new HashMap<>();
drivers.put("postgres", "org.postgresql.Driver");
drivers.put("oracle", "oracle.jdbc.OracleDriver");
drivers.put("mysql", "com.mysql.jdbc.Driver");
return drivers.getOrDefault(dbUri.getScheme(), "java.sql.Driver");
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return getDataSource().unwrap(iface);
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return getDataSource().isWrapperFor(iface);
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
getDataSource().setLoginTimeout(seconds);
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
getDataSource().setLogWriter(out);
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return getDataSource().getParentLogger();
}
@Override
public int getLoginTimeout() throws SQLException {
return getDataSource().getLoginTimeout();
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return getDataSource().getLogWriter();
}
}
package com.johannesbrodwall;
import com.johannesbrodwall.infrastructure.AppConfiguration;
public class MyAppConfig extends AppConfiguration {
private MyAppConfig() {
this("myapp.properties");
}
public MyAppConfig(String propertyFile) {
super(propertyFile, "myapp");
}
private static MyAppConfig instance = new MyAppConfig();
public String getServiceHost() {
return getRequiredProperty("service.host");
}
public boolean getShouldStartSlow() {
return getFlag("start-slow", false);
}
public static MyAppConfig instance() {
return instance;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment