diff --git a/org.eclipse.scout.rt.platform.test/src/test/java/org/eclipse/scout/rt/platform/config/PropertiesHelperTest.java b/org.eclipse.scout.rt.platform.test/src/test/java/org/eclipse/scout/rt/platform/config/PropertiesHelperTest.java
index c00a3da7201..9ff7a067d05 100644
--- a/org.eclipse.scout.rt.platform.test/src/test/java/org/eclipse/scout/rt/platform/config/PropertiesHelperTest.java
+++ b/org.eclipse.scout.rt.platform.test/src/test/java/org/eclipse/scout/rt/platform/config/PropertiesHelperTest.java
@@ -10,11 +10,9 @@
******************************************************************************/
package org.eclipse.scout.rt.platform.config;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
import java.io.IOException;
import java.net.MalformedURLException;
@@ -27,9 +25,13 @@
import java.util.List;
import java.util.Map;
+import org.eclipse.scout.rt.platform.util.CollectionUtility;
import org.eclipse.scout.rt.platform.util.IOUtility;
+import org.eclipse.scout.rt.platform.util.ImmutablePair;
import org.junit.Assert;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.mockito.Mockito;
/**
@@ -72,6 +74,9 @@ public class PropertiesHelperTest {
private static final String MAP_KEY = "mapKey";
private static final String EMPTY_KEY = "emptyKey";
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
@Test
public void testPropertiesHelper() throws Exception {
PropertiesHelper instance = new PropertiesHelper(SAMPLE_CONFIG_PROPS);
@@ -91,6 +96,10 @@ public void testNamespaceProperty() {
assertEquals(null, instance.getProperty(NAMESPACE_PROP + "-not-existing", null, NAMESPACE));
assertEquals(null, instance.getProperty(NAMESPACE_PROP, null, NAMESPACE + "-not-existing"));
assertEquals("defaultval", instance.getProperty(NAMESPACE_PROP, "defaultval", NAMESPACE + "-not-existing"));
+
+ PropertiesHelper spiedInstance = spy(instance);
+ when(spiedInstance.getEnvironmentVariable(NAMESPACE + "__" + NAMESPACE_PROP)).thenReturn(NAMESPACE_PROP_VAL + "-from-env");
+ assertThat(spiedInstance.getProperty(NAMESPACE_PROP, null, NAMESPACE), is(NAMESPACE_PROP_VAL + "-from-env"));
}
@Test
@@ -223,6 +232,140 @@ public void testPropertyList() {
}
}
+ @Test
+ public void testReadPropertyMapFromEnvironment() {
+ PropertiesHelper originalInstance = new PropertiesHelper(SAMPLE_CONFIG_PROPS);
+ PropertiesHelper spiedInstance = spy(originalInstance);
+ when(spiedInstance.getEnvironmentVariable("map_not_in_file")).thenReturn("{\"keya\": \"valuea\",\"keyb\": \"valueb\",\"keyc\": \"valuec\"}");
+
+ assertThat(spiedInstance.getPropertyMap("map.not.in.file"), is(CollectionUtility.hashMap(
+ new ImmutablePair<>("keya", "valuea"),
+ new ImmutablePair<>("keyb", "valueb"),
+ new ImmutablePair<>("keyc", "valuec"))));
+ }
+
+ @Test
+ public void testReadPropertyMapFromEnvironmentWithVariableReference() throws Exception {
+ PropertiesHelper originalInstance = new PropertiesHelper(SAMPLE_CONFIG_PROPS);
+ PropertiesHelper spiedInstance = spy(originalInstance);
+ try {
+ System.setProperty("sysProp", "sysPropVal");
+ System.setProperty("stringKey", "stringKeyValueFromSystemProperty");
+ when(spiedInstance.getEnvironmentVariable("envProp")).thenReturn("envPropVal");
+ when(spiedInstance.getEnvironmentVariable("intKey")).thenReturn("intKeyFromEnv");
+ when(spiedInstance.getEnvironmentVariable("map_not_in_file")).thenReturn("{" +
+ "\"propFromConfigProperties\": \"${otherProp}\"," +
+ "\"propFromSystemProperties\": \"${sysProp}\"," +
+ "\"propFromEnvironment\": \"${envProp}\"," +
+ "\"propFromConfigPropertiesOverriddenByEnv\": \"${intKey}\"," +
+ "\"propFromConfigPropertiesOverriddenBySystemProperty\": \"${stringKey}\"," +
+ "\"propFromConfigPropertiesInString\": \"test${longKey}testtest\"" +
+ "}");
+
+ assertThat(spiedInstance.getPropertyMap("map.not.in.file"), is(CollectionUtility.hashMap(
+ new ImmutablePair<>("propFromConfigProperties", "otherVal"),
+ new ImmutablePair<>("propFromSystemProperties", "sysPropVal"),
+ new ImmutablePair<>("propFromEnvironment", "envPropVal"),
+ new ImmutablePair<>("propFromConfigPropertiesOverriddenByEnv", "intKeyFromEnv"),
+ new ImmutablePair<>("propFromConfigPropertiesOverriddenBySystemProperty", "stringKeyValueFromSystemProperty"),
+ new ImmutablePair<>("propFromConfigPropertiesInString", "test2testtest"))));
+ }
+ finally {
+ System.clearProperty("sysProp");
+ System.clearProperty("stringKey");
+ }
+ }
+
+ @Test
+ public void testReadPropertyMapFromEnvironmentMixingWithOtherPropertyMapSources() throws Exception {
+ PropertiesHelper originalInstance = new PropertiesHelper(MAP_CONFIG_PROPS);
+ PropertiesHelper spiedInstance = spy(originalInstance);
+ when(spiedInstance.getEnvironmentVariable("mapKey")).thenReturn("{\"second\": \"zwei\", \"third\": null}");
+
+ assertThat(spiedInstance.getPropertyMap("mapKey"), is(CollectionUtility.hashMap(
+ new ImmutablePair<>("first", "one"),
+ new ImmutablePair<>("second", "zwei"),
+ new ImmutablePair<>("empty", null),
+ new ImmutablePair<>("last", "last"))));
+ }
+
+ @Test
+ public void testReadPropertyMapRespectingPrecedence() throws Exception {
+ PropertiesHelper originalInstance = new PropertiesHelper(MAP_CONFIG_PROPS);
+ PropertiesHelper spiedInstance = spy(originalInstance);
+ try {
+ System.setProperty("mapKey[first]", "one-from-system-property");
+ when(spiedInstance.getEnvironmentVariable("mapKey")).thenReturn("{\"first\": \"one-from-env\", \"second\": \"two-from-env\"}");
+
+ assertThat(spiedInstance.getPropertyMap("mapKey"), is(CollectionUtility.hashMap(
+ new ImmutablePair<>("first", "one-from-system-property"),
+ new ImmutablePair<>("second", "two-from-env"),
+ new ImmutablePair<>("third", "three"),
+ new ImmutablePair<>("empty", null),
+ new ImmutablePair<>("last", "last"))));
+ }
+ finally {
+ System.clearProperty("mapKey[first]");
+ }
+ }
+
+ @Test
+ public void testReadPropertyMapFromEnvironmentWithNamespace() throws Exception {
+ PropertiesHelper originalInstance = new PropertiesHelper(MAP_CONFIG_PROPS);
+ PropertiesHelper spiedInstance = spy(originalInstance);
+ when(spiedInstance.getEnvironmentVariable("mapKey")).thenReturn("{\"second\": \"zwei\", \"third\": null}");
+ when(spiedInstance.getEnvironmentVariable("namespace1__mapKey")).thenReturn("{\"a\": null, \"b\": \"b\", \"c\": \"ce\"}");
+ when(spiedInstance.getEnvironmentVariable("namespace2__mapKey")).thenReturn("{\"d\": \"de\", \"e\": \"ee\"}");
+
+ assertThat(spiedInstance.getPropertyMap("mapKey"), is(CollectionUtility.hashMap(
+ new ImmutablePair<>("first", "one"),
+ new ImmutablePair<>("second", "zwei"),
+ new ImmutablePair<>("empty", null),
+ new ImmutablePair<>("last", "last"))));
+ assertThat(spiedInstance.getPropertyMap("mapKey", "namespace1"), is(CollectionUtility.hashMap(
+ new ImmutablePair<>("b", "b"),
+ new ImmutablePair<>("c", "ce"))));
+ assertThat(spiedInstance.getPropertyMap("mapKey", "namespace2"), is(CollectionUtility.hashMap(
+ new ImmutablePair<>("d", "de"),
+ new ImmutablePair<>("e", "ee"))));
+ }
+
+ @Test
+ public void testExpectedExceptionForMalformedJsonStringMissingComma() {
+ PropertiesHelper originalInstance = new PropertiesHelper(MAP_CONFIG_PROPS);
+ PropertiesHelper spiedInstance = spy(originalInstance);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Error parsing value of environment variable 'mapKey' as JSON value");
+
+ when(spiedInstance.getEnvironmentVariable("mapKey")).thenReturn("{\"second\": \"zwei\" \"third\": null}"); // missing comma after "zwei"
+ spiedInstance.getPropertyMap("mapKey");
+ }
+
+ @Test
+ public void testExpectedExceptionForMalformedJsonStringMissingClosingBraces() {
+ PropertiesHelper originalInstance = new PropertiesHelper(MAP_CONFIG_PROPS);
+ PropertiesHelper spiedInstance = spy(originalInstance);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Error parsing value of environment variable 'mapKey' as JSON value");
+
+ when(spiedInstance.getEnvironmentVariable("mapKey")).thenReturn("{\"second\": \"zwei\", \"third\": null"); // missing } at the end
+ spiedInstance.getPropertyMap("mapKey");
+ }
+
+ @Test
+ public void testReadPropertyListFromEnvironment() throws Exception {
+ PropertiesHelper originalInstance = new PropertiesHelper(LIST_PROPS);
+ PropertiesHelper spiedInstance = spy(originalInstance);
+
+ when(spiedInstance.getEnvironmentVariable("list")).thenReturn("{\"0\": \"zero\", \"1\": \"one\", \"2\": \"two\", \"3\": \"three\"}");
+ when(spiedInstance.getEnvironmentVariable("listWithValidIndices")).thenReturn("{\"2\": \"two\", \"4\": \"four\", \"5\": \"five\"}");
+
+ assertThat(spiedInstance.getPropertyList("list"), is(Arrays.asList("zero", "one", "two", "three")));
+ assertThat(spiedInstance.getPropertyList("listWithValidIndices"), is(Arrays.asList("a", null, "two", "b", "four", "five")));
+ }
+
@Test
public void testPropertyString() {
PropertiesHelper instance = new PropertiesHelper(SAMPLE_CONFIG_PROPS);
diff --git a/org.eclipse.scout.rt.platform.test/src/test/resources/org/eclipse/scout/rt/platform/config/map-test.properties b/org.eclipse.scout.rt.platform.test/src/test/resources/org/eclipse/scout/rt/platform/config/map-test.properties
index fd7131b7ecf..52467e4d0c5 100644
--- a/org.eclipse.scout.rt.platform.test/src/test/resources/org/eclipse/scout/rt/platform/config/map-test.properties
+++ b/org.eclipse.scout.rt.platform.test/src/test/resources/org/eclipse/scout/rt/platform/config/map-test.properties
@@ -19,3 +19,6 @@ namespace|mapKey[a]=b
namespace|mapKey[c]=c
namespace|mapKey[c=not-a-valid-map-property
namespace|mapKey[]=not-a-valid-map-property-2
+namespace1|mapKey[a]=a
+namespace1|mapKey[a]=b
+namespace1|mapKey[c]=c
diff --git a/org.eclipse.scout.rt.platform/pom.xml b/org.eclipse.scout.rt.platform/pom.xml
index 120e2908094..dfb6b7ba3a3 100644
--- a/org.eclipse.scout.rt.platform/pom.xml
+++ b/org.eclipse.scout.rt.platform/pom.xml
@@ -26,6 +26,11 @@
${project.groupId}:${project.artifactId}
+
+ org.eclipse.scout.rt
+ org.eclipse.scout.json
+
+
org.jboss
diff --git a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/config/PropertiesHelper.java b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/config/PropertiesHelper.java
index 7cc0ad927b8..4a9de9a930c 100644
--- a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/config/PropertiesHelper.java
+++ b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/config/PropertiesHelper.java
@@ -22,13 +22,17 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Objects;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.Set;
+import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.scout.rt.platform.util.StringUtility;
+import org.json.JSONException;
+import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -89,6 +93,7 @@ public class PropertiesHelper {
public static final String CLASSPATH_PROTOCOL_NAME = "classpath";
public static final char PROTOCOL_DELIMITER = ':';
public static final char NAMESPACE_DELIMITER = '|';
+ public static final String NAMESPACE_DELIMITER_FOR_ENV = "__";
public static final char COLLECTION_DELIMITER_START = '[';
public static final char COLLECTION_DELIMITER_END = ']';
public static final String CLASSPATH_PREFIX = CLASSPATH_PROTOCOL_NAME + PROTOCOL_DELIMITER;
@@ -206,27 +211,41 @@ public String getProperty(String key, String defaultValue, String namespace) {
}
String propKey = toPropertyKey(key, namespace).toString();
- String value = null;
- // system config
- value = System.getProperty(propKey);
+ List> propertyValueRetrievers = Arrays.asList(
+ this::getSystemPropertyValue,
+ this::getEnvironmentPropertyValue,
+ this::getConfigPropertyValue);
+
+ return propertyValueRetrievers.stream()
+ .map(propertyValueRetriever -> propertyValueRetriever.apply(propKey))
+ .filter(Objects::nonNull)
+ .findFirst()
+ .orElse(defaultValue);
+ }
+
+ protected String getSystemPropertyValue(String propKey) {
+ String value = System.getProperty(propKey);
if (StringUtility.hasText(value)) {
return resolve(value, PLACEHOLDER_PATTERN);
}
+ return null;
+ }
- // environment config
+ protected String getEnvironmentPropertyValue(String propKey) {
String envValue = lookupEnvironmentVariableValue(propKey);
if (StringUtility.hasText(envValue)) {
return resolve(envValue, PLACEHOLDER_PATTERN);
}
+ return null;
+ }
- // properties file
- value = m_configProperties.get(propKey);
+ protected String getConfigPropertyValue(String propKey) {
+ String value = m_configProperties.get(propKey);
if (StringUtility.hasText(value)) {
return value;
}
-
- return defaultValue;
+ return null;
}
/**
@@ -239,10 +258,14 @@ public String getProperty(String key, String defaultValue, String namespace) {
* Original in uppercase: MY.PROPERTY
* Periods replaced, in uppercase: MY_PROPERTY
*
+ * The standard namespace delimiter (|) will always be replaced by a delimiter more suited for use in environment
+ * variables (__).
*/
protected String lookupEnvironmentVariableValue(String propKey) {
+ String nsDelimiterReplacedPropKey = propKey.replace(String.valueOf(NAMESPACE_DELIMITER), NAMESPACE_DELIMITER_FOR_ENV);
+
// 1. Original
- String value = getEnvironmentVariable(propKey);
+ String value = getEnvironmentVariable(nsDelimiterReplacedPropKey);
if (value != null) {
return value;
}
@@ -250,7 +273,7 @@ protected String lookupEnvironmentVariableValue(String propKey) {
// Periods in environment variable names are not POSIX compliant (See IEEE Standard 1003.1-2017, Chapter 8.1 "Environment Variable Definition"),
// but supported by some shells. To allow overriding via environment variables (Bugzilla 541099) in any shell, convert them to underscores.
// 2. With periods replaced
- String keyWithoutDots = propKey.replace('.', ENVIRONMENT_VARIABLE_DOT_REPLACEMENT);
+ String keyWithoutDots = nsDelimiterReplacedPropKey.replace('.', ENVIRONMENT_VARIABLE_DOT_REPLACEMENT);
value = getEnvironmentVariable(keyWithoutDots);
if (value != null) {
logInexactEnvNameMatch(propKey, keyWithoutDots);
@@ -260,7 +283,7 @@ protected String lookupEnvironmentVariableValue(String propKey) {
// Applications may define environment variable names with lower case, but only upper case is POSIX compliant for the environment.
// To override from a shell, we should also check for upper case.
// 3. In Uppercase, original periods
- String uppercasedKey = propKey.toUpperCase();
+ String uppercasedKey = nsDelimiterReplacedPropKey.toUpperCase();
value = getEnvironmentVariable(uppercasedKey);
if (value != null) {
logInexactEnvNameMatch(propKey, uppercasedKey);
@@ -507,9 +530,9 @@ public Map getPropertyMap(String key, Map defaul
String keyPrefix = toCollectionKeyPrefix(key, namespace).toString();
Map result = new HashMap<>();
- collectMapEntriesWith(keyPrefix, System.getenv().keySet(), result);
- collectMapEntriesWith(keyPrefix, m_configProperties.keySet(), result);
- collectMapEntriesWith(keyPrefix, System.getProperties().keySet(), result);
+ collectMapEntriesWith(keyPrefix, m_configProperties.keySet(), this::getConfigPropertyValue, result);
+ collectMapEntriesFromEnvironment(key, namespace, result);
+ collectMapEntriesWith(keyPrefix, System.getProperties().keySet(), this::getSystemPropertyValue, result);
if (result.isEmpty()) {
return defaultValue;
@@ -795,13 +818,34 @@ public boolean isInitialized() {
return m_isInitialized;
}
- protected void collectMapEntriesWith(String keyPrefix, Set> keySet, Map collector) {
+ protected void collectMapEntriesWith(String keyPrefix, Set> keySet, Function propertyValueRetriever, Map collector) {
for (Object propKey : keySet) {
String k = propKey.toString();
String mapKey = toMapKey(k, keyPrefix);
if (mapKey != null) {
- // we can overwrite here because the old entry has already the same value
- collector.put(mapKey, getProperty(k));
+ collector.put(mapKey, propertyValueRetriever.apply(k));
+ }
+ }
+ }
+
+ protected void collectMapEntriesFromEnvironment(String key, String namespace, Map collector) {
+ String envKey = toPropertyKey(key, namespace).toString();
+ String keyValueFromEnv = lookupEnvironmentVariableValue(envKey);
+ if (StringUtility.hasText(keyValueFromEnv)) {
+ try {
+ JSONObject jsonObject = new JSONObject(keyValueFromEnv);
+ for (String mapKey : jsonObject.keySet()) {
+ Object mapValue = jsonObject.get(mapKey);
+ if (JSONObject.NULL.equals(mapValue)) {
+ collector.remove(mapKey);
+ }
+ else {
+ collector.put(mapKey, resolve((String) mapValue, PLACEHOLDER_PATTERN));
+ }
+ }
+ }
+ catch (JSONException e) {
+ throw new IllegalArgumentException(String.format("Error parsing value of environment variable '%s' as JSON value", envKey), e);
}
}
}