This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch 
fix/WW-5586-enable-withlazyparams-stack-configuration
in repository https://gitbox.apache.org/repos/asf/struts.git

commit c828dc2b77ffc99bfbfcff23f039d3d6338a4ef4
Author: Lukasz Lenart <[email protected]>
AuthorDate: Sun Nov 16 10:31:52 2025 +0100

    fix(core): enable WithLazyParams interceptor configuration in stacks
    
    Resolves WW-5586
    
    Previously, interceptors implementing WithLazyParams could not be
    configured via interceptor stack parameters because 
DefaultInterceptorFactory
    skipped property setting entirely for these interceptors.
    
    Changes:
    - DefaultInterceptorFactory: Always set properties for all interceptors
    - WithLazyParams: Updated JavaDoc to clarify dual initialization
    - Added test for stack parameter configuration with WithLazyParams
    
    Expression parameters are re-evaluated at invocation time via 
LazyParamInjector,
    while static parameters remain unchanged. This enables both static 
configuration
    (e.g., allowedTypes) and dynamic expressions (e.g., ${maxUploadSize}) in the
    same interceptor stack reference.
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    Co-Authored-By: Claude <[email protected]>
---
 .../struts2/factory/DefaultInterceptorFactory.java |   5 +-
 .../apache/struts2/interceptor/WithLazyParams.java |  19 ++-
 .../struts2/DefaultActionInvocationTest.java       |  29 +++-
 .../apache/struts2/mock/MockLazyInterceptor.java   |  18 ++-
 core/src/test/resources/xwork-sample.xml           | 163 +++++++++++----------
 5 files changed, 147 insertions(+), 87 deletions(-)

diff --git 
a/core/src/main/java/org/apache/struts2/factory/DefaultInterceptorFactory.java 
b/core/src/main/java/org/apache/struts2/factory/DefaultInterceptorFactory.java
index 77b68e487..cd5bc09bd 100644
--- 
a/core/src/main/java/org/apache/struts2/factory/DefaultInterceptorFactory.java
+++ 
b/core/src/main/java/org/apache/struts2/factory/DefaultInterceptorFactory.java
@@ -68,11 +68,10 @@ public class DefaultInterceptorFactory implements 
InterceptorFactory {
                 throw new ConfigurationException("Class [" + 
interceptorClassName + "] does not implement Interceptor", interceptorConfig);
             }
 
+            reflectionProvider.setProperties(params, interceptor);
             if (interceptor instanceof WithLazyParams) {
-                LOG.debug("Interceptor {} is marked with interface {} and 
params will be set during action invocation",
+                LOG.debug("Interceptor {} implements {} - expression 
parameters will be re-evaluated during action invocation",
                         interceptorClassName, WithLazyParams.class.getName());
-            } else {
-                reflectionProvider.setProperties(params, interceptor);
             }
 
             interceptor.init();
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/WithLazyParams.java 
b/core/src/main/java/org/apache/struts2/interceptor/WithLazyParams.java
index 999546160..cdfb3a8ad 100644
--- a/core/src/main/java/org/apache/struts2/interceptor/WithLazyParams.java
+++ b/core/src/main/java/org/apache/struts2/interceptor/WithLazyParams.java
@@ -29,10 +29,19 @@ import 
org.apache.struts2.util.reflection.ReflectionProvider;
 import java.util.Map;
 
 /**
- * Interceptors marked with this interface won't be fully initialised during 
initialisation.
- * Appropriated params will be injected just before usage of the interceptor.
- *
- * Please be aware that in such case {@link Interceptor#init()} method must be 
prepared for this.
+ * Interceptors marked with this interface support dynamic parameter 
evaluation at action invocation time.
+ * Parameters are set during interceptor creation (factory time), then 
re-evaluated during each action
+ * invocation to resolve expressions like ${someValue}.
+ * <p>
+ * This enables both:
+ * <ul>
+ *   <li>Static configuration in interceptor stacks (e.g., 
allowedTypes="image/png,image/jpeg")</li>
+ *   <li>Dynamic expressions evaluated per-request (e.g., 
maximumSize="${maxUploadSize}")</li>
+ * </ul>
+ * <p>
+ * The {@link Interceptor#init()} method is called after initial parameter 
setting, so interceptors
+ * can rely on configured values during initialization. Expression parameters 
(containing ${...})
+ * are re-evaluated at invocation time via {@link LazyParamInjector}.
  *
  * @since 2.5.9
  */
@@ -68,7 +77,7 @@ public interface WithLazyParams {
 
         public Interceptor injectParams(Interceptor interceptor, Map<String, 
String> params, ActionContext invocationContext) {
             for (Map.Entry<String, String> entry : params.entrySet()) {
-                Object paramValue = textParser.evaluate(new char[]{ '$' }, 
entry.getValue(), valueEvaluator, TextParser.DEFAULT_LOOP_COUNT);
+                Object paramValue = textParser.evaluate(new char[]{'$'}, 
entry.getValue(), valueEvaluator, TextParser.DEFAULT_LOOP_COUNT);
                 ognlUtil.setProperty(entry.getKey(), paramValue, interceptor, 
invocationContext.getContextMap());
             }
             return interceptor;
diff --git 
a/core/src/test/java/org/apache/struts2/DefaultActionInvocationTest.java 
b/core/src/test/java/org/apache/struts2/DefaultActionInvocationTest.java
index 76d3cd432..02618d295 100644
--- a/core/src/test/java/org/apache/struts2/DefaultActionInvocationTest.java
+++ b/core/src/test/java/org/apache/struts2/DefaultActionInvocationTest.java
@@ -384,6 +384,33 @@ public class DefaultActionInvocationTest extends 
XWorkTestCase {
         assertEquals("this is blah", action.getName());
     }
 
+    /**
+     * Test for WW-5586: WithLazyParams interceptors can be configured in 
interceptor stacks
+     * with both static parameters and dynamic expression parameters.
+     */
+    public void testInvokeWithLazyParamsStackConfiguration() throws Exception {
+        HashMap<String, Object> params = new HashMap<>();
+        params.put("blah", "dynamic value");
+
+        ActionContext extraContext = ActionContext.of()
+                .withParameters(HttpParameters.create(params).build());
+
+        DefaultActionInvocation defaultActionInvocation = new 
DefaultActionInvocation(extraContext.getContextMap(), true);
+        container.inject(defaultActionInvocation);
+
+        ActionProxy actionProxy = actionProxyFactory.createActionProxy("", 
"LazyFooWithStackParams", null, extraContext.getContextMap());
+        defaultActionInvocation.init(actionProxy);
+        defaultActionInvocation.invoke();
+
+        SimpleAction action = (SimpleAction) 
defaultActionInvocation.getAction();
+
+        // Verify expression parameter is evaluated at invocation time
+        assertEquals("dynamic value", action.getName());
+
+        // Verify static parameter is set and not evaluated as expression
+        assertEquals("static value", action.getBlah());
+    }
+
     public void testInvokeWithAsyncManager() throws Exception {
         DefaultActionInvocation dai = new DefaultActionInvocation(new 
HashMap<>(), false);
         dai.stack = 
container.getInstance(ValueStackFactory.class).createValueStack();
@@ -458,7 +485,7 @@ public class DefaultActionInvocationTest extends 
XWorkTestCase {
 
     public void testActionEventListener() throws Exception {
         ActionProxy actionProxy = actionProxyFactory.createActionProxy("",
-            "ExceptionFoo", "exceptionMethod", new HashMap<>());
+                "ExceptionFoo", "exceptionMethod", new HashMap<>());
         DefaultActionInvocation defaultActionInvocation = 
(DefaultActionInvocation) actionProxy.getInvocation();
 
         SimpleActionEventListener actionEventListener = new 
SimpleActionEventListener("prepared", "exceptionHandled");
diff --git 
a/core/src/test/java/org/apache/struts2/mock/MockLazyInterceptor.java 
b/core/src/test/java/org/apache/struts2/mock/MockLazyInterceptor.java
index bc7dee2da..ae3aea3cc 100644
--- a/core/src/test/java/org/apache/struts2/mock/MockLazyInterceptor.java
+++ b/core/src/test/java/org/apache/struts2/mock/MockLazyInterceptor.java
@@ -21,20 +21,36 @@ package org.apache.struts2.mock;
 import org.apache.struts2.ActionInvocation;
 import org.apache.struts2.SimpleAction;
 import org.apache.struts2.interceptor.AbstractInterceptor;
-import org.apache.struts2.interceptor.Interceptor;
 import org.apache.struts2.interceptor.WithLazyParams;
 
 public class MockLazyInterceptor extends AbstractInterceptor implements 
WithLazyParams {
 
     private String foo = "";
+    private String bar = "";
 
     public void setFoo(String foo) {
         this.foo = foo;
     }
 
+    public String getFoo() {
+        return foo;
+    }
+
+    public void setBar(String bar) {
+        this.bar = bar;
+    }
+
+    public String getBar() {
+        return bar;
+    }
+
     public String intercept(ActionInvocation invocation) throws Exception {
         if (invocation.getAction() instanceof SimpleAction) {
             ((SimpleAction) invocation.getAction()).setName(foo);
+            // Only set blah if bar is configured (not empty)
+            if (bar != null && !bar.isEmpty()) {
+                ((SimpleAction) invocation.getAction()).setBlah(bar);
+            }
         }
         return invocation.invoke();
     }
diff --git a/core/src/test/resources/xwork-sample.xml 
b/core/src/test/resources/xwork-sample.xml
index a27985b1b..93c35c134 100644
--- a/core/src/test/resources/xwork-sample.xml
+++ b/core/src/test/resources/xwork-sample.xml
@@ -45,7 +45,7 @@
         </action>
 
         <action name="ExceptionFoo" class="org.apache.struts2.SimpleAction" 
method="exceptionMethod">
-            <result name="exceptionHandled" type="void" />
+            <result name="exceptionHandled" type="void"/>
             <interceptor-ref name="debugStack"/>
             <interceptor-ref name="defaultStack"/>
         </action>
@@ -56,77 +56,86 @@
         </action>
 
         <action name="LazyFoo" class="org.apache.struts2.SimpleAction">
-          <result name="error" type="void" />
-          <interceptor-ref name="params"/>
-          <interceptor-ref name="lazy">
-            <param name="foo">${blah}</param>
-          </interceptor-ref>
+            <result name="error" type="void"/>
+            <interceptor-ref name="params"/>
+            <interceptor-ref name="lazy">
+                <param name="foo">${blah}</param>
+            </interceptor-ref>
+        </action>
+
+        <action name="LazyFooWithStackParams" 
class="org.apache.struts2.SimpleAction">
+            <result name="error" type="void"/>
+            <interceptor-ref name="params"/>
+            <interceptor-ref name="lazy">
+                <param name="foo">${blah}</param>
+                <param name="bar">static value</param>
+            </interceptor-ref>
         </action>
 
         <action name="WildCard" class="org.apache.struts2.SimpleAction">
             <param name="foo">17</param>
             <param name="bar">23</param>
-            <result name="success" type="void" />
-            <result name="*" type="mock" />
+            <result name="success" type="void"/>
+            <result name="*" type="mock"/>
             <interceptor-ref name="defaultStack"/>
         </action>
 
         <action name="WildCardInput" class="org.apache.struts2.SimpleAction" 
method="input">
             <param name="foo">17</param>
             <param name="bar">23</param>
-            <result name="success" type="void" />
-            <result name="*" type="mock" />
+            <result name="success" type="void"/>
+            <result name="*" type="mock"/>
             <interceptor-ref name="defaultStack"/>
         </action>
 
         <action name="WildCardError" class="org.apache.struts2.SimpleAction">
-            <result name="success" type="void" />
-            <result name="*" type="mock" />
+            <result name="success" type="void"/>
+            <result name="*" type="mock"/>
             <interceptor-ref name="defaultStack"/>
         </action>
 
-               <action name="WildCard/*/*" class="org.apache.struts2.{1}Action"
-                       method="{2}">
+        <action name="WildCard/*/*" class="org.apache.struts2.{1}Action"
+                method="{2}">
             <param name="foo">{1}</param>
             <param name="bar">{2}</param>
-            <result name="success" type="void" />
+            <result name="success" type="void"/>
             <interceptor-ref name="defaultStack"/>
         </action>
 
         <action name="aliasTest" class="org.apache.struts2.SimpleAction">
-               <param name="aliases">#{ "aliasSource" : "aliasDest", 
"bar":"baz", "notExisting":"blah" }</param>
-                       <interceptor-ref name="params"/>
-               <interceptor-ref name="alias"/>
-               <result name="success" type="mock" />
+            <param name="aliases">#{ "aliasSource" : "aliasDest", "bar":"baz", 
"notExisting":"blah" }</param>
+            <interceptor-ref name="params"/>
+            <interceptor-ref name="alias"/>
+            <result name="success" type="mock"/>
         </action>
 
         <action name="dynamicAliasTest" 
class="org.apache.struts2.SimpleAction">
             <param name="aliases">#{ #parameters['name'] : 
#parameters['value'] }</param>
             <interceptor-ref name="params"/>
             <interceptor-ref name="alias"/>
-            <result name="success" type="mock" />
+            <result name="success" type="mock"/>
         </action>
 
         <action name="packagelessAction" class="PackagelessAction">
         </action>
 
         <action name="Bar" class="org.apache.struts2.SimpleAction">
-               <param name="foo">17</param>
+            <param name="foo">17</param>
             <param name="bar">23</param>
-            <result name="error" type="mock" />
+            <result name="error" type="mock"/>
         </action>
 
         <action name="MyBean" class="org.apache.struts2.util.MyBeanAction">
             <interceptor-ref name="debugStack"/>
             <interceptor-ref name="defaultStack"/>
-            <result name="success" type="mock" />
+            <result name="success" type="mock"/>
         </action>
 
         <action name="TestInterceptorParam" 
class="org.apache.struts2.SimpleAction">
             <interceptor-ref name="test">
                 <param name="expectedFoo">expectedFoo</param>
             </interceptor-ref>
-            <result name="error" type="mock" />
+            <result name="error" type="mock"/>
         </action>
 
         <action name="TestInterceptorParamOverride" 
class="org.apache.struts2.SimpleAction">
@@ -134,18 +143,18 @@
                 <param name="foo">foo123</param>
                 <param name="expectedFoo">foo123</param>
             </interceptor-ref>
-            <result name="error" type="mock" />
+            <result name="error" type="mock"/>
         </action>
 
         <action name="TestModelDrivenValidation" 
class="org.apache.struts2.ModelDrivenAction">
             <interceptor-ref name="defaultStack"/>
             <interceptor-ref name="validation"/>
-            <result name="success" type="mock" />
+            <result name="success" type="mock"/>
         </action>
 
         <!-- chain resursion detection -->
         <action name="InfiniteRecursionChain" 
class="org.apache.struts2.ActionSupport">
-                <result name="success" 
type="chain">InfiniteRecursionChain</result>
+            <result name="success" type="chain">InfiniteRecursionChain</result>
         </action>
         <action name="chain_with_namespace" 
class="org.apache.struts2.SimpleAction">
             <result name="error" type="chain">
@@ -163,9 +172,9 @@
             <interceptor-ref name="defaultStack"/>
         </action>
 
-   </package>
+    </package>
 
-   <package name="bar" extends="default" namespace="/foo/bar">
+    <package name="bar" extends="default" namespace="/foo/bar">
         <interceptors>
             <interceptor-stack name="barDefaultStack">
                 <interceptor-ref name="debugStack"/>
@@ -179,14 +188,14 @@
 
         <action name="Bar" class="org.apache.struts2.SimpleAction">
             <interceptor-ref name="barDefaultStack"/>
-            <result name="error" type="mock" />
+            <result name="error" type="mock"/>
         </action>
 
         <action name="TestInterceptorParamInheritance" 
class="org.apache.struts2.SimpleAction">
             <interceptor-ref name="test">
                 <param name="expectedFoo">expectedFoo</param>
             </interceptor-ref>
-            <result name="error" type="mock" />
+            <result name="error" type="mock"/>
         </action>
 
         <action name="TestInterceptorParamInehritanceOverride" 
class="org.apache.struts2.SimpleAction">
@@ -194,7 +203,7 @@
                 <param name="foo">foo123</param>
                 <param name="expectedFoo">foo123</param>
             </interceptor-ref>
-            <result name="error" type="mock" />
+            <result name="error" type="mock"/>
         </action>
     </package>
 
@@ -210,7 +219,7 @@
             <result name="error" type="chain">
                 <param name="actionName">bar</param>
             </result>
-            <result name="success" type="mock" />
+            <result name="success" type="mock"/>
             <interceptor-ref name="staticParams"/>
         </action>
         <action name="myCommand" class="org.apache.struts2.SimpleAction" 
method="commandMethod">
@@ -222,10 +231,10 @@
             <interceptor-ref name="logger"/>
         </action>
         <action name="doMethodTest" class="org.apache.struts2.SimpleAction" 
method="input">
-            <result name="input" type="mock" />
+            <result name="input" type="mock"/>
         </action>
         <action name="unknownMethodTest" 
class="org.apache.struts2.SimpleAction">
-            <result name="found" type="mock" />
+            <result name="found" type="mock"/>
         </action>
         <action name="resultAction" class="org.apache.struts2.SimpleAction" 
method="resultAction">
             <param name="bar">456</param>
@@ -238,7 +247,7 @@
 
     <package name="wildcardNamespaces" extends="default" 
namespace="/animals/*">
         <action name="commandTest" class="org.apache.struts2.SimpleAction">
-            <result name="success" type="mock" />
+            <result name="success" type="mock"/>
             <interceptor-ref name="staticParams"/>
         </action>
     </package>
@@ -253,47 +262,47 @@
     </package>
 
 
-       <package name="Abstract-crud" extends="default">
-       <!--  edit is often used as the create/view -->
-                          <default-class-ref 
class="org.apache.struts2.SimpleAction"/>
-                <action name="edit" >
-                        <result name="input" type="mock">edit.vm</result>
-                        <result name="success" type="mock">edit.vm</result>
-                        <result name="error" type="mock">edit.vm</result>
-                </action>
-                <action name="save" >
-                        <result name="input" type="mock">edit.vm</result>
-                        <result name="success" type="chain">list</result>
-                        <result name="error" type="mock">edit.vm</result>
-                        <result name="cancel" type="mock">list.action</result>
-                </action>
-                <action name="list">
-                        <result name="success" type="mock">list</result>
-                </action>
-                <action name="delete">
-                        <result name="success" type="mock">list</result>
-                </action>
-
-        </package>
-
-        <package name="Example" extends="Abstract-crud" namespace="/example">
-                 <default-class-ref 
class="org.apache.struts2.ModelDrivenAction" />
-        </package>
-
-        <package name="Example2" extends="Abstract-crud" namespace="/example2">
-                <default-class-ref 
class="org.apache.struts2.ModelDrivenAction" />
-                <action name="override">
-                        <result name="success" 
type="mock">somethingelse.vm</result>
-                </action>
-        </package>
-
-        <package name="SubItem" extends="Abstract-crud" 
namespace="/example2/subItem">
-                <default-class-ref 
class="org.apache.struts2.ModelDrivenAction" />
-        </package>
-
-        <package name="Example3" extends="Abstract-crud" namespace="/example3">
-                       <!--  default-class-ref is expected to be inherited -->
-        </package>
+    <package name="Abstract-crud" extends="default">
+        <!--  edit is often used as the create/view -->
+        <default-class-ref class="org.apache.struts2.SimpleAction"/>
+        <action name="edit">
+            <result name="input" type="mock">edit.vm</result>
+            <result name="success" type="mock">edit.vm</result>
+            <result name="error" type="mock">edit.vm</result>
+        </action>
+        <action name="save">
+            <result name="input" type="mock">edit.vm</result>
+            <result name="success" type="chain">list</result>
+            <result name="error" type="mock">edit.vm</result>
+            <result name="cancel" type="mock">list.action</result>
+        </action>
+        <action name="list">
+            <result name="success" type="mock">list</result>
+        </action>
+        <action name="delete">
+            <result name="success" type="mock">list</result>
+        </action>
+
+    </package>
+
+    <package name="Example" extends="Abstract-crud" namespace="/example">
+        <default-class-ref class="org.apache.struts2.ModelDrivenAction"/>
+    </package>
+
+    <package name="Example2" extends="Abstract-crud" namespace="/example2">
+        <default-class-ref class="org.apache.struts2.ModelDrivenAction"/>
+        <action name="override">
+            <result name="success" type="mock">somethingelse.vm</result>
+        </action>
+    </package>
+
+    <package name="SubItem" extends="Abstract-crud" 
namespace="/example2/subItem">
+        <default-class-ref class="org.apache.struts2.ModelDrivenAction"/>
+    </package>
+
+    <package name="Example3" extends="Abstract-crud" namespace="/example3">
+        <!--  default-class-ref is expected to be inherited -->
+    </package>
 
     <include file="includeTest.xml"/>
 </struts>

Reply via email to