Skip to content

Commit 1450f9d

Browse files
authored
Merge pull request #496 from venmanyarun/expansion_variable_server_env
adding expansion variable resolution for server.env props
2 parents d7b1499 + d345847 commit 1450f9d

File tree

3 files changed

+117
-6
lines changed

3 files changed

+117
-6
lines changed

src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* (C) Copyright IBM Corporation 2017, 2025.
2+
* (C) Copyright IBM Corporation 2017, 2026.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -34,6 +34,8 @@
3434
import java.util.Set;
3535
import java.util.Map;
3636
import java.util.Properties;
37+
import java.util.regex.Matcher;
38+
import java.util.regex.Pattern;
3739

3840
import javax.xml.XMLConstants;
3941
import javax.xml.parsers.DocumentBuilder;
@@ -46,13 +48,12 @@
4648
import javax.xml.xpath.XPathFactory;
4749

4850
import io.openliberty.tools.common.plugins.util.LibertyPropFilesUtility;
51+
import io.openliberty.tools.common.plugins.util.OSUtil;
4952
import io.openliberty.tools.common.plugins.util.PluginExecutionException;
5053
import org.apache.commons.io.comparator.NameFileComparator;
5154
import org.w3c.dom.Document;
5255
import org.w3c.dom.Element;
5356
import org.w3c.dom.NodeList;
54-
import org.w3c.dom.Node;
55-
import org.w3c.dom.NamedNodeMap;
5657
import org.xml.sax.SAXException;
5758

5859
import io.openliberty.tools.common.CommonLoggerI;
@@ -88,6 +89,10 @@ public class ServerConfigDocument {
8889
private static final XPathExpression XPATH_SERVER_INCLUDE;
8990
public static final XPathExpression XPATH_SERVER_VARIABLE;
9091
private static final XPathExpression XPATH_ALL_SERVER_APPLICATIONS;
92+
// Windows style: !VAR!
93+
private static final Pattern WINDOWS_EXPANSION_VAR_PATTERN;
94+
// Linux style: ${VAR}
95+
private static final Pattern LINUX_EXPANSION_VAR_PATTERN;
9196

9297

9398
static {
@@ -106,6 +111,8 @@ public class ServerConfigDocument {
106111
// correct
107112
throw new RuntimeException(ex);
108113
}
114+
WINDOWS_EXPANSION_VAR_PATTERN = Pattern.compile("!(\\w+)!");
115+
LINUX_EXPANSION_VAR_PATTERN = Pattern.compile("\\$\\{(\\w+)\\}");
109116
}
110117

111118
public Set<String> getLocations() {
@@ -321,6 +328,64 @@ public void processServerEnv() throws Exception, FileNotFoundException {
321328
parsePropertiesFromFile(new File(libertyDirectoryPropertyToFile.get(ServerFeatureUtil.WLP_USER_DIR),
322329
"shared" + File.separator + serverEnvString));
323330
parsePropertiesFromFile(getFileFromConfigDirectory(serverEnvString));
331+
Map<String, String> resolvedMap = new HashMap<>();
332+
333+
props.forEach((k, v) -> {
334+
String key = (String) k;
335+
String value = (String) v;
336+
Set<String> resolveInProgressProps = new HashSet<>();
337+
resolveInProgressProps.add(key);
338+
resolvedMap.put(key, resolveExpansionProperties(props, value, key, resolveInProgressProps));
339+
});
340+
341+
// After all resolutions are calculated, update the original props
342+
props.putAll(resolvedMap);
343+
}
344+
345+
/**
346+
* Resolves property placeholders recursively with safety guards.
347+
* Uses appendReplacement to ensure a single-pass scan and strict depth control.
348+
*
349+
* @param props The properties source.
350+
* @param value The string currently being processed.
351+
* @param key key of property being processed.
352+
* @param resolveInProgressProps The set of variables in the current stack to detect loops.
353+
* @return The resolved string or raw text if depth/circularity limits are hit.
354+
*/
355+
private String resolveExpansionProperties(Properties props, String value, String key, Set<String> resolveInProgressProps) {
356+
if (value == null) return null;
357+
Pattern pattern = OSUtil.isWindows() ? WINDOWS_EXPANSION_VAR_PATTERN : LINUX_EXPANSION_VAR_PATTERN;
358+
Matcher matcher = pattern.matcher(value);
359+
StringBuffer sb = new StringBuffer();
360+
while (matcher.find()) {
361+
String finalReplacement;
362+
String varName = matcher.group(1);
363+
// 2. Circular Reference Guard
364+
if (resolveInProgressProps.contains(varName)) {
365+
log.warn("Circular reference detected: " + varName + " depends on itself in value " + value + ". Skipping expansion.");
366+
break;
367+
}
368+
String replacement = props.getProperty(varName);
369+
if (replacement != null) {
370+
// 3. Recursive call
371+
// Add to stack before recursing
372+
resolveInProgressProps.add(varName);
373+
try {
374+
finalReplacement = resolveExpansionProperties(props, replacement, key, resolveInProgressProps);
375+
} finally {
376+
// Remove from stack after finishing this branch (backtracking)
377+
resolveInProgressProps.remove(varName);
378+
}
379+
} else {
380+
// Variable not found in Properties; leave the original ${VAR} or !VAR!
381+
finalReplacement = matcher.group(0); // Keep original
382+
}
383+
matcher.appendReplacement(sb, Matcher.quoteReplacement(finalReplacement));
384+
log.debug(String.format("Resolving Property %s for %s. Resolved value is %s", varName , value , sb));
385+
}
386+
// 4. Finalize the string
387+
matcher.appendTail(sb);
388+
return sb.toString();
324389
}
325390

326391
/**

src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* (C) Copyright IBM Corporation 2023, 2024.
2+
* (C) Copyright IBM Corporation 2023, 2026.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
1515
*/
1616
package io.openliberty.tools.common.plugins.util;
1717

18+
import static org.junit.Assert.assertEquals;
1819
import static org.junit.Assert.assertTrue;
1920

2021
import org.junit.Test;
@@ -91,7 +92,20 @@ public void testAppLocationUsesLibertyProperty() throws Exception {
9192
assertTrue("App location four not found.", locFourFound);
9293
assertTrue("App location five not found.", locFiveFound);
9394
assertTrue("App location six not found.", locSixFound);
94-
95+
if (OSUtil.isWindows()) {
96+
assertEquals("Variable Expanded for !VAR!", "DEFINED_VAL", scd.getProperties().getProperty("this2_value"));
97+
assertEquals("Variable Expanded for ${VAR}", "DEFINED\\old_value\\dir", scd.getProperties().getProperty("this5_value"));
98+
assertEquals("Variable Expanded for recursive this8_value=!this5_value!\\!overriden_value!\\dir", "DEFINED\\old_value\\dir\\old_value\\dir", scd.getProperties().getProperty("this8_value"));
99+
assertEquals("circular or self reference value is not resolved", "var_var_!circ_v1_win!", scd.getProperties().getProperty("circ_v1_win"));
100+
} else {
101+
assertEquals("Variable Expanded for ${VAR}", "DEFINED_VAL", scd.getProperties().getProperty("this3_value"));
102+
assertEquals("Variable Expanded for ${VAR}", "DEFINED/old_value/dir", scd.getProperties().getProperty("this4_value"));
103+
assertEquals("Variable Expanded for recursive this7_value=${this3_value}/${overriden_value}/dir", "DEFINED_VAL/old_value/dir", scd.getProperties().getProperty("this7_value"));
104+
assertEquals("circular reference value is not resolved", "DEFINED_VAL/${self_ref_value}", scd.getProperties().getProperty("self_ref_value"));
105+
assertEquals("recursive reference resolved", "v7_v6_v5_v4_v3_v2_1", scd.getProperties().getProperty("depth_max"));
106+
assertEquals("recursive reference resolved", "v7_v6_v5_v4_v3_v2_1", scd.getProperties().getProperty("depth_v7"));
107+
}
108+
assertEquals("Variable not Expanded for !this_val", "!this_val", scd.getProperties().getProperty("this6_value"));
95109
}
96110

97111
/**
Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,37 @@
11
keystore_password=C7ANPlAi0MQD154BJ5ZOURn
22
http.port=1111
3+
# --- Base Definitions ---
34
overriden_value=old_value
45
this_value=DEFINED
5-
bootstrap.properties.override=false
6+
bootstrap.properties.override=false
7+
8+
# Combines multiple recursive lookups with forward property substitution linux
9+
this7_value=${this3_value}/${overriden_value}/dir
10+
# Combines multiple recursive lookups linux
11+
this4_value=${this_value}/${overriden_value}/dir
12+
this3_value=${this_value}_VAL
13+
# self reference -> will throw warning but no infinite loop
14+
self_ref_value=${this3_value}/${self_ref_value}
15+
# circular reference -> will throw warning but no infinite loop
16+
circ_v1=var_${circ_v2}
17+
circ_v2=var_${circ_v1}
18+
19+
# Combines multiple recursive lookups with forward property substitution windows
20+
this8_value=!this5_value!\\!overriden_value!\\dir
21+
this2_value=!this_value!_VAL
22+
# Combines multiple recursive lookups windows
23+
this5_value=!this_value!\\!overriden_value!\\dir
24+
this6_value=!this_val
25+
# circular reference -> will throw warning but no infinite loop
26+
circ_v1_win=var_!circ_v2_win!
27+
circ_v2_win=var_!circ_v1_win!
28+
29+
# testing max recursion level, here since max level is 5, depth_max, depth_v7 will not be resolved
30+
depth_max=${depth_v7}
31+
depth_v7=v7_${depth_v6}
32+
depth_v6=v6_${depth_v5}
33+
depth_v5=v5_${depth_v4}
34+
depth_v4=v4_${depth_v3}
35+
depth_v3=v3_${depth_v2}
36+
depth_v2=v2_${depth_v1}
37+
depth_v1=1

0 commit comments

Comments
 (0)