Skip to content

Instantly share code, notes, and snippets.

@thomas-p-wilson
Created December 8, 2018 01:46
Show Gist options
  • Save thomas-p-wilson/35a71c4eb9bd351066edcc877176b59e to your computer and use it in GitHub Desktop.
Save thomas-p-wilson/35a71c4eb9bd351066edcc877176b59e to your computer and use it in GitHub Desktop.
Concept for extensible configuration in Java 8+

The base configuration provides some helpful facilities for retrieving configuration from environment variables, system properties, and configuration files.

The configuration to be used should be an interface of the same format (ExtendedConfiguration in this case), and extend all other required configurations.

System.setProperty("CONFIGURATION_CLASS", "ExtendedConfiguration");

final Configuration configuration = Configuration.getInstance();
System.out.println("Is instanceof Configuration? " + (configuration instanceof Configuration)); // true
System.out.println("Is instanceof ExtendedConfiguration? " + (configuration instanceof ExtendedConfiguration)); // true
import java.io.IOException;
import java.io.InputStream;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Properties;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
interface Configuration {
Logger LOGGER = LoggerFactory.getLogger(Configuration.class);
enum Name implements NameEnumeration {
CONFIGURATION_CLASS,
CONFIGURATION_FILE,
DB_DRIVER_CLASS,
DB_DRIVER_URL,
DB_DRIVER_USER,
DB_DRIVER_PASS;
}
default Class dbDriver() {
return Name.DB_DRIVER_CLASS.as(Class.class);
}
default String dbDriverUrl() {
return Name.DB_DRIVER_URL.as(String.class);
}
default String dbDriverUser() {
return Name.DB_DRIVER_USER.as(String.class);
}
default String dbDriverPass() {
return Name.DB_DRIVER_PASS.as(String.class);
}
static Configuration getInstance() {
return Builder.getInstance();
}
@SuppressWarnings("unchecked")
interface NameEnumeration {
default <T> T as(final Class<T> as) {
final String raw = getString();
if (as.isArray()) {
final String[] arr = raw.split(",");
return ConversionUtils.stringArrayToTypeArray(arr, as);
}
return ConversionUtils.stringToType(raw, as);
}
default String getString() {
if (!"".equals(System.getProperty(this.toString(), ""))) {
return System.getProperty(this.toString());
}
if (System.getenv(this.toString()) != null) {
return System.getenv(this.toString());
}
if (Builder.defaults != null && Builder.defaults.containsKey(this.toString())) {
return Builder.defaults.getProperty(this.toString());
}
return null;
}
}
final class Builder {
private static Configuration instance = null;
private static Properties defaults = null;
private Builder() {}
static Configuration getInstance() {
if (instance != null) {
return instance;
}
final Class<?> configClass;
if (Name.CONFIGURATION_CLASS.as(Class.class) != null) {
configClass = Name.CONFIGURATION_CLASS.as(Class.class);
} else {
configClass = Configuration.class;
}
// Build the configuration instance
instance = (Configuration) proxy(configClass);
// Get the configuration defaults
final String configurationFile = StringUtils.defaultIfBlank(
Name.CONFIGURATION_FILE.as(String.class),
"/defaults.properties"
);
try (InputStream is = Configuration.class.getResourceAsStream(configurationFile)) {
defaults = new Properties();
defaults.load(is);
} catch (IOException ex) {
LOGGER.error(ex.getMessage(), ex);
throw new RuntimeException(ex);
}
return instance;
}
@SuppressWarnings("unchecked")
private static <T> T proxy(final Class<T> type) {
return (T) java.lang.reflect.Proxy.newProxyInstance(
Configuration.class.getClassLoader(),
new Class[]{type},
(proxy, method, args) -> {
if (JavaUtils.getMajorVersion() == JavaUtils.VERSION_9) {
return call(MethodHandles.lookup(), method, type, proxy, args);
}
try {
Constructor<MethodHandles.Lookup> constructor
= MethodHandles.Lookup.class.getDeclaredConstructor(Class.class);
constructor.setAccessible(true);
constructor.newInstance(type);
call(constructor.newInstance(type), method, type, proxy, args);
} catch (Throwable ex) {
LOGGER.error(ex.getMessage(), ex);
}
try {
call(MethodHandles.privateLookupIn(
type, MethodHandles.lookup()), method, type, proxy, args);
} catch (Throwable ex) {
LOGGER.error(ex.getMessage(), ex);
}
throw new IllegalStateException("Does not support Java " + JavaUtils.getMajorVersion());
}
);
}
private static Object call(final MethodHandles.Lookup lookup,
final Method method,
final Class<?> type,
final Object proxy,
final Object[] args)
throws Throwable {
if (JavaUtils.getMajorVersion() == JavaUtils.VERSION_8) {
return lookup.findSpecial(
type, method.getName(), MethodType.methodType(void.class, new Class[0]), type)
.bindTo(proxy)
.invokeWithArguments(args);
}
if (JavaUtils.getMajorVersion() >= JavaUtils.VERSION_9) {
return lookup.in(type)
.unreflectSpecial(type.getMethod(method.getName()), type)
.bindTo(proxy)
.invokeWithArguments(args);
}
throw new IllegalStateException("Does not support Java " + JavaUtils.getMajorVersion());
}
}
}
public interface ExtendedConfiguration extends Configuration {
enum Name implements NameEnumeration {
AUTH_SESSION_LIFESPAN,
AUTH_SESSION_NAME,
AUTH_REALM,
AUTH_SCHEME
}
default int authSessionLifespan() {
return Name.AUTH_SESSION_LIFESPAN.as(Integer.class);
}
default String authSessionName() {
return Name.AUTH_SESSION_NAME.as(String.class);
}
default String authRealm() {
return Name.AUTH_REALM.as(String.class);
}
default String authScheme() {
return Name.AUTH_SCHEME.as(String.class);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment