diff --git a/config/src/main/java/com/typesafe/config/ConfigFactory.java b/config/src/main/java/com/typesafe/config/ConfigFactory.java index 6c4dd3a0..7538817d 100644 --- a/config/src/main/java/com/typesafe/config/ConfigFactory.java +++ b/config/src/main/java/com/typesafe/config/ConfigFactory.java @@ -3,17 +3,16 @@ */ package com.typesafe.config; +import com.typesafe.config.impl.ConfigImpl; +import com.typesafe.config.impl.Parseable; + import java.io.File; import java.io.Reader; -import java.net.MalformedURLException; import java.net.URL; import java.util.Map; import java.util.Properties; 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. * @@ -36,6 +35,8 @@ import com.typesafe.config.impl.Parseable; * examples. */ public final class ConfigFactory { + private static final String STRATEGY_PROPERTY_NAME = "config.strategy"; + private ConfigFactory() { } @@ -213,54 +214,7 @@ public final class ConfigFactory { .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) @@ -516,7 +470,7 @@ public final class ConfigFactory { * @return the default application configuration */ 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 values) { 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(); + } + } } diff --git a/config/src/main/java/com/typesafe/config/ConfigLoadingStrategy.java b/config/src/main/java/com/typesafe/config/ConfigLoadingStrategy.java new file mode 100644 index 00000000..8a9818a7 --- /dev/null +++ b/config/src/main/java/com/typesafe/config/ConfigLoadingStrategy.java @@ -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); +} diff --git a/config/src/main/java/com/typesafe/config/DefaultConfigLoadingStrategy.java b/config/src/main/java/com/typesafe/config/DefaultConfigLoadingStrategy.java new file mode 100644 index 00000000..1063af09 --- /dev/null +++ b/config/src/main/java/com/typesafe/config/DefaultConfigLoadingStrategy.java @@ -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); + } + } + } + } +} diff --git a/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala b/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala index 127a6bd1..70870c66 100644 --- a/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala @@ -624,6 +624,27 @@ class PublicApiTest extends TestUtils { 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 def usesContextClassLoaderForApplicationConf() { val loaderA1 = new TestClassLoader(this.getClass().getClassLoader(), @@ -1104,3 +1125,16 @@ include "onclasspath" 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 +} \ No newline at end of file