"Is there any chance that following request matches to readSingleByName
not to readSingleById
?"
Let's test it:
@Path("/ambiguous")
public class AmbiguousResource {
@GET
@Path("/{id: \\d+}")
public Response readSingleById(@PathParam("id") long id) {
return Response.ok("callById").build();
}
@GET
@Path("/{name: .+}")
public Response readSingleByName(@PathParam("name") String name) {
return Response.ok("callByName").build();
}
}
@Test
public void testGetIt() throws Exception {
int idCount = 0;
int nameCount = 0;
for (int i = 0; i < 10 * 1000; i++) {
String response = c.target(Main.BASE_URI)
.path("ambiguous").path("1234").request().get(String.class);
switch (response) {
case "callById":
idCount++;
break;
case "callByName":
nameCount++;
break;
}
}
System.out.println("Id Count: " + idCount);
System.out.println("Name Count: " + nameCount);
}
Results:
Jersey 2.13
Id Count: 10000
Name Count: 0
Resteasy 3.0.7
Id Count: 10000
Name Count: 0
Now let's do play with it a bit. What happens if we switch the method declaration positions, i.e. the "name method" before the "id method"
@GET
@Path("/{name: .+}")
public Response readSingleByName(@PathParam("name") String name) {
return Response.ok("callByName").build();
}
@GET
@Path("/{id: \\d+}")
public Response readSingleById(@PathParam("id") long id) {
return Response.ok("callById").build();
}
Now if we run the same test, the Jersey result will be the same (id == 10000, name == 0). But with with Resteasy, we get
Resteasy 3.0.7
Id Count: 0
Name Count: 10000
So it appears the behavior at this level of ambiguity is implementation specific. A snippet from the JAX-RS spec states (at this point of "method filtering"):
Sort E
using the number of literal characters in each member as the primary key (descending order), the number of capturing groups as a secondary key (descending order) and the number of capturing groups with non-default regular expressions (i.e. not ([^ /]+?)
) as the tertiary key (descending order)
This basically reads as:
- Check the number of literal characters. (In your case, none).
- Check the number of
{ }
s (regex or not)
- Check the number of
{ }
s (non regex)
From there the regex should be checked. But it does not anywhere state that all remaining candidate methods should be checked for "best matching" regex, which is the case you are hoping for.
I'm not great with regex, so the concept of determining a "best matching" regex is over my head. I may be wrong, but it appears this is what Jersey is doing. I tested also making the id
parameter a String (thinking maybe the parameter type had something to do with it), but same result, the "id method" is always hit.
Another option, you can make a simple alteration/or maybe some might call a hack and do something like
@GET
@Path("/{id: \\d+}{dummy: (/)?}")
public Response readSingleById(@PathParam("id") long id) {
Based on the second sort key (mentioned above), this would make the "id method" to always be in front of the "name method" after the sorting.
Whatever you decide, I would make sure to do thorough testing.
As far as design, you should strive to make the URI schemes less ambiguous, but I can see what you are attempting, allowing a resource to be discovered by name and by id. I personally, don't have a strong opinion about this matter, but you can find a good discussion at REST - multiple URI for the same resource (???)