You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2022/11/04 07:50:06 UTC

[struts] 02/02: WW-4514 Extracts parameters string building into a dedicated class with a proper extension point

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

lukaszlenart pushed a commit to branch WW-4514-url
in repository https://gitbox.apache.org/repos/asf/struts.git

commit 6135a713355080a32f32805ae0ca056ada3a0c34
Author: Lukasz Lenart <lu...@apache.org>
AuthorDate: Fri Nov 4 08:49:57 2022 +0100

    WW-4514 Extracts parameters string building into a dedicated class with a proper extension point
---
 .../StrutsDefaultConfigurationProvider.java        |  3 +
 .../java/org/apache/struts2/StrutsConstants.java   |  1 +
 .../config/StrutsBeanSelectionProvider.java        |  2 +
 .../struts2/url/ParametersStringBuilder.java       | 31 +++++++
 .../struts2/url/StrutsParametersStringBuilder.java | 91 ++++++++++++++++++++
 .../struts2/views/util/DefaultUrlHelper.java       | 66 ++++-----------
 .../org/apache/struts2/default.properties          |  6 ++
 core/src/main/resources/struts-default.xml         |  2 +
 .../url/StrutsParametersStringBuilderTest.java     | 99 ++++++++++++++++++++++
 .../struts2/views/util/DefaultUrlHelperTest.java   | 64 +-------------
 .../struts2/json/JSONActionRedirectResultTest.java | 25 ++++--
 11 files changed, 276 insertions(+), 114 deletions(-)

diff --git a/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java b/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java
index 2ac045d45..d193c4d24 100644
--- a/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java
@@ -118,6 +118,8 @@ import org.apache.struts2.conversion.StrutsTypeConverterCreator;
 import org.apache.struts2.conversion.StrutsTypeConverterHolder;
 import org.apache.struts2.dispatcher.HttpParameters;
 import org.apache.struts2.dispatcher.Parameter;
+import org.apache.struts2.url.ParametersStringBuilder;
+import org.apache.struts2.url.StrutsParametersStringBuilder;
 import org.apache.struts2.url.StrutsUrlDecoder;
 import org.apache.struts2.url.StrutsUrlEncoder;
 import org.apache.struts2.url.UrlDecoder;
@@ -234,6 +236,7 @@ public class StrutsDefaultConfigurationProvider implements ConfigurationProvider
 
             .factory(ValueSubstitutor.class, EnvsValueSubstitutor.class, Scope.SINGLETON)
 
+            .factory(ParametersStringBuilder.class, StrutsParametersStringBuilder.class, Scope.SINGLETON)
             .factory(UrlEncoder.class, StrutsUrlEncoder.class, Scope.SINGLETON)
             .factory(UrlDecoder.class, StrutsUrlDecoder.class, Scope.SINGLETON)
         ;
diff --git a/core/src/main/java/org/apache/struts2/StrutsConstants.java b/core/src/main/java/org/apache/struts2/StrutsConstants.java
index 5dff4724c..7f6955648 100644
--- a/core/src/main/java/org/apache/struts2/StrutsConstants.java
+++ b/core/src/main/java/org/apache/struts2/StrutsConstants.java
@@ -458,6 +458,7 @@ public final class StrutsConstants {
     /** See {@link org.apache.struts2.components.Date#setDateFormatter(DateFormatter)} */
     public static final String STRUTS_DATE_FORMATTER = "struts.date.formatter";
 
+    public static final String STRUTS_URL_PARAMETERS_STRING_BUILDER = "struts.url.parametersStringBuilder";
     public static final String STRUTS_URL_ENCODER = "struts.url.encoder";
     public static final String STRUTS_URL_DECODER = "struts.url.decoder";
 }
diff --git a/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java b/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java
index 2e5a9315c..15b79f09b 100644
--- a/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java
+++ b/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java
@@ -66,6 +66,7 @@ import org.apache.struts2.dispatcher.DispatcherErrorHandler;
 import org.apache.struts2.dispatcher.StaticContentLoader;
 import org.apache.struts2.dispatcher.mapper.ActionMapper;
 import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
+import org.apache.struts2.url.ParametersStringBuilder;
 import org.apache.struts2.url.UrlDecoder;
 import org.apache.struts2.url.UrlEncoder;
 import org.apache.struts2.util.ContentTypeMatcher;
@@ -431,6 +432,7 @@ public class StrutsBeanSelectionProvider extends AbstractBeanSelectionProvider {
         alias(ExpressionCacheFactory.class, StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_FACTORY, builder, props, Scope.SINGLETON);
         alias(BeanInfoCacheFactory.class, StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_FACTORY, builder, props, Scope.SINGLETON);
 
+        alias(ParametersStringBuilder.class, StrutsConstants.STRUTS_URL_PARAMETERS_STRING_BUILDER, builder, props, Scope.SINGLETON);
         alias(UrlEncoder.class, StrutsConstants.STRUTS_URL_ENCODER, builder, props, Scope.SINGLETON);
         alias(UrlDecoder.class, StrutsConstants.STRUTS_URL_DECODER, builder, props, Scope.SINGLETON);
 
diff --git a/core/src/main/java/org/apache/struts2/url/ParametersStringBuilder.java b/core/src/main/java/org/apache/struts2/url/ParametersStringBuilder.java
new file mode 100644
index 000000000..651c46ddb
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/url/ParametersStringBuilder.java
@@ -0,0 +1,31 @@
+/*
+ * 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.url;
+
+import java.util.Map;
+
+/**
+ * A builder used to create a proper query string out of a set of parameters
+ * @since Struts 6.1.0
+ */
+public interface ParametersStringBuilder {
+
+    void buildParametersString(Map<String, Object> params, StringBuilder link, String paramSeparator);
+
+}
diff --git a/core/src/main/java/org/apache/struts2/url/StrutsParametersStringBuilder.java b/core/src/main/java/org/apache/struts2/url/StrutsParametersStringBuilder.java
new file mode 100644
index 000000000..7b13fa310
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/url/StrutsParametersStringBuilder.java
@@ -0,0 +1,91 @@
+/*
+ * 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.url;
+
+import com.opensymphony.xwork2.inject.Inject;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.Map;
+
+public class StrutsParametersStringBuilder implements ParametersStringBuilder {
+
+    private static final Logger LOG = LogManager.getLogger(StrutsParametersStringBuilder.class);
+
+    private final UrlEncoder encoder;
+
+    @Inject
+    public StrutsParametersStringBuilder(UrlEncoder encoder) {
+        this.encoder = encoder;
+    }
+
+    @Override
+    public void buildParametersString(Map<String, Object> params, StringBuilder link, String paramSeparator) {
+        if ((params != null) && (params.size() > 0)) {
+            LOG.debug("Building query string out of: {}", params);
+            StringBuilder queryString = new StringBuilder();
+
+            // Set params
+            for (Map.Entry<String, Object> entry : params.entrySet()) {
+                String name = entry.getKey();
+                Object value = entry.getValue();
+
+                if (value instanceof Iterable) {
+                    for (Object o : (Iterable<?>) value) {
+                        appendParameterSubstring(queryString, paramSeparator, name, o);
+                    }
+                } else if (value instanceof Object[]) {
+                    Object[] array = (Object[]) value;
+                    for (Object o : array) {
+                        appendParameterSubstring(queryString, paramSeparator, name, o);
+                    }
+                } else {
+                    appendParameterSubstring(queryString, paramSeparator, name, value);
+                }
+            }
+
+            if (queryString.length() > 0) {
+                if (!link.toString().contains("?")) {
+                    link.append("?");
+                } else {
+                    link.append(paramSeparator);
+                }
+                link.append(queryString);
+            }
+        } else {
+            LOG.debug("Params are empty, skipping building the query string");
+        }
+    }
+
+    private void appendParameterSubstring(StringBuilder queryString, String paramSeparator, String name, Object value) {
+        if (queryString.length() > 0) {
+            queryString.append(paramSeparator);
+        }
+
+        String encodedName = encoder.encode(name);
+        queryString.append(encodedName);
+
+        queryString.append('=');
+        if (value != null) {
+            String encodedValue = encoder.encode(value.toString());
+            queryString.append(encodedValue);
+        }
+    }
+
+}
diff --git a/core/src/main/java/org/apache/struts2/views/util/DefaultUrlHelper.java b/core/src/main/java/org/apache/struts2/views/util/DefaultUrlHelper.java
index 6998e24c6..547efef11 100644
--- a/core/src/main/java/org/apache/struts2/views/util/DefaultUrlHelper.java
+++ b/core/src/main/java/org/apache/struts2/views/util/DefaultUrlHelper.java
@@ -24,6 +24,7 @@ import org.apache.commons.text.StringEscapeUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.apache.struts2.StrutsConstants;
+import org.apache.struts2.url.ParametersStringBuilder;
 import org.apache.struts2.url.UrlDecoder;
 import org.apache.struts2.url.UrlEncoder;
 
@@ -48,6 +49,7 @@ public class DefaultUrlHelper implements UrlHelper {
     private int httpPort = DEFAULT_HTTP_PORT;
     private int httpsPort = DEFAULT_HTTPS_PORT;
 
+    private ParametersStringBuilder parametersStringBuilder;
     private UrlEncoder encoder;
     private UrlDecoder decoder;
 
@@ -71,6 +73,11 @@ public class DefaultUrlHelper implements UrlHelper {
         this.decoder = decoder;
     }
 
+    @Inject
+    public void setParametersStringBuilder(ParametersStringBuilder builder) {
+        this.parametersStringBuilder = builder;
+    }
+
     public String buildUrl(String action, HttpServletRequest request, HttpServletResponse response, Map<String, Object> params) {
         return buildUrl(action, request, response, params, null, true, true);
     }
@@ -169,9 +176,9 @@ public class DefaultUrlHelper implements UrlHelper {
 
         //if the action was not explicitly set grab the params from the request
         if (escapeAmp) {
-            buildParametersString(params, link, AMP);
+            parametersStringBuilder.buildParametersString(params, link, AMP);
         } else {
-            buildParametersString(params, link, "&");
+            parametersStringBuilder.buildParametersString(params, link, "&");
         }
 
         String result = link.toString();
@@ -196,38 +203,16 @@ public class DefaultUrlHelper implements UrlHelper {
         }
     }
 
+    /**
+     * Builds parameters assigned to url - a query string
+     * @param params a set of params to assign
+     * @param link a based url
+     * @param paramSeparator separator used
+     * @deprecated since Struts 6.1.0, use {@link ParametersStringBuilder} instead
+     */
+    @Deprecated
     public void buildParametersString(Map<String, Object> params, StringBuilder link, String paramSeparator) {
-        if ((params != null) && (params.size() > 0)) {
-            StringBuilder queryString = new StringBuilder();
-
-            // Set params
-            for (Map.Entry<String, Object> entry : params.entrySet()) {
-                String name = entry.getKey();
-                Object value = entry.getValue();
-
-                if (value instanceof Iterable) {
-                    for (Object o : (Iterable<?>) value) {
-                        appendParameterSubstring(queryString, paramSeparator, name, o);
-                    }
-                } else if (value instanceof Object[]) {
-                    Object[] array = (Object[]) value;
-                    for (Object o : array) {
-                        appendParameterSubstring(queryString, paramSeparator, name, o);
-                    }
-                } else {
-                    appendParameterSubstring(queryString, paramSeparator, name, value);
-                }
-            }
-
-            if (queryString.length() > 0) {
-                if (!link.toString().contains("?")) {
-                    link.append("?");
-                } else {
-                    link.append(paramSeparator);
-                }
-                link.append(queryString);
-            }
-        }
+        parametersStringBuilder.buildParametersString(params, link, paramSeparator);
     }
 
     /**
@@ -247,21 +232,6 @@ public class DefaultUrlHelper implements UrlHelper {
         return HTTP_PROTOCOL.equals(scheme) || HTTPS_PROTOCOL.equals(scheme);
     }
 
-    private void appendParameterSubstring(StringBuilder queryString, String paramSeparator, String name, Object value) {
-        if (queryString.length() > 0) {
-            queryString.append(paramSeparator);
-        }
-
-        String encodedName = encoder.encode(name);
-        queryString.append(encodedName);
-
-        queryString.append('=');
-        if (value != null) {
-            String encodedValue = encoder.encode(value.toString());
-            queryString.append(encodedValue);
-        }
-    }
-
     /**
      * Encodes the URL using {@link UrlEncoder#encode} with the encoding specified in the configuration.
      *
diff --git a/core/src/main/resources/org/apache/struts2/default.properties b/core/src/main/resources/org/apache/struts2/default.properties
index 07f362a7a..a57c48bea 100644
--- a/core/src/main/resources/org/apache/struts2/default.properties
+++ b/core/src/main/resources/org/apache/struts2/default.properties
@@ -279,6 +279,12 @@ struts.ognl.expressionMaxLength=256
 ### These formatters are using a slightly different patterns, please check JavaDocs of both and more details is in WW-5016
 struts.date.formatter=dateTimeFormatter
 
+### Defines which instance of ParametersStringBuilder to use, Struts provides just one instance:
+### - strutsParametersStringBuilder
+### The builder is used by UrlHelp to create a proper query string out of provided parameters map
+struts.url.parametersStringBuilder=strutsParametersStringBuilder
+
+### Defines which instances of encoder and decoder to use, Struts provides one default implementation for each
 struts.url.encoder=strutsUrlEncoder
 struts.url.decoder=strutsUrlDecoder
 
diff --git a/core/src/main/resources/struts-default.xml b/core/src/main/resources/struts-default.xml
index 7980af2f2..35b5bf419 100644
--- a/core/src/main/resources/struts-default.xml
+++ b/core/src/main/resources/struts-default.xml
@@ -313,6 +313,8 @@
     <bean type="com.opensymphony.xwork2.ognl.BeanInfoCacheFactory" name="struts"
           class="com.opensymphony.xwork2.ognl.DefaultOgnlBeanInfoCacheFactory" scope="singleton"/>
 
+    <bean type="org.apache.struts2.url.ParametersStringBuilder" name="strutsParametersStringBuilder"
+          class="org.apache.struts2.url.StrutsParametersStringBuilder" scope="singleton"/>
     <bean type="org.apache.struts2.url.UrlEncoder" name="strutsUrlEncoder"
           class="org.apache.struts2.url.StrutsUrlEncoder" scope="singleton"/>
     <bean type="org.apache.struts2.url.UrlDecoder" name="strutsUrlDecoder"
diff --git a/core/src/test/java/org/apache/struts2/url/StrutsParametersStringBuilderTest.java b/core/src/test/java/org/apache/struts2/url/StrutsParametersStringBuilderTest.java
new file mode 100644
index 000000000..815c5ae16
--- /dev/null
+++ b/core/src/test/java/org/apache/struts2/url/StrutsParametersStringBuilderTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.url;
+
+import org.apache.struts2.views.util.UrlHelper;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+public class StrutsParametersStringBuilderTest {
+
+    private ParametersStringBuilder builder;
+
+    @Test
+    public void testBuildParametersStringWithUrlHavingSomeExistingParameters() {
+        String expectedUrl = "http://localhost:8080/myContext/myPage.jsp?initParam=initValue&amp;param1=value1&amp;param2=value2&amp;param3%22%3CsCrIpT%3Ealert%281%29%3B%3C%2FsCrIpT%3E=value3";
+
+        Map<String, Object> parameters = new LinkedHashMap<>();
+        parameters.put("param1", "value1");
+        parameters.put("param2", "value2");
+        parameters.put("param3\"<sCrIpT>alert(1);</sCrIpT>", "value3");
+
+        StringBuilder url = new StringBuilder("http://localhost:8080/myContext/myPage.jsp?initParam=initValue");
+
+        builder.buildParametersString(parameters, url, UrlHelper.AMP);
+
+        assertEquals(expectedUrl, url.toString());
+    }
+
+    @Test
+    public void testBuildParametersStringWithJavaScriptInjected() {
+        String expectedUrl = "http://localhost:8080/myContext/myPage.jsp?initParam=initValue&amp;param1=value1&amp;param2=value2&amp;param3%22%3Cscript+type%3D%22text%2Fjavascript%22%3Ealert%281%29%3B%3C%2Fscript%3E=value3";
+
+        Map<String, Object> parameters = new LinkedHashMap<>();
+        parameters.put("param1", "value1");
+        parameters.put("param2", "value2");
+        parameters.put("param3\"<script type=\"text/javascript\">alert(1);</script>", "value3");
+
+        StringBuilder url = new StringBuilder("http://localhost:8080/myContext/myPage.jsp?initParam=initValue");
+
+        builder.buildParametersString(parameters, url, UrlHelper.AMP);
+
+        assertEquals(expectedUrl, url.toString());
+    }
+
+    @Test
+    public void testBuildParametersStringWithEmptyListParameters() {
+        String expectedUrl = "https://www.nowhere.com/myworld.html";
+        Map<String, Object> parameters = new LinkedHashMap<>();
+        parameters.put("param1", new String[]{});
+        parameters.put("param2", new ArrayList<>());
+        StringBuilder url = new StringBuilder("https://www.nowhere.com/myworld.html");
+        builder.buildParametersString(parameters, url, UrlHelper.AMP);
+        assertEquals(expectedUrl, url.toString());
+    }
+
+    @Test
+    public void testBuildParametersStringWithListParameters() {
+        String expectedUrl = "https://www.nowhere.com/myworld.html?param1=x&param2=y&param2=z";
+        Map<String, Object> parameters = new LinkedHashMap<>();
+        parameters.put("param1", new String[]{"x"});
+        parameters.put("param2", new ArrayList<String>() {
+            {
+                add("y");
+                add("z");
+            }
+        });
+        StringBuilder url = new StringBuilder("https://www.nowhere.com/myworld.html");
+        builder.buildParametersString(parameters, url, "&");
+        assertEquals(expectedUrl, url.toString());
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        builder = new StrutsParametersStringBuilder(new StrutsUrlEncoder());
+    }
+
+}
diff --git a/core/src/test/java/org/apache/struts2/views/util/DefaultUrlHelperTest.java b/core/src/test/java/org/apache/struts2/views/util/DefaultUrlHelperTest.java
index b1836f42a..acbee3010 100644
--- a/core/src/test/java/org/apache/struts2/views/util/DefaultUrlHelperTest.java
+++ b/core/src/test/java/org/apache/struts2/views/util/DefaultUrlHelperTest.java
@@ -23,14 +23,13 @@ import com.opensymphony.xwork2.ActionContext;
 import com.opensymphony.xwork2.inject.Container;
 import com.opensymphony.xwork2.inject.Scope.Strategy;
 import org.apache.struts2.StrutsInternalTestCase;
+import org.apache.struts2.url.StrutsParametersStringBuilder;
 import org.apache.struts2.url.StrutsUrlDecoder;
 import org.apache.struts2.url.StrutsUrlEncoder;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
@@ -92,63 +91,6 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
         mockHttpServletRequest.verify();
     }
 
-    public void testBuildParametersStringWithUrlHavingSomeExistingParameters() {
-        String expectedUrl = "http://localhost:8080/myContext/myPage.jsp?initParam=initValue&amp;param1=value1&amp;param2=value2&amp;param3%22%3CsCrIpT%3Ealert%281%29%3B%3C%2FsCrIpT%3E=value3";
-
-        Map<String, Object> parameters = new LinkedHashMap<>();
-        parameters.put("param1", "value1");
-        parameters.put("param2", "value2");
-        parameters.put("param3\"<sCrIpT>alert(1);</sCrIpT>", "value3");
-
-        StringBuilder url = new StringBuilder("http://localhost:8080/myContext/myPage.jsp?initParam=initValue");
-
-        urlHelper.buildParametersString(parameters, url, UrlHelper.AMP);
-
-        assertEquals(
-            expectedUrl, url.toString());
-    }
-
-    public void testBuildParametersStringWithJavaScriptInjected() {
-        String expectedUrl = "http://localhost:8080/myContext/myPage.jsp?initParam=initValue&amp;param1=value1&amp;param2=value2&amp;param3%22%3Cscript+type%3D%22text%2Fjavascript%22%3Ealert%281%29%3B%3C%2Fscript%3E=value3";
-
-        Map<String, Object> parameters = new LinkedHashMap<>();
-        parameters.put("param1", "value1");
-        parameters.put("param2", "value2");
-        parameters.put("param3\"<script type=\"text/javascript\">alert(1);</script>", "value3");
-
-        StringBuilder url = new StringBuilder("http://localhost:8080/myContext/myPage.jsp?initParam=initValue");
-
-        urlHelper.buildParametersString(parameters, url, UrlHelper.AMP);
-
-        assertEquals(
-            expectedUrl, url.toString());
-    }
-
-    public void testBuildParametersStringWithEmptyListParameters() {
-        String expectedUrl = "https://www.nowhere.com/myworld.html";
-        Map<String, Object> parameters = new LinkedHashMap<>();
-        parameters.put("param1", new String[]{});
-        parameters.put("param2", new ArrayList<>());
-        StringBuilder url = new StringBuilder("https://www.nowhere.com/myworld.html");
-        urlHelper.buildParametersString(parameters, url, UrlHelper.AMP);
-        assertEquals(expectedUrl, url.toString());
-    }
-
-    public void testBuildParametersStringWithListParameters() {
-        String expectedUrl = "https://www.nowhere.com/myworld.html?param1=x&param2=y&param2=z";
-        Map<String, Object> parameters = new LinkedHashMap<>();
-        parameters.put("param1", new String[]{"x"});
-        parameters.put("param2", new ArrayList<String>() {
-            {
-                add("y");
-                add("z");
-            }
-        });
-        StringBuilder url = new StringBuilder("https://www.nowhere.com/myworld.html");
-        urlHelper.buildParametersString(parameters, url, "&");
-        assertEquals(expectedUrl, url.toString());
-    }
-
     public void testForceAddNullSchemeHostAndPort() {
         String expectedUrl = "http://localhost/contextPath/path1/path2/myAction.action";
 
@@ -434,7 +376,9 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
         StubContainer stubContainer = new StubContainer(container);
         ActionContext.getContext().withContainer(stubContainer);
         urlHelper = new DefaultUrlHelper();
-        urlHelper.setEncoder(new StrutsUrlEncoder());
+        StrutsUrlEncoder encoder = new StrutsUrlEncoder();
+        urlHelper.setParametersStringBuilder(new StrutsParametersStringBuilder(encoder));
+        urlHelper.setEncoder(encoder);
         urlHelper.setDecoder(new StrutsUrlDecoder());
     }
 
diff --git a/plugins/json/src/test/java/org/apache/struts2/json/JSONActionRedirectResultTest.java b/plugins/json/src/test/java/org/apache/struts2/json/JSONActionRedirectResultTest.java
index 9e93fc5d1..268ee6087 100644
--- a/plugins/json/src/test/java/org/apache/struts2/json/JSONActionRedirectResultTest.java
+++ b/plugins/json/src/test/java/org/apache/struts2/json/JSONActionRedirectResultTest.java
@@ -26,6 +26,9 @@ import com.opensymphony.xwork2.util.ValueStack;
 import org.apache.struts2.StrutsStatics;
 import org.apache.struts2.dispatcher.mapper.DefaultActionMapper;
 import org.apache.struts2.junit.StrutsTestCase;
+import org.apache.struts2.url.StrutsParametersStringBuilder;
+import org.apache.struts2.url.StrutsUrlDecoder;
+import org.apache.struts2.url.StrutsUrlEncoder;
 import org.apache.struts2.views.util.DefaultUrlHelper;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
@@ -33,6 +36,9 @@ import org.springframework.mock.web.MockServletContext;
 
 public class JSONActionRedirectResultTest extends StrutsTestCase {
 
+    private DefaultActionMapper actionMapper;
+    private DefaultUrlHelper urlHelper;
+
     MockActionInvocation invocation;
     MockHttpServletResponse response;
     MockServletContext servletContext;
@@ -43,8 +49,8 @@ public class JSONActionRedirectResultTest extends StrutsTestCase {
     public void testNormalRedirect() throws Exception {
         JSONActionRedirectResult result = new JSONActionRedirectResult();
         result.setActionName("targetAction");
-        result.setActionMapper(new DefaultActionMapper());
-        result.setUrlHelper(new DefaultUrlHelper());
+        result.setActionMapper(actionMapper);
+        result.setUrlHelper(urlHelper);
 
         Object action = new Object();
         stack.push(action);
@@ -62,8 +68,8 @@ public class JSONActionRedirectResultTest extends StrutsTestCase {
     public void testJsonRedirect() throws Exception {
         JSONActionRedirectResult result = new JSONActionRedirectResult();
         result.setActionName("targetAction");
-        result.setActionMapper(new DefaultActionMapper());
-        result.setUrlHelper(new DefaultUrlHelper());
+        result.setActionMapper(actionMapper);
+        result.setUrlHelper(urlHelper);
 
         request.setParameter("struts.enableJSONValidation", "true");
         request.setParameter("struts.validateOnly", "false");
@@ -82,8 +88,8 @@ public class JSONActionRedirectResultTest extends StrutsTestCase {
     public void testValidateOnlyFalse() throws Exception {
         JSONActionRedirectResult result = new JSONActionRedirectResult();
         result.setActionName("targetAction");
-        result.setActionMapper(new DefaultActionMapper());
-        result.setUrlHelper(new DefaultUrlHelper());
+        result.setActionMapper(actionMapper);
+        result.setUrlHelper(urlHelper);
 
         request.setParameter("struts.enableJSONValidation", "true");
         request.setParameter("struts.validateOnly", "true");
@@ -118,5 +124,12 @@ public class JSONActionRedirectResultTest extends StrutsTestCase {
         MockActionProxy mockActionProxy = new MockActionProxy();
         mockActionProxy.setConfig(new ActionConfig.Builder(null, null, null).build());
         this.invocation.setProxy(mockActionProxy);
+
+        this.actionMapper = new DefaultActionMapper();
+        this.urlHelper = new DefaultUrlHelper();
+        StrutsUrlEncoder encoder = new StrutsUrlEncoder();
+        this.urlHelper.setParametersStringBuilder(new StrutsParametersStringBuilder(encoder));
+        this.urlHelper.setEncoder(encoder);
+        this.urlHelper.setDecoder(new StrutsUrlDecoder());
     }
 }