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();
