Tuesday, February 9, 2010

JAX-RS with Jersey: An Introduction

The JAX-RS (JSR 311: The Java API for RESTful Web Services) specification provides a standardized Java-based approach to implementing REST-style web services. Jersey is the reference implementation of JAX-RS and I provide a brief introduction to JAX-RS via Jersey in this blog post.

Although Jersey doesn't require the use of GlassFish, I use Jersey in conjunction with GlassFish v3 in this post. GlassFish v3 provides the reference implementation for Java EE 6. I download the GlassFish v3 Windows Installer and ran it for installation. After its installation, I set the environment variable GLASSFISH_HOME to point to the installation's root directory and added %GLASSFISH_HOME% to my PATH. GlassFish can then be run with the command asadmin start-domain (starts the default domain) as demonstrated in the next screen snapshot.



Because I used the default settings during my installation of GlassFish, the web-based administrative console is available on my machine at the URI http://localhost:4848/ (default port is 4848). With GlassFish running, this URI leads to the login page for the Administrative Console. The administrative username and password were specified in the installation. This screen is shown in the next screen snapshot.




With GlassFish set up, I now move onto developing a very simple REST application using Jersey. I start with a properly JAX-RS-annotated class called MovieOfTheDay:

MovieOfTheDay.java

package rmoug.td2010.rest;

import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import javax.ws.rs.GET;

import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;

/**
* Simple class that provides a movie for the provided month and day of that
* month.
*/
@Path("/movies")
public class MovieOfTheDay
{
private static final Logger LOGGER = Logger.getLogger("rmoug.td2010.rest.MovieOfTheDay");

private static final Map<Integer, Map<Integer, String>> MOVIE_OF_THE_DAY;

static
{
MOVIE_OF_THE_DAY = new HashMap<Integer, Map<Integer, String>>();

final Map<Integer, String> janMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JANUARY), janMovies);

final Map<Integer, String> febMovies = new HashMap<Integer, String>();
febMovies.put(2, "Groundhog Day");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.FEBRUARY), febMovies);

final Map<Integer, String> marMovies = new HashMap<Integer, String>();
marMovies.put(16, "The Fugitive");
marMovies.put(17, "Darby O'Gill and the Little People");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.MARCH), marMovies);

final Map<Integer, String> aprMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.APRIL), aprMovies);

final Map<Integer, String> mayMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.MAY), mayMovies);

final Map<Integer, String> junMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JUNE), junMovies);

final Map<Integer, String> julMovies = new HashMap<Integer, String>();
julMovies.put(4, "Independence Day");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JULY), julMovies);

final Map<Integer, String> augMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.AUGUST), augMovies);

final Map<Integer, String> sepMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.SEPTEMBER), sepMovies);

final Map<Integer, String> octMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.OCTOBER), octMovies);

final Map<Integer, String> novMovies = new HashMap<Integer, String>();
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.NOVEMBER), novMovies);

final Map<Integer, String> decMovies = new HashMap<Integer, String>();
decMovies.put(24, "It's A Wonderful Life");
decMovies.put(25, "A Christmas Carol");
decMovies.put(26, "A Christmas Story");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.DECEMBER), decMovies);
}

@GET
@Path("/")
@Produces("text/plain")
public String getMovie()
{
return
"To see the movie of the day, provide URL with month and day: "
+ "\thttp://localhost:8080/rest/resources/movies/<<month>>/<<day>>";
}

/**
* Obtain the movie of the day as indicated by the provided month and date.
*
* @param month Month for which movie of the day is desired.
* @param date Date for which movie of the day is desired.
* @return Title of the movie of the day for provided month and date.
*/
@GET
@Path("/{month}/{date}")
@Consumes("text/plain")
@Produces("text/html")
public String getMovieOfTheDay(
@PathParam("month") final Integer month,
@PathParam("date") final Integer date)
{
final Map<Integer, String> moviesOfTheMonth =
MOVIE_OF_THE_DAY.get(month-1);
final String movieOfTheDay = moviesOfTheMonth != null
? moviesOfTheMonth.get(date)
: "Fletch";
return movieOfTheDay != null
? generateHtml(movieOfTheDay, month, date)
: generateHtml("Fletch Lives!", month, date);
}

private String generateHtml(
final String movieTitle, final int movieMonth, final int movieDay)
{
final StringBuilder builder = new StringBuilder();
builder.append("<html>")
.append("<head><title>Movie of the Day</title></head>")
.append("<body><span style='font-weight: bold; font-size: 125%;")
.append("text-align: center;'>Movie of the Day</span>")
.append("<p>The movie of the day for ")
.append(movieMonth)
.append("/")
.append(movieDay)
.append(" is '")
.append(movieTitle)
.append("'.</p></body></html>");
return builder.toString();
}
}


The static initialization block is not specific to JAX-RS, but is instead used to simulate a database. In a real REST application, I'd almost certainly have a database on the back-end, but the in-memory static map simulates that here.

Although simple, the above class demonstrates key JAX-RS features. The most interesting JAX-RS pieces of the class are the JAX-RS annotations such as @Path, @GET, @Consumes, @Produces, and @PathParam. I won't delve into what these JAX-RS annotations do in this post because my emphasis is on using Jersey. See the Java EE 6 Tutorial chapter on REST with Jersey for more background on these annotations.

I will deploy the JAX-RS-annotated class to GlassFish in a WAR file with the appropriate web.xml file as shown next:

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>ServletAdaptor</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>ServletAdaptor</servlet-name>
<url-pattern>/resources/*</url-pattern>
</servlet-mapping>

<session-config>
<session-timeout>
30
</session-timeout>
</session-config>

</web-app>


In my case, NetBeans 6.8 generated this web.xml file for me automatically when I added the appropriate JAX-RS and Jersey JAR files to my project's libraries. This is a relatively simple web.xml file because of GlassFish's awareness of JAX-RS. (A strikingly similar web.xml works for deploying Jersey-based REST applications to Tomcat as demonstrated in Jason Drake's blog post Deploying Jersey in Tomcat 6.0.)

For my example, a WAR file called Rest1.war is generated. Its contents are displayed in the next screen snapshot.



As the screen snapshot indicates, there are JAX-RS and Jersey JAR files included in the generated WAR file. The class MovieRestApplication can be ignored because it is not used with Jersey on GlassFish. This means that the only custom files in the WAR are the JAX-RS-annotated class MovieOfTheDay, the web.xml file, and the index page (index.jsp). The content of the index.jsp page is shown next.

index.jsp

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>REST with JAX-RS Example</title>
</head>
<body>
<h1>REST Can be Easy!!!</h1>
</body>
</html>


The next screen snapshot demonstrates deployment of the generated WAR file via the web-based Glass Administrative Console:



The most important detail to note from the image of the deployment of the WAR file is that I have named the context root "rest." This will be part of the URIs by which my deployed REST services are accessed. The earlier web.xml file also showed that resources/ will be part of this REST service access URI as well. The remainder of the appropriate URI is based on the pieces of URI provided in the JAX-RS annotations on the Java class (/movies, /, and /{month}/{date}). The portions of URI denoted with curly braces indicate that the placeholders will be injected with values from the JAX-RS implementation that are in the calling URI. For example, if the relevant piece of URI was /7/4, this would indicate, in this case, a month of 7 (July because not using Java's zero-based month index in the URI) and a day of 4.

When deployment is successful, the administrative console appears as shown in the next screen snapshot.



With the JAX-RS application deployed, I can now access it from a myriad of different clients. JAX-RS does not spell out a standardized approach for clients, but Jersey and most other popular JAX-RS implementations provide their own approach for building clients. Other HTTP/REST clients are also available such as RESTClient. For now, I'll simply use a web browser.

Placing the URI http://localhost:8080/ in my browser displays the main page indicating that GlassFish is running:



If I add the web context (rest) to the URI, I see my index.jsp page:



To access the JAX-RS-powered REST applications, I need to add the resources portion of the URI as specified in the web.xml file. When I add this plus the /movies portion (as specified in the @Path annotation), I see the following page.



The above screen snapshot indicates that the GET access was invoked with the path "/" and the getMovie method was called. At this point, I can add a month and date to the URI to get a movie for that specific day. The next two screen snapshots demonstrate this for Groundhog Day and for Christmas Day.





As the above screen snapshots demonstrate, the months and days provided in the URIs are automatically injected by the JAX-RS provider into the parameters to the appropriate method. Now that's easy!


Conclusion

The process of deploying a JAX-RS-based web service using Jersey and GlassFish is relatively straightforward. All I really needed was access to the JAX-RS and Jersey JARs, the properly annotated Java class, and the short web.xml file that allowed Jersey to be used as a servlet. This blog post has attempted to show the basic steps involved with writing a simple JAX-RS-annotated class, deploying it to GlassFish, and taking advantage of Jersey's implementation of JAX-RS.


Other Resources

RESTful Web Services Developer's Guide

RESTful Java, Some Links

JSR 311: A Java API for RESTful Web Services?

Deploying and Testing a Jersey Application without NetBeans

Jersey 1.0: Getting Started

JSR-311 Javadoc-based API

No comments: