Monday, January 5, 2009

RESTful Web Services with Jersey JSR-311

Currently I'm excited about RESTful web services. REST is a kind of buzzword at this time and there are some good reasons for buzz. I do believe that REST for web services will stay for long time or hopefully even as long as web will exist. And today I'll try to do my first RESTful web service based on jersey.

Project Jersey is a reference implementation for JSR-311 for building RESTful Web services. As this specification states "it defines a set of Java APIs for the development of Web services built according to the 3
Representational State Transfer (REST) architectural style".

I was watching this project for quite long time, saw some presentations and red quite a lot about it and did a little bit of RESTful web services. So all this staff is not very new to me. Nevertheless this is my first try on JSR-311 and Jersey.

First impressions

At the beginning I thought and hoped that jersey will be something very or reasonably lightweight, few jars and servlet configuration for web.xml. These expectations comes from the stripes framework which I currently use and enjoy it very much. In general it has a very similar annotation based mapping between URI and handler methods and can be used with little bit adoption for similar things. But as you already guessed it is different, actually it has a lot of dependencies and comes even with grizzly or light weight http server, all JAXB jars if you a still on Java SE 5, ASM and other jars as optional. Documentation on jersey site scared me a little bit, it was not easy to understand what do I need for minimal start. I guess if I would be a maven friend everything could be easier.

At some point, when I was close to getting angry, about insane documentation I made decision to start with NetBeans. For the beginning I created a new Hello World (RESTful) Web Services project from one of the NetBeans samples project, after what I got a new project what was running and ready to go. This is the easiest way to get started. From that point everything went very smooth I knew what REST is and what I like to get at the end. Of course in order to understand what I should expect form Jersey and how can it differ from traditional RESTful I downloaded JSR-311 documentation in PDF form and JavaDocs.

JSR documentation is very short, only 49 pages and easy to read and understand. I'm true believer in the KISS, so I liked simplicity of this JSR.

The source code of my application is in one file. Application is just a simple test case where I want to have a catalog of the products and vendors. For this time I did most of the CRUD operations for vendor, they are : list vendors, add new vendor, and delete vendor. Update operation for vendor I didn't do because just for simplicity vendor has only one field which is a vendor's title and that makes unreasonable to do update operation.


package helloworld;

import java.util.ArrayList;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;

/**
*
* @author remis
*/
@Path("/catalog")
public class CatalogResource {

private static ArrayList vendors = new ArrayList();

@GET
public String getCatalog() {
return "catalog index";
}

/**
* On HTTP GET method for /catalog/vendor returns full list of vendors.
* @return string with full list of vendors separated by semicolon.
*/
@GET
@Path("vendor")
public Response getVendors() {
StringBuilder sb = new StringBuilder();
for (String vendor : vendors) {
sb.append(vendor).append(";");
}
return Response.ok(sb.toString()).build();
}

/**
* On HTTP GET method for /catalog/vendor/{title} returns
* data of the specified vendor.
* @return string with title of found vendor. In case if such vendor
* does not exist
*/
@GET
@Produces("text/plain")
@Path("vendor/{title}")
public Response getVendor(@PathParam("title") String title) {
Response result = null;
if (! hasVendor(title)) {
result = Response.ok(title).build();
} else {
result = Response.noContent().build();
}
return result;
}

private boolean hasVendor(String vendor) {
return (vendor != null && vendors.contains(vendor));
}

@DELETE
@Produces("text/plain")
@Path("vendor/{title}")
public Response deleteVendor(@PathParam("title") String title) {
Response result = null;
if (hasVendor(title)) {
vendors.remove(title);
result = Response.ok().build();
}
return result;
}

@POST
@Produces("text/plain")
@Path("vendor/{title}")
public Response addVendor(@PathParam("title") String title) {
Response result = null;
if (! hasVendor(title)) {
vendors.add(title);
result = Response.ok().build();
}
return result;
}

@GET
@Path("product")
public String getProducts() {
return "product list is not implemented yet";
}

@GET
@Path("feature")
public String getFeatures() {
return "feature list";
}

@GET
@Path("tag")
public String getTags() {
return "tag list";
}

@GET
@Produces("text/plain")
public String getList() {
return "catalog list";
}
}


And here is result of my solution. There is a set of steps, with command curl -d "" URI I adding three vendors step by step. Three HTTP post methods are used to add 3 new vendors: SONY, PANASONIC and PHILIPS. Last HTTP GET command is used to retrieve all added vendors.


remis$ curl -X POST http://localhost:8080/restHello/resources/catalog/vendor/SONY
remis$ curl -X POST http://localhost:8080/restHello/resources/catalog/vendor/PANASONIC
remis$ curl -X POST http://localhost:8080/restHello/resources/catalog/vendor/PHILIPS
remis$ curl -X GET http://localhost:8080/restHello/resources/catalog/vendor
remis$ curl -X DELETE http://localhost:8080/restHello/resources/catalog/vendor/PANASONIC
remis$ curl -X GET http://localhost:8080/restHello/resources/catalog/vendor


How does it works? Simple, on every http request jersey does the matching between URI and path annotation, then based on the http method issued, corresponding handler method is executed. In case if method has additional parameter, which in my case is retrieved from URI path, this parameter is passed to the method and method uses parameter for business logic.

The list of curl commands listed above does the following commands line by line. Adds new vendor SONY, then another vendor PANASONIC and PHILIPS, later it prints out a full list of vendors and removes PANASONIC vendor. At the end of the list another command which outputs full list of vendors, but in this case it will enter only two vendors, because PANASONIC vendor was removed by previous command.

You can use a regular web browser in order to get an output for HTTP GET method, but it will be much harder to do HTTP POST and DELETE methods. If you know any easier and more user friendly way to issue HTTP POST and DELETE methods be very kind and enlighten my.

Maybe, I will update this blog entry later or even will write another story about Jersey, but for now it's enough. I have tried Jersey, I got my own opinion about it and next time decisions will be based on real experience.

As conclusion I would like to say, I haven't seen nothing easier that this Web Service API. It is easy to understand, clean, very productive and looks very fresh as fresh as spring. I haven't tested performance of it, but I guess hardly anything will beat it. As the last word I think I will look at other JSR-311 implementations, maybe they will be easier to start and lighter on dependencies.

4 comments:

rosco said...

You might also want to consider Spring 3.0's new REST support which comes as a part of their MVC framework, rather than as a JAX-RS (JSR-311) implementation.

Anonymous said...

oh goody - another trivial example that replicates 10 000 other tutorials.

Ever done something original?

Anonymous said...

your example is in once source file?
yeah right.
Where is the code that starts the server, or configures one.
Did you use the light weight http server that comes with the jdk?
or were you using glassfish or jetty or some other web app container?

Unknown said...

Many thanks for the example. It all helps when starting something new. Brian