Skip to content

Instantly share code, notes, and snippets.

@eshioji
Created October 10, 2012 20:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eshioji/3868359 to your computer and use it in GitHub Desktop.
Save eshioji/3868359 to your computer and use it in GitHub Desktop.
My config code
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Closeables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.concurrent.ThreadSafe;
import java.io.*;
import java.util.Date;
import java.util.Properties;
/**
* @author eshioji@gmail.com
*/
@ThreadSafe
public class Config {
private static final Logger log = LoggerFactory.getLogger(Config.class);
private static final Joiner joiner = Joiner.on("\n\t");
public static final String CONFIG_FILE_NAME = "some.stuff";
private volatile ImmutableMap<Prop, Object> store;
public void load(String resourceName){
Properties properties = new Properties();
InputStream is = ClassLoader.getSystemResourceAsStream(resourceName);
try {
Preconditions.checkNotNull(is,"No config file found. You must include the config file "+CONFIG_FILE_NAME+" in the classpath");
properties.load(is);
ImmutableMap.Builder<Prop, Object> ret = ImmutableMap.builder();
for (Prop prop : Prop.values()) {
String val = properties.getProperty(prop.name());
if (val == null) {
val = prop._default;
if (val == null) {
throw new PropertiesException("No configured value nor a default value is available for " + prop + ". Please provide a configuration in " + CONFIG_FILE_NAME);
}
}
ret.put(prop, prop.parse(val));
}
ImmutableMap<Prop, Object> built = ret.build();
store = built;
log.info("Read configuration:\n\t" + joiner.join(built.entrySet()));
} catch (IOException e) {
log.error("Unable to read config file", e);
throw new PropertiesException(e);
} finally {
Closeables.closeQuietly(is);
}
}
public <T> T value(Prop prop, Class<T> type) {
Preconditions.checkState(store!=null,"Coding error; Please call load method before using this method.");
Object ret = store.get(prop);
Preconditions.checkArgument(ret.getClass() == type,"Coding error. You are specifying the wrong class. You should give:" + prop.type);
return (T)ret;
}
public static void main(String[] args) throws IOException {
if(args.length!=1){
throw new RuntimeException("Please provide output directory");
}
PrintWriter pw = new PrintWriter(new FileWriter(args[0] + File.separator + CONFIG_FILE_NAME + ".template"));
try {
pw.println("#Configuration file template for " + CONFIG_FILE_NAME + " generated on " + new Date());
for (Prop prop : Prop.values()) {
pw.println("# " + prop);
pw.println("# " + prop.name() + "=" + prop._default);
pw.println();
}
} finally {
Closeables.closeQuietly(pw);
}
}
}
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.ning.http.client.ProxyServer;
import com.ning.http.client.oauth.ConsumerKey;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
/**
* Entity class for managing properties.</br>
* THE HARD CODED VALUES ARE DEFAULTS! VALUE IN THE PROPERTY FILE WILL OVERWRITE THEM</br>
*
* <h3>How to add a new property</h3>
* basic example</br>
<pre>
{@code
// name(default_value, type)
MAX_FAILURE("3",Integer.class),
}
* </pre>
* advanced example:</br>
* <pre>
{@code
//name // default value in string // type // description (optional)
WORKER_THREAD_NUM(""+Runtime.getRuntime().availableProcessors(), Integer.class, "Number of workers threads. Default value is number of available cores."),
}
* </pre>
*
* If your type has "valueOf" method, this is all you need.</br>
* If you want to use a type that has no "valueOf" method, you have to implement the {@link #doParse} method, like so:</br>
* <pre>
{@code
TWITTER_TOKEN_FILE("src/test/resources/dummy.dummy", File.class){
@Override
protected Object doParse(String value) {
File tokenFile = new File(value);
if(tokenFile.canRead()){
return tokenFile;
}else{
throw new PropertiesException("Configuration for " + this + " invalid. File "+tokenFile.getAbsolutePath() +" cannot be read.");
}
}
},
* </pre>
*
* If you provide null as the default value, the initialization fails unless you provide a configuration value in the property file.
* @author Enno Shioji (eshioji@gmail.com)
*/
public enum Prop {
// Simplest example
THREAD_NUM("10", Integer.class),
// Requires user to provide config
SECRET_KEY(null, String.class),
// Dynamic values
WORKER_THREAD_NUM(""+Runtime.getRuntime().availableProcessors(),Integer.class, "Number of workers. Default value is number of available cores."),
// In-build validation
TWITTER_TOKEN_FILE("src/test/resources/dummy.tokens", File.class){
@Override
protected Object doParse(String value) {
File tokenFile = new File(value);
if(tokenFile.canRead()){
return tokenFile;
}else{
throw new PropertiesException("Configuration for " + this + " invalid. File "+tokenFile.getAbsolutePath() +" cannot be read.");
}
}
},
// Some other examples
FOLLOW_REDIRECTS("true",Boolean.class),
HTTP_CONN_TIMEOUT_MS("12000",Integer.class),
HTTP_COMPRESSION_ENABLED("true",Boolean.class),
PROXY_ENABLED("false", Boolean.class),
PROXY_SERVER("secret.proxy:8080", ProxyServer.class){
@Override
protected Object doParse(String value) {
try{
new URI("http://" + value);
} catch (URISyntaxException e) {
throw new PropertiesException("Invalid config for " + this +" Was given:"+value +" was expecting something like " +_default+" (host:port)");
}
Splitter splitter = Splitter.on(":");
List<String> split = ImmutableList.copyOf(splitter.split(value));
if(split.size()!=2){
throw new PropertiesException("Invalid config for " + this +" Was given:"+value +" was expecting something like " +_default+" (host:port) (did you forgot to give port?)");
}
String host = split.get(0);
int port = Integer.valueOf(split.get(1));
return new ProxyServer(ProxyServer.Protocol.HTTP,host,port);
}
};
final String _default;
final Class<?> type;
final String desc;
/**
*
* @param _default
* @param type
*/
Prop(String _default, Class<?> type) {
this(_default, type, null);
}
Prop(String _default, Class<?> type, String desc) {
this._default = _default;
this.type = type;
this.desc = desc;
}
final Object parse(String value) {
return doParse(value);
}
protected Object doParse(String value) {
return valof(value);
}
private Object valof(String value) {
try {
if (this.type == String.class) {
return value;
}
Method valof = this.type.getDeclaredMethod("valueOf", String.class);
return valof.invoke(null, value);
} catch (InvocationTargetException e) {
throw new PropertiesException("Invalid input for " +this + ". Was given " + value + ". Please check your config. Was expecting something like " + _default);
}catch (NoSuchMethodException e) {
throw new AssertionError("Please implement a custom doParse method on " + this);
} catch (IllegalAccessException e) {
throw new AssertionError("Please implement a custom doParse method on " + this);
}catch(Exception e){
throw new PropertiesException("Invalid input for " +this + ". Was given " + value + ". Please check your config. Was expecting something like " + _default);
}
}
@Override
public String toString() {
return "Name:" + this.name() + " Type:" + this.type.getSimpleName() + " Default value=" + (this._default != null ? this._default : "No default value") +(this.desc != null ? " Description:" + this.desc : "");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment