Skip to content

Commit b1aaad3

Browse files
committed
Implemented: Handle URI templates in request maps
(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
1 parent b9d9f61 commit b1aaad3

File tree

4 files changed

+138
-23
lines changed

4 files changed

+138
-23
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ checkstyle {
307307
// the sum of errors that were present before introducing the
308308
// ‘checkstyle’ tool present in the framework and in the official
309309
// plugins.
310-
maxErrors = 37915
310+
maxErrors = 37880
311311
// Currently there are a lot of errors so we need to temporarily
312312
// hide them to avoid polluting the terminal output.
313313
showViolations = false

framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/ControlServlet.java

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,35 @@ public void init() throws ServletException {
7777
}
7878

7979
@Override
80-
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
81-
doGet(request, response);
80+
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
81+
handle(req, resp);
82+
}
83+
84+
@Override
85+
public void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
86+
handle(req, resp);
87+
}
88+
89+
@Override
90+
public void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
91+
handle(req, resp);
92+
}
93+
94+
@Override
95+
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
96+
handle(req, resp);
8297
}
8398

8499
/**
85-
* @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
100+
* Invokes {@link RequestHandler#doRequest} with error handling.
101+
*
102+
* @param req an {@link HttpServletRequest} object that contains the request
103+
* the client has made of the servlet
104+
* @param resp an {@link HttpServletResponse} object that contains the response
105+
* the servlet sends to the client
106+
* @throws IOException if an output error is detected when trying to write on the response.
86107
*/
87-
@Override
88-
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
108+
public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException {
89109
long requestStartTime = System.currentTimeMillis();
90110
HttpSession session = request.getSession();
91111

framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/RequestHandler.java

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@
4141
import javax.servlet.http.HttpServletRequest;
4242
import javax.servlet.http.HttpServletResponse;
4343
import javax.servlet.http.HttpSession;
44+
import javax.ws.rs.core.MultivaluedHashMap;
4445

46+
import org.apache.cxf.jaxrs.model.URITemplate;
4547
import org.apache.ofbiz.base.location.FlexibleLocation;
4648
import org.apache.ofbiz.base.util.Debug;
4749
import org.apache.ofbiz.base.util.SSLUtil;
@@ -183,7 +185,7 @@ public ConfigXMLReader.ControllerConfig getControllerConfig() {
183185
}
184186

185187
/**
186-
* Find a collection of request maps in {@code ccfg} matching {@code req}.
188+
* Finds a collection of request maps in {@code ccfg} matching {@code req}.
187189
* Otherwise fall back to matching the {@code defaultReq} field in {@code ccfg}.
188190
*
189191
* @param ccfg The controller containing the current configuration
@@ -192,20 +194,23 @@ public ConfigXMLReader.ControllerConfig getControllerConfig() {
192194
*/
193195
static Collection<RequestMap> resolveURI(ControllerConfig ccfg, HttpServletRequest req) {
194196
Map<String, List<RequestMap>> requestMapMap = ccfg.getRequestMapMap();
195-
Map<String, ConfigXMLReader.ViewMap> viewMapMap = ccfg.getViewMapMap();
196-
String defaultRequest = ccfg.getDefaultRequest();
197-
String path = req.getPathInfo();
198-
String requestUri = getRequestUri(path);
199-
String viewUri = getOverrideViewUri(path);
200-
Collection<RequestMap> rmaps;
201-
if (requestMapMap.containsKey(requestUri)
202-
// Ensure that overridden view exists.
203-
&& (viewUri == null || viewMapMap.containsKey(viewUri))) {
204-
rmaps = requestMapMap.get(requestUri);
205-
} else if (defaultRequest != null) {
206-
rmaps = requestMapMap.get(defaultRequest);
207-
} else {
208-
rmaps = null;
197+
Collection<RequestMap> rmaps = resolveTemplateURI(requestMapMap, req);
198+
if (rmaps.isEmpty()) {
199+
Map<String, ConfigXMLReader.ViewMap> viewMapMap = ccfg.getViewMapMap();
200+
String defaultRequest = ccfg.getDefaultRequest();
201+
String path = req.getPathInfo();
202+
String requestUri = getRequestUri(path);
203+
String overrideViewUri = getOverrideViewUri(path);
204+
if (requestMapMap.containsKey(requestUri)
205+
// Ensure that overridden view exists.
206+
&& (overrideViewUri == null || viewMapMap.containsKey(overrideViewUri))) {
207+
rmaps = requestMapMap.get(requestUri);
208+
req.setAttribute("overriddenView", overrideViewUri);
209+
} else if (defaultRequest != null) {
210+
rmaps = requestMapMap.get(defaultRequest);
211+
} else {
212+
rmaps = null;
213+
}
209214
}
210215
return rmaps != null ? rmaps : Collections.emptyList();
211216
}
@@ -234,6 +239,33 @@ static Optional<RequestMap> resolveMethod(String method, Collection<RequestMap>
234239
}
235240
}
236241

242+
/**
243+
* Finds the request maps matching a segmented path.
244+
*
245+
* <p>A segmented path can match request maps where the {@code uri} attribute
246+
* contains an URI template like in the {@code foo/bar/{baz}} example.
247+
*
248+
* @param rMapMap the map associating URIs to a list of request maps corresponding to different HTTP methods
249+
* @param request the HTTP request to match
250+
* @return a collection of request maps which might be empty but not {@code null}
251+
*/
252+
private static Collection<RequestMap> resolveTemplateURI(Map<String, List<RequestMap>> rMapMap,
253+
HttpServletRequest request) {
254+
// Retrieve the request path without the leading '/' character.
255+
String path = request.getPathInfo().substring(1);
256+
MultivaluedHashMap<String, String> vars = new MultivaluedHashMap<>();
257+
for (Map.Entry<String, List<RequestMap>> entry : rMapMap.entrySet()) {
258+
URITemplate uriTemplate = URITemplate.createExactTemplate(entry.getKey());
259+
// Check if current path the URI template exactly.
260+
if (uriTemplate.match(path, vars) && vars.getFirst("FINAL_MATCH_GROUP").equals("/")) {
261+
// Set attributes from template variables to be used in context.
262+
uriTemplate.getVariables().forEach(var -> request.setAttribute(var, vars.getFirst(var)));
263+
return entry.getValue();
264+
}
265+
}
266+
return Collections.emptyList();
267+
}
268+
237269
public void doRequest(HttpServletRequest request, HttpServletResponse response, String chain,
238270
GenericValue userLogin, Delegator delegator) throws RequestHandlerException, RequestHandlerExceptionAllowExternalRequests {
239271

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

270302
String path = request.getPathInfo();
271303
String requestUri = getRequestUri(path);
272-
String overrideViewUri = getOverrideViewUri(path);
273304

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

291-
String method = request.getMethod();
324+
String restMethod = request.getParameter("restMethod");
325+
String method = (restMethod != null) ? restMethod : request.getMethod();
292326
RequestMap requestMap = resolveMethod(method, rmaps).orElseThrow(() -> {
293327
String msg = UtilProperties.getMessage("WebappUiLabels", "RequestMethodNotMatchConfig",
294328
UtilMisc.toList(requestUri, method), UtilHttp.getLocale(request));

framework/webapp/src/test/java/org/apache/ofbiz/webapp/control/RequestHandlerTests.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import static org.mockito.ArgumentMatchers.anyString;
2929
import static org.mockito.Mockito.mock;
3030
import static org.mockito.Mockito.when;
31+
import static org.mockito.Mockito.verify;
32+
import static org.mockito.Mockito.times;
3133

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

126+
/**
127+
* Checks that segmented URIs are resolved and does not
128+
* conflict with overrideViewUri mechanism
129+
*/
130+
@Test
131+
public void resolveTemplateURISergmented() {
132+
RequestMap foo = new RequestMap(dummyElement);
133+
RequestMap bar = new RequestMap(dummyElement);
134+
RequestMap baz = new RequestMap(dummyElement);
135+
reqMaps.putSingle("baz/foo", foo);
136+
reqMaps.putSingle("bar", bar);
137+
reqMaps.putSingle("baz", baz);
138+
139+
viewMaps.put("foo", new ViewMap(dummyElement));
140+
141+
when(req.getPathInfo()).thenReturn("/baz/foo");
142+
when(ccfg.getDefaultRequest()).thenReturn("bar");
143+
assertThat(RequestHandler.resolveURI(ccfg, req), both(hasItem(foo)).and(not(hasItem(baz))));
144+
}
145+
146+
@Test
147+
public void resolveTemplateURIWithVariables() {
148+
RequestMap foo = new RequestMap(dummyElement);
149+
RequestMap bar = new RequestMap(dummyElement);
150+
reqMaps.putSingle("foo/bar/{var1}/baz/{var2}", foo);
151+
reqMaps.putSingle("bar", bar);
152+
153+
when(req.getPathInfo()).thenReturn("/foo/bar/toto/baz/titi");
154+
155+
assertThat(RequestHandler.resolveURI(ccfg, req), hasItem(foo));
156+
verify(req, times(1)).setAttribute("var1", "toto");
157+
verify(req, times(1)).setAttribute("var2", "titi");
158+
}
159+
160+
/**
161+
* Currently it is up to the developer to manage URIs with custom
162+
* variables that are conflicting with other routes by excluding
163+
* them using regular expressions as shown in the test.
164+
*/
165+
@Test
166+
public void resolveTemplateURIConflictingRoutes() {
167+
RequestMap foo = new RequestMap(dummyElement);
168+
RequestMap bar = new RequestMap(dummyElement);
169+
RequestMap baz = new RequestMap(dummyElement);
170+
reqMaps.putSingle("foo/bar", foo);
171+
reqMaps.putSingle("foo/qux", bar);
172+
reqMaps.putSingle("foo/{var:(?!(bar)|(qux)).*}", baz);
173+
174+
when(req.getPathInfo()).thenReturn("/foo/bar");
175+
assertThat(RequestHandler.resolveURI(ccfg, req), hasItem(foo));
176+
177+
when(req.getPathInfo()).thenReturn("/foo/qux");
178+
assertThat(RequestHandler.resolveURI(ccfg, req), hasItem(bar));
179+
180+
when(req.getPathInfo()).thenReturn("/foo/toto");
181+
assertThat(RequestHandler.resolveURI(ccfg, req), hasItem(baz));
182+
verify(req, times(1)).setAttribute("var", "toto");
183+
}
184+
124185
@Test
125186
public void resolveURIBasicOverrideView() throws Exception {
126187
RequestMap foo = new RequestMap(dummyElement);

0 commit comments

Comments
 (0)