WW-5641 Restore struts.json.writer / struts.json.reader override in JSON plugin (#1766)

* WW-5641 docs: design spec for JSON writer/reader override regression

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* WW-5641 docs: implementation plan for JSON writer/reader override fix

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* WW-5641 fix: run JSON bean-selection from struts-deferred.xml

The JSON plugin declared <bean-selection> in struts-plugin.xml, which runs
at plugin-parse time, before the application struts.xml is folded in. That
froze the JSONWriter/JSONReader default binding to StrutsJSONWriter/Reader,
so struts.json.writer / struts.json.reader overrides were ignored.

Move the element to struts-deferred.xml, which Dispatcher loads last (after
the app config and core's StrutsBeanSelectionProvider), so the alias honors
the override. Mirrors the velocity plugin. JSONUtil is unchanged from main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Lukasz Lenart
2026-07-12 11:29:41 +02:00
committed by GitHub
parent 08fba59807
commit 5c130f9411
8 changed files with 838 additions and 2 deletions
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<bean-selection name="jsonBeans" class="org.apache.struts2.json.JSONBeanSelectionProvider"/>
</struts>
@@ -64,6 +64,4 @@
</interceptors>
</package>
<bean-selection name="jsonBeans" class="org.apache.struts2.json.JSONBeanSelectionProvider"/>
</struts>
@@ -0,0 +1,39 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.json;
/**
* Marker reader proving that a user-configured {@code struts.json.reader} override
* wins over the default {@link StrutsJSONReader}.
*/
public class CustomTestJSONReader implements JSONReader {
public static final String SENTINEL = "__customReader__";
@Override
public Object read(String string) throws JSONException {
return SENTINEL;
}
// Limit setters are intentionally ignored: this marker always returns SENTINEL.
@Override public void setMaxElements(int maxElements) { /* no-op marker */ }
@Override public void setMaxDepth(int maxDepth) { /* no-op marker */ }
@Override public void setMaxStringLength(int maxStringLength) { /* no-op marker */ }
@Override public void setMaxKeyLength(int maxKeyLength) { /* no-op marker */ }
}
@@ -0,0 +1,51 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.json;
import java.util.Collection;
import java.util.regex.Pattern;
/**
* Marker writer proving that a user-configured {@code struts.json.writer} override
* wins over the default {@link StrutsJSONWriter}. Emits a sentinel so output-level
* assertions are unambiguous.
*/
public class CustomTestJSONWriter implements JSONWriter {
public static final String SENTINEL = "{\"__customWriter__\":true}";
@Override
public String write(Object object) throws JSONException {
return SENTINEL;
}
@Override
public String write(Object object, Collection<Pattern> excludeProperties,
Collection<Pattern> includeProperties,
boolean excludeNullProperties) throws JSONException {
return SENTINEL;
}
// Configuration setters are intentionally ignored: this marker always emits SENTINEL.
@Override public void setIgnoreHierarchy(boolean ignoreHierarchy) { /* no-op marker */ }
@Override public void setEnumAsBean(boolean enumAsBean) { /* no-op marker */ }
@Override public void setDateFormatter(String defaultDateFormat) { /* no-op marker */ }
@Override public void setCacheBeanInfo(boolean cacheBeanInfo) { /* no-op marker */ }
@Override public void setExcludeProxyProperties(boolean excludeProxyProperties) { /* no-op marker */ }
}
@@ -0,0 +1,78 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.json;
import org.apache.struts2.junit.StrutsTestCase;
import java.util.HashMap;
import java.util.Map;
/**
* Regression test for WW-5641: {@code struts.json.writer} / {@code struts.json.reader}
* overrides from an application config were ignored on the 7.2.x line because the JSON
* plugin's {@code <bean-selection>} ran at plugin-parse time, before the app config.
* Moving it to {@code struts-deferred.xml} makes it run last, so the override wins.
*
* <p>Boots the real Dispatcher chain. {@code struts-deferred.xml} is loaded unconditionally
* by {@code Dispatcher.init()} after the {@code config} chain, so it is intentionally omitted
* from the config init param below.</p>
*/
public class JSONWriterOverrideTest extends StrutsTestCase {
@Override
protected void setupBeforeInitDispatcher() throws Exception {
Map<String, String> params = new HashMap<>();
params.put("config", "struts-default.xml,struts-plugin.xml,struts-json-override.xml");
dispatcherInitParams = params;
}
/**
* The default JSONWriter binding must resolve to the app override, not StrutsJSONWriter.
*/
public void testCustomWriterWinsAsDefaultBinding() {
JSONWriter writer = container.getInstance(JSONWriter.class);
assertEquals("struts.json.writer override was ignored; default StrutsJSONWriter was used",
CustomTestJSONWriter.class, writer.getClass());
}
/**
* The default JSONReader binding must resolve to the app override, not StrutsJSONReader.
*/
public void testCustomReaderWinsAsDefaultBinding() {
JSONReader reader = container.getInstance(JSONReader.class);
assertEquals("struts.json.reader override was ignored; default StrutsJSONReader was used",
CustomTestJSONReader.class, reader.getClass());
}
/**
* End-to-end: the writer JSONUtil actually serializes with must be the override.
*/
public void testJSONUtilUsesCustomWriter() throws Exception {
JSONUtil jsonUtil = container.getInstance(JSONUtil.class);
assertEquals(CustomTestJSONWriter.SENTINEL, jsonUtil.serialize(new Object(), false));
}
/**
* End-to-end: the reader JSONUtil exposes must be the override.
*/
public void testJSONUtilUsesCustomReader() {
JSONUtil jsonUtil = container.getInstance(JSONUtil.class);
assertEquals(CustomTestJSONReader.class, jsonUtil.getReader().getClass());
}
}
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<bean type="org.apache.struts2.json.JSONWriter" name="customTestWriter"
class="org.apache.struts2.json.CustomTestJSONWriter" scope="prototype"/>
<bean type="org.apache.struts2.json.JSONReader" name="customTestReader"
class="org.apache.struts2.json.CustomTestJSONReader" scope="prototype"/>
<constant name="struts.json.writer" value="customTestWriter"/>
<constant name="struts.json.reader" value="customTestReader"/>
</struts>