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 6cfd34c94 fix(core): enable WithLazyParams interceptor configuration
in stacks (#1414)
6cfd34c94 is described below
commit 6cfd34c945893ad20c9f115319034469ff868cb7
Author: Lukasz Lenart <[email protected]>
AuthorDate: Mon Nov 17 15:24:09 2025 +0100
fix(core): enable WithLazyParams interceptor configuration in stacks (#1414)
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>