diff --git a/config/src/main/java/com/typesafe/config/ConfigFactory.java b/config/src/main/java/com/typesafe/config/ConfigFactory.java index 2c09a647..9d3d2a58 100644 --- a/config/src/main/java/com/typesafe/config/ConfigFactory.java +++ b/config/src/main/java/com/typesafe/config/ConfigFactory.java @@ -1137,13 +1137,13 @@ public static java.util.Optional parseApplicationReplacement(ConfigParse // override application.conf with config.file, config.resource, // config.url if requested. - String resource = System.getProperty("config.resource"); + String resource = SystemOverride.getProperty("config.resource"); if (resource != null) specified += 1; - String file = System.getProperty("config.file"); + String file = SystemOverride.getProperty("config.file"); if (file != null) specified += 1; - String url = System.getProperty("config.url"); + String url = SystemOverride.getProperty("config.url"); if (url != null) specified += 1; @@ -1238,7 +1238,7 @@ public static Config parseMap(Map values) { } private static ConfigLoadingStrategy getConfigLoadingStrategy() { - String className = System.getProperties().getProperty(STRATEGY_PROPERTY_NAME); + String className = SystemOverride.getProperties().getProperty(STRATEGY_PROPERTY_NAME); if (className != null) { try { @@ -1256,7 +1256,7 @@ private static ConfigLoadingStrategy getConfigLoadingStrategy() { } private static Boolean getOverrideWithEnv() { - String overrideWithEnv = System.getProperties().getProperty(OVERRIDE_WITH_ENV_PROPERTY_NAME); + String overrideWithEnv = SystemOverride.getProperties().getProperty(OVERRIDE_WITH_ENV_PROPERTY_NAME); return Boolean.parseBoolean(overrideWithEnv); } diff --git a/config/src/main/java/com/typesafe/config/SystemOverride.java b/config/src/main/java/com/typesafe/config/SystemOverride.java new file mode 100644 index 00000000..05df8362 --- /dev/null +++ b/config/src/main/java/com/typesafe/config/SystemOverride.java @@ -0,0 +1,138 @@ +package com.typesafe.config; + +import java.io.PrintStream; +import java.util.Map; +import java.util.Properties; +import java.util.function.Supplier; + +public class SystemOverride { + + /** Runs the specified synchronous (blocking) operation + * guaranteeing that all SystemOverride methods return the specified values + * while the operation is being executed. + *

+ * The configuration library only ever accesses System through SystemOverride, + * so this method is useful whenever you want to influence the configuration resolution + * through altering the environment variables (also system properties and standard error stream) programmatically. + * + * @param systemProperties replacement for the system properties + * @param environmentVariables replacement for the environment variables + * @param errorStream replacement for the standard error stream + * + * @return T the result of the specified operation + * @throws RuntimeException in case the specified operation throws + * */ + public static T withSystemOverride( + Map systemProperties, + Map environmentVariables, + PrintStream errorStream, + Supplier configurationAccessOperation) { + SystemImplementation overridden = new OverriddenSystemImplementation( + systemProperties, + environmentVariables, + errorStream); + try { + current.set(overridden); + return configurationAccessOperation.get(); + } finally { + current.set(real); + } + } + + public static Properties getProperties() { + return current.get().getProperties(); + } + + public static String getProperty(String propertyKey) { + return current.get().getProperty(propertyKey); + } + + public static String getenv(String environmentVariableName) { + return current.get().getenv(environmentVariableName); + } + + public static Map getenv() { + return current.get().getenv(); + } + + public static PrintStream err() { + return current.get().err(); + } + + private static interface SystemImplementation { + Properties getProperties(); + + String getProperty(String propertyKey); + + String getenv(String environmentVariableName); + + Map getenv(); + + PrintStream err(); + + } + + private static class LiveSystemImplementation implements SystemImplementation { + public Properties getProperties() { + return System.getProperties(); + } + + public String getProperty(String propertyKey) { + return System.getProperty(propertyKey); + } + + public String getenv(String environmentVariableName) { + return System.getenv(environmentVariableName); + } + + public Map getenv() { + return System.getenv(); + } + + public PrintStream err() { + return System.err; + } + + + } + + private static class OverriddenSystemImplementation implements SystemImplementation { + + private final Map systemProperties; + private final Map environmentVariables; + private final PrintStream errorStream; + + private OverriddenSystemImplementation(Map systemProperties, Map environmentVariables, PrintStream errorStream) { + this.systemProperties = systemProperties; + this.environmentVariables = environmentVariables; + this.errorStream = errorStream; + } + + public Properties getProperties() { + Properties result = new Properties(); + result.putAll(systemProperties); + return result; + } + + public String getProperty(String propertyKey) { + return systemProperties.get(propertyKey); + } + + public String getenv(String environmentVariableName) { + return environmentVariables.get(environmentVariableName); + } + + public Map getenv() { + return environmentVariables; + } + + public PrintStream err() { + return errorStream; + } + } + + private static final SystemImplementation real = new LiveSystemImplementation(); + + private static final ThreadLocal current = ThreadLocal.withInitial(() -> real); + +} 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 05be7d26..cba7cca6 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java @@ -25,6 +25,7 @@ import com.typesafe.config.ConfigParseOptions; import com.typesafe.config.ConfigParseable; import com.typesafe.config.ConfigValue; +import com.typesafe.config.SystemOverride; import com.typesafe.config.impl.SimpleIncluder.NameSource; /** @@ -299,7 +300,7 @@ static ConfigIncluder defaultIncluder() { private static Properties getSystemProperties() { // Avoid ConcurrentModificationException due to parallel setting of system properties by copying properties - final Properties systemProperties = System.getProperties(); + final Properties systemProperties = SystemOverride.getProperties(); final Properties systemPropertiesCopy = new Properties(); synchronized (systemProperties) { for (Map.Entry entry: systemProperties.entrySet()) { @@ -342,7 +343,7 @@ public static void reloadSystemPropertiesConfig() { } private static AbstractConfigObject loadEnvVariables() { - return PropertiesParser.fromStringMap(newEnvVariable("env variables"), System.getenv()); + return PropertiesParser.fromStringMap(newSimpleOrigin("env variables"), SystemOverride.getenv()); } private static class EnvVariablesHolder { @@ -370,8 +371,8 @@ public static void reloadEnvVariablesConfig() { private static AbstractConfigObject loadEnvVariablesOverrides() { - Map env = new HashMap(System.getenv()); - Map result = new HashMap(); + Map env = new HashMap<>(SystemOverride.getenv()); + Map result = new HashMap<>(); for (String key : env.keySet()) { if (key.startsWith(ENV_VAR_OVERRIDE_PREFIX)) { @@ -453,7 +454,7 @@ private static Map loadDiagnostics() { result.put(SUBSTITUTIONS, false); // People do -Dconfig.trace=foo,bar to enable tracing of different things - String s = System.getProperty("config.trace"); + String s = SystemOverride.getProperty("config.trace"); if (s == null) { return result; } else { @@ -464,7 +465,7 @@ private static Map loadDiagnostics() { } else if (k.equals(SUBSTITUTIONS)) { result.put(SUBSTITUTIONS, true); } else { - System.err.println("config.trace property contains unknown trace topic '" + SystemOverride.err().println("config.trace property contains unknown trace topic '" + k + "'"); } } @@ -503,15 +504,15 @@ public static boolean traceSubstitutionsEnabled() { } public static void trace(String message) { - System.err.println(message); + SystemOverride.err().println(message); } public static void trace(int indentLevel, String message) { while (indentLevel > 0) { - System.err.print(" "); + SystemOverride.err().print(" "); indentLevel -= 1; } - System.err.println(message); + SystemOverride.err().println(message); } // the basic idea here is to add the "what" and have a canonical diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigSubstitutionTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigSubstitutionTest.scala index 61047c40..97977236 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigSubstitutionTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigSubstitutionTest.scala @@ -10,6 +10,7 @@ import com.typesafe.config.ConfigException import com.typesafe.config.ConfigResolveOptions import com.typesafe.config.Config import com.typesafe.config.ConfigFactory +import com.typesafe.config.SystemOverride import scala.collection.JavaConverters._ class ConfigSubstitutionTest extends TestUtils { @@ -731,11 +732,20 @@ class ConfigSubstitutionTest extends TestUtils { |"a": ${testList} """.stripMargin) - System.setProperty("testList.0", "0") - System.setProperty("testList.1", "1") - ConfigImpl.reloadSystemPropertiesConfig() - - val resolved = resolve(ConfigFactory.systemProperties().withFallback(props).root.asInstanceOf[AbstractConfigObject]) + val systemProperties = Map( + "testList.0" -> "0", + "testList.1" -> "1" + ).asJava + + val resolved = SystemOverride.withSystemOverride( + systemProperties, + new java.util.HashMap[String, String](), + System.err, + () => { + ConfigImpl.reloadSystemPropertiesConfig() + resolve(ConfigFactory.systemProperties().withFallback(props).root.asInstanceOf[AbstractConfigObject]) + } + ) assertEquals(List("0", "1"), resolved.getList("a").unwrapped().asScala) }