This is an automated email from the ASF dual-hosted git repository.
lukaszlenart pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/struts.git
The following commit(s) were added to refs/heads/main by this push:
new da2bcf678 WW-5767 Apply value substitution to scoped-constant values
(#2007)
da2bcf678 is described below
commit da2bcf6783cda302da94216cd91d81e94d4aa3d0
Author: Lukasz Lenart <[email protected]>
AuthorDate: Mon Oct 12 19:56:39 2026 +0200
WW-5767 Apply value substitution to scoped-constant values (#2007)
* WW-5767 feat(config): apply value substitution to scoped-constant values
<scoped-constant> values now go through the injected ValueSubstitutor at
load time, the same as <constant>, so ${env.NAME} and ${system.property}
resolve
instead of being stored literally. Both paths share one helper.
The test loads struts-default.xml so the substitutor comes from the main
container that loadPackages() is injected with; the ConfigurationTestBase
container registers none and could not observe the change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* WW-5767 test(config): cover scoped constants loaded without a
ValueSubstitutor
The substitutor is injected with required = false, so a provider used
without injection keeps values as written. Every harness in the suite
injects one through DefaultConfiguration.bootstrapFactories, so nothing
exercised that branch.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
---
.../providers/XmlDocConfigurationProvider.java | 16 +++---
.../config/ScopedConstantsSubstitutionTest.java | 65 ++++++++++++++++++++++
.../XmlConfigurationProviderPackagesTest.java | 18 ++++++
.../struts-scoped-constants-substitution.xml | 33 +++++++++++
4 files changed, 125 insertions(+), 7 deletions(-)
diff --git
a/core/src/main/java/org/apache/struts2/config/providers/XmlDocConfigurationProvider.java
b/core/src/main/java/org/apache/struts2/config/providers/XmlDocConfigurationProvider.java
index b6c606e66..a5547d935 100644
---
a/core/src/main/java/org/apache/struts2/config/providers/XmlDocConfigurationProvider.java
+++
b/core/src/main/java/org/apache/struts2/config/providers/XmlDocConfigurationProvider.java
@@ -277,14 +277,16 @@ public abstract class XmlDocConfigurationProvider
implements ConfigurationProvid
protected void registerConstant(Element child, LocatableProperties props) {
String name = child.getAttribute("name");
- String value = child.getAttribute("value");
+ String value = substituteValue(child.getAttribute("value"));
+ props.setProperty(name, value, child);
+ }
- if (valueSubstitutor != null) {
- LOG.debug("Substituting value [{}] using [{}]", value,
valueSubstitutor.getClass().getName());
- value = valueSubstitutor.substitute(value);
+ private String substituteValue(String value) {
+ if (valueSubstitutor == null) {
+ return value;
}
-
- props.setProperty(name, value, child);
+ LOG.debug("Substituting value [{}] using [{}]", value,
valueSubstitutor.getClass().getName());
+ return valueSubstitutor.substitute(value);
}
protected void registerUnknownHandlerStack(Element child) {
@@ -852,7 +854,7 @@ public abstract class XmlDocConfigurationProvider
implements ConfigurationProvid
throw new ConfigurationException(format("Package [%s] declares
scoped constant [%s] more than once",
packageContext.getName(), name), constantElement);
}
- packageContext.addScopedConstant(name,
constantElement.getAttribute("value"));
+ packageContext.addScopedConstant(name,
substituteValue(constantElement.getAttribute("value")));
});
}
diff --git
a/core/src/test/java/org/apache/struts2/config/ScopedConstantsSubstitutionTest.java
b/core/src/test/java/org/apache/struts2/config/ScopedConstantsSubstitutionTest.java
new file mode 100644
index 000000000..2dc7f53c7
--- /dev/null
+++
b/core/src/test/java/org/apache/struts2/config/ScopedConstantsSubstitutionTest.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.struts2.config;
+
+import org.apache.struts2.ActionContext;
+import org.apache.struts2.inject.Container;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ScopedConstantsSubstitutionTest {
+
+ private static final String PROPERTY = "ww5767.value";
+
+ private ConfigurationManager configurationManager;
+
+ @Before
+ public void setUp() {
+ System.setProperty(PROPERTY, "from-system-property");
+ }
+
+ @After
+ public void tearDown() {
+ System.clearProperty(PROPERTY);
+ if (configurationManager != null) {
+ configurationManager.destroyConfiguration();
+ }
+ ActionContext.clear();
+ }
+
+ @Test
+ public void valuesAreSubstitutedLikeGlobalConstants() {
+ configurationManager = new
ConfigurationManager(Container.DEFAULT_NAME);
+ configurationManager.addContainerProvider(new
DefaultPropertiesProvider());
+ configurationManager.addContainerProvider(new
StrutsXmlConfigurationProvider("struts-default.xml"));
+ configurationManager.addContainerProvider(new
StrutsXmlConfigurationProvider(
+
"org/apache/struts2/config/struts-scoped-constants-substitution.xml"));
+ configurationManager.addContainerProvider(new
StrutsBeanSelectionProvider());
+
+ Configuration configuration = configurationManager.getConfiguration();
+
+
assertThat(configuration.getPackageConfig("substituted").getScopedConstants())
+ .isEqualTo(Map.of("sample.scopable", "from-system-property",
"sample.unset", "literal"));
+ }
+}
diff --git
a/core/src/test/java/org/apache/struts2/config/providers/XmlConfigurationProviderPackagesTest.java
b/core/src/test/java/org/apache/struts2/config/providers/XmlConfigurationProviderPackagesTest.java
index 35e2cdb2f..b866f6ce5 100644
---
a/core/src/test/java/org/apache/struts2/config/providers/XmlConfigurationProviderPackagesTest.java
+++
b/core/src/test/java/org/apache/struts2/config/providers/XmlConfigurationProviderPackagesTest.java
@@ -21,8 +21,12 @@ package org.apache.struts2.config.providers;
import org.apache.struts2.config.ConfigurationException;
import org.apache.struts2.config.ConfigurationProvider;
import org.apache.struts2.config.RuntimeConfiguration;
+import org.apache.struts2.config.StrutsXmlConfigurationProvider;
import org.apache.struts2.config.entities.PackageConfig;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import javax.xml.parsers.DocumentBuilderFactory;
import java.util.List;
import java.util.Map;
@@ -229,6 +233,20 @@ public class XmlConfigurationProviderPackagesTest extends
ConfigurationTestBase
}
}
+ public void testScopedConstantIsKeptAsWrittenWithoutValueSubstitutor()
throws Exception {
+ Document document =
DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+ Element packageElement = document.createElement("package");
+ Element scopedConstant = document.createElement("scoped-constant");
+ scopedConstant.setAttribute("name", "first");
+ scopedConstant.setAttribute("value", "${ww5767.value}");
+ packageElement.appendChild(scopedConstant);
+ PackageConfig.Builder packageContext = new
PackageConfig.Builder("raw");
+
+ new
StrutsXmlConfigurationProvider("unused.xml").loadScopedConstants(packageContext,
packageElement);
+
+ assertEquals(Map.of("first", "${ww5767.value}"),
packageContext.build().getScopedConstants());
+ }
+
private String getXmlConfigFilePath(String fileName) {
return "org/apache/struts2/config/providers/" + fileName;
}
diff --git
a/core/src/test/resources/org/apache/struts2/config/struts-scoped-constants-substitution.xml
b/core/src/test/resources/org/apache/struts2/config/struts-scoped-constants-substitution.xml
new file mode 100644
index 000000000..0b0f5e166
--- /dev/null
+++
b/core/src/test/resources/org/apache/struts2/config/struts-scoped-constants-substitution.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+-->
+<!DOCTYPE struts PUBLIC
+ "-//Apache Software Foundation//DTD Struts Configuration 7.5//EN"
+ "struts-7.5.dtd">
+<struts>
+ <bean type="org.apache.struts2.config.ScopableConstants" name="sample"
+ class="org.apache.struts2.config.SampleScopableConstants"/>
+
+ <package name="substituted" namespace="/substituted">
+ <scoped-constant name="sample.scopable" value="${ww5767.value}"/>
+ <scoped-constant name="sample.unset" value="literal"/>
+ </package>
+</struts>