Skip to content

Commit 5d81443

Browse files
committed
Support overlapping paths on resource classes
1 parent 63962a0 commit 5d81443

File tree

6 files changed

+315
-95
lines changed

6 files changed

+315
-95
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package io.quarkus.resteasy.reactive.server.test.resource.basic;
2+
3+
import static io.restassured.RestAssured.given;
4+
import static org.hamcrest.Matchers.equalTo;
5+
6+
import java.util.function.Supplier;
7+
8+
import jakarta.ws.rs.GET;
9+
import jakarta.ws.rs.Path;
10+
11+
import org.jboss.resteasy.reactive.RestPath;
12+
import org.jboss.shrinkwrap.api.ShrinkWrap;
13+
import org.jboss.shrinkwrap.api.spec.JavaArchive;
14+
import org.junit.jupiter.api.Test;
15+
import org.junit.jupiter.api.extension.RegisterExtension;
16+
17+
import io.quarkus.resteasy.reactive.server.test.simple.PortProviderUtil;
18+
import io.quarkus.test.QuarkusUnitTest;
19+
20+
class OverlappingResourceClassPathTest {
21+
@RegisterExtension
22+
static QuarkusUnitTest testExtension = new QuarkusUnitTest()
23+
.setArchiveProducer(new Supplier<>() {
24+
@Override
25+
public JavaArchive get() {
26+
JavaArchive war = ShrinkWrap.create(JavaArchive.class);
27+
war.addClasses(PortProviderUtil.class);
28+
war.addClasses(UsersResource.class);
29+
war.addClasses(UserResource.class);
30+
war.addClasses(GreetingResource.class);
31+
return war;
32+
}
33+
});
34+
35+
@Test
36+
void basicTest() {
37+
given()
38+
.get("/users/userId")
39+
.then()
40+
.statusCode(200)
41+
.body(equalTo("userId"));
42+
43+
given()
44+
.get("/users/userId/by-id")
45+
.then()
46+
.statusCode(200)
47+
.body(equalTo("getByIdInUserResource-userId"));
48+
}
49+
50+
@Path("/users")
51+
public static class UsersResource {
52+
53+
@GET
54+
@Path("{id}")
55+
public String getByIdInUsersResource(@RestPath String id) {
56+
return id;
57+
}
58+
}
59+
60+
@Path("/users/{id}")
61+
public static class UserResource {
62+
63+
@GET
64+
@Path("by-id")
65+
public String getByIdInUserResource(@RestPath String id) {
66+
return "getByIdInUserResource-" + id;
67+
}
68+
}
69+
70+
@Path("/i-do-not-match")
71+
public static class GreetingResource {
72+
73+
@GET
74+
@Path("greet")
75+
public String greet() {
76+
return "Hello";
77+
}
78+
}
79+
}

extensions/resteasy-reactive/rest/runtime/src/test/java/io/quarkus/resteasy/reactive/runtime/mapping/RequestMapperTestCase.java

