JAX-RS: Providing a "default" Sub-resource locator - java

I am using JAX-RS to develop a RESTful API. A simplified version of my API is as follows:
GET /appapi/v1.0/users
POST /appapi/v1.1/users
... ... and so on
As you can see, the pattern follows {api_name}/v{major_version}.{minor_version}/{rest_of_the_path}.
I have an additional requirement:
If the version is not specified, then the latest version should be used by default - i.e.,
GET /appapi/users should be equivalent to GET /appapi/v1.1/users (assuming 1.1 is the latest version of users).
This is how I have implemented this using JAX RS.
#Path("/appapi")
public class AppApiRootResource {
#Path("/{version: [v]\\d+[[.]\\d+]?}/")
public AppApiSubResource getVersionedSubResource
(#PathParam("version") String version){
System.out.println("Version: "+version);
String versionString = version.substring(1); //Discard the leading 'v'
String majorVersion = "";
String minorVersion = "0";
if(versionString.contains(".")){
String [] splits = versionString.split(".");
majorVersion = splits[0];
minorVersion = splits[1];
} else {
majorVersion = versionString;
}
return SubResourceFactory.getSubResource(majorVersion, minorVersion);
}
#Path("/{^([v]\\d+[[.]\\d+]?)}/") //Is This Correct??
public AppApiSubResource getDefaultApiResource(){
/*
* Need help defining the regular expression here
* so that this is used for anything that doesn't match
* the "version" regexp above
*/
System.out.println("API version not specified; Using default version");
return SubResourceFactory.getLatestVersionSubResource();
}
}
My Sub-Resource class is defined as follows. I have sub-classes of this class to deal with multiple versions of the API.
The implementation of SubResourceFactory is not relevant for the purposes of this discussion. It just returns an instance of AppApiSubResource or its sub-class
public class AppApiSubResource {
/**
* Create a new user
*/
#POST
#Path("users")
#Consumes(MediaType.APPLICATION_XML)
public Response createUser(String newUser, #Context UriInfo uriInfo) {
URI uri = uriInfo.getAbsolutePathBuilder().path("10")).build();
return Response.created(uri).build();
}
/**
* Get a user
*/
#GET
#Path("users/{id}")
#Produces(MediaType.APPLICATION_XML)
public Response getUser(#PathParam("id") String userId
) {
return Response.ok().entity("<user></user>").build();
}
}
Problem statement:
If I comment out getDefaultApiResource(), then things work as expected when there is a version specifier in the API. However, if I un-comment getDefaultApiResource(), it is always being invoked, irrespective of whether I have the v1.0 in the request or not.
Also, if I un-comment getDefaultApiResource(), I get a 404 when I do a GET /appapi/users (i.e, without the version specifier); but things work fine if I use GET /appapi/v1.0/users (i.e., with a version specifier)
So, how do I set up my sub-resource locator paths/regexps such that the method is invoked when there is no version specifier in the request?
I'm using Restlet framework, but this question is implementation-agnostic.

The reason getDefaultApiResource always gets invoked is that its URI pattern is the same regular expression as that of getVersionedSubResource, and when more than one pattern matches the request URI, the longest pattern (literally, the one with the most characters) wins. ("version: " is not considered part of the pattern.) See section 3.7.2 of the JAX-RS specification for all the details.
I've never tried this, but I think #Path("") will do what you want.
By the way, it appears your regular expression isn't quite right:
[v]\\d+[[.]\\d+]?
That says "lowercase v, followed by one or more digits, optionally followed by a single period, digit, or plus sign." It should be:
[v]\\d+([.]\\d+)?

Related

How are java annotations like "#PathParam(" ") " processed in java? Where can we find the source code of that particular annotation processor?

I have a Service File in jax-rs that has a method which accepts parameters through #QueryParam Annotation.
The annotation clearly is processed somewhere in the compilation process. Which is the class that performs the modification operation? Like for QueryParam to fetch Parameters from the URL.
#PUT
#Path("/score")
#Produces("application/json")
public String update(#QueryParam("wins") int wins,
#QueryParam("losses") int losses,
#QueryParam("ties") int ties) {
Hello.wins = wins;
Hello.ties = ties;
Hello.losses = losses;
String pattern =
"{ \"wins\":\"%s\", \"losses\":\"%s\", \"ties\": \"%s\"}";
return String.format(pattern, Hello.wins, Hello.losses, Hello.ties );
}
Thanks, I found the solution myself. There are InjectableProvider classes for each annotation present in the jersey library which process the incoming annotations.

