Add ConfigFactory.invalidateCaches() to support reloading system props

This lets people change system props in unit tests for example.
Issue  on github.
This commit is contained in:
Havoc Pennington 2012-10-08 15:27:34 -04:00
parent b739f4be6e
commit 5f486f65ac
3 changed files with 94 additions and 13 deletions
config/src
main/java/com/typesafe/config
test/scala/com/typesafe/config/impl

View File

@ -227,7 +227,9 @@ public final class ConfigFactory {
* load("application")} 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.
* 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
@ -399,6 +401,31 @@ public final class ConfigFactory {
return systemProperties();
}
/**
* Reloads any cached configs, picking up changes to system properties for
* example. Because a {@link Config} is immutable, anyone with a reference
* to the old configs will still have the same outdated objects. However,
* new calls to {@link #load()} or {@link #defaultOverrides()} or
* {@link #defaultReference} may return a new object.
* <p>
* This method is primarily intended for use in unit tests, for example,
* that may want to update a system property then confirm that it's used
* correctly. In many cases, use of this method may indicate there's a
* better way to set up your code.
* <p>
* Caches may be reloaded immediately or lazily; once you call this method,
* the reload can occur at any time, even during the invalidation process.
* So FIRST make the changes you'd like the caches to notice, then SECOND
* call this method to invalidate caches. Don't expect that invalidating,
* making changes, then calling {@link #load()}, will work. Make changes
* before you invalidate.
*/
public static void invalidateCaches() {
// We rely on this having the side effect that it drops
// all caches
ConfigImpl.reloadSystemPropertiesConfig();
}
/**
* Gets an empty configuration. See also {@link #empty(String)} to create an
* empty configuration with a description, which may improve user-visible
@ -429,16 +456,19 @@ public final class ConfigFactory {
/**
* Gets a <code>Config</code> containing the system properties from
* {@link java.lang.System#getProperties()}, parsed and converted as with
* {@link #parseProperties}. This method can return a global immutable
* singleton, so it's preferred over parsing system properties yourself.
*
* {@link #parseProperties}.
* <p>
* This method can return a global immutable singleton, so it's preferred
* over parsing system properties yourself.
* <p>
* {@link #load} will include the system properties as overrides already, as
* will {@link #defaultReference} and {@link #defaultOverrides}.
*
* <p>
* Because this returns a singleton, it will not notice changes to system
* properties made after the first time this method is called.
* properties made after the first time this method is called. Use
* {@link #invalidateCaches()} to force the singleton to reload if you
* modify system properties.
*
* @return system properties parsed into a <code>Config</code>
*/

View File

@ -26,21 +26,29 @@ import com.typesafe.config.impl.SimpleIncluder.NameSource;
public class ConfigImpl {
private static class LoaderCache {
private ClassLoader current;
private Config currentSystemProperties;
private ClassLoader currentLoader;
private Map<String, Config> cache;
LoaderCache() {
this.current = null;
this.currentSystemProperties = null;
this.currentLoader = null;
this.cache = new HashMap<String, Config>();
}
// for now, caching as long as the loader remains the same,
// drop entire cache if it changes.
synchronized Config getOrElseUpdate(ClassLoader loader, String key, Callable<Config> updater) {
if (loader != current) {
if (loader != currentLoader) {
// reset the cache if we start using a different loader
cache.clear();
current = loader;
currentLoader = loader;
}
Config systemProperties = systemPropertiesAsConfig();
if (systemProperties != currentSystemProperties) {
cache.clear();
currentSystemProperties = systemProperties;
}
Config config = cache.get(key);
@ -288,7 +296,7 @@ public class ConfigImpl {
private static class SystemPropertiesHolder {
// this isn't final due to the reloadSystemPropertiesConfig() hack below
static AbstractConfigObject systemProperties = loadSystemProperties();
static volatile AbstractConfigObject systemProperties = loadSystemProperties();
}
static AbstractConfigObject systemPropertiesAsConfigObject() {
@ -304,9 +312,10 @@ public class ConfigImpl {
return systemPropertiesAsConfigObject().toConfig();
}
// this is a hack to let us set system props in the test suite.
// obviously not thread-safe.
static void reloadSystemPropertiesConfig() {
/** For use ONLY by library internals, DO NOT TOUCH not guaranteed ABI */
public static void reloadSystemPropertiesConfig() {
// ConfigFactory.invalidateCaches() relies on this having the side
// effect that it drops all caches
SystemPropertiesHolder.systemProperties = loadSystemProperties();
}

View File

@ -856,4 +856,46 @@ class PublicApiTest extends TestUtils {
assertTrue("cause messages equal after deserialize", e.getCause().getMessage.equals(eCopy.getCause().getMessage))
assertTrue("origins equal after deserialize", e.origin().equals(eCopy.origin()))
}
@Test
def invalidateCaches() {
val conf0 = ConfigFactory.load()
val sys0 = ConfigFactory.systemProperties()
val conf1 = ConfigFactory.load()
val sys1 = ConfigFactory.systemProperties()
ConfigFactory.invalidateCaches()
val conf2 = ConfigFactory.load()
val sys2 = ConfigFactory.systemProperties()
System.setProperty("invalidateCachesTest", "Hello!")
ConfigFactory.invalidateCaches()
val conf3 = ConfigFactory.load()
val sys3 = ConfigFactory.systemProperties()
val conf4 = ConfigFactory.load()
val sys4 = ConfigFactory.systemProperties()
System.clearProperty("invalidateCachesTest")
assertTrue("stuff gets cached sys", sys0 eq sys1)
assertTrue("stuff gets cached conf", conf0 eq conf1)
assertTrue("test system property is not set sys", !sys0.hasPath("invalidateCachesTest"))
assertTrue("test system property is not set conf", !conf0.hasPath("invalidateCachesTest"))
assertTrue("invalidate caches works on unchanged system props sys", sys1 ne sys2)
assertTrue("invalidate caches works on unchanged system props conf", conf1 ne conf2)
assertTrue("invalidate caches works on changed system props sys", sys2 ne sys3)
assertTrue("invalidate caches works on changed system props conf", conf2 ne conf3)
assertTrue("invalidate caches doesn't change value if no system prop changes sys", sys1 == sys2)
assertTrue("invalidate caches doesn't change value if no system prop changes conf", conf1 == conf2)
assertTrue("test system property is set sys", sys3.hasPath("invalidateCachesTest"))
assertTrue("test system property is set conf", conf3.hasPath("invalidateCachesTest"))
assertTrue("invalidate caches DOES change value if system props changed sys", sys2 != sys3)
assertTrue("invalidate caches DOES change value if system props changed conf", conf2 != conf3)
assertTrue("stuff gets cached repeatedly sys", sys3 eq sys4)
assertTrue("stuff gets cached repeatedly conf", conf3 eq conf4)
}
}