You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by sh...@apache.org on 2018/08/15 11:45:45 UTC

svn commit: r1838081 - in /ofbiz/ofbiz-framework/trunk/framework: base/src/main/java/org/apache/ofbiz/base/util/collections/ base/src/test/java/org/apache/ofbiz/base/collections/ webapp/config/ webapp/dtd/ webapp/src/main/java/org/apache/ofbiz/webapp/c...

Author: shijh
Date: Wed Aug 15 11:45:45 2018
New Revision: 1838081

URL: http://svn.apache.org/viewvc?rev=1838081&view=rev
Log:
Implemented: Add method attribute to request-map to controll a uri can be called GET or POST only
OFBIZ-10438

Thanks: Mathieu Lirzin for the contribution.

Added:
    ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java   (with props)
    ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java   (with props)
    ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/
    ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java   (with props)
    ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java   (with props)
    ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java   (with props)
    ofbiz/ofbiz-framework/trunk/framework/webapp/src/test/java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java   (with props)
Modified:
    ofbiz/ofbiz-framework/trunk/framework/webapp/config/WebappUiLabels.xml
    ofbiz/ofbiz-framework/trunk/framework/webapp/dtd/site-conf.xsd
    ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java
    ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlServlet.java
    ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java

Added: ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java?rev=1838081&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java (added)
+++ ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java Wed Aug 15 11:45:45 2018
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * 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.ofbiz.base.util.collections;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * MultivaluedMap Context
+ *
+ * A MapContext which handles multiple values for the same key.
+ */
+public class MultivaluedMapContext<K, V> extends MapContext<K, List<V>> {
+
+    public static final String module = MultivaluedMapContext.class.getName();
+
+    /**
+     * Create a multi-value map initialized with one context
+     */
+    public MultivaluedMapContext() {
+        push();
+    }
+
+    /**
+     * Associate {@code key} with the single value {@code value}.
+     * If other values are already associated with {@code key} then override them.
+     *
+     * @param key the key to associate {@code value} with
+     * @param value the value to add to the context
+     */
+    public void putSingle(K key, V value) {
+        List<V> box = new LinkedList<>();
+        box.add(value);
+        put(key, box);
+    }
+
+    /**
+     * Associate {@code key} with the single value {@code value}.
+     * If other values are already associated with {@code key},
+     * then add {@code value} to them.
+     *
+     * @param key the key to associate {@code value} with
+     * @param value the value to add to the context
+     */
+    public void add(K key, V value) {
+        List<V> cur = contexts.getFirst().get(key);
+        if (cur == null) {
+            cur = new LinkedList<>();
+            /* if this method is called after a context switch, copy the previous values
+               in current context to not mask them. */
+            List<V> old = get(key);
+            if (old != null) {
+                cur.addAll(old);
+            }
+        }
+        cur.add(value);
+        put(key, cur);
+    }
+
+    /**
+     * Get the first value contained in the list of values associated with {@code key}.
+     *
+     * @param key a candidate key
+     * @return the first value associated with {@code key} or null if no value
+     * is associated with it.
+     */
+    public V getFirst(Object key) {
+        List<V> res = get(key);
+        return res == null ? null : res.get(0);
+    }
+}

Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java?rev=1838081&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java (added)
+++ ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java Wed Aug 15 11:45:45 2018
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * 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.ofbiz.base.util.collections;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+// Adapter which allows viewing a multi-value map as a single-value map.
+public class MultivaluedMapContextAdapter<K, V> implements Map<K, V> {
+    private MultivaluedMapContext<K, V> adaptee;
+
+    public MultivaluedMapContextAdapter(MultivaluedMapContext<K, V> adaptee) {
+        this.adaptee = adaptee;
+    }
+
+    @Override
+    public int size() {
+        return adaptee.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return adaptee.isEmpty();
+    }
+
+    @Override
+    public boolean containsKey(Object key) {
+        return adaptee.containsKey(key);
+    }
+
+    @Override
+    public boolean containsValue(Object value) {
+        return adaptee.values().stream()
+                .map(l -> l.get(0))
+                .anyMatch(value::equals);
+    }
+
+    @Override
+    public V get(Object key) {
+        return adaptee.getFirst(key);
+    }
+
+    @Override
+    public V put(K key, V value) {
+        V prev = get(key);
+        adaptee.putSingle(key, value);
+        return prev;
+    }
+
+    @Override
+    public V remove(Object key) {
+        V prev = get(key);
+        adaptee.remove(key);
+        return prev;
+    }
+
+    @Override
+    public void putAll(Map<? extends K, ? extends V> m) {
+        m.forEach(adaptee::putSingle);
+    }
+
+    @Override
+    public void clear() {
+        adaptee.clear();
+    }
+
+    @Override
+    public Set<K> keySet() {
+        return adaptee.keySet();
+    }
+
+    @Override
+    public Collection<V> values() {
+        return adaptee.values().stream()
+                .map(l -> l.get(0))
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public Set<Entry<K, V>> entrySet() {
+        return adaptee.keySet().stream()
+                .collect(Collectors.toMap(k -> k, k -> get(k)))
+                .entrySet();
+    }
+}

Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java?rev=1838081&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java (added)
+++ ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java Wed Aug 15 11:45:45 2018
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * 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.ofbiz.base.collections;
+
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+
+import org.apache.ofbiz.base.util.collections.MultivaluedMapContext;
+import org.apache.ofbiz.base.util.collections.MultivaluedMapContextAdapter;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MultivaluedMapContextAdapterTests {
+    private MultivaluedMapContext<String, Integer> adaptee;
+    private MultivaluedMapContextAdapter<String, Integer> adapter;
+
+    @Before
+    public void setUp() throws Exception {
+        adaptee = new MultivaluedMapContext<>();
+        adaptee.add("foo", 0);
+        adaptee.add("foo", 1);
+        adaptee.add("foo", 2);
+        adaptee.add("bar", 3);
+        adapter = new MultivaluedMapContextAdapter<>(adaptee);
+    }
+
+    @Test
+    public void containsValueBasic() {
+        assertTrue(adapter.containsValue(0));
+        assertFalse(adapter.containsValue(1));
+        assertFalse(adapter.containsValue(2));
+        assertTrue(adapter.containsValue(3));
+    }
+
+    @Test
+    public void valuesBasic() {
+        assertThat(adapter.values(), hasItems(0, 3));
+        assertThat(adapter.values().size(), is(2));
+    }
+
+    @Test
+    public void entrySetBasic() {
+        HashMap<String, Integer> expected = new HashMap<>();
+        expected.put("foo", 0);
+        expected.put("bar", 3);
+        assertEquals(expected.entrySet(), adapter.entrySet());
+    }
+}

Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java?rev=1838081&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java (added)
+++ ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java Wed Aug 15 11:45:45 2018
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * 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.ofbiz.base.collections;
+
+import static org.hamcrest.CoreMatchers.both;
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+import org.apache.ofbiz.base.util.collections.MultivaluedMapContext;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MultivaluedMapContextTests {
+    private MultivaluedMapContext<String, Integer> m;
+
+    @Before
+    public void setUp() throws Exception {
+        m = new MultivaluedMapContext<>();
+    }
+
+    @Test
+    public void getEmpty() {
+        assertThat(m.get("foo"), is(nullValue()));
+    }
+
+    @Test
+    public void putSingleBasic() {
+        m.putSingle("foo", 0);
+        assertThat(m.get("foo"), hasItem(0));
+        m.putSingle("foo", 1);
+        assertThat(m.get("foo"), both(hasItem(1)).and(not(hasItem(0))));
+    }
+
+    @Test
+    public void addBasic() {
+        m.add("foo", 0);
+        assertThat(m.get("foo"), hasItem(0));
+        m.add("foo", 1);
+        assertThat(m.get("foo"), hasItems(0, 1));
+    }
+
+    @Test
+    public void addWithPreviousContext() {
+        m.add("foo", 0);
+        m.push();
+        assertThat(m.get("foo"), hasItem(0));
+        m.add("foo", 1);
+        assertThat(m.get("foo"), hasItems(0, 1));
+    }
+
+    @Test
+    public void getFirstBasic() {
+        m.add("foo", 0);
+        m.add("foo", 1);
+        assertThat(m.getFirst("foo"), is(0));
+    }
+
+    @Test
+    public void getFirstEmpty() {
+        assertThat(m.getFirst("foo"), is(nullValue()));
+    }
+}

Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: ofbiz/ofbiz-framework/trunk/framework/webapp/config/WebappUiLabels.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/webapp/config/WebappUiLabels.xml?rev=1838081&r1=1838080&r2=1838081&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/webapp/config/WebappUiLabels.xml (original)
+++ ofbiz/ofbiz-framework/trunk/framework/webapp/config/WebappUiLabels.xml Wed Aug 15 11:45:45 2018
@@ -339,4 +339,8 @@
         <value xml:lang="zh">没有完成事件</value>
         <value xml:lang="zh-TW">沒有完成事件</value>
     </property>
+    <property key="RequestMethodNotMatchConfig">
+        <value xml:lang="en">[{0}] cannot be called by [{1}] method.</value>
+        <value xml:lang="zh">[{0}]不能用[{1}]方法请求。</value>
+    </property>
 </resource>

Modified: ofbiz/ofbiz-framework/trunk/framework/webapp/dtd/site-conf.xsd
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/webapp/dtd/site-conf.xsd?rev=1838081&r1=1838080&r2=1838081&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/webapp/dtd/site-conf.xsd (original)
+++ ofbiz/ofbiz-framework/trunk/framework/webapp/dtd/site-conf.xsd Wed Aug 15 11:45:45 2018
@@ -216,6 +216,24 @@ under the License.
                 </xs:documentation>
             </xs:annotation>
         </xs:attribute>
+        <xs:attribute name="method" use="optional" default="all">
+            <xs:annotation>
+                <xs:documentation>
+                    The HTTP of this request. This will be the HTTP method used to access the request.
+                </xs:documentation>
+            </xs:annotation>
+            <xs:simpleType>
+                <xs:restriction base="xs:token">
+                    <xs:enumeration value="get"/>
+                    <xs:enumeration value="post"/>
+                    <xs:enumeration value="put"/>
+                    <xs:enumeration value="delete"/>
+                    <xs:enumeration value="patch"/>
+                    <xs:enumeration value="options"/>
+                    <xs:enumeration value="all"/>
+                </xs:restriction>
+            </xs:simpleType>
+        </xs:attribute>
         <xs:attribute type="xs:boolean" name="edit" default="true">
             <xs:annotation>
                 <xs:documentation>

Modified: ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java?rev=1838081&r1=1838080&r2=1838081&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java (original)
+++ ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java Wed Aug 15 11:45:45 2018
@@ -49,6 +49,8 @@ import org.apache.ofbiz.base.util.UtilVa
 import org.apache.ofbiz.base.util.UtilXml;
 import org.apache.ofbiz.base.util.cache.UtilCache;
 import org.apache.ofbiz.base.util.collections.MapContext;
+import org.apache.ofbiz.base.util.collections.MultivaluedMapContext;
+import org.apache.ofbiz.base.util.collections.MultivaluedMapContextAdapter;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
@@ -192,7 +194,7 @@ public class ConfigXMLReader {
         private Map<String, Event> beforeLogoutEventList = new LinkedHashMap<String, Event>();
         private Map<String, String> eventHandlerMap = new HashMap<String, String>();
         private Map<String, String> viewHandlerMap = new HashMap<String, String>();
-        private Map<String, RequestMap> requestMapMap = new HashMap<String, RequestMap>();
+        private MultivaluedMapContext<String, RequestMap> requestMapMap = new MultivaluedMapContext<>();
         private Map<String, ViewMap> viewMapMap = new HashMap<String, ViewMap>();
 
         public ControllerConfig(URL url) throws WebAppConfigurationException {
@@ -276,11 +278,16 @@ public class ConfigXMLReader {
             return getIncludes(ccfg -> ccfg.protectView);
         }
 
+        // XXX: Keep it for backward compatibility until moving everything to 鈥榞etRequestMapMultiMap鈥�.
         public Map<String, RequestMap> getRequestMapMap() throws WebAppConfigurationException {
-            MapContext<String, RequestMap> result = new MapContext<>();
+            return new MultivaluedMapContextAdapter<>(getRequestMapMultiMap());
+        }
+
+        public MultivaluedMapContext<String, RequestMap> getRequestMapMultiMap() throws WebAppConfigurationException {
+            MultivaluedMapContext<String, RequestMap> result = new MultivaluedMapContext<>();
             for (URL includeLocation : includes) {
                 ControllerConfig controllerConfig = getControllerConfig(includeLocation);
-                result.push(controllerConfig.getRequestMapMap());
+                result.push(controllerConfig.getRequestMapMultiMap());
             }
             result.push(requestMapMap);
             return result;
@@ -403,7 +410,7 @@ public class ConfigXMLReader {
         private void loadRequestMap(Element root) {
             for (Element requestMapElement : UtilXml.childElementList(root, "request-map")) {
                 RequestMap requestMap = new RequestMap(requestMapElement);
-                this.requestMapMap.put(requestMap.uri, requestMap);
+                this.requestMapMap.add(requestMap.uri, requestMap);
             }
         }
 
@@ -450,6 +457,7 @@ public class ConfigXMLReader {
 
     public static class RequestMap {
         public String uri;
+        public String method;
         public boolean edit = true;
         public boolean trackVisit = true;
         public boolean trackServerHit = true;
@@ -466,6 +474,7 @@ public class ConfigXMLReader {
         public RequestMap(Element requestMapElement) {
             // Get the URI info
             this.uri = requestMapElement.getAttribute("uri");
+            this.method = requestMapElement.getAttribute("method");
             this.edit = !"false".equals(requestMapElement.getAttribute("edit"));
             this.trackServerHit = !"false".equals(requestMapElement.getAttribute("track-serverhit"));
             this.trackVisit = !"false".equals(requestMapElement.getAttribute("track-visit"));

Modified: ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlServlet.java
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlServlet.java?rev=1838081&r1=1838080&r2=1838081&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlServlet.java (original)
+++ ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlServlet.java Wed Aug 15 11:45:45 2018
@@ -211,6 +211,12 @@ public class ControlServlet extends Http
         try {
             // the ServerHitBin call for the event is done inside the doRequest method
             requestHandler.doRequest(request, response, null, userLogin, delegator);
+        } catch (MethodNotAllowedException e) {
+            response.setContentType("text/plain");
+            response.setCharacterEncoding(request.getCharacterEncoding());
+            response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+            response.getWriter().print(e.getMessage());
+            Debug.logError(e.getMessage(), module);
         } catch (RequestHandlerException e) {
             Throwable throwable = e.getNested() != null ? e.getNested() : e;
             if (throwable instanceof IOException) {

Added: ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java?rev=1838081&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java (added)
+++ ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java Wed Aug 15 11:45:45 2018
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * 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.ofbiz.webapp.control;
+
+@SuppressWarnings("serial")
+public class MethodNotAllowedException extends RequestHandlerException {
+    MethodNotAllowedException(String str) {
+        super(str);
+    }
+}

Propchange: ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java?rev=1838081&r1=1838080&r2=1838081&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java (original)
+++ ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java Wed Aug 15 11:45:45 2018
@@ -24,11 +24,15 @@ import java.io.IOException;
 import java.io.Serializable;
 import java.net.URL;
 import java.security.cert.X509Certificate;
+
+import java.util.Collection;
+import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Optional;
 
 import javax.servlet.ServletContext;
 import javax.servlet.http.HttpServletRequest;
@@ -45,12 +49,14 @@ import org.apache.ofbiz.base.util.UtilMi
 import org.apache.ofbiz.base.util.UtilObject;
 import org.apache.ofbiz.base.util.UtilProperties;
 import org.apache.ofbiz.base.util.UtilValidate;
+import org.apache.ofbiz.base.util.collections.MultivaluedMapContext;
 import org.apache.ofbiz.entity.Delegator;
 import org.apache.ofbiz.entity.GenericEntityException;
 import org.apache.ofbiz.entity.GenericValue;
 import org.apache.ofbiz.entity.util.EntityQuery;
 import org.apache.ofbiz.entity.util.EntityUtilProperties;
 import org.apache.ofbiz.webapp.OfbizUrlBuilder;
+import org.apache.ofbiz.webapp.control.ConfigXMLReader.RequestMap;
 import org.apache.ofbiz.webapp.event.EventFactory;
 import org.apache.ofbiz.webapp.event.EventHandler;
 import org.apache.ofbiz.webapp.event.EventHandlerException;
@@ -68,12 +74,74 @@ import org.apache.ofbiz.widget.model.The
 public class RequestHandler {
 
     public static final String module = RequestHandler.class.getName();
-    private final String defaultStatusCodeString = UtilProperties.getPropertyValue("requestHandler", "status-code", "302");
+    private final static String defaultStatusCodeString =
+            UtilProperties.getPropertyValue("requestHandler", "status-code", "302");
     private final ViewFactory viewFactory;
     private final EventFactory eventFactory;
     private final URL controllerConfigURL;
     private final boolean trackServerHit;
     private final boolean trackVisit;
+    private ControllerConfig ccfg;
+
+    static class ControllerConfig {
+        private final MultivaluedMapContext<String, RequestMap> requestMapMap;
+        private final Map<String, ConfigXMLReader.ViewMap> viewMapMap;
+        private String statusCodeString;
+        private final String defaultRequest;
+        private final Map<String, ConfigXMLReader.Event> firstVisitEventList;
+        private final Map<String, ConfigXMLReader.Event> preprocessorEventList;
+        private final Map<String, ConfigXMLReader.Event> postprocessorEventList;
+        private final String protectView;
+
+        ControllerConfig(ConfigXMLReader.ControllerConfig ccfg) throws WebAppConfigurationException {
+            preprocessorEventList = ccfg.getPreprocessorEventList();
+            postprocessorEventList = ccfg.getPostprocessorEventList();
+            requestMapMap = ccfg.getRequestMapMultiMap();
+            viewMapMap = ccfg.getViewMapMap();
+            defaultRequest = ccfg.getDefaultRequest();
+            firstVisitEventList = ccfg.getFirstVisitEventList();
+            protectView = ccfg.getProtectView();
+
+            String status = ccfg.getStatusCode();
+            statusCodeString = UtilValidate.isEmpty(status) ? defaultStatusCodeString : status;
+        }
+
+        public MultivaluedMapContext<String, RequestMap> getRequestMapMap() {
+            return requestMapMap;
+        }
+
+        public Map<String, ConfigXMLReader.ViewMap> getViewMapMap() {
+            return viewMapMap;
+        }
+
+        public String getStatusCodeString() {
+            return statusCodeString;
+        }
+
+        public String getDefaultRequest() {
+            return defaultRequest;
+        }
+
+        public void setStatusCodeString(String statusCodeString) {
+            this.statusCodeString = statusCodeString;
+        }
+
+        public Map<String, ConfigXMLReader.Event> getFirstVisitEventList() {
+            return firstVisitEventList;
+        }
+
+        public Map<String, ConfigXMLReader.Event> getPreprocessorEventList() {
+            return preprocessorEventList;
+        }
+
+        public Map<String, ConfigXMLReader.Event> getPostprocessorEventList() {
+            return postprocessorEventList;
+        }
+
+        public String getProtectView() {
+            return protectView;
+        }
+    }
 
     public static RequestHandler getRequestHandler(ServletContext servletContext) {
         RequestHandler rh = (RequestHandler) servletContext.getAttribute("_REQUEST_HANDLER_");
@@ -110,6 +178,56 @@ public class RequestHandler {
         return null;
     }
 
+    /**
+     * Find a collection of request maps in {@code ccfg} matching {@code req}.
+     * Otherwise fall back to matching the {@code defaultReq} field in {@code ccfg}.
+     *
+     * @param ccfg The controller containing the current configuration
+     * @param req The HTTP request to match
+     * @return a collection of request maps which might be empty
+     */
+    static Collection<RequestMap> resolveURI(ControllerConfig ccfg, HttpServletRequest req) {
+        Map<String, List<RequestMap>> requestMapMap = ccfg.getRequestMapMap();
+        Map<String, ConfigXMLReader.ViewMap> viewMapMap = ccfg.getViewMapMap();
+        String defaultRequest = ccfg.getDefaultRequest();
+        String path = req.getPathInfo();
+        String requestUri = getRequestUri(path);
+        String viewUri = getOverrideViewUri(path);
+        Collection<RequestMap> rmaps;
+        if (requestMapMap.containsKey(requestUri) && !viewMapMap.containsKey(viewUri)) {
+            rmaps = requestMapMap.get(requestUri);
+        } else if (defaultRequest != null) {
+            rmaps = requestMapMap.get(defaultRequest);
+        } else {
+            rmaps = null;
+        }
+        return rmaps != null ? rmaps : Collections.emptyList();
+    }
+
+    /**
+     * Find the request map matching {@code method}.
+     * Otherwise fall back to the one matching the "all" and "" special methods
+     * in that respective order.
+     *
+     * @param method the HTTP method to match
+     * @param rmaps the collection of request map candidates
+     * @return a request map {@code Optional}
+     */
+    static Optional<RequestMap> resolveMethod(String method, Collection<RequestMap> rmaps) {
+        for (RequestMap map : rmaps) {
+            if (map.method.equalsIgnoreCase(method)) {
+                return Optional.of(map);
+            }
+        }
+        if (method.isEmpty()) {
+            return Optional.empty();
+        } else if (method.equals("all")) {
+            return resolveMethod("", rmaps);
+        } else {
+            return resolveMethod("all", rmaps);
+        }
+    }
+
     public void doRequest(HttpServletRequest request, HttpServletResponse response, String requestUri) throws RequestHandlerException, RequestHandlerExceptionAllowExternalRequests {
         HttpSession session = request.getSession();
         Delegator delegator = (Delegator) request.getAttribute("delegator");
@@ -125,20 +243,13 @@ public class RequestHandler {
         long startTime = System.currentTimeMillis();
         HttpSession session = request.getSession();
 
-        // get the controllerConfig once for this method so we don't have to get it over and over inside the method
-        ConfigXMLReader.ControllerConfig controllerConfig = this.getControllerConfig();
-        Map<String, ConfigXMLReader.RequestMap> requestMapMap = null;
-        String statusCodeString = null;
+        // Parse controller config.
         try {
-            requestMapMap = controllerConfig.getRequestMapMap();
-            statusCodeString = controllerConfig.getStatusCode();
+            ccfg = new ControllerConfig(getControllerConfig());
         } catch (WebAppConfigurationException e) {
             Debug.logError(e, "Exception thrown while parsing controller.xml file: ", module);
             throw new RequestHandlerException(e);
         }
-        if (UtilValidate.isEmpty(statusCodeString)) {
-            statusCodeString = defaultStatusCodeString;
-        }
 
         // workaround if we are in the root webapp
         String cname = UtilHttp.getApplicationName(request);
@@ -153,50 +264,29 @@ public class RequestHandler {
             }
         }
 
-        String overrideViewUri = RequestHandler.getOverrideViewUri(request.getPathInfo());
-
-        String requestMissingErrorMessage = "Unknown request [" + defaultRequestUri + "]; this request does not exist or cannot be called directly.";
-        ConfigXMLReader.RequestMap requestMap = null;
-        if (defaultRequestUri != null) {
-            requestMap = requestMapMap.get(defaultRequestUri);
-        }
-        // check for default request
-        if (requestMap == null) {
-            String defaultRequest;
-            try {
-                defaultRequest = controllerConfig.getDefaultRequest();
-            } catch (WebAppConfigurationException e) {
-                Debug.logError(e, "Exception thrown while parsing controller.xml file: ", module);
-                throw new RequestHandlerException(e);
-            }
-            if (defaultRequest != null) { // required! to avoid a null pointer exception and generate a requesthandler exception if default request not found.
-                requestMap = requestMapMap.get(defaultRequest);
-            }
-        }
-
-        // check for override view
-        if (overrideViewUri != null) {
-            ConfigXMLReader.ViewMap viewMap;
-            try {
-                viewMap = getControllerConfig().getViewMapMap().get(overrideViewUri);
-                if (viewMap == null) {
-                    String defaultRequest = controllerConfig.getDefaultRequest();
-                    if (defaultRequest != null) { // required! to avoid a null pointer exception and generate a requesthandler exception if default request not found.
-                        requestMap = requestMapMap.get(defaultRequest);
-                    }
-                }
-            } catch (WebAppConfigurationException e) {
-                Debug.logError(e, "Exception thrown while parsing controller.xml file: ", module);
-                throw new RequestHandlerException(e);
+        String requestMissingErrorMessage = "Unknown request ["
+                + defaultRequestUri
+                + "]; this request does not exist or cannot be called directly.";
+
+        String path = request.getPathInfo();
+        String requestUri = getRequestUri(path);
+        String overrideViewUri = getOverrideViewUri(path);
+
+        Collection<RequestMap> rmaps = resolveURI(ccfg, request);
+        if (rmaps.isEmpty()) {
+            if (throwRequestHandlerExceptionOnMissingLocalRequest) {
+              throw new RequestHandlerException(requestMissingErrorMessage);
+            } else {
+              throw new RequestHandlerExceptionAllowExternalRequests();
             }
         }
 
-        // if no matching request is found in the controller, depending on throwRequestHandlerExceptionOnMissingLocalRequest
-        //  we throw a RequestHandlerException or RequestHandlerExceptionAllowExternalRequests
-        if (requestMap == null) {
-            if (throwRequestHandlerExceptionOnMissingLocalRequest) throw new RequestHandlerException(requestMissingErrorMessage);
-            else throw new RequestHandlerExceptionAllowExternalRequests();
-         }
+        String method = request.getMethod();
+        RequestMap requestMap = resolveMethod(method, rmaps).orElseThrow(() -> {
+            String msg = UtilProperties.getMessage("WebappUiLabels", "RequestMethodNotMatchConfig",
+                    UtilMisc.toList(requestUri, method), UtilHttp.getLocale(request));
+            return new MethodNotAllowedException(msg);
+        });
 
         String eventReturn = null;
         if (requestMap.metrics != null && requestMap.metrics.getThreshold() != 0.0 && requestMap.metrics.getTotalEvents() > 3 && requestMap.metrics.getThreshold() < requestMap.metrics.getServiceRate()) {
@@ -210,7 +300,7 @@ public class RequestHandler {
         // Check for chained request.
         if (chain != null) {
             String chainRequestUri = RequestHandler.getRequestUri(chain);
-            requestMap = requestMapMap.get(chainRequestUri);
+            requestMap = ccfg.getRequestMapMap().getFirst(chainRequestUri);
             if (requestMap == null) {
                 throw new RequestHandlerException("Unknown chained request [" + chainRequestUri + "]; this request does not exist");
             }
@@ -234,18 +324,11 @@ public class RequestHandler {
             // Check to make sure we are allowed to access this request directly. (Also checks if this request is defined.)
             // If the request cannot be called, or is not defined, check and see if there is a default-request we can process
             if (!requestMap.securityDirectRequest) {
-                String defaultRequest;
-                try {
-                    defaultRequest = controllerConfig.getDefaultRequest();
-                } catch (WebAppConfigurationException e) {
-                    Debug.logError(e, "Exception thrown while parsing controller.xml file: ", module);
-                    throw new RequestHandlerException(e);
-                }
-                if (defaultRequest == null || !requestMapMap.get(defaultRequest).securityDirectRequest) {
+                if (ccfg.getDefaultRequest() == null || !ccfg.getRequestMapMap().getFirst(ccfg.getDefaultRequest()).securityDirectRequest) {
                     // use the same message as if it was missing for security reasons, ie so can't tell if it is missing or direct request is not allowed
                     throw new RequestHandlerException(requestMissingErrorMessage);
                 } else {
-                    requestMap = requestMapMap.get(defaultRequest);
+                    requestMap = ccfg.getRequestMapMap().getFirst(ccfg.getDefaultRequest());
                 }
             }
             // Check if we SHOULD be secure and are not.
@@ -288,7 +371,7 @@ public class RequestHandler {
                     String newUrl = RequestHandler.makeUrl(request, response, urlBuf.toString());
                     if (newUrl.toUpperCase().startsWith("HTTPS")) {
                         // if we are supposed to be secure, redirect secure.
-                        callRedirect(newUrl, response, request, statusCodeString);
+                        callRedirect(newUrl, response, request, ccfg.getStatusCodeString());
                         return;
                     }
                 }
@@ -333,63 +416,52 @@ public class RequestHandler {
                 if (Debug.infoOn())
                     Debug.logInfo("This is the first request in this visit." + showSessionId(request), module);
                 session.setAttribute("_FIRST_VISIT_EVENTS_", "complete");
-                try {
-                    for (ConfigXMLReader.Event event: controllerConfig.getFirstVisitEventList().values()) {
-                        try {
-                            String returnString = this.runEvent(request, response, event, null, "firstvisit");
-                            if (returnString == null || "none".equalsIgnoreCase(returnString)) {
-                                interruptRequest = true;
-                            } else if (!"success".equalsIgnoreCase(returnString)) {
-                                throw new EventHandlerException("First-Visit event did not return 'success'.");
-                            }
-                        } catch (EventHandlerException e) {
-                            Debug.logError(e, module);
+                for (ConfigXMLReader.Event event: ccfg.getFirstVisitEventList().values()) {
+                    try {
+                        String returnString = this.runEvent(request, response, event, null, "firstvisit");
+                        if (returnString == null || "none".equalsIgnoreCase(returnString)) {
+                            interruptRequest = true;
+                        } else if (!"success".equalsIgnoreCase(returnString)) {
+                            throw new EventHandlerException("First-Visit event did not return 'success'.");
                         }
+                    } catch (EventHandlerException e) {
+                        Debug.logError(e, module);
                     }
-                } catch (WebAppConfigurationException e) {
-                    Debug.logError(e, "Exception thrown while parsing controller.xml file: ", module);
-                    throw new RequestHandlerException(e);
                 }
             }
 
             // Invoke the pre-processor (but NOT in a chain)
-            try {
-                for (ConfigXMLReader.Event event: controllerConfig.getPreprocessorEventList().values()) {
-                    try {
-                        String returnString = this.runEvent(request, response, event, null, "preprocessor");
-                        if (returnString == null || "none".equalsIgnoreCase(returnString)) {
-                            interruptRequest = true;
-                        } else if (!"success".equalsIgnoreCase(returnString)) {
-                            if (!returnString.contains(":_protect_:")) {
-                                throw new EventHandlerException("Pre-Processor event [" + event.invoke + "] did not return 'success'.");
-                            } else { // protect the view normally rendered and redirect to error response view
-                                returnString = returnString.replace(":_protect_:", "");
-                                if (returnString.length() > 0) {
-                                    request.setAttribute("_ERROR_MESSAGE_", returnString);
-                                }
-                                eventReturn = null;
-                                // check to see if there is a "protect" response, if so it's ok else show the default_error_response_view
-                                if (!requestMap.requestResponseMap.containsKey("protect")) {
-                                    String protectView = controllerConfig.getProtectView();
-                                    if (protectView != null) {
-                                        overrideViewUri = protectView;
-                                    } else {
-                                        overrideViewUri = EntityUtilProperties.getPropertyValue("security", "default.error.response.view", delegator);
-                                        overrideViewUri = overrideViewUri.replace("view:", "");
-                                        if ("none:".equals(overrideViewUri)) {
-                                            interruptRequest = true;
-                                        }
+            for (ConfigXMLReader.Event event: ccfg.getPreprocessorEventList().values()) {
+                try {
+                    String returnString = this.runEvent(request, response, event, null, "preprocessor");
+                    if (returnString == null || "none".equalsIgnoreCase(returnString)) {
+                        interruptRequest = true;
+                    } else if (!"success".equalsIgnoreCase(returnString)) {
+                        if (!returnString.contains(":_protect_:")) {
+                            throw new EventHandlerException("Pre-Processor event [" + event.invoke + "] did not return 'success'.");
+                        } else { // protect the view normally rendered and redirect to error response view
+                            returnString = returnString.replace(":_protect_:", "");
+                            if (returnString.length() > 0) {
+                                request.setAttribute("_ERROR_MESSAGE_", returnString);
+                            }
+                            eventReturn = null;
+                            // check to see if there is a "protect" response, if so it's ok else show the default_error_response_view
+                            if (!requestMap.requestResponseMap.containsKey("protect")) {
+                                if (ccfg.getProtectView() != null) {
+                                    overrideViewUri = ccfg.getProtectView();
+                                } else {
+                                    overrideViewUri = EntityUtilProperties.getPropertyValue("security", "default.error.response.view", delegator);
+                                    overrideViewUri = overrideViewUri.replace("view:", "");
+                                    if ("none:".equals(overrideViewUri)) {
+                                        interruptRequest = true;
                                     }
                                 }
                             }
                         }
-                    } catch (EventHandlerException e) {
-                        Debug.logError(e, module);
                     }
+                } catch (EventHandlerException e) {
+                    Debug.logError(e, module);
                 }
-            } catch (WebAppConfigurationException e) {
-                Debug.logError(e, "Exception thrown while parsing controller.xml file: ", module);
-                throw new RequestHandlerException(e);
             }
         }
 
@@ -409,7 +481,7 @@ public class RequestHandler {
             // Invoke the security handler
             // catch exceptions and throw RequestHandlerException if failed.
             if (Debug.verboseOn()) Debug.logVerbose("[RequestHandler]: AuthRequired. Running security check. " + showSessionId(request), module);
-            ConfigXMLReader.Event checkLoginEvent = requestMapMap.get("checkLogin").event;
+            ConfigXMLReader.Event checkLoginEvent = ccfg.getRequestMapMap().getFirst("checkLogin").event;
             String checkLoginReturnString = null;
 
             try {
@@ -422,9 +494,9 @@ public class RequestHandler {
                 eventReturn = checkLoginReturnString;
                 // if the request is an ajax request we don't want to return the default login check
                 if (!"XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
-                    requestMap = requestMapMap.get("checkLogin");
+                    requestMap = ccfg.getRequestMapMap().getFirst("checkLogin");
                 } else {
-                    requestMap = requestMapMap.get("ajaxCheckLogin");
+                    requestMap = ccfg.getRequestMapMap().getFirst("ajaxCheckLogin");
                 }
             }
         }
@@ -556,7 +628,7 @@ public class RequestHandler {
                     redirectTarget += "?" + queryString;
                 }
                 
-                callRedirect(makeLink(request, response, redirectTarget), response, request, statusCodeString);
+                callRedirect(makeLink(request, response, redirectTarget), response, request, ccfg.getStatusCodeString());
                 return;
             }
         }
@@ -603,41 +675,35 @@ public class RequestHandler {
             // ======== handle views ========
 
             // first invoke the post-processor events.
-            try {
-                for (ConfigXMLReader.Event event: controllerConfig.getPostprocessorEventList().values()) {
-                    try {
-                        String returnString = this.runEvent(request, response, event, requestMap, "postprocessor");
-                        if (returnString != null && !"success".equalsIgnoreCase(returnString)) {
-                            throw new EventHandlerException("Post-Processor event did not return 'success'.");
-                        }
-                    } catch (EventHandlerException e) {
-                        Debug.logError(e, module);
+            for (ConfigXMLReader.Event event: ccfg.getPostprocessorEventList().values()) {
+                try {
+                    String returnString = this.runEvent(request, response, event, requestMap, "postprocessor");
+                    if (returnString != null && !"success".equalsIgnoreCase(returnString)) {
+                        throw new EventHandlerException("Post-Processor event did not return 'success'.");
                     }
+                } catch (EventHandlerException e) {
+                    Debug.logError(e, module);
                 }
-            } catch (WebAppConfigurationException e) {
-                Debug.logError(e, "Exception thrown while parsing controller.xml file: ", module);
-                throw new RequestHandlerException(e);
             }
 
             String responseStatusCode  = nextRequestResponse.statusCode;
             if(UtilValidate.isNotEmpty(responseStatusCode))
-                statusCodeString = responseStatusCode;            
-            
+                ccfg.setStatusCodeString(responseStatusCode);
             
             if ("url".equals(nextRequestResponse.type)) {
                 if (Debug.verboseOn()) Debug.logVerbose("[RequestHandler.doRequest]: Response is a URL redirect." + showSessionId(request), module);
-                callRedirect(nextRequestResponse.value, response, request, statusCodeString);
+                callRedirect(nextRequestResponse.value, response, request, ccfg.getStatusCodeString());
             } else if ("cross-redirect".equals(nextRequestResponse.type)) {
                 // check for a cross-application redirect
                 if (Debug.verboseOn()) Debug.logVerbose("[RequestHandler.doRequest]: Response is a Cross-Application redirect." + showSessionId(request), module);
                 String url = nextRequestResponse.value.startsWith("/") ? nextRequestResponse.value : "/" + nextRequestResponse.value;
-                callRedirect(url + this.makeQueryString(request, nextRequestResponse), response, request, statusCodeString);
+                callRedirect(url + this.makeQueryString(request, nextRequestResponse), response, request, ccfg.getStatusCodeString());
             } else if ("request-redirect".equals(nextRequestResponse.type)) {
                 if (Debug.verboseOn()) Debug.logVerbose("[RequestHandler.doRequest]: Response is a Request redirect." + showSessionId(request), module);
-                callRedirect(makeLinkWithQueryString(request, response, "/" + nextRequestResponse.value, nextRequestResponse), response, request, statusCodeString);
+                callRedirect(makeLinkWithQueryString(request, response, "/" + nextRequestResponse.value, nextRequestResponse), response, request, ccfg.getStatusCodeString());
             } else if ("request-redirect-noparam".equals(nextRequestResponse.type)) {
                 if (Debug.verboseOn()) Debug.logVerbose("[RequestHandler.doRequest]: Response is a Request redirect with no parameters." + showSessionId(request), module);
-                callRedirect(makeLink(request, response, nextRequestResponse.value), response, request, statusCodeString);
+                callRedirect(makeLink(request, response, nextRequestResponse.value), response, request, ccfg.getStatusCodeString());
             } else if ("view".equals(nextRequestResponse.type)) {
                 if (Debug.verboseOn()) Debug.logVerbose("[RequestHandler.doRequest]: Response is a view." + showSessionId(request), module);
 

Added: ofbiz/ofbiz-framework/trunk/framework/webapp/src/test/java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/webapp/src/test/java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java?rev=1838081&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/framework/webapp/src/test/java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java (added)
+++ ofbiz/ofbiz-framework/trunk/framework/webapp/src/test/java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java Wed Aug 15 11:45:45 2018
@@ -0,0 +1,188 @@
+/*******************************************************************************
+ * 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.ofbiz.webapp.control;
+
+import static org.hamcrest.CoreMatchers.both;
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.ofbiz.base.util.collections.MultivaluedMapContext;
+import org.apache.ofbiz.webapp.control.ConfigXMLReader.RequestMap;
+import org.apache.ofbiz.webapp.control.ConfigXMLReader.ViewMap;
+import org.junit.Before;
+import org.junit.Test;
+import org.w3c.dom.Element;
+
+public class RequestHandlerTests {
+    public static class ResolveURITests {
+        private MultivaluedMapContext<String,RequestMap> reqMaps;
+        private Map<String, ViewMap> viewMaps;
+        private HttpServletRequest req;
+        private Element dummyElement;
+        private RequestHandler.ControllerConfig ccfg;
+
+        @Before
+        public void setUp() {
+            ccfg = mock(RequestHandler.ControllerConfig.class);
+            reqMaps = new MultivaluedMapContext<>();
+            viewMaps = new HashMap<>();
+            when(ccfg.getDefaultRequest()).thenReturn(null);
+            when(ccfg.getRequestMapMap()).thenReturn(reqMaps);
+            when(ccfg.getViewMapMap()).thenReturn(viewMaps);
+            req = mock(HttpServletRequest.class);
+            dummyElement = mock(Element.class);
+            when(dummyElement.getAttribute("method")).thenReturn("all");
+            when(req.getMethod()).thenReturn("get");
+        }
+
+        @Test
+        public void resolveURIBasic() throws RequestHandlerException {
+            RequestMap foo = new RequestMap(dummyElement);
+            RequestMap bar = new RequestMap(dummyElement);
+            reqMaps.putSingle("foo", foo);
+            reqMaps.putSingle("bar", bar);
+            when(req.getPathInfo()).thenReturn("/foo");
+            assertThat(RequestHandler.resolveURI(ccfg, req),
+                    both(hasItem(foo)).and(not(hasItem(bar))));
+            assertThat(RequestHandler.resolveURI(ccfg, req).size(), is(1));
+        }
+
+        @Test
+        public void resolveURIBasicPut() throws RequestHandlerException {
+            when(dummyElement.getAttribute("method")).thenReturn("put");
+            when(req.getPathInfo()).thenReturn("/foo");
+            when(req.getMethod()).thenReturn("put");
+
+            RequestMap foo = new RequestMap(dummyElement);
+
+            assertTrue(RequestHandler.resolveURI(ccfg, req).isEmpty());
+            reqMaps.putSingle("foo", foo);
+            assertFalse(RequestHandler.resolveURI(ccfg, req).isEmpty());
+        }
+
+        @Test
+        public void resolveURIUpperCase() throws RequestHandlerException {
+            when(dummyElement.getAttribute("method")).thenReturn("get");
+            RequestMap foo = new RequestMap(dummyElement);
+            when(dummyElement.getAttribute("method")).thenReturn("put");
+            RequestMap bar = new RequestMap(dummyElement);
+            reqMaps.putSingle("foo", foo);
+            reqMaps.putSingle("bar", bar);
+
+            when(req.getPathInfo()).thenReturn("/foo");
+            when(req.getMethod()).thenReturn("GET");
+            assertThat(RequestHandler.resolveURI(ccfg, req), hasItem(foo));
+
+            when(req.getPathInfo()).thenReturn("/bar");
+            when(req.getMethod()).thenReturn("PUT");
+            assertThat(RequestHandler.resolveURI(ccfg, req), hasItem(bar));
+        }
+
+        @Test
+        public void resolveURIDefault() throws Exception {
+            RequestMap foo = new RequestMap(dummyElement);
+            RequestMap bar = new RequestMap(dummyElement);
+            reqMaps.putSingle("foo", foo);
+            reqMaps.putSingle("bar", bar);
+
+            when(req.getPathInfo()).thenReturn("/baz");
+            when(ccfg.getDefaultRequest()).thenReturn("bar");
+            assertThat(RequestHandler.resolveURI(ccfg, req), hasItem(bar));
+        }
+
+        @Test
+        public void resolveURIOverrideView() throws Exception {
+            RequestMap foo = new RequestMap(dummyElement);
+            RequestMap bar = new RequestMap(dummyElement);
+            reqMaps.putSingle("foo", foo);
+            reqMaps.putSingle("bar", bar);
+
+            viewMaps.put("baz", new ViewMap(dummyElement));
+
+            when(req.getPathInfo()).thenReturn("/foo/baz");
+            when(ccfg.getDefaultRequest()).thenReturn("bar");
+            assertThat(RequestHandler.resolveURI(ccfg, req), hasItem(bar));
+        }
+
+        @Test
+        public void resolveURINoDefault() throws Exception {
+            RequestMap foo = new RequestMap(dummyElement);
+            RequestMap bar = new RequestMap(dummyElement);
+            reqMaps.putSingle("foo", foo);
+            reqMaps.putSingle("bar", bar);
+
+            when(req.getPathInfo()).thenReturn("/baz");
+            assertTrue(RequestHandler.resolveURI(ccfg, req).isEmpty());
+        }
+    }
+
+    public static class ResolveMethodTests {
+        private Element dummyElement;
+        private Collection<RequestMap> rmaps;
+
+        @Before
+        public void setUp() {
+            dummyElement = mock(Element.class);
+            rmaps = new ArrayList<>();
+        }
+
+        @Test
+        public void resolveMethodBasic() throws RequestHandlerException {
+            RequestMap fooPut = new RequestMap(dummyElement);
+            fooPut.method = "put";
+            rmaps.add(fooPut);
+
+            RequestMap fooAll = new RequestMap(dummyElement);
+            fooAll.method = "all";
+            rmaps.add(fooAll);
+
+            assertThat(RequestHandler.resolveMethod("put", rmaps).get(), is(fooPut));
+            assertThat(RequestHandler.resolveMethod("get", rmaps).get(), is(fooAll));
+        }
+
+        @Test
+        public void resolveMethodCatchAll() throws RequestHandlerException {
+            assertFalse(RequestHandler.resolveMethod("get", rmaps).isPresent());
+            assertFalse(RequestHandler.resolveMethod("post", rmaps).isPresent());
+            assertFalse(RequestHandler.resolveMethod("put", rmaps).isPresent());
+            assertFalse(RequestHandler.resolveMethod("delete", rmaps).isPresent());
+
+            RequestMap foo = new RequestMap(dummyElement);
+            foo.method = "all";
+            rmaps.add(foo);
+            assertTrue(RequestHandler.resolveMethod("get", rmaps).isPresent());
+            assertTrue(RequestHandler.resolveMethod("post", rmaps).isPresent());
+            assertTrue(RequestHandler.resolveMethod("put", rmaps).isPresent());
+            assertTrue(RequestHandler.resolveMethod("delete", rmaps).isPresent());
+        }
+    }
+}

Propchange: ofbiz/ofbiz-framework/trunk/framework/webapp/src/test/java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/ofbiz-framework/trunk/framework/webapp/src/test/java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/framework/webapp/src/test/java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain



Re: svn commit: r1838081 - in /ofbiz/ofbiz-framework/trunk/framework: base/src/main/java/org/apache/ofbiz/base/util/collections/ base/src/test/java/org/apache/ofbiz/base/collections/ webapp/config/ webapp/dtd/ webapp/src/main/java/org/apache/ofbiz/webapp/c...

Posted by Rishi Solanki <ri...@gmail.com>.
Deepak,

Thanks for reporting the issue, can you please create Jira ticket and add
details (images are not coming at mailing list) with logs if possible?



Rishi Solanki
Sr Manager, Enterprise Software Development
HotWax Systems Pvt. Ltd.
Direct: +91-9893287847
http://www.hotwaxsystems.com
www.hotwax.co

On Fri, Aug 17, 2018 at 6:32 PM, deepak nigam <de...@gmail.com>
wrote:

> I am using OFBiz trunk and on creating an order from backend and getting
> error screens while adding an item to cart and selecting shipping methods.
> Please find the attached images for your reference.
>
> On further investigation, I found that on reverting this commit things are
> working fine. Can we verify this commit once more?
>
> On Wed, Aug 15, 2018 at 5:15 PM <sh...@apache.org> wrote:
>
>> Author: shijh
>> Date: Wed Aug 15 11:45:45 2018
>> New Revision: 1838081
>>
>> URL: http://svn.apache.org/viewvc?rev=1838081&view=rev
>> Log:
>> Implemented: Add method attribute to request-map to controll a uri can be
>> called GET or POST only
>> OFBIZ-10438
>>
>> Thanks: Mathieu Lirzin for the contribution.
>>
>> Added:
>>     ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/
>> org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java
>>  (with props)
>>     ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/
>> org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java
>>  (with props)
>>     ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/
>> org/apache/ofbiz/base/collections/
>>     ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/
>> org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java
>>  (with props)
>>     ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/
>> org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java
>>  (with props)
>>     ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/
>> java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java
>>  (with props)
>>     ofbiz/ofbiz-framework/trunk/framework/webapp/src/test/
>> java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java   (with
>> props)
>> Modified:
>>     ofbiz/ofbiz-framework/trunk/framework/webapp/config/
>> WebappUiLabels.xml
>>     ofbiz/ofbiz-framework/trunk/framework/webapp/dtd/site-conf.xsd
>>     ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/
>> java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java
>>     ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/
>> java/org/apache/ofbiz/webapp/control/ControlServlet.java
>>     ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/
>> java/org/apache/ofbiz/webapp/control/RequestHandler.java
>>
>> Added: ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/
>> org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java
>> URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/
>> framework/base/src/main/java/org/apache/ofbiz/base/util/collections/
>> MultivaluedMapContext.java?rev=1838081&view=auto
>> ============================================================
>> ==================
>> --- ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/
>> org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java (added)
>> +++ ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/
>> org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java Wed
>> Aug 15 11:45:45 2018
>> @@ -0,0 +1,87 @@
>> +/**********************************************************
>> *********************
>> + * 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.ofbiz.base.util.collections;
>> +
>> +import java.util.LinkedList;
>> +import java.util.List;
>> +
>> +/**
>> + * MultivaluedMap Context
>> + *
>> + * A MapContext which handles multiple values for the same key.
>> + */
>> +public class MultivaluedMapContext<K, V> extends MapContext<K, List<V>> {
>> +
>> +    public static final String module = MultivaluedMapContext.class.
>> getName();
>> +
>> +    /**
>> +     * Create a multi-value map initialized with one context
>> +     */
>> +    public MultivaluedMapContext() {
>> +        push();
>> +    }
>> +
>> +    /**
>> +     * Associate {@code key} with the single value {@code value}.
>> +     * If other values are already associated with {@code key} then
>> override them.
>> +     *
>> +     * @param key the key to associate {@code value} with
>> +     * @param value the value to add to the context
>> +     */
>> +    public void putSingle(K key, V value) {
>> +        List<V> box = new LinkedList<>();
>> +        box.add(value);
>> +        put(key, box);
>> +    }
>> +
>> +    /**
>> +     * Associate {@code key} with the single value {@code value}.
>> +     * If other values are already associated with {@code key},
>> +     * then add {@code value} to them.
>> +     *
>> +     * @param key the key to associate {@code value} with
>> +     * @param value the value to add to the context
>> +     */
>> +    public void add(K key, V value) {
>> +        List<V> cur = contexts.getFirst().get(key);
>> +        if (cur == null) {
>> +            cur = new LinkedList<>();
>> +            /* if this method is called after a context switch, copy the
>> previous values
>> +               in current context to not mask them. */
>> +            List<V> old = get(key);
>> +            if (old != null) {
>> +                cur.addAll(old);
>> +            }
>> +        }
>> +        cur.add(value);
>> +        put(key, cur);
>> +    }
>> +
>> +    /**
>> +     * Get the first value contained in the list of values associated
>> with {@code key}.
>> +     *
>> +     * @param key a candidate key
>> +     * @return the first value associated with {@code key} or null if no
>> value
>> +     * is associated with it.
>> +     */
>> +    public V getFirst(Object key) {
>> +        List<V> res = get(key);
>> +        return res == null ? null : res.get(0);
>> +    }
>> +}
>>
>> Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/
>> org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java
>> ------------------------------------------------------------
>> ------------------
>>     svn:eol-style = native
>>
>> Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/
>> org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java
>> ------------------------------------------------------------
>> ------------------
>>     svn:keywords = Date Rev Author URL Id
>>
>> Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/
>> org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java
>> ------------------------------------------------------------
>> ------------------
>>     svn:mime-type = text/plain
>>
>> Added: ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/
>> org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java
>> URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/
>> framework/base/src/main/java/org/apache/ofbiz/base/util/collections/
>> MultivaluedMapContextAdapter.java?rev=1838081&view=auto
>> ============================================================
>> ==================
>> --- ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/
>> org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java
>> (added)
>> +++ ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/
>> org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java
>> Wed Aug 15 11:45:45 2018
>> @@ -0,0 +1,103 @@
>> +/**********************************************************
>> *********************
>> + * 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.ofbiz.base.util.collections;
>> +
>> +import java.util.Collection;
>> +import java.util.Map;
>> +import java.util.Set;
>> +import java.util.stream.Collectors;
>> +
>> +// Adapter which allows viewing a multi-value map as a single-value map.
>> +public class MultivaluedMapContextAdapter<K, V> implements Map<K, V> {
>> +    private MultivaluedMapContext<K, V> adaptee;
>> +
>> +    public MultivaluedMapContextAdapter(MultivaluedMapContext<K, V>
>> adaptee) {
>> +        this.adaptee = adaptee;
>> +    }
>> +
>> +    @Override
>> +    public int size() {
>> +        return adaptee.size();
>> +    }
>> +
>> +    @Override
>> +    public boolean isEmpty() {
>> +        return adaptee.isEmpty();
>> +    }
>> +
>> +    @Override
>> +    public boolean containsKey(Object key) {
>> +        return adaptee.containsKey(key);
>> +    }
>> +
>> +    @Override
>> +    public boolean containsValue(Object value) {
>> +        return adaptee.values().stream()
>> +                .map(l -> l.get(0))
>> +                .anyMatch(value::equals);
>> +    }
>> +
>> +    @Override
>> +    public V get(Object key) {
>> +        return adaptee.getFirst(key);
>> +    }
>> +
>> +    @Override
>> +    public V put(K key, V value) {
>> +        V prev = get(key);
>> +        adaptee.putSingle(key, value);
>> +        return prev;
>> +    }
>> +
>> +    @Override
>> +    public V remove(Object key) {
>> +        V prev = get(key);
>> +        adaptee.remove(key);
>> +        return prev;
>> +    }
>> +
>> +    @Override
>> +    public void putAll(Map<? extends K, ? extends V> m) {
>> +        m.forEach(adaptee::putSingle);
>> +    }
>> +
>> +    @Override
>> +    public void clear() {
>> +        adaptee.clear();
>> +    }
>> +
>> +    @Override
>> +    public Set<K> keySet() {
>> +        return adaptee.keySet();
>> +    }
>> +
>> +    @Override
>> +    public Collection<V> values() {
>> +        return adaptee.values().stream()
>> +                .map(l -> l.get(0))
>> +                .collect(Collectors.toList());
>> +    }
>> +
>> +    @Override
>> +    public Set<Entry<K, V>> entrySet() {
>> +        return adaptee.keySet().stream()
>> +                .collect(Collectors.toMap(k -> k, k -> get(k)))
>> +                .entrySet();
>> +    }
>> +}
>>
>> Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/
>> org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java
>> ------------------------------------------------------------
>> ------------------
>>     svn:eol-style = native
>>
>> Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/
>> org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java
>> ------------------------------------------------------------
>> ------------------
>>     svn:keywords = Date Rev Author URL Id
>>
>> Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/
>> org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java
>> ------------------------------------------------------------
>> ------------------
>>     svn:mime-type = text/plain
>>
>> Added: ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/
>> org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java
>> URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/
>> framework/base/src/test/java/org/apache/ofbiz/base/collections/
>> MultivaluedMapContextAdapterTests.java?rev=1838081&view=auto
>> ============================================================
>> ==================
>> --- ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/
>> org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java
>> (added)
>> +++ ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/
>> org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java
>> Wed Aug 15 11:45:45 2018
>> @@ -0,0 +1,70 @@
>> +/**********************************************************
>> *********************
>> + * 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.ofbiz.base.collections;
>> +
>> +import static org.hamcrest.CoreMatchers.hasItems;
>> +import static org.hamcrest.CoreMatchers.is;
>> +import static org.junit.Assert.assertEquals;
>> +import static org.junit.Assert.assertFalse;
>> +import static org.junit.Assert.assertThat;
>> +import static org.junit.Assert.assertTrue;
>> +
>> +import java.util.HashMap;
>> +
>> +import org.apache.ofbiz.base.util.collections.MultivaluedMapContext;
>> +import org.apache.ofbiz.base.util.collections.
>> MultivaluedMapContextAdapter;
>> +import org.junit.Before;
>> +import org.junit.Test;
>> +
>> +public class MultivaluedMapContextAdapterTests {
>> +    private MultivaluedMapContext<String, Integer> adaptee;
>> +    private MultivaluedMapContextAdapter<String, Integer> adapter;
>> +
>> +    @Before
>> +    public void setUp() throws Exception {
>> +        adaptee = new MultivaluedMapContext<>();
>> +        adaptee.add("foo", 0);
>> +        adaptee.add("foo", 1);
>> +        adaptee.add("foo", 2);
>> +        adaptee.add("bar", 3);
>> +        adapter = new MultivaluedMapContextAdapter<>(adaptee);
>> +    }
>> +
>> +    @Test
>> +    public void containsValueBasic() {
>> +        assertTrue(adapter.containsValue(0));
>> +        assertFalse(adapter.containsValue(1));
>> +        assertFalse(adapter.containsValue(2));
>> +        assertTrue(adapter.containsValue(3));
>> +    }
>> +
>> +    @Test
>> +    public void valuesBasic() {
>> +        assertThat(adapter.values(), hasItems(0, 3));
>> +        assertThat(adapter.values().size(), is(2));
>> +    }
>> +
>> +    @Test
>> +    public void entrySetBasic() {
>> +        HashMap<String, Integer> expected = new HashMap<>();
>> +        expected.put("foo", 0);
>> +        expected.put("bar", 3);
>> +        assertEquals(expected.entrySet(), adapter.entrySet());
>> +    }
>> +}
>>
>> Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/
>> org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java
>> ------------------------------------------------------------
>> ------------------
>>     svn:eol-style = native
>>
>> Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/
>> org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java
>> ------------------------------------------------------------
>> ------------------
>>     svn:keywords = Date Rev Author URL Id
>>
>> Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/
>> org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java
>> ------------------------------------------------------------
>> ------------------
>>     svn:mime-type = text/plain
>>
>> Added: ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/
>> org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java
>> URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/
>> framework/base/src/test/java/org/apache/ofbiz/base/collections/
>> MultivaluedMapContextTests.java?rev=1838081&view=auto
>> ============================================================
>> ==================
>> --- ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/
>> org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java (added)
>> +++ ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/
>> org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java Wed
>> Aug 15 11:45:45 2018
>> @@ -0,0 +1,82 @@
>> +/**********************************************************
>> *********************
>> + * 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.ofbiz.base.collections;
>> +
>> +import static org.hamcrest.CoreMatchers.both;
>> +import static org.hamcrest.CoreMatchers.hasItem;
>> +import static org.hamcrest.CoreMatchers.hasItems;
>> +import static org.hamcrest.CoreMatchers.is;
>> +import static org.hamcrest.CoreMatchers.not;
>> +import static org.hamcrest.CoreMatchers.nullValue;
>> +import static org.junit.Assert.assertThat;
>> +
>> +import org.apache.ofbiz.base.util.collections.MultivaluedMapContext;
>> +import org.junit.Before;
>> +import org.junit.Test;
>> +
>> +public class MultivaluedMapContextTests {
>> +    private MultivaluedMapContext<String, Integer> m;
>> +
>> +    @Before
>> +    public void setUp() throws Exception {
>> +        m = new MultivaluedMapContext<>();
>> +    }
>> +
>> +    @Test
>> +    public void getEmpty() {
>> +        assertThat(m.get("foo"), is(nullValue()));
>> +    }
>> +
>> +    @Test
>> +    public void putSingleBasic() {
>> +        m.putSingle("foo", 0);
>> +        assertThat(m.get("foo"), hasItem(0));
>> +        m.putSingle("foo", 1);
>> +        assertThat(m.get("foo"), both(hasItem(1)).and(not(hasItem(0))));
>> +    }
>> +
>> +    @Test
>> +    public void addBasic() {
>> +        m.add("foo", 0);
>> +        assertThat(m.get("foo"), hasItem(0));
>> +        m.add("foo", 1);
>> +        assertThat(m.get("foo"), hasItems(0, 1));
>> +    }
>> +
>> +    @Test
>> +    public void addWithPreviousContext() {
>> +        m.add("foo", 0);
>> +        m.push();
>> +        assertThat(m.get("foo"), hasItem(0));
>> +        m.add("foo", 1);
>> +        assertThat(m.get("foo"), hasItems(0, 1));
>> +    }
>> +
>> +    @Test
>> +    public void getFirstBasic() {
>> +        m.add("foo", 0);
>> +        m.add("foo", 1);
>> +        assertThat(m.getFirst("foo"), is(0));
>> +    }
>> +
>> +    @Test
>> +    public void getFirstEmpty() {
>> +        assertThat(m.getFirst("foo"), is(nullValue()));
>> +    }
>> +}
>>
>> Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/
>> org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java
>> ------------------------------------------------------------
>> ------------------
>>     svn:eol-style = native
>>
>> Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/
>> org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java
>> ------------------------------------------------------------
>> ------------------
>>     svn:keywords = Date Rev Author URL Id
>>
>> Propchange: ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/
>> org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java
>> ------------------------------------------------------------
>> ------------------
>>     svn:mime-type = text/plain
>>
>> Modified: ofbiz/ofbiz-framework/trunk/framework/webapp/config/
>> WebappUiLabels.xml
>> URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/
>> framework/webapp/config/WebappUiLabels.xml?rev=
>> 1838081&r1=1838080&r2=1838081&view=diff
>> ============================================================
>> ==================
>> --- ofbiz/ofbiz-framework/trunk/framework/webapp/config/WebappUiLabels.xml
>> (original)
>> +++ ofbiz/ofbiz-framework/trunk/framework/webapp/config/WebappUiLabels.xml
>> Wed Aug 15 11:45:45 2018
>> @@ -339,4 +339,8 @@
>>          <value xml:lang="zh">æ²¡æœ‰å®Œæˆ äº‹ä»¶</value>
>>          <value xml:lang="zh-TW">æ²’æœ‰å®Œæˆ äº‹ä»¶</value>
>>      </property>
>> +    <property key="RequestMethodNotMatchConfig">
>> +        <value xml:lang="en">[{0}] cannot be called by [{1}]
>> method.</value>
>> +        <value xml:lang="zh">[{0}]ä¸ èƒ½ç”¨[{1}]方法请求。</value>
>> +    </property>
>>  </resource>
>>
>> Modified: ofbiz/ofbiz-framework/trunk/framework/webapp/dtd/site-conf.xsd
>> URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/
>> framework/webapp/dtd/site-conf.xsd?rev=1838081&r1=
>> 1838080&r2=1838081&view=diff
>> ============================================================
>> ==================
>> --- ofbiz/ofbiz-framework/trunk/framework/webapp/dtd/site-conf.xsd
>> (original)
>> +++ ofbiz/ofbiz-framework/trunk/framework/webapp/dtd/site-conf.xsd Wed
>> Aug 15 11:45:45 2018
>> @@ -216,6 +216,24 @@ under the License.
>>                  </xs:documentation>
>>              </xs:annotation>
>>          </xs:attribute>
>> +        <xs:attribute name="method" use="optional" default="all">
>> +            <xs:annotation>
>> +                <xs:documentation>
>> +                    The HTTP of this request. This will be the HTTP
>> method used to access the request.
>> +                </xs:documentation>
>> +            </xs:annotation>
>> +            <xs:simpleType>
>> +                <xs:restriction base="xs:token">
>> +                    <xs:enumeration value="get"/>
>> +                    <xs:enumeration value="post"/>
>> +                    <xs:enumeration value="put"/>
>> +                    <xs:enumeration value="delete"/>
>> +                    <xs:enumeration value="patch"/>
>> +                    <xs:enumeration value="options"/>
>> +                    <xs:enumeration value="all"/>
>> +                </xs:restriction>
>> +            </xs:simpleType>
>> +        </xs:attribute>
>>          <xs:attribute type="xs:boolean" name="edit" default="true">
>>              <xs:annotation>
>>                  <xs:documentation>
>>
>> Modified: ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/
>> java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java
>> URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/
>> framework/webapp/src/main/java/org/apache/ofbiz/webapp/
>> control/ConfigXMLReader.java?rev=1838081&r1=1838080&r2=1838081&view=diff
>> ============================================================
>> ==================
>> --- ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/
>> java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java (original)
>> +++ ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/
>> java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java Wed Aug 15
>> 11:45:45 2018
>> @@ -49,6 +49,8 @@ import org.apache.ofbiz.base.util.UtilVa
>>  import org.apache.ofbiz.base.util.UtilXml;
>>  import org.apache.ofbiz.base.util.cache.UtilCache;
>>  import org.apache.ofbiz.base.util.collections.MapContext;
>> +import org.apache.ofbiz.base.util.collections.MultivaluedMapContext;
>> +import org.apache.ofbiz.base.util.collections.
>> MultivaluedMapContextAdapter;
>>  import org.w3c.dom.Document;
>>  import org.w3c.dom.Element;
>>
>> @@ -192,7 +194,7 @@ public class ConfigXMLReader {
>>          private Map<String, Event> beforeLogoutEventList = new
>> LinkedHashMap<String, Event>();
>>          private Map<String, String> eventHandlerMap = new
>> HashMap<String, String>();
>>          private Map<String, String> viewHandlerMap = new HashMap<String,
>> String>();
>> -        private Map<String, RequestMap> requestMapMap = new
>> HashMap<String, RequestMap>();
>> +        private MultivaluedMapContext<String, RequestMap> requestMapMap
>> = new MultivaluedMapContext<>();
>>          private Map<String, ViewMap> viewMapMap = new HashMap<String,
>> ViewMap>();
>>
>>          public ControllerConfig(URL url) throws
>> WebAppConfigurationException {
>> @@ -276,11 +278,16 @@ public class ConfigXMLReader {
>>              return getIncludes(ccfg -> ccfg.protectView);
>>          }
>>
>> +        // XXX: Keep it for backward compatibility until moving
>> everything to 鈥榞etRequestMapMultiMap鈥�.
>>          public Map<String, RequestMap> getRequestMapMap() throws
>> WebAppConfigurationException {
>> -            MapContext<String, RequestMap> result = new MapContext<>();
>> +            return new MultivaluedMapContextAdapter<>
>> (getRequestMapMultiMap());
>> +        }
>> +
>> +        public MultivaluedMapContext<String, RequestMap>
>> getRequestMapMultiMap() throws WebAppConfigurationException {
>> +            MultivaluedMapContext<String, RequestMap> result = new
>> MultivaluedMapContext<>();
>>              for (URL includeLocation : includes) {
>>                  ControllerConfig controllerConfig = getControllerConfig(
>> includeLocation);
>> -                result.push(controllerConfig.getRequestMapMap());
>> +                result.push(controllerConfig.getRequestMapMultiMap());
>>              }
>>              result.push(requestMapMap);
>>              return result;
>> @@ -403,7 +410,7 @@ public class ConfigXMLReader {
>>          private void loadRequestMap(Element root) {
>>              for (Element requestMapElement :
>> UtilXml.childElementList(root, "request-map")) {
>>                  RequestMap requestMap = new
>> RequestMap(requestMapElement);
>> -                this.requestMapMap.put(requestMap.uri, requestMap);
>> +                this.requestMapMap.add(requestMap.uri, requestMap);
>>              }
>>          }
>>
>> @@ -450,6 +457,7 @@ public class ConfigXMLReader {
>>
>>      public static class RequestMap {
>>          public String uri;
>> +        public String method;
>>          public boolean edit = true;
>>          public boolean trackVisit = true;
>>          public boolean trackServerHit = true;
>> @@ -466,6 +474,7 @@ public class ConfigXMLReader {
>>          public RequestMap(Element requestMapElement) {
>>              // Get the URI info
>>              this.uri = requestMapElement.getAttribute("uri");
>> +            this.method = requestMapElement.getAttribute("method");
>>              this.edit = !"false".equals(requestMapElement.
>> getAttribute("edit"));
>>              this.trackServerHit = !"false".equals(requestMapElement.
>> getAttribute("track-serverhit"));
>>              this.trackVisit = !"false".equals(requestMapElement.
>> getAttribute("track-visit"));
>>
>> Modified: ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/
>> java/org/apache/ofbiz/webapp/control/ControlServlet.java
>> URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/
>> framework/webapp/src/main/java/org/apache/ofbiz/webapp/
>> control/ControlServlet.java?rev=1838081&r1=1838080&r2=1838081&view=diff
>> ============================================================
>> ==================
>> --- ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/
>> java/org/apache/ofbiz/webapp/control/ControlServlet.java (original)
>> +++ ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/
>> java/org/apache/ofbiz/webapp/control/ControlServlet.java Wed Aug 15
>> 11:45:45 2018
>> @@ -211,6 +211,12 @@ public class ControlServlet extends Http
>>          try {
>>              // the ServerHitBin call for the event is done inside the
>> doRequest method
>>              requestHandler.doRequest(request, response, null,
>> userLogin, delegator);
>> +        } catch (MethodNotAllowedException e) {
>> +            response.setContentType("text/plain");
>> +            response.setCharacterEncoding(request.getCharacterEncoding()
>> );
>> +            response.setStatus(HttpServletResponse.SC_METHOD_
>> NOT_ALLOWED);
>> +            response.getWriter().print(e.getMessage());
>> +            Debug.logError(e.getMessage(), module);
>>          } catch (RequestHandlerException e) {
>>              Throwable throwable = e.getNested() != null ? e.getNested()
>> : e;
>>              if (throwable instanceof IOException) {
>>
>> Added: ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/
>> java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java
>> URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/
>> framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/
>> MethodNotAllowedException.java?rev=1838081&view=auto
>> ============================================================
>> ==================
>> --- ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/
>> java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java
>> (added)
>> +++ ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/
>> java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java Wed
>> Aug 15 11:45:45 2018
>> @@ -0,0 +1,26 @@
>> +/**********************************************************
>> *********************
>> + * 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.ofbiz.webapp.control;
>> +
>> +@SuppressWarnings("serial")
>> +public class MethodNotAllowedException extends RequestHandlerException {
>> +    MethodNotAllowedException(String str) {
>> +        super(str);
>> +    }
>> +}
>>
>> Propchange: ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/
>> java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java
>> ------------------------------------------------------------
>> ------------------
>>     svn:eol-style = native
>>
>> Propchange: ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/
>> java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java
>> ------------------------------------------------------------
>> ------------------
>>     svn:keywords = Date Rev Author URL Id
>>
>> Propchange: ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/
>> java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java
>> ------------------------------------------------------------
>> ------------------
>>     svn:mime-type = text/plain
>>
>> Modified: ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/
>> java/org/apache/ofbiz/webapp/control/RequestHandler.java
>> URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/
>> framework/webapp/src/main/java/org/apache/ofbiz/webapp/
>> control/RequestHandler.java?rev=1838081&r1=1838080&r2=1838081&view=diff
>> ============================================================
>> ==================
>> --- ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/
>> java/org/apache/ofbiz/webapp/control/RequestHandler.java (original)
>> +++ ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/
>> java/org/apache/ofbiz/webapp/control/RequestHandler.java Wed Aug 15
>> 11:45:45 2018
>> @@ -24,11 +24,15 @@ import java.io.IOException;
>>  import java.io.Serializable;
>>  import java.net.URL;
>>  import java.security.cert.X509Certificate;
>> +
>> +import java.util.Collection;
>> +import java.util.Collections;
>>  import java.util.Enumeration;
>>  import java.util.HashMap;
>>  import java.util.List;
>>  import java.util.Locale;
>>  import java.util.Map;
>> +import java.util.Optional;
>>
>>  import javax.servlet.ServletContext;
>>  import javax.servlet.http.HttpServletRequest;
>> @@ -45,12 +49,14 @@ import org.apache.ofbiz.base.util.UtilMi
>>  import org.apache.ofbiz.base.util.UtilObject;
>>  import org.apache.ofbiz.base.util.UtilProperties;
>>  import org.apache.ofbiz.base.util.UtilValidate;
>> +import org.apache.ofbiz.base.util.collections.MultivaluedMapContext;
>>  import org.apache.ofbiz.entity.Delegator;
>>  import org.apache.ofbiz.entity.GenericEntityException;
>>  import org.apache.ofbiz.entity.GenericValue;
>>  import org.apache.ofbiz.entity.util.EntityQuery;
>>  import org.apache.ofbiz.entity.util.EntityUtilProperties;
>>  import org.apache.ofbiz.webapp.OfbizUrlBuilder;
>> +import org.apache.ofbiz.webapp.control.ConfigXMLReader.RequestMap;
>>  import org.apache.ofbiz.webapp.event.EventFactory;
>>  import org.apache.ofbiz.webapp.event.EventHandler;
>>  import org.apache.ofbiz.webapp.event.EventHandlerException;
>> @@ -68,12 +74,74 @@ import org.apache.ofbiz.widget.model.The
>>  public class RequestHandler {
>>
>>      public static final String module = RequestHandler.class.getName();
>> -    private final String defaultStatusCodeString = UtilProperties.
>> getPropertyValue("requestHandler", "status-code", "302");
>> +    private final static String defaultStatusCodeString =
>> +            UtilProperties.getPropertyValue("requestHandler",
>> "status-code", "302");
>>      private final ViewFactory viewFactory;
>>      private final EventFactory eventFactory;
>>      private final URL controllerConfigURL;
>>      private final boolean trackServerHit;
>>      private final boolean trackVisit;
>> +    private ControllerConfig ccfg;
>> +
>> +    static class ControllerConfig {
>> +        private final MultivaluedMapContext<String, RequestMap>
>> requestMapMap;
>> +        private final Map<String, ConfigXMLReader.ViewMap> viewMapMap;
>> +        private String statusCodeString;
>> +        private final String defaultRequest;
>> +        private final Map<String, ConfigXMLReader.Event>
>> firstVisitEventList;
>> +        private final Map<String, ConfigXMLReader.Event>
>> preprocessorEventList;
>> +        private final Map<String, ConfigXMLReader.Event>
>> postprocessorEventList;
>> +        private final String protectView;
>> +
>> +        ControllerConfig(ConfigXMLReader.ControllerConfig ccfg) throws
>> WebAppConfigurationException {
>> +            preprocessorEventList = ccfg.getPreprocessorEventList();
>> +            postprocessorEventList = ccfg.getPostprocessorEventList();
>> +            requestMapMap = ccfg.getRequestMapMultiMap();
>> +            viewMapMap = ccfg.getViewMapMap();
>> +            defaultRequest = ccfg.getDefaultRequest();
>> +            firstVisitEventList = ccfg.getFirstVisitEventList();
>> +            protectView = ccfg.getProtectView();
>> +
>> +            String status = ccfg.getStatusCode();
>> +            statusCodeString = UtilValidate.isEmpty(status) ?
>> defaultStatusCodeString : status;
>> +        }
>> +
>> +        public MultivaluedMapContext<String, RequestMap>
>> getRequestMapMap() {
>> +            return requestMapMap;
>> +        }
>> +
>> +        public Map<String, ConfigXMLReader.ViewMap> getViewMapMap() {
>> +            return viewMapMap;
>> +        }
>> +
>> +        public String getStatusCodeString() {
>> +            return statusCodeString;
>> +        }
>> +
>> +        public String getDefaultRequest() {
>> +            return defaultRequest;
>> +        }
>> +
>> +        public void setStatusCodeString(String statusCodeString) {
>> +            this.statusCodeString = statusCodeString;
>> +        }
>> +
>> +        public Map<String, ConfigXMLReader.Event>
>> getFirstVisitEventList() {
>> +            return firstVisitEventList;
>> +        }
>> +
>> +        public Map<String, ConfigXMLReader.Event>
>> getPreprocessorEventList() {
>> +            return preprocessorEventList;
>> +        }
>> +
>> +        public Map<String, ConfigXMLReader.Event>
>> getPostprocessorEventList() {
>> +            return postprocessorEventList;
>> +        }
>> +
>> +        public String getProtectView() {
>> +            return protectView;
>> +        }
>> +    }
>>
>>      public static RequestHandler getRequestHandler(ServletContext
>> servletContext) {
>>          RequestHandler rh = (RequestHandler)
>> servletContext.getAttribute("_REQUEST_HANDLER_");
>> @@ -110,6 +178,56 @@ public class RequestHandler {
>>          return null;
>>      }
>>
>> +    /**
>> +     * Find a collection of request maps in {@code ccfg} matching {@code
>> req}.
>> +     * Otherwise fall back to matching the {@code defaultReq} field in
>> {@code ccfg}.
>> +     *
>> +     * @param ccfg The controller containing the current configuration
>> +     * @param req The HTTP request to match
>> +     * @return a collection of request maps which might be empty
>> +     */
>> +    static Collection<RequestMap> resolveURI(ControllerConfig ccfg,
>> HttpServletRequest req) {
>> +        Map<String, List<RequestMap>> requestMapMap =
>> ccfg.getRequestMapMap();
>> +        Map<String, ConfigXMLReader.ViewMap> viewMapMap =
>> ccfg.getViewMapMap();
>> +        String defaultRequest = ccfg.getDefaultRequest();
>> +        String path = req.getPathInfo();
>> +        String requestUri = getRequestUri(path);
>> +        String viewUri = getOverrideViewUri(path);
>> +        Collection<RequestMap> rmaps;
>> +        if (requestMapMap.containsKey(requestUri) &&
>> !viewMapMap.containsKey(viewUri)) {
>> +            rmaps = requestMapMap.get(requestUri);
>> +        } else if (defaultRequest != null) {
>> +            rmaps = requestMapMap.get(defaultRequest);
>> +        } else {
>> +            rmaps = null;
>> +        }
>> +        return rmaps != null ? rmaps : Collections.emptyList();
>> +    }
>> +
>> +    /**
>> +     * Find the request map matching {@code method}.
>> +     * Otherwise fall back to the one matching the "all" and "" special
>> methods
>> +     * in that respective order.
>> +     *
>> +     * @param method the HTTP method to match
>> +     * @param rmaps the collection of request map candidates
>> +     * @return a request map {@code Optional}
>> +     */
>> +    static Optional<RequestMap> resolveMethod(String method,
>> Collection<RequestMap> rmaps) {
>> +        for (RequestMap map : rmaps) {
>> +            if (map.method.equalsIgnoreCase(method)) {
>> +                return Optional.of(map);
>> +            }
>> +        }
>> +        if (method.isEmpty()) {
>> +            return Optional.empty();
>> +        } else if (method.equals("all")) {
>> +            return resolveMethod("", rmaps);
>> +        } else {
>> +            return resolveMethod("all", rmaps);
>> +        }
>> +    }
>> +
>>      public void doRequest(HttpServletRequest request,
>> HttpServletResponse response, String requestUri) throws
>> RequestHandlerException, RequestHandlerExceptionAllowExternalRequests {
>>          HttpSession session = request.getSession();
>>          Delegator delegator = (Delegator) request.getAttribute("
>> delegator");
>> @@ -125,20 +243,13 @@ public class RequestHandler {
>>          long startTime = System.currentTimeMillis();
>>          HttpSession session = request.getSession();
>>
>> -        // get the controllerConfig once for this method so we don't
>> have to get it over and over inside the method
>> -        ConfigXMLReader.ControllerConfig controllerConfig =
>> this.getControllerConfig();
>> -        Map<String, ConfigXMLReader.RequestMap> requestMapMap = null;
>> -        String statusCodeString = null;
>> +        // Parse controller config.
>>          try {
>> -            requestMapMap = controllerConfig.getRequestMapMap();
>> -            statusCodeString = controllerConfig.getStatusCode();
>> +            ccfg = new ControllerConfig(getControllerConfig());
>>          } catch (WebAppConfigurationException e) {
>>              Debug.logError(e, "Exception thrown while parsing
>> controller.xml file: ", module);
>>              throw new RequestHandlerException(e);
>>          }
>> -        if (UtilValidate.isEmpty(statusCodeString)) {
>> -            statusCodeString = defaultStatusCodeString;
>> -        }
>>
>>          // workaround if we are in the root webapp
>>          String cname = UtilHttp.getApplicationName(request);
>> @@ -153,50 +264,29 @@ public class RequestHandler {
>>              }
>>          }
>>
>> -        String overrideViewUri = RequestHandler.
>> getOverrideViewUri(request.getPathInfo());
>> -
>> -        String requestMissingErrorMessage = "Unknown request [" +
>> defaultRequestUri + "]; this request does not exist or cannot be called
>> directly.";
>> -        ConfigXMLReader.RequestMap requestMap = null;
>> -        if (defaultRequestUri != null) {
>> -            requestMap = requestMapMap.get(defaultRequestUri);
>> -        }
>> -        // check for default request
>> -        if (requestMap == null) {
>> -            String defaultRequest;
>> -            try {
>> -                defaultRequest = controllerConfig.getDefaultRequest();
>> -            } catch (WebAppConfigurationException e) {
>> -                Debug.logError(e, "Exception thrown while parsing
>> controller.xml file: ", module);
>> -                throw new RequestHandlerException(e);
>> -            }
>> -            if (defaultRequest != null) { // required! to avoid a null
>> pointer exception and generate a requesthandler exception if default
>> request not found.
>> -                requestMap = requestMapMap.get(defaultRequest);
>> -            }
>> -        }
>> -
>> -        // check for override view
>> -        if (overrideViewUri != null) {
>> -            ConfigXMLReader.ViewMap viewMap;
>> -            try {
>> -                viewMap = getControllerConfig().getViewMapMap().get(
>> overrideViewUri);
>> -                if (viewMap == null) {
>> -                    String defaultRequest = controllerConfig.
>> getDefaultRequest();
>> -                    if (defaultRequest != null) { // required! to avoid
>> a null pointer exception and generate a requesthandler exception if default
>> request not found.
>> -                        requestMap = requestMapMap.get(defaultRequest);
>> -                    }
>> -                }
>> -            } catch (WebAppConfigurationException e) {
>> -                Debug.logError(e, "Exception thrown while parsing
>> controller.xml file: ", module);
>> -                throw new RequestHandlerException(e);
>> +        String requestMissingErrorMessage = "Unknown request ["
>> +                + defaultRequestUri
>> +                + "]; this request does not exist or cannot be called
>> directly.";
>> +
>> +        String path = request.getPathInfo();
>> +        String requestUri = getRequestUri(path);
>> +        String overrideViewUri = getOverrideViewUri(path);
>> +
>> +        Collection<RequestMap> rmaps = resolveURI(ccfg, request);
>> +        if (rmaps.isEmpty()) {
>> +            if (throwRequestHandlerExceptionOnMissingLocalRequest) {
>> +              throw new RequestHandlerException(
>> requestMissingErrorMessage);
>> +            } else {
>> +              throw new RequestHandlerExceptionAllowExternalRequests();
>>              }
>>          }
>>
>> -        // if no matching request is found in the controller, depending
>> on throwRequestHandlerExceptionOnMissingLocalRequest
>> -        //  we throw a RequestHandlerException or
>> RequestHandlerExceptionAllowExternalRequests
>> -        if (requestMap == null) {
>> -            if (throwRequestHandlerExceptionOnMissingLocalRequest)
>> throw new RequestHandlerException(requestMissingErrorMessage);
>> -            else throw new RequestHandlerExceptionAllowEx
>> ternalRequests();
>> -         }
>> +        String method = request.getMethod();
>> +        RequestMap requestMap = resolveMethod(method,
>> rmaps).orElseThrow(() -> {
>> +            String msg = UtilProperties.getMessage("WebappUiLabels",
>> "RequestMethodNotMatchConfig",
>> +                    UtilMisc.toList(requestUri, method),
>> UtilHttp.getLocale(request));
>> +            return new MethodNotAllowedException(msg);
>> +        });
>>
>>          String eventReturn = null;
>>          if (requestMap.metrics != null && requestMap.metrics.getThreshold()
>> != 0.0 && requestMap.metrics.getTotalEvents() > 3 && requestMap.metrics.getThreshold()
>> < requestMap.metrics.getServiceRate()) {
>> @@ -210,7 +300,7 @@ public class RequestHandler {
>>          // Check for chained request.
>>          if (chain != null) {
>>              String chainRequestUri = RequestHandler.getRequestUri(
>> chain);
>> -            requestMap = requestMapMap.get(chainRequestUri);
>> +            requestMap = ccfg.getRequestMapMap().
>> getFirst(chainRequestUri);
>>              if (requestMap == null) {
>>                  throw new RequestHandlerException("Unknown chained
>> request [" + chainRequestUri + "]; this request does not exist");
>>              }
>> @@ -234,18 +324,11 @@ public class RequestHandler {
>>              // Check to make sure we are allowed to access this request
>> directly. (Also checks if this request is defined.)
>>              // If the request cannot be called, or is not defined, check
>> and see if there is a default-request we can process
>>              if (!requestMap.securityDirectRequest) {
>> -                String defaultRequest;
>> -                try {
>> -                    defaultRequest = controllerConfig.
>> getDefaultRequest();
>> -                } catch (WebAppConfigurationException e) {
>> -                    Debug.logError(e, "Exception thrown while parsing
>> controller.xml file: ", module);
>> -                    throw new RequestHandlerException(e);
>> -                }
>> -                if (defaultRequest == null || !requestMapMap.get(
>> defaultRequest).securityDirectRequest) {
>> +                if (ccfg.getDefaultRequest() == null ||
>> !ccfg.getRequestMapMap().getFirst(ccfg.getDefaultRequest()).securityDirectRequest)
>> {
>>                      // use the same message as if it was missing for
>> security reasons, ie so can't tell if it is missing or direct request is
>> not allowed
>>                      throw new RequestHandlerException(
>> requestMissingErrorMessage);
>>                  } else {
>> -                    requestMap = requestMapMap.get(defaultRequest);
>> +                    requestMap = ccfg.getRequestMapMap().getFirst(ccfg.
>> getDefaultRequest());
>>                  }
>>              }
>>              // Check if we SHOULD be secure and are not.
>> @@ -288,7 +371,7 @@ public class RequestHandler {
>>                      String newUrl = RequestHandler.makeUrl(request,
>> response, urlBuf.toString());
>>                      if (newUrl.toUpperCase().startsWith("HTTPS")) {
>>                          // if we are supposed to be secure, redirect
>> secure.
>> -                        callRedirect(newUrl, response, request,
>> statusCodeString);
>> +                        callRedirect(newUrl, response, request,
>> ccfg.getStatusCodeString());
>>                          return;
>>                      }
>>                  }
>> @@ -333,63 +416,52 @@ public class RequestHandler {
>>                  if (Debug.infoOn())
>>                      Debug.logInfo("This is the first request in this
>> visit." + showSessionId(request), module);
>>                  session.setAttribute("_FIRST_VISIT_EVENTS_",
>> "complete");
>> -                try {
>> -                    for (ConfigXMLReader.Event event: controllerConfig.
>> getFirstVisitEventList().values()) {
>> -                        try {
>> -                            String returnString = this.runEvent(request,
>> response, event, null, "firstvisit");
>> -                            if (returnString == null ||
>> "none".equalsIgnoreCase(returnString)) {
>> -                                interruptRequest = true;
>> -                            } else if (!"success".equalsIgnoreCase(returnString))
>> {
>> -                                throw new EventHandlerException("First-Visit
>> event did not return 'success'.");
>> -                            }
>> -                        } catch (EventHandlerException e) {
>> -                            Debug.logError(e, module);
>> +                for (ConfigXMLReader.Event event:
>> ccfg.getFirstVisitEventList().values()) {
>> +                    try {
>> +                        String returnString = this.runEvent(request,
>> response, event, null, "firstvisit");
>> +                        if (returnString == null ||
>> "none".equalsIgnoreCase(returnString)) {
>> +                            interruptRequest = true;
>> +                        } else if (!"success".equalsIgnoreCase(returnString))
>> {
>> +                            throw new EventHandlerException("First-Visit
>> event did not return 'success'.");
>>                          }
>> +                    } catch (EventHandlerException e) {
>> +                        Debug.logError(e, module);
>>                      }
>> -                } catch (WebAppConfigurationException e) {
>> -                    Debug.logError(e, "Exception thrown while parsing
>> controller.xml file: ", module);
>> -                    throw new RequestHandlerException(e);
>>                  }
>>              }
>>
>>              // Invoke the pre-processor (but NOT in a chain)
>> -            try {
>> -                for (ConfigXMLReader.Event event: controllerConfig.
>> getPreprocessorEventList().values()) {
>> -                    try {
>> -                        String returnString = this.runEvent(request,
>> response, event, null, "preprocessor");
>> -                        if (returnString == null ||
>> "none".equalsIgnoreCase(returnString)) {
>> -                            interruptRequest = true;
>> -                        } else if (!"success".equalsIgnoreCase(returnString))
>> {
>> -                            if (!returnString.contains(":_protect_:")) {
>> -                                throw new EventHandlerException("Pre-Processor
>> event [" + event.invoke + "] did not return 'success'.");
>> -                            } else { // protect the view normally
>> rendered and redirect to error response view
>> -                                returnString = returnString.replace(":_protect_:",
>> "");
>> -                                if (returnString.length() > 0) {
>> -                                    request.setAttribute("_ERROR_MESSAGE_",
>> returnString);
>> -                                }
>> -                                eventReturn = null;
>> -                                // check to see if there is a "protect"
>> response, if so it's ok else show the default_error_response_view
>> -                                if (!requestMap.requestResponseMap.containsKey("protect"))
>> {
>> -                                    String protectView =
>> controllerConfig.getProtectView();
>> -                                    if (protectView != null) {
>> -                                        overrideViewUri = protectView;
>> -                                    } else {
>> -                                        overrideViewUri =
>> EntityUtilProperties.getPropertyValue("security",
>> "default.error.response.view", delegator);
>> -                                        overrideViewUri =
>> overrideViewUri.replace("view:", "");
>> -                                        if ("none:".equals(overrideViewUri))
>> {
>> -                                            interruptRequest = true;
>> -                                        }
>> +            for (ConfigXMLReader.Event event:
>> ccfg.getPreprocessorEventList().values()) {
>> +                try {
>> +                    String returnString = this.runEvent(request,
>> response, event, null, "preprocessor");
>> +                    if (returnString == null || "none".equalsIgnoreCase(returnString))
>> {
>> +                        interruptRequest = true;
>> +                    } else if (!"success".equalsIgnoreCase(returnString))
>> {
>> +                        if (!returnString.contains(":_protect_:")) {
>> +                            throw new EventHandlerException("Pre-Processor
>> event [" + event.invoke + "] did not return 'success'.");
>> +                        } else { // protect the view normally rendered
>> and redirect to error response view
>> +                            returnString = returnString.replace(":_protect_:",
>> "");
>> +                            if (returnString.length() > 0) {
>> +                                request.setAttribute("_ERROR_MESSAGE_",
>> returnString);
>> +                            }
>> +                            eventReturn = null;
>> +                            // check to see if there is a "protect"
>> response, if so it's ok else show the default_error_response_view
>> +                            if (!requestMap.requestResponseMap.containsKey("protect"))
>> {
>> +                                if (ccfg.getProtectView() != null) {
>> +                                    overrideViewUri =
>> ccfg.getProtectView();
>> +                                } else {
>> +                                    overrideViewUri =
>> EntityUtilProperties.getPropertyValue("security",
>> "default.error.response.view", delegator);
>> +                                    overrideViewUri =
>> overrideViewUri.replace("view:", "");
>> +                                    if ("none:".equals(overrideViewUri))
>> {
>> +                                        interruptRequest = true;
>>                                      }
>>                                  }
>>                              }
>>                          }
>> -                    } catch (EventHandlerException e) {
>> -                        Debug.logError(e, module);
>>                      }
>> +                } catch (EventHandlerException e) {
>> +                    Debug.logError(e, module);
>>                  }
>> -            } catch (WebAppConfigurationException e) {
>> -                Debug.logError(e, "Exception thrown while parsing
>> controller.xml file: ", module);
>> -                throw new RequestHandlerException(e);
>>              }
>>          }
>>
>> @@ -409,7 +481,7 @@ public class RequestHandler {
>>              // Invoke the security handler
>>              // catch exceptions and throw RequestHandlerException if
>> failed.
>>              if (Debug.verboseOn()) Debug.logVerbose("[RequestHandler]:
>> AuthRequired. Running security check. " + showSessionId(request), module);
>> -            ConfigXMLReader.Event checkLoginEvent =
>> requestMapMap.get("checkLogin").event;
>> +            ConfigXMLReader.Event checkLoginEvent =
>> ccfg.getRequestMapMap().getFirst("checkLogin").event;
>>              String checkLoginReturnString = null;
>>
>>              try {
>> @@ -422,9 +494,9 @@ public class RequestHandler {
>>                  eventReturn = checkLoginReturnString;
>>                  // if the request is an ajax request we don't want to
>> return the default login check
>>                  if (!"XMLHttpRequest".equals(request.getHeader("X-Requested-With")))
>> {
>> -                    requestMap = requestMapMap.get("checkLogin");
>> +                    requestMap = ccfg.getRequestMapMap().
>> getFirst("checkLogin");
>>                  } else {
>> -                    requestMap = requestMapMap.get("ajaxCheckLogin");
>> +                    requestMap = ccfg.getRequestMapMap().
>> getFirst("ajaxCheckLogin");
>>                  }
>>              }
>>          }
>> @@ -556,7 +628,7 @@ public class RequestHandler {
>>                      redirectTarget += "?" + queryString;
>>                  }
>>
>> -                callRedirect(makeLink(request, response,
>> redirectTarget), response, request, statusCodeString);
>> +                callRedirect(makeLink(request, response,
>> redirectTarget), response, request, ccfg.getStatusCodeString());
>>                  return;
>>              }
>>          }
>> @@ -603,41 +675,35 @@ public class RequestHandler {
>>              // ======== handle views ========
>>
>>              // first invoke the post-processor events.
>> -            try {
>> -                for (ConfigXMLReader.Event event: controllerConfig.
>> getPostprocessorEventList().values()) {
>> -                    try {
>> -                        String returnString = this.runEvent(request,
>> response, event, requestMap, "postprocessor");
>> -                        if (returnString != null &&
>> !"success".equalsIgnoreCase(returnString)) {
>> -                            throw new EventHandlerException("Post-Processor
>> event did not return 'success'.");
>> -                        }
>> -                    } catch (EventHandlerException e) {
>> -                        Debug.logError(e, module);
>> +            for (ConfigXMLReader.Event event: ccfg.
>> getPostprocessorEventList().values()) {
>> +                try {
>> +                    String returnString = this.runEvent(request,
>> response, event, requestMap, "postprocessor");
>> +                    if (returnString != null &&
>> !"success".equalsIgnoreCase(returnString)) {
>> +                        throw new EventHandlerException("Post-Processor
>> event did not return 'success'.");
>>                      }
>> +                } catch (EventHandlerException e) {
>> +                    Debug.logError(e, module);
>>                  }
>> -            } catch (WebAppConfigurationException e) {
>> -                Debug.logError(e, "Exception thrown while parsing
>> controller.xml file: ", module);
>> -                throw new RequestHandlerException(e);
>>              }
>>
>>              String responseStatusCode  = nextRequestResponse.statusCode;
>>              if(UtilValidate.isNotEmpty(responseStatusCode))
>> -                statusCodeString = responseStatusCode;
>> -
>> +                ccfg.setStatusCodeString(responseStatusCode);
>>
>>              if ("url".equals(nextRequestResponse.type)) {
>>                  if (Debug.verboseOn()) Debug.logVerbose("[RequestHandler.doRequest]:
>> Response is a URL redirect." + showSessionId(request), module);
>> -                callRedirect(nextRequestResponse.value, response,
>> request, statusCodeString);
>> +                callRedirect(nextRequestResponse.value, response,
>> request, ccfg.getStatusCodeString());
>>              } else if ("cross-redirect".equals(nextRequestResponse.type))
>> {
>>                  // check for a cross-application redirect
>>                  if (Debug.verboseOn()) Debug.logVerbose("[RequestHandler.doRequest]:
>> Response is a Cross-Application redirect." + showSessionId(request),
>> module);
>>                  String url = nextRequestResponse.value.startsWith("/")
>> ? nextRequestResponse.value : "/" + nextRequestResponse.value;
>> -                callRedirect(url + this.makeQueryString(request,
>> nextRequestResponse), response, request, statusCodeString);
>> +                callRedirect(url + this.makeQueryString(request,
>> nextRequestResponse), response, request, ccfg.getStatusCodeString());
>>              } else if ("request-redirect".equals(nextRequestResponse.type))
>> {
>>                  if (Debug.verboseOn()) Debug.logVerbose("[RequestHandler.doRequest]:
>> Response is a Request redirect." + showSessionId(request), module);
>> -                callRedirect(makeLinkWithQueryString(request, response,
>> "/" + nextRequestResponse.value, nextRequestResponse), response, request,
>> statusCodeString);
>> +                callRedirect(makeLinkWithQueryString(request, response,
>> "/" + nextRequestResponse.value, nextRequestResponse), response, request,
>> ccfg.getStatusCodeString());
>>              } else if ("request-redirect-noparam".
>> equals(nextRequestResponse.type)) {
>>                  if (Debug.verboseOn()) Debug.logVerbose("[RequestHandler.doRequest]:
>> Response is a Request redirect with no parameters." +
>> showSessionId(request), module);
>> -                callRedirect(makeLink(request, response,
>> nextRequestResponse.value), response, request, statusCodeString);
>> +                callRedirect(makeLink(request, response,
>> nextRequestResponse.value), response, request, ccfg.getStatusCodeString());
>>              } else if ("view".equals(nextRequestResponse.type)) {
>>                  if (Debug.verboseOn()) Debug.logVerbose("[RequestHandler.doRequest]:
>> Response is a view." + showSessionId(request), module);
>>
>>
>> Added: ofbiz/ofbiz-framework/trunk/framework/webapp/src/test/
>> java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java
>> URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/
>> framework/webapp/src/test/java/org/apache/ofbiz/webapp/
>> control/RequestHandlerTests.java?rev=1838081&view=auto
>> ============================================================
>> ==================
>> --- ofbiz/ofbiz-framework/trunk/framework/webapp/src/test/
>> java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java (added)
>> +++ ofbiz/ofbiz-framework/trunk/framework/webapp/src/test/
>> java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java Wed Aug 15
>> 11:45:45 2018
>> @@ -0,0 +1,188 @@
>> +/**********************************************************
>> *********************
>> + * 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.ofbiz.webapp.control;
>> +
>> +import static org.hamcrest.CoreMatchers.both;
>> +import static org.hamcrest.CoreMatchers.hasItem;
>> +import static org.hamcrest.CoreMatchers.is;
>> +import static org.hamcrest.CoreMatchers.not;
>> +import static org.junit.Assert.assertFalse;
>> +import static org.junit.Assert.assertThat;
>> +import static org.junit.Assert.assertTrue;
>> +import static org.mockito.Mockito.mock;
>> +import static org.mockito.Mockito.when;
>> +
>> +import java.util.ArrayList;
>> +import java.util.Collection;
>> +import java.util.HashMap;
>> +import java.util.Map;
>> +
>> +import javax.servlet.http.HttpServletRequest;
>> +
>> +import org.apache.ofbiz.base.util.collections.MultivaluedMapContext;
>> +import org.apache.ofbiz.webapp.control.ConfigXMLReader.RequestMap;
>> +import org.apache.ofbiz.webapp.control.ConfigXMLReader.ViewMap;
>> +import org.junit.Before;
>> +import org.junit.Test;
>> +import org.w3c.dom.Element;
>> +
>> +public class RequestHandlerTests {
>> +    public static class ResolveURITests {
>> +        private MultivaluedMapContext<String,RequestMap> reqMaps;
>> +        private Map<String, ViewMap> viewMaps;
>> +        private HttpServletRequest req;
>> +        private Element dummyElement;
>> +        private RequestHandler.ControllerConfig ccfg;
>> +
>> +        @Before
>> +        public void setUp() {
>> +            ccfg = mock(RequestHandler.ControllerConfig.class);
>> +            reqMaps = new MultivaluedMapContext<>();
>> +            viewMaps = new HashMap<>();
>> +            when(ccfg.getDefaultRequest()).thenReturn(null);
>> +            when(ccfg.getRequestMapMap()).thenReturn(reqMaps);
>> +            when(ccfg.getViewMapMap()).thenReturn(viewMaps);
>> +            req = mock(HttpServletRequest.class);
>> +            dummyElement = mock(Element.class);
>> +            when(dummyElement.getAttribute("method")).thenReturn("all");
>> +            when(req.getMethod()).thenReturn("get");
>> +        }
>> +
>> +        @Test
>> +        public void resolveURIBasic() throws RequestHandlerException {
>> +            RequestMap foo = new RequestMap(dummyElement);
>> +            RequestMap bar = new RequestMap(dummyElement);
>> +            reqMaps.putSingle("foo", foo);
>> +            reqMaps.putSingle("bar", bar);
>> +            when(req.getPathInfo()).thenReturn("/foo");
>> +            assertThat(RequestHandler.resolveURI(ccfg, req),
>> +                    both(hasItem(foo)).and(not(hasItem(bar))));
>> +            assertThat(RequestHandler.resolveURI(ccfg, req).size(),
>> is(1));
>> +        }
>> +
>> +        @Test
>> +        public void resolveURIBasicPut() throws RequestHandlerException {
>> +            when(dummyElement.getAttribute("method")).thenReturn("put");
>> +
>
>

Re: svn commit: r1838081 - in /ofbiz/ofbiz-framework/trunk/framework: base/src/main/java/org/apache/ofbiz/base/util/collections/ base/src/test/java/org/apache/ofbiz/base/collections/ webapp/config/ webapp/dtd/ webapp/src/main/java/org/apache/ofbiz/webapp/c...

Posted by deepak nigam <de...@gmail.com>.
I am using OFBiz trunk and on creating an order from backend and getting
error screens while adding an item to cart and selecting shipping methods.
Please find the attached images for your reference.

On further investigation, I found that on reverting this commit things are
working fine. Can we verify this commit once more?

On Wed, Aug 15, 2018 at 5:15 PM <sh...@apache.org> wrote:

> Author: shijh
> Date: Wed Aug 15 11:45:45 2018
> New Revision: 1838081
>
> URL: http://svn.apache.org/viewvc?rev=1838081&view=rev
> Log:
> Implemented: Add method attribute to request-map to controll a uri can be
> called GET or POST only
> OFBIZ-10438
>
> Thanks: Mathieu Lirzin for the contribution.
>
> Added:
>
> ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java
>  (with props)
>
> ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java
>  (with props)
>
> ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/
>
> ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java
>  (with props)
>
> ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java
>  (with props)
>
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java
>  (with props)
>
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/test/java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java
>  (with props)
> Modified:
>     ofbiz/ofbiz-framework/trunk/framework/webapp/config/WebappUiLabels.xml
>     ofbiz/ofbiz-framework/trunk/framework/webapp/dtd/site-conf.xsd
>
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java
>
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlServlet.java
>
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java
>
> Added:
> ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java
> URL:
> http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java?rev=1838081&view=auto
>
> ==============================================================================
> ---
> ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java
> (added)
> +++
> ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java
> Wed Aug 15 11:45:45 2018
> @@ -0,0 +1,87 @@
>
> +/*******************************************************************************
> + * 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.ofbiz.base.util.collections;
> +
> +import java.util.LinkedList;
> +import java.util.List;
> +
> +/**
> + * MultivaluedMap Context
> + *
> + * A MapContext which handles multiple values for the same key.
> + */
> +public class MultivaluedMapContext<K, V> extends MapContext<K, List<V>> {
> +
> +    public static final String module =
> MultivaluedMapContext.class.getName();
> +
> +    /**
> +     * Create a multi-value map initialized with one context
> +     */
> +    public MultivaluedMapContext() {
> +        push();
> +    }
> +
> +    /**
> +     * Associate {@code key} with the single value {@code value}.
> +     * If other values are already associated with {@code key} then
> override them.
> +     *
> +     * @param key the key to associate {@code value} with
> +     * @param value the value to add to the context
> +     */
> +    public void putSingle(K key, V value) {
> +        List<V> box = new LinkedList<>();
> +        box.add(value);
> +        put(key, box);
> +    }
> +
> +    /**
> +     * Associate {@code key} with the single value {@code value}.
> +     * If other values are already associated with {@code key},
> +     * then add {@code value} to them.
> +     *
> +     * @param key the key to associate {@code value} with
> +     * @param value the value to add to the context
> +     */
> +    public void add(K key, V value) {
> +        List<V> cur = contexts.getFirst().get(key);
> +        if (cur == null) {
> +            cur = new LinkedList<>();
> +            /* if this method is called after a context switch, copy the
> previous values
> +               in current context to not mask them. */
> +            List<V> old = get(key);
> +            if (old != null) {
> +                cur.addAll(old);
> +            }
> +        }
> +        cur.add(value);
> +        put(key, cur);
> +    }
> +
> +    /**
> +     * Get the first value contained in the list of values associated
> with {@code key}.
> +     *
> +     * @param key a candidate key
> +     * @return the first value associated with {@code key} or null if no
> value
> +     * is associated with it.
> +     */
> +    public V getFirst(Object key) {
> +        List<V> res = get(key);
> +        return res == null ? null : res.get(0);
> +    }
> +}
>
> Propchange:
> ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java
>
> ------------------------------------------------------------------------------
>     svn:eol-style = native
>
> Propchange:
> ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java
>
> ------------------------------------------------------------------------------
>     svn:keywords = Date Rev Author URL Id
>
> Propchange:
> ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java
>
> ------------------------------------------------------------------------------
>     svn:mime-type = text/plain
>
> Added:
> ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java
> URL:
> http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java?rev=1838081&view=auto
>
> ==============================================================================
> ---
> ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java
> (added)
> +++
> ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java
> Wed Aug 15 11:45:45 2018
> @@ -0,0 +1,103 @@
>
> +/*******************************************************************************
> + * 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.ofbiz.base.util.collections;
> +
> +import java.util.Collection;
> +import java.util.Map;
> +import java.util.Set;
> +import java.util.stream.Collectors;
> +
> +// Adapter which allows viewing a multi-value map as a single-value map.
> +public class MultivaluedMapContextAdapter<K, V> implements Map<K, V> {
> +    private MultivaluedMapContext<K, V> adaptee;
> +
> +    public MultivaluedMapContextAdapter(MultivaluedMapContext<K, V>
> adaptee) {
> +        this.adaptee = adaptee;
> +    }
> +
> +    @Override
> +    public int size() {
> +        return adaptee.size();
> +    }
> +
> +    @Override
> +    public boolean isEmpty() {
> +        return adaptee.isEmpty();
> +    }
> +
> +    @Override
> +    public boolean containsKey(Object key) {
> +        return adaptee.containsKey(key);
> +    }
> +
> +    @Override
> +    public boolean containsValue(Object value) {
> +        return adaptee.values().stream()
> +                .map(l -> l.get(0))
> +                .anyMatch(value::equals);
> +    }
> +
> +    @Override
> +    public V get(Object key) {
> +        return adaptee.getFirst(key);
> +    }
> +
> +    @Override
> +    public V put(K key, V value) {
> +        V prev = get(key);
> +        adaptee.putSingle(key, value);
> +        return prev;
> +    }
> +
> +    @Override
> +    public V remove(Object key) {
> +        V prev = get(key);
> +        adaptee.remove(key);
> +        return prev;
> +    }
> +
> +    @Override
> +    public void putAll(Map<? extends K, ? extends V> m) {
> +        m.forEach(adaptee::putSingle);
> +    }
> +
> +    @Override
> +    public void clear() {
> +        adaptee.clear();
> +    }
> +
> +    @Override
> +    public Set<K> keySet() {
> +        return adaptee.keySet();
> +    }
> +
> +    @Override
> +    public Collection<V> values() {
> +        return adaptee.values().stream()
> +                .map(l -> l.get(0))
> +                .collect(Collectors.toList());
> +    }
> +
> +    @Override
> +    public Set<Entry<K, V>> entrySet() {
> +        return adaptee.keySet().stream()
> +                .collect(Collectors.toMap(k -> k, k -> get(k)))
> +                .entrySet();
> +    }
> +}
>
> Propchange:
> ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java
>
> ------------------------------------------------------------------------------
>     svn:eol-style = native
>
> Propchange:
> ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java
>
> ------------------------------------------------------------------------------
>     svn:keywords = Date Rev Author URL Id
>
> Propchange:
> ofbiz/ofbiz-framework/trunk/framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContextAdapter.java
>
> ------------------------------------------------------------------------------
>     svn:mime-type = text/plain
>
> Added:
> ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java
> URL:
> http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java?rev=1838081&view=auto
>
> ==============================================================================
> ---
> ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java
> (added)
> +++
> ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java
> Wed Aug 15 11:45:45 2018
> @@ -0,0 +1,70 @@
>
> +/*******************************************************************************
> + * 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.ofbiz.base.collections;
> +
> +import static org.hamcrest.CoreMatchers.hasItems;
> +import static org.hamcrest.CoreMatchers.is;
> +import static org.junit.Assert.assertEquals;
> +import static org.junit.Assert.assertFalse;
> +import static org.junit.Assert.assertThat;
> +import static org.junit.Assert.assertTrue;
> +
> +import java.util.HashMap;
> +
> +import org.apache.ofbiz.base.util.collections.MultivaluedMapContext;
> +import
> org.apache.ofbiz.base.util.collections.MultivaluedMapContextAdapter;
> +import org.junit.Before;
> +import org.junit.Test;
> +
> +public class MultivaluedMapContextAdapterTests {
> +    private MultivaluedMapContext<String, Integer> adaptee;
> +    private MultivaluedMapContextAdapter<String, Integer> adapter;
> +
> +    @Before
> +    public void setUp() throws Exception {
> +        adaptee = new MultivaluedMapContext<>();
> +        adaptee.add("foo", 0);
> +        adaptee.add("foo", 1);
> +        adaptee.add("foo", 2);
> +        adaptee.add("bar", 3);
> +        adapter = new MultivaluedMapContextAdapter<>(adaptee);
> +    }
> +
> +    @Test
> +    public void containsValueBasic() {
> +        assertTrue(adapter.containsValue(0));
> +        assertFalse(adapter.containsValue(1));
> +        assertFalse(adapter.containsValue(2));
> +        assertTrue(adapter.containsValue(3));
> +    }
> +
> +    @Test
> +    public void valuesBasic() {
> +        assertThat(adapter.values(), hasItems(0, 3));
> +        assertThat(adapter.values().size(), is(2));
> +    }
> +
> +    @Test
> +    public void entrySetBasic() {
> +        HashMap<String, Integer> expected = new HashMap<>();
> +        expected.put("foo", 0);
> +        expected.put("bar", 3);
> +        assertEquals(expected.entrySet(), adapter.entrySet());
> +    }
> +}
>
> Propchange:
> ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java
>
> ------------------------------------------------------------------------------
>     svn:eol-style = native
>
> Propchange:
> ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java
>
> ------------------------------------------------------------------------------
>     svn:keywords = Date Rev Author URL Id
>
> Propchange:
> ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextAdapterTests.java
>
> ------------------------------------------------------------------------------
>     svn:mime-type = text/plain
>
> Added:
> ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java
> URL:
> http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java?rev=1838081&view=auto
>
> ==============================================================================
> ---
> ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java
> (added)
> +++
> ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java
> Wed Aug 15 11:45:45 2018
> @@ -0,0 +1,82 @@
>
> +/*******************************************************************************
> + * 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.ofbiz.base.collections;
> +
> +import static org.hamcrest.CoreMatchers.both;
> +import static org.hamcrest.CoreMatchers.hasItem;
> +import static org.hamcrest.CoreMatchers.hasItems;
> +import static org.hamcrest.CoreMatchers.is;
> +import static org.hamcrest.CoreMatchers.not;
> +import static org.hamcrest.CoreMatchers.nullValue;
> +import static org.junit.Assert.assertThat;
> +
> +import org.apache.ofbiz.base.util.collections.MultivaluedMapContext;
> +import org.junit.Before;
> +import org.junit.Test;
> +
> +public class MultivaluedMapContextTests {
> +    private MultivaluedMapContext<String, Integer> m;
> +
> +    @Before
> +    public void setUp() throws Exception {
> +        m = new MultivaluedMapContext<>();
> +    }
> +
> +    @Test
> +    public void getEmpty() {
> +        assertThat(m.get("foo"), is(nullValue()));
> +    }
> +
> +    @Test
> +    public void putSingleBasic() {
> +        m.putSingle("foo", 0);
> +        assertThat(m.get("foo"), hasItem(0));
> +        m.putSingle("foo", 1);
> +        assertThat(m.get("foo"), both(hasItem(1)).and(not(hasItem(0))));
> +    }
> +
> +    @Test
> +    public void addBasic() {
> +        m.add("foo", 0);
> +        assertThat(m.get("foo"), hasItem(0));
> +        m.add("foo", 1);
> +        assertThat(m.get("foo"), hasItems(0, 1));
> +    }
> +
> +    @Test
> +    public void addWithPreviousContext() {
> +        m.add("foo", 0);
> +        m.push();
> +        assertThat(m.get("foo"), hasItem(0));
> +        m.add("foo", 1);
> +        assertThat(m.get("foo"), hasItems(0, 1));
> +    }
> +
> +    @Test
> +    public void getFirstBasic() {
> +        m.add("foo", 0);
> +        m.add("foo", 1);
> +        assertThat(m.getFirst("foo"), is(0));
> +    }
> +
> +    @Test
> +    public void getFirstEmpty() {
> +        assertThat(m.getFirst("foo"), is(nullValue()));
> +    }
> +}
>
> Propchange:
> ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java
>
> ------------------------------------------------------------------------------
>     svn:eol-style = native
>
> Propchange:
> ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java
>
> ------------------------------------------------------------------------------
>     svn:keywords = Date Rev Author URL Id
>
> Propchange:
> ofbiz/ofbiz-framework/trunk/framework/base/src/test/java/org/apache/ofbiz/base/collections/MultivaluedMapContextTests.java
>
> ------------------------------------------------------------------------------
>     svn:mime-type = text/plain
>
> Modified:
> ofbiz/ofbiz-framework/trunk/framework/webapp/config/WebappUiLabels.xml
> URL:
> http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/webapp/config/WebappUiLabels.xml?rev=1838081&r1=1838080&r2=1838081&view=diff
>
> ==============================================================================
> --- ofbiz/ofbiz-framework/trunk/framework/webapp/config/WebappUiLabels.xml
> (original)
> +++ ofbiz/ofbiz-framework/trunk/framework/webapp/config/WebappUiLabels.xml
> Wed Aug 15 11:45:45 2018
> @@ -339,4 +339,8 @@
>          <value xml:lang="zh">æ²¡æœ‰å®Œæˆ äº‹ä»¶</value>
>          <value xml:lang="zh-TW">æ²’æœ‰å®Œæˆ äº‹ä»¶</value>
>      </property>
> +    <property key="RequestMethodNotMatchConfig">
> +        <value xml:lang="en">[{0}] cannot be called by [{1}]
> method.</value>
> +        <value xml:lang="zh">[{0}]ä¸ èƒ½ç”¨[{1}]方法请求。</value>
> +    </property>
>  </resource>
>
> Modified: ofbiz/ofbiz-framework/trunk/framework/webapp/dtd/site-conf.xsd
> URL:
> http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/webapp/dtd/site-conf.xsd?rev=1838081&r1=1838080&r2=1838081&view=diff
>
> ==============================================================================
> --- ofbiz/ofbiz-framework/trunk/framework/webapp/dtd/site-conf.xsd
> (original)
> +++ ofbiz/ofbiz-framework/trunk/framework/webapp/dtd/site-conf.xsd Wed Aug
> 15 11:45:45 2018
> @@ -216,6 +216,24 @@ under the License.
>                  </xs:documentation>
>              </xs:annotation>
>          </xs:attribute>
> +        <xs:attribute name="method" use="optional" default="all">
> +            <xs:annotation>
> +                <xs:documentation>
> +                    The HTTP of this request. This will be the HTTP
> method used to access the request.
> +                </xs:documentation>
> +            </xs:annotation>
> +            <xs:simpleType>
> +                <xs:restriction base="xs:token">
> +                    <xs:enumeration value="get"/>
> +                    <xs:enumeration value="post"/>
> +                    <xs:enumeration value="put"/>
> +                    <xs:enumeration value="delete"/>
> +                    <xs:enumeration value="patch"/>
> +                    <xs:enumeration value="options"/>
> +                    <xs:enumeration value="all"/>
> +                </xs:restriction>
> +            </xs:simpleType>
> +        </xs:attribute>
>          <xs:attribute type="xs:boolean" name="edit" default="true">
>              <xs:annotation>
>                  <xs:documentation>
>
> Modified:
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java
> URL:
> http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java?rev=1838081&r1=1838080&r2=1838081&view=diff
>
> ==============================================================================
> ---
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java
> (original)
> +++
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java
> Wed Aug 15 11:45:45 2018
> @@ -49,6 +49,8 @@ import org.apache.ofbiz.base.util.UtilVa
>  import org.apache.ofbiz.base.util.UtilXml;
>  import org.apache.ofbiz.base.util.cache.UtilCache;
>  import org.apache.ofbiz.base.util.collections.MapContext;
> +import org.apache.ofbiz.base.util.collections.MultivaluedMapContext;
> +import
> org.apache.ofbiz.base.util.collections.MultivaluedMapContextAdapter;
>  import org.w3c.dom.Document;
>  import org.w3c.dom.Element;
>
> @@ -192,7 +194,7 @@ public class ConfigXMLReader {
>          private Map<String, Event> beforeLogoutEventList = new
> LinkedHashMap<String, Event>();
>          private Map<String, String> eventHandlerMap = new HashMap<String,
> String>();
>          private Map<String, String> viewHandlerMap = new HashMap<String,
> String>();
> -        private Map<String, RequestMap> requestMapMap = new
> HashMap<String, RequestMap>();
> +        private MultivaluedMapContext<String, RequestMap> requestMapMap =
> new MultivaluedMapContext<>();
>          private Map<String, ViewMap> viewMapMap = new HashMap<String,
> ViewMap>();
>
>          public ControllerConfig(URL url) throws
> WebAppConfigurationException {
> @@ -276,11 +278,16 @@ public class ConfigXMLReader {
>              return getIncludes(ccfg -> ccfg.protectView);
>          }
>
> +        // XXX: Keep it for backward compatibility until moving
> everything to 鈥榞etRequestMapMultiMap鈥�.
>          public Map<String, RequestMap> getRequestMapMap() throws
> WebAppConfigurationException {
> -            MapContext<String, RequestMap> result = new MapContext<>();
> +            return new
> MultivaluedMapContextAdapter<>(getRequestMapMultiMap());
> +        }
> +
> +        public MultivaluedMapContext<String, RequestMap>
> getRequestMapMultiMap() throws WebAppConfigurationException {
> +            MultivaluedMapContext<String, RequestMap> result = new
> MultivaluedMapContext<>();
>              for (URL includeLocation : includes) {
>                  ControllerConfig controllerConfig =
> getControllerConfig(includeLocation);
> -                result.push(controllerConfig.getRequestMapMap());
> +                result.push(controllerConfig.getRequestMapMultiMap());
>              }
>              result.push(requestMapMap);
>              return result;
> @@ -403,7 +410,7 @@ public class ConfigXMLReader {
>          private void loadRequestMap(Element root) {
>              for (Element requestMapElement :
> UtilXml.childElementList(root, "request-map")) {
>                  RequestMap requestMap = new RequestMap(requestMapElement);
> -                this.requestMapMap.put(requestMap.uri, requestMap);
> +                this.requestMapMap.add(requestMap.uri, requestMap);
>              }
>          }
>
> @@ -450,6 +457,7 @@ public class ConfigXMLReader {
>
>      public static class RequestMap {
>          public String uri;
> +        public String method;
>          public boolean edit = true;
>          public boolean trackVisit = true;
>          public boolean trackServerHit = true;
> @@ -466,6 +474,7 @@ public class ConfigXMLReader {
>          public RequestMap(Element requestMapElement) {
>              // Get the URI info
>              this.uri = requestMapElement.getAttribute("uri");
> +            this.method = requestMapElement.getAttribute("method");
>              this.edit =
> !"false".equals(requestMapElement.getAttribute("edit"));
>              this.trackServerHit =
> !"false".equals(requestMapElement.getAttribute("track-serverhit"));
>              this.trackVisit =
> !"false".equals(requestMapElement.getAttribute("track-visit"));
>
> Modified:
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlServlet.java
> URL:
> http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlServlet.java?rev=1838081&r1=1838080&r2=1838081&view=diff
>
> ==============================================================================
> ---
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlServlet.java
> (original)
> +++
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlServlet.java
> Wed Aug 15 11:45:45 2018
> @@ -211,6 +211,12 @@ public class ControlServlet extends Http
>          try {
>              // the ServerHitBin call for the event is done inside the
> doRequest method
>              requestHandler.doRequest(request, response, null, userLogin,
> delegator);
> +        } catch (MethodNotAllowedException e) {
> +            response.setContentType("text/plain");
> +            response.setCharacterEncoding(request.getCharacterEncoding());
> +            response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
> +            response.getWriter().print(e.getMessage());
> +            Debug.logError(e.getMessage(), module);
>          } catch (RequestHandlerException e) {
>              Throwable throwable = e.getNested() != null ? e.getNested() :
> e;
>              if (throwable instanceof IOException) {
>
> Added:
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java
> URL:
> http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java?rev=1838081&view=auto
>
> ==============================================================================
> ---
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java
> (added)
> +++
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java
> Wed Aug 15 11:45:45 2018
> @@ -0,0 +1,26 @@
>
> +/*******************************************************************************
> + * 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.ofbiz.webapp.control;
> +
> +@SuppressWarnings("serial")
> +public class MethodNotAllowedException extends RequestHandlerException {
> +    MethodNotAllowedException(String str) {
> +        super(str);
> +    }
> +}
>
> Propchange:
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java
>
> ------------------------------------------------------------------------------
>     svn:eol-style = native
>
> Propchange:
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java
>
> ------------------------------------------------------------------------------
>     svn:keywords = Date Rev Author URL Id
>
> Propchange:
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java
>
> ------------------------------------------------------------------------------
>     svn:mime-type = text/plain
>
> Modified:
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java
> URL:
> http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java?rev=1838081&r1=1838080&r2=1838081&view=diff
>
> ==============================================================================
> ---
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java
> (original)
> +++
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java
> Wed Aug 15 11:45:45 2018
> @@ -24,11 +24,15 @@ import java.io.IOException;
>  import java.io.Serializable;
>  import java.net.URL;
>  import java.security.cert.X509Certificate;
> +
> +import java.util.Collection;
> +import java.util.Collections;
>  import java.util.Enumeration;
>  import java.util.HashMap;
>  import java.util.List;
>  import java.util.Locale;
>  import java.util.Map;
> +import java.util.Optional;
>
>  import javax.servlet.ServletContext;
>  import javax.servlet.http.HttpServletRequest;
> @@ -45,12 +49,14 @@ import org.apache.ofbiz.base.util.UtilMi
>  import org.apache.ofbiz.base.util.UtilObject;
>  import org.apache.ofbiz.base.util.UtilProperties;
>  import org.apache.ofbiz.base.util.UtilValidate;
> +import org.apache.ofbiz.base.util.collections.MultivaluedMapContext;
>  import org.apache.ofbiz.entity.Delegator;
>  import org.apache.ofbiz.entity.GenericEntityException;
>  import org.apache.ofbiz.entity.GenericValue;
>  import org.apache.ofbiz.entity.util.EntityQuery;
>  import org.apache.ofbiz.entity.util.EntityUtilProperties;
>  import org.apache.ofbiz.webapp.OfbizUrlBuilder;
> +import org.apache.ofbiz.webapp.control.ConfigXMLReader.RequestMap;
>  import org.apache.ofbiz.webapp.event.EventFactory;
>  import org.apache.ofbiz.webapp.event.EventHandler;
>  import org.apache.ofbiz.webapp.event.EventHandlerException;
> @@ -68,12 +74,74 @@ import org.apache.ofbiz.widget.model.The
>  public class RequestHandler {
>
>      public static final String module = RequestHandler.class.getName();
> -    private final String defaultStatusCodeString =
> UtilProperties.getPropertyValue("requestHandler", "status-code", "302");
> +    private final static String defaultStatusCodeString =
> +            UtilProperties.getPropertyValue("requestHandler",
> "status-code", "302");
>      private final ViewFactory viewFactory;
>      private final EventFactory eventFactory;
>      private final URL controllerConfigURL;
>      private final boolean trackServerHit;
>      private final boolean trackVisit;
> +    private ControllerConfig ccfg;
> +
> +    static class ControllerConfig {
> +        private final MultivaluedMapContext<String, RequestMap>
> requestMapMap;
> +        private final Map<String, ConfigXMLReader.ViewMap> viewMapMap;
> +        private String statusCodeString;
> +        private final String defaultRequest;
> +        private final Map<String, ConfigXMLReader.Event>
> firstVisitEventList;
> +        private final Map<String, ConfigXMLReader.Event>
> preprocessorEventList;
> +        private final Map<String, ConfigXMLReader.Event>
> postprocessorEventList;
> +        private final String protectView;
> +
> +        ControllerConfig(ConfigXMLReader.ControllerConfig ccfg) throws
> WebAppConfigurationException {
> +            preprocessorEventList = ccfg.getPreprocessorEventList();
> +            postprocessorEventList = ccfg.getPostprocessorEventList();
> +            requestMapMap = ccfg.getRequestMapMultiMap();
> +            viewMapMap = ccfg.getViewMapMap();
> +            defaultRequest = ccfg.getDefaultRequest();
> +            firstVisitEventList = ccfg.getFirstVisitEventList();
> +            protectView = ccfg.getProtectView();
> +
> +            String status = ccfg.getStatusCode();
> +            statusCodeString = UtilValidate.isEmpty(status) ?
> defaultStatusCodeString : status;
> +        }
> +
> +        public MultivaluedMapContext<String, RequestMap>
> getRequestMapMap() {
> +            return requestMapMap;
> +        }
> +
> +        public Map<String, ConfigXMLReader.ViewMap> getViewMapMap() {
> +            return viewMapMap;
> +        }
> +
> +        public String getStatusCodeString() {
> +            return statusCodeString;
> +        }
> +
> +        public String getDefaultRequest() {
> +            return defaultRequest;
> +        }
> +
> +        public void setStatusCodeString(String statusCodeString) {
> +            this.statusCodeString = statusCodeString;
> +        }
> +
> +        public Map<String, ConfigXMLReader.Event>
> getFirstVisitEventList() {
> +            return firstVisitEventList;
> +        }
> +
> +        public Map<String, ConfigXMLReader.Event>
> getPreprocessorEventList() {
> +            return preprocessorEventList;
> +        }
> +
> +        public Map<String, ConfigXMLReader.Event>
> getPostprocessorEventList() {
> +            return postprocessorEventList;
> +        }
> +
> +        public String getProtectView() {
> +            return protectView;
> +        }
> +    }
>
>      public static RequestHandler getRequestHandler(ServletContext
> servletContext) {
>          RequestHandler rh = (RequestHandler)
> servletContext.getAttribute("_REQUEST_HANDLER_");
> @@ -110,6 +178,56 @@ public class RequestHandler {
>          return null;
>      }
>
> +    /**
> +     * Find a collection of request maps in {@code ccfg} matching {@code
> req}.
> +     * Otherwise fall back to matching the {@code defaultReq} field in
> {@code ccfg}.
> +     *
> +     * @param ccfg The controller containing the current configuration
> +     * @param req The HTTP request to match
> +     * @return a collection of request maps which might be empty
> +     */
> +    static Collection<RequestMap> resolveURI(ControllerConfig ccfg,
> HttpServletRequest req) {
> +        Map<String, List<RequestMap>> requestMapMap =
> ccfg.getRequestMapMap();
> +        Map<String, ConfigXMLReader.ViewMap> viewMapMap =
> ccfg.getViewMapMap();
> +        String defaultRequest = ccfg.getDefaultRequest();
> +        String path = req.getPathInfo();
> +        String requestUri = getRequestUri(path);
> +        String viewUri = getOverrideViewUri(path);
> +        Collection<RequestMap> rmaps;
> +        if (requestMapMap.containsKey(requestUri) &&
> !viewMapMap.containsKey(viewUri)) {
> +            rmaps = requestMapMap.get(requestUri);
> +        } else if (defaultRequest != null) {
> +            rmaps = requestMapMap.get(defaultRequest);
> +        } else {
> +            rmaps = null;
> +        }
> +        return rmaps != null ? rmaps : Collections.emptyList();
> +    }
> +
> +    /**
> +     * Find the request map matching {@code method}.
> +     * Otherwise fall back to the one matching the "all" and "" special
> methods
> +     * in that respective order.
> +     *
> +     * @param method the HTTP method to match
> +     * @param rmaps the collection of request map candidates
> +     * @return a request map {@code Optional}
> +     */
> +    static Optional<RequestMap> resolveMethod(String method,
> Collection<RequestMap> rmaps) {
> +        for (RequestMap map : rmaps) {
> +            if (map.method.equalsIgnoreCase(method)) {
> +                return Optional.of(map);
> +            }
> +        }
> +        if (method.isEmpty()) {
> +            return Optional.empty();
> +        } else if (method.equals("all")) {
> +            return resolveMethod("", rmaps);
> +        } else {
> +            return resolveMethod("all", rmaps);
> +        }
> +    }
> +
>      public void doRequest(HttpServletRequest request, HttpServletResponse
> response, String requestUri) throws RequestHandlerException,
> RequestHandlerExceptionAllowExternalRequests {
>          HttpSession session = request.getSession();
>          Delegator delegator = (Delegator)
> request.getAttribute("delegator");
> @@ -125,20 +243,13 @@ public class RequestHandler {
>          long startTime = System.currentTimeMillis();
>          HttpSession session = request.getSession();
>
> -        // get the controllerConfig once for this method so we don't have
> to get it over and over inside the method
> -        ConfigXMLReader.ControllerConfig controllerConfig =
> this.getControllerConfig();
> -        Map<String, ConfigXMLReader.RequestMap> requestMapMap = null;
> -        String statusCodeString = null;
> +        // Parse controller config.
>          try {
> -            requestMapMap = controllerConfig.getRequestMapMap();
> -            statusCodeString = controllerConfig.getStatusCode();
> +            ccfg = new ControllerConfig(getControllerConfig());
>          } catch (WebAppConfigurationException e) {
>              Debug.logError(e, "Exception thrown while parsing
> controller.xml file: ", module);
>              throw new RequestHandlerException(e);
>          }
> -        if (UtilValidate.isEmpty(statusCodeString)) {
> -            statusCodeString = defaultStatusCodeString;
> -        }
>
>          // workaround if we are in the root webapp
>          String cname = UtilHttp.getApplicationName(request);
> @@ -153,50 +264,29 @@ public class RequestHandler {
>              }
>          }
>
> -        String overrideViewUri =
> RequestHandler.getOverrideViewUri(request.getPathInfo());
> -
> -        String requestMissingErrorMessage = "Unknown request [" +
> defaultRequestUri + "]; this request does not exist or cannot be called
> directly.";
> -        ConfigXMLReader.RequestMap requestMap = null;
> -        if (defaultRequestUri != null) {
> -            requestMap = requestMapMap.get(defaultRequestUri);
> -        }
> -        // check for default request
> -        if (requestMap == null) {
> -            String defaultRequest;
> -            try {
> -                defaultRequest = controllerConfig.getDefaultRequest();
> -            } catch (WebAppConfigurationException e) {
> -                Debug.logError(e, "Exception thrown while parsing
> controller.xml file: ", module);
> -                throw new RequestHandlerException(e);
> -            }
> -            if (defaultRequest != null) { // required! to avoid a null
> pointer exception and generate a requesthandler exception if default
> request not found.
> -                requestMap = requestMapMap.get(defaultRequest);
> -            }
> -        }
> -
> -        // check for override view
> -        if (overrideViewUri != null) {
> -            ConfigXMLReader.ViewMap viewMap;
> -            try {
> -                viewMap =
> getControllerConfig().getViewMapMap().get(overrideViewUri);
> -                if (viewMap == null) {
> -                    String defaultRequest =
> controllerConfig.getDefaultRequest();
> -                    if (defaultRequest != null) { // required! to avoid a
> null pointer exception and generate a requesthandler exception if default
> request not found.
> -                        requestMap = requestMapMap.get(defaultRequest);
> -                    }
> -                }
> -            } catch (WebAppConfigurationException e) {
> -                Debug.logError(e, "Exception thrown while parsing
> controller.xml file: ", module);
> -                throw new RequestHandlerException(e);
> +        String requestMissingErrorMessage = "Unknown request ["
> +                + defaultRequestUri
> +                + "]; this request does not exist or cannot be called
> directly.";
> +
> +        String path = request.getPathInfo();
> +        String requestUri = getRequestUri(path);
> +        String overrideViewUri = getOverrideViewUri(path);
> +
> +        Collection<RequestMap> rmaps = resolveURI(ccfg, request);
> +        if (rmaps.isEmpty()) {
> +            if (throwRequestHandlerExceptionOnMissingLocalRequest) {
> +              throw new
> RequestHandlerException(requestMissingErrorMessage);
> +            } else {
> +              throw new RequestHandlerExceptionAllowExternalRequests();
>              }
>          }
>
> -        // if no matching request is found in the controller, depending
> on throwRequestHandlerExceptionOnMissingLocalRequest
> -        //  we throw a RequestHandlerException or
> RequestHandlerExceptionAllowExternalRequests
> -        if (requestMap == null) {
> -            if (throwRequestHandlerExceptionOnMissingLocalRequest) throw
> new RequestHandlerException(requestMissingErrorMessage);
> -            else throw new RequestHandlerExceptionAllowExternalRequests();
> -         }
> +        String method = request.getMethod();
> +        RequestMap requestMap = resolveMethod(method,
> rmaps).orElseThrow(() -> {
> +            String msg = UtilProperties.getMessage("WebappUiLabels",
> "RequestMethodNotMatchConfig",
> +                    UtilMisc.toList(requestUri, method),
> UtilHttp.getLocale(request));
> +            return new MethodNotAllowedException(msg);
> +        });
>
>          String eventReturn = null;
>          if (requestMap.metrics != null &&
> requestMap.metrics.getThreshold() != 0.0 &&
> requestMap.metrics.getTotalEvents() > 3 &&
> requestMap.metrics.getThreshold() < requestMap.metrics.getServiceRate()) {
> @@ -210,7 +300,7 @@ public class RequestHandler {
>          // Check for chained request.
>          if (chain != null) {
>              String chainRequestUri = RequestHandler.getRequestUri(chain);
> -            requestMap = requestMapMap.get(chainRequestUri);
> +            requestMap =
> ccfg.getRequestMapMap().getFirst(chainRequestUri);
>              if (requestMap == null) {
>                  throw new RequestHandlerException("Unknown chained
> request [" + chainRequestUri + "]; this request does not exist");
>              }
> @@ -234,18 +324,11 @@ public class RequestHandler {
>              // Check to make sure we are allowed to access this request
> directly. (Also checks if this request is defined.)
>              // If the request cannot be called, or is not defined, check
> and see if there is a default-request we can process
>              if (!requestMap.securityDirectRequest) {
> -                String defaultRequest;
> -                try {
> -                    defaultRequest = controllerConfig.getDefaultRequest();
> -                } catch (WebAppConfigurationException e) {
> -                    Debug.logError(e, "Exception thrown while parsing
> controller.xml file: ", module);
> -                    throw new RequestHandlerException(e);
> -                }
> -                if (defaultRequest == null ||
> !requestMapMap.get(defaultRequest).securityDirectRequest) {
> +                if (ccfg.getDefaultRequest() == null ||
> !ccfg.getRequestMapMap().getFirst(ccfg.getDefaultRequest()).securityDirectRequest)
> {
>                      // use the same message as if it was missing for
> security reasons, ie so can't tell if it is missing or direct request is
> not allowed
>                      throw new
> RequestHandlerException(requestMissingErrorMessage);
>                  } else {
> -                    requestMap = requestMapMap.get(defaultRequest);
> +                    requestMap =
> ccfg.getRequestMapMap().getFirst(ccfg.getDefaultRequest());
>                  }
>              }
>              // Check if we SHOULD be secure and are not.
> @@ -288,7 +371,7 @@ public class RequestHandler {
>                      String newUrl = RequestHandler.makeUrl(request,
> response, urlBuf.toString());
>                      if (newUrl.toUpperCase().startsWith("HTTPS")) {
>                          // if we are supposed to be secure, redirect
> secure.
> -                        callRedirect(newUrl, response, request,
> statusCodeString);
> +                        callRedirect(newUrl, response, request,
> ccfg.getStatusCodeString());
>                          return;
>                      }
>                  }
> @@ -333,63 +416,52 @@ public class RequestHandler {
>                  if (Debug.infoOn())
>                      Debug.logInfo("This is the first request in this
> visit." + showSessionId(request), module);
>                  session.setAttribute("_FIRST_VISIT_EVENTS_", "complete");
> -                try {
> -                    for (ConfigXMLReader.Event event:
> controllerConfig.getFirstVisitEventList().values()) {
> -                        try {
> -                            String returnString = this.runEvent(request,
> response, event, null, "firstvisit");
> -                            if (returnString == null ||
> "none".equalsIgnoreCase(returnString)) {
> -                                interruptRequest = true;
> -                            } else if
> (!"success".equalsIgnoreCase(returnString)) {
> -                                throw new
> EventHandlerException("First-Visit event did not return 'success'.");
> -                            }
> -                        } catch (EventHandlerException e) {
> -                            Debug.logError(e, module);
> +                for (ConfigXMLReader.Event event:
> ccfg.getFirstVisitEventList().values()) {
> +                    try {
> +                        String returnString = this.runEvent(request,
> response, event, null, "firstvisit");
> +                        if (returnString == null ||
> "none".equalsIgnoreCase(returnString)) {
> +                            interruptRequest = true;
> +                        } else if
> (!"success".equalsIgnoreCase(returnString)) {
> +                            throw new EventHandlerException("First-Visit
> event did not return 'success'.");
>                          }
> +                    } catch (EventHandlerException e) {
> +                        Debug.logError(e, module);
>                      }
> -                } catch (WebAppConfigurationException e) {
> -                    Debug.logError(e, "Exception thrown while parsing
> controller.xml file: ", module);
> -                    throw new RequestHandlerException(e);
>                  }
>              }
>
>              // Invoke the pre-processor (but NOT in a chain)
> -            try {
> -                for (ConfigXMLReader.Event event:
> controllerConfig.getPreprocessorEventList().values()) {
> -                    try {
> -                        String returnString = this.runEvent(request,
> response, event, null, "preprocessor");
> -                        if (returnString == null ||
> "none".equalsIgnoreCase(returnString)) {
> -                            interruptRequest = true;
> -                        } else if
> (!"success".equalsIgnoreCase(returnString)) {
> -                            if (!returnString.contains(":_protect_:")) {
> -                                throw new
> EventHandlerException("Pre-Processor event [" + event.invoke + "] did not
> return 'success'.");
> -                            } else { // protect the view normally
> rendered and redirect to error response view
> -                                returnString =
> returnString.replace(":_protect_:", "");
> -                                if (returnString.length() > 0) {
> -
> request.setAttribute("_ERROR_MESSAGE_", returnString);
> -                                }
> -                                eventReturn = null;
> -                                // check to see if there is a "protect"
> response, if so it's ok else show the default_error_response_view
> -                                if
> (!requestMap.requestResponseMap.containsKey("protect")) {
> -                                    String protectView =
> controllerConfig.getProtectView();
> -                                    if (protectView != null) {
> -                                        overrideViewUri = protectView;
> -                                    } else {
> -                                        overrideViewUri =
> EntityUtilProperties.getPropertyValue("security",
> "default.error.response.view", delegator);
> -                                        overrideViewUri =
> overrideViewUri.replace("view:", "");
> -                                        if
> ("none:".equals(overrideViewUri)) {
> -                                            interruptRequest = true;
> -                                        }
> +            for (ConfigXMLReader.Event event:
> ccfg.getPreprocessorEventList().values()) {
> +                try {
> +                    String returnString = this.runEvent(request,
> response, event, null, "preprocessor");
> +                    if (returnString == null ||
> "none".equalsIgnoreCase(returnString)) {
> +                        interruptRequest = true;
> +                    } else if (!"success".equalsIgnoreCase(returnString))
> {
> +                        if (!returnString.contains(":_protect_:")) {
> +                            throw new
> EventHandlerException("Pre-Processor event [" + event.invoke + "] did not
> return 'success'.");
> +                        } else { // protect the view normally rendered
> and redirect to error response view
> +                            returnString =
> returnString.replace(":_protect_:", "");
> +                            if (returnString.length() > 0) {
> +                                request.setAttribute("_ERROR_MESSAGE_",
> returnString);
> +                            }
> +                            eventReturn = null;
> +                            // check to see if there is a "protect"
> response, if so it's ok else show the default_error_response_view
> +                            if
> (!requestMap.requestResponseMap.containsKey("protect")) {
> +                                if (ccfg.getProtectView() != null) {
> +                                    overrideViewUri =
> ccfg.getProtectView();
> +                                } else {
> +                                    overrideViewUri =
> EntityUtilProperties.getPropertyValue("security",
> "default.error.response.view", delegator);
> +                                    overrideViewUri =
> overrideViewUri.replace("view:", "");
> +                                    if ("none:".equals(overrideViewUri)) {
> +                                        interruptRequest = true;
>                                      }
>                                  }
>                              }
>                          }
> -                    } catch (EventHandlerException e) {
> -                        Debug.logError(e, module);
>                      }
> +                } catch (EventHandlerException e) {
> +                    Debug.logError(e, module);
>                  }
> -            } catch (WebAppConfigurationException e) {
> -                Debug.logError(e, "Exception thrown while parsing
> controller.xml file: ", module);
> -                throw new RequestHandlerException(e);
>              }
>          }
>
> @@ -409,7 +481,7 @@ public class RequestHandler {
>              // Invoke the security handler
>              // catch exceptions and throw RequestHandlerException if
> failed.
>              if (Debug.verboseOn()) Debug.logVerbose("[RequestHandler]:
> AuthRequired. Running security check. " + showSessionId(request), module);
> -            ConfigXMLReader.Event checkLoginEvent =
> requestMapMap.get("checkLogin").event;
> +            ConfigXMLReader.Event checkLoginEvent =
> ccfg.getRequestMapMap().getFirst("checkLogin").event;
>              String checkLoginReturnString = null;
>
>              try {
> @@ -422,9 +494,9 @@ public class RequestHandler {
>                  eventReturn = checkLoginReturnString;
>                  // if the request is an ajax request we don't want to
> return the default login check
>                  if
> (!"XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
> -                    requestMap = requestMapMap.get("checkLogin");
> +                    requestMap =
> ccfg.getRequestMapMap().getFirst("checkLogin");
>                  } else {
> -                    requestMap = requestMapMap.get("ajaxCheckLogin");
> +                    requestMap =
> ccfg.getRequestMapMap().getFirst("ajaxCheckLogin");
>                  }
>              }
>          }
> @@ -556,7 +628,7 @@ public class RequestHandler {
>                      redirectTarget += "?" + queryString;
>                  }
>
> -                callRedirect(makeLink(request, response, redirectTarget),
> response, request, statusCodeString);
> +                callRedirect(makeLink(request, response, redirectTarget),
> response, request, ccfg.getStatusCodeString());
>                  return;
>              }
>          }
> @@ -603,41 +675,35 @@ public class RequestHandler {
>              // ======== handle views ========
>
>              // first invoke the post-processor events.
> -            try {
> -                for (ConfigXMLReader.Event event:
> controllerConfig.getPostprocessorEventList().values()) {
> -                    try {
> -                        String returnString = this.runEvent(request,
> response, event, requestMap, "postprocessor");
> -                        if (returnString != null &&
> !"success".equalsIgnoreCase(returnString)) {
> -                            throw new
> EventHandlerException("Post-Processor event did not return 'success'.");
> -                        }
> -                    } catch (EventHandlerException e) {
> -                        Debug.logError(e, module);
> +            for (ConfigXMLReader.Event event:
> ccfg.getPostprocessorEventList().values()) {
> +                try {
> +                    String returnString = this.runEvent(request,
> response, event, requestMap, "postprocessor");
> +                    if (returnString != null &&
> !"success".equalsIgnoreCase(returnString)) {
> +                        throw new EventHandlerException("Post-Processor
> event did not return 'success'.");
>                      }
> +                } catch (EventHandlerException e) {
> +                    Debug.logError(e, module);
>                  }
> -            } catch (WebAppConfigurationException e) {
> -                Debug.logError(e, "Exception thrown while parsing
> controller.xml file: ", module);
> -                throw new RequestHandlerException(e);
>              }
>
>              String responseStatusCode  = nextRequestResponse.statusCode;
>              if(UtilValidate.isNotEmpty(responseStatusCode))
> -                statusCodeString = responseStatusCode;
> -
> +                ccfg.setStatusCodeString(responseStatusCode);
>
>              if ("url".equals(nextRequestResponse.type)) {
>                  if (Debug.verboseOn())
> Debug.logVerbose("[RequestHandler.doRequest]: Response is a URL redirect."
> + showSessionId(request), module);
> -                callRedirect(nextRequestResponse.value, response,
> request, statusCodeString);
> +                callRedirect(nextRequestResponse.value, response,
> request, ccfg.getStatusCodeString());
>              } else if ("cross-redirect".equals(nextRequestResponse.type))
> {
>                  // check for a cross-application redirect
>                  if (Debug.verboseOn())
> Debug.logVerbose("[RequestHandler.doRequest]: Response is a
> Cross-Application redirect." + showSessionId(request), module);
>                  String url = nextRequestResponse.value.startsWith("/") ?
> nextRequestResponse.value : "/" + nextRequestResponse.value;
> -                callRedirect(url + this.makeQueryString(request,
> nextRequestResponse), response, request, statusCodeString);
> +                callRedirect(url + this.makeQueryString(request,
> nextRequestResponse), response, request, ccfg.getStatusCodeString());
>              } else if
> ("request-redirect".equals(nextRequestResponse.type)) {
>                  if (Debug.verboseOn())
> Debug.logVerbose("[RequestHandler.doRequest]: Response is a Request
> redirect." + showSessionId(request), module);
> -                callRedirect(makeLinkWithQueryString(request, response,
> "/" + nextRequestResponse.value, nextRequestResponse), response, request,
> statusCodeString);
> +                callRedirect(makeLinkWithQueryString(request, response,
> "/" + nextRequestResponse.value, nextRequestResponse), response, request,
> ccfg.getStatusCodeString());
>              } else if
> ("request-redirect-noparam".equals(nextRequestResponse.type)) {
>                  if (Debug.verboseOn())
> Debug.logVerbose("[RequestHandler.doRequest]: Response is a Request
> redirect with no parameters." + showSessionId(request), module);
> -                callRedirect(makeLink(request, response,
> nextRequestResponse.value), response, request, statusCodeString);
> +                callRedirect(makeLink(request, response,
> nextRequestResponse.value), response, request, ccfg.getStatusCodeString());
>              } else if ("view".equals(nextRequestResponse.type)) {
>                  if (Debug.verboseOn())
> Debug.logVerbose("[RequestHandler.doRequest]: Response is a view." +
> showSessionId(request), module);
>
>
> Added:
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/test/java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java
> URL:
> http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/framework/webapp/src/test/java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java?rev=1838081&view=auto
>
> ==============================================================================
> ---
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/test/java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java
> (added)
> +++
> ofbiz/ofbiz-framework/trunk/framework/webapp/src/test/java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java
> Wed Aug 15 11:45:45 2018
> @@ -0,0 +1,188 @@
>
> +/*******************************************************************************
> + * 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.ofbiz.webapp.control;
> +
> +import static org.hamcrest.CoreMatchers.both;
> +import static org.hamcrest.CoreMatchers.hasItem;
> +import static org.hamcrest.CoreMatchers.is;
> +import static org.hamcrest.CoreMatchers.not;
> +import static org.junit.Assert.assertFalse;
> +import static org.junit.Assert.assertThat;
> +import static org.junit.Assert.assertTrue;
> +import static org.mockito.Mockito.mock;
> +import static org.mockito.Mockito.when;
> +
> +import java.util.ArrayList;
> +import java.util.Collection;
> +import java.util.HashMap;
> +import java.util.Map;
> +
> +import javax.servlet.http.HttpServletRequest;
> +
> +import org.apache.ofbiz.base.util.collections.MultivaluedMapContext;
> +import org.apache.ofbiz.webapp.control.ConfigXMLReader.RequestMap;
> +import org.apache.ofbiz.webapp.control.ConfigXMLReader.ViewMap;
> +import org.junit.Before;
> +import org.junit.Test;
> +import org.w3c.dom.Element;
> +
> +public class RequestHandlerTests {
> +    public static class ResolveURITests {
> +        private MultivaluedMapContext<String,RequestMap> reqMaps;
> +        private Map<String, ViewMap> viewMaps;
> +        private HttpServletRequest req;
> +        private Element dummyElement;
> +        private RequestHandler.ControllerConfig ccfg;
> +
> +        @Before
> +        public void setUp() {
> +            ccfg = mock(RequestHandler.ControllerConfig.class);
> +            reqMaps = new MultivaluedMapContext<>();
> +            viewMaps = new HashMap<>();
> +            when(ccfg.getDefaultRequest()).thenReturn(null);
> +            when(ccfg.getRequestMapMap()).thenReturn(reqMaps);
> +            when(ccfg.getViewMapMap()).thenReturn(viewMaps);
> +            req = mock(HttpServletRequest.class);
> +            dummyElement = mock(Element.class);
> +            when(dummyElement.getAttribute("method")).thenReturn("all");
> +            when(req.getMethod()).thenReturn("get");
> +        }
> +
> +        @Test
> +        public void resolveURIBasic() throws RequestHandlerException {
> +            RequestMap foo = new RequestMap(dummyElement);
> +            RequestMap bar = new RequestMap(dummyElement);
> +            reqMaps.putSingle("foo", foo);
> +            reqMaps.putSingle("bar", bar);
> +            when(req.getPathInfo()).thenReturn("/foo");
> +            assertThat(RequestHandler.resolveURI(ccfg, req),
> +                    both(hasItem(foo)).and(not(hasItem(bar))));
> +            assertThat(RequestHandler.resolveURI(ccfg, req).size(),
> is(1));
> +        }
> +
> +        @Test
> +        public void resolveURIBasicPut() throws RequestHandlerException {
> +            when(dummyElement.getAttribute("method")).thenReturn("put");
> +