Hi Folks,

I could be mistaken, but this seems like a very major change that did
not have a thorough and proper discussion at the mailing list? I would
rather at least have an explanation of what was committed and to
discuss the merits and cons of the implementation.

Thanks,

Taher

On Tue, Jun 26, 2018 at 5:00 AM,  <sh...@apache.org> wrote:
> Author: shijh
> Date: Tue Jun 26 02:00:33 2018
> New Revision: 1834389
>
> URL: http://svn.apache.org/viewvc?rev=1834389&view=rev
> Log:
> Implemented: Add method attribute to request-map to controll a uri can be 
> called GET or POST only
> OFBIZ-10438
>
> The request-map element has a new method attribute to control a uri be called 
> by GET or POST or all.
>
> 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/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=1834389&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
>  Tue Jun 26 02:00:33 2018
> @@ -0,0 +1,249 @@
> +/*******************************************************************************
> + * 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.Deque;
> +import java.util.HashSet;
> +import java.util.Iterator;
> +import java.util.LinkedList;
> +import java.util.List;
> +import java.util.Locale;
> +import java.util.Map;
> +import java.util.Objects;
> +import java.util.Set;
> +import java.util.stream.Collectors;
> +import java.util.stream.Stream;
> +import java.util.stream.StreamSupport;
> +
> +import javax.ws.rs.core.MultivaluedHashMap;
> +import javax.ws.rs.core.MultivaluedMap;
> +
> +import org.apache.ofbiz.base.util.UtilGenerics;
> +
> +/**
> + * Map Stack
> + *
> + */
> +public class MultiValuedMapContext<K, V> implements MultivaluedMap<K, V>, 
> LocalizedMap<V> {
> +
> +    public static final String module = 
> MultiValuedMapContext.class.getName();
> +
> +    public static final <K, V> MultiValuedMapContext<K, V> getMapContext() {
> +        return new MultiValuedMapContext<>();
> +    }
> +
> +    public static <K, V> MultiValuedMapContext<K, V> createMapContext() {
> +        MultiValuedMapContext<K, V> newValue = 
> MultiValuedMapContext.getMapContext();
> +        // initialize with a single entry
> +        newValue.push();
> +        return newValue;
> +    }
> +
> +    public static <K, V> MultiValuedMapContext<K, V> 
> createMapContext(MultivaluedMap<K, V> baseMap) {
> +        if (baseMap instanceof MultiValuedMapContext) {
> +            return createMapContext((MultiValuedMapContext<K, V>) baseMap);
> +        } else {
> +            MultiValuedMapContext<K, V> newValue = 
> MultiValuedMapContext.getMapContext();
> +            newValue.maps.addFirst(baseMap);
> +            return newValue;
> +        }
> +    }
> +
> +    /** Does a shallow copy of the internal stack of the passed MapContext; 
> enables simultaneous stacks that share common parent Maps */
> +    public static <K, V> MultiValuedMapContext<K, V> 
> createMapContext(MultiValuedMapContext<K, V> source) {
> +        MultiValuedMapContext<K, V> newValue = 
> MultiValuedMapContext.getMapContext();
> +        newValue.maps.addAll(source.maps);
> +        return newValue;
> +    }
> +
> +    protected MultiValuedMapContext() {
> +        super();
> +    }
> +
> +    protected Deque<MultivaluedMap<K, V>> maps = new LinkedList<>();
> +
> +    public void reset() {
> +        maps = new LinkedList<>();
> +    }
> +
> +    /** Puts a new Map on the top of the stack */
> +    public void push() {
> +        MultivaluedMap<K, V> newMap = new MultivaluedHashMap<>();
> +        this.maps.addFirst(newMap);
> +    }
> +
> +    /** Puts an existing Map on the top of the stack (top meaning will 
> override lower layers on the stack) */
> +    public void push(MultivaluedMap<K, V> existingMap) {
> +        if (existingMap == null) {
> +            throw new IllegalArgumentException("Error: cannot push null 
> existing Map onto a MapContext");
> +        }
> +        this.maps.addFirst(existingMap);
> +    }
> +
> +    /** Puts an existing Map on the BOTTOM of the stack (bottom meaning will 
> be overriden by lower layers on the stack, ie everything else already there) 
> */
> +    public void addToBottom(MultivaluedMap<K, V> existingMap) {
> +        if (existingMap == null) {
> +            throw new IllegalArgumentException("Error: cannot add null 
> existing Map to bottom of a MapContext");
> +        }
> +        this.maps.add(existingMap);
> +    }
> +
> +    /** Remove and returns the Map from the top of the stack; if there is 
> only one Map on the stack it returns null and does not remove it */
> +    public MultivaluedMap<K, V> pop() {
> +        // always leave at least one Map in the List, ie never pop off the 
> last Map
> +        return (maps.size() < 1) ? null : maps.removeFirst();
> +    }
> +
> +    /**
> +     * Creates a MapContext object that has the same Map objects on its 
> stack;
> +     * meant to be used to enable a
> +     * situation where a parent and child context are operating 
> simultaneously
> +     * using two different MapContext objects, but sharing the Maps in common
> +     */
> +    public MultiValuedMapContext<K, V> standAloneStack() {
> +        MultiValuedMapContext<K, V> standAlone = 
> MultiValuedMapContext.createMapContext(this);
> +        return standAlone;
> +    }
> +
> +    /**
> +     * Creates a MapContext object that has the same Map objects on its 
> stack,
> +     * but with a new Map pushed on the top; meant to be used to enable a
> +     * situation where a parent and child context are operating 
> simultaneously
> +     * using two different MapContext objects, but sharing the Maps in common
> +     */
> +    public MultiValuedMapContext<K, V> standAloneChildStack() {
> +        MultiValuedMapContext<K, V> standAloneChild = 
> MultiValuedMapContext.createMapContext(this);
> +        standAloneChild.push();
> +        return standAloneChild;
> +    }
> +
> +    @Override
> +    public int size() {
> +        return keySet().size();
> +    }
> +
> +    @Override
> +    public boolean isEmpty() {
> +        return maps.stream().allMatch(MultivaluedMap::isEmpty);
> +    }
> +
> +    @Override
> +    public boolean containsKey(Object key) {
> +        return maps.stream().anyMatch(map -> map.containsKey(key));
> +    }
> +
> +    @Override
> +    public boolean containsValue(Object value) {
> +        return maps.stream().anyMatch(map -> map.containsValue(value));
> +    }
> +
> +    @Override
> +    public List<V> get(Object key) {
> +        return maps.stream()
> +                   .filter(m -> m.containsKey(key))
> +                   .flatMap(m -> m.get(key).stream())
> +                   .collect(Collectors.toList());
> +    }
> +
> +    @SuppressWarnings("unchecked")
> +    @Override
> +    public V get(String name, Locale locale) {
> +        for (MultivaluedMap<K, V> curMap: maps) {
> +            // only return if the curMap contains the key, rather than 
> checking for null; this allows a null at a lower level to override a value at 
> a higher level
> +            if (curMap.containsKey(name)) {
> +                if (curMap instanceof LocalizedMap<?>) {
> +                    LocalizedMap<V> lmap = UtilGenerics.cast(curMap);
> +                    return lmap.get(name, locale);
> +                }
> +                return curMap.get((K) name).stream().findFirst().get();
> +            }
> +        }
> +        return null;
> +    }
> +
> +    @Override
> +    public List<V> remove(Object key) {
> +        return maps.getFirst().remove(key);
> +    }
> +
> +    @Override
> +    public void clear() {
> +        maps.getFirst().clear();
> +    }
> +
> +    // Convert an iterator to a stream.
> +    private static <U> Stream<U> stream(Iterator<U> it) {
> +        Iterable<U> dmaps = () -> it;
> +        return StreamSupport.stream(dmaps.spliterator(), false);
> +    }
> +
> +    @Override
> +    public Set<K> keySet() {
> +        // Collect in reverse order to let the first maps masks the next 
> ones.
> +        return stream(maps.descendingIterator())
> +                .flatMap(m -> m.keySet().stream())
> +                .collect(Collectors.toCollection(HashSet::new));
> +    }
> +
> +    @Override
> +    public List<V> put(K key, List<V> value) {
> +        return maps.getFirst().put(key, value);
> +    }
> +
> +    @Override
> +    public void putAll(Map<? extends K, ? extends List<V>> m) {
> +        maps.getFirst().putAll(m);
> +    }
> +
> +    @Override
> +    public Set<Entry<K, List<V>>> entrySet() {
> +        // Collect in reverse order to let the first maps masks the next 
> ones.
> +        return stream(maps.descendingIterator())
> +                .flatMap(m -> m.entrySet().stream())
> +                .collect(Collectors.toCollection(HashSet::new));
> +    }
> +
> +    @Override
> +    public void putSingle(K key, V value) {
> +        maps.getFirst().putSingle(key, value);
> +    }
> +
> +    @Override
> +    public void add(K key, V value) {
> +        maps.getFirst().add(key, value);
> +    }
> +
> +    @Override
> +    public V getFirst(K key) {
> +        return maps.stream()
> +                   .map(m -> m.get(key))
> +                   .filter(Objects::nonNull)
> +                   .flatMap(List::stream)
> +                   .findFirst()
> +                   .orElse(null);
> +    }
> +
> +    @Override
> +    public Collection<List<V>> values() {
> +        return maps.stream()
> +                   .flatMap(m -> m.values().stream())
> +                   .collect(Collectors.toList());
> +    }
> +}
>
> 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
>
> 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=1834389&r1=1834388&r2=1834389&view=diff
> ==============================================================================
> --- ofbiz/ofbiz-framework/trunk/framework/webapp/config/WebappUiLabels.xml 
> (original)
> +++ ofbiz/ofbiz-framework/trunk/framework/webapp/config/WebappUiLabels.xml 
> Tue Jun 26 02:00:33 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}] is restricted to be a [{1}] request, 
> cannot be called by [{2}] method.</value>
> +        <value xml:lang="zh">[{0}]必须使用[{1}]æ–¹æ³•è¯·æ±‚ï¼Œä¸ 
> 能用[{2}]方法请求。</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=1834389&r1=1834388&r2=1834389&view=diff
> ==============================================================================
> --- ofbiz/ofbiz-framework/trunk/framework/webapp/dtd/site-conf.xsd (original)
> +++ ofbiz/ofbiz-framework/trunk/framework/webapp/dtd/site-conf.xsd Tue Jun 26 
> 02:00:33 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=1834389&r1=1834388&r2=1834389&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
>  Tue Jun 26 02:00:33 2018
> @@ -23,15 +23,20 @@ import java.io.IOException;
>  import java.net.MalformedURLException;
>  import java.net.URL;
>  import java.util.ArrayList;
> +import java.util.Collection;
>  import java.util.HashMap;
>  import java.util.HashSet;
>  import java.util.LinkedHashMap;
>  import java.util.LinkedList;
>  import java.util.List;
>  import java.util.Map;
> +import java.util.Objects;
>  import java.util.Set;
> +import java.util.stream.Collectors;
>
>  import javax.servlet.ServletContext;
> +import javax.ws.rs.core.MultivaluedHashMap;
> +import javax.ws.rs.core.MultivaluedMap;
>
>  import org.apache.ofbiz.base.component.ComponentConfig.WebappInfo;
>  import org.apache.ofbiz.base.location.FlexibleLocation;
> @@ -48,6 +53,7 @@ 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.w3c.dom.Document;
>  import org.w3c.dom.Element;
>
> @@ -191,7 +197,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 MultivaluedMap<String, RequestMap> requestMapMap = new 
> MultivaluedHashMap<>();
>          private Map<String, ViewMap> viewMapMap = new HashMap<String, 
> ViewMap>();
>
>          public ControllerConfig(URL url) throws WebAppConfigurationException 
> {
> @@ -328,11 +334,57 @@ public class ConfigXMLReader {
>              return null;
>          }
>
> +        // XXX: Temporary wrapper to handle conversion from Map to MultiMap.
>          public Map<String, RequestMap> getRequestMapMap() throws 
> WebAppConfigurationException {
> -            MapContext<String, RequestMap> result = 
> MapContext.getMapContext();
> +            return new Map<String, RequestMap> () {
> +                private MultivaluedMap<String, RequestMap> deleg = 
> getRequestMapMultiMap();
> +                @Override public int size() { return deleg.size(); }
> +                @Override public boolean isEmpty() { return deleg.isEmpty(); 
> }
> +                @Override public boolean containsKey(Object key) {
> +                    return deleg.containsKey(key);
> +                }
> +                @Override public boolean containsValue(Object value) {
> +                    return deleg.containsValue(value);
> +                }
> +                @Override public RequestMap get(Object key) {
> +                    return deleg.getFirst((String) key);
> +                }
> +                @Override public RequestMap put(String key, RequestMap 
> value) {
> +                    RequestMap res = get(key);
> +                    deleg.putSingle(key, value);
> +                    return res;
> +                }
> +                @Override public RequestMap remove(Object key) {
> +                    RequestMap res = get(key);
> +                    deleg.remove(key);
> +                    return res;
> +                }
> +                @Override public void putAll(Map<? extends String, ? extends 
> RequestMap> m) {
> +                    m.forEach(deleg::add);
> +                }
> +                @Override public void clear() { deleg.clear(); }
> +                @Override public Set<String> keySet() { return 
> deleg.keySet(); }
> +                @Override public Collection<RequestMap> values() {
> +                    return deleg.values()
> +                                .stream()
> +                                .map(m -> 
> m.stream().findFirst().orElse(null))
> +                                .filter(Objects::nonNull)
> +                                .collect(Collectors.toList());
> +                }
> +                @Override public Set<Entry<String, RequestMap>> entrySet() {
> +                    return deleg.keySet()
> +                                .stream()
> +                                .collect(Collectors.toMap(k -> k, k -> 
> get(k)))
> +                                .entrySet();
> +                }
> +            };
> +        }
> +
> +        public MultivaluedMap<String, RequestMap> getRequestMapMultiMap() 
> throws WebAppConfigurationException {
> +            MultiValuedMapContext<String, RequestMap> result = 
> MultiValuedMapContext.getMapContext();
>              for (URL includeLocation : includes) {
>                  ControllerConfig controllerConfig = 
> getControllerConfig(includeLocation);
> -                result.push(controllerConfig.getRequestMapMap());
> +                result.push(controllerConfig.getRequestMapMultiMap());
>              }
>              result.push(requestMapMap);
>              return result;
> @@ -487,7 +539,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.putSingle(requestMap.uri, requestMap);
>              }
>          }
>
> @@ -534,6 +586,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;
> @@ -550,6 +603,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=1834389&r1=1834388&r2=1834389&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
>  Tue Jun 26 02:00:33 2018
> @@ -19,6 +19,7 @@
>  package org.apache.ofbiz.webapp.control;
>
>  import java.io.IOException;
> +import java.net.URL;
>  import java.util.Enumeration;
>
>  import javax.servlet.RequestDispatcher;
> @@ -29,11 +30,14 @@ import javax.servlet.http.HttpServlet;
>  import javax.servlet.http.HttpServletRequest;
>  import javax.servlet.http.HttpServletResponse;
>  import javax.servlet.http.HttpSession;
> +import javax.ws.rs.core.MultivaluedMap;
>
>  import org.apache.ofbiz.base.util.Debug;
>  import org.apache.ofbiz.base.util.UtilCodec;
>  import org.apache.ofbiz.base.util.UtilGenerics;
>  import org.apache.ofbiz.base.util.UtilHttp;
> +import org.apache.ofbiz.base.util.UtilMisc;
> +import org.apache.ofbiz.base.util.UtilProperties;
>  import org.apache.ofbiz.base.util.UtilTimer;
>  import org.apache.ofbiz.base.util.UtilValidate;
>  import org.apache.ofbiz.base.util.template.FreeMarkerWorker;
> @@ -45,6 +49,7 @@ import org.apache.ofbiz.entity.transacti
>  import org.apache.ofbiz.entity.transaction.TransactionUtil;
>  import org.apache.ofbiz.security.Security;
>  import org.apache.ofbiz.service.LocalDispatcher;
> +import org.apache.ofbiz.webapp.control.ConfigXMLReader.RequestMap;
>  import org.apache.ofbiz.webapp.stats.ServerHitBin;
>  import org.apache.ofbiz.webapp.stats.VisitHandler;
>  import org.apache.ofbiz.widget.renderer.VisualTheme;
> @@ -58,6 +63,9 @@ import freemarker.ext.servlet.ServletCon
>  public class ControlServlet extends HttpServlet {
>
>      public static final String module = ControlServlet.class.getName();
> +    private static final String METHOD_GET = "GET";
> +    private static final String METHOD_POST = "POST";
> +    private static final String REQUEST_METHOD_ALL = "all";
>
>      public ControlServlet() {
>          super();
> @@ -80,18 +88,20 @@ public class ControlServlet extends Http
>      }
>
>      /**
> -     * @see 
> javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, 
> javax.servlet.http.HttpServletResponse)
> +     * Handle every HTTP methods.
> +     * @see javax.servlet.http.HttpServlet#service
>       */
>      @Override
> -    public void doPost(HttpServletRequest request, HttpServletResponse 
> response) throws ServletException, IOException {
> -        doGet(request, response);
> -    }
> -
> -    /**
> -     * @see 
> javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, 
> javax.servlet.http.HttpServletResponse)
> -     */
> -    @Override
> -    public void doGet(HttpServletRequest request, HttpServletResponse 
> response) throws ServletException, IOException {
> +    protected void service(HttpServletRequest request, HttpServletResponse 
> response)
> +            throws ServletException, IOException {
> +        String method = request.getMethod();
> +        if (!matchRequestMethod(request, response, method)) {
> +            return;
> +        }
> +        if (!method.equals(METHOD_GET) && !method.equals(METHOD_POST)) {
> +            super.service(request, response);
> +            return;
> +        }
>          long requestStartTime = System.currentTimeMillis();
>          RequestHandler requestHandler = this.getRequestHandler();
>          HttpSession session = request.getSession();
> @@ -378,4 +388,29 @@ public class ControlServlet extends Http
>          }
>          if (Debug.verboseOn()) Debug.logVerbose("--- End ServletContext 
> Attributes ---", module);
>      }
> +
> +    private boolean matchRequestMethod(HttpServletRequest request, 
> HttpServletResponse response, String method) {
> +        URL controllerConfigUrl = 
> ConfigXMLReader.getControllerConfigURL(request.getServletContext());
> +        try {
> +            ConfigXMLReader.ControllerConfig controllerConfig = 
> ConfigXMLReader.getControllerConfig(controllerConfigUrl);
> +            MultivaluedMap<String, RequestMap> requestMapMap = 
> controllerConfig.getRequestMapMultiMap();
> +            if (UtilValidate.isEmpty(requestMapMap)) {
> +                return true;
> +            }
> +            String uri = RequestHandler.getRequestUri(request.getPathInfo());
> +            RequestMap requestMap = requestMapMap.getFirst(uri);
> +            if (UtilValidate.isEmpty(requestMap) || 
> UtilValidate.isEmpty(requestMap.method) || 
> REQUEST_METHOD_ALL.equalsIgnoreCase(requestMap.method) || 
> method.equalsIgnoreCase(requestMap.method)) {
> +                return true;
> +            }
> +            String errMsg = UtilProperties.getMessage("WebappUiLabels", 
> "RequestMethodNotMatchConfig", UtilMisc.toList(uri, requestMap.method, 
> method), UtilHttp.getLocale(request));
> +            response.setContentType("text/plain");
> +            response.setCharacterEncoding(request.getCharacterEncoding());
> +            response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
> +            response.getWriter().print(errMsg);
> +            Debug.logError(errMsg, module);
> +        } catch (WebAppConfigurationException | IOException e) {
> +            Debug.logError("Error while loading " + controllerConfigUrl, 
> module);
> +        }
> +        return false;
> +    }
>  }
>
> 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=1834389&r1=1834388&r2=1834389&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
>  Tue Jun 26 02:00:33 2018
> @@ -24,16 +24,21 @@ import java.io.IOException;
>  import java.io.Serializable;
>  import java.net.URL;
>  import java.security.cert.X509Certificate;
> +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 java.util.function.Predicate;
> +import java.util.stream.Stream;
>
>  import javax.servlet.ServletContext;
>  import javax.servlet.http.HttpServletRequest;
>  import javax.servlet.http.HttpServletResponse;
>  import javax.servlet.http.HttpSession;
> +import javax.ws.rs.core.MultivaluedMap;
>
>  import org.apache.ofbiz.base.util.Debug;
>  import org.apache.ofbiz.base.util.SSLUtil;
> @@ -51,6 +56,7 @@ import org.apache.ofbiz.entity.GenericVa
>  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;
> @@ -110,6 +116,29 @@ public class RequestHandler {
>          return null;
>      }
>
> +    /**
> +     * Find a request map in {@code reqMaps} matching {@code req}.
> +     * Otherwise fall back to the one matching {@code defaultReq}.
> +     *
> +     * @param reqMaps The dictionary associating URI to request maps
> +     * @param req The HTTP request to match
> +     * @param defaultReq the default request which serves as a fallback.
> +     * @return an request map {@code Optional}
> +     */
> +    static Optional<RequestMap> resolveURI(MultivaluedMap<String, 
> RequestMap> reqMaps,
> +            HttpServletRequest req, String defaultReq) {
> +        String path = getRequestUri(req.getPathInfo());
> +        List<RequestMap> rmaps = reqMaps.get(path);
> +        if (rmaps == null && defaultReq != null) {
> +            rmaps = reqMaps.get(defaultReq);
> +        }
> +        List<RequestMap> frmaps = (rmaps != null) ? rmaps : 
> Collections.emptyList();
> +        return Stream.of(req.getMethod(), "all", "")
> +                .map(verb -> (Predicate<String>) verb::equalsIgnoreCase)
> +                .flatMap(p -> frmaps.stream().filter(m -> p.test(m.method)))
> +                .findFirst();
> +    }
> +
>      public void doRequest(HttpServletRequest request, HttpServletResponse 
> response, String requestUri) throws RequestHandlerException, 
> RequestHandlerExceptionAllowExternalRequests {
>          HttpSession session = request.getSession();
>          Delegator delegator = (Delegator) request.getAttribute("delegator");
> @@ -127,10 +156,10 @@ public class RequestHandler {
>
>          // 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;
> +        MultivaluedMap<String, ConfigXMLReader.RequestMap> requestMapMap = 
> null;
>          String statusCodeString = null;
>          try {
> -            requestMapMap = controllerConfig.getRequestMapMap();
> +            requestMapMap = controllerConfig.getRequestMapMultiMap();
>              statusCodeString = controllerConfig.getStatusCode();
>          } catch (WebAppConfigurationException e) {
>              Debug.logError(e, "Exception thrown while parsing controller.xml 
> file: ", module);
> @@ -143,6 +172,15 @@ public class RequestHandler {
>          // workaround if we are in the root webapp
>          String cname = UtilHttp.getApplicationName(request);
>
> +        // Set the fallback default request.
> +        String defaultRequest = null;
> +        try {
> +            defaultRequest = controllerConfig.getDefaultRequest();
> +        } catch (WebAppConfigurationException e) {
> +            Debug.logError(e, "Exception thrown while parsing controller.xml 
> file: ", module);
> +            throw new RequestHandlerException(e);
> +        }
> +
>          // Grab data from request object to process
>          String defaultRequestUri = 
> RequestHandler.getRequestUri(request.getPathInfo());
>          if (request.getAttribute("targetRequestUri") == null) {
> @@ -156,34 +194,16 @@ 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);
> -            }
> -        }
> +        RequestMap requestMap =
> +                resolveURI(requestMapMap, request, 
> defaultRequest).orElse(null);
>
>          // 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);
> -                    }
> +                ConfigXMLReader.ViewMap viewMap =
> +                        
> controllerConfig.getViewMapMap().get(overrideViewUri);
> +                if (viewMap == null && defaultRequest != null) {
> +                    requestMap = requestMapMap.getFirst(defaultRequest);
>                  }
>              } catch (WebAppConfigurationException e) {
>                  Debug.logError(e, "Exception thrown while parsing 
> controller.xml file: ", module);
> @@ -196,7 +216,7 @@ public class RequestHandler {
>          if (requestMap == null) {
>              if (throwRequestHandlerExceptionOnMissingLocalRequest) throw new 
> RequestHandlerException(requestMissingErrorMessage);
>              else throw new RequestHandlerExceptionAllowExternalRequests();
> -         }
> +        }
>
>          String eventReturn = null;
>          if (requestMap.metrics != null && requestMap.metrics.getThreshold() 
> != 0.0 && requestMap.metrics.getTotalEvents() > 3 && 
> requestMap.metrics.getThreshold() < requestMap.metrics.getServiceRate()) {
> @@ -204,13 +224,11 @@ public class RequestHandler {
>          }
>          ConfigXMLReader.RequestMap originalRequestMap = requestMap; // Save 
> this so we can update the correct performance metrics.
>
> -
>          boolean interruptRequest = false;
> -
>          // Check for chained request.
>          if (chain != null) {
>              String chainRequestUri = RequestHandler.getRequestUri(chain);
> -            requestMap = requestMapMap.get(chainRequestUri);
> +            requestMap = requestMapMap.getFirst(chainRequestUri);
>              if (requestMap == null) {
>                  throw new RequestHandlerException("Unknown chained request 
> [" + chainRequestUri + "]; this request does not exist");
>              }
> @@ -234,18 +252,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 (defaultRequest == null || 
> !requestMapMap.getFirst(defaultRequest).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 = requestMapMap.getFirst(defaultRequest);
>                  }
>              }
>              // Check if we SHOULD be secure and are not.
> @@ -409,7 +420,8 @@ 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 =
> +                    requestMapMap.getFirst("checkLogin").event;
>              String checkLoginReturnString = null;
>
>              try {
> @@ -422,9 +434,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 = requestMapMap.getFirst("checkLogin");
>                  } else {
> -                    requestMap = requestMapMap.get("ajaxCheckLogin");
> +                    requestMap = requestMapMap.getFirst("ajaxCheckLogin");
>                  }
>              }
>          }
>
> 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=1834389&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
>  Tue Jun 26 02:00:33 2018
> @@ -0,0 +1,156 @@
> +/*******************************************************************************
> + * 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.mockito.Mockito.mock;
> +import static org.mockito.Mockito.when;
> +import static org.junit.Assert.assertFalse;
> +import static org.junit.Assert.assertSame;
> +import static org.junit.Assert.assertTrue;
> +
> +import javax.servlet.http.HttpServletRequest;
> +import javax.ws.rs.core.MultivaluedHashMap;
> +import javax.ws.rs.core.MultivaluedMap;
> +
> +import org.apache.ofbiz.webapp.control.ConfigXMLReader.RequestMap;
> +import org.junit.Before;
> +import org.junit.Test;
> +import org.w3c.dom.Element;
> +
> +public class RequestHandlerTests {
> +    public static class ResolveURITests {
> +        private MultivaluedMap<String,RequestMap> reqMaps;
> +        private HttpServletRequest req;
> +        private Element dummyElement;
> +
> +        @Before
> +        public void setUp() {
> +            reqMaps = new MultivaluedHashMap<>();
> +            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");
> +            assertSame(foo, RequestHandler.resolveURI(reqMaps, req, 
> null).get());
> +        }
> +
> +        @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);
> +
> +            assertFalse(RequestHandler.resolveURI(reqMaps, req, 
> null).isPresent());
> +            reqMaps.putSingle("foo", foo);
> +            assertTrue(RequestHandler.resolveURI(reqMaps, req, 
> null).isPresent());
> +        }
> +
> +        @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");
> +            assertSame(foo, RequestHandler.resolveURI(reqMaps, req, 
> null).get());
> +
> +            when(req.getPathInfo()).thenReturn("/bar");
> +            when(req.getMethod()).thenReturn("PUT");
> +            assertSame(bar, RequestHandler.resolveURI(reqMaps, req, 
> null).get());
> +        }
> +
> +        @Test
> +        public void resolveURICatchAll() throws RequestHandlerException {
> +            when(req.getPathInfo()).thenReturn("/foo");
> +            RequestMap foo = new RequestMap(dummyElement);
> +            when(req.getMethod()).thenReturn("get");
> +            assertFalse(RequestHandler.resolveURI(reqMaps, req, 
> null).isPresent());
> +            when(req.getMethod()).thenReturn("post");
> +            assertFalse(RequestHandler.resolveURI(reqMaps, req, 
> null).isPresent());
> +            when(req.getMethod()).thenReturn("put");
> +            assertFalse(RequestHandler.resolveURI(reqMaps, req, 
> null).isPresent());
> +            when(req.getMethod()).thenReturn("delete");
> +            assertFalse(RequestHandler.resolveURI(reqMaps, req, 
> null).isPresent());
> +
> +            reqMaps.putSingle("foo", foo);
> +            when(req.getMethod()).thenReturn("get");
> +            assertTrue(RequestHandler.resolveURI(reqMaps, req, 
> null).isPresent());
> +            when(req.getMethod()).thenReturn("post");
> +            assertTrue(RequestHandler.resolveURI(reqMaps, req, 
> null).isPresent());
> +            when(req.getMethod()).thenReturn("put");
> +            assertTrue(RequestHandler.resolveURI(reqMaps, req, 
> null).isPresent());
> +            when(req.getMethod()).thenReturn("delete");
> +            assertTrue(RequestHandler.resolveURI(reqMaps, req, 
> null).isPresent());
> +        }
> +
> +        @Test
> +        public void resolveURISegregate() throws RequestHandlerException {
> +            when(dummyElement.getAttribute("method")).thenReturn("put");
> +            RequestMap fooPut = new RequestMap(dummyElement);
> +            when(dummyElement.getAttribute("method")).thenReturn("all");
> +            RequestMap fooAll = new RequestMap(dummyElement);
> +            reqMaps.putSingle("foo", fooAll);
> +            reqMaps.add("foo", fooPut);
> +
> +            when(req.getPathInfo()).thenReturn("/foo");
> +            when(req.getMethod()).thenReturn("put");
> +            assertSame(fooPut, RequestHandler.resolveURI(reqMaps, req, 
> null).get());
> +            when(req.getMethod()).thenReturn("get");
> +            assertSame(fooAll, RequestHandler.resolveURI(reqMaps, req, 
> null).get());
> +        }
> +
> +        @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("/bar");
> +            when(req.getMethod()).thenReturn("get");
> +            assertSame(bar, RequestHandler.resolveURI(reqMaps, req, 
> "bar").get());
> +        }
> +
> +        @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");
> +            when(req.getMethod()).thenReturn("get");
> +            assertFalse(RequestHandler.resolveURI(reqMaps, req, 
> null).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
>
>

Reply via email to