You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by ya...@apache.org on 2019/04/20 05:24:40 UTC

[struts] branch struts-2-5-x updated: Proposed fix for WW-5029 for the 2.5.x branch (#347)

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

yasserzamani pushed a commit to branch struts-2-5-x
in repository https://gitbox.apache.org/repos/asf/struts.git


The following commit(s) were added to refs/heads/struts-2-5-x by this push:
     new fb38a91  Proposed fix for WW-5029 for the 2.5.x branch (#347)
fb38a91 is described below

commit fb38a919c9f6494cb0d3fbd83c11741c62eb3143
Author: JCgH4164838Gh792C124B5 <43...@users.noreply.github.com>
AuthorDate: Sat Apr 20 01:24:36 2019 -0400

    Proposed fix for WW-5029 for the 2.5.x branch (#347)
    
    * Proposed fix for WW-5029 for the 2.5.x branch:
    - NOTE: If the PR is accepted please credit Maxime Clement for this change as they found
            the issue, identified the probable cause/related details and opened the JIRA.
    - Updated XWorkConfigurationProvider buildAllowedMethods(), loadGlobalAllowedMethods() so that
      they now handle situations when a SAX parser produces multiple elements to represent the tag
      body value.
    - No changes to unit tests.
    
    * Update commit to fix weakness identified by Maxime Clement:
    - Implementation should now properly concatenate the node children values together (as a single unified string)
      in both buildAllowedMethods(), loadGlobalAllowedMethods() - before generating the method Set to be added.
    - Made some eligible variables final.
    
    * Update commit to provide new unit tests:
    - Added unit tests to confirm the fixes for buildAllowedMethods(), loadGlobalAllowedMethods()
    - Added Mock DOM classes sufficient for these tests.
    - Added unit tests to cover buildResults() and loadGlobalResults().
---
 .../config/providers/XmlConfigurationProvider.java |  41 +-
 .../providers/XmlConfigurationProviderTest.java    | 802 +++++++++++++++++++++
 2 files changed, 831 insertions(+), 12 deletions(-)

diff --git a/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java b/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java
index 3b72352..9cd21fb 100644
--- a/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java
@@ -874,15 +874,22 @@ public class XmlConfigurationProvider implements ConfigurationProvider {
         if (allowedMethodsEls.getLength() > 0) {
             // user defined 'allowed-methods' so used them whatever Strict DMI was enabled or not
             allowedMethods = new HashSet<>(packageContext.getGlobalAllowedMethods());
-
-            if (allowedMethodsEls.getLength() > 0) {
-                Node n = allowedMethodsEls.item(0).getFirstChild();
-                if (n != null) {
-                    String s = n.getNodeValue().trim();
-                    if (s.length() > 0) {
-                        allowedMethods.addAll(TextParseUtil.commaDelimitedStringToSet(s));
+            // Fix for WW-5029 (concatenate all possible text node children)
+            final Node allowedMethodsNode = allowedMethodsEls.item(0);
+            if (allowedMethodsNode != null) {
+                final NodeList allowedMethodsChildren = allowedMethodsNode.getChildNodes();
+                final StringBuilder allowedMethodsSB = new StringBuilder();
+                for (int i = 0; i < allowedMethodsChildren.getLength(); i++) {
+                    Node allowedMethodsChildNode = allowedMethodsChildren.item(i);
+                    String childNodeValue = (allowedMethodsChildNode != null ? allowedMethodsChildNode.getNodeValue() : "");
+                    childNodeValue = (childNodeValue != null ? childNodeValue.trim() : "");
+                    if (childNodeValue.length() > 0) {
+                        allowedMethodsSB.append(childNodeValue);
                     }
                 }
+                if (allowedMethodsSB.length() > 0) {
+                    allowedMethods.addAll(TextParseUtil.commaDelimitedStringToSet(allowedMethodsSB.toString()));
+                }
             }
         } else if (packageContext.isStrictMethodInvocation()) {
             // user enabled Strict DMI but didn't defined action specific 'allowed-methods' so we use 'global-allowed-methods' only
@@ -937,11 +944,21 @@ public class XmlConfigurationProvider implements ConfigurationProvider {
 
         if (globalAllowedMethodsElms.getLength() > 0) {
             Set<String> globalAllowedMethods = new HashSet<>();
-            Node n = globalAllowedMethodsElms.item(0).getFirstChild();
-            if (n != null) {
-                String s = n.getNodeValue().trim();
-                if (s.length() > 0) {
-                    globalAllowedMethods = TextParseUtil.commaDelimitedStringToSet(s);
+            // Fix for WW-5029 (concatenate all possible text node children)
+            Node globaAllowedMethodsNode = globalAllowedMethodsElms.item(0);
+            if (globaAllowedMethodsNode != null) {
+                NodeList globaAllowedMethodsChildren = globaAllowedMethodsNode.getChildNodes();
+                final StringBuilder globalAllowedMethodsSB = new StringBuilder();
+                for (int i = 0; i < globaAllowedMethodsChildren.getLength(); i++) {
+                    Node globalAllowedMethodsChildNode = globaAllowedMethodsChildren.item(i);
+                    String childNodeValue = (globalAllowedMethodsChildNode != null ? globalAllowedMethodsChildNode.getNodeValue() : "");
+                    childNodeValue = (childNodeValue != null ? childNodeValue.trim() : "");
+                    if (childNodeValue.length() > 0) {
+                        globalAllowedMethodsSB.append(childNodeValue);
+                    }
+                }
+                if (globalAllowedMethodsSB.length() > 0) {
+                    globalAllowedMethods.addAll(TextParseUtil.commaDelimitedStringToSet(globalAllowedMethodsSB.toString()));
                 }
             }
             packageContext.addGlobalAllowedMethods(globalAllowedMethods);
diff --git a/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderTest.java b/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderTest.java
index faa6b93..6aefed8 100644
--- a/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderTest.java
@@ -23,8 +23,11 @@ import com.opensymphony.xwork2.ObjectFactory;
 import com.opensymphony.xwork2.config.ConfigurationProvider;
 import com.opensymphony.xwork2.config.RuntimeConfiguration;
 import com.opensymphony.xwork2.config.entities.PackageConfig;
+import com.opensymphony.xwork2.config.entities.ResultConfig;
+import com.opensymphony.xwork2.config.entities.ResultTypeConfig;
 import com.opensymphony.xwork2.config.impl.MockConfiguration;
 import com.opensymphony.xwork2.util.ClassLoaderUtil;
+import com.sun.org.apache.xerces.internal.dom.ElementImpl;
 import org.w3c.dom.Document;
 
 import java.io.File;
@@ -35,9 +38,20 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import org.apache.struts2.result.ServletDispatcherResult;
+import org.w3c.dom.Attr;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.TypeInfo;
+import org.w3c.dom.UserDataHandler;
 
 
 public class XmlConfigurationProviderTest extends ConfigurationTestBase {
@@ -294,4 +308,792 @@ public class XmlConfigurationProviderTest extends ConfigurationTestBase {
         assertTrue(loadedFileNames.contains("xwork-test-beans.xml"));
         assertTrue(loadedFileNames.contains("xwork-test-default.xml"));
     }
+
+    /**
+     * Test buildAllowedMethods() to ensure consistent results for processing
+     * <allowed-methods/> in <action/> XML configuration elements.
+     * 
+     * @throws Exception 
+     */
+    public void testBuildAllowedMethods() throws Exception {
+        // Test introduced with WW-5029 fix.
+        // Set up test using two mock DOM Elements:
+        //   1) A mock "action" Element with a single "allowed-methods" child Element that contains a single
+        //      TEXT_NODE Node.  This simulates a typical result from a SAX parser parsing the allowed-methods
+        //      tag body.
+        //   2) A mock "action" Element with a single "allowed-methods" child Element that contains multiple
+        //      TEXT_NODE Nodes.  This simulates an unusal result from a SAX parser parsing the allowed-methods
+        //      tag body (as reported with WW-5029).
+        final String fakeBodyString = "allowedMethod1,allowedMethod2,allowedMethod3";
+        PackageConfig.Builder testPackageConfigBuilder = new PackageConfig.Builder("allowedMethodsPackage");
+        List<String> singleStringList = new ArrayList(1);
+        List<String> multipleStringList = new ArrayList(4);
+        singleStringList.add(fakeBodyString);
+        multipleStringList.add("allowedMethod1,");
+        multipleStringList.add("allowed");
+        multipleStringList.add("Method2,");
+        multipleStringList.add("allowedMethod3");
+        NodeList mockNodeListSingleChild = new MockNodeList(singleStringList);
+        NodeList mockNodeListMultipleChild = new MockNodeList(multipleStringList);
+        Element mockSingleChildAllowedMethodsElement = new MockElement("allowed-methods", fakeBodyString,
+                "allowed-methods", fakeBodyString, Node.TEXT_NODE, mockNodeListSingleChild, null);
+        Element mockMultipleChildAllowedMethodsElement = new MockElement("allowed-methods", fakeBodyString,
+                "allowed-methods", fakeBodyString, Node.TEXT_NODE, mockNodeListMultipleChild, null);
+        MockNodeList mockActionElementChildrenSingle = new MockNodeList();
+        mockActionElementChildrenSingle.addToNodeList(mockSingleChildAllowedMethodsElement);
+        MockNodeList mockActionElementChildrenMultiple = new MockNodeList();
+        mockActionElementChildrenMultiple.addToNodeList(mockMultipleChildAllowedMethodsElement);
+        Element mockActionElementSingle = new MockElement("action", "fakeBody", "action", "fakeValue",
+                Node.TEXT_NODE, mockActionElementChildrenSingle, null);
+        Element mockActionElementMultiple = new MockElement("action", "fakeBody", "action", "fakeValue",
+                Node.TEXT_NODE, mockActionElementChildrenMultiple, null);
+        // Attempt the method using both types of Elements (single child and multiple child) and confirm
+        // the result is the same for both.  Also confirm the results are as expected.
+        XmlConfigurationProvider prov = new XmlConfigurationProvider("com/opensymphony/xwork2/config/providers/xwork- test.xml", false);
+        Set<String> singleChildResult = prov.buildAllowedMethods(mockActionElementSingle, testPackageConfigBuilder);
+        Set<String> multipleChildResult = prov.buildAllowedMethods(mockActionElementMultiple, testPackageConfigBuilder);
+        assertNotNull("singleChildResult is null ?", singleChildResult);
+        assertNotNull("multipleChildResult is null ?", multipleChildResult);
+        assertEquals("singleChildResult not equal to multipleChildResult ?", singleChildResult, multipleChildResult);
+        // Since both Sets are equal, only need to test one to confirm contents are correct
+        assertEquals("result Sets not of length 3 ?", 3, multipleChildResult.size());
+        assertTrue("allowedMethod1 not present ?", multipleChildResult.contains("allowedMethod1"));
+        assertTrue("allowedMethod2 not present ?", multipleChildResult.contains("allowedMethod2"));
+        assertTrue("allowedMethod3 not present ?", multipleChildResult.contains("allowedMethod3"));
+        assertFalse("noSuchMethod is present ?", multipleChildResult.contains("noSuchMethod"));
+    }
+
+    /**
+     * Test loadGlobalAllowedMethods() to ensure consistent results for processing
+     * <global-allowed-methods/> in <package/> XML configuration elements.
+     * 
+     * @throws Exception 
+     */
+    public void testLoadGlobalAllowedMethods() throws Exception {
+        // Test introduced with WW-5029 fix.
+        // Set up test using two mock DOM Elements:
+        //   1) A mock "package" Element with a single "global-allowed-methods" child Element that contains a single
+        //      TEXT_NODE Node.  This simulates a typical result from a SAX parser parsing the global-allowed-methods
+        //      tag body.
+        //   2) A mock "package" Element with a single "global-allowed-methods" child Element that contains multiple
+        //      TEXT_NODE Nodes.  This simulates an unusal result from a SAX parser parsing the global-allowed-methods
+        //      tag body (as reported with WW-5029).
+        final String fakeBodyString = "allowedMethod1,allowedMethod2,allowedMethod3";
+        PackageConfig.Builder testPackageConfigBuilder = new PackageConfig.Builder("globalAllowedMethodsPackage");
+        List<String> singleStringList = new ArrayList(1);
+        List<String> multipleStringList = new ArrayList(4);
+        singleStringList.add(fakeBodyString);
+        multipleStringList.add("allowedMethod4,");
+        multipleStringList.add("allowed");
+        multipleStringList.add("Method5,");
+        multipleStringList.add("allowedMethod6");
+        NodeList mockNodeListSingleChild = new MockNodeList(singleStringList);
+        NodeList mockNodeListMultipleChild = new MockNodeList(multipleStringList);
+        Element mockSingleChildAllowedMethodsElement = new MockElement("global-allowed-methods", fakeBodyString,
+                "global-allowed-methods", fakeBodyString, Node.TEXT_NODE, mockNodeListSingleChild, null);
+        Element mockMultipleChildAllowedMethodsElement = new MockElement("global-allowed-methods", fakeBodyString,
+                "global-allowed-methods", fakeBodyString, Node.TEXT_NODE, mockNodeListMultipleChild, null);
+        MockNodeList mockPackageElementChildrenSingle = new MockNodeList();
+        mockPackageElementChildrenSingle.addToNodeList(mockSingleChildAllowedMethodsElement);
+        MockNodeList mockPackageElementChildrenMultiple = new MockNodeList();
+        mockPackageElementChildrenMultiple.addToNodeList(mockMultipleChildAllowedMethodsElement);
+        Element mockPackageElementSingle = new MockElement("package", "fakeBody", "package", "fakeValue",
+                Node.TEXT_NODE, mockPackageElementChildrenSingle, null);
+        Element mockPackageElementMultiple = new MockElement("package", "fakeBody", "package", "fakeValue",
+                Node.TEXT_NODE, mockPackageElementChildrenMultiple, null);
+        // Attempt the method using the single child Element and confirm the result is as expected.
+        XmlConfigurationProvider prov = new XmlConfigurationProvider("com/opensymphony/xwork2/config/providers/xwork- test.xml", false);
+        prov.loadGlobalAllowedMethods(testPackageConfigBuilder, mockPackageElementSingle);
+        Set<String> currentGlobalResult = testPackageConfigBuilder.getGlobalAllowedMethods();
+        assertNotNull("currentGlobalResult is null ?", currentGlobalResult);
+        assertEquals("currentGlobalResult Sets not of length 3 ?", 3, currentGlobalResult.size());
+        assertTrue("allowedMethod1 not present ?", currentGlobalResult.contains("allowedMethod1"));
+        assertTrue("allowedMethod2 not present ?", currentGlobalResult.contains("allowedMethod2"));
+        assertTrue("allowedMethod3 not present ?", currentGlobalResult.contains("allowedMethod3"));
+        assertFalse("noSuchMethod is present ?", currentGlobalResult.contains("noSuchMethod"));
+        // Attempt the method using the multiple child Element and confirm the result is as expected.
+        prov.loadGlobalAllowedMethods(testPackageConfigBuilder, mockPackageElementMultiple);
+        currentGlobalResult = testPackageConfigBuilder.getGlobalAllowedMethods();
+        assertNotNull("currentGlobalResult is null ?", currentGlobalResult);
+        assertEquals("currentGlobalResult Sets not of length 6 ?", 6, currentGlobalResult.size());
+        assertTrue("allowedMethod4 not present ?", currentGlobalResult.contains("allowedMethod4"));
+        assertTrue("allowedMethod5 not present ?", currentGlobalResult.contains("allowedMethod5"));
+        assertTrue("allowedMethod6 not present ?", currentGlobalResult.contains("allowedMethod6"));
+        assertFalse("noSuchMethod is present ?", currentGlobalResult.contains("snoSUchMethod"));
+        // Confirm the previously added elements are still present
+        assertTrue("allowedMethod1 not present ?", currentGlobalResult.contains("allowedMethod1"));
+        assertTrue("allowedMethod2 not present ?", currentGlobalResult.contains("allowedMethod2"));
+        assertTrue("allowedMethod3 not present ?", currentGlobalResult.contains("allowedMethod3"));
+    }
+
+    /**
+     * Test buildResults() to ensure consistent results for processing
+     * <result/> in <action/> XML configuration elements.
+     * 
+     * @throws Exception 
+     */
+    public void testBuildResults() throws Exception {
+        // Set up test using two mock DOM Elements:
+        //   1) A mock "action" Element with a two "result" child Elements that each contains a single
+        //      TEXT_NODE Node.  This simulates a typical result from a SAX parser parsing the result
+        //      tag body.
+        //   2) A mock "action" Element with two "result" child Elements that each contain multiple
+        //      TEXT_NODE Nodes.  This simulates an unusal result from a SAX parser parsing the result
+        //      tag body.
+        final String fakeBodyString = "/SomePath/SomePath/SomePath/SomeJSP.jsp";
+        final String fakeBodyString2 = "/SomePath2/SomePath2/SomePath2/SomeJSP2.jsp";
+        final String resultParam = "nonNullDefaultParam";
+        PackageConfig.Builder testPackageConfigBuilder = new PackageConfig.Builder("resultsPackage");
+        ResultTypeConfig.Builder resultTypeConfigBuilder = new ResultTypeConfig.Builder("dispatcher", (String) ServletDispatcherResult.class.getName());
+        resultTypeConfigBuilder.defaultResultParam(resultParam);
+        ResultTypeConfig resultTypeConfig = resultTypeConfigBuilder.build();
+        testPackageConfigBuilder.addResultTypeConfig(resultTypeConfig);
+        List<String> singleStringList = new ArrayList(1);
+        List<String> singleStringList2 = new ArrayList(1);
+        List<String> multipleStringList = new ArrayList(4);
+        List<String> multipleStringList2 = new ArrayList(4);
+        singleStringList.add(fakeBodyString);
+        singleStringList2.add(fakeBodyString2);
+        multipleStringList.add("/SomePath");
+        multipleStringList.add("/SomePath/");
+        multipleStringList.add("SomePath/");
+        multipleStringList.add("SomeJSP.jsp");
+        multipleStringList2.add("/SomePath2");
+        multipleStringList2.add("/SomePath2/");
+        multipleStringList2.add("SomePath2/");
+        multipleStringList2.add("SomeJSP2.jsp");
+        NodeList mockNodeListSingleChild = new MockNodeList(singleStringList);
+        NodeList mockNodeListSingleChild2 = new MockNodeList(singleStringList2);
+        NodeList mockNodeListMultipleChild = new MockNodeList(multipleStringList);
+        NodeList mockNodeListMultipleChild2 = new MockNodeList(multipleStringList2);
+        Element mockSingleChildResultElement = new MockElement("result", fakeBodyString,
+                "result", fakeBodyString, Node.TEXT_NODE, mockNodeListSingleChild, null);
+        mockSingleChildResultElement.setAttribute("name", "input");
+        mockSingleChildResultElement.setAttribute("type", "dispatcher");
+        Element mockSingleChildResultElement2 = new MockElement("result", fakeBodyString2,
+                "result", fakeBodyString2, Node.TEXT_NODE, mockNodeListSingleChild2, null);
+        mockSingleChildResultElement2.setAttribute("name", "success");
+        mockSingleChildResultElement2.setAttribute("type", "dispatcher");
+        Element mockMultipleChildAllowedMethodsElement = new MockElement("result", fakeBodyString,
+                "result", fakeBodyString, Node.TEXT_NODE, mockNodeListMultipleChild, null);
+        mockMultipleChildAllowedMethodsElement.setAttribute("name", "input");
+        mockMultipleChildAllowedMethodsElement.setAttribute("type", "dispatcher");
+        Element mockMultipleChildAllowedMethodsElement2 = new MockElement("result", fakeBodyString2,
+                "result", fakeBodyString2, Node.TEXT_NODE, mockNodeListMultipleChild2, null);
+        mockMultipleChildAllowedMethodsElement2.setAttribute("name", "success");
+        mockMultipleChildAllowedMethodsElement2.setAttribute("type", "dispatcher");
+        MockNodeList mockActionElementChildrenSingle = new MockNodeList();
+        mockActionElementChildrenSingle.addToNodeList(mockSingleChildResultElement);
+        mockActionElementChildrenSingle.addToNodeList(mockSingleChildResultElement2);
+        MockNodeList mockActionElementChildrenMultiple = new MockNodeList();
+        mockActionElementChildrenMultiple.addToNodeList(mockMultipleChildAllowedMethodsElement);
+        mockActionElementChildrenMultiple.addToNodeList(mockMultipleChildAllowedMethodsElement2);
+        Element mockActionElementSingle = new MockElement("action", "fakeBody", "action", "fakeValue",
+                Node.TEXT_NODE, mockActionElementChildrenSingle, null);
+        Element mockActionElementMultiple = new MockElement("action", "fakeBody", "action", "fakeValue",
+                Node.TEXT_NODE, mockActionElementChildrenMultiple, null);
+        // Attempt the method using both types of Elements (single child and multiple child) and confirm
+        // the result is the same for both.  Also confirm the results are as expected.
+        XmlConfigurationProvider prov = new XmlConfigurationProvider("com/opensymphony/xwork2/config/providers/xwork- test.xml", false);
+        Map<String, ResultConfig> singleChildResult = prov.buildResults(mockActionElementSingle, testPackageConfigBuilder);
+        Map<String, ResultConfig> multipleChildResult = prov.buildResults(mockActionElementMultiple, testPackageConfigBuilder);
+        assertNotNull("singleChildResult is null ?", singleChildResult);
+        assertNotNull("multipleChildResult is null ?", multipleChildResult);
+        assertEquals("singleChildResult not equal to multipleChildResult ?", singleChildResult, multipleChildResult);
+        // Since both Maps are equal, only need to test one to confirm the contents of ONE are correct.contents are correct
+        // The multipleChildResult (constructed from the multiple child TEXT_NODES) will be checked for correctness.
+        assertEquals("result Maps not of size 2 ?", 2, multipleChildResult.size());
+        ResultConfig inputResult = multipleChildResult.get("input");
+        ResultConfig successResult = multipleChildResult.get("success");
+        assertNotNull("inputResult (multipleChildResult) is null ?", inputResult);
+        assertNotNull("successResult (multipleChildResult) is null ?", successResult);
+        Map<String, String> inputResultParams = inputResult.getParams();
+        Map<String, String> successResultParams = successResult.getParams();
+        assertNotNull("inputResultParams (multipleChildResult) is null ?", inputResultParams);
+        assertNotNull("successResultParams (multipleChildResult) is null ?", successResultParams);
+        assertEquals("inputResult (multipleChildResult) resultParam value not equal to fakeBodyString ?",
+                fakeBodyString, inputResultParams.get(resultParam));
+        assertEquals("successResult (multipleChildResult) resultParam value not equal to fakeBodyString2 ?",
+                fakeBodyString2, successResultParams.get(resultParam));
+    }
+
+    /**
+     * Test loadGlobalResults() to ensure consistent results for processing
+     * <global-results/> in <package/> XML configuration elements.
+     * 
+     * @throws Exception 
+     */
+    public void testLoadGlobalResults() throws Exception {
+        // Set up test using two mock DOM Elements:
+        //   1) A mock "package" Element containing a mock "global-results" Element with a two "result"
+        //      child Elements that each contains a single TEXT_NODE Node.  This simulates a typical result
+        //      from a SAX parser parsing the result tag body.
+        //   2) A mock "package" Element containing a mock "global-results" Element with two "result"
+        //      child Elements that each contain multiple TEXT_NODE Nodes.  This simulates an unusal result
+        //      from a SAX parser parsing the result tag body.
+        final String fakeBodyString = "/SomePath/SomePath/SomePath/SomeJSP.jsp";
+        final String fakeBodyString2 = "/SomePath2/SomePath2/SomePath2/SomeJSP2.jsp";
+        final String resultParam = "nonNullDefaultParam";
+        PackageConfig.Builder testPackageConfigBuilder = new PackageConfig.Builder("resultsPackage");
+        ResultTypeConfig.Builder resultTypeConfigBuilder = new ResultTypeConfig.Builder("dispatcher", (String) ServletDispatcherResult.class.getName());
+        resultTypeConfigBuilder.defaultResultParam(resultParam);
+        ResultTypeConfig resultTypeConfig = resultTypeConfigBuilder.build();
+        testPackageConfigBuilder.addResultTypeConfig(resultTypeConfig);
+        List<String> singleStringList = new ArrayList(1);
+        List<String> singleStringList2 = new ArrayList(1);
+        List<String> multipleStringList = new ArrayList(4);
+        List<String> multipleStringList2 = new ArrayList(4);
+        singleStringList.add(fakeBodyString);
+        singleStringList2.add(fakeBodyString2);
+        multipleStringList.add("/SomePath");
+        multipleStringList.add("/SomePath/");
+        multipleStringList.add("SomePath/");
+        multipleStringList.add("SomeJSP.jsp");
+        multipleStringList2.add("/SomePath2");
+        multipleStringList2.add("/SomePath2/");
+        multipleStringList2.add("SomePath2/");
+        multipleStringList2.add("SomeJSP2.jsp");
+        NodeList mockNodeListSingleChild = new MockNodeList(singleStringList);
+        NodeList mockNodeListSingleChild2 = new MockNodeList(singleStringList2);
+        NodeList mockNodeListMultipleChild = new MockNodeList(multipleStringList);
+        NodeList mockNodeListMultipleChild2 = new MockNodeList(multipleStringList2);
+        Element mockSingleChildResultElement = new MockElement("result", fakeBodyString,
+                "result", fakeBodyString, Node.TEXT_NODE, mockNodeListSingleChild, null);
+        mockSingleChildResultElement.setAttribute("name", "input");
+        mockSingleChildResultElement.setAttribute("type", "dispatcher");
+        Element mockSingleChildResultElement2 = new MockElement("result", fakeBodyString2,
+                "result", fakeBodyString2, Node.TEXT_NODE, mockNodeListSingleChild2, null);
+        mockSingleChildResultElement2.setAttribute("name", "success");
+        mockSingleChildResultElement2.setAttribute("type", "dispatcher");
+        Element mockMultipleChildAllowedMethodsElement = new MockElement("result", fakeBodyString,
+                "result", fakeBodyString, Node.TEXT_NODE, mockNodeListMultipleChild, null);
+        mockMultipleChildAllowedMethodsElement.setAttribute("name", "input2");
+        mockMultipleChildAllowedMethodsElement.setAttribute("type", "dispatcher");
+        Element mockMultipleChildAllowedMethodsElement2 = new MockElement("result", fakeBodyString,
+                "result", fakeBodyString2, Node.TEXT_NODE, mockNodeListMultipleChild2, null);
+        mockMultipleChildAllowedMethodsElement2.setAttribute("name", "success2");
+        mockMultipleChildAllowedMethodsElement2.setAttribute("type", "dispatcher");
+        MockNodeList mockGlobalResultsElementChildrenSingle = new MockNodeList();
+        mockGlobalResultsElementChildrenSingle.addToNodeList(mockSingleChildResultElement);
+        mockGlobalResultsElementChildrenSingle.addToNodeList(mockSingleChildResultElement2);
+        MockNodeList mockGlobalResultsElementChildrenMultiple = new MockNodeList();
+        mockGlobalResultsElementChildrenMultiple.addToNodeList(mockMultipleChildAllowedMethodsElement);
+        mockGlobalResultsElementChildrenMultiple.addToNodeList(mockMultipleChildAllowedMethodsElement2);
+        Element mockGlobalResultsElementSingle = new MockElement("global-results", "fakeBody", "global-results", "fakeValue",
+                Node.TEXT_NODE, mockGlobalResultsElementChildrenSingle, null);
+        Element mockGlobalResultsGlobalResultsElementMultiple = new MockElement("global-results", "fakeBody", "global-results", "fakeValue",
+                Node.TEXT_NODE, mockGlobalResultsElementChildrenMultiple, null);
+        MockNodeList mockPackageElementChildrenSingle = new MockNodeList();
+        mockPackageElementChildrenSingle.addToNodeList(mockGlobalResultsElementSingle);
+        MockNodeList mockPackageElementChildrenMultiple = new MockNodeList();
+        mockPackageElementChildrenMultiple.addToNodeList(mockGlobalResultsGlobalResultsElementMultiple);
+        Element mockPackageElementSingle = new MockElement("package", "fakeBody", "package", "fakeValue",
+                Node.TEXT_NODE, mockPackageElementChildrenSingle, null);
+        Element mockPackageElementMultiple = new MockElement("package", "fakeBody", "package", "fakeValue",
+                Node.TEXT_NODE, mockPackageElementChildrenMultiple, null);
+        // Attempt the global laod method using single child Elements first, and confirm the results are as expected.
+        XmlConfigurationProvider prov = new XmlConfigurationProvider("com/opensymphony/xwork2/config/providers/xwork- test.xml", false);
+        prov.loadGlobalResults(testPackageConfigBuilder, mockPackageElementSingle);
+        PackageConfig testPackageConfig = testPackageConfigBuilder.build();
+        Map<String, ResultConfig> currentGlobalResults = testPackageConfig.getAllGlobalResults();
+        assertNotNull("currentGlobalResults is null ?", currentGlobalResults);
+        assertEquals("currentGlobalResults size not 2 ?", 2, currentGlobalResults.size());
+        ResultConfig inputResult = currentGlobalResults.get("input");
+        ResultConfig successResult = currentGlobalResults.get("success");
+        assertNotNull("inputResult (currentGlobalResults - single) is null ?", inputResult);
+        assertNotNull("successResult (currentGlobalResults - single) is null ?", successResult);
+        Map<String, String> inputResultParams = inputResult.getParams();
+        Map<String, String> successResultParams = successResult.getParams();
+        assertNotNull("inputResultParams (currentGlobalResults - single) is null ?", inputResultParams);
+        assertNotNull("successResultParams (currentGlobalResults - single) is null ?", successResultParams);
+        assertEquals("inputResult (currentGlobalResults - single) resultParam value not equal to fakeBodyString ?",
+                fakeBodyString, inputResultParams.get(resultParam));
+        assertEquals("successResult (currentGlobalResults - single) resultParam value not equal to fakeBodyString2 ?",
+                fakeBodyString2, successResultParams.get(resultParam));
+        // Attempt the global laod method using mutliple child Elements next, and confirm the results are as expected.
+        prov.loadGlobalResults(testPackageConfigBuilder, mockPackageElementMultiple);
+        testPackageConfig = testPackageConfigBuilder.build();
+        currentGlobalResults = testPackageConfig.getAllGlobalResults();
+        assertNotNull("currentGlobalResults is null ?", currentGlobalResults);
+        assertEquals("currentGlobalResults size not 4 ?", 4, currentGlobalResults.size());
+        ResultConfig inputResult2 = currentGlobalResults.get("input2");
+        ResultConfig successResult2 = currentGlobalResults.get("success2");
+        assertNotNull("inputResult2 (currentGlobalResults - multiple) is null ?", inputResult2);
+        assertNotNull("successResult2 (currentGlobalResults - multiple) is null ?", successResult2);
+        Map<String, String> inputResultParams2 = inputResult2.getParams();
+        Map<String, String> successResultParams2 = successResult2.getParams();
+        assertNotNull("inputResultParams2 (currentGlobalResults - multiple) is null ?", inputResultParams2);
+        assertNotNull("successResultParams2 (currentGlobalResults - multiple) is null ?", successResultParams2);
+        assertEquals("inputResult2 (currentGlobalResults - multiple) resultParam value not equal to fakeBodyString ?",
+                fakeBodyString, inputResultParams2.get(resultParam));
+        assertEquals("successResult2 (currentGlobalResults - multiple) resultParam value not equal to fakeBodyString2 ?",
+                fakeBodyString2, successResultParams2.get(resultParam));
+        // Confirm the previous global results are still present
+        inputResult = currentGlobalResults.get("input");
+        successResult = currentGlobalResults.get("success");
+        assertNotNull("inputResult (currentGlobalResults - single) is null ?", inputResult);
+        assertNotNull("successResult (currentGlobalResults - single) is null ?", successResult);
+        inputResultParams = inputResult.getParams();
+        successResultParams = successResult.getParams();
+        assertNotNull("inputResultParams (currentGlobalResults - single) is null ?", inputResultParams);
+        assertNotNull("successResultParams (currentGlobalResults - single) is null ?", successResultParams);
+        assertEquals("inputResult (currentGlobalResults - single) resultParam value not equal to fakeBodyString ?",
+                fakeBodyString, inputResultParams.get(resultParam));
+        assertEquals("successResult (currentGlobalResults - single) resultParam value not equal to fakeBodyString2 ?",
+                fakeBodyString2, successResultParams.get(resultParam));
+        inputResult = currentGlobalResults.get("input");
+        successResult = currentGlobalResults.get("success");
+        assertNotNull("inputResult (currentGlobalResults - single) is null ?", inputResult);
+        assertNotNull("successResult (currentGlobalResults - single) is null ?", successResult);
+        inputResultParams = inputResult.getParams();
+        successResultParams = successResult.getParams();
+        assertNotNull("inputResultParams (currentGlobalResults - single) is null ?", inputResultParams);
+        assertNotNull("successResultParams (currentGlobalResults - single) is null ?", successResultParams);
+        assertEquals("inputResult (currentGlobalResults - single) resultParam value not equal to fakeBodyString ?",
+                fakeBodyString, inputResultParams.get(resultParam));
+        assertEquals("successResult (currentGlobalResults - single) resultParam value not equal to fakeBodyString2 ?",
+                fakeBodyString2, successResultParams.get(resultParam));
+    }
+
+    /**
+     * Mock NodeList.
+     * 
+     * Provides minimal functionality to permit limited mock DOM testing.
+     */
+    protected class MockNodeList implements org.w3c.dom.NodeList {
+        List<Node> nodeList;
+
+        public MockNodeList() {
+            this.nodeList = new ArrayList(0);
+        }
+
+        /**
+         * Produces TEXT_NODE Nodes based on the input List of Strings.  Node names
+         * follow a simple pattern "nodeX" where X is the index.
+         * 
+         * @param stringList 
+         */
+        public MockNodeList(List<String> stringList) {
+            if (stringList != null) {
+                final int nodeListLength = stringList.size();
+                this.nodeList = new ArrayList(nodeListLength);
+                for (int index = 0; index < nodeListLength; index++) {
+                    this.nodeList.add(new MockNode("node" + index, stringList.get(index), Node.TEXT_NODE, null, null));
+                }
+            } else {
+                this.nodeList = new ArrayList(0);
+            }
+        }
+
+        public MockNodeList(NodeList nodeList) {
+            if (nodeList != null) {
+                final int nodeListLength = nodeList.getLength();
+                this.nodeList = new ArrayList(nodeListLength);
+                for (int index = 0; index < nodeListLength; index++) {
+                    this.nodeList.add(nodeList.item(index));
+                }
+            } else {
+                this.nodeList = new ArrayList(0);
+            }
+        }
+
+        public void addToNodeList(List<Node> nodeList) {
+            if (nodeList != null) {
+                final int nodeListLength = nodeList.size();
+                for (int index = 0; index < nodeListLength; index++) {
+                    this.nodeList.add(nodeList.get(index));
+                }
+            } else {
+                this.nodeList = new ArrayList(0);
+            }
+        }
+
+        public void addToNodeList(Node node) {
+            nodeList.add(node);
+        }
+
+        public void setParentForListNodes(Node parentNode) {
+            if (nodeList != null) {
+                final int nodeListLength = nodeList.size();
+                for (int index = 0; index < nodeListLength; index++) {
+                    Node node = nodeList.get(index);
+                    if (node instanceof MockNode) {
+                        MockNode mockNode = (MockNode) node;
+                        mockNode.setParentNode(parentNode);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public Node item(int index) {
+            return (nodeList != null ? nodeList.get(index) : null);
+        }
+
+        @Override
+        public int getLength() {
+            return (nodeList != null ? nodeList.size() : 0);
+        }
+    }
+
+    /**
+     * MockNode
+     * 
+     * Provides minimal functionality to permit limited mock DOM testing.
+     */
+    protected class MockNode implements org.w3c.dom.Node {
+        final private String nodeName;
+        private String nodeValue;
+        final private short nodeType;
+        private Node parentNode;
+        final private NodeList childNodes;
+
+        public MockNode(String nodeName, String nodeValue, short nodeType, NodeList childNodes, Node parentNode) {
+            this.nodeName = nodeName;
+            this.nodeValue = nodeValue;
+            this.nodeType = nodeType;
+            this.childNodes = childNodes;
+            this.parentNode = parentNode;
+
+            if (childNodes instanceof MockNodeList) {
+                MockNodeList mockNodeList = (MockNodeList) childNodes;
+                mockNodeList.setParentForListNodes(this);
+            }
+        }
+
+        @Override
+        public String getNodeName() {
+            return nodeName;
+        }
+
+        @Override
+        public String getNodeValue() throws DOMException {
+            return nodeValue;
+        }
+
+        @Override
+        public void setNodeValue(String nodeValue) throws DOMException {
+            this.nodeValue = nodeValue;
+        }
+
+        @Override
+        public short getNodeType() {
+            return nodeType;
+        }
+
+        public void setParentNode(Node parentNode) {
+            this.parentNode = parentNode;
+        }
+
+        @Override
+        public Node getParentNode() {
+            return parentNode;
+        }
+
+        @Override
+        public NodeList getChildNodes() {
+            return childNodes;
+        }
+
+        @Override
+        public Node getFirstChild() {
+            if (childNodes != null) {
+                return childNodes.item(0);
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        public Node getLastChild() {
+            if (childNodes != null) {
+                return childNodes.item(childNodes.getLength());
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        public Node getPreviousSibling() {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public Node getNextSibling() {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public NamedNodeMap getAttributes() {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public Document getOwnerDocument() {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public Node insertBefore(Node newChild, Node refChild) throws DOMException {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public Node replaceChild(Node newChild, Node oldChild) throws DOMException {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public Node removeChild(Node oldChild) throws DOMException {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public Node appendChild(Node newChild) throws DOMException {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public boolean hasChildNodes() {
+            return (childNodes != null ? childNodes.getLength() > 0 : false);
+        }
+
+        @Override
+        public Node cloneNode(boolean deep) {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public void normalize() {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public boolean isSupported(String feature, String version) {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public String getNamespaceURI() {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public String getPrefix() {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public void setPrefix(String prefix) throws DOMException {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public String getLocalName() {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public boolean hasAttributes() {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public String getBaseURI() {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public short compareDocumentPosition(Node other) throws DOMException {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public String getTextContent() throws DOMException {
+            return nodeValue;
+        }
+
+        @Override
+        public void setTextContent(String textContent) throws DOMException {
+            setNodeValue(textContent);
+        }
+
+        @Override
+        public boolean isSameNode(Node other) {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public String lookupPrefix(String namespaceURI) {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public boolean isDefaultNamespace(String namespaceURI) {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public String lookupNamespaceURI(String prefix) {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public boolean isEqualNode(Node arg) {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public Object getFeature(String feature, String version) {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public Object setUserData(String key, Object data, UserDataHandler handler) {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public Object getUserData(String key) {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+    }
+
+    /**
+     * Mock Element.
+     * 
+     * Provides minimal functionality to permit limited mock DOM testing.
+     */
+    protected class MockElement extends MockNode implements org.w3c.dom.Element {
+        final private String tagName;
+        final private String tagBody;
+        final private Map<String, String> attributes; 
+
+        public MockElement(String tagName, String tagBody,
+                String nodeName, String nodeValue, short nodeType, NodeList childNodes, Node parentNode) {
+            super(nodeName, nodeValue, nodeType, childNodes, parentNode);
+            this.tagName = nodeName;
+            this.tagBody = nodeValue;
+            attributes = new HashMap<>();
+        }
+
+        @Override
+        public String getTagName() {
+            return tagName;
+        }
+
+        @Override
+        public String getAttribute(String name) {
+            return attributes.get(name);
+        }
+
+        @Override
+        public void setAttribute(String name, String value) throws DOMException {
+            attributes.put(name, value);
+        }
+
+        @Override
+        public void removeAttribute(String name) throws DOMException {
+            attributes.remove(name);
+        }
+
+        @Override
+        public Attr getAttributeNode(String name) {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public Attr setAttributeNode(Attr newAttr) throws DOMException {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public Attr removeAttributeNode(Attr oldAttr) throws DOMException {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public NodeList getElementsByTagName(String name) {
+            if (name != null) {
+                final NodeList tempChildren = getChildNodes();
+                final MockNodeList result = new MockNodeList();
+                if (tempChildren != null) {
+                    final int nodeListLength = tempChildren.getLength();
+                    for (int index = 0; index < nodeListLength; index++) {
+                        Node node = tempChildren.item(index);
+                        if (node instanceof Element) {
+                            Element element = (Element) node;
+                            if (name.equals(element.getTagName())) {
+                                result.addToNodeList(element);
+                            }
+                        }
+                    }
+                }
+                return result;
+            } else {
+                return new MockNodeList((NodeList) null);
+            }
+        }
+
+        @Override
+        public String getAttributeNS(String namespaceURI, String localName) throws DOMException {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public void setAttributeNS(String namespaceURI, String qualifiedName, String value) throws DOMException {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public void removeAttributeNS(String namespaceURI, String localName) throws DOMException {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public Attr getAttributeNodeNS(String namespaceURI, String localName) throws DOMException {
+            return null;  // Sufficient for Result processing
+        }
+
+        @Override
+        public Attr setAttributeNodeNS(Attr newAttr) throws DOMException {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public NodeList getElementsByTagNameNS(String namespaceURI, String localName) throws DOMException {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public boolean hasAttribute(String name) {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public boolean hasAttributeNS(String namespaceURI, String localName) throws DOMException {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public TypeInfo getSchemaTypeInfo() {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public void setIdAttribute(String name, boolean isId) throws DOMException {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public void setIdAttributeNS(String namespaceURI, String localName, boolean isId) throws DOMException {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+
+        @Override
+        public void setIdAttributeNode(Attr idAttr, boolean isId) throws DOMException {
+            throw new UnsupportedOperationException("No mock support.");
+        }
+    }
+
 }