Created
October 10, 2012 20:57
-
-
Save eshioji/3868359 to your computer and use it in GitHub Desktop.
My config code
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
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