Variable length endpoints in jax-rs/microprofile client with Quarkus - java

I'm facing the following issue and have been able to find a proper fix.
As a use case example, let's imagine a Rest client that fetches a json object from a server, where the request is the path to the object. This api does not accept query parameters nor bodies, and no catalogue is available.
say that I have the following RestClient:
#Path("/json")
#RegisterRestClient(configKey="json-api")
public interface JsonService {
#GET
#Path("/{MyVariableLengthEndpoint}")
Response getJson(#PathParam("MyVariableLengthEndpoint") ????? endpoint);
}
Examples of requests could be :
/json/employees/Dwight/jobs/assistantRegionalManager/salary
/json/games/theLastOfUs/rating
Passing a string with / characters gets encoded with %. To bypass this, I've tried:
Using the #Encoded annotation
Adding a regex in the pathParameter {MyVariableLengthEndpoint: .*}
Passing a List<PathSegment>
None of those worked.
Is there a proper way to do this ?

You should use Regex in your #Path definition, something like this:
#GET
#Path(“/{varPath : .+}”)
Response getJson(#PathParam(“varPath”) String endpoint);
This will match anything that comes after /json.
For more info search: “JAXRS path Regex”, on Google.

Related

resteasy: #QueryParam to parse nested array structure

I'm using a javascript library called tabulator to display data inside tables on the client side.
The Tabulator js library provides a feature to encode a representation of filters in the query parameters of an ajax request. For example, here's what the query params look like:
https://host/myEndpoint?size=10&page=1&filters%5B0%5D%5Bfield%5D=username&filters%5B0%5D%5Btype%5D=like&filters%5B0%5D%5Bvalue%5D=filteredBy
Here's the same url decoded:
https://host/myEndpoint?size=10&page=1&filters[0][field]=username&filters[0][type]=like&filters[0][value]=filteredBy
If possible, I'd like to have a Resteasy endpoint like this:
#GET
#Path("/myEndpoint")
#Consumes("application/json")
#Produces("application/json")
public Response myEndpoint(#QueryParam("page") Integer page,
#QueryParam("size") Integer size,
#QueryParam("filters") List<Filter> filters) {
resteasy interprets page and size no problem, but filters is always a list of size 0.
My Filter bean has 3 fields named field, type, and value with a constructor with single String argument as described here.
But it doesn't seem like resteasy is recognizing and parsing the filters query param? Is it possible to parse this type of nested array structure query parameters in resteasy?
filters[0][field]=username&filters[0][type]=like&filters[0][value]=filteredB
I'm still hoping that there's a better way, but here's a possible solution that works for me for now at least:
#GET
#Path("/myEndpoint")
#Consumes("application/json")
#Produces("application/json")
public Response myEndpoint(#QueryParam("page") Integer page,
#QueryParam("size") Integer size,
#Context UriInfo uriInfo) {
for(String key : uriInfo.getQueryParameters().keySet()) {
// check if key starts with something like `filters[0]`
// and then parse it however you need.
}
}

#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.

I cannot get to the correct #Path

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.

Restlet response type

How can I return Restlet response in desired format?
I am using the method:
#Get ("json")
public Address sendResponse(){
Address add = getAddress();
return add;
}
Right now I have to explicitly convert java object to a json string as a response to browser. Can't it be taken care by Restlet framework itself?
Spring MVC's Restful implementation can do it. I am looking similar implementation in Restlet too.
In fact, there are two ways to do that with Restlet:
the explicit one using JSON representations. The JSONRepresentation if you use objects from org.json or the JacksonRepresentation if you want JSON / Object mapping. You can find below an example:
#Get ("json")
public Representation sendResponse(){
Address add = getAddress();
return new JacksonRepresentation<Address>(address);
}
the implicit one using converter. In this case, it's the code you gave. You must have in your classpath an appropriate converter such as the one provided by the org.restlet.ext.jackson extension. It will detect that a JSON content needs to be returned and implicitly convert your Address object to a JSON content.
Just for hint, the json media specified in the GET annotation tells Restlet to use the associated method to handle the request when application/json is defined for conneg (content negociation) with the accept header.
Hope it helps you.
Thierry
Try setting the response type to application/json instead of just json. Normally, you need to specify the correct MIME type. As you say, if you set the MIME type correctly, other frameworks will do the conversion automatically.

JAX-RS + RESTEasy service return JSON String without double quote

I'm new to JAX-RS + RESTEasy
What is impeding me is that the service return the JSON String without double quotes.
#Path("/hello")
public class HelloService {
#GET
#Path("say")
#Produces(MediaType.APPLICATION_JSON)
public String say() {
return "Hello";
}
}
When I call "/hello/say" it just returns Hello but what I'm expecting is "Hello"
Googled for several days. I have a piece of Javascript using JQuery which calls the service like this:
$(function(){
$.ajax({
url : "services/hello/say",
context : $('#message'),
success : function(data){
$(this).html(data);
},
error : function(xhr, ajaxOptions, thrownError){
$(this).html(xhr.status + "<br>" + thrownError);
}
});
});
And this is the result
SyntaxError: Unable to parse JSON string
Although the status is 200.
Is there a way to solve this rather than manually adding the double quotes to the string ?
Which JSON implementation are you using together with RESTeasy, Jettison or Jackson?
Just tried with Jettison, same problem. I just looked into the JSON specification and from there it is written, that a value itself is not a valid JSON response (see http://www.json.org/). Either you have an object or an array of values.
I guess that could be the problem as RESTeasy isn't sure what to do as you don't return an Object moreover a single type (e.g. String, Integer, ...).
Wonder what happen if one returns an array.
The problem isn't with the framework, but with your code. According to JAX-RS, the method signature public String say() indicates that the String you return from the method is the entity to be returned in the response. No further processing is done. I'd link the relevant docs, but jcp.org, where they used live, appears to be gone. If you want your RESTEasy to do JSON marshalling for you, you need to return a POJO.
I would suggest you to use Jackson JSON provider. You just need to add the resteasy-jackson-provider.jar in your build path of the project. Assign the annotation, #Produces("application/json") to your method in your Business Service. And you are ready to go. Cheers !!!

Categories