Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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;
Expand All @@ -46,13 +48,12 @@
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;
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;
Expand Down Expand Up @@ -88,6 +89,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 {
Expand All @@ -106,6 +111,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<String> getLocations() {
Expand Down Expand Up @@ -321,6 +328,64 @@ public void processServerEnv() throws Exception, FileNotFoundException {
parsePropertiesFromFile(new File(libertyDirectoryPropertyToFile.get(ServerFeatureUtil.WLP_USER_DIR),
"shared" + File.separator + serverEnvString));
parsePropertiesFromFile(getFileFromConfigDirectory(serverEnvString));
Map<String, String> resolvedMap = new HashMap<>();

props.forEach((k, v) -> {
String key = (String) k;
String value = (String) v;
Set<String> resolveInProgressProps = new HashSet<>();
resolveInProgressProps.add(key);
resolvedMap.put(key, resolveExpansionProperties(props, value, key, resolveInProgressProps));
});

// After all resolutions are calculated, update the original props
props.putAll(resolvedMap);
}

/**
* 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 key key of property being processed.
* @param resolveInProgressProps The set of variables in the current stack to detect loops.
* @return The resolved string or raw text if depth/circularity limits are hit.
*/
private String resolveExpansionProperties(Properties props, String value, String key, Set<String> resolveInProgressProps) {
if (value == null) return null;
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 value " + value + ". Skipping expansion.");
break;
}
String replacement = props.getProperty(varName);
if (replacement != null) {
// 3. Recursive call
// 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!
finalReplacement = matcher.group(0); // Keep original
}
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);
return sb.toString();
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -91,7 +92,20 @@ 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"));
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"));
} 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 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"));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
keystore_password=C7ANPlAi0MQD154BJ5ZOURn
http.port=1111
# --- Base Definitions ---
overriden_value=old_value
this_value=DEFINED
bootstrap.properties.override=false
bootstrap.properties.override=false

# 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
# 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}
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