In my current architecture, I have a JAX-RS resource that sits behind:
/categories
/categories/{catId}
that's implemented like this:
@Path("/categories")
@Produces("application/json")
public class CategoryResourcesApi {
@GET
public Response getCategories() {
// ...
}
@GET @Path("/{catId}")
public Response getCategory(@PathParam("catId") String catId) {
// ...
}
// ...
}
and another one that serves:
/products
/products/{prodId}
and has a similar implementation:
@Path("/products")
@Produces("application/json")
public class ProductResourcesApi {
@GET
public Response getProducts() {
// ...
}
// ...
}
Apart from these straightforward paths, I also need to serve these:
/categories/{catId}/products
/categories/{catId}/products/{prodId}
which would be products related to a specific category.
The most natural thing to do would be make ProductResourcesApi
serve them, but by the way I understand the JAX-RS annotations structure, this can only be served by CategoryResourcesApi
(or eventually by a third class, I think).
I'm using @Context
and other annotations in my resource implementations, so a direct new ProductResourcesAPI().getProducts()
wouldn't work, I guess.
Is there any way to forward from one resource path to another within the JAX-RS (or Jersey) framework? What other options do I have? I'd like to keep all this easily maintainable if possible, that's why I chose one resource for each root path with subresources within.
For this you can use Sub-resource locators, which is basically a method in the resource class that returns another resource class. The thing about the examples in the link is that they instantiate the resource class themselves, for example
@Path("/item")
public class ItemResource {
@Path("content")
public ItemContentResource getItemContentResource() {
return new ItemContentResource();
}
}
public class ItemContentResource {
@PUT
@Path("{version}")
public void put(@PathParam("version") int version)
}
}
which works, but I am not sure if it preserves injections, for instance if you wanted to inject @Context UriInfo
into a field in ItemContentResource
. It should work though if you injected into the method param instead.
To get around this, there is the ResourceContext
, which when used, should preserve all the injections. For example in your current case, you can do
@Path("/categories")
@Produces("application/json")
public static class CategoryResourcesApi {
@Context
private ResourceContext resourceContext;
@Path("/{catId}/products")
public ProductResourcesApi getProducts() {
return resourceContext.getResource(ProductResourcesApi.class);
}
}
@Path("/products")
@Produces("application/json")
public static class ProductResourcesApi {
@Context
private UriInfo info;
@GET
@Path("/{id}")
public Response getProducts(
@PathParam("id") String prodId,
@PathParam("catId") String catId) {
}
}
The getProducts
would map to the URI /categories/{catId}/products/{prodId}
. You just need to check if the catId
is null (only if you need it to do any lookup) I guess to determine if the request is a request to the root products resource or to the parent categories resource. Small price to pay for code reuse I guess.
And just looking at your comment, I believe in the past Swagger didn't support sub-resource locators, but I believe now they do. You may want to search around for any discussions if you have problems with it. Here's a discussion, and another one, and another one