I am getting following for two completely different URLs and I cannot explain why:
RESTEASY002142:
Multiple resource methods match request "GET /devices/distinctValues/3".
Selecting one.
Matching methods:
[public javax.ws.rs.core.Response
mypackage.DevService.getDistinctValues(int) throws java.lang.Exception,
public javax.ws.rs.core.Response
mypackage.DevRESTService.getDevice(int,java.lang.String)
throws java.lang.Exception]
This warning should not come up, since the URLS are completely different. If anybody knows why this is happening:
URLs for both methods:
getDevice:
#GET
#Path("devices/{customerId}/{deviceIds}")
#Produces({ "application/json" })
getDistinctValues:
#GET
#Path("devices/distinctValues/{customerId}")
#Consumes("application/json")
#Produces("application/json")
The warning happens because your request string can match both path templates. The request "devices/distinctValues/3"
matches devices/distinctValues/{customerId} in that customerId = "3"
matches devices/{customerId}/{deviceIds} in that customerId = "distinctValues" and deviceIds = "3".
There is no type resolution and since your request is a String there is no way to tell customerId that it cannot accept "distinctValues".
As a workaround, you can either specify a regex as shown in the linked question, or use the RESTEasy proxy framework which is basically a shared interface that both server (your JAX-RS resource) and client use, and then you have a common language with type resolution. Note that there is a typo in the example of the docs.
Related
We were using Resteasy 3.0.9 for our JAX-RS webservices, and recently switched to 3.0.19, where we started to see a lot of RESTEASY002142: Multiple resource methods match request warnings.
For example, we have methods like:
#Path("/{id}")
public String getSome(UUID id)
#Path("/{id}")
public String getSome(int id)
I'm not sure how it worked in 3.0.9, probably, we just were very lucky as Resteasy seems to select first method from all candidates (and 3.0.19 sorts candidate methods).
One solution is to explicitly specify regex: #Path("/{id : [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}")
But is there a way to somehow tell Resteasy to look into method parameters and construct appropriate regex automatically?
As far as I know, RESTEasy won't take the method parameter type into consideration when matching a request. According the JSR-339 (that RESTEasy implements), this is how the request matching process works:
A request is matched to the corresponding resource method or sub-resource method by comparing the normalized
request URI, the media type of any request entity, and the requested response
entity format to the metadata annotations on the resource classes and their methods. If no matching resource
method or sub-resource method can be found then an appropriate error response is returned. [...]
The JAX-RS implementations must match the requested URI with the #Path annotation values. In the #Path annotation value you can define variables, that are denoted by braces ({ and }).
As part of the request matching, the JAX-RS implementation will replace each URI template variable with the specified regular expression or ([ˆ/]+?) if no regular expression is specified.
To address the situation you mentioned in your question, you should specify a regex to match UUIDs on one resource method:
#Path("{id : [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}")
And you also may consider a regex to match integers on the other resource method:
#Path("{id : \\d+}")
#GET
#Produces(MediaType.APPLICATION_JSON)
public String getRscSubTypes(){
return AddResourceMysql.getRscSubType();
}
#GET
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public String getDbTypes() {
return AddResourceMysql.getDbType();
}
This is returning the following exception:
org.glassfish.jersey.server.model.ModelValidationException:
Validation of the application resource model has failed during application initialization.
Can you please help me?
How request matching works
Definitely, you can have more than one method annotated with #GET in the same class. However, your current definition is ambiguous.
For more clarification, have a look at the JAX-RS 2.0 specification:
3.7.2 Request Matching
A request is matched to the corresponding resource method or sub-resource method by comparing the normalized request URI, the media type of any request entity, and the requested response entity format to the metadata annotations on the resource classes and their methods. [...]
How to fix it
You need change your method annotations to ensure you have no ambiguity. To do it, you can play with the following annotations:
HTTP method: #GET, #POST, #PUT, #DELETE, #HEAD and #OPTIONS
Request URI: #Path
Media type of any request entity: #Consumes
Requested response entity format: #Produces
To fix it, for example, you can just add a #Path annotation with different values to each method.
If you want to define multiple resource methods, which handle GET requests for the same MIME type, within the same class, you have to specify a different subpath for the methods:
#Path("rcsubtypes")
#GET
#Produces(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public String getRscSubTypes()
{
return AddResourceMysql.getRscSubType();
}
#Path("dbtypes")
#GET
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public String getDbTypes()
{
return AddResourceMysql.getDbType();
}
The path, specified in the #Path annotation of this method, is a subpath of the path specified in the #Path annotation of the class, which is a subpath of the path you defined for your application.
To explain your behaviour, that always the second method is called, if there is no #Consumes annotation present on the first method: #Consumes defines which media type (set in the Content-Type header of the request) can be accepted by the method. Without a #Consumes annotation all requests are accepted, but i think, if a method specifies an accepted media-type, it will be preferred.
The matching section in the jersey documentation: 3.1. Root Resource Classes
In JavaEE documentation for JAX-RS webservices, I came across below statement:
http://docs.oracle.com/javaee/6/tutorial/doc/gilik.html
If the URI path template variable cannot be cast to the specified
type, the JAX-RS runtime returns an HTTP 400 (“Bad Request”) error to
the client. If the #PathParam annotation cannot be cast to the
specified type, the JAX-RS runtime returns an HTTP 404 (“Not Found”)
error to the client.
#Path("/{username}")
public class MyResourceBean {
...
#GET
public String printUsername(#PathParam("username") String userId) {
...
}
}
So if request has parameter "username" and if it cannot be type-casted to String then we get 400 error, then when we will get 404 error? I am new to web-services, please help me in understanding this.
Let's say you had something like this instead:
#Path("/{userId}")
public class MyResourceBean {
...
#GET
public String printUsername(#PathParam("userId") int userId) {
...
}
}
if the URI for your request is something like /abc, then you'd get a 400 because abc cannot be cast into an int. Now, if your request URI is '/', you'll get a 404 because there's no resource method associated with this URI.
URIs are used as resource locators in this case. These rules match the requirements of the HTTP specification.
Say JAX-RS exposes a single end-point matching on the URL http://host/app/{someInt} and {someInt} must be converted to an integer.
http://host/app does not match this so it returns 404 - not found.
http://host/app/foo matches the pattern but foo cannot be parsed to an integer so it returns 400 - bad request.
In order to deal with different versions of a content-type i am trying to use the accept-parameters of the "Accept*" headers (RFC 2616).
Accept: application/vnd.mycompany.mytype;version=2 , application/vnd.mycompany.mytype;version=1;q=0.1
The problem is that Jax-RS annotations do not support Accept-parameters...
#GET
#Produces("application/vnd.test;version=1")
public Response test1() {
return Response.ok("Version 1", "application/vnd.test").build();
}
#GET
#Produces("application/vnd.test;version=2")
public Response test2() {
return Response.ok("Version 2", "application/vnd.test").build();
}
Results in a media type conflict exception:
Producing media type conflict. The resource methods public javax.ws.rs.core.Response test.resources.TestResource.test2() and public javax.ws.rs.core.Response test.resources.TestResource.test1() can produce the same media type
Maybe, this exception only related to my JAX-RS framework (Jersey), but i'm afraid this is due to the JSR311 which is not explicit about accept-parameters.
By now, i'm using content-types which holds the version within their names, but i found this solution pretty uggly.
#GET
#Produces("application/vnd.test-v1")
public Response test() {
return Response.ok("Version 1", "application/vnd.test-v1").build();
}
Do you have any ideas about how to deal with accept-parameters ?
EDIT
I think i wasn't clear enough.
I want to automatically route the request to specific methods.
These methods are versioned and correspond to a specific version of the returned content-type.
JAX-RS current implementation prevents me to use accept-parameters to route the request (to the corresponding method).
greenkode suggest that i manage the version accept-parameter within a dispatching method (using #HeaderParam("Accept")).
This solution would end-up in re-writing the content negociation logic which is embeded in the framework (and described in JSR 311).
What can i do to use both accept-parameter and content-negociation logic from JAX-RS ?
Maybe a solution is to use another framework (I only worked with Jersey by Now). But i don't know which one.
The JAX-RS specification does not explicitly state anything about ignoring Accept header parameters. But the only parameter for which handling is definitely defined is quality (q). This is a possible area for improvement as it seems to have lead to ambiguity (or outright bugginess) in the Jersey implementation. The current version of Jersey (1.17) does not take Accept header parameters into consideration when matching incoming requests to resource methods, which is why you are getting the error:
SEVERE: Producing media type conflict. The resource methods ...
For the resource:
#GET
#Produces("application/vnd.test;version=1")
public Response test1() {
return Response.ok("Version 1", "application/vnd.test").build();
}
#GET
#Produces("application/vnd.test;version=2")
public Response test2() {
return Response.ok("Version 2", "application/vnd.test").build();
}
It would appear that Jersey performs a 'uniqueness' check based on the Accept header 'type/subtype', totally omitting any parameters. This can be confirmed by testing with various pairs of headers on the 'matching' resource methods:
Resource 1 Resource 2
----------------------------------------
text/html;q=0.4 text/html;q=0.8
text/html text/html;q=0.2
text/html text/html;qs=1.4
text/html;qs=1.4 text/html;qs=1.8
text/html;level=1 text/html;level=2
text/html;foo=bleh text/html;bar=23
All fail with the same error. If the assumption were made that only the quality parameter is ever sent, then it would make sense to only match on 'type/subtype', because this kind of request is nonsensical:
Accept: text/html;q=0.8, text/html;q=0.4, text/html
Aka, quality parameters only make sense when you are dealing with a mix of possible content types. However, this sort of limited matching fails when non-quality parameters or additional parameters are being sent:
Accept: text/html;version=4.0;q=0.8, text/html;version=3.2;q=0.4
So what are the possible solutions?
Intercept the high level request based off 'type/subtype', then route to more appropriate method (you've indicated you do not want to do this)
Modify your expected headers. For example 'application/vnd.mycompany.mytype+v2' and 'application/vnd.mycompany.mytype+v1'. No other changes would be required and you could keep on using Jersey
Switch frameworks. RESTEasy happens to handle your scenario with ease.
With RESTEasy, and resource:
#Path("/content/version")
public class ContentVersionResource {
#GET
#Produces("application/vnd.test;version=1")
public Response test1() {
return Response.ok("Version 1", "application/vnd.test").build();
}
#GET
#Produces("application/vnd.test;version=2")
public Response test2() {
return Response.ok("Version 2", "application/vnd.test").build();
}
}
A successful match is made with the following Accept header:
Accept: application/vnd.test;version=1;q=0.3, application/vnd.test;version=2;q=0.5
Response: Version 2
And this:
Accept: application/vnd.test;version=1;q=0.5, application/vnd.test;version=2;q=0.3
Response: Version 1
You can download and test with this sample project. Git, Maven and JBoss 7.x required
Unless I'm missing something. JAX-RS does support Accept parameters. look at the #Consumes("*/*") annotation. Also, The exception you're getting with media type conflict occurs because you have two GET methods at the same url. annotate the test2() method with #Path("test2"), then send your GET request to url/test2 instead. that should get rid of that error.
EDIT
You can inject the Value of the Accept header using #HeaderParams. Here's a sample of what I did.
#Path("/conneg")
public class ConnNeg {
#GET
#Produces("application/vnd.test;version=1")
public Response test1(#HeaderParam("Accept") String header) {
System.out.println(header);
return Response.ok("Version 1", "application/vnd.test").build();
}
}
passing the request
Accept: application/vnd.test;version=2 , application/vnd.test;version=1;q=0.1
this will print
application/vnd.test;version=2 , application/vnd.test;version=1;q=0.1
You can then handle it manually. Is this what you're looking for?
With Jersey framework, the Accept header of the HTTP request declared what is most acceptable. If a resource class is capable of producing more that one MIME media type then the resource method chosen will correspond to the most acceptable media type as declared by the client.
In your case, if the accept header is
Accept: application/vnd.mycompany.mytype;version=2
then the method test1() will be invoked.
If it is
Accept: application/vnd.mycompany.mytype;q=0.9 version=2, application/vnd.mycompany.mytype;version=1
the latter one will be called.
More than one media type may be declared in the same #Produces declaration, for example:
#GET
#Produces({"application/vnd.mycompany.mytype; version=2", "application/vnd.mycompany.mytype; version=1"})
public Response test() {
return Response.ok("").build();
}
the test(9 method will be called if either of the 2 mediatypes is acceptable. if both are acceptable, the first will be invoked.
Hope it helps!
I'm trying to figure out how #Consumes works here.
I have a simplified resource that looks like the below, and I only want this resource to consume "application/vnd.myApp+xml".
#Path("/app")
#Consumes("application/vnd.myApp+xml")
#Produces("application/vnd.myApp+xml")
public class AppResource {
#POST
public Response postStuff() {
...
}
}
I have the following testcases:-
public class AppResourceTest extends JerseyTest {
#Test
public void testApp() {
// #1: Works fine
ClientResponse response = resource().path("app")
.accept("application/vnd.myApp+xml")
.post(ClientResponse.class);
...
// #2: Throws a 415 Unsupported Media Type
ClientResponse response = resource().path("app")
.accept("application/vnd.myApp+xml")
.type("text/plain")
.post(ClientResponse.class);
...
// #3: Works fine
ClientResponse response = resource().path("app")
.accept("application/vnd.myApp+xml")
.type("application/vnd.myApp+xml")
.post(ClientResponse.class);
...
}
}
From the 3 tests above, #2 and #3 work as expected.
As for #1, if I don't set the content-type, why doesn't it throw a 415 too?
Based on #Consumes api (http://jsr311.java.net/nonav/releases/1.0/javax/ws/rs/Consumes.html) and the HTTP type spec (http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1) coupled with the behavior you are seeing I think it is safe to conclude the following Jersey implementation:
If the Content-Type is NOT set by the client Jersey does NOT default but allows it to pass through any/all #Consumes annotions.
When multiple #Consumes {different types} are set for the URI and the client has NOT set the Content-Type then Jersey will default to the first #Consumes annotation or first type in a list of acceptable types.
When the Accepts header value is set Jersey will find the best fitting method to execute. If multiple methods are a best fit it will default to the first defined.
In conclusion the #Consumes only acts as a filter if and ONLY if the client sets the Content-Type otherwise Jersey will attempt to find the best fitting match. This does match the HTTP spec:
Any HTTP/1.1 message containing an entity-body SHOULD include a Content-Type header field defining the media type of that body. If and only if the media type is not given by a Content-Type field, the recipient MAY attempt to guess the media type via inspection of its content and/or the name extension(s) of the URI used to identify the resource. If the media type remains unknown, the recipient SHOULD treat it as type "application/octet-stream".
If the goal is to have the #Consumes to act as a white list then a servlet filter could be used to default the Content-Type on requests where none is set.
You should specify the type- for example:
ClientResponse res =
service.path("accounts")
.type("application/vnd.dsu.account+json")
.post(ClientResponse.class,ent);
Based on the docs it seems using #Consumes at the class level does not explicitly override method level definitions (which default to */*), so it stands to reason it may be working in an additive manner...
Have you tried applying the same #Consumes on the method definition?