Handling different subresources with JAX-RS subresource locator
Java, REST ·In this article I won’t explain what a resource or sub resource is. There are several pages that explain perfectly well its meaning. For example, you can check Oracle tutorial or Jersey documentation. I will focus on implementing a RESTful service with a JAX-RS sub resource locator. This locator will decide at runtime what type of sub resource will be returned.
You can check the source code on my Github repository.
1 Configuration
First of all, you need to include the JAX-RS reference implementation libraries:
<properties> <jersey.version>1.17.1</jersey.version> </properties> <dependencies> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-server</artifactId> <version>${jersey.version}</version> </dependency> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-servlet</artifactId> <version>${jersey.version}</version> </dependency> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-json</artifactId> <version>${jersey.version}</version> </dependency> </dependencies>
The first two dependencies are necessary if you want to develop services, while the third contains the implementation to convert your classes to JSON.
Next step is to define the Jersey servlet that will handle requests to our services. So, include the content below on your web.xml file:
<servlet> <servlet-name>Jersey Servlet</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <init-param> <param-name>com.sun.jersey.config.property.packages</param-name> <param-value>xpadro.rest.ri.resources</param-value> </init-param> <init-param> <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name> <param-value>true</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Jersey Servlet</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping>
I have included two init parameters:
- com.sun.jersey.config.property.packages: Required. It must define one or more packages separated by “;”. These are the packages where you put your resource classes.
- com.sun.jersey.api.json.POJOMappingFeature: Activates POJO support, which means that it will use the Jackson library to convert your Java Objects to JSON and the other way back.
We are done with the configuration. Let’s implement the resources.
2 The root resource
To declare a root resource you must annotate your class with @Path:
import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; /** * Root resource */ @Path("/warehouse") public class WarehouseResource { @GET public String getWarehouseInfo() { return "Warehouse location: Barcelona"; } @Path("/items/{itemId}") public ItemResource getItem(@PathParam("itemId") Integer itemId) { ItemResource itemResource = null; if (itemId > 10) { itemResource = new TypeAResource(itemId); } else { itemResource = new TypeBResource(itemId); } return itemResource; } }
The @GET annotated getWarehouseInfo() method will handle requests to the root resource. So, when the user enters the following URI:
It will return the warehouse information.
Take into account that if I had included the @Path annotation in conjunction with @GET, I would be declaring a sub resource method.
The getItem method is annotated with @Path but not with any request method designator (get, post…). This is because I’m declaring a sub resource locator. This sub resource locator will return an object that will handle the HTTP request, but which one? It will depend on the id parameter. Both the TypeAResource and TypeBResource implement the same interface _ItemResource. S_o, we can return any of them.
3 Subresources
These sub resources have a method with a request method designator, but no @Path annotation has been included:
import javax.ws.rs.GET; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import xpadro.rest.ri.model.TypeAItem; import xpadro.rest.ri.repository.TypeAItemRepository; /** * Type A Subresource */ public class TypeAResource implements ItemResource { private int itemId; private TypeAItemRepository itemRepository = new TypeAItemRepository(); public TypeAResource(int id) { this.itemId = id; } @GET @Produces(MediaType.APPLICATION_JSON) public TypeAItem getTypeAItem() { TypeAItem item = itemRepository.retrieveItem(itemId); if (item == null) { throw new WebApplicationException(Response.Status.NOT_FOUND); } return item; } } import javax.ws.rs.GET; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import xpadro.rest.ri.model.TypeBItem; import xpadro.rest.ri.repository.TypeBItemRepository; /** * Type B Subresource */ public class TypeBResource implements ItemResource { private int itemId; private TypeBItemRepository itemRepository = new TypeBItemRepository(); public TypeBResource(int id) { this.itemId = id; } @GET @Produces(MediaType.APPLICATION_JSON) public TypeBItem getTypeBItem() { TypeBItem item = itemRepository.retrieveItem(itemId); if (item == null) { throw new WebApplicationException(Response.Status.NOT_FOUND); } return item; } }
The following requests will return a different sub resource:
http://localhost:8080/rest-subresources/rest/warehouse/items/1: Will return a Type B resource:
{“id”:1,”price”:33.5,”type”:”B”}
http://localhost:8080/rest-subresources/rest/warehouse/items/12: Will return a Type A resource:
{“id”:12,”price”:35.5,”type”:”A”,”code”:”12SS34″}
4 Possible mistakes
I’m including some errors that may occur if you fail to configure the application properly:
Fail on deploy. com.sun.jersey.api.container.ContainerException
The ResourceConfig instance does not contain any root resource classes
You did not define the packages init-param, so it will fail to find nor load your resource classes.
<init-param> <param-name>com.sun.jersey.config.property.packages</param-name> <param-value>xpadro.rest.ri.resources</param-value> </init-param>
Fail on runtime. com.sun.jersey.api.MessageException
A message body writer for Java class [yourModelClass], and Java type class [yourModelClass], and MIME media type application/json was not found.
You did not define the JSON feature on web.xml. It won’t be able to convert your object to JSON:
<init-param> <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name> <param-value>true</param-value> </init-param>
Fail on runtime. com.sun.jersey.api.MessageException
A message body writer for Java class [yourModelClass], and Java type class [yourModelClass], and MIME media type application/json was not found.
This error might also be produced because the JSON maven dependency was not included:
<dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-json</artifactId> <version>${jersey.version}</version> </dependency>
Fail on deploy. java.lang.ClassNotFoundException: com.sun.jersey.spi.container.servlet.ServletContainer
The jersey-servlet maven dependency was not included. Older versions of Jersey included this class in jersey-core library, but in newer versions they have put it in a separate jar.
<dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-servlet</artifactId> <version>${jersey.version}</version> </dependency>