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>