Is it possible to store pathparams as a list?

I have a Rest Service that I want to respond to requests with the following paths
1) /v1/config/type/service
2) /v1/config/type/service, service2
What I'd like is to be able to store the path param serviceName as a List where each element is delimited by a comma. For example, if someone types v1/config/foo/bar1,bar2,bar3 I'd like serviceName to be a List with 3 elements (bar1, bar2, bar3). Right now it just returns a list with 1 element that contains all three service strings. Is that even possible? Or is that something I'll simply have to parse. The code I have is shown below, it's pretty rough as I'm in the beginning stages of the project:
#ApplicationPath("/")
#Path("/v1/config")
public class ServiceRetriever extends Application {
#GET
#Produces(MediaType.TEXT_PLAIN)
public String getHelloWorld() {
return "Hello World";
}
#GET
#Path("{type}/{serviceName}")
#Produces("application/zip")
public Response getServices(#PathParam("type") String type, #PathParam("serviceName")List<String> serviceNames,
#QueryParam("with_config") boolean withConfig, #QueryParam("with_drive") boolean withDriver) throws IOException
{
//some random file i made to test that we can return a zip
File file = new File(System.getProperty("user.home")+"/dummy.zip");
System.out.println(serviceNames.size()); //returns 1
//we can change the zip file name to be whatever
return Response.ok(file).header("Content-Type","application/zip").
header("Content-Disposition", "attachment; filename="+file.getName()).build();
}
The problems is that you have to alter the deserialization process of that variable. Typically only query parameters are lists so this might not be compatible with some libraries.
You could:
Capture the parameter as a string and parse it internally via helper method (obvious)
Create your own annotation like #PathParamMutli and return Arrays.asList(parameter.split(","));. Ideally you should have access to the framework source code and branching privileges.
Use a query parameter instead

How to get path parameters and matrix parameters from the following uri string? Uri is /flights/flightid;numberofseats=2/date

Am trying to separate the path param and matrix param from the following uri string in jersey.
/flights/flightid;numberofseats=2/date.
Things I wanna separate following parameters using jersey
Flightid
numberofseats
date
I tried all this code to separate but am failing miserably.
#GET
#Path(value = "/flight/{flightid}/{date}")
#Produces(MediaType.TEXT_PLAIN)
public Response bookFlight(#MatrixParam("numberofseats") int numberOfSeats,
#PathParam(value = "flightid") int flightid,
#PathParam(value = "date") String date) {
//Logic
}
All matrix param examples I know carry them at the and of the URI. In your case the regex derived from your Path would eat up all URI parts and leave nothing for the #PathParam to capture. See section 3.7.3 of the JAX-RS 2.0 Spec.
If possible, move theses parameters to the end.
Or parse flightid (which would contain the matrix params, I think) in your own code.

Partial matching of request with RESTITO

Can I match a REST-request content without a exact match of content with test framework RESTITO? Lets say I have a timestamp from now in my request but I don't want to match with this specific value (I probably don't know it anyway)?
If your URL looks like
http://example.com/api/endpoint?weight=100&timestamp=1413108487
then you can to the following:
match(get("/api/endpoint"), parameter("weight", "100"))
It will just ignore all the timestamps. If timestamp is part of URI:
http://example.com/api/endpoint/1413108487/bla
then you can use matchesUri() e.g.:
match(method(Method.GET), matchesUri(new Regexp("/api/endpoint/[0-9]+/bla")))
And of course you always can write a custom condition, where you can do any checks on the request you want and return a boolean e.g.:
Predicate<Call> uriEndsWithA = new Predicate<Call>() {
#Override
public boolean apply(final Call input) {
return input.getUri().endsWith("a");
}
};
whenHttp(server).match(custom(uriEndsWithA)).then(ok());

Spring REST Controller understanding arrays of strings when having special characters like blank spaces or commas

I am trying to write a Spring REST Controller getting an array of strings as input parameter of a HTTP GET request.
The problem arises when in the GET request, in some of the strings of the array, I use special characters like commas ,, blank spaces or forward slash /, no matter if I URL encode the query part of the URL HTTP GET request.
That means that the string "1/4 cup ricotta, yogurt" (edit which needs to be considered as a unique ingredient contained as a string element of the input array) in either this format:
http://127.0.0.1:8080/[...]/parseThis?[...]&ingredients=1/4 cup ricotta, yogurt
This format (please note the blank spaces encoded as + plus, rather than the hex code):
http://127.0.0.1:8080/[...]/parseThis?[...]&ingredients=1%2F4+cup+ricotta%2C+yogurt
Or this format (please note the blank space encoded as hex code %20):
http://127.0.0.1:8080/[...]/parseThis?[...]&ingredients=1%2F4%20cup%20ricotta%2C%20yogurt
is not rendered properly.
The system does not recognize the input string as one single element of the array.
In the 2nd and 3rd case the system splits the input string on the comma and returns an array of 2 elements rather than 1 element. I am expecting 1 element here.
The relevant code for the controller is:
#RequestMapping(
value = "/parseThis",
params = {
"language",
"ingredients"
}, method = RequestMethod.GET, headers = HttpHeaders.ACCEPT + "=" + MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public HttpEntity<CustomOutputObject> parseThis(
#RequestParam String language,
#RequestParam String[] ingredients){
try {
CustomOutputObject responseFullData = parsingService.parseThis(ingredients, language);
return new ResponseEntity<>(responseFullData, HttpStatus.OK);
} catch (Exception e) {
// TODO
}
}
I need to perform HTTP GET request against this Spring controller, that's a requirement (so no HTTP POST can be used here).
Edit 1:
If I add HttpServletRequest request to the signature of the method in the controller, then I add a log statement like log.debug("The query string is: '" + request.getQueryString() + "'"); then I am seeing in the log a line like The query string is: '&language=en&ingredients=1%2F4+cup+ricotta%2C+yogurt' (So still URL encoded).
Edit 2:
On the other hand if I add WebRequest request to the signature of the method, the the log as log.debug("The query string is: '" + request.getParameter("ingredients") + "'"); then I am getting a string in the log as The query string is: '1/4 cup ricotta, yogurt' (So URL decoded).
I am using Apache Tomcat as a server.
Is there any filter or something I need to add/review to the Spring/webapp configuration files?
Edit 3:
The main problem is in the interpretation of a comma:
#ResponseBody
#RequestMapping(value="test", method=RequestMethod.GET)
public String renderTest(#RequestParam("test") String[] test) {
return test.length + ": " + Arrays.toString(test);
// /app/test?test=foo,bar => 2: [foo, bar]
// /app/test?test=foo,bar&test=baz => 2: [foo,bar, baz]
}
Can this behavior be prevented?
The path of a request parameter to your method argument goes through parameter value extraction and then parameter value conversion. Now what happens is:
Extraction:
The parameter is extracted as a single String value. This is probably to allow simple attributes to be passed as simple string values for later value conversion.
Conversion:
Spring uses ConversionService for the value conversion. In its default setup StringToArrayConverter is used, which unfortunately handles the string as comma delimited list.
What to do:
You are pretty much screwed with the way Spring handles single valued request parameters. So I would do the binding manually:
// Method annotations
public HttpEntity<CustomOutputObject> handlerMethod(WebRequest request) {
String[] ingredients = request.getParameterValues("ingredients");
// Do other stuff
}
You can also check what Spring guys have to say about this.. and the related SO question.
Well, you could register a custom conversion service (from this SO answer), but that seems like a lot of work. :) If it were me, I would ignore the declaration the #RequestParam in the method signature and parse the value using the incoming request object.
May I suggest you try the following format:
ingredients=egg&ingredients=milk&ingredients=butter
Appending &ingredients to the end will handle the case where the array only has a single value.
ingredients=egg&ingredients=milk&ingredients=butter&ingredients
ingredients=milk,skimmed&ingredients
The extra entry would need to be removed from the array, using a List<String> would make this easier.
Alternatively if you are trying to implement a REST controller to pipe straight into a database with spring-data-jpa, you should take a look at spring-data-rest. Here is an example.
You basically annotate your repository with #RepositoryRestResource and spring does the rest :)
A solution from here
public String get(WebRequest req) {
String[] ingredients = req.getParameterValues("ingredients");
for(String ingredient:ingredients ) {
System.out.println(ingredient);
}
...
}
This works for the case when you have a single ingredient containing commas

Categories