From 4aba9d2e8bc33f7d864e0bf58c2f2c486fae5efa Mon Sep 17 00:00:00 2001 From: Arun Venmany Date: Thu, 29 Jan 2026 12:49:42 +0530 Subject: [PATCH 01/10] adding expansion variable resolution for server.env props Signed-off-by: Arun Venmany --- .../plugins/config/ServerConfigDocument.java | 47 ++++++++++++++++++- .../util/ServerConfigDocumentTest.java | 9 +++- .../wlp/usr/servers/defaultServer/server.env | 4 +- 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java b/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java index 89a04f30d..5a9ae6d57 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java +++ b/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java @@ -1,5 +1,5 @@ /** - * (C) Copyright IBM Corporation 2017, 2025. + * (C) Copyright IBM Corporation 2017, 2026. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,8 @@ import java.util.Set; import java.util.Map; import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; @@ -46,6 +48,7 @@ import javax.xml.xpath.XPathFactory; import io.openliberty.tools.common.plugins.util.LibertyPropFilesUtility; +import io.openliberty.tools.common.plugins.util.OSUtil; import io.openliberty.tools.common.plugins.util.PluginExecutionException; import org.apache.commons.io.comparator.NameFileComparator; import org.w3c.dom.Document; @@ -88,6 +91,10 @@ public class ServerConfigDocument { private static final XPathExpression XPATH_SERVER_INCLUDE; public static final XPathExpression XPATH_SERVER_VARIABLE; private static final XPathExpression XPATH_ALL_SERVER_APPLICATIONS; + // Windows style: !VAR! + private static final Pattern WINDOWS_EXPANSION_VAR_PATTERN; + // Linux style: ${VAR} + private static final Pattern LINUX_EXPANSION_VAR_PATTERN; static { @@ -106,6 +113,8 @@ public class ServerConfigDocument { // correct throw new RuntimeException(ex); } + WINDOWS_EXPANSION_VAR_PATTERN = Pattern.compile("!(\\w+)!"); + LINUX_EXPANSION_VAR_PATTERN = Pattern.compile("\\$\\{(\\w+)\\}"); } public Set getLocations() { @@ -321,6 +330,42 @@ public void processServerEnv() throws Exception, FileNotFoundException { parsePropertiesFromFile(new File(libertyDirectoryPropertyToFile.get(ServerFeatureUtil.WLP_USER_DIR), "shared" + File.separator + serverEnvString)); parsePropertiesFromFile(getFileFromConfigDirectory(serverEnvString)); + props.forEach((k, v) -> props.setProperty((String)k, resolveExpansionProperties(props, (String)v))); + } + + /** + * Resolves both Windows (!VAR!) and Linux (${VAR}) placeholders. + */ + private String resolveExpansionProperties(Properties props, String value) { + if (value == null) return null; + // Resolve Linux style or Windows style + String result; + if (OSUtil.isWindows()) { + result = resolveByPattern(props, value, WINDOWS_EXPANSION_VAR_PATTERN); + } else { + result = resolveByPattern(props, value, LINUX_EXPANSION_VAR_PATTERN); + } + return result; + } + + private String resolveByPattern(Properties props, String value, Pattern pattern) { + StringBuilder sb = new StringBuilder(value); + Matcher matcher = pattern.matcher(sb); + + while (matcher.find()) { + String varName = matcher.group(1); + String replacement = props.getProperty(varName); + if (replacement != null) { + // Recursively resolve the replacement + String resolvedReplacement = resolveExpansionProperties(props, replacement); + String sbBeforeReplacement = sb.toString(); + sb.replace(matcher.start(), matcher.end(), resolvedReplacement); + log.debug("Found a recursive variable reference when resolving "+ sbBeforeReplacement +" in server.env. Resolved value is " + sb); + // Reset matcher because the string length changed + matcher = pattern.matcher(sb); + } + } + return sb.toString(); } /** diff --git a/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java b/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java index 686044f17..8c36de1fb 100644 --- a/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java +++ b/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java @@ -1,5 +1,5 @@ /** - * (C) Copyright IBM Corporation 2023, 2024. + * (C) Copyright IBM Corporation 2023, 2026. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package io.openliberty.tools.common.plugins.util; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import org.junit.Test; @@ -91,7 +92,11 @@ public void testAppLocationUsesLibertyProperty() throws Exception { assertTrue("App location four not found.", locFourFound); assertTrue("App location five not found.", locFiveFound); assertTrue("App location six not found.", locSixFound); - + if (OSUtil.isWindows()) { + assertEquals("Variable Expanded for !VAR!", "DEFINED_VAL", scd.getProperties().getProperty("this2_value")); + } else { + assertEquals("Variable Expanded for ${VAR}", "DEFINED_VAL", scd.getProperties().getProperty("this3_value")); + } } /** diff --git a/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.env b/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.env index 9caccadeb..d444b0b41 100644 --- a/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.env +++ b/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.env @@ -2,4 +2,6 @@ keystore_password=C7ANPlAi0MQD154BJ5ZOURn http.port=1111 overriden_value=old_value this_value=DEFINED -bootstrap.properties.override=false \ No newline at end of file +bootstrap.properties.override=false +this2_value=!this_value!_VAL +this3_value=${this_value}_VAL \ No newline at end of file From bf90e73cf2a527f723c1278895bf4fb921afa52c Mon Sep 17 00:00:00 2001 From: Arun Venmany Date: Thu, 29 Jan 2026 13:06:02 +0530 Subject: [PATCH 02/10] adding more tests Signed-off-by: Arun Venmany --- .../tools/common/plugins/util/ServerConfigDocumentTest.java | 3 +++ .../liberty/wlp/usr/servers/defaultServer/server.env | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java b/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java index 8c36de1fb..3a9a15cbe 100644 --- a/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java +++ b/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java @@ -94,9 +94,12 @@ public void testAppLocationUsesLibertyProperty() throws Exception { assertTrue("App location six not found.", locSixFound); if (OSUtil.isWindows()) { assertEquals("Variable Expanded for !VAR!", "DEFINED_VAL", scd.getProperties().getProperty("this2_value")); + assertEquals("Variable Expanded for ${VAR}", "DEFINED\\old_value\\dir", scd.getProperties().getProperty("this5_value")); } else { assertEquals("Variable Expanded for ${VAR}", "DEFINED_VAL", scd.getProperties().getProperty("this3_value")); + assertEquals("Variable Expanded for ${VAR}", "DEFINED/old_value/dir", scd.getProperties().getProperty("this4_value")); } + assertEquals("Variable not Expanded for !this_val", "!this_val", scd.getProperties().getProperty("this6_value")); } /** diff --git a/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.env b/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.env index d444b0b41..e0566e493 100644 --- a/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.env +++ b/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.env @@ -4,4 +4,7 @@ overriden_value=old_value this_value=DEFINED bootstrap.properties.override=false this2_value=!this_value!_VAL -this3_value=${this_value}_VAL \ No newline at end of file +this3_value=${this_value}_VAL +this4_value=${this_value}/${overriden_value}/dir +this5_value=!this_value!\\!overriden_value!\\dir +this6_value=!this_val \ No newline at end of file From a86c3ebdb43e761de9cda7a43baf0232c7975b21 Mon Sep 17 00:00:00 2001 From: Arun Venmany Date: Fri, 30 Jan 2026 16:11:41 +0530 Subject: [PATCH 03/10] adding code for setting max recursion depth and eliminate circular ref Signed-off-by: Arun Venmany --- .../plugins/config/ServerConfigDocument.java | 80 +++++++++++++------ .../util/ServerConfigDocumentTest.java | 5 ++ .../wlp/usr/servers/defaultServer/server.env | 30 ++++++- 3 files changed, 88 insertions(+), 27 deletions(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java b/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java index 5a9ae6d57..d3772a547 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java +++ b/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java @@ -54,8 +54,6 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; -import org.w3c.dom.Node; -import org.w3c.dom.NamedNodeMap; import org.xml.sax.SAXException; import io.openliberty.tools.common.CommonLoggerI; @@ -95,6 +93,7 @@ public class ServerConfigDocument { private static final Pattern WINDOWS_EXPANSION_VAR_PATTERN; // Linux style: ${VAR} private static final Pattern LINUX_EXPANSION_VAR_PATTERN; + private static final int MAX_SUBSTITUTION_DEPTH = 5; static { @@ -330,41 +329,74 @@ public void processServerEnv() throws Exception, FileNotFoundException { parsePropertiesFromFile(new File(libertyDirectoryPropertyToFile.get(ServerFeatureUtil.WLP_USER_DIR), "shared" + File.separator + serverEnvString)); parsePropertiesFromFile(getFileFromConfigDirectory(serverEnvString)); - props.forEach((k, v) -> props.setProperty((String)k, resolveExpansionProperties(props, (String)v))); + Map resolvedMap = new HashMap<>(); + + props.forEach((k, v) -> { + String key = (String) k; + String value = (String) v; + Set resolveInProgressProps = new HashSet<>(); + resolveInProgressProps.add(key); + resolvedMap.put(key, resolveExpansionProperties(props, value,key, resolveInProgressProps, MAX_SUBSTITUTION_DEPTH)); + }); + + // After all resolutions are calculated, update the original props + props.putAll(resolvedMap); } /** - * Resolves both Windows (!VAR!) and Linux (${VAR}) placeholders. + * Resolves property placeholders recursively with safety guards. + * Uses appendReplacement to ensure a single-pass scan and strict depth control. + * + * @param props The properties source. + * @param value The string currently being processed. + * @param resolveInProgressProps The set of variables in the current stack to detect loops. + * @param remainingDepth Remaining levels of recursion allowed. + * @return The resolved string or raw text if depth/circularity limits are hit. */ - private String resolveExpansionProperties(Properties props, String value) { + private String resolveExpansionProperties(Properties props, String value, String key, Set resolveInProgressProps, int remainingDepth) { if (value == null) return null; - // Resolve Linux style or Windows style - String result; - if (OSUtil.isWindows()) { - result = resolveByPattern(props, value, WINDOWS_EXPANSION_VAR_PATTERN); - } else { - result = resolveByPattern(props, value, LINUX_EXPANSION_VAR_PATTERN); - } - return result; - } - private String resolveByPattern(Properties props, String value, Pattern pattern) { - StringBuilder sb = new StringBuilder(value); - Matcher matcher = pattern.matcher(sb); + // 1. Initial Depth Check + if (remainingDepth <= 0) { + log.warn("Max substitution depth reached for key: " + key + ". Returning raw value: " + value); + return value; + } + Pattern pattern = OSUtil.isWindows() ? WINDOWS_EXPANSION_VAR_PATTERN : LINUX_EXPANSION_VAR_PATTERN; + Matcher matcher = pattern.matcher(value); + StringBuffer sb = new StringBuffer(); while (matcher.find()) { String varName = matcher.group(1); + + // 2. Circular Reference Guard + if (resolveInProgressProps.contains(varName)) { + log.warn("Circular reference detected: " + varName + " depends on itself in key " + key + ". Skipping expansion."); + matcher.appendReplacement(sb, Matcher.quoteReplacement(matcher.group(0))); + continue; + } String replacement = props.getProperty(varName); if (replacement != null) { - // Recursively resolve the replacement - String resolvedReplacement = resolveExpansionProperties(props, replacement); - String sbBeforeReplacement = sb.toString(); - sb.replace(matcher.start(), matcher.end(), resolvedReplacement); - log.debug("Found a recursive variable reference when resolving "+ sbBeforeReplacement +" in server.env. Resolved value is " + sb); - // Reset matcher because the string length changed - matcher = pattern.matcher(sb); + // 3. Recursive Logic with Depth Guard + if (remainingDepth <= 1) { + log.warn("Depth limit hit at '" + varName + "'. Appending raw value without further expansion."); + matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement)); + } else { + resolveInProgressProps.add(varName); + try { + String resolved = resolveExpansionProperties(props, replacement, key, resolveInProgressProps, remainingDepth - 1); + matcher.appendReplacement(sb, Matcher.quoteReplacement(resolved)); + } finally { + resolveInProgressProps.remove(varName); + } + } + } else { + // Variable not found in Properties; leave the original ${VAR} or !VAR! + matcher.appendReplacement(sb, Matcher.quoteReplacement(matcher.group(0))); } + log.debug("Resolving Property " + varName + "value with " + sb); } + // 4. Finalize the string + matcher.appendTail(sb); return sb.toString(); } diff --git a/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java b/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java index 3a9a15cbe..0ca1efba4 100644 --- a/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java +++ b/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java @@ -95,9 +95,14 @@ public void testAppLocationUsesLibertyProperty() throws Exception { if (OSUtil.isWindows()) { assertEquals("Variable Expanded for !VAR!", "DEFINED_VAL", scd.getProperties().getProperty("this2_value")); assertEquals("Variable Expanded for ${VAR}", "DEFINED\\old_value\\dir", scd.getProperties().getProperty("this5_value")); + assertEquals("Variable Expanded for recursive this8_value=!this5_value!\\!overriden_value!\\dir", "DEFINED_VAL\\old_value\\dir\\old_value\\dir", scd.getProperties().getProperty("this8_value")); } else { assertEquals("Variable Expanded for ${VAR}", "DEFINED_VAL", scd.getProperties().getProperty("this3_value")); assertEquals("Variable Expanded for ${VAR}", "DEFINED/old_value/dir", scd.getProperties().getProperty("this4_value")); + assertEquals("Variable Expanded for recursive this7_value=${this3_value}/${overriden_value}/dir", "DEFINED_VAL/old_value/dir", scd.getProperties().getProperty("this7_value")); + assertEquals("circular or self reference value is not resolved", "DEFINED_VAL/${self_ref_value}", scd.getProperties().getProperty("self_ref_value")); + assertEquals("recursive reference stopped with more than max", "v7_v6_v5_v4_v3_${depth_v2}", scd.getProperties().getProperty("depth_max")); + assertEquals("recursive reference stopped with more than max", "v7_v6_v5_v4_v3_v2_${depth_v1}", scd.getProperties().getProperty("depth_v7")); } assertEquals("Variable not Expanded for !this_val", "!this_val", scd.getProperties().getProperty("this6_value")); } diff --git a/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.env b/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.env index e0566e493..e0fbc3228 100644 --- a/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.env +++ b/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.env @@ -1,10 +1,34 @@ keystore_password=C7ANPlAi0MQD154BJ5ZOURn http.port=1111 +# --- Base Definitions --- overriden_value=old_value this_value=DEFINED bootstrap.properties.override=false -this2_value=!this_value!_VAL -this3_value=${this_value}_VAL + +# Combines multiple recursive lookups with forward property substitution linux +this7_value=${this3_value}/${overriden_value}/dir +# Combines multiple recursive lookups linux this4_value=${this_value}/${overriden_value}/dir +this3_value=${this_value}_VAL +# self reference -> will throw warning but no infinite loop +self_ref_value=${this3_value}/${self_ref_value} +# circular reference -> will throw warning but no infinite loop +circ_v1=var_${circ_v2} +circ_v2=var_${circ_v1} + +# Combines multiple recursive lookups with forward property substitution windows +this8_value=!this5_value!\\!overriden_value!\\dir +this2_value=!this_value!_VAL +# Combines multiple recursive lookups windows this5_value=!this_value!\\!overriden_value!\\dir -this6_value=!this_val \ No newline at end of file +this6_value=!this_val + +# testing max recursion level, here since max level is 5, depth_max, depth_v7 will not be resolved +depth_max=${depth_v7} +depth_v7=v7_${depth_v6} +depth_v6=v6_${depth_v5} +depth_v5=v5_${depth_v4} +depth_v4=v4_${depth_v3} +depth_v3=v3_${depth_v2} +depth_v2=v2_${depth_v1} +depth_v1=1 \ No newline at end of file From a5020f1e1601cea03ef7105d0b0eefffe65a3bdb Mon Sep 17 00:00:00 2001 From: Arun Venmany Date: Fri, 30 Jan 2026 16:13:44 +0530 Subject: [PATCH 04/10] fixing windows tests Signed-off-by: Arun Venmany --- .../tools/common/plugins/util/ServerConfigDocumentTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java b/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java index 0ca1efba4..0fa15d932 100644 --- a/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java +++ b/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java @@ -95,7 +95,7 @@ public void testAppLocationUsesLibertyProperty() throws Exception { if (OSUtil.isWindows()) { assertEquals("Variable Expanded for !VAR!", "DEFINED_VAL", scd.getProperties().getProperty("this2_value")); assertEquals("Variable Expanded for ${VAR}", "DEFINED\\old_value\\dir", scd.getProperties().getProperty("this5_value")); - assertEquals("Variable Expanded for recursive this8_value=!this5_value!\\!overriden_value!\\dir", "DEFINED_VAL\\old_value\\dir\\old_value\\dir", scd.getProperties().getProperty("this8_value")); + assertEquals("Variable Expanded for recursive this8_value=!this5_value!\\!overriden_value!\\dir", "DEFINED\\old_value\\dir\\old_value\\dir", scd.getProperties().getProperty("this8_value")); } else { assertEquals("Variable Expanded for ${VAR}", "DEFINED_VAL", scd.getProperties().getProperty("this3_value")); assertEquals("Variable Expanded for ${VAR}", "DEFINED/old_value/dir", scd.getProperties().getProperty("this4_value")); From 0e6b31220f4e9c1e1bac26ab614755a814f26cf1 Mon Sep 17 00:00:00 2001 From: Arun Venmany Date: Fri, 30 Jan 2026 16:32:16 +0530 Subject: [PATCH 05/10] updated log message Signed-off-by: Arun Venmany --- .../common/plugins/config/ServerConfigDocument.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java b/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java index d3772a547..3a72d8415 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java +++ b/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java @@ -366,12 +366,12 @@ private String resolveExpansionProperties(Properties props, String value, String StringBuffer sb = new StringBuffer(); while (matcher.find()) { + String finalReplacement; String varName = matcher.group(1); // 2. Circular Reference Guard if (resolveInProgressProps.contains(varName)) { log.warn("Circular reference detected: " + varName + " depends on itself in key " + key + ". Skipping expansion."); - matcher.appendReplacement(sb, Matcher.quoteReplacement(matcher.group(0))); continue; } String replacement = props.getProperty(varName); @@ -379,21 +379,22 @@ private String resolveExpansionProperties(Properties props, String value, String // 3. Recursive Logic with Depth Guard if (remainingDepth <= 1) { log.warn("Depth limit hit at '" + varName + "'. Appending raw value without further expansion."); - matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement)); + finalReplacement = replacement; } else { resolveInProgressProps.add(varName); try { String resolved = resolveExpansionProperties(props, replacement, key, resolveInProgressProps, remainingDepth - 1); - matcher.appendReplacement(sb, Matcher.quoteReplacement(resolved)); + finalReplacement = resolved; } finally { resolveInProgressProps.remove(varName); } } } else { // Variable not found in Properties; leave the original ${VAR} or !VAR! - matcher.appendReplacement(sb, Matcher.quoteReplacement(matcher.group(0))); + finalReplacement = matcher.group(0); // Keep original } - log.debug("Resolving Property " + varName + "value with " + sb); + matcher.appendReplacement(sb, Matcher.quoteReplacement(finalReplacement)); + log.debug(String.format("Resolving Property %s for %s. Resolved value is %s", varName , value , sb)); } // 4. Finalize the string matcher.appendTail(sb); From 7c49c87e8daf2998c5aba6a7d43f3337717a425c Mon Sep 17 00:00:00 2001 From: Arun Venmany Date: Mon, 2 Feb 2026 10:03:15 +0530 Subject: [PATCH 06/10] removed max depth Signed-off-by: Arun Venmany --- .../plugins/config/ServerConfigDocument.java | 33 ++++++------------- .../util/ServerConfigDocumentTest.java | 8 +++-- .../wlp/usr/servers/defaultServer/server.env | 3 ++ 3 files changed, 18 insertions(+), 26 deletions(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java b/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java index 3a72d8415..d23733814 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java +++ b/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java @@ -93,7 +93,6 @@ public class ServerConfigDocument { private static final Pattern WINDOWS_EXPANSION_VAR_PATTERN; // Linux style: ${VAR} private static final Pattern LINUX_EXPANSION_VAR_PATTERN; - private static final int MAX_SUBSTITUTION_DEPTH = 5; static { @@ -336,7 +335,7 @@ public void processServerEnv() throws Exception, FileNotFoundException { String value = (String) v; Set resolveInProgressProps = new HashSet<>(); resolveInProgressProps.add(key); - resolvedMap.put(key, resolveExpansionProperties(props, value,key, resolveInProgressProps, MAX_SUBSTITUTION_DEPTH)); + resolvedMap.put(key, resolveExpansionProperties(props, value, key, resolveInProgressProps)); }); // After all resolutions are calculated, update the original props @@ -349,26 +348,18 @@ public void processServerEnv() throws Exception, FileNotFoundException { * * @param props The properties source. * @param value The string currently being processed. + * @param key key of property being processed. * @param resolveInProgressProps The set of variables in the current stack to detect loops. - * @param remainingDepth Remaining levels of recursion allowed. * @return The resolved string or raw text if depth/circularity limits are hit. */ - private String resolveExpansionProperties(Properties props, String value, String key, Set resolveInProgressProps, int remainingDepth) { + private String resolveExpansionProperties(Properties props, String value, String key, Set resolveInProgressProps) { if (value == null) return null; - - // 1. Initial Depth Check - if (remainingDepth <= 0) { - log.warn("Max substitution depth reached for key: " + key + ". Returning raw value: " + value); - return value; - } Pattern pattern = OSUtil.isWindows() ? WINDOWS_EXPANSION_VAR_PATTERN : LINUX_EXPANSION_VAR_PATTERN; Matcher matcher = pattern.matcher(value); StringBuffer sb = new StringBuffer(); - while (matcher.find()) { String finalReplacement; String varName = matcher.group(1); - // 2. Circular Reference Guard if (resolveInProgressProps.contains(varName)) { log.warn("Circular reference detected: " + varName + " depends on itself in key " + key + ". Skipping expansion."); @@ -377,17 +368,13 @@ private String resolveExpansionProperties(Properties props, String value, String String replacement = props.getProperty(varName); if (replacement != null) { // 3. Recursive Logic with Depth Guard - if (remainingDepth <= 1) { - log.warn("Depth limit hit at '" + varName + "'. Appending raw value without further expansion."); - finalReplacement = replacement; - } else { - resolveInProgressProps.add(varName); - try { - String resolved = resolveExpansionProperties(props, replacement, key, resolveInProgressProps, remainingDepth - 1); - finalReplacement = resolved; - } finally { - resolveInProgressProps.remove(varName); - } + // Add to stack before recursing + resolveInProgressProps.add(varName); + try { + finalReplacement = resolveExpansionProperties(props, replacement, key, resolveInProgressProps); + } finally { + // Remove from stack after finishing this branch (backtracking) + resolveInProgressProps.remove(varName); } } else { // Variable not found in Properties; leave the original ${VAR} or !VAR! diff --git a/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java b/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java index 0fa15d932..5b2e2083e 100644 --- a/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java +++ b/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java @@ -96,13 +96,15 @@ public void testAppLocationUsesLibertyProperty() throws Exception { assertEquals("Variable Expanded for !VAR!", "DEFINED_VAL", scd.getProperties().getProperty("this2_value")); assertEquals("Variable Expanded for ${VAR}", "DEFINED\\old_value\\dir", scd.getProperties().getProperty("this5_value")); assertEquals("Variable Expanded for recursive this8_value=!this5_value!\\!overriden_value!\\dir", "DEFINED\\old_value\\dir\\old_value\\dir", scd.getProperties().getProperty("this8_value")); + assertEquals("circular reference value is not resolved", "DEFINED_VAL/${self_ref_value}", scd.getProperties().getProperty("self_ref_value")); + assertEquals("circular or self reference value is not resolved", "var_!circ_v2_win!", scd.getProperties().getProperty("circ_v1_win")); } else { assertEquals("Variable Expanded for ${VAR}", "DEFINED_VAL", scd.getProperties().getProperty("this3_value")); assertEquals("Variable Expanded for ${VAR}", "DEFINED/old_value/dir", scd.getProperties().getProperty("this4_value")); assertEquals("Variable Expanded for recursive this7_value=${this3_value}/${overriden_value}/dir", "DEFINED_VAL/old_value/dir", scd.getProperties().getProperty("this7_value")); - assertEquals("circular or self reference value is not resolved", "DEFINED_VAL/${self_ref_value}", scd.getProperties().getProperty("self_ref_value")); - assertEquals("recursive reference stopped with more than max", "v7_v6_v5_v4_v3_${depth_v2}", scd.getProperties().getProperty("depth_max")); - assertEquals("recursive reference stopped with more than max", "v7_v6_v5_v4_v3_v2_${depth_v1}", scd.getProperties().getProperty("depth_v7")); + assertEquals("circular reference value is not resolved", "DEFINED_VAL/${self_ref_value}", scd.getProperties().getProperty("self_ref_value")); + assertEquals("recursive reference resolved", "v7_v6_v5_v4_v3_v2_1", scd.getProperties().getProperty("depth_max")); + assertEquals("recursive reference resolved", "v7_v6_v5_v4_v3_v2_1", scd.getProperties().getProperty("depth_v7")); } assertEquals("Variable not Expanded for !this_val", "!this_val", scd.getProperties().getProperty("this6_value")); } diff --git a/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.env b/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.env index e0fbc3228..f729de54f 100644 --- a/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.env +++ b/src/test/resources/serverConfig/liberty/wlp/usr/servers/defaultServer/server.env @@ -22,6 +22,9 @@ this2_value=!this_value!_VAL # Combines multiple recursive lookups windows this5_value=!this_value!\\!overriden_value!\\dir this6_value=!this_val +# circular reference -> will throw warning but no infinite loop +circ_v1_win=var_!circ_v2_win! +circ_v2_win=var_!circ_v1_win! # testing max recursion level, here since max level is 5, depth_max, depth_v7 will not be resolved depth_max=${depth_v7} From c1c60e45f334c2a1a0745912f0e13cd0cc673fbe Mon Sep 17 00:00:00 2001 From: Arun Venmany Date: Mon, 2 Feb 2026 10:06:52 +0530 Subject: [PATCH 07/10] removed max depth Signed-off-by: Arun Venmany --- .../tools/common/plugins/util/ServerConfigDocumentTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java b/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java index 5b2e2083e..4ef6455b4 100644 --- a/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java +++ b/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java @@ -96,7 +96,6 @@ public void testAppLocationUsesLibertyProperty() throws Exception { assertEquals("Variable Expanded for !VAR!", "DEFINED_VAL", scd.getProperties().getProperty("this2_value")); assertEquals("Variable Expanded for ${VAR}", "DEFINED\\old_value\\dir", scd.getProperties().getProperty("this5_value")); assertEquals("Variable Expanded for recursive this8_value=!this5_value!\\!overriden_value!\\dir", "DEFINED\\old_value\\dir\\old_value\\dir", scd.getProperties().getProperty("this8_value")); - assertEquals("circular reference value is not resolved", "DEFINED_VAL/${self_ref_value}", scd.getProperties().getProperty("self_ref_value")); assertEquals("circular or self reference value is not resolved", "var_!circ_v2_win!", scd.getProperties().getProperty("circ_v1_win")); } else { assertEquals("Variable Expanded for ${VAR}", "DEFINED_VAL", scd.getProperties().getProperty("this3_value")); From ecada0f28677a5dc4f3c257f4e77c79e24eb01b9 Mon Sep 17 00:00:00 2001 From: Arun Venmany Date: Mon, 2 Feb 2026 10:09:03 +0530 Subject: [PATCH 08/10] removed max depth Signed-off-by: Arun Venmany --- .../tools/common/plugins/util/ServerConfigDocumentTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java b/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java index 4ef6455b4..772edf856 100644 --- a/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java +++ b/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java @@ -96,7 +96,7 @@ public void testAppLocationUsesLibertyProperty() throws Exception { assertEquals("Variable Expanded for !VAR!", "DEFINED_VAL", scd.getProperties().getProperty("this2_value")); assertEquals("Variable Expanded for ${VAR}", "DEFINED\\old_value\\dir", scd.getProperties().getProperty("this5_value")); assertEquals("Variable Expanded for recursive this8_value=!this5_value!\\!overriden_value!\\dir", "DEFINED\\old_value\\dir\\old_value\\dir", scd.getProperties().getProperty("this8_value")); - assertEquals("circular or self reference value is not resolved", "var_!circ_v2_win!", scd.getProperties().getProperty("circ_v1_win")); + assertEquals("circular or self reference value is not resolved", "var_!var_circ_v1_win!", scd.getProperties().getProperty("circ_v1_win")); } else { assertEquals("Variable Expanded for ${VAR}", "DEFINED_VAL", scd.getProperties().getProperty("this3_value")); assertEquals("Variable Expanded for ${VAR}", "DEFINED/old_value/dir", scd.getProperties().getProperty("this4_value")); From 4110f675b787ce8f47ca33b1d0147b831e87276a Mon Sep 17 00:00:00 2001 From: Arun Venmany Date: Mon, 2 Feb 2026 10:12:29 +0530 Subject: [PATCH 09/10] removed max depth Signed-off-by: Arun Venmany --- .../tools/common/plugins/util/ServerConfigDocumentTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java b/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java index 772edf856..a3cbacbc7 100644 --- a/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java +++ b/src/test/java/io/openliberty/tools/common/plugins/util/ServerConfigDocumentTest.java @@ -96,7 +96,7 @@ public void testAppLocationUsesLibertyProperty() throws Exception { assertEquals("Variable Expanded for !VAR!", "DEFINED_VAL", scd.getProperties().getProperty("this2_value")); assertEquals("Variable Expanded for ${VAR}", "DEFINED\\old_value\\dir", scd.getProperties().getProperty("this5_value")); assertEquals("Variable Expanded for recursive this8_value=!this5_value!\\!overriden_value!\\dir", "DEFINED\\old_value\\dir\\old_value\\dir", scd.getProperties().getProperty("this8_value")); - assertEquals("circular or self reference value is not resolved", "var_!var_circ_v1_win!", scd.getProperties().getProperty("circ_v1_win")); + assertEquals("circular or self reference value is not resolved", "var_var_!circ_v1_win!", scd.getProperties().getProperty("circ_v1_win")); } else { assertEquals("Variable Expanded for ${VAR}", "DEFINED_VAL", scd.getProperties().getProperty("this3_value")); assertEquals("Variable Expanded for ${VAR}", "DEFINED/old_value/dir", scd.getProperties().getProperty("this4_value")); From d345847b44bbf63b260be8d6c326667ff6b246ab Mon Sep 17 00:00:00 2001 From: Arun Venmany Date: Mon, 2 Feb 2026 19:56:46 +0530 Subject: [PATCH 10/10] changes based on review comments Signed-off-by: Arun Venmany --- .../tools/common/plugins/config/ServerConfigDocument.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java b/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java index d23733814..fd6e80aaf 100644 --- a/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java +++ b/src/main/java/io/openliberty/tools/common/plugins/config/ServerConfigDocument.java @@ -362,12 +362,12 @@ private String resolveExpansionProperties(Properties props, String value, String String varName = matcher.group(1); // 2. Circular Reference Guard if (resolveInProgressProps.contains(varName)) { - log.warn("Circular reference detected: " + varName + " depends on itself in key " + key + ". Skipping expansion."); - continue; + log.warn("Circular reference detected: " + varName + " depends on itself in value " + value + ". Skipping expansion."); + break; } String replacement = props.getProperty(varName); if (replacement != null) { - // 3. Recursive Logic with Depth Guard + // 3. Recursive call // Add to stack before recursing resolveInProgressProps.add(varName); try {