I cannot get to the correct #Path - java

Using JAX-RS, I have the following 3 #Paths.
#Path(JobRest.PATH)
#Api(value = JobRest.PATH, description = "REST APIs for Jobs")
public interface JobRest {
public static final String PATH = "/job";
#GET
#Path("/last")
#Produces(MediaType.APPLICATION_JSON)
public Job retrieveLastJob(...);
#GET
#Path("/{jobId}")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Job retrieveJob(...., #PathParam("jobId") String jobId, );
#GET
#Produces(MediaType.APPLICATION_JSON)
public JobList retrieveAllJobs(....);
}
/job correctly calls retrieveAllJobs()
/job/1236 correctly calls retrieveJob(..., "1236", ...).
I expected that /job/last would call retrieveLastJob(...), since it matches, but it calls retrieveJob(..., "last", ...) instead.
How do I change the notation so that /job/last will call retrieveLastJob(...)?

TL;DR
Remove the #Consumes(MediaType.APPLICATION_JSON) on the retrieveJob method. For one, it does not accept a body, so it does not consume anything. Secondly it conflicts with the expected behavior.
I've tested with both Jersey and RESTeasy and it seems to be a difference in implementation. Jersey works fine with your code, while RESTeasy always hits the retrieveJob method, as you are experiencing.
Here's my take. If you look at the JAX-RS spec; 3.7.2 Request Matching, there's a semi-cryptic algorithm for matching resources, that goes something like this.
Get all matching resource class (by path), put them into a set.
Get all matching resource methods (by path), put them into a set.
Sort the methods by best matching path (most literal characters go first).
Sort by media type (with consumes and produces).
From my perspective, in this particular case, after step 3, the retrieveLastJob should automatically win, as it has the most literal characters. The producing media types are the same, and the consumes media type should not even matter, since it is a GET request with no Content-Type to do any matching.
My guess it RESTeasy still uses the annotation to sort even though it should not even be taken into consideration in this case. So it would appear that the method with the annotation is given more precedence, as it appears to be more specific, by way of just having an annotation, while the other does not. But that (step 4) level of specificity really shouldn't matter in this case.
I don't know if it's a bug against the spec. It's not too clear on how it should be handled, but I personally think the Jersey behavior is the correct behavior, for the simple fact that this level of specificity should not matter in this particular case. In any case, it is not correct to have the #Consumes annotation anyway for a GET request with no body.

Related

Do path parameters in jax-rs #Path expression need to be slash-separated?

I'm inspecting some code in a JAX-RS springboot microservice that I'm starting to work on. I saw the following (modified):
#POST
#Path("{foo: ([^/]+?)?}{bar: (/[^/]+?)?}")
public Response doit(
#PathParam("foo") String foo,
#PathParam("bar") String bar,
#RequestBody UpdateRequest updateRequest, #Context HttpHeaders httpHeaders);
That #Path value looks odd. Instead of having explicit "/" markers in the string, it's trying to do it through the regex. I'm guessing this can work, because this is existing code, but is this really advisable? Is there any reason that this would be necessary?
I suppose a similar questionable example would be this:
#Path("foo{bar: (/[^/]+?)?}")
Is there any reason this is better than the simpler:
#Path("foo/{bar}")
The JAX-RS specification, specifically the “URI Templates” section under the Resources chapter, has the answer:
Template parameters can optionally specify the regular expression used to match their values. The default value matches any text and terminates at the end of a path segment but other values can be used to alter this behavior, e.g.:
#Path("widgets/{path:.+}")
public class Widget {
...
}
In the above example the Widget resource class will be matched for any request whose path starts with widgets and contains at least one more path segment; the value of the path parameter will be the request path following widgets. E.g. given the request path widgets/small/a the value of path would be small/a.
So, if you don’t provide a customn regex, the default boundary is the /.
Therefore, that complex regex is unnecessary. #Path("{foo}/{bar}" is fine.
Technically, it’s not exactly the same; the regex forces {bar} to include the leading /. Is it worth the complexity of regexes that need extra visual analysis? Not in my opinion.
If you just used
#Path("foo/{bar}")
then calling /foo would lead to a 404, because the / is static and it would require requesting /foo/. But when it's in the regex of bar, it makes it optional. So the example
#Path("foo{bar: (/[^/]+?)?}")
allows you to access the parent resource and the sub resource from the same resource method. As a more realistic example say you have
#Path("customers{id: (/[^/]+?)?}")
With this, we would have a resource method that could handle both accessing a collection resource and an single resource. As opposed to having two separate resource methods, one for each case. For example
#GET
#Path("customers{id: (/[^/]+?)?}")
public Response get(#PathParam("id") String id) {
if (id == null) {
return collection customers collection
} else {
fetch custom by id and return customer.
}
}
That's the only real benefit I can see in this situation. Would probably need more context, maybe some documenting comments from the author as to what they were trying to accomplish. Overall though, IMO the code looks unnecessarily over complicated.

Swagger doesn't read Path parameter: #Path("folder{path:.*}")

I have a problem getting Swagger to generate correct documentation for an API call that has an optional path parameter.
I'm building an API that peers into a hierarchical structure, similar to a file system. I want to call the same method to get the root structure as I do to get a sub resource. E.g:
Get the root: /folder
Get a sub folder: /folder/path/to
My Jax-rs method looks like this:
#GET #Path("folder{path:.*}")
Response folderContents(#ApiParam(value = "The folder to list", required = false) #PathParam("path") String path)
{...}
My method call works, but my swagger documentation is incorrect and doesn't work. Swagger-ui generates GET calls that look like this when I run it:
http://localhost:8080/storage-war/rest/filestore/folder{path:.*}
I'm looking for a way to either force Swagger to generate the correct signature or rebuild my regular expression so that my generated Swagger is correct.
Previously I'v tried using #Path("folder/{path:.}")*; his generated correct Swagger documentation but didn't match my no path given case. I've also tried #Path("/folder{p:/?}{path:(.)}")*; This produced a working method call but incorrect Swagger docs.
Is there a straightforward way to do what I'm looking for?
Edit:
In the end I created separate method calls for root and folders. Then I decorated the root call with it with #ApiOperation(hidden = true). This way I have an extra method in my code but only one method show up in my Swagger docs.
#GET #Path("folder/{path:.*}")
Response folderContents(#PathParam("path") String path)
{...}
#GET #Path("folder")
#ApiOperation(hidden = true)
Response rootContents()
{...}
In swagger, path parameters are always required. Understanding that in many frameworks and in practice they can be optional, but in the swagger definition they are required. See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#fixed-fields-7

#QueryParam not parsing parameter after anchor ('#')

I've noticed a weird behaviour in #QueryParam annotation from javax.ws.rs.QueryParam;
With reference to the snippet of code below, when I set the url to something like:
http://host:port/services/serv123?test=OK
I can retrieve the value of 'test' as expected.
However, when I set the url to something like:
http://host:port/services/serv123#top?test=OK
#QueryParam("test") returns null.
Here is the code I am using. Each annotation is explicitly imported.
#Path("/services")
public class Services {
[...]
#GET
#Path("/{srvID}")
#Produces(MediaType.TEXT_HTML)
public ServicesView getServiceDetailPage(#PathParam("srvID") String srvId,
#QueryParam("test") String test) {
[...]
return new ServicesView([...]);
}
[...]
}
I have tested this in Dropwizard 0.9.1 (and included Jersey)
Please note:
I'm interested in the explanation of the behaviour, not in a workaround (I have already a couple and can post them if someone is interested).
To my knowledge, http://host:port/services/serv123#top?test=OK is a perfectly legitimate url and #QueryParam should be able to handle it (I'm happy to be proven wrong, just explain why it would not be a legal url).
For the sake of testing, I have tried also encoding '#' into %23%3F but, as expected, it did not work.
What am I missing?
Instead of
http://host:port/services/serv123#top?test=OK
use
http://host:port/services/serv123?test=OK#top
if you want ?test=OK to be a query parameter instead of part of the anchor.

Using String array "constant" in Spring #RequestMapping value

I should probably point out that Spring is not in and of itself necessarily crucial to this question, but I encountered this behavior while using Spring, so the question uses the situation in Spring in which I encountered this.
I have a controller class that maps requests for GET and POST requests to the same set of URLs for a particular form. This form has different URLs for different locales, but there is only one method for the GET request, and one for the POST, since the logic at the controller level for the form is identical for each locale site (but things deeper in the logic, like locale-specific validation, may be different). Example:
#Controller
public class MyFormController {
// GET request
#RequestMapping(value={"/us-form.html", "/de-form.html", "/fr-form.html"},
method={RequestMethod.GET})
public String showMyForm() {
// Do some stuff like adding values to the model
return "my-form-view";
}
// POST request
#RequestMapping(value={"/us-form.html", "/de-form.html", "/fr-form.html"},
method={RequestMethod.POST})
public String submitMyForm() {
// Do stuff like validation and error marking in the model
return "my-form-view"; // Same as GET
}
}
The form GET and POST works just fine when written like this. You'll notice that the String arrays used for the #RequestMapping values are identical. What I want to do is put those URLs into one spot (ideally a static final field in the controller) so that when we add new URLs (which correspond to the form in future localized sites), we can just add them in one spot. So I tried this modification to the controller:
#Controller
public class MyFormController {
// Moved URLs up here, with references in #RequestMappings
private static final String[] MY_URLS =
{"/us-form.html", "/de-form.html", "/fr-form.html"};
// GET request
#RequestMapping(value=MY_URLS, // <-- considered non-constant
method={RequestMethod.GET})
public String showMyForm() {
// Do some stuff like adding values to the model
return "my-form-view";
}
// POST request
#RequestMapping(value=MY_URLS, // <-- considered non-constant
method={RequestMethod.POST})
public String submitMyForm() {
// Do stuff like validation and error marking in the model
return "my-form-view"; // Same as GET
}
}
The problem here is that the compiler complains about the value attribute no longer being a constant. I am aware that Spring requires that value must be a constant, but I had thought that using a final field (or static final in my case) with an Array literal containing String literals would have passed as "constant". My suspicion here is that the array literal has to be constructed on the fly in such a way that it is uninitialized when the value attribute is parsed.
I feel like this shouldn't be a hard thing to figure out with a basic Java knowledge, but something is escaping me that I haven't been able to find any answers for after some research. Can someone confirm my suspicion and give a citation or good explanation for why that may be so, or deny my suspicion and explain what the actual issue is?
Note: I cannot simply combine the URLs into a Path Pattern, as each form URL is in its localized site's language, and matching on that would be impossible. I merely give the "/{locale}-form.html" strings above as my URLs for example's sake.
You're right, this is nothing to do with Spring, all Annotation parameters must be compile-time constants. That's a basic java language rule.
Marking the array reference as final doesn't cut it because this is still perfectly legal:
MY_URLS[0] = "es-form.html";
Also, how locked in are you into embedding locale into the url like that in the first place? Are you emulating legacy links? Spring has plenty of built in support for using the browser's actual locale.

