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 8e37691f3 WW-5663 fix(core): hand a WithLazyParams interceptor its own
mapping's params (#1933)
8e37691f3 is described below
commit 8e37691f36b641d5dd08f3b0377396956a297fe4
Author: Lukasz Lenart <[email protected]>
AuthorDate: Sun Sep 13 20:06:33 2026 +0200
WW-5663 fix(core): hand a WithLazyParams interceptor its own mapping's
params (#1933)
DefaultActionInvocation.mergedParams looked the interceptor mapping up
again by name in the proxy's interceptor list - the very list the
invocation iterates - and merged the first match's params over the
mapping's own. With a unique name that merged a map into a copy of
itself; with a repeated name, every later invocation ran with the first
ref's params. The lookup came from WW-5585, written while stack-ref
params were not yet stored on the mapping (WW-5587, fixed in 7.2.1), so
nothing has needed it since.
The mapping's params go straight to LazyParamInjector.resolveInto, which
only reads them, so the defensive copy from WW-5659 goes with the lookup.
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
.../apache/struts2/DefaultActionInvocation.java | 23 +---------------------
.../struts2/DefaultActionInvocationTest.java | 19 ++++++++++++++++++
core/src/test/resources/xwork-sample.xml | 10 ++++++++++
3 files changed, 30 insertions(+), 22 deletions(-)
diff --git a/core/src/main/java/org/apache/struts2/DefaultActionInvocation.java
b/core/src/main/java/org/apache/struts2/DefaultActionInvocation.java
index 0a671648a..1c44165e4 100644
--- a/core/src/main/java/org/apache/struts2/DefaultActionInvocation.java
+++ b/core/src/main/java/org/apache/struts2/DefaultActionInvocation.java
@@ -43,7 +43,6 @@ import org.apache.struts2.util.ValueStackFactory;
import java.util.ArrayList;
import java.util.Iterator;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
@@ -318,7 +317,7 @@ public class DefaultActionInvocation implements
ActionInvocation {
private <P extends InterceptorParams> String invokeWithLazyParams(
WithLazyParams<P> lazyInterceptor, InterceptorMapping
interceptorMapping) throws Exception {
P lazyParams = lazyParamInjector.resolveInto(
- lazyInterceptor.newLazyParams(),
mergedParams(interceptorMapping), invocationContext);
+ lazyInterceptor.newLazyParams(),
interceptorMapping.getParams(), invocationContext);
if (lazyParams instanceof DisableParams disableParams &&
disableParams.isDisabled()) {
LOG.debug("Interceptor: {} is disabled by its lazily resolved
params, skipping to next", interceptorMapping.getName());
@@ -333,26 +332,6 @@ public class DefaultActionInvocation implements
ActionInvocation {
return lazyInterceptor.intercept(this, lazyParams);
}
- /**
- * Merges the params declared on the interceptor-ref with those of the
mapping being invoked.
- * <p>
- * The name-based lookup is inherited behaviour, kept as-is: the mapping
is normally the very one
- * found by name, so the merge is a no-op, and when a stack references the
same interceptor name
- * twice with different params it merges the first mapping's params over
the current one, which
- * is questionable. Changing it is out of scope here.
- *
- * @return a fresh map preserving the configuration order, so params are
applied to the holder
- * deterministically; the mapping's own param map is shared across
requests and must not be mutated
- */
- private Map<String, String> mergedParams(InterceptorMapping
interceptorMapping) {
- Map<String, String> merged = new
LinkedHashMap<>(interceptorMapping.getParams());
- proxy.getConfig().getInterceptors().stream()
- .filter(im ->
im.getName().equals(interceptorMapping.getName()))
- .findFirst()
- .ifPresent(im -> merged.putAll(im.getParams()));
- return merged;
- }
-
/**
* Replaces the single-argument form removed in 7.3.0. That one had no
callers left once the
* mapping name became available here, so a subclass still overriding it
would have gone quietly
diff --git
a/core/src/test/java/org/apache/struts2/DefaultActionInvocationTest.java
b/core/src/test/java/org/apache/struts2/DefaultActionInvocationTest.java
index 6c355860a..44c008b66 100644
--- a/core/src/test/java/org/apache/struts2/DefaultActionInvocationTest.java
+++ b/core/src/test/java/org/apache/struts2/DefaultActionInvocationTest.java
@@ -459,6 +459,25 @@ public class DefaultActionInvocationTest extends
XWorkTestCase {
assertEquals("static value", action.getBlah());
}
+ /**
+ * WW-5663: the same WithLazyParams interceptor referenced twice with
different params. Each invocation
+ * must see its own ref's params; the lookup by name used to merge the
first ref's params over every
+ * later one, so the action ended up with "first".
+ */
+ public void testInvokeWithLazyParamsRepeatedRefKeepsEachRefsOwnParams()
throws Exception {
+ ActionContext extraContext = ActionContext.of();
+
+ DefaultActionInvocation defaultActionInvocation = new
DefaultActionInvocation(extraContext.getContextMap(), true);
+ container.inject(defaultActionInvocation);
+
+ ActionProxy actionProxy = actionProxyFactory.createActionProxy("",
"LazyFooTwice", null, extraContext.getContextMap());
+ defaultActionInvocation.init(actionProxy);
+ defaultActionInvocation.invoke();
+
+ SimpleAction action = (SimpleAction)
defaultActionInvocation.getAction();
+ assertEquals("second", action.getName());
+ }
+
/**
* Regression for WW-5659: a {@code disabled} param resolved lazily from
the value stack must skip
* the interceptor for that invocation. It arrives through the interceptor
mapping's params, so it
diff --git a/core/src/test/resources/xwork-sample.xml
b/core/src/test/resources/xwork-sample.xml
index 7400bebc0..7a513fc73 100644
--- a/core/src/test/resources/xwork-sample.xml
+++ b/core/src/test/resources/xwork-sample.xml
@@ -72,6 +72,16 @@
</interceptor-ref>
</action>
+ <action name="LazyFooTwice" class="org.apache.struts2.SimpleAction">
+ <result name="error" type="void"/>
+ <interceptor-ref name="lazy">
+ <param name="foo">first</param>
+ </interceptor-ref>
+ <interceptor-ref name="lazy">
+ <param name="foo">second</param>
+ </interceptor-ref>
+ </action>
+
<!-- disabled resolved lazily per invocation: reaches the interceptor
mapping's params,
so it lands on the params holder and not on the shared
interceptor -->
<action name="LazyFooLazilyDisabled"
class="org.apache.struts2.SimpleAction">