Saturday, February 13, 2010

JAX-RS and the REST Uniform Interface

In Roy Fielding's dissertation that started it all, he stated:

"The central feature that distinguishes the REST architectural style from other network-based styles is its emphasis on a uniform interface between components."


The above quotation can be found in Section 5.1.5 ("Uniform Interface") of Chapter 5 ("Representational State Transfer (REST)") of Fielding's now-famous PhD dissertation "Architectural Styles and the Design of Network-based Software Architectures."

The "uniform interface" concept is obviously important to REST and is a driving force behind the design of HTTP methods. JAX-RS provides sophisticated annotations-based support for providing uniform interfaces to clients from regular Java classes. The main intent of this post is to briefly cover how JAX-RS makes it easy to write Java classes that provide this uniform interface.

Nearly all REST-based applications "happen" to be HTTP based as well, but it is often emphasized that HTTP is not absolutely required to implement a REST-based application. Still, HTTP and REST often do go together, so it is not surprising that the JAX-RS-provided annotations @GET, @DELETE, @POST, and @PUT closely mirror in name the respective HTTP methods GET, DELETE, POST, and PUT. Note that JAX-RS also provides @OPTIONS and @HEAD annotations.

In my blog post JAX-RS with Jersey: An Introduction, I demonstrated use of the @GET annotation in conjunction with fellow JAX-RS annotations @Path and @PathParam. Only data retrieval (hence the "GET") was shown in that post. In this post, I expand upon (and change) the MovieOfTheDay class I used in that post to support creation/insertion of new data, updating of existing data, and deletion/removal of data.

As I stated in the previous post, I use an (slightly modified for this post) internal static map to emulate what would normally be a datastore such as a database. This piece of code is shown next.

MovieOfTheDay.java Fragment: Emulated Data Storage Portion

/** Overall simulated database of movies of the day. */
private static final Map<Integer, Map<Integer, String>> MOVIE_OF_THE_DAY;

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

final Map<Integer, String> janMovies = new ConcurrentHashMap<Integer, String>();
janMovies.put(10, "Trading Places");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JANUARY), janMovies);

final Map<Integer, String> febMovies = new ConcurrentHashMap<Integer, String>();
febMovies.put(2, "Groundhog Day");
febMovies.put(13, "Casablanca");
febMovies.put(14, "Sleepless in Seattle");
febMovies.put(15, "How to Lose a Guy in 10 Days");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.FEBRUARY), febMovies);

final Map<Integer, String> marMovies = new ConcurrentHashMap<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 ConcurrentHashMap<Integer, String>();
aprMovies.put(25, "Raiders of the Lost Ark");
aprMovies.put(26, "Indiana Jones and the Temple of Doom");
aprMovies.put(27, "Indiana Jones and the Last Crusade");
aprMovies.put(28, "Indiana Jones and the Kingdom of the Crystal Skull");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.APRIL), aprMovies);

final Map<Integer, String> mayMovies = new ConcurrentHashMap<Integer, String>();
mayMovies.put(26, "Star Wars: Episode 1 - The Phantom Menace");
mayMovies.put(27, "Star Wars: Episode 2 - Attack of the Clones");
mayMovies.put(28, "Star Wars: Episode 3 - Revenge of the Sith");
mayMovies.put(29, "Star Wars: Episode 4 - A New Hope");
mayMovies.put(30, "Star Wars: Episode 5 - The Empire Strikes Back");
mayMovies.put(31, "Star Wars: Episode 6 - Return of the Jedi");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.MAY), mayMovies);

final Map<Integer, String> junMovies = new ConcurrentHashMap<Integer, String>();
junMovies.put(15, "The Great Outdoors");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JUNE), junMovies);

final Map<Integer, String> julMovies = new ConcurrentHashMap<Integer, String>();
julMovies.put(4, "Independence Day");
julMovies.put(21, "Men in Black");
julMovies.put(22, "Men in Black 2");
julMovies.put(23, "I, Robot");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JULY), julMovies);

final Map<Integer, String> augMovies = new ConcurrentHashMap<Integer, String>();
augMovies.put(10, "North by Northwest");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.AUGUST), augMovies);

final Map<Integer, String> sepMovies = new ConcurrentHashMap<Integer, String>();
sepMovies.put(1, "Dead Poets Society");
sepMovies.put(20, "Uncle Buck");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.SEPTEMBER), sepMovies);

final Map<Integer, String> octMovies = new ConcurrentHashMap<Integer, String>();
octMovies.put(28, "Psycho");
octMovies.put(29, "Sixth Sense");
octMovies.put(30, "Ghostbusters");
octMovies.put(31, "Young Frankenstein");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.OCTOBER), octMovies);

final Map<Integer, String> novMovies = new ConcurrentHashMap<Integer, String>();
novMovies.put(10, "The Italian Job");
novMovies.put(11, "Ocean's Eleven");
novMovies.put(12, "Ocean's Twelve");
novMovies.put(13, "Ocean's Thirteen");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.NOVEMBER), novMovies);

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


The above code snippet sets up an underlying data store with the movies of the day. The MovieOfTheDay class represents a resource and its class declaration is shown in the next code listing. The @Path annotation is significant because it tells the JAX-RS provider that this class represents a resource.

MovieOfTheDay.java Fragment: Class Declaration with @Path

/**
* Simple class that provides a movie for the provided month and day of that
* month.
*/
@Path("/movies")
public class MovieOfTheDay
{
// . . .
}


By specifying "/movies" as an argument to the @Path annotation, I am instructing the JAX-RS provider that this class represents a resource accessible in part by the URI portion "/movies".

With the @Path annotation provided on the class level, methods can be defined for the JAX-RS provider to tie to the uniform interface. The next code snippet demonstrates this for GET. There are actually two methods annotated with the @GET annotation. The first method, getMovieUsage(), simply returns a String specifying how the other GET method can be used. The other GET method is called getMovieOfTheDay(Integer,Integer) and it provides the movie of the day for the day provided as the two integer parameters (month and date in that month).

MovieOfTheDay.java Fragment: Two GET Methods

/**
* This method provides a "usage" string related to retrieving movie of the
* day information.
*
* @return String describing how to access movie of the day information.
*/
@GET
@Path("/")
@Produces("text/plain")
public String getMovieUsage()
{
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 Lives!";
return movieOfTheDay != null
? generateSelectedMovieHtml(movieOfTheDay, month, date)
: generateSelectedMovieHtml("Fletch", month, date);
}


The @Path annotation was used on this entire class and then was again used in the previous code sample to annotate the two GET methods to add more to the overall path. In the first case, the usage method's path is defined simply as "/", meaning that it remains the same path as to the overall class. The second GET method, however, has an @Path annotation with "/month/date" specified as an argument. This means that the JAX-RS provider should match this method with a URI containing the class-level @Path's URI portion concatenated with this method's @Path's URI portion: movies/{month}/{date}. The curly braces indicate that the value is a placeholder. In this case, the {month} and {date} are placeholders for Integers (the types of the two parameters to the method that are annotated with @PathParam annotations that tie them directly to the placeholder names).

The code for the other methods on this HTTP-based uniform interface is similar @DELETE annotation makes it obvious which method is associated with deletion of a resource. In my case, I'm using the method associated with the @PUT annotation for both insert/create (new) behavior and for update/modify (change to existing) behavior. There has been controversy in the REST community about how closely (if at all) HTTP methods in HTTP-based REST applications should apply to the CRUD concept (Create/Read/Update/Delete) with the particularly contentious point being the relationship of HTTP methods PUT/POST to the CRUD operations of UPDATE/CREATE.

My current favorite treatment of the POST/PUT issue is John Calcote's blog post PUT or POST: The REST of the Story. For me, whether to use POST or PUT does come down to asking myself the question, "Is the modeled behavior idempotent?" If I answer "yes" (repeated calls always end up with same value/state), then I use PUT. If the modeled behavior is not idempotent (the same call leads to different results), then I use POST. As a side note, it is interesting that "Idempotence" was Eric Lippert's first covered word in his series Five-Dollar Words for Programmers.

