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>

Reply via email to