Do not cache default config, and use context class loader to load it

So we get any reference.conf from the context class loader.

This does NOT fix loading non-default configs, we need new API
to allow passing in a class loader for that. It also makes
things a bit less efficient since it no longer caches;
in the future we could do a per-class-loader cache.
This commit is contained in:
Havoc Pennington 2012-02-29 10:44:24 -05:00
parent e2c0979422
commit 9733578ebb
6 changed files with 93 additions and 58 deletions

View File

@ -11,7 +11,6 @@ import java.util.Map;
import java.util.Properties; import java.util.Properties;
import com.typesafe.config.impl.ConfigImpl; import com.typesafe.config.impl.ConfigImpl;
import com.typesafe.config.impl.ConfigImplUtil;
import com.typesafe.config.impl.Parseable; import com.typesafe.config.impl.Parseable;
/** /**
@ -103,49 +102,43 @@ public final class ConfigFactory {
.resolve(resolveOptions); .resolve(resolveOptions);
} }
private static class DefaultConfigHolder { private static Config loadDefaultConfig() {
int specified = 0;
private static Config loadDefaultConfig() { // override application.conf with config.file, config.resource,
int specified = 0; // 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;
// override application.conf with config.file, config.resource, if (specified == 0) {
// config.url if requested. return load("application");
String resource = System.getProperty("config.resource"); } else if (specified > 1) {
if (resource != null) throw new ConfigException.Generic("You set more than one of config.file='" + file
specified += 1; + "', config.url='" + url + "', config.resource='" + resource
String file = System.getProperty("config.file"); + "'; don't know which one to use!");
if (file != null) } else {
specified += 1; if (resource != null) {
String url = System.getProperty("config.url"); // this deliberately does not parseResourcesAnySyntax; if
if (url != null) // people want that they can use an include statement.
specified += 1; return load(parseResources(ConfigFactory.class, resource));
} else if (file != null) {
if (specified == 0) { return load(parseFile(new File(file)));
return load("application");
} 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 { } else {
if (resource != null) { try {
// this deliberately does not parseResourcesAnySyntax; if return load(parseURL(new URL(url)));
// people want that they can use an include statement. } catch (MalformedURLException e) {
return load(parseResources(ConfigFactory.class, resource)); throw new ConfigException.Generic("Bad URL in config.url system property: '"
} else if (file != null) { + url + "': " + e.getMessage(), e);
return load(parseFile(new File(file)));
} else {
try {
return load(parseURL(new URL(url)));
} catch (MalformedURLException e) {
throw new ConfigException.Generic(
"Bad URL in config.url system property: '" + url + "': "
+ e.getMessage(), e);
}
} }
} }
} }
static final Config defaultConfig = loadDefaultConfig();
} }
/** /**
@ -176,11 +169,7 @@ public final class ConfigFactory {
* @return configuration for an application * @return configuration for an application
*/ */
public static Config load() { public static Config load() {
try { return loadDefaultConfig();
return DefaultConfigHolder.defaultConfig;
} catch (ExceptionInInitializerError e) {
throw ConfigImplUtil.extractInitializerError(e);
}
} }
/** /**
@ -301,12 +290,12 @@ public final class ConfigFactory {
* string values. If you have both "a=foo" and "a.b=bar" in your properties * string values. If you have both "a=foo" and "a.b=bar" in your properties
* file, so "a" is both the object containing "b" and the string "foo", then * file, so "a" is both the object containing "b" and the string "foo", then
* the string value is dropped. * the string value is dropped.
* *
* <p> * <p>
* If you want to have <code>System.getProperties()</code> as a * If you want to have <code>System.getProperties()</code> as a
* ConfigObject, it's better to use the {@link #systemProperties()} method * ConfigObject, it's better to use the {@link #systemProperties()} method
* which returns a cached global singleton. * which returns a cached global singleton.
* *
* @param properties * @param properties
* a Java Properties object * a Java Properties object
* @param options * @param options

View File

@ -397,21 +397,12 @@ public class ConfigImpl {
return envVariablesAsConfigObject().toConfig(); return envVariablesAsConfigObject().toConfig();
} }
private static class ReferenceHolder {
private static final Config unresolvedResources = Parseable
.newResources(ConfigImpl.class, "/reference.conf", ConfigParseOptions.defaults())
.parse().toConfig();
static final Config referenceConfig = systemPropertiesAsConfig().withFallback(
unresolvedResources).resolve();
}
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */ /** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static Config defaultReference() { public static Config defaultReference() {
try { Config unresolvedResources = Parseable
return ReferenceHolder.referenceConfig; .newResources(Thread.currentThread().getContextClassLoader(), "reference.conf",
} catch (ExceptionInInitializerError e) { ConfigParseOptions.defaults()).parse().toConfig();
throw ConfigImplUtil.extractInitializerError(e); return systemPropertiesAsConfig().withFallback(unresolvedResources).resolve();
}
} }
private static class DebugHolder { private static class DebugHolder {

View File

@ -0,0 +1 @@
a=1

View File

@ -0,0 +1 @@
b=2

View File

@ -464,4 +464,28 @@ class PublicApiTest extends TestUtils {
assertEquals("\"a\"", ConfigUtil.quoteString("a")) assertEquals("\"a\"", ConfigUtil.quoteString("a"))
assertEquals("\"\\n\"", ConfigUtil.quoteString("\n")) assertEquals("\"\\n\"", ConfigUtil.quoteString("\n"))
} }
@Test
def usesContextClassLoader() {
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 = withContextClassLoader(loaderA1) {
ConfigFactory.load()
}
assertEquals(1, configA1.getInt("a"))
assertFalse("no b", configA1.hasPath("b"))
val configB2 = withContextClassLoader(loaderB2) {
ConfigFactory.load()
}
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"))
}
} }

View File

@ -19,6 +19,9 @@ import java.io.ByteArrayInputStream
import java.io.ObjectInputStream import java.io.ObjectInputStream
import org.apache.commons.codec.binary.Hex import org.apache.commons.codec.binary.Hex
import scala.annotation.tailrec import scala.annotation.tailrec
import java.net.URL
import java.util.concurrent.Executors
import java.util.concurrent.Callable
abstract trait TestUtils { abstract trait TestUtils {
protected def intercept[E <: Throwable: Manifest](block: => Unit): E = { protected def intercept[E <: Throwable: Manifest](block: => Unit): E = {
@ -547,4 +550,30 @@ abstract trait TestUtils {
protected def resourceFile(filename: String) = { protected def resourceFile(filename: String) = {
new File(resourceDir, filename) new File(resourceDir, filename)
} }
protected class TestClassLoader(parent: ClassLoader, val additions: Map[String, URL]) extends ClassLoader(parent) {
override def findResources(name: String) = {
import scala.collection.JavaConverters._
val other = super.findResources(name).asScala
additions.get(name).map({ url => Iterator(url) ++ other }).getOrElse(other).asJavaEnumeration
}
override def findResource(name: String) = {
additions.get(name).getOrElse(null)
}
}
protected def withContextClassLoader[T](loader: ClassLoader)(body: => T): T = {
val executor = Executors.newSingleThreadExecutor()
val f = executor.submit(new Callable[T] {
override def call(): T = {
val t = Thread.currentThread()
val old = t.getContextClassLoader()
t.setContextClassLoader(loader)
val result = body
t.setContextClassLoader(old)
result
}
})
f.get
}
} }