diff --git a/config/src/main/java/com/typesafe/config/ConfigFactory.java b/config/src/main/java/com/typesafe/config/ConfigFactory.java index 8506552f..49cff71d 100644 --- a/config/src/main/java/com/typesafe/config/ConfigFactory.java +++ b/config/src/main/java/com/typesafe/config/ConfigFactory.java @@ -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(); } diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java b/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java index 583d2507..11d12909 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java @@ -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(); } 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 2948d69f..e0a60e8a 100644 --- a/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/PublicApiTest.scala @@ -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")) + } + } }