Last active
August 2, 2024 10:22
-
-
Save jhannes/b8b143e0e5b287d73038 to your computer and use it in GitHub Desktop.
Dead simple configuration
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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