Skip to content

Commit d0c9f82

Browse files
Merge pull request #780 from mvanhorn/feat/526-env-placeholder-resolution
Resolve property placeholders in target-env files
2 parents b4c392e + 29db52e commit d0c9f82

3 files changed

Lines changed: 96 additions & 1 deletion

File tree

core/src/main/java/org/jsmart/zerocode/core/di/main/ApplicationMainModule.java

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
import com.google.inject.AbstractModule;
44
import com.google.inject.name.Names;
5+
import java.util.Map;
56
import java.util.Properties;
67
import java.util.logging.Logger;
8+
import java.util.regex.Matcher;
9+
import java.util.regex.Pattern;
710
import org.jsmart.zerocode.core.di.module.CsvParserModule;
811
import org.jsmart.zerocode.core.di.module.GsonModule;
912
import org.jsmart.zerocode.core.di.module.HttpClientModule;
@@ -30,13 +33,16 @@
3033
import org.jsmart.zerocode.core.runner.ZeroCodeMultiStepsScenarioRunner;
3134
import org.jsmart.zerocode.core.runner.ZeroCodeMultiStepsScenarioRunnerImpl;
3235

36+
import static org.jsmart.zerocode.core.utils.EnvUtils.getEnvValueString;
3337
import static org.jsmart.zerocode.core.utils.PropertiesProviderUtils.checkAndLoadOldProperties;
3438
import static org.jsmart.zerocode.core.utils.PropertiesProviderUtils.loadAbsoluteProperties;
3539
import static org.jsmart.zerocode.core.utils.SmartUtils.isValidAbsolutePath;
3640

3741
public class ApplicationMainModule extends AbstractModule {
3842
private static final Logger LOGGER = Logger.getLogger(ApplicationMainModule.class.getName());
3943

44+
private static final Pattern ENV_PLACEHOLDER_PATTERN = Pattern.compile("\\$\\{([^}]+)\\}");
45+
4046
private final String serverEnv;
4147

4248
public ApplicationMainModule(String serverEnv) {
@@ -79,7 +85,7 @@ public Properties getProperties(String host) {
7985
final Properties properties = new Properties();
8086

8187
if(isValidAbsolutePath(host)){
82-
return loadAbsoluteProperties(host, properties);
88+
return resolveEnvPlaceholders(loadAbsoluteProperties(host, properties));
8389
}
8490

8591
try {
@@ -96,7 +102,54 @@ public Properties getProperties(String host) {
96102
throw new RuntimeException("could not read the target-env properties file --" + host + "-- from the classpath.");
97103
}
98104

105+
return resolveEnvPlaceholders(properties);
106+
}
107+
108+
/**
109+
* Resolves any {@code ${name}} placeholders found in the loaded property values against
110+
* system properties and OS environment variables (in that order, via
111+
* {@link org.jsmart.zerocode.core.utils.EnvUtils#getEnvValueString(String)}).
112+
* <p>
113+
* This lets a target-env properties file reference values supplied on the command line, e.g.
114+
* <pre>
115+
* db.username=${db.username}
116+
* </pre>
117+
* run with {@code -Ddb.username=alice} so the injected value becomes {@code alice}.
118+
* <p>
119+
* A placeholder whose name resolves to no system property or env var is left untouched, so
120+
* files that legitimately contain {@code ${...}} for downstream tooling are not corrupted.
121+
* Values without any placeholder are returned unchanged.
122+
*/
123+
public Properties resolveEnvPlaceholders(Properties properties) {
124+
if (properties == null) {
125+
return null;
126+
}
127+
128+
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
129+
Object value = entry.getValue();
130+
if (value instanceof String) {
131+
properties.setProperty((String) entry.getKey(), resolvePlaceholders((String) value));
132+
}
133+
}
134+
99135
return properties;
100136
}
101137

138+
private static String resolvePlaceholders(String value) {
139+
Matcher matcher = ENV_PLACEHOLDER_PATTERN.matcher(value);
140+
StringBuffer resolved = new StringBuffer();
141+
142+
while (matcher.find()) {
143+
String placeholderName = matcher.group(1);
144+
String replacement = getEnvValueString(placeholderName);
145+
146+
// Leave the placeholder literally in place when it cannot be resolved (backward compatible).
147+
String token = replacement != null ? replacement : matcher.group(0);
148+
matcher.appendReplacement(resolved, Matcher.quoteReplacement(token));
149+
}
150+
matcher.appendTail(resolved);
151+
152+
return resolved.toString();
153+
}
154+
102155
}

core/src/test/java/org/jsmart/zerocode/core/di/ApplicationMainModuleTest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import com.google.inject.AbstractModule;
44
import com.google.inject.Inject;
55
import com.google.inject.name.Named;
6+
import java.util.Properties;
67
import org.jsmart.zerocode.core.di.main.ApplicationMainModule;
78
import org.jsmart.zerocode.core.guice.ZeroCodeGuiceTestRule;
89
import org.jsmart.zerocode.core.utils.SmartUtils;
10+
import org.junit.After;
911
import org.junit.Rule;
1012
import org.junit.Test;
1113
import org.junit.rules.ExpectedException;
@@ -49,4 +51,40 @@ public void willInject_host() throws Exception {
4951
assertThat(host, is("http://localhost-test"));
5052
}
5153

54+
@After
55+
public void clearTestSystemProperties() {
56+
System.clearProperty("zc.test.host");
57+
}
58+
59+
@Test
60+
public void willResolveSystemPropertyPlaceholder() throws Exception {
61+
System.setProperty("zc.test.host", "http://resolved-host");
62+
63+
ApplicationMainModule module = new ApplicationMainModule("config_hosts_test.properties");
64+
Properties properties = module.getProperties("config_hosts_test.properties");
65+
66+
assertThat(properties.getProperty("placeholder.resolved.host"), is("http://resolved-host"));
67+
}
68+
69+
@Test
70+
public void willKeepUnresolvedPlaceholderLiteral() throws Exception {
71+
ApplicationMainModule module = new ApplicationMainModule("config_hosts_test.properties");
72+
Properties properties = module.getProperties("config_hosts_test.properties");
73+
74+
// No matching system property / env var -> placeholder is preserved verbatim (backward compatible).
75+
assertThat(properties.getProperty("placeholder.unresolved.host"), is("${zc.test.absent.host}"));
76+
}
77+
78+
@Test
79+
public void willReturnPlainPropertyUnchanged() throws Exception {
80+
ApplicationMainModule module = new ApplicationMainModule("config_hosts_test.properties");
81+
82+
Properties input = new Properties();
83+
input.setProperty("plain.key", "plain-value-without-placeholder");
84+
85+
Properties resolved = module.resolveEnvPlaceholders(input);
86+
87+
assertThat(resolved.getProperty("plain.key"), is("plain-value-without-placeholder"));
88+
}
89+
5290
}

core/src/test/resources/config_hosts_test.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@ web.application.endpoint.port=8820
44

55
# Google Map Service Context
66
web.application.endpoint.context=/google-map-services
7+
8+
# Placeholders resolved against system properties / OS env vars at load time
9+
placeholder.resolved.host=${zc.test.host}
10+
placeholder.unresolved.host=${zc.test.absent.host}

0 commit comments

Comments
 (0)