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}.
+ *
+ * This enables both:
+ *
+ * - Static configuration in interceptor stacks (e.g., allowedTypes="image/png,image/jpeg")
+ * - Dynamic expressions evaluated per-request (e.g., maximumSize="${maxUploadSize}")
+ *
+ *
+ * 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 params, ActionContext invocationContext) {
for (Map.Entry 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 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 @@
-
+
@@ -56,77 +56,86 @@
-
-
-
- ${blah}
-
+
+
+
+ ${blah}
+
+
+
+
+
+
+
+ ${blah}
+ static value
+
17
23
-
-
+
+
17
23
-
-
+
+
-
-
+
+
-
+
{1}
{2}
-
+
- #{ "aliasSource" : "aliasDest", "bar":"baz", "notExisting":"blah" }
-
-
-
+ #{ "aliasSource" : "aliasDest", "bar":"baz", "notExisting":"blah" }
+
+
+
#{ #parameters['name'] : #parameters['value'] }
-
+
- 17
+ 17
23
-
+
-
+
expectedFoo
-
+
@@ -134,18 +143,18 @@
foo123
foo123
-
+
-
+
- InfiniteRecursionChain
+ InfiniteRecursionChain
@@ -163,9 +172,9 @@
-
+
-
+
@@ -179,14 +188,14 @@
-
+
expectedFoo
-
+
@@ -194,7 +203,7 @@
foo123
foo123
-
+
@@ -210,7 +219,7 @@
bar
-
+
@@ -222,10 +231,10 @@
-
+
-
+
456
@@ -238,7 +247,7 @@
-
+
@@ -253,47 +262,47 @@
-
-
-
-
- edit.vm
- edit.vm
- edit.vm
-
-
- edit.vm
- list
- edit.vm
- list.action
-
-
- list
-
-
- list
-
+
+
+
+
+ edit.vm
+ edit.vm
+ edit.vm
+
+
+ edit.vm
+ list
+ edit.vm
+ list.action
+
+
+ list
+
+
+ list
+
-
+
-
-
-
+
+
+
-
-
-
- somethingelse.vm
-
-
+
+
+
+ somethingelse.vm
+
+
-
-
-
+
+
+
-
-
-
+
+
+