This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch WW-5650-json-per-request-reader-writer
in repository https://gitbox.apache.org/repos/asf/struts.git

commit b51dff6dd5ec45df6ecd16128f13016902dccb65
Author: Lukasz Lenart <[email protected]>
AuthorDate: Tue Jul 14 10:15:15 2026 +0200

    WW-5650 obtain a fresh JSONUtil per request in JSONInterceptor
---
 .../org/apache/struts2/json/JSONInterceptor.java   | 18 +++++++----
 .../apache/struts2/json/JSONInterceptorTest.java   | 37 ++++++++++------------
 2 files changed, 29 insertions(+), 26 deletions(-)

diff --git 
a/plugins/json/src/main/java/org/apache/struts2/json/JSONInterceptor.java 
b/plugins/json/src/main/java/org/apache/struts2/json/JSONInterceptor.java
index b1c172388..8328196ad 100644
--- a/plugins/json/src/main/java/org/apache/struts2/json/JSONInterceptor.java
+++ b/plugins/json/src/main/java/org/apache/struts2/json/JSONInterceptor.java
@@ -22,6 +22,7 @@ import org.apache.struts2.action.Action;
 import org.apache.struts2.action.ParameterNameAware;
 import org.apache.struts2.action.ParameterValueAware;
 import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.inject.Container;
 import org.apache.struts2.inject.Inject;
 import org.apache.struts2.interceptor.AbstractInterceptor;
 import org.apache.struts2.interceptor.parameter.ParameterAuthorizer;
