Skip to content

Commit

Permalink
Implemented: Handle URI templates in request maps
Browse files Browse the repository at this point in the history
(OFBIZ-11007)

It is now possible to use segmented paths by using URI templates like
‘/foo/bar/{baz}’ in the ‘uri’ attribute of <request-map> elements.

Thanks: Artemiy Rozovyk for your contribution.


git-svn-id: https://svn.apache.org/repos/asf/ofbiz/ofbiz-framework/trunk@1868963 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
mthl committed Oct 25, 2019
1 parent b9d9f61 commit b1aaad3
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 23 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Expand Up @@ -307,7 +307,7 @@ checkstyle {
// the sum of errors that were present before introducing the
// ‘checkstyle’ tool present in the framework and in the official
// plugins.
maxErrors = 37915
maxErrors = 37880
// Currently there are a lot of errors so we need to temporarily
// hide them to avoid polluting the terminal output.
showViolations = false
Expand Down
Expand Up @@ -77,15 +77,35 @@ public void init() throws ServletException {
}

@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
handle(req, resp);
}

@Override
public void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
handle(req, resp);
}

@Override
public void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
handle(req, resp);
}

@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
handle(req, resp);
}

/**
* @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
* Invokes {@link RequestHandler#doRequest} with error handling.
*
* @param req an {@link HttpServletRequest} object that contains the request
* the client has made of the servlet
* @param resp an {@link HttpServletResponse} object that contains the response
* the servlet sends to the client
* @throws IOException if an output error is detected when trying to write on the response.
*/
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException {
long requestStartTime = System.currentTimeMillis();
HttpSession session = request.getSession();

Expand Down
Expand Up @@ -41,7 +41,9 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.ws.rs.core.MultivaluedHashMap;

import org.apache.cxf.jaxrs.model.URITemplate;
import org.apache.ofbiz.base.location.FlexibleLocation;
import org.apache.ofbiz.base.util.Debug;
import org.apache.ofbiz.base.util.SSLUtil;
Expand Down Expand Up @@ -183,7 +185,7 @@ public ConfigXMLReader.ControllerConfig getControllerConfig() {
}

/**
* Find a collection of request maps in {@code ccfg} matching {@code req}.
* Finds a collection of request maps in {@code ccfg} matching {@code req}.
* Otherwise fall back to matching the {@code defaultReq} field in {@code ccfg}.
*
* @param ccfg The controller containing the current configuration
Expand All @@ -192,20 +194,23 @@ public ConfigXMLReader.ControllerConfig getControllerConfig() {
*/
static Collection<RequestMap> resolveURI(ControllerConfig ccfg, HttpServletRequest req) {
Map<String, List<RequestMap>> requestMapMap = ccfg.getRequestMapMap();
Map<String, ConfigXMLReader.ViewMap> viewMapMap = ccfg.getViewMapMap();
String defaultRequest = ccfg.getDefaultRequest();
String path = req.getPathInfo();
String requestUri = getRequestUri(path);
String viewUri = getOverrideViewUri(path);
Collection<RequestMap> rmaps;
if (requestMapMap.containsKey(requestUri)
// Ensure that overridden view exists.
&& (viewUri == null || viewMapMap.containsKey(viewUri))) {
rmaps = requestMapMap.get(requestUri);
} else if (defaultRequest != null) {
rmaps = requestMapMap.get(defaultRequest);
} else {
rmaps = null;
Collection<RequestMap> rmaps = resolveTemplateURI(requestMapMap, req);
if (rmaps.isEmpty()) {
Map<String, ConfigXMLReader.ViewMap> viewMapMap = ccfg.getViewMapMap();
String defaultRequest = ccfg.getDefaultRequest();
String path = req.getPathInfo();
String requestUri = getRequestUri(path);
String overrideViewUri = getOverrideViewUri(path);
if (requestMapMap.containsKey(requestUri)
// Ensure that overridden view exists.
&& (overrideViewUri == null || viewMapMap.containsKey(overrideViewUri))) {
rmaps = requestMapMap.get(requestUri);
req.setAttribute("overriddenView", overrideViewUri);
} else if (defaultRequest != null) {
rmaps = requestMapMap.get(defaultRequest);
} else {
rmaps = null;
}
}
return rmaps != null ? rmaps : Collections.emptyList();
}
Expand Down Expand Up @@ -234,6 +239,33 @@ static Optional<RequestMap> resolveMethod(String method, Collection<RequestMap>
}
}

/**
* Finds the request maps matching a segmented path.
*
* <p>A segmented path can match request maps where the {@code uri} attribute
* contains an URI template like in the {@code foo/bar/{baz}} example.
*
* @param rMapMap the map associating URIs to a list of request maps corresponding to different HTTP methods
* @param request the HTTP request to match
* @return a collection of request maps which might be empty but not {@code null}
*/
private static Collection<RequestMap> resolveTemplateURI(Map<String, List<RequestMap>> rMapMap,
HttpServletRequest request) {
// Retrieve the request path without the leading '/' character.
String path = request.getPathInfo().substring(1);
MultivaluedHashMap<String, String> vars = new MultivaluedHashMap<>();
for (Map.Entry<String, List<RequestMap>> entry : rMapMap.entrySet()) {
URITemplate uriTemplate = URITemplate.createExactTemplate(entry.getKey());
// Check if current path the URI template exactly.
if (uriTemplate.match(path, vars) && vars.getFirst("FINAL_MATCH_GROUP").equals("/")) {
// Set attributes from template variables to be used in context.
uriTemplate.getVariables().forEach(var -> request.setAttribute(var, vars.getFirst(var)));
return entry.getValue();
}
}
return Collections.emptyList();
}

public void doRequest(HttpServletRequest request, HttpServletResponse response, String chain,
GenericValue userLogin, Delegator delegator) throws RequestHandlerException, RequestHandlerExceptionAllowExternalRequests {

Expand Down Expand Up @@ -269,7 +301,6 @@ public void doRequest(HttpServletRequest request, HttpServletResponse response,

String path = request.getPathInfo();
String requestUri = getRequestUri(path);
String overrideViewUri = getOverrideViewUri(path);

Collection<RequestMap> rmaps = resolveURI(ccfg, request);
if (rmaps.isEmpty()) {
Expand All @@ -287,8 +318,11 @@ public void doRequest(HttpServletRequest request, HttpServletResponse response,
throw new RequestHandlerExceptionAllowExternalRequests();
}
}
// The "overriddenView" attribute is set by resolveURI when necessary.
String overrideViewUri = (String) request.getAttribute("overriddenView");

String method = request.getMethod();
String restMethod = request.getParameter("restMethod");
String method = (restMethod != null) ? restMethod : request.getMethod();
RequestMap requestMap = resolveMethod(method, rmaps).orElseThrow(() -> {
String msg = UtilProperties.getMessage("WebappUiLabels", "RequestMethodNotMatchConfig",
UtilMisc.toList(requestUri, method), UtilHttp.getLocale(request));
Expand Down
Expand Up @@ -28,6 +28,8 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.times;

import java.security.cert.X509Certificate;
import java.util.ArrayList;
Expand Down Expand Up @@ -121,6 +123,65 @@ public void resolveURIDefault() throws Exception {
assertThat(RequestHandler.resolveURI(ccfg, req), hasItem(bar));
}

/**
* Checks that segmented URIs are resolved and does not
* conflict with overrideViewUri mechanism
*/
@Test
public void resolveTemplateURISergmented() {
RequestMap foo = new RequestMap(dummyElement);
RequestMap bar = new RequestMap(dummyElement);
RequestMap baz = new RequestMap(dummyElement);
reqMaps.putSingle("baz/foo", foo);
reqMaps.putSingle("bar", bar);
reqMaps.putSingle("baz", baz);

viewMaps.put("foo", new ViewMap(dummyElement));

when(req.getPathInfo()).thenReturn("/baz/foo");
when(ccfg.getDefaultRequest()).thenReturn("bar");
assertThat(RequestHandler.resolveURI(ccfg, req), both(hasItem(foo)).and(not(hasItem(baz))));
}

@Test
public void resolveTemplateURIWithVariables() {
RequestMap foo = new RequestMap(dummyElement);
RequestMap bar = new RequestMap(dummyElement);
reqMaps.putSingle("foo/bar/{var1}/baz/{var2}", foo);
reqMaps.putSingle("bar", bar);

when(req.getPathInfo()).thenReturn("/foo/bar/toto/baz/titi");

assertThat(RequestHandler.resolveURI(ccfg, req), hasItem(foo));
verify(req, times(1)).setAttribute("var1", "toto");
verify(req, times(1)).setAttribute("var2", "titi");
}

/**
* Currently it is up to the developer to manage URIs with custom
* variables that are conflicting with other routes by excluding
* them using regular expressions as shown in the test.
*/
@Test
public void resolveTemplateURIConflictingRoutes() {
RequestMap foo = new RequestMap(dummyElement);
RequestMap bar = new RequestMap(dummyElement);
RequestMap baz = new RequestMap(dummyElement);
reqMaps.putSingle("foo/bar", foo);
reqMaps.putSingle("foo/qux", bar);
reqMaps.putSingle("foo/{var:(?!(bar)|(qux)).*}", baz);

when(req.getPathInfo()).thenReturn("/foo/bar");
assertThat(RequestHandler.resolveURI(ccfg, req), hasItem(foo));

when(req.getPathInfo()).thenReturn("/foo/qux");
assertThat(RequestHandler.resolveURI(ccfg, req), hasItem(bar));

when(req.getPathInfo()).thenReturn("/foo/toto");
assertThat(RequestHandler.resolveURI(ccfg, req), hasItem(baz));
verify(req, times(1)).setAttribute("var", "toto");
}

@Test
public void resolveURIBasicOverrideView() throws Exception {
RequestMap foo = new RequestMap(dummyElement);
Expand Down

0 comments on commit b1aaad3

Please sign in to comment.