Reputation: 1052
I'm playing around with Jersey 2.21
and I'd like to know if it's possible to have an "optional" param which can, or not, be present in the request made to the server.
I want to successfully access this two methods:
http://localhost:8080/my_domain/rest/api/myMethod/1
http://localhost:8080/my_domain/rest/api/myMethod
As you can see, I'm trying to make the integer (id
) param an optional one.
I've declared myMethod
as follows:
@GET
@Path("myMethod/{id}")
@Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
public String myMethod(@PathParam("id") Integer id, @Context HttpHeaders hh)
This works:
http://localhost:8080/my_domain/rest/api/myMethod/1
and this works too:
http://localhost:8080/my_domain/rest/api/myMethod/
but this won't work and I don't understand why. It throws a 404 Not Found
error:
http://localhost:8080/my_domain/rest/api/myMethod
Can you point me in the right direction to work this out? I don't like the slash being mandatory on all my REST method calls and would like to suppress it if possible.
Upvotes: 11
Views: 9049
Reputation: 15779
There is a way easier way to do this:
@GET
@Path("myMethod/{id}")
public String myMethod(@PathParam("id") Integer id) {
}
@GET
@Path("myMethod")
public String myMethod() {
return myMethod(null);
}
No tricky regex required.
Upvotes: 14
Reputation: 208974
So after some dabbling around with some of the answers in Optional @PathParam in Jax-RS, the problem is that using this
@Path("/myMethod{id: (/\\d+)?}")
public Response get(@PathParam("id") int id) {}
causes the /
to be in the capture group. So when Jersey tries to parse /1
it will get an exception and send a 404. We could use a String, but then it gets ugly, as we need to get rid of the leading /
and parse it ourselves.
@Path("/myMethod{id: (/\\d+)?}")
public Response get(@PathParam("id") String id) {
id = id.replace("/", "");
int parsed = Integer.parseInt(id);
}
The other solution I came up with (the one that works for the OP), is to separate the /
from the digits into two different path expressions, so that the leading /
is not captured in the actual id and doesn't fail in parsing
@Path("/method{noop: (/)?}{id: ((?<=/)\\d+)?}")
public Response get(@PathParam("id") int id) {}
The {noop: (/)?}
captures the optional /
. And the {id: ((?<=/)\\d+)?}
uses a positive lookbehind, saying that the numbers (\\d+
) are allowed if and only if there is a /
before it ((?<=/)
). This is necessary as the /
is optional. If we didn't use this assertion, then /myMethod123
would be allowed.
Here is a complete test case using Jersey Test Framework
public class OptionalParamTest extends JerseyTest {
@Path("optional")
public static class Resource {
@GET
@Path("/method{noop: (/)?}{id: ((?<=/)\\d+)?}")
public String get(@PathParam("id") int id) {
return String.valueOf(id);
}
}
@Override
public ResourceConfig configure() {
return new ResourceConfig(Resource.class);
}
@Test
public void should_return_id_1() {
Response response = target("optional/method/1").request().get();
System.out.println("status=" + response.getStatus());
assertEquals("1", response.readEntity(String.class));
}
@Test
public void should_return_id_0_with_no_id() {
Response response = target("optional/method").request().get();
assertEquals(200, response.getStatus());
assertEquals("0", response.readEntity(String.class));
}
@Test
public void should_return_404_with_numbers_and_no_slash() {
Response response = target("optional/method12").request().get();
assertEquals(404, response.getStatus());
}
@Test
public void should_return_404_with_numbers_and_letters() {
Response response = target("optional/method/12b").request().get();
assertEquals(404, response.getStatus());
}
@Test
public void should_return_404_with_only_letters() {
Response response = target("optional/method/ab").request().get();
assertEquals(404, response.getStatus());
}
}
Here's the dependency for the test
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>${jersey2.version}</version>
<scope>test</scope>
</dependency>
For the tests, it would be better to use a boxed Integer
instead of an int
as the method parameter. With the former you would be able to do a null check, instead of receiving the default 0
for the primitive.
Upvotes: 11