Returning to the subject of JAX-RS and the uniform interface, here is a code snippet with the appropriate JAX-RS annotations for deleting and inserting/updating movies of the day.

MovieOfTheDay.java Fragment: DELETE and PUT Methods

/**
* Remove the movie of the day for the day whose month and date are provided
* as parameters.
*
* @param month Month of date for which movie is to be removed.
* @param date Date for which movie is to be removed.
* @return String representing movie of the day information was deleted.
*/
@DELETE
@Path("/{month}/{date}")
@Consumes("text/plain")
@Produces("text/html")
public String deleteMovieOfTheDay(
@PathParam("month") final Integer month,
@PathParam("date") final Integer date)
{
final Map<Integer, String> moviesOfTheMonth =
MOVIE_OF_THE_DAY.get(month-1);
String movieRemoved = "";
if (moviesOfTheMonth != null)
{
movieRemoved = moviesOfTheMonth.remove(date);
}
return generateDeletedMovieHtml(movieRemoved);
}

/**
* Add a new entry or update an existing entry representing a movie of the day.
*
* @param month Month for the movie of the day.
* @param date Day of the month for the movie of the day.
* @param title Title of the movie of the day.
* @return HTML excerpt for movie of the day entry status.
*/
@PUT
@Path("/{month}/{date}/{title}")
@Consumes("text/plain")
@Produces("text/html")
public String addMovieOfTheDay(
@PathParam("month") final Integer month,
@PathParam("date") final Integer date,
@PathParam("title") final String title)
{
final Map<Integer, String> moviesOfTheMonth =
MOVIE_OF_THE_DAY.get(month-1);
if (moviesOfTheMonth != null)
{
moviesOfTheMonth.put(date, title);
}
return generateAddedMovieHtml(title);
}


The code shown to this point in this post illustrates use of JAX-RS annotations @GET, @DELETE, and @PUT to expose methods in the Java class as methods roughly equivalent respectively to reading/retrieving, deleting, and adding/updating. Before moving onto screen snapshots showing these simple examples in action, I provide the entire class (including the example snippets above and the helper utility methods called from the examples above) here in one listing for convenience.

MovieOfTheDay.java: The Complete Class

package rmoug.td2010.rest;

import java.util.Calendar;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
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.DELETE;
import javax.ws.rs.PUT;
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
{
/** Handle to logger. */
private static final Logger LOGGER = Logger.getLogger("rmoug.td2010.rest.MovieOfTheDay");

/** Overall simulated database of movies of the day. */
private static final Map<Integer, Map<Integer, String>> MOVIE_OF_THE_DAY;

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

final Map<Integer, String> janMovies = new ConcurrentHashMap<Integer, String>();
janMovies.put(10, "Trading Places");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JANUARY), janMovies);

final Map<Integer, String> febMovies = new ConcurrentHashMap<Integer, String>();
febMovies.put(2, "Groundhog Day");
febMovies.put(13, "Casablanca");
febMovies.put(14, "Sleepless in Seattle");
febMovies.put(15, "How to Lose a Guy in 10 Days");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.FEBRUARY), febMovies);

final Map<Integer, String> marMovies = new ConcurrentHashMap<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 ConcurrentHashMap<Integer, String>();
aprMovies.put(25, "Raiders of the Lost Ark");
aprMovies.put(26, "Indiana Jones and the Temple of Doom");
aprMovies.put(27, "Indiana Jones and the Last Crusade");
aprMovies.put(28, "Indiana Jones and the Kingdom of the Crystal Skull");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.APRIL), aprMovies);

final Map<Integer, String> mayMovies = new ConcurrentHashMap<Integer, String>();
mayMovies.put(26, "Star Wars: Episode 1 - The Phantom Menace");
mayMovies.put(27, "Star Wars: Episode 2 - Attack of the Clones");
mayMovies.put(28, "Star Wars: Episode 3 - Revenge of the Sith");
mayMovies.put(29, "Star Wars: Episode 4 - A New Hope");
mayMovies.put(30, "Star Wars: Episode 5 - The Empire Strikes Back");
mayMovies.put(31, "Star Wars: Episode 6 - Return of the Jedi");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.MAY), mayMovies);