Jersey #Path for plural/single REST nouns in same class

I have a class this is annotated with #Path like so:
#Path("widgets")
#Produces(MediaType.APPLICATION_XML)
public class WidgetResource {
#GET
public Response getWidgets(#QueryParam("limit"))
{
//This class returns the plural noun, a list of widgets
//...}
#GET
#Path("widget/{id}")
public Response getWidgetById(#PathParam("id") long id)
{
//This class returns a single widget by id
//...}
When I fire up a test client the localhost/widgets maps as expected, but when the getWidgetById method is mapped to localhost/widgets/widget/{id}. This is not what I want - I would like to have localhost/widgets and localhost/widget/{id}
I have tried omitting the #Path annotation at the class level, but that prevents Jersey from recognizing this class as a REST Resource (I tried both the ScanningResourceConfig and the ClassNameResourceConfig - both failed to load the class as a resource unless there was a #Path at the class level).
I guess a (ugly) workaround would be to split the methods between classes a WidgetResource class and a WidgetsResource class. I think this is a terrible solution since both of these methods share resources in the same class, but I really need the REST-ful localhost/widget (for a single entity) and localhost/widgets (for plural).
Am I missing something - is there some way for me to have Jersey pick up the class as a Resource class if I just #Path annotate the methods (I couldn't get it to work), if not can I force absolute mapping (#Path(/widget/{id})) or some relative mapping (#Path(../widget/id) - neither of those work in reality - just an analogy of what I'm after. Thanks!
This part is about what you need:
Personally, I find your mapping strange and confusing. Just keep it like this:
#Path("widgets")
#Produces(MediaType.APPLICATION_XML)
public class WidgetResource {
#GET
public Response getWidgets(#QueryParam("limit")) {
//This method returns the plural noun, a list of widgets
// it's also possible to limit the number returned by
// using a query parameter. You could easily implement
// pagination by adding further query parameters like
// 'offset', 'sortOrder', etc.
//...
}
#GET
#Path("{id}")
public Response getWidgetById(#PathParam("id") long id) {
//This method returns a single widget by id
//...
}
}
It seems natural to append the path to a collection with an ID to fetch an object from the collection. There's really no need to make it widgets/widget/{id}. The widget part is obvious and unnecessary.
Here's a really neat tutorial on RESTful APIs: "Teach a dog to REST" by apigee I think it's a really good video. The authors make a couple of good points. And here's a link to a longer version of the same presentation
This part is about what you want:
If you really want to keep the plural/singular dualism (which I really don't recomment), you can annotate your code like this:
But it's really ugly
#Path("/")
#Produces(MediaType.APPLICATION_XML)
public class WidgetResource {
#GET
#Path("widgets")
public Response getWidgets(#QueryParam("limit")) {
//This method returns the plural noun, a list of widgets
//...}
#GET
#Path("widget/{id}")
public Response getWidgetById(#PathParam("id") long id) {
//This method returns a single widget by id
//...
}
}
My suggestion is to have your paths be:
"widgets" and "widgets/id/{id}". Or if you knew you were never going to query by anything other than id, your second one could simply be "widgets/{id}".
I would not switch between plural and singular in your path. Since you accessing the same type of resource for both, your root should be the same. The second form just specifies it more -- a vectoring-based approach for getting more specific.

Categories