@@ -77,7 +78,7 @@ public class JSONInterceptor extends AbstractInterceptor {
     private String jsonContentType = "application/json";
     private String jsonRpcContentType = "application/json-rpc";
 
-    private JSONUtil jsonUtil;
+    private Container container;
     private ParameterAuthorizer parameterAuthorizer;
     private ExcludedPatternsChecker excludedPatterns;
     private AcceptedPatternsChecker acceptedPatterns;
@@ -98,6 +99,7 @@ public class JSONInterceptor extends AbstractInterceptor {
 
         String requestContentType = readContentType(request);
         String requestContentTypeEncoding = readContentTypeEncoding(request);
+        JSONUtil jsonUtil = getJSONUtil();
 
         Object rootObject = null;
         final ValueStack stack = invocation.getStack();
@@ -111,7 +113,7 @@ public class JSONInterceptor extends AbstractInterceptor {
 
         if (jsonContentType.equalsIgnoreCase(requestContentType)) {
             // load JSON object
-            applyLimitsToReader();
+            applyLimitsToReader(jsonUtil);
             Object obj = jsonUtil.deserializeInput(request.getReader(), 
maxLength);
 
             // JSON array (this.root cannot be null in this case)
@@ -157,7 +159,7 @@ public class JSONInterceptor extends AbstractInterceptor {
             Object result;
             if (this.enableSMD) {
                 // load JSON object
-                applyLimitsToReader();
+                applyLimitsToReader(jsonUtil);
                 Object obj = jsonUtil.deserializeInput(request.getReader(), 
maxLength);
 
                 if (obj instanceof Map) {
@@ -208,7 +210,7 @@ public class JSONInterceptor extends AbstractInterceptor {
         return invocation.invoke();
     }
 
-    private void applyLimitsToReader() {
+    private void applyLimitsToReader(JSONUtil jsonUtil) {
         JSONReader reader = jsonUtil.getReader();
         reader.setMaxElements(maxElements);
         reader.setMaxDepth(maxDepth);
@@ -744,8 +746,12 @@ public class JSONInterceptor extends AbstractInterceptor {
     }
 
     @Inject
-    public void setJsonUtil(JSONUtil jsonUtil) {
-        this.jsonUtil = jsonUtil;
+    public void setContainer(Container container) {
+        this.container = container;
+    }
+
+    protected JSONUtil getJSONUtil() {
+        return container.getInstance(JSONUtil.class);
     }
 
     @Inject
diff --git 
a/plugins/json/src/test/java/org/apache/struts2/json/JSONInterceptorTest.java 
b/plugins/json/src/test/java/org/apache/struts2/json/JSONInterceptorTest.java
index e1aed361b..3bf9a33ce 100644
--- 
a/plugins/json/src/test/java/org/apache/struts2/json/JSONInterceptorTest.java
+++ 
b/plugins/json/src/test/java/org/apache/struts2/json/JSONInterceptorTest.java
@@ -44,10 +44,7 @@ public class JSONInterceptorTest extends StrutsTestCase {
 
     private JSONInterceptor createInterceptor() {
         JSONInterceptor interceptor = new JSONInterceptor();
-        JSONUtil jsonUtil = new JSONUtil();
-        jsonUtil.setReader(new StrutsJSONReader());
-        jsonUtil.setWriter(new StrutsJSONWriter());
-        interceptor.setJsonUtil(jsonUtil);
+        interceptor.setContainer(container);
         // Default: allow all parameters (simulates requireAnnotations=false)
         interceptor.setParameterAuthorizer((parameterName, target, action) -> 
true);
         interceptor.setExcludedPatterns(new 
org.apache.struts2.security.DefaultExcludedPatternsChecker());
@@ -567,10 +564,7 @@ public class JSONInterceptorTest extends StrutsTestCase {
         this.request.addHeader("Content-Type", "application/json");
 
         JSONInterceptor interceptor = new JSONInterceptor();
-        JSONUtil jsonUtil = new JSONUtil();
-        jsonUtil.setReader(new StrutsJSONReader());
-        jsonUtil.setWriter(new StrutsJSONWriter());
-        interceptor.setJsonUtil(jsonUtil);
+        interceptor.setContainer(container);
         // Only authorize "foo", reject "bar"
         interceptor.setParameterAuthorizer((parameterName, target, action) -> 
"foo".equals(parameterName));
         TestAction action = new TestAction();
@@ -589,10 +583,7 @@ public class JSONInterceptorTest extends StrutsTestCase {
         // Simulate a custom JSON reader producing a Map with a non-String key.
         // The authorizer should skip the entry rather than throw 
ClassCastException.
         JSONInterceptor interceptor = new JSONInterceptor();
-        JSONUtil jsonUtil = new JSONUtil();
-        jsonUtil.setReader(new StrutsJSONReader());
-        jsonUtil.setWriter(new StrutsJSONWriter());
-        interceptor.setJsonUtil(jsonUtil);
+        interceptor.setContainer(container);
         interceptor.setParameterAuthorizer((parameterName, target, action) -> 
true);
 
         java.util.Map<Object, Object> mixedKeyMap = new 
java.util.LinkedHashMap<>();
@@ -658,10 +649,7 @@ public class JSONInterceptorTest extends StrutsTestCase {
         this.request.addHeader("Content-Type", "application/json");
 
         JSONInterceptor interceptor = new JSONInterceptor();
-        JSONUtil jsonUtil = new JSONUtil();
-        jsonUtil.setReader(new StrutsJSONReader());
-        jsonUtil.setWriter(new StrutsJSONWriter());
-        interceptor.setJsonUtil(jsonUtil);
+        interceptor.setContainer(container);
         // Authorize "bean" (top-level) and "bean.stringField" (nested) but 
reject "bean.intField"
         interceptor.setParameterAuthorizer((parameterName, target, action) ->
                 "bean".equals(parameterName) || 
"bean.stringField".equals(parameterName));
@@ -688,10 +676,7 @@ public class JSONInterceptorTest extends StrutsTestCase {
         this.request.addHeader("Content-Type", "application/json");
 
         JSONInterceptor interceptor = new JSONInterceptor();
-        JSONUtil jsonUtil = new JSONUtil();
-        jsonUtil.setReader(new StrutsJSONReader());
-        jsonUtil.setWriter(new StrutsJSONWriter());
-        interceptor.setJsonUtil(jsonUtil);
+        interceptor.setContainer(container);
         interceptor.setRoot("bean");
         // Reject all parameters — simulates strict requireAnnotations
         interceptor.setParameterAuthorizer((parameterName, target, action) -> 
false);
@@ -905,6 +890,18 @@ public class JSONInterceptorTest extends StrutsTestCase {
         assertEquals("b", action.getBar());
     }
 
+    public void testObtainsFreshJSONUtilAndReaderPerInvocation() {
+        JSONInterceptor interceptor = new JSONInterceptor();
+        interceptor.setContainer(container); // StrutsTestCase-provided 
container
+
+        JSONUtil first = interceptor.getJSONUtil();
+        JSONUtil second = interceptor.getJSONUtil();
+
+        assertNotSame("interceptor must obtain a fresh JSONUtil per request", 
first, second);
+        assertNotSame("each fresh JSONUtil must carry its own reader (no 
shared parse state)",
+                first.getReader(), second.getReader());
+    }
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();

Reply via email to