final Map<Integer, String> junMovies = new ConcurrentHashMap<Integer, String>();
junMovies.put(15, "The Great Outdoors");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JUNE), junMovies);

final Map<Integer, String> julMovies = new ConcurrentHashMap<Integer, String>();
julMovies.put(4, "Independence Day");
julMovies.put(21, "Men in Black");
julMovies.put(22, "Men in Black 2");
julMovies.put(23, "I, Robot");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.JULY), julMovies);

final Map<Integer, String> augMovies = new ConcurrentHashMap<Integer, String>();
augMovies.put(10, "North by Northwest");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.AUGUST), augMovies);

final Map<Integer, String> sepMovies = new ConcurrentHashMap<Integer, String>();
sepMovies.put(1, "Dead Poets Society");
sepMovies.put(20, "Uncle Buck");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.SEPTEMBER), sepMovies);

final Map<Integer, String> octMovies = new ConcurrentHashMap<Integer, String>();
octMovies.put(28, "Psycho");
octMovies.put(29, "Sixth Sense");
octMovies.put(30, "Ghostbusters");
octMovies.put(31, "Young Frankenstein");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.OCTOBER), octMovies);

final Map<Integer, String> novMovies = new ConcurrentHashMap<Integer, String>();
novMovies.put(10, "The Italian Job");
novMovies.put(11, "Ocean's Eleven");
novMovies.put(12, "Ocean's Twelve");
novMovies.put(13, "Ocean's Thirteen");
MOVIE_OF_THE_DAY.put(Integer.valueOf(Calendar.NOVEMBER), novMovies);

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

/**
* This method provides a "usage" string related to retrieving movie of the
* day information.
*
* @return String describing how to access movie of the day information.
*/
@GET
@Path("/")
@Produces("text/plain")
public String getMovieUsage()
{
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 Lives!";
return movieOfTheDay != null
? generateSelectedMovieHtml(movieOfTheDay, month, date)
: generateSelectedMovieHtml("Fletch", month, date);
}

/**
* Remove the movie of the day for the day whose month and date are provided
* as parameters.
*
* @param month Month of date for which movie is to be removed.
* @param date Date for which movie is to be removed.
* @return String representing movie of the day information was deleted.
*/
@DELETE
@Path("/{month}/{date}")
@Consumes("text/plain")
@Produces("text/html")
public String deleteMovieOfTheDay(
@PathParam("month") final Integer month,
@PathParam("date") final Integer date)
{
final Map<Integer, String> moviesOfTheMonth =
MOVIE_OF_THE_DAY.get(month-1);
String movieRemoved = "";
if (moviesOfTheMonth != null)
{
movieRemoved = moviesOfTheMonth.remove(date);
}
return generateDeletedMovieHtml(movieRemoved);
}

/**
* Add a new entry or update an existing entry representing a movie of the day.
*
* @param month Month for the movie of the day.
* @param date Day of the month for the movie of the day.
* @param title Title of the movie of the day.
* @return HTML excerpt for movie of the day entry status.
*/
@PUT
@Path("/{month}/{date}/{title}")
@Consumes("text/plain")
@Produces("text/html")
public String addMovieOfTheDay(
@PathParam("month") final Integer month,
@PathParam("date") final Integer date,
@PathParam("title") final String title)
{
final Map<Integer, String> moviesOfTheMonth =
MOVIE_OF_THE_DAY.get(month-1);
if (moviesOfTheMonth != null)
{
moviesOfTheMonth.put(date, title);
}
return generateAddedMovieHtml(title);
}

