Hello,
Following revert of rev 1834917, I want to “properly” propose adding a
‘method’ attribute to ‘request-map’ elements.
I am currently working on adding REST based Web APIs to OFBiz [1]. In
order to do that, it is important for the HTTP controller to handle
various HTTP methods differently, meaning allowing a different request
handler to be called depending on the method of the HTTP request.
Syntactically I am proposing the following:
<request-map uri="examples" method="get">
<security https="true" auth="true"/>
<event type="java" path="ExamplesHandlers" invoke="getExamples"/>
<response name="success" type="view" value="..."/>
<response name="error" type="view" value="..."/>
</request-map>
<request-map uri="examples" method="post">
<security https="true" auth="true"/>
<event type="java" path="ExamplesHandlers" invoke="postExamples"/>
<response name="success" type="view" value="..."/>
<response name="error" type="view" value="..."/>
</request-map>
Here ‘method’ is an optional attribute which defaults to "all" and/or
"". Currently the patch supports both but I would be in favour of
handling only one for the sake of simplicity. The semantic of that
default is that it mimics the current behavior, meaning it handles every
methods.
Attached you will find a serie of 3 patches (meant to be applied in
order) implementing the described change:
- The first one handles the parsing of the XML (the change in the xsd
file has not been reverted).
- The second is a simple refactoring to agglomerate the read of the XML
file in one place. This is done to ease the implementation and tests
in the third patch.
- The third patch implements the segregation of ‘request-map’ elements
based on the actual request method. In order to associate a request
to an event handler we now need to dispatch both on the URI and on the
method. To achieve that, I have decided to keep using the URI as a
key but associate it to a List of ‘request-map’ (one for each method)
instead of a single one. In term of implementation a ‘MultivaluedMap’
has been used. The list of candidates for a particular URI is then
filtered to keep only the best matching one. The ‘request-map’ Map
concrete type was a MapContext which is a stack of Maps (to handle
includes), The ‘MultivaluedMapContext’ implementation is an adaptation
of MapContext to MultivaluedMaps. Since
‘ControllerConfig::getRequestMapMap’ is used at multiple places I have
kept it temporarily and replaced its code with an adapter to the new
‘ControllerConfig::getRequestMapMultiMap’ method.
diff --git framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java
index a14eace3c2..6c02513ed5 100644
--- framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java
+++ framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java
@@ -450,6 +450,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 +467,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"));
--
2.17.1
diff --git framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java
index 067587f169..b8f70c81dd 100644
--- framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java
+++ framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java
@@ -68,12 +68,74 @@ import org.apache.ofbiz.widget.model.ThemeFactory;
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 Controller ctrl;
+
+ static class Controller {
+ private final Map<String, ConfigXMLReader.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;
+
+ Controller(ConfigXMLReader.ControllerConfig ccfg) throws WebAppConfigurationException {
+ preprocessorEventList = ccfg.getPreprocessorEventList();
+ postprocessorEventList = ccfg.getPostprocessorEventList();
+ requestMapMap = ccfg.getRequestMapMap();
+ viewMapMap = ccfg.getViewMapMap();
+ defaultRequest = ccfg.getDefaultRequest();
+ firstVisitEventList = ccfg.getFirstVisitEventList();
+ protectView = ccfg.getProtectView();
+
+ String status = ccfg.getStatusCode();
+ statusCodeString = UtilValidate.isEmpty(status) ? defaultStatusCodeString : status;
+ }
+
+ public Map<String, ConfigXMLReader.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_");
@@ -129,16 +191,14 @@ public class RequestHandler {
ConfigXMLReader.ControllerConfig controllerConfig = this.getControllerConfig();
Map<String, ConfigXMLReader.RequestMap> requestMapMap = null;
String statusCodeString = null;
+
+ // Parse controller config.
try {
- requestMapMap = controllerConfig.getRequestMapMap();
- statusCodeString = controllerConfig.getStatusCode();
+ ctrl = new Controller(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);
@@ -158,36 +218,18 @@ public class RequestHandler {
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);
+ requestMap = ctrl.getRequestMapMap().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);
- }
+ if (requestMap == null && ctrl.getDefaultRequest() != null) {
+ requestMap = ctrl.getRequestMapMap().get(ctrl.getDefaultRequest());
}
// 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);
+ ConfigXMLReader.ViewMap viewMap = ctrl.getViewMapMap().get(overrideViewUri);
+ if (viewMap == null && ctrl.getDefaultRequest() != null) {
+ requestMap = ctrl.getRequestMapMap().get(ctrl.getDefaultRequest());
}
}
@@ -210,7 +252,7 @@ public class RequestHandler {
// Check for chained request.
if (chain != null) {
String chainRequestUri = RequestHandler.getRequestUri(chain);
- requestMap = requestMapMap.get(chainRequestUri);
+ requestMap = ctrl.getRequestMapMap().get(chainRequestUri);
if (requestMap == null) {
throw new RequestHandlerException("Unknown chained request [" + chainRequestUri + "]; this request does not exist");
}
@@ -234,18 +276,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 (ctrl.getDefaultRequest() == null || !ctrl.getRequestMapMap().get(ctrl.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 = ctrl.getRequestMapMap().get(ctrl.getDefaultRequest());
}
}
// Check if we SHOULD be secure and are not.
@@ -288,7 +323,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, ctrl.getStatusCodeString());
return;
}
}
@@ -333,63 +368,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: ctrl.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: ctrl.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 (ctrl.getProtectView() != null) {
+ overrideViewUri = ctrl.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 +433,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 = ctrl.getRequestMapMap().get("checkLogin").event;
String checkLoginReturnString = null;
try {
@@ -422,9 +446,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 = ctrl.getRequestMapMap().get("checkLogin");
} else {
- requestMap = requestMapMap.get("ajaxCheckLogin");
+ requestMap = ctrl.getRequestMapMap().get("ajaxCheckLogin");
}
}
}
@@ -556,7 +580,7 @@ public class RequestHandler {
redirectTarget += "?" + queryString;
}
- callRedirect(makeLink(request, response, redirectTarget), response, request, statusCodeString);
+ callRedirect(makeLink(request, response, redirectTarget), response, request, ctrl.getStatusCodeString());
return;
}
}
@@ -603,41 +627,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: ctrl.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;
-
+ ctrl.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, ctrl.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, ctrl.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, ctrl.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, ctrl.getStatusCodeString());
} else if ("view".equals(nextRequestResponse.type)) {
if (Debug.verboseOn()) Debug.logVerbose("[RequestHandler.doRequest]: Response is a view." + showSessionId(request), module);
--
2.17.1
diff --git framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java
new file mode 100644
index 0000000000..d31bfba4a6
--- /dev/null
+++ framework/base/src/main/java/org/apache/ofbiz/base/util/collections/MultivaluedMapContext.java
@@ -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;
+
+/**
+ * MultivaluedMap 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());
+ }
+}
diff --git framework/webapp/config/WebappUiLabels.xml framework/webapp/config/WebappUiLabels.xml
index 474facf187..bcd89a3862 100644
--- framework/webapp/config/WebappUiLabels.xml
+++ framework/webapp/config/WebappUiLabels.xml
@@ -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>
diff --git framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java
index 6c02513ed5..aa29c247f2 100644
--- framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java
+++ framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ConfigXMLReader.java
@@ -23,16 +23,21 @@ 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.function.Function;
+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;
@@ -49,6 +54,7 @@ import org.apache.ofbiz.base.util.UtilValidate;
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;
@@ -192,7 +198,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 {
@@ -276,11 +282,49 @@ public class ConfigXMLReader {
return getIncludes(ccfg -> ccfg.protectView);
}
+ // 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;
@@ -403,7 +447,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);
}
}
diff --git framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlServlet.java framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlServlet.java
index d552473234..4d365ceab2 100644
--- framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlServlet.java
+++ framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlServlet.java
@@ -211,6 +211,12 @@ public class ControlServlet extends HttpServlet {
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) {
diff --git framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java
new file mode 100644
index 0000000000..8e545e27c3
--- /dev/null
+++ framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/MethodNotAllowedException.java
@@ -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);
+ }
+}
diff --git framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java
index b8f70c81dd..ed3e2b6038 100644
--- framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java
+++ framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java
@@ -24,16 +24,21 @@ 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;
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.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;
@@ -78,7 +84,7 @@ public class RequestHandler {
private Controller ctrl;
static class Controller {
- private final Map<String, ConfigXMLReader.RequestMap> requestMapMap;
+ private final MultivaluedMap<String, RequestMap> requestMapMap;
private final Map<String, ConfigXMLReader.ViewMap> viewMapMap;
private String statusCodeString;
private final String defaultRequest;
@@ -90,7 +96,7 @@ public class RequestHandler {
Controller(ConfigXMLReader.ControllerConfig ccfg) throws WebAppConfigurationException {
preprocessorEventList = ccfg.getPreprocessorEventList();
postprocessorEventList = ccfg.getPostprocessorEventList();
- requestMapMap = ccfg.getRequestMapMap();
+ requestMapMap = ccfg.getRequestMapMultiMap();
viewMapMap = ccfg.getViewMapMap();
defaultRequest = ccfg.getDefaultRequest();
firstVisitEventList = ccfg.getFirstVisitEventList();
@@ -100,7 +106,7 @@ public class RequestHandler {
statusCodeString = UtilValidate.isEmpty(status) ? defaultStatusCodeString : status;
}
- public Map<String, ConfigXMLReader.RequestMap> getRequestMapMap() {
+ public MultivaluedMap<String, RequestMap> getRequestMapMap() {
return requestMapMap;
}
@@ -172,6 +178,56 @@ public class RequestHandler {
return null;
}
+ /**
+ * Find a collection of request maps in {@code ctrl} matching {@code req}.
+ * Otherwise fall back to matching the {@code defaultReq} field.
+ *
+ * @param ctrl 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(Controller ctrl, HttpServletRequest req) {
+ Map<String, List<RequestMap>> requestMapMap = ctrl.getRequestMapMap();
+ Map<String, ConfigXMLReader.ViewMap> viewMapMap = ctrl.getViewMapMap();
+ String defaultRequest = ctrl.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");
@@ -187,11 +243,6 @@ 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 {
ctrl = new Controller(getControllerConfig());
@@ -213,32 +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.";
- String requestMissingErrorMessage = "Unknown request [" + defaultRequestUri + "]; this request does not exist or cannot be called directly.";
- ConfigXMLReader.RequestMap requestMap = null;
- if (defaultRequestUri != null) {
- requestMap = ctrl.getRequestMapMap().get(defaultRequestUri);
- }
- // check for default request
- if (requestMap == null && ctrl.getDefaultRequest() != null) {
- requestMap = ctrl.getRequestMapMap().get(ctrl.getDefaultRequest());
- }
+ String path = request.getPathInfo();
+ String requestUri = getRequestUri(path);
+ String overrideViewUri = getOverrideViewUri(path);
- // check for override view
- if (overrideViewUri != null) {
- ConfigXMLReader.ViewMap viewMap = ctrl.getViewMapMap().get(overrideViewUri);
- if (viewMap == null && ctrl.getDefaultRequest() != null) {
- requestMap = ctrl.getRequestMapMap().get(ctrl.getDefaultRequest());
+ Collection<RequestMap> rmaps = resolveURI(ctrl, 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()) {
@@ -246,13 +294,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 = ctrl.getRequestMapMap().get(chainRequestUri);
+ requestMap = ctrl.getRequestMapMap().getFirst(chainRequestUri);
if (requestMap == null) {
throw new RequestHandlerException("Unknown chained request [" + chainRequestUri + "]; this request does not exist");
}
@@ -276,11 +322,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) {
- if (ctrl.getDefaultRequest() == null || !ctrl.getRequestMapMap().get(ctrl.getDefaultRequest()).securityDirectRequest) {
+ if (ctrl.getDefaultRequest() == null || !ctrl.getRequestMapMap().getFirst(ctrl.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 = ctrl.getRequestMapMap().get(ctrl.getDefaultRequest());
+ requestMap = ctrl.getRequestMapMap().getFirst(ctrl.getDefaultRequest());
}
}
// Check if we SHOULD be secure and are not.
@@ -433,7 +479,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 = ctrl.getRequestMapMap().get("checkLogin").event;
+ ConfigXMLReader.Event checkLoginEvent = ctrl.getRequestMapMap().getFirst("checkLogin").event;
String checkLoginReturnString = null;
try {
@@ -446,9 +492,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 = ctrl.getRequestMapMap().get("checkLogin");
+ requestMap = ctrl.getRequestMapMap().getFirst("checkLogin");
} else {
- requestMap = ctrl.getRequestMapMap().get("ajaxCheckLogin");
+ requestMap = ctrl.getRequestMapMap().getFirst("ajaxCheckLogin");
}
}
}
diff --git framework/webapp/src/test/java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java framework/webapp/src/test/java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java
new file mode 100644
index 0000000000..e86dd3d41b
--- /dev/null
+++ framework/webapp/src/test/java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java
@@ -0,0 +1,180 @@
+/*******************************************************************************
+ * 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 java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.hamcrest.CoreMatchers.*;
+
+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.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 MultivaluedMap<String,RequestMap> reqMaps;
+ private Map<String, ViewMap> viewMaps;
+ private HttpServletRequest req;
+ private Element dummyElement;
+ private RequestHandler.Controller ctrl;
+
+ @Before
+ public void setUp() {
+ ctrl = mock(RequestHandler.Controller.class);
+ reqMaps = new MultivaluedHashMap<>();
+ viewMaps = new HashMap<>();
+ when(ctrl.getDefaultRequest()).thenReturn(null);
+ when(ctrl.getRequestMapMap()).thenReturn(reqMaps);
+ when(ctrl.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(ctrl, req),
+ both(hasItem(foo)).and(not(hasItem(bar))));
+ assertThat(RequestHandler.resolveURI(ctrl, 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(ctrl, req).isEmpty());
+ reqMaps.putSingle("foo", foo);
+ assertFalse(RequestHandler.resolveURI(ctrl, 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(ctrl, req), hasItem(foo));
+
+ when(req.getPathInfo()).thenReturn("/bar");
+ when(req.getMethod()).thenReturn("PUT");
+ assertThat(RequestHandler.resolveURI(ctrl, 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(ctrl.getDefaultRequest()).thenReturn("bar");
+ assertThat(RequestHandler.resolveURI(ctrl, 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(ctrl.getDefaultRequest()).thenReturn("bar");
+ assertThat(RequestHandler.resolveURI(ctrl, 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(ctrl, req).isEmpty());
+ }
+
+ @Test
+ public void resolveMethodCatchAll() throws RequestHandlerException {
+ when(req.getPathInfo()).thenReturn("/foo");
+ RequestMap foo = new RequestMap(dummyElement);
+ Collection<RequestMap> rmaps = RequestHandler.resolveURI(ctrl, req);
+ assertFalse(RequestHandler.resolveMethod("get", rmaps).isPresent());
+ assertFalse(RequestHandler.resolveMethod("post", rmaps).isPresent());
+ assertFalse(RequestHandler.resolveMethod("put", rmaps).isPresent());
+ assertFalse(RequestHandler.resolveMethod("delete", rmaps).isPresent());
+
+ reqMaps.putSingle("foo", foo);
+ Collection<RequestMap> rmaps2 = RequestHandler.resolveURI(ctrl, req);
+ assertTrue(RequestHandler.resolveMethod("get", rmaps2).isPresent());
+ assertTrue(RequestHandler.resolveMethod("post", rmaps2).isPresent());
+ assertTrue(RequestHandler.resolveMethod("put", rmaps2).isPresent());
+ assertTrue(RequestHandler.resolveMethod("delete", rmaps2).isPresent());
+ }
+
+ @Test
+ public void resolveMethodBasic() 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");
+ Collection<RequestMap> rmaps = RequestHandler.resolveURI(ctrl, req);
+ assertThat(rmaps, hasItems(fooPut, fooAll));
+ assertThat(RequestHandler.resolveMethod("put", rmaps).get(), is(fooPut));
+ assertThat(RequestHandler.resolveMethod("get", rmaps).get(), is(fooAll));
+ }
+ }
+}
--
2.17.1
Comments, questions and/or reviews welcome.
[1] https://issues.apache.org/jira/browse/OFBIZ-4274
--
Mathieu Lirzin
GPG: F2A3 8D7E EB2B 6640 5761 070D 0ADE E100 9460 4D37