How do I support localization with Jersey? - java

I am designing a REST API that I would like to be localizable in the future.
Therefore I am defining the URLs to be of the form
/en/<resource_url>
With the intention of being able to support
/fr/<resource_url>
In the future if need be.
However I only want to define each resource url service once. Therefore I figure I need to get the URL parsed and rewritten without the language piece of the URL before it is matched to services. Finally that language should be made available to the services somehow for them to localize if necessary.
How can I achieve this?
I am using Jersey 1.17 inside Jetty container embedded in a larger server process.

You can make the /en/ or the /fr/ part a variable. Then set your locale to the value of the variable. Here's an example:
#Path("/{locale}/username")
public class UserResource {
#GET
#Produces("text/xml")
public String getUser(#PathParam("locale") String locale) {
...
}
}
But that may not be the best way to go about it. After answering, I found this other SO question that is a better way to solve the problem: Getting the client locale in a jersey request With this way, you don't need to add this to the URL. Just make the client set a header.

Related

Map spring rest controller to a path without the servlet context

I have a spring boot application with the following context path:
server.servlet.context-path:/api
I need to write a rest controller that's mapped to
http://localhost:8080/logout
instead of http://localhost:8080/api/logout
Is there a way to achieve this? changing the "server.servelt.context-path" value is not an option.
this is what I tried and didn't work:
#GetMapping(value="../signout"){
public void logout(){
}
Nero, you say you can't change the "server.servlet.context-path" value. I bet you say this because you don't want to break the API, but I think you can manage to change this without breaking the API. Set the context-path to blank, which is permitted. Then in your application change the "api" mapping, which I assume is currently "/", to "api".
Change server.servlet.context-path:/api to server.servlet.context-path:/ or maybe server.servlet.context-path: (no slash). (Supposedly this is the default so you might just remove this entry altogether.)
Somewhere in your application change #RequestMapping("/") to #RequestMapping("/api").
Now you can also have #GetMapping(value="/signout") and you will have resources at http://localhost:8080/logout and http://localhost:8080/api.
I don't know what mapping annotations you happen to be using, but hopefully this is clear enough.
It may not be possible within that application to go outside its context root. Maybe you can create a separate Rest service app for that particular url and take it from there.

HATEOAS JAX-RS Link-Headers

I am currently trying to build a RESTful API using raw JAX-RS. I have learned that when building REST APIs, there is the principle called HATEOAS(Hypermedia as the engine of application state). In my class we used Link Headers to tell the client, how to further progress the application. I have managed to implement all basic functionality and can access the server after deploying the application to a tomcat server.
My question now is, how do I add a header-link that contains a wildcard for the user to fill in, for example an id?
So far I have tried
#Path("/resources")
#Produces(MediaType.APPLICATION_JSON)
public Response listAllResources()
{
List<TestResource> resources = ...
// get stuff from database
return Response.ok(resources)
.link(UriInfo.getAbsolutePathBuilder().path("{id}").build(), "edit")
.build;
}
After I try to access the above defined path, I get an error message that the template variable id is undefined.
I can't find any helpful resource that shows me how to create a link header that looks like:
link: <http://example.com/api/resources/{id}>; rel: "edit"
I hope my question was clear enough since this is my first question on stackoverflow :)
Thanks in advance!
I found out, that links like in my example http://example.com/api/resources/{id} aren't possible by JAX-RS because the UriBuilder tries to resolve any URL part that's surrounded by curly braces. So just use like http://example.com/api/resources/:id, if you want to give an Uri Template. Unfortunately the client then has to do something like a String.replace() to actually "create" a valid URI.

Can you specify preferred default media type for a single path in Spring MVC?

I have a Jersey application which has been converted to Spring MVC. One piece of functionality that I don't see a way to port directly over is the ability, per path, to specify the preferred media type if none is specified. In Jersey, I could specify the "qs" property on the media type, and it would use that to determine which response type to send if none were specified (or if multiple options were specified in the Accept header, I believe this value was multiplied by the quality scores specified).
#Produces("application/json")
#GET
#Path("/some/path")
public Response preferredResponseType() {
//Implementation goes here
}
#Produces({"application/schema+json;qs=0.9"})
#GET
#Path("/some/path")
public Response otherResponseType() {
//Implementation goes here
}
In this example, if I do a GET request against /some/path with no Accept header, it will return the application/json response.
I don't see any easy way to do this in Spring MVC, particularly not if I want to restrict the default to applying to just that one endpoint (there are other endpoints in the app that should have a different preferred default). I do see that there is a way to globally set a default content type (per the "defaultContentType" and "defaultContentTypeStrategy" methods in ContentNegotiationConfigurer), but that does not easily address the per-path use case.
Is there an easy way to achieve per-path media type defaults different from the application global default?
Spring issue 19050 requests this functionality. Per the conversation there, it looks like there is no simple way to declaratively specify the default content type to use. Furthermore, the Spring team has closed the issue with a decision not to implement this functionality.
The "defaultContentTypeStrategy" allows to provide your own ContentNegotiationStrategy to use. It has access to the full request so you can make path based decisions possibly with the an AntPathMatcher to support patterns easily.

CorsFilter setAllowedOrigins(*) redundancy

Is there any reason to write
corsFilter.setAllowedOrigins(new HashSet<String>(Arrays.asList("*")));
where the definition of allowedOrigins in the Restlet framework is
private Set<String> allowedOrigins = SetUtils.newHashSet("*");
Another question - when I write the above line, I get an error running my app.
For some reason I get duplicate origin, and the client refuses to accept it - in the request I can see "*" and the domain name where I sent the request from.
How does this duplication can happen, and what is the best way to deal with it?
You're right, there is no need to provide this value as it is already the default one. Could you tell me where you read that such value must be set?
I don't understand what really happens with the second part of your question, as I'm not able to reproduce it (with CorsFilter, or CorsService).
Could you try using the CorsService instead? This service helps to configure the Cors feature, and is integrated in the list of services either of the Application, or the Component, for example in the constructor of the application:
public TestCorsApplication() {
CorsService corsService = new CorsService();
corsService.setAllowedCredentials(true);
corsService.setSkippingResourceForCorsOptions(true);
getServices().add(corsService);
}

Easy REST resource versioning in JAX-RS based implementations?

Best practice for REST resource versioning is putting version information into Accept/Content-Type headers of HTTP request leaving URI intact.
Here is the sample request/response to REST API for retrieving system information:
==>
GET /api/system-info HTTP/1.1
Accept: application/vnd.COMPANY.systeminfo-v1+json
<==
HTTP/1.1 200 OK
Content-Type: application/vnd.COMPANY.systeminfo-v1+json
{
“session-count”: 19
}
Pay attention that version is specified in MIME type.
Here is another request/response for version 2:
==>
GET /api/system-info HTTP/1.1
Accept: application/vnd.COMPANY.systeminfo-v2+json
<==
HTTP/1.1 200 OK
Content-Type: application/vnd.COMPANY.systeminfo-v2+json
{
“uptime”: 234564300,
“session-count”: 19
}
See http://barelyenough.org/blog/tag/rest-versioning/ for more explanation and examples.
Is it possible to implement this approach easily in Java-targeted JAX-RS based implementations, such as Jersey or Apache CXF?
The goal is to have several #Resource classes with the same #Path value, but serving the request based on actual version specified in MIME type?
I've looked into JAX-RS in general and Jersey in particlaur and found no support for that. Jersey doesn't give a chance to register two resources with the same path. Replacement for WebApplicationImpl class needs to implemented to support that.
Can you suggest something?
NOTE: It is required for multiple versions of the same resource needs to be available simultaneously. New versions may introduce incompatibale changes.
JAX-RS dispatches to methods annotated with #Produces via the Accept header. So, if you want JAX-RS to do your dispatching, you'll need to leverage this mechanism. Without any extra work, you would have to create a method (and Provider) for every media type you wish to support.
There's nothing stopping you from having several methods based on media type that all call a common method to do that work, but you'd have to update that and add code every time you added a new media type.
One idea is to add a filter that "normalizes" your Accept header specifically for dispatch. That is, perhaps, taking your:
Accept: application/vnd.COMPANY.systeminfo-v1+json
And converting that to, simply:
Accept: application/vnd.COMPANY.systeminfo+json
At the same time, you extract the version information for later use (perhaps in the request, or some other ad hoc mechanism).
Then, JAX-RS will dispatch to the single method that handles "application/vnd.COMPANY.systeminfo+json".
THAT method then takes the "out of band" versioning information to handle details in processing (such as selecting the proper class to load via OSGi).
Next, you then create a Provider with an appropriate MessageBodyWriter. The provider will be selected by JAX-RS for the application/vnd.COMPANY.systeminfo+json media type. It will be up to your MBW to figure out the actual media type (based again on that version information) and to create the proper output format (again, perhaps dispatching to the correct OSGi loaded class).
I don't know if an MBW can overwrite the Content-Type header or not. If not, then you can delegate the earlier filter to rewrite that part for you on the way out.
It's a little convoluted, but if you want to leverage JAX-RS dispatch, and not create methods for every version of your media type, then this is a possible path to do that.
Edit in response to comment:
Yea, essentially, you want JAX-RS to dispatch to the proper class based on both Path and Accept type. It is unlikely that JAX-RS will do this out of the box, as it's a bit of an edge case. I have not looked at any of the JAX-RS implementations, but you may be able to do what you want by tweaking one of the at the infrastructure level.
Possibly another less invasive option is to use an age old trick from the Apache world, and simply create a filter that rewrites your path based on the Accept header.
So, when the system gets:
GET /resource
Accept: application/vnd.COMPANY.systeminfo-v1+json
You rewrite it to:
GET /resource-v1
Accept: application/vnd.COMPANY.systeminfo-v1+json
Then, in your JAX-RS class:
#Path("resource-v1")
#Produces("application/vnd.COMPANY.systeminfo-v1+json")
public class ResourceV1 {
...
}
So, your clients get the correct view, but your classes get dispatched properly by JAX-RS. The only other issue is that your classes, if they look, will see the modified Path, not the original path (but your filter can stuff that in the request as a reference if you like).
It's not ideal, but it's (mostly) free.
This is an existing filter that might do what you want to do, if not it perhaps can act as an inspiration for you to do it yourself.
With current version of Jersey, I would suggest an implementation with two different API methods and two different return values that are automatically serialised to the applicable MIME type. Once the requests to the different versions of the API are received, common code can be used underneath.
Example:
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
#GET
#Path("/{id}")
#Produces(MediaType.APPLICATION_JSON)
public VersionOneDTO get(#PathParam("id") final String id) {
return new VersionOneDTO( ... );
}
#GET
#Path("/{id}")
#Produces("application/vnd.COMPANY.systeminfo-v2+json;qs=0.9")
public VersionTwoDTO get_v2(#PathParam("id") final String id) {
return new VersionTwoDTO( ... );
}
If method get(...) and get_v2(...) use common logic, I would suggest to put that in a common private method if it's API related (such as session or JWT handling) or else in a common public method of a Service Layer that you access via inheritance or Dependency Injection. By having two different methods with different return types, you ensure that the structure returned is of correct type for the different versions of the API.
Note that some old client may not specify Accept header at all. That means implicitly that they would accept any content type, thus any version of your API. In practice, this is most often not the truth. For this reason you should specify a weight to newer versions of the API using the qs extension of the MIME type as shown in the #Produces annotation in the example above.
If you are testing with restAssured it would look something like this:
import static com.jayway.restassured.RestAssured.get;
import static com.jayway.restassured.RestAssured.given;
#Test
public void testGetEntityV1() {
given()
.header("Accept", MediaType.APPLICATION_JSON)
.when()
.get("/basepath/1")
.then()
.assertThat()
... // Some check that Version 1 was called
;
}
#Test
public void testGetEntityV1OldClientNoAcceptHeader() {
get("/basepath/1")
.then()
.assertThat()
... // Some check that Version 1 was called
;
}
#Test
public void testGetEntityV2() {
given()
.header("Accept", "application/vnd.COMPANY.systeminfo-v2+json")
.when()
.get("/basepath/1")
.then()
.assertThat()
... // Some check that Version 2 was called
;
}
One possible solution is to use one #Path with
Content-Type:
application/vnd.COMPANY.systeminfo-{version}+json
Then, inside the method of the given #Path you can call the version of the WebService
If you're using CXF, you could use the technique specified here to build a new serialization provider (building off the existing infrastructure) which produces the data in the specific format desired. Declare a couple of those, one for each specific format that you want, and use the #Produces annotation to let the machinery handle the rest of the negotiation for you, though it might also be an idea to support the standard JSON content type too so that normal clients can handle it without needing to grok your specialness. The only real question then becomes what is the best way to do the serialization; I presume you can figure that out for yourself…
[EDIT]: Further digging in the CXF documentation leads to the revelation that both the #Consumes and #Produces annotations are considered to be axes for doing selection. If you want to have two methods that handle the production of the response for different media types, you most certainly can. (You'll have to add the serialization and/or deserialization providers if you're using custom types, but you can do the delegation of the majority of the work to the standard providers.) I'd still like to caution that you should still ensure that the resource indicated by the path should be the same in both cases; to do otherwise is not RESTful.
You should be able to use different classes with the same path provided they consume/produce different media types. So this should work with any jax-rs provider:
#Path("/api/system-info")
#Consumes("application/vnd.COMPANY.systeminfo-v1+json")
#Produces("application/vnd.COMPANY.systeminfo-v1+json")
public class SystemInfoResourceV1 {
}
and
#Path("/api/system-info")
#Consumes("application/vnd.COMPANY.systeminfo-v2+json")
#Produces("application/vnd.COMPANY.systeminfo-v2+json")
public class SystemInfoResourceV2 {
}

Categories

Resources