/**
* Generate snippet of HTML related to selection of a movie.
*
* @param movieTitle Title of movie selected.
* @param movieMonth Month in which this was movie of the day.
* @param movieDay Day on which this was movie of the day.
* @return HTML describing selected movie of the day.
*/
private String generateSelectedMovieHtml(
final String movieTitle, final int movieMonth, final int movieDay)
{
final String movieOfTheDayBody =
"<p>The movie of the day for " + movieMonth + "/" + movieDay
+ " is '" + movieTitle + "'.</p>";
return generateHtmlTemplate("Movie of the Day", movieOfTheDayBody);
}

/**
* Generate snippet of HTML related to deletion of a movie.
*
* @param deletedMovieTitle Title of movie that was deleted.
* @return HTML describing movie that was deleted.
*/
private String generateDeletedMovieHtml(
final String deletedMovieTitle)
{
final String deletedMovieBody =
deletedMovieTitle != null
? "<p>The movie '" + deletedMovieTitle + "' was removed.</p>"
: "<p>No movie was deleted because no matching movie found.</p>";
return generateHtmlTemplate("Deleted Movie", deletedMovieBody);
}

/**
* Generate snippet of HTML related to added/updated movie information.
*
* @param addedMovieTitle Title of movie being added/updated.
* @return HTML describing added/updated movie.
*/
private String generateAddedMovieHtml(final String addedMovieTitle)
{
final String addedMovieBody =
addedMovieTitle != null
? "<p>The movie '" + addedMovieTitle + "' was added as the Movie of the Day."
: "<p>No movie added for movie of the day.</p>";
return generateHtmlTemplate("Added Movie", addedMovieBody);
}

/**
* Generic HTML template generated from provided HTML title and body.
*
* @param htmlTitle Title portion of HTML document to be generated.
* @param htmlBody Body portion of HTML document to be generated.
* @return HTML template populated with provided head and body.
*/
private String generateHtmlTemplate(
final String htmlTitle, final String htmlBody)
{
final StringBuilder builder = new StringBuilder();
builder.append("<html>")
.append("<head><title>")
.append(htmlTitle)
.append("</title></head>")
.append("<body><span style='font-weight: bold; font-size: 125%;")
.append("text-align: center;'>")
.append(htmlTitle)
.append("</span>");
builder.append(htmlBody);
builder.append("</body></html>");
return builder.toString();
}
}


In my introductory post on JAX-RS with GlassFish, I showed the web.xml file I used to deploy the JAX-RS-annotated class properly on GlassFish. That file is shown here again for convenience.

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>


The previously referenced blog post provided relatively deep coverage of deploying the JAX-RS application to GlassFish v3, so I won't go into that again here. In that same post, I used a web browser as my simple REST client because I only used methods accessed via HTTP GET. In this post, however, I am also demonstrating DELETE and PUT, so I will use RESTClient. I have blogged previously on using RESTClient (in Easy Java-Based REST Testing Tools and in Using RESTClient with Java's HTTPServlet).

The first screen snapshot demonstrates running RESTClient with the method exposed via HTTP GET that provides usage information.



The next screen snapshot demonstrates the other GET-exposed method. This method provides an actual movie of the day.



In the previous two screen snapshots, the GET HTTP method was used as shown by the checked GET bullet. The next screen snapshot shows a movie of the day being added. In this case, the PUT radio bullet is checked on REST Client. The URL changes here to include a movie title.



I needed to specify the space in the movie title ("Mission Impossible") with %20 in this case. In a realistic, programmatic REST client, I'd make sure either the client framework or my own code ensured that spaces were translated for users rather than relying forcing the client to handle these %20 representations.

To call the deletion method, I simply need to change the HTTP method selected in the RESTClient GUI to "DELETE" and return to the URI with month and date integers. This is shown in the next screen snapshot.



If I run RESTClient GET with the same URI at this point, I'll get a message back saying that the movie of the day is Fletch because that is the default I use for any dates on which no movie of the day is assigned (and I had just deleted the movie assigned to that day).

JAX-RS supports REST in many more ways besides its making it easy to tie custom methods to specific HTTP methods, but I wanted to focus on how JAX-RS enables the uniform interface concept via the standard HTTP methods in this post. In future posts, I plan to consider how JAX-RS further helps with an HTTP-based REST application by handling other items such as content types and responses.

No comments: