Add ConfigFactory.load variants that let you supply a ClassLoader

This commit is contained in:
Havoc Pennington 2012-02-29 12:05:35 -05:00
parent 9733578ebb
commit 30bdac884b
3 changed files with 284 additions and 18 deletions

View File

@ -40,6 +40,12 @@ public final class ConfigFactory {
* {@link Class#getResource}).
*
* <p>
* Resources are loaded from the current thread's
* {@link Thread#getContextClassLoader()}. In general, a library needs its
* configuration to come from the class loader used to load that library, so
* the proper "reference.conf" are present.
*
* <p>
* The loaded object will already be resolved (substitutions have already
* been processed). As a result, if you add more fallbacks then they won't
* be seen by substitutions. Substitutions are the "${foo.bar}" syntax. If
@ -48,16 +54,29 @@ public final class ConfigFactory {
*
* @param resourceBasename
* name (optionally without extension) of a resource on classpath
* @return configuration for an application
* @return configuration for an application relative to context class loader
*/
public static Config load(String resourceBasename) {
return load(resourceBasename, ConfigParseOptions.defaults(),
return load(Thread.currentThread().getContextClassLoader(), resourceBasename);
}
/**
* Like {@link #load(String)} but uses the supplied class loader instead of
* the current thread's context class loader.
*
* @param loader
* @param resourceBasename
* @return configuration for an application relative to given class loader
*/
public static Config load(ClassLoader loader, String resourceBasename) {
return load(loader, resourceBasename, ConfigParseOptions.defaults(),
ConfigResolveOptions.defaults());
}
/**
* Like {@link #load(String)} but allows you to specify parse and resolve
* options.
*
* @param resourceBasename
* the classpath resource name with optional extension
* @param parseOptions
@ -68,9 +87,29 @@ public final class ConfigFactory {
*/
public static Config load(String resourceBasename, ConfigParseOptions parseOptions,
ConfigResolveOptions resolveOptions) {
Config appConfig = ConfigFactory.parseResourcesAnySyntax(ConfigFactory.class, "/"
+ resourceBasename, parseOptions);
return load(appConfig, resolveOptions);
return load(Thread.currentThread().getContextClassLoader(), resourceBasename, parseOptions,
resolveOptions);
}
/**
* Like {@link #load(String,ConfigParseOptions,ConfigResolveOptions)} but
* allows you to specify a class loader
*
* @param loader
* class loader in which to find resources
* @param resourceBasename
* the classpath resource name with optional extension
* @param parseOptions
* options to use when parsing the resource
* @param resolveOptions
* options to use when resolving the stack
* @return configuration for an application
*/
public static Config load(ClassLoader loader, String resourceBasename,
ConfigParseOptions parseOptions, ConfigResolveOptions resolveOptions) {
Config appConfig = ConfigFactory.parseResourcesAnySyntax(loader, resourceBasename,
parseOptions);
return load(loader, appConfig, resolveOptions);
}
/**
@ -84,7 +123,11 @@ public final class ConfigFactory {
* @return resolved configuration with overrides and fallbacks added
*/
public static Config load(Config config) {
return load(config, ConfigResolveOptions.defaults());
return load(Thread.currentThread().getContextClassLoader(), config);
}
public static Config load(ClassLoader loader, Config config) {
return load(loader, config, ConfigResolveOptions.defaults());
}
/**
@ -98,11 +141,28 @@ public final class ConfigFactory {
* @return resolved configuration with overrides and fallbacks added
*/
public static Config load(Config config, ConfigResolveOptions resolveOptions) {
return defaultOverrides().withFallback(config).withFallback(defaultReference())
return load(Thread.currentThread().getContextClassLoader(), config, resolveOptions);
}
/**
* Like {@link #load(Config,ConfigResolveOptions)} but allows you to specify
* a class loader other than the context class loader.
*
* @param loader
* class loader to use when looking up override and reference
* configs
* @param config
* the application's portion of the configuration
* @param resolveOptions
* options for resolving the assembled config stack
* @return resolved configuration with overrides and fallbacks added
*/
public static Config load(ClassLoader loader, Config config, ConfigResolveOptions resolveOptions) {
return defaultOverrides(loader).withFallback(config).withFallback(defaultReference(loader))
.resolve(resolveOptions);
}
private static Config loadDefaultConfig() {
private static Config loadDefaultConfig(ClassLoader loader) {
int specified = 0;
// override application.conf with config.file, config.resource,
@ -118,7 +178,7 @@ public final class ConfigFactory {
specified += 1;
if (specified == 0) {
return load("application");
return load(loader, "application");
} else if (specified > 1) {
throw new ConfigException.Generic("You set more than one of config.file='" + file
+ "', config.url='" + url + "', config.resource='" + resource
@ -127,12 +187,12 @@ public final class ConfigFactory {
if (resource != null) {
// this deliberately does not parseResourcesAnySyntax; if
// people want that they can use an include statement.
return load(parseResources(ConfigFactory.class, resource));
return load(loader, parseResources(loader, resource));
} else if (file != null) {
return load(parseFile(new File(file)));
return load(loader, parseFile(new File(file)));
} else {
try {
return load(parseURL(new URL(url)));
return load(loader, parseURL(new URL(url)));
} catch (MalformedURLException e) {
throw new ConfigException.Generic("Bad URL in config.url system property: '"
+ url + "': " + e.getMessage(), e);
@ -169,7 +229,19 @@ public final class ConfigFactory {
* @return configuration for an application
*/
public static Config load() {
return loadDefaultConfig();
return load(Thread.currentThread().getContextClassLoader());
}
/**
* Like {@link #load()} but allows specifying a class loader other than the
* thread's current context class loader.
*
* @param loader
* class loader for finding resources
* @return configuration for an application
*/
public static Config load(ClassLoader loader) {
return loadDefaultConfig(loader);
}
/**
@ -183,6 +255,13 @@ public final class ConfigFactory {
* jar.
*
* <p>
* The reference config must be looked up in the class loader that contains
* the libraries that you want to use with this config, so the
* "reference.conf" for each library can be found. Use
* {@link #defaultReference(ClassLoader)} if the context class loader is not
* suitable.
*
* <p>
* The {@link #load()} methods merge this configuration for you
* automatically.
*
@ -191,10 +270,21 @@ public final class ConfigFactory {
* is not guaranteed that this method <em>only</em> looks at
* "reference.conf".
*
* @return the default reference config
* @return the default reference config for context class loader
*/
public static Config defaultReference() {
return ConfigImpl.defaultReference();
return defaultReference(Thread.currentThread().getContextClassLoader());
}
/**
* Like {@link #defaultReference()} but allows you to specify a class loader
* to use rather than the current context class loader.
*
* @param loader
* @return the default reference config for this class loader
*/
public static Config defaultReference(ClassLoader loader) {
return ConfigImpl.defaultReference(loader);
}
/**
@ -216,6 +306,17 @@ public final class ConfigFactory {
return systemProperties();
}
/**
* Like {@link #defaultOverrides()} but allows you to specify a class loader
* to use rather than the current context class loader.
*
* @param loader
* @return the default override configuration
*/
public static Config defaultOverrides(ClassLoader loader) {
return systemProperties();
}
/**
* Gets an empty configuration. See also {@link #empty(String)} to create an
* empty configuration with a description, which may improve user-visible
@ -454,6 +555,106 @@ public final class ConfigFactory {
return parseResourcesAnySyntax(klass, resourceBasename, ConfigParseOptions.defaults());
}
/**
* Parses all resources on the classpath with the given name and merges them
* into a single <code>Config</code>.
*
* <p>
* This works like {@link java.lang.ClassLoader#getResource}, not like
* {@link java.lang.Class#getResource}, so the name never begins with a
* slash.
*
* <p>
* See {@link #parseResources(Class,String,ConfigParseOptions)} for full
* details.
*
* @param loader
* will be used to load resources
* @param resource
* resource to look up
* @param options
* parse options
* @return the parsed configuration
*/
public static Config parseResources(ClassLoader loader, String resource,
ConfigParseOptions options) {
return Parseable.newResources(loader, resource, options).parse().toConfig();
}
public static Config parseResources(ClassLoader loader, String resource) {
return parseResources(loader, resource, ConfigParseOptions.defaults());
}
/**
* Parses classpath resources with a flexible extension. In general, this
* method has the same behavior as
* {@link #parseFileAnySyntax(File,ConfigParseOptions)} but for classpath
* resources instead, as in
* {@link #parseResources(ClassLoader,String,ConfigParseOptions)}.
*
* <p>
* {@link #parseResourcesAnySyntax(Class,String,ConfigParseOptions)} differs
* in the syntax for the resource name, but otherwise see
* {@link #parseResourcesAnySyntax(Class,String,ConfigParseOptions)} for
* some details and caveats on this method.
*
* @param loader
* class loader to look up resources in
* @param resourceBasename
* a resource name as in
* {@link java.lang.ClassLoader#getResource}, with or without
* extension
* @param options
* parse options
* @return the parsed configuration
*/
public static Config parseResourcesAnySyntax(ClassLoader loader, String resourceBasename,
ConfigParseOptions options) {
return ConfigImpl.parseResourcesAnySyntax(loader, resourceBasename, options).toConfig();
}
public static Config parseResourcesAnySyntax(ClassLoader loader, String resourceBasename) {
return parseResourcesAnySyntax(loader, resourceBasename, ConfigParseOptions.defaults());
}
/**
* Like {@link #parseResources(ClassLoader,String,ConfigParseOptions)} but
* uses thread's current context class loader.
*/
public static Config parseResources(String resource, ConfigParseOptions options) {
return Parseable
.newResources(Thread.currentThread().getContextClassLoader(), resource, options)
.parse().toConfig();
}
/**
* Like {@link #parseResources(ClassLoader,String)} but uses thread's
* current context class loader.
*/
public static Config parseResources(String resource) {
return parseResources(Thread.currentThread().getContextClassLoader(), resource,
ConfigParseOptions.defaults());
}
/**
* Like
* {@link #parseResourcesAnySyntax(ClassLoader,String,ConfigParseOptions)}
* but uses thread's current context class loader.
*/
public static Config parseResourcesAnySyntax(String resourceBasename, ConfigParseOptions options) {
return ConfigImpl.parseResourcesAnySyntax(Thread.currentThread().getContextClassLoader(),
resourceBasename, options).toConfig();
}
/**
* Like {@link #parseResourcesAnySyntax(ClassLoader,String)} but uses
* thread's current context class loader.
*/
public static Config parseResourcesAnySyntax(String resourceBasename) {
return parseResourcesAnySyntax(Thread.currentThread().getContextClassLoader(),
resourceBasename, ConfigParseOptions.defaults());
}
public static Config parseString(String s, ConfigParseOptions options) {
return Parseable.newString(s, options).parse().toConfig();
}

View File

@ -118,6 +118,18 @@ public class ConfigImpl {
return fromBasename(source, resourceBasename, baseOptions);
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigObject parseResourcesAnySyntax(final ClassLoader loader,
String resourceBasename, final ConfigParseOptions baseOptions) {
NameSource source = new NameSource() {
@Override
public ConfigParseable nameToParseable(String name) {
return Parseable.newResources(loader, name, baseOptions);
}
};
return fromBasename(source, resourceBasename, baseOptions);
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static ConfigObject parseFileAnySyntax(final File basename,
final ConfigParseOptions baseOptions) {
@ -398,10 +410,10 @@ public class ConfigImpl {
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static Config defaultReference() {
public static Config defaultReference(ClassLoader loader) {
Config unresolvedResources = Parseable
.newResources(Thread.currentThread().getContextClassLoader(), "reference.conf",
ConfigParseOptions.defaults()).parse().toConfig();
.newResources(loader, "reference.conf", ConfigParseOptions.defaults()).parse()
.toConfig();
return systemPropertiesAsConfig().withFallback(unresolvedResources).resolve();
}

View File

@ -488,4 +488,57 @@ class PublicApiTest extends TestUtils {
assertFalse("no a", configPlain.hasPath("a"))
assertFalse("no b", configPlain.hasPath("b"))
}
@Test
def usesSuppliedClassLoader() {
val loaderA1 = new TestClassLoader(this.getClass().getClassLoader(),
Map("reference.conf" -> resourceFile("a_1.conf").toURI.toURL()))
val loaderB2 = new TestClassLoader(this.getClass().getClassLoader(),
Map("reference.conf" -> resourceFile("b_2.conf").toURI.toURL()))
val configA1 = ConfigFactory.load(loaderA1)
assertEquals(1, configA1.getInt("a"))
assertFalse("no b", configA1.hasPath("b"))
val configB2 = ConfigFactory.load(loaderB2)
assertEquals(2, configB2.getInt("b"))
assertFalse("no a", configB2.hasPath("a"))
val configPlain = ConfigFactory.load()
assertFalse("no a", configPlain.hasPath("a"))
assertFalse("no b", configPlain.hasPath("b"))
// check the various overloads that take a loader parameter
for (
c <- Seq(ConfigFactory.parseResources(loaderA1, "reference.conf"),
ConfigFactory.parseResourcesAnySyntax(loaderA1, "reference"),
ConfigFactory.parseResources(loaderA1, "reference.conf", ConfigParseOptions.defaults()),
ConfigFactory.parseResourcesAnySyntax(loaderA1, "reference", ConfigParseOptions.defaults()),
ConfigFactory.load(loaderA1, "application"),
ConfigFactory.load(loaderA1, "application", ConfigParseOptions.defaults(), ConfigResolveOptions.defaults()),
ConfigFactory.load(loaderA1, ConfigFactory.parseString("")),
ConfigFactory.load(loaderA1, ConfigFactory.parseString(""), ConfigResolveOptions.defaults()),
ConfigFactory.defaultReference(loaderA1))
) {
assertEquals(1, c.getInt("a"))
assertFalse("no b", c.hasPath("b"))
}
for (
c <- Seq(ConfigFactory.parseResources("reference.conf"),
ConfigFactory.parseResourcesAnySyntax("reference"),
ConfigFactory.parseResources("reference.conf", ConfigParseOptions.defaults()),
ConfigFactory.parseResourcesAnySyntax("reference", ConfigParseOptions.defaults()),
ConfigFactory.load("application"),
ConfigFactory.load("application", ConfigParseOptions.defaults(), ConfigResolveOptions.defaults()),
ConfigFactory.load(ConfigFactory.parseString("")),
ConfigFactory.load(ConfigFactory.parseString(""), ConfigResolveOptions.defaults()),
ConfigFactory.defaultReference())
) {
assertFalse("no a", c.hasPath("a"))
assertFalse("no b", c.hasPath("b"))
}
}
}