+21-4
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
public class RequestMapperTestCase {
1111

1212
@Test
13-
public void testPathMapper() {
13+
public void testMap() {
1414

15-
RequestMapper<String> mapper = mapper("/id", "/id/{param}", "/bar/{p1}/{p2}", "/bar/{p1}");
15+
RequestMapper<String> mapper = mapper(false, "/id", "/id/{param}", "/bar/{p1}/{p2}", "/bar/{p1}");
1616
mapper.dump();
1717

1818
RequestMapper.RequestMatch<String> result = mapper.map("/bar/34/44");
@@ -31,13 +31,30 @@ public void testPathMapper() {
3131
result = mapper.map("/bar/34");
3232
Assertions.assertEquals("/bar/{p1}", result.value);
3333
Assertions.assertEquals("34", result.pathParamValues[0]);
34+
}
35+
36+
@Test
37+
public void testAllMatches() {
38+
RequestMapper<String> mapper = mapper(true, "/greetings", "/greetings/{id}", "/greetings/unrelated");
39+
mapper.dump();
3440

41+
var result = mapper.allMatches("/not-existing");
42+
Assertions.assertFalse(result.hasNext());
43+
44+
result = mapper.allMatches("/greetings/greeting-id");
45+
Assertions.assertTrue(result.hasNext());
46+
var next = result.next();
47+
Assertions.assertEquals("", next.remaining);
48+
Assertions.assertTrue(result.hasNext());
49+
next = result.next();
50+
Assertions.assertEquals("/greeting-id", next.remaining);
51+
Assertions.assertFalse(result.hasNext());
3552
}
3653

37-
RequestMapper<String> mapper(String... vals) {
54+
RequestMapper<String> mapper(boolean prefixTemplates, String... vals) {
3855
ArrayList<RequestMapper.RequestPath<String>> list = new ArrayList<>();
3956
for (String i : vals) {
40-
list.add(new RequestMapper.RequestPath<>(false, new URITemplate(i, false), i));
57+
list.add(new RequestMapper.RequestPath<>(prefixTemplates, new URITemplate(i, false), i));
4158
}
4259
return new RequestMapper<>(list);
4360
}

independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java

+37
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.util.Arrays;
1313
import java.util.Collections;
1414
import java.util.Deque;
15+
import java.util.Iterator;
1516
import java.util.LinkedList;
1617
import java.util.List;
1718
import java.util.Map;
@@ -51,6 +52,7 @@
5152
import org.jboss.resteasy.reactive.server.SimpleResourceInfo;
5253
import org.jboss.resteasy.reactive.server.core.multipart.FormData;
5354
import org.jboss.resteasy.reactive.server.core.serialization.EntityWriter;
55+
import org.jboss.resteasy.reactive.server.handlers.RestInitialHandler;
5456
import org.jboss.resteasy.reactive.server.injection.ResteasyReactiveInjectionContext;
5557
import org.jboss.resteasy.reactive.server.jaxrs.AsyncResponseImpl;
5658
import org.jboss.resteasy.reactive.server.jaxrs.ContainerRequestContextImpl;
@@ -62,6 +64,7 @@
6264
import org.jboss.resteasy.reactive.server.jaxrs.SseEventSinkImpl;
6365
import org.jboss.resteasy.reactive.server.jaxrs.SseImpl;
6466
import org.jboss.resteasy.reactive.server.jaxrs.UriInfoImpl;
67+
import org.jboss.resteasy.reactive.server.mapping.RequestMapper;
6568
import org.jboss.resteasy.reactive.server.mapping.RuntimeResource;
6669
import org.jboss.resteasy.reactive.server.mapping.URITemplate;
6770
import org.jboss.resteasy.reactive.server.multipart.FormValue;
@@ -156,6 +159,8 @@ public abstract class ResteasyReactiveRequestContext
156159
private FormData formData;
157160
private boolean producesChecked;
158161

162+
private Iterator<RequestMapper.RequestMatch<RestInitialHandler.InitialMatch>> initialMatches;
163+
159164
public ResteasyReactiveRequestContext(Deployment deployment,
160165
ThreadSetupAction requestContext, ServerRestHandler[] handlerChain, ServerRestHandler[] abortHandlerChain) {
161166
super(handlerChain, abortHandlerChain, requestContext);
@@ -203,6 +208,30 @@ public void restart(RuntimeResource target, boolean setLocatorTarget) {
203208
this.target = target;
204209
}
205210

211+
/**
212+
* Restarts handler chain processing if another initial match exists. Initial matches are determined in RestInitialHandler.
213+
*
214+
* @return true if a restart occurred
215+
*/
216+
public boolean restartWithNextInitialMatch() {
217+
if (initialMatches == null || !initialMatches.hasNext()) {
218+
return false;
219+
}
220+
RequestMapper.RequestMatch<RestInitialHandler.InitialMatch> initialMatch = initialMatches
221+
.next();
222+
restart(initialMatch.value.handlers);
223+
setMaxPathParams(initialMatch.value.maxPathParams);
224+
setRemaining(initialMatch.remaining);
225+
for (int i = 0; i < initialMatch.pathParamValues.length; ++i) {
226+
String pathParamValue = initialMatch.pathParamValues[i];
227+
if (pathParamValue == null) {
228+
break;
229+
}
230+
setPathParamValue(i, initialMatch.pathParamValues[i]);
231+
}
232+
return true;
233+
}
234+
206235
/**
207236
* Meant to be used when an error occurred early in processing chain
208237
*/
@@ -1254,6 +1283,14 @@ private String getResourceLocatorPathParam(String name, PreviousResource previou
12541283

12551284
public abstract boolean resumeExternalProcessing();
12561285

1286+
public Iterator<RequestMapper.RequestMatch<RestInitialHandler.InitialMatch>> getInitialMatches() {
1287+
return initialMatches;
1288+
}
1289+
1290+
public void setInitialMatches(Iterator<RequestMapper.RequestMatch<RestInitialHandler.InitialMatch>> initialMatches) {
1291+
this.initialMatches = initialMatches;
1292+
}
1293+
12571294
static class PreviousResource {
12581295

12591296
private static final String PROPERTY_KEY = AbstractResteasyReactiveContext.CUSTOM_RR_PROPERTIES_PREFIX

independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ClassRoutingHandler.java

+6
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti
6666
mapper = mappers.get(null);
6767
}
6868
if (mapper == null) {
69+
if (requestContext.restartWithNextInitialMatch()) {
70+
return;
71+
}
6972
// The idea here is to check if any of the mappers of the class could map the request - if the HTTP Method were correct
7073
String remaining = getRemaining(requestContext);
7174
for (RequestMapper<RuntimeResource> existingMapper : mappers.values()) {
@@ -89,6 +92,9 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti
8992
}
9093

9194
if (target == null) {
95+
if (requestContext.restartWithNextInitialMatch()) {
96+
return;
97+
}
9298
// The idea here is to check if any of the mappers of the class could map the request - if the HTTP Method were correct
9399
for (Map.Entry<String, RequestMapper<RuntimeResource>> entry : mappers.entrySet()) {
94100
if (entry.getKey() == null) {

independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/RestInitialHandler.java

+5-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.jboss.resteasy.reactive.server.handlers;
22

3+
import java.util.Iterator;
34
import java.util.List;
45

56
import jakarta.ws.rs.NotFoundException;
@@ -58,8 +59,8 @@ public void beginProcessing(Object externalHttpContext, Throwable throwable) {
5859

5960
@Override
6061
public void handle(ResteasyReactiveRequestContext requestContext) throws Exception {
61-
RequestMapper.RequestMatch<InitialMatch> target = mappers.map(requestContext.getPathWithoutPrefix());
62-
if (target == null) {
62+
Iterator<RequestMapper.RequestMatch<InitialMatch>> targets = mappers.allMatches(requestContext.getPathWithoutPrefix());
63+
if (!targets.hasNext()) {
6364
ProvidersImpl providers = requestContext.getProviders();
6465
ExceptionMapper<NotFoundException> exceptionMapper = providers.getExceptionMapper(NotFoundException.class);
6566

@@ -73,16 +74,8 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti
7374
return;
7475
}
7576
}
76-
requestContext.restart(target.value.handlers);
77-
requestContext.setMaxPathParams(target.value.maxPathParams);
78-
requestContext.setRemaining(target.remaining);
79-
for (int i = 0; i < target.pathParamValues.length; ++i) {
80-
String pathParamValue = target.pathParamValues[i];
81-
if (pathParamValue == null) {
82-
break;
83-
}
84-
requestContext.setPathParamValue(i, target.pathParamValues[i]);
85-
}
77+
requestContext.setInitialMatches(targets);
78+
requestContext.restartWithNextInitialMatch();
8679
}
8780

8881
public static class InitialMatch {

0 commit comments

Comments
 (0)