Add ConfigFactory.defaultApplication and load(parseOptions, resolveOptions)

This is a rearrangement of ConfigFactory so that defaultApplication is public.
Also fixes a bug where we were caching the overload that takes parse and resolve
options, without putting those options in the cache key. Moved to only cache
the actual default config (the one that doesn't take any options).

Fixes #196 plus the separate bug about bad caching.
This commit is contained in:
Havoc Pennington 2015-03-05 13:48:28 -05:00
parent cf84e26ddd
commit 7d21966a3e
2 changed files with 109 additions and 47 deletions

View File

@ -203,7 +203,7 @@ public final class ConfigFactory {
.resolve(resolveOptions);
}
private static Config loadDefaultConfig(ConfigParseOptions parseOptions, ConfigResolveOptions resolveOptions) {
private static Config parseApplicationConfig(ConfigParseOptions parseOptions) {
ClassLoader loader = parseOptions.getClassLoader();
if (loader == null)
throw new ConfigException.BugOrBroken(
@ -225,7 +225,7 @@ public final class ConfigFactory {
specified += 1;
if (specified == 0) {
return load(loader, "application", parseOptions, resolveOptions);
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
@ -238,15 +238,12 @@ public final class ConfigFactory {
resource = resource.substring(1);
// this deliberately does not parseResourcesAnySyntax; if
// people want that they can use an include statement.
Config parsedResources = parseResources(loader, resource, overrideOptions);
return load(loader, parsedResources, resolveOptions);
return parseResources(loader, resource, overrideOptions);
} else if (file != null) {
Config parsedFile = parseFile(new File(file), overrideOptions);
return load(loader, parsedFile, resolveOptions);
return parseFile(new File(file), overrideOptions);
} else {
try {
Config parsedURL = parseURL(new URL(url), overrideOptions);
return load(loader, parsedURL, resolveOptions);
return parseURL(new URL(url), overrideOptions);
} catch (MalformedURLException e) {
throw new ConfigException.Generic("Bad URL in config.url system property: '"
+ url + "': " + e.getMessage(), e);
@ -256,36 +253,19 @@ public final class ConfigFactory {
}
/**
* Loads a default configuration, equivalent to {@link #load(String)
* load("application")} in most cases. This configuration should be used by
* Loads a default configuration, equivalent to {@link #load(Config)
* load(defaultApplication())} in most cases. This configuration should be used by
* libraries and frameworks unless an application provides a different one.
* <p>
* This method may return a cached singleton so will not see changes to
* system properties or config files. (Use {@link #invalidateCaches()} to
* force it to reload.)
* <p>
* If the system properties <code>config.resource</code>,
* <code>config.file</code>, or <code>config.url</code> are set, then the
* classpath resource, file, or URL specified in those properties will be
* used rather than the default
* <code>application.{conf,json,properties}</code> classpath resources.
* These system properties should not be set in code (after all, you can
* just parse whatever you want manually and then use {@link #load(Config)}
* if you don't want to use <code>application.conf</code>). The properties
* are intended for use by the person or script launching the application.
* For example someone might have a <code>production.conf</code> that
* include <code>application.conf</code> but then change a couple of values.
* When launching the app they could specify
* <code>-Dconfig.resource=production.conf</code> to get production mode.
* <p>
* If no system properties are set to change the location of the default
* configuration, <code>ConfigFactory.load()</code> is equivalent to
* <code>ConfigFactory.load("application")</code>.
*
* @return configuration for an application
*/
public static Config load() {
return load(ConfigParseOptions.defaults());
ClassLoader loader = checkedContextClassLoader("load");
return load(loader);
}
/**
@ -308,7 +288,13 @@ public final class ConfigFactory {
* @return configuration for an application
*/
public static Config load(final ClassLoader loader) {
return load(ConfigParseOptions.defaults().setClassLoader(loader));
final ConfigParseOptions withLoader = ConfigParseOptions.defaults().setClassLoader(loader);
return ConfigImpl.computeCachedConfig(loader, "load", new Callable<Config>() {
@Override
public Config call() {
return load(loader, defaultApplication(withLoader));
}
});
}
/**
@ -316,7 +302,7 @@ public final class ConfigFactory {
* thread's current context class loader, and parse options
*
* @param loader
* class loader for finding resources
* class loader for finding resources (overrides any loader in parseOptions)
* @param parseOptions
* Options for parsing resources
* @return configuration for an application
@ -345,7 +331,7 @@ public final class ConfigFactory {
* thread's current context class loader, parse options, and resolve options
*
* @param loader
* class loader for finding resources
* class loader for finding resources (overrides any loader in parseOptions)
* @param parseOptions
* Options for parsing resources
* @param resolveOptions
@ -353,31 +339,25 @@ public final class ConfigFactory {
* @return configuration for an application
*/
public static Config load(ClassLoader loader, ConfigParseOptions parseOptions, ConfigResolveOptions resolveOptions) {
return load(parseOptions.setClassLoader(loader), resolveOptions);
final ConfigParseOptions withLoader = ensureClassLoader(parseOptions, "load");
return load(loader, defaultApplication(withLoader), resolveOptions);
}
// TODO this is private to avoid adding API in stable release, but no reason
// it isn't public in the long run.
/**
* Like {@link #load()} but allows specifying parse options and resolve
* options
*
* options.
*
* @param parseOptions
* Options for parsing resources
* @param resolveOptions
* options for resolving the assembled config stack
* @return configuration for an application
*
* @since 1.2.1
*
* @since 1.3.0
*/
private static Config load(ConfigParseOptions parseOptions, final ConfigResolveOptions resolveOptions) {
public static Config load(ConfigParseOptions parseOptions, final ConfigResolveOptions resolveOptions) {
final ConfigParseOptions withLoader = ensureClassLoader(parseOptions, "load");
return ConfigImpl.computeCachedConfig(withLoader.getClassLoader(), "load", new Callable<Config>() {
@Override
public Config call() {
return loadDefaultConfig(withLoader, resolveOptions);
}
});
return load(defaultApplication(withLoader), resolveOptions);
}
/**
@ -453,6 +433,82 @@ public final class ConfigFactory {
return systemProperties();
}
/**
* Obtains the default application-specific configuration,
* which defaults to parsing <code>application.conf</code>,
* <code>application.json</code>, and
* <code>application.properties</code> on the classpath, but
* can also be rerouted using the <code>config.file</code>,
* <code>config.resource</code>, and <code>config.url</code>
* system properties.
*
* <p> The no-arguments {@link #load()} method automatically
* stacks the {@link #defaultReference()}, {@link
* #defaultApplication()}, and {@link #defaultOverrides()}
* configs. You would use <code>defaultApplication()</code>
* directly only if you're somehow customizing behavior by
* reimplementing <code>load()</code>.
*
* <p>The configuration returned by
* <code>defaultApplication()</code> will not be resolved
* already, in contrast to <code>defaultReference()</code> and
* <code>defaultOverrides()</code>. This is because
* application.conf would normally be resolved <em>after</em>
* merging with the reference and override configs.
*
* <p>
* If the system properties <code>config.resource</code>,
* <code>config.file</code>, or <code>config.url</code> are set, then the
* classpath resource, file, or URL specified in those properties will be
* used rather than the default
* <code>application.{conf,json,properties}</code> classpath resources.
* These system properties should not be set in code (after all, you can
* just parse whatever you want manually and then use {@link #load(Config)}
* if you don't want to use <code>application.conf</code>). The properties
* are intended for use by the person or script launching the application.
* For example someone might have a <code>production.conf</code> that
* include <code>application.conf</code> but then change a couple of values.
* When launching the app they could specify
* <code>-Dconfig.resource=production.conf</code> to get production mode.
*
* <p>
* If no system properties are set to change the location of the default
* configuration, <code>defaultApplication()</code> is equivalent to
* <code>ConfigFactory.parseResources("application")</code>.
*
* @since 1.3.0
*
* @return the default application.conf or system-property-configured configuration
*/
public static Config defaultApplication() {
return defaultApplication(ConfigParseOptions.defaults());
}
/**
* Like {@link #defaultApplication()} but allows you to specify a class loader
* to use rather than the current context class loader.
*
* @since 1.3.0
*
* @param loader
* @return the default application configuration
*/
public static Config defaultApplication(ClassLoader loader) {
return defaultApplication(ConfigParseOptions.defaults().setClassLoader(loader));
}
/**
* Like {@link #defaultApplication()} but allows you to specify parse options.
*
* @since 1.3.0
*
* @param parseOptions the options
* @return the default application configuration
*/
public static Config defaultApplication(ConfigParseOptions options) {
return parseApplicationConfig(ensureClassLoader(options, "defaultApplication"));
}
/**
* Reloads any cached configs, picking up changes to system properties for
* example. Because a {@link Config} is immutable, anyone with a reference

View File

@ -676,6 +676,7 @@ class PublicApiTest extends TestUtils {
ConfigFactory.parseResourcesAnySyntax(loaderA1, "reference", ConfigParseOptions.defaults()),
ConfigFactory.load(loaderA1, "application"),
ConfigFactory.load(loaderA1, "application", ConfigParseOptions.defaults(), ConfigResolveOptions.defaults()),
ConfigFactory.load(loaderA1, "application", ConfigParseOptions.defaults(), ConfigResolveOptions.defaults()),
ConfigFactory.load(loaderA1, ConfigFactory.parseString("")),
ConfigFactory.load(loaderA1, ConfigFactory.parseString(""), ConfigResolveOptions.defaults()),
ConfigFactory.defaultReference(loaderA1))
@ -758,7 +759,8 @@ class PublicApiTest extends TestUtils {
ConfigFactory.parseResources(loaderA1, "application.conf", ConfigParseOptions.defaults()),
ConfigFactory.parseResourcesAnySyntax(loaderA1, "application", ConfigParseOptions.defaults()),
ConfigFactory.load(loaderA1, "application"),
ConfigFactory.load(loaderA1, "application", ConfigParseOptions.defaults(), ConfigResolveOptions.defaults()))
ConfigFactory.load(loaderA1, "application", ConfigParseOptions.defaults(), ConfigResolveOptions.defaults()),
ConfigFactory.defaultApplication(loaderA1))
) {
assertEquals(1, c.getInt("a"))
assertFalse("no b", c.hasPath("b"))
@ -769,6 +771,8 @@ class PublicApiTest extends TestUtils {
for (
c <- Seq(ConfigFactory.parseResources("application.conf", withLoader),
ConfigFactory.parseResourcesAnySyntax("application", withLoader),
ConfigFactory.defaultApplication(withLoader),
ConfigFactory.load(withLoader, ConfigResolveOptions.defaults()),
ConfigFactory.load("application", withLoader, ConfigResolveOptions.defaults()))
) {
assertEquals(1, c.getInt("a"))
@ -782,6 +786,7 @@ class PublicApiTest extends TestUtils {
ConfigFactory.parseResources("application.conf", ConfigParseOptions.defaults()),
ConfigFactory.parseResourcesAnySyntax("application", ConfigParseOptions.defaults()),
ConfigFactory.load("application"),
ConfigFactory.defaultApplication(),
ConfigFactory.load("application", ConfigParseOptions.defaults(), ConfigResolveOptions.defaults()))
) {
assertFalse("no a", c.hasPath("a"))
@ -796,6 +801,7 @@ class PublicApiTest extends TestUtils {
ConfigFactory.parseResources("application.conf", ConfigParseOptions.defaults()),
ConfigFactory.parseResourcesAnySyntax("application", ConfigParseOptions.defaults()),
ConfigFactory.load("application"),
ConfigFactory.defaultApplication(),
ConfigFactory.load("application", ConfigParseOptions.defaults(), ConfigResolveOptions.defaults()))
) {
assertEquals(1, c.getInt("a"))