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: + *

+ *

+ * 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 + + - - - + + + - - - + + +