Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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,11 @@ 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;
private static final int MAX_SUBSTITUTION_DEPTH = 5;


static {
Expand All @@ -106,6 +112,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 +329,76 @@ 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, MAX_SUBSTITUTION_DEPTH));
});

// 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 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<String> resolveInProgressProps, int remainingDepth) {
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.");
continue;
}
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);
}
}
} 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,19 @@ 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"));
} 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"));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
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

# 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