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">

Reply via email to