Jersey 2.x - Conflicting route priority of PUT and GET - java

I'm working with Dropwizard, which uses Jersey internally. I have two methods on a controller:
PUT /garbage/[id1,id2,...idN] is intended to take a path parameter that's a list of numeric IDs representing resources to be updated. I'm using a regex-based PathParam here. I've fudged the regex in this example because I don't think it matters, but the point is that a single numeric ID should match the regex.
GET /garbage/[id] fetches data about a single piece of garbage.
Jersey seems to get confused, despite the difference in method. When I query with something like
curl localhost:8080/garbage/1
Jersey gives me a 405 error. If I take the PUT out of the picture (for example, sabotage the path param regex, or remove it entirely), the GET endpoint works fine.
I assume there is some detail in JAX-RS 3.7.2 I'm missing that explains why this should be the case, but I can't figure out what it is.
Here's the code:
#Path("/garbage")
#Produces(MediaType.APPLICATION_JSON)
public class GarbageController {
private static final Logger LOG = LoggerFactory.getLogger(GarbageController.class);
#PUT
#Path("/{params: [\\d,]+}")
#Consumes(MediaType.APPLICATION_JSON)
#Timed
public Response updateGarbage(#PathParam("params") List<PathSegment> params) {
LOG.warn("updateGarbage");
return Response.status(Response.Status.OK).build();
}
#GET
#Path("/{garbageId}")
public Response getGarbageById(#PathParam("garbageId") long garbageId) {
LOG.warn("getGarbage");
return Response.status(Response.Status.OK).build();
}
}

The main purpose of #PathSegment is to handle fragments of the URI which is useful to retrieve Matrix Parameters. For example the method below:
#GET
#Path("/book/{id}")
public String getBook(#PathParam("id") PathSegment id) {...}
Should be able to handle this request:
GET /book;name=EJB 3.0;author=Bill Burke
Because the #PathSegment intercepts the entire URL fragment the GET method seems to be ignored. You can handle the comma-separated IDs on the PUT request with a simple String split:
#PUT
#Path("/{params}")
#Consumes(MediaType.APPLICATION_JSON)
#Timed
public Response updateGarbage(#PathParam("params") String params) {
LOG.warn("updateGarbage ", params.split(","));
return Response.status(Response.Status.OK).build();
}
You can also change the request format to query parameters or implement a Converter/Provider to handle a custom object. All of them should solve the GET not implemented issue.
I believe this is not a case of route priorities between GET and PUT but instead this is related to the #Consumes annotation which cannot be used on a GET request. Either DW is ignoring this endpoint or is converting it into the default POST method, which would explain the 405 response for the GET request.

I figured this out, although I have not traced far enough into Jersey to know why it works. The solution is to rewrite the #GET method to use the same regex syntax as the #PUT. Jersey will handle the type conversion in the method signature, with the note that it will return a 404 if the type conversion fails (ie, GET /garbage/xyz).
#PUT
#Path("/{params: .+}")
#Consumes(MediaType.APPLICATION_JSON)
public Response updateGarbage(#PathParam("params") List<PathSegment> params) {
LOG.warn("updateGarbage");
return Response.status(Response.Status.OK).build();
}
#GET
#Path("/{params: .+}")
public Response getGarbageById(#PathParam("params") long garbageId) {
LOG.warn("getGarbage {}", garbageId);
return Response.status(Response.Status.OK).build();
}

Related

Rest Api creation in java [duplicate]

This question already has an answer here:
Spring #RestController Get Request Content-Type to response json or html
(1 answer)
Closed 4 years ago.
I need to make a single api, by which I can return data format of json or xml, as it has been requested from the customer.
guys, any idea how I achieve this because when I use #Produces annotation it makes it fix for specific format, but I need to return as it has been requested.
If you can use Spring, configure ContentNegotiationManager in DispatcherServlet.xml and then you can use response type as parameters to url.
For example:
http://localhost:8080/employee-management-system/viewEmployee/4?type=xml
http://localhost:8080/employee-management-system/viewEmployee/4?type=json
More detailed instructions you can find here:
https://www.javainuse.com/spring/rest4
You could specify the response content type using the ResponseEntity object as follows:
return ResponseEntity
.ok()
.contentType(MediaType.IMAGE_GIF);
What I would normally expect to see here are two methods, one that #Produces ("application/json"), and the other that #Produces("application/xml").
#Path("/foobar")
public final class FooBar {
#Produces("application/xml")
public String xml () { ... }
#Produces("application/json")
public String json() { ... }
}
The example in Oracle's description of the #Produces annotation includes an example for text/plain and text/html which is similar.
this is one way to do it, but I don't want to write 2 methods. I want to do it in one method.
Another reasonable approach would be to get closer to the metal
#Path("/foobar")
public final class FooBar {
public Response foobar() (#Context HttpHeaders headers) { ... }
}
And then inspect the headers yourself to decide what to do. See Get HTTP header in JAX-RS

POST request without mime type is not triggering the endpoint method

I have an API that I am NOT allowed to change:
#POST
#Path("/accomodation/{area}/{val1}/{val2}")
Response createAccomEntry(#PathParam("area") String area, #PathParam("val1") String val1, #PathParam("val2") String val2);
I can only change the implementation:
#Override
public Response createAccomEntry(#PathParam("area") String area, #PathParam("val1") String val1, #PathParam("val2") String val2) {
//can't debug the code here
return Response.status(Response.Status.OK).build();
}
The creators of the API did not specify any mime type to be consumed etc. and I am unable to debug it at the moment. From my REST client I make a POST request to http://localhost:8080/accomodation/area/val1/val2 I set "application/x-www-form-urlencoded" as the Content-Type header and in the payload I give it like:
area=mock&val1=mock&val2=mock
But all I get is 404. What am I doing wrong?
It's because once you add any JAX-RS annotations on the implementation, it negates all JAX-RS annotations on the corresponding interface method. This is how the spec is designed. So when you use #PathParam, that alone will negate all the other annotations on the interface method, including the #POST and the #Path, which is what is need to register that endpoint with Jersey. So just remove all the #PathParams on the implementation, and it should work.

Jersey server, rest api : How to remove the code and type from the response body?

I'm trying to create a Rest Api using Jax-rs Jersey from a base code generated by swagger.
The specifications are for exemple for a specific Request :
Code : 200
Description : User token for login
Schema : String
My problem is that the generated code use the class :javax.ws.rs.core.Response that should not be extended according to the documentation.
I'm using this kind of code to build the response :
return Response.ok().entity(new ApiResponseMessage(ApiResponseMessage.OK,apiToken)).build();
The response generated looks like that :
{"code":4,"type":"ok","message":"uHN2cE7REfZz1pD17ITa"}
When i only want to have :"uHN2cE7REfZz1pD17ITa" in the body. Is that possible using Jersey ? Or is this format part of the jax-rs specifications ?
Thank you.
ApiResponseMessage from Swagger does not extend Response from JAX-RS. Check the code and you will see that ApiResponseMessage is just a POJO. That is, the piece of code you posted in your question is just fine.
If you only need the token, you can use:
return Response.ok(apiToken).build();
The following gives you the same result:
return Response.ok().entity(apiToken).build();
Since your resource method will produce just a piece of text (not a valid JSON unless the piece of text is wrapped into quotes), the most suitable media type for the response would be text/plain. It could be achieved by either annotating the resource method with #Produces(MediaType.TEXT_PLAIN) or setting the media type in the response, as following:
#GET
#Produces(MediaType.TEXT_PLAIN)
public Response getToken() {
String apiToken = ...
return Response.ok(apiToken).build();
}
#GET
public Response getToken() {
String apiToken = ...
return Response.ok(apiToken, MediaType.TEXT_PLAIN).build();
}
Alternatively you also could make your method return just a String:
#GET
#Produces(MediaType.TEXT_PLAIN)
public String getToken() {
String apiToken = ...
return apiToken;
}
JAX-RS does not require Request or Response in specific format for text,json, xml, or html fallowing a schema . But They all have to be well formated in according to their specifications.
You can send text response like this in jersey
like this
return Response.ok().entity("uHN2cE7REfZz1pD17ITa").build();
I am new to swagger myself So i don't know if the Response in question can be changed or not . But There is no restriction from jersey side

Is #Consumes annotation required or optional in the DELETE method Jersey

I'm new in jersey rest service and I want to understand in this example the utility of adding #Consumes annotation to a delete method in this case this is the code it's work well (in a video ), is the #Consumes annotation optional here ? Thanks in advance
#path("activities")
public class ActivityResource {
#DELETE
#Path("{activityId}")
#Consumes(MediaType.APPLICATION_JSON)
#Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
public Response delete(#PathParam("activityId")String activityId) {
activityRepository.delete(activityId);
return Response.ok().build() ;
}
}
Is the #Consumes annotation optional here ?
Yes, I would even say that it is not needed as you have only one parameter and it is a PathParam which means that it will be extracted from the path.
The annotation #Consumes is used to indicate the JAX-RS implementation how to dynamically parse/deserialize the body of your request in order to have it as parameter in a more convenient type.
For example:
#POST
#Consumes("application/xml")
public void registerUser(User user) {
...
}
In this example, we indicate that the body of the request is of type application/xml, the JAX-RS implementation will then parse the body's content as an XML to finally get an instance of User.
NB: The HTTP method used has no effect on whether or not #Consumes is needed, only the need to parse the body matter.
A DELETE should not be interested in anything that is in the request body. It should only identify the resource to be deleted based on the URI.
Remove the #Consumes, it is wrong here.
Also think about returning a HTTP status 204 No Content instead of 200 OK. After deleting a resource, there is nothing to return. You should also remove the #Produces because of this.

Advice on http-method overloading in REST

I've used a regular expression in #Path to achieve overloading and at first I thought it was really neat, but overloading methods is usually not good practice. Does the same apply to RESTful web services? Is there a better way to achieve this using JAX-RS?
So I can now call my getProject REST service by /project/ProjectNumber1000 or /project/12345
#Path("/project")
public class ProjectPropertiesResource
{
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/{name : [a-zA-Z]+}")
public Response getProjectPropertiesByName(#PathParam("name") String name)
{
...
}
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/{id : \\d+}")
public Response getProjectPropertiesById(#PathParam("id") long id)
{
...
}
}
You can do it, however, only one of the overloads should actually return response body with a 200. The other overloads should return a 303 redirect to the URI that returns the body.
This will ensure that caches only have one copy of the resource and if you do PUT or POST on the main URI you will invalidate the one copy. Otherwise, you can start to get inconsistent results due to different versions existing in the cache.

Categories