is it possible to call one jax-rs method from another? - java

suppose i have some jax-rs resource class:
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class ResourceA {
#GET
public Something get(#Context UriInfo uriInfo) {
if (...) {
//how to get to ResourceB ?
}
}
}
and i want to conditionally redirect the call to some other jax-rs resource:
public class ResourceB {
#GET
#Path("{identifier}")
public Other get(#PathParam("identifier")String someArg) {
}
}
how do i do this?
note that i dont want this to be visible to the client (so no http redirects) and generally the resource methods i want to redirect to dont share the same signature (they may have path params etc as in the example i gave).
im running jersey 2.6 under apache tomcat (its a spring app, if thats any help)
EDIT - im looking for a jax-rs equivalent of servlet forward. i dont want to do an extra http hop or worry abour instantiating resource classes myself

You can get it using ResourceContext as follows:
#Context
ResourceContext resourceContext;
This will inject the ResourceContext into your Resource. You then get the resource you want using:
ResourceB b = resourceContext.getResource(ResourceB.class);
The Javadoc for ResourceContext is here. You can find a similar question here

I'm not aware of any possibility to do this from a resource method, but if it fits your use case, what you could do is implement your redirect logic in a pre matching request filter, for example like so:
#Provider
#PreMatching
public class RedirectFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) {
UriInfo uriInfo = requestContext.getUriInfo();
String prefix = "/redirect";
String path = uriInfo.getRequestUri().getPath();
if (path.startsWith(prefix)) {
String newPath = path.substring(prefix.length());
URI newRequestURI = uriInfo.getBaseUriBuilder().path(newPath).build();
requestContext.setRequestUri(newRequestURI);
}
}
}
This will redirect every request to /redirect/some/resource to /some/resource (or whatever you pass to requestContext.setRequestUri()) internally, before the resource method has been matched to the request and is executed and without http redirects or an additional internal http request.

Related

How to exclude some url from jersey filter?

I've used jersey to create webservices. I've created request filter using ContainerRequestFilter. I've gone through Jersey Request Filter only on certain URI question but I want to exclude filter for some urls only.
#Provider
public class AuthFilter implements ContainerRequestFilter{
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// business logic
}
}
Name binding filters
Instead of excluding URIs from a global filter, you could consider using a name binding filter to select the endpoints your filter will be bound to.
Also check this answer for some examples with name binding filters.
Global filters
If you are still happy with the global filter approach, you could consider using the UriInfo interface to get details about the requested URI. Use one of the following approaches to get an instance of UriInfo:
Using the #Context annotation:
#Provider
public class AuthFilter implements ContainerRequestFilter {
#Context
private UriInfo info;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
...
}
}
Getting it from the ContainerRequestContext:
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
UriInfo info = requestContext.getUriInfo();
...
}
Once you have the UriInfo instance, you'll have access to a bunch of methods that may be useful:
getAbsolutePath(): Get the absolute path of the request.
getBaseUri(): Get the base URI of the application.
getMatchedResources(): Get a read-only list of the currently matched resource class instances.
getMatchedURIs(): Get a read-only list of URIs for matched resources.
getPath(): Get the path of the current request relative to the base URI as a string.
getPathSegments(): Get the path of the current request relative to the base URI as a list of PathSegment.
getRequestUri(): Get the absolute request URI including any query parameters.
relativize(URI): Relativize a URI with respect to the current request URI.
resolve(URI): Resolve a relative URI with respect to the base URI of the application.
For more details, check the UriInfo documentation.
If the requested URI does not match the URIs you want to apply the filter to, simply use a return instruction:
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
UriInfo info = requestContext.getUriInfo();
if (!info.getPath().contains("secured")) {
return;
}
}
Dynamic binding
Another approach is dynamic binding. It allows you to assign filters and interceptors to the resource methods in a dynamic manner. Name binding, mentioned above, uses a static approach and changes to binding require source code change and recompilation. With dynamic binding you can implement code which defines bindings during the application initialization time.
The following example extracted from the Jersey documentation shows how to implement dynamic binding:
#Path("helloworld")
public class HelloWorldResource {
#GET
#Produces("text/plain")
public String getHello() {
return "Hello World!";
}
#GET
#Path("too-much-data")
public String getVeryLongString() {
String str = ... // very long string
return str;
}
}
// This dynamic binding provider registers GZIPWriterInterceptor
// only for HelloWorldResource and methods that contain
// "VeryLongString" in their name. It will be executed during
// application initialization phase.
public class CompressionDynamicBinding implements DynamicFeature {
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
if (HelloWorldResource.class.equals(resourceInfo.getResourceClass())
&& resourceInfo.getResourceMethod().getName().contains("VeryLongString")) {
context.register(GZIPWriterInterceptor.class);
}
}
}
The binding is done using the provider which implements the DynamicFeature interface. The interface defines one configure method with two arguments, ResourceInfo and FeatureContext.
ResourceInfo contains information about the resource and method to which the binding can be done. The configure method will be executed once for each resource method that is defined in the application. In the example above the provider will be executed twice, once for the getHello() method and once for getVeryLongString() (once the resourceInfo will contain information about getHello() method and once it will point to getVeryLongString()).
If a dynamic binding provider wants to register any provider for the actual resource method it will do that using provided FeatureContext which extends JAX-RS Configurable API. All methods for registration of filter or interceptor classes or instances can be used. Such dynamically registered filters or interceptors will be bound only to the actual resource method. In the example above the GZIPWriterInterceptor will be bound only to the method getVeryLongString() which will cause that data will be compressed only for this method and not for the method getHello().
Note that filters and interceptors registered using dynamic binding are only additional filters run for the resource method. If there are any name bound providers or global providers they will still be executed.
For more details, check the Jersey documentation about filters and interceptors.
Using #NameBinding may be the most elegant approach, but if you just want to exclude a single resource and apply the filter on all others you have to remember putting the binding annotation on all resources. In this case you can use ContainerRequestContext.getUriInfo().getMatchedResources() to check whether the target resource has been matched. This is better than hard-coding a path that might change.
The example below will apply the filter logic on all resources but StatusResource:
public class CorsContainerRequestFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext req) {
if (!matchesStatusResource(req)) {
// filter logic
}
}
private boolean matchesStatusResource(ContainerRequestContext req) {
List<Object> matchedResources = req.getUriInfo().getMatchedResources();
for (Object matchedResource : matchedResources) {
if (matchedResource instanceof StatusResource) {
return true;
}
}
return false;
}
}
As mentioned by others Dynamic bindings can be used instead but it is quite ugly as it is not obvious that the filter wouldn't be applied to all resources.
Probably you can check the URL pattern and abort the request, using getUri
Something like the following
public class AuthFilter implements ContainerRequestFilter{
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String path = requestContext.getUriInfo().getPath();
if(path.contains("admin")){
requestContext.abortWith(new Response());
}
}
}
https://eclipse-ee4j.github.io/jersey.github.io/documentation/latest/filters-and-interceptors.html#d0e9339

Equivalent of Spring MVC #ResponseStatus(HttpStatus.CREATED) in Jersey?

What's the Jersey equivalent of this Spring MVC code? I need the response to return 201 along with the resource URL, following successful POST:
#RequestMapping(method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
Widget create(#RequestBody #Valid Widget wid) {
return service.create(wid);
}
This is the shortest example I found in Jersey. Is it required to build the response manually for successful POST/201?
#POST #Path("widget")
Response create(#RequestBody #Valid Widget wid) {
return Response
.status(Response.Status.CREATED)
.entity("new widget created")
.header("Location","http://localhost:7001/widget"+wid)
.build();
}
Example of comment, per request of OP:
I don't think there is an equivalent, but personally, I like creating my own response. I have more control. Also there is a Response.created(...), this will automatically set the status. It accepts the URI or String as an argument, and sets the location header with that argument. Also You can use UriInfo to getAbsolutePathBuilder() then just append the created id. That's generally the way I go about it.
#Path("/widgets")
public class WidgetResource {
#Inject
WidgetService widgetService;
#POST
#Consumes(...)
public Response createWidget(#Context UriInfo uriInfo, Widget widget) {
Widget created = widgetService.createWidget(widget);
UriBuilder builder = uriInfo.getAbsolutePathBuilder();
URI uri = builder.path(created.getId()).build();
return Response.created(uri).build();
}
}
This is the general pattern I use for my create methods. The collection path will be the absolute path obtained from uriInfo.getAbsolutePath(Builder), then you just append the created id to the path. So if the collection path is http://blah.com/widgets, and the id is someId, then the location header will be Location: http://blah.com/widgets/someId (which is the location of the new resource), and the status will get set to 201 Created
Response.created(..) returns Response.ResponseBuilder, just like Response.status, so you can do the usual method chaining. There are a number of static method on Response that have default settings, like ok, noContent. Just do through the API. Their names pretty much match up with the status name.
I don't think there is an annotation like that in Jersey. You could create one using Name Binding.
Basically, you create an annotation and add the #NameBinding meta-annotation:
#NameBinding
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface ResponseStatusCreated {}
Next you create an filter which will override the status.
#ResponseStatusCreated
#Provider
class StatusCreatedFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
responseContext.setStatusInfo(Response.Status.CREATED)
String location = "..."; // set based on responseContext.getEntity()
// or any other properties
responseContext.getHeaders().putSingle("Location", location);
}
}
Then use the same annotation on your resource methods.
#POST
#Path("widget")
#ResponseStatusCreated
Object create(#RequestBody #Valid Widget wid) {
return ... // return whatever you need to build the
// correct header fields in the filter
}
You could also make it more generic by creating an annotation that will accept the status as an argument, i.e. #ResponseStatus(Status.CREATED) and get the status in the filter using responseContext.getAnnotations().

Jersey: forbid unspecified parameters

I'm developing REST API with Jersey as JAX-RS implementation.
In every resource I explicitly define expected parameters:
#GET
#Path("/someData")
public Response getSomeData(
#QueryParam("id") final Long id,
#QueryParam("name") final String name) {
...
}
There are a number of fixed parameters, which are common for all resources (e.g. "locale").
Is there any way (I'm ok with introducing Jersey-specific dependencies) I can forbid any parameters that belong neither to method parameters nor to the common parameters?
So for example if user invokes
/api/resource/someData?id=10&locale=en - he gets the data, but if he invokes
/api/resource/someData?id=10&locale=en&fakeParam=AAA - status 400 is returned, with content stating that fakeParam is unknown parameter.
Currently second request is processed the same way as the first one, and fakeParam is simply ignored.
I think described validation will help users of my API to spot bugs earlier.
I don't know of any way to do this with JAX-RS but you could easily roll your own solution. This is a bit cumbersome but you could do something like:
#Path("/api")
public class Service {
#Context
UriInfo uriInfo;
ImmutableSet<String> commonParams = ImmutableSet.of("locale");
#GET
#Path("validate")
#Produces(MediaType.APPLICATION_JSON)
public String validate(#QueryParam("foo") String param) {
Set<String> validParams = newHashSet(commonParams);
class Local {};
for (Annotation[] annotations: Local.class.getEnclosingMethod().getParameterAnnotations()) {
for (Annotation annotation: annotations) {
if (annotation instanceof QueryParam) {
validParams.add(((QueryParam)annotation).value());
}
}
}
if (!difference(uriInfo.getQueryParameters().keySet(), validParams).isEmpty()) {
//throw an unknown parameter exception
}
return "hello";
}
And if you're using Guice or some other AOP tool with Jersey you could probably put this into an aspect s.t. you wouldn't have to add boilerplate to every method you want to validate.

JAX-RS multiple classes with the same path

With JAX-RS, is it possible to have more than one class assigned to a single path? I'm trying to do something like this:
#Path("/foo")
public class GetHandler {
#GET
public Response handleGet() { ...
}
#Path("/foo")
public class PostHandler {
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response handlePost() { ...
}
This apparently isn't allowed as I get:
com.sun.jersey.api.container.ContainerException: A root resource, class PostHandler, has a non-unique URI template /foo
I can always create one class to handle requests and then delegate to helper classes. I was hoping there was a standard way of doing so.
The JAX-RS spec doesn't forbid such a mapping. For example, Resteasy JAX-RS implementation allows for it. The feature should be jersey specific.
Regarding:
I can always create one class to handle requests and then delegate to helper classes. I was hoping there was a standard way of doing so.
Usually you have the resource classes with the same name as the path:
#Path("/foo")
public class FooResource {
#GET
#Path("/{someFooId}")
public Response handleGet() {
...
}
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response handlePost() {
...
}
}
You cannot have multiple resources mapped to the same path. I tried that few days back and landed up at similar error.
I ended up doing subpaths such as /api/contacts for one resource and /api/tags for another.
The only other long way is to create resources in multiple packages and then create different app for each.
I had the similar issue, making the class level #PATH annotation to empty string and moving the resource name to method level #PATH annotation fixed this issue.
#Path("")
public class GetHandler {
#GET
#Path("/foo")
public Response handleGet() {
// impl
}
}
#Path("")
public class PostHandler {
#POST
#Path("/foo")
#Consumes(MediaType.APPLICATION_JSON)
public Response handlePost() {
// impl
}
}

How do I get the JAX-RS #Path of a different resource during a POST?

I have two REST classes for a simple web service (Jersey and GlassFish) that involves user resources - one to operate on all users (e.g., a factory for #POSTing) and another on individual users (e.g., #GET, #PUT, #DELETE). They are at:
#Stateless #Path("users") public class AllUsersResource {...}
#Stateless #Path("user") public class OneUserResource {...}
respectively. When POSTing to AllUsersResource I want to return the Location (via Response.created(uri).build()) of the new User, e.g.,
http://localhost:8080/.../user/152
My question is how to do this. AllUsersResource injects #Context UriInfo uriInfo, but that does not get me #Path info for OneUserResource, only that of the current call ("users"). The way I finally got it working was simply to use reflection, but I'm worried it is brittle and unclean:
OneUserResource.class.getAnnotation(Path.class).value();
Searching StackOverflow the only other things I found to try were the following, without success:
com.sun.jersey.api.core.ResourceContext
javax.ws.rs.core.UriInfo.getMatchedResources()
#javax.inject.Inject OneUserResource oneUserRes;
Any help would be terrific!
You can use UriBuilder.fromresource(), but this only works if the supplied Resource class is a root resource (this is clearly mentioned in the javadocs). I found a way to achieve this even if you are in a sub-resource class:
#POST
#Consumes({MediaType.APPLICATION-XML, MediaType.APPLICATION-JSON})
public Response createUser(final User user, #Context UriInfo uriInfo) {
// persist the user here
URI uri = uriInfo.getAbsolutePathBuilder().path(user.getId()).build();
return Response.created(uri).build();
}
I found a couple of javax.ws.rs.core.UriBuilder methods that did the trick, which I wanted to share in case others had this question. They are: UriBuilder.fromResource(OneUserResource.class) and javax.ws.rs.core.UriBuilder.path(Class). I used the latter in a one-shot call:
URI newUserUri = uriInfo.getBaseUriBuilder().path(OneUserResource.class).path("/" + user.getId()).build();
return Response.created(newUserUri).build();
With the strict REST concept you can make it as one root resource
#POST /users -> CREATE a single user
#GET /users -> READ all users
#PUT /users -> UPDATE (REPLACE) all users ##?
#DELETE /users -> DELETE all users ##?
#POST /users/{id} -> CREATE a single user's some other child; ##?
#GET /users/{id} -> READ a single user
#PUT /users/{id} -> UPDATE a single user
#DELETE /users/{id} -> DELETE a single user
#Path("/users")
#Stateless
public class UsersResouce {
// /users
#POST
#Consumes({MediaType.APPLICATION-XML, MediaType.APPLICATION-JSON})
public Response createUser(final User user) {
// persist the user here
return Response.created("/" + user.getId()).build();
}
// /users
#GET
#Produces({MediaType.APPLICATION-XML, MediaType.APPLICATION-JSON})
public Response readUsers() {
//return all users
}
// /users/{id}
#GET
#Path("/{user_id: \\d+}")
#Produces({MediaType.APPLICATION-XML, MediaType.APPLICATION-JSON})
public Response readUser(
#PathParam("user_id") final Long userId) {
final User persisted = userBean.find(userId);
if (persisted == null) {
return Response.status(Status.NOT_FOUND).build();
}
return Response.ok().entity(persisted).build();
}
// /users/{id}
#Consumes({MediaType.APPLICATION-XML, MediaType.APPLICATION-JSON})
#PUT
#Path("/{user_id: \\d+}")
public Response updateUser(
#PathParam("user_id") final Long userId,
final User mergeable) {
final User persisted = userBean.find(userId);
if (persisted == null) {
userBean.persist(mergeable);
} else {
persist.setName(mergeable.getName());
userBean.merge(persisted);
}
return Response.status(Status.NO_CONTENT).build();
}
// /users/{id}
#DELETE
#Path("/{user_id: \\d+}")
public Response deleteUser(
#PathParam("user_id") final Long userId) {
userBean.delete(userId);
return Response.status(Status.NO_CONTENT).build();
}
#EJB
private UserBean userBean;
}
As of JAX-RS 2.0, the most correct way (As far as I know) is to use the builder method like so:
String uri = uriInfo.getBaseUriBuilder()
.path(ODataV4Endpoint.class)
.path(ODataV4Endpoint.class, "serviceEndpointJSONCatalog")
.resolveTemplate("endpointId", endpointId).build().toString();
FYI, I need to call path twice in my case, once for the path annotation on the class, and the second time for the annotation on the method. I suspected the call to the method would do both, but it does not.
The Path annotation on the endpoint serviceEndpointJSONCatalog declared a parameter, like so: 'endpoint/{endpointId}', so the call to resolveTemplate was needed. Otherwise you would just call path(Class cl, String method).
In my case I created a builder and a symbolic way to reference the methods so the compiler / runtime could check them.

Categories