Merge pull request #407 from FeiWongReed/master

issue #405 fixed: config loading strategy implemented
This commit is contained in:
Havoc Pennington 2016-07-04 12:40:34 -04:00 committed by GitHub
commit 3639c645cf
4 changed files with 136 additions and 52 deletions

View File

@ -3,17 +3,16 @@
*/ */
package com.typesafe.config; package com.typesafe.config;
import com.typesafe.config.impl.ConfigImpl;
import com.typesafe.config.impl.Parseable;
import java.io.File; import java.io.File;
import java.io.Reader; import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import com.typesafe.config.impl.ConfigImpl;
import com.typesafe.config.impl.Parseable;
/** /**
* Contains static methods for creating {@link Config} instances. * Contains static methods for creating {@link Config} instances.
* *
@ -36,6 +35,8 @@ import com.typesafe.config.impl.Parseable;
* examples. * examples.
*/ */
public final class ConfigFactory { public final class ConfigFactory {
private static final String STRATEGY_PROPERTY_NAME = "config.strategy";
private ConfigFactory() { private ConfigFactory() {
} }
@ -213,54 +214,7 @@ public final class ConfigFactory {
.resolve(resolveOptions); .resolve(resolveOptions);
} }
private static Config parseApplicationConfig(ConfigParseOptions parseOptions) {
ClassLoader loader = parseOptions.getClassLoader();
if (loader == null)
throw new ConfigException.BugOrBroken(
"ClassLoader should have been set here; bug in ConfigFactory. "
+ "(You can probably work around this bug by passing in a class loader or calling currentThread().setContextClassLoader() though.)");
int specified = 0;
// override application.conf with config.file, config.resource,
// config.url if requested.
String resource = System.getProperty("config.resource");
if (resource != null)
specified += 1;
String file = System.getProperty("config.file");
if (file != null)
specified += 1;
String url = System.getProperty("config.url");
if (url != null)
specified += 1;
if (specified == 0) {
return ConfigFactory.parseResourcesAnySyntax("application", parseOptions);
} else if (specified > 1) {
throw new ConfigException.Generic("You set more than one of config.file='" + file
+ "', config.url='" + url + "', config.resource='" + resource
+ "'; don't know which one to use!");
} else {
// the override file/url/resource MUST be present or it's an error
ConfigParseOptions overrideOptions = parseOptions.setAllowMissing(false);
if (resource != null) {
if (resource.startsWith("/"))
resource = resource.substring(1);
// this deliberately does not parseResourcesAnySyntax; if
// people want that they can use an include statement.
return parseResources(loader, resource, overrideOptions);
} else if (file != null) {
return parseFile(new File(file), overrideOptions);
} else {
try {
return parseURL(new URL(url), overrideOptions);
} catch (MalformedURLException e) {
throw new ConfigException.Generic("Bad URL in config.url system property: '"
+ url + "': " + e.getMessage(), e);
}
}
}
}
/** /**
* Loads a default configuration, equivalent to {@link #load(Config) * Loads a default configuration, equivalent to {@link #load(Config)
@ -516,7 +470,7 @@ public final class ConfigFactory {
* @return the default application configuration * @return the default application configuration
*/ */
public static Config defaultApplication(ConfigParseOptions options) { public static Config defaultApplication(ConfigParseOptions options) {
return parseApplicationConfig(ensureClassLoader(options, "defaultApplication")); return getConfigLoadingStrategy().parseApplicationConfig(ensureClassLoader(options, "defaultApplication"));
} }
/** /**
@ -1094,4 +1048,18 @@ public final class ConfigFactory {
public static Config parseMap(Map<String, ? extends Object> values) { public static Config parseMap(Map<String, ? extends Object> values) {
return parseMap(values, null); return parseMap(values, null);
} }
private static ConfigLoadingStrategy getConfigLoadingStrategy() {
String className = System.getProperties().getProperty(STRATEGY_PROPERTY_NAME);
if (className != null) {
try {
return ConfigLoadingStrategy.class.cast(Class.forName(className).newInstance());
} catch (Throwable e) {
throw new ConfigException.BugOrBroken("Failed to load strategy: " + className, e);
}
} else {
return new DefaultConfigLoadingStrategy();
}
}
} }

View File

@ -0,0 +1,20 @@
package com.typesafe.config;
/**
* This method allows you to alter default config loading strategy for all the code which
* calls {@link ConfigFactory#load}.
*
* Usually you don't have to implement this interface but it may be required
* when you fixing a improperly implemented library with unavailable source code.
*
* You have to define VM property {@code config.strategy} to replace default strategy with your own.
*/
public interface ConfigLoadingStrategy {
/**
* This method must load and parse application config.
*
* @param parseOptions {@link ConfigParseOptions} to use
* @return loaded config
*/
Config parseApplicationConfig(ConfigParseOptions parseOptions);
}

View File

@ -0,0 +1,62 @@
package com.typesafe.config;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
/**
* Default config loading strategy. Able to load resource, file or URL.
* Behavior may be altered by defining one of VM properties
* {@code config.resource}, {@code config.file} or {@code config.url}
*/
public class DefaultConfigLoadingStrategy implements ConfigLoadingStrategy {
@Override
public Config parseApplicationConfig(ConfigParseOptions parseOptions) {
ClassLoader loader = parseOptions.getClassLoader();
if (loader == null)
throw new ConfigException.BugOrBroken(
"ClassLoader should have been set here; bug in ConfigFactory. "
+ "(You can probably work around this bug by passing in a class loader or calling currentThread().setContextClassLoader() though.)");
int specified = 0;
// override application.conf with config.file, config.resource,
// config.url if requested.
String resource = System.getProperty("config.resource");
if (resource != null)
specified += 1;
String file = System.getProperty("config.file");
if (file != null)
specified += 1;
String url = System.getProperty("config.url");
if (url != null)
specified += 1;
if (specified == 0) {
return ConfigFactory.parseResourcesAnySyntax("application", parseOptions);
} else if (specified > 1) {
throw new ConfigException.Generic("You set more than one of config.file='" + file
+ "', config.url='" + url + "', config.resource='" + resource
+ "'; don't know which one to use!");
} else {
// the override file/url/resource MUST be present or it's an error
ConfigParseOptions overrideOptions = parseOptions.setAllowMissing(false);
if (resource != null) {
if (resource.startsWith("/"))
resource = resource.substring(1);
// this deliberately does not parseResourcesAnySyntax; if
// people want that they can use an include statement.
return ConfigFactory.parseResources(loader, resource, overrideOptions);
} else if (file != null) {
return ConfigFactory.parseFile(new File(file), overrideOptions);
} else {
try {
return ConfigFactory.parseURL(new URL(url), overrideOptions);
} catch (MalformedURLException e) {
throw new ConfigException.Generic("Bad URL in config.url system property: '"
+ url + "': " + e.getMessage(), e);
}
}
}
}
}

View File

@ -624,6 +624,27 @@ class PublicApiTest extends TestUtils {
assertFalse("no b", configPlain.hasPath("b")) assertFalse("no b", configPlain.hasPath("b"))
} }
@Test
def supportsConfigLoadingStrategyAlteration(): Unit = {
assertEquals("config.strategy is not set", null, System.getProperty("config.strategy"))
System.setProperty("config.strategy", classOf[TestStrategy].getCanonicalName)
try {
val incovationsBeforeTest = TestStrategy.getIncovations()
val loaderA1 = new TestClassLoader(this.getClass().getClassLoader(),
Map("reference.conf" -> resourceFile("a_1.conf").toURI.toURL()))
val configA1 = withContextClassLoader(loaderA1) {
ConfigFactory.load()
}
ConfigFactory.load()
assertEquals(1, configA1.getInt("a"))
assertEquals(2, TestStrategy.getIncovations() - incovationsBeforeTest)
} finally {
System.clearProperty("config.strategy")
}
}
@Test @Test
def usesContextClassLoaderForApplicationConf() { def usesContextClassLoaderForApplicationConf() {
val loaderA1 = new TestClassLoader(this.getClass().getClassLoader(), val loaderA1 = new TestClassLoader(this.getClass().getClassLoader(),
@ -1104,3 +1125,16 @@ include "onclasspath"
intercept[ConfigException.Missing] { conf.getIsNull("x.c.y") } intercept[ConfigException.Missing] { conf.getIsNull("x.c.y") }
} }
} }
class TestStrategy extends DefaultConfigLoadingStrategy {
override def parseApplicationConfig(parseOptions: ConfigParseOptions): Config = {
TestStrategy.increment()
super.parseApplicationConfig(parseOptions)
}
}
object TestStrategy {
private var invocations = 0
def getIncovations() = invocations
def increment() = invocations += 1
}