Your First JAX-RS Service - REST and the JAX-RS Standard - RESTful Java with JAX-RS 2.0 (2013)

RESTful Java with JAX-RS 2.0 (2013)

Part I. REST and the JAX-RS Standard

Chapter 3. Your First JAX-RS Service

The first two chapters of this book focused on the theory of REST and designing the RESTful interface for a simple ecommerce order entry system. Now it’s time to implement a part of our system in the Java language.

Writing RESTful services in Java has been possible for years with the servlet API. If you have written a web application in Java, you are probably already very familiar with servlets. Servlets bring you very close to the HTTP protocol and require a lot of boilerplate code to move information to and from an HTTP request. In 2008, a new specification called JAX-RS was defined to simplify RESTful service implementation.

JAX-RS is a framework that focuses on applying Java annotations to plain Java objects. It has annotations to bind specific URI patterns and HTTP operations to individual methods of your Java class. It has parameter injection annotations so that you can easily pull in information from the HTTP request. It has message body readers and writers that allow you to decouple data format marshalling and unmarshalling from your Java data objects. It has exception mappers that can map an application-thrown exception to an HTTP response code and message. Finally, it has some nice facilities for HTTP content negotiation.

This chapter gives a brief introduction to writing a JAX-RS service. You’ll find that getting it up and running is fairly simple.

Developing a JAX-RS RESTful Service

Let’s start by implementing one of the resources of the order entry system we defined in Chapter 2. Specifically, we’ll define a JAX-RS service that allows us to read, create, and update Customers. To do this, we will need to implement two Java classes. One class will be used to represent actual Customers. The other will be our JAX-RS service.

Customer: The Data Class

First, we will need a Java class to represent customers in our system. We will name this class Customer. Customer is a simple Java class that defines eight properties: id, firstName, lastName, street, city, state, zip, and country. Properties are attributes that can be accessed via the class’s fields or through public set and get methods. A Java class that follows this pattern is also called a Java bean:

package com.restfully.shop.domain;

public class Customer {

private int id;

private String firstName;

private String lastName;

private String street;

private String city;

private String state;

private String zip;

private String country;

public int getId() { return id; }

public void setId(int id) { this.id = id; }

public String getFirstName() { return firstName; }

public void setFirstName(String firstName) {

this.firstName = firstName; }

public String getLastName() { return lastName; }

public void setLastName(String lastName) {

this.lastName = lastName; }

public String getStreet() { return street; }

public void setStreet(String street) { this.street = street; }

public String getCity() { return city; }

public void setCity(String city) { this.city = city; }

public String getState() { return state; }

public void setState(String state) { this.state = state; }

public String getZip() { return zip; }

public void setZip(String zip) { this.zip = zip; }

public String getCountry() { return country; }

public void setCountry(String country) { this.country = country; }

}

In an Enterprise Java application, the Customer class would usually be a Java Persistence API (JPA) Entity bean and would be used to interact with a relational database. It could also be annotated with JAXB annotations that allow you to map a Java class directly to XML. To keep our example simple, Customer will be just a plain Java object and stored in memory. In Chapter 6, I’ll show how you can use JAXB with JAX-RS to make translating between your customer’s data format (XML) and your Customer objects easier. Chapter 14 will show you how JAX-RS works in the context of a Java EE (Enterprise Edition) application and things like JPA.

CustomerResource: Our JAX-RS Service

Now that we have defined a domain object that will represent our customers at runtime, we need to implement our JAX-RS service so that remote clients can interact with our customer database. A JAX-RS service is a Java class that uses JAX-RS annotations to bind and map specific incoming HTTP requests to Java methods that can service these requests. While JAX-RS can integrate with popular component models like Enterprise JavaBeans (EJB), Web Beans, JBoss Seam, and Spring, it does define its own lightweight model.

In vanilla JAX-RS, services can either be singletons or per-request objects. A singleton means that one and only one Java object services HTTP requests. Per-request means that a Java object is created to process each incoming request and is thrown away at the end of that request. Per-request also implies statelessness, as no service state is held between requests.

For our example, we will write a CustomerResource class to implement our JAX-RS service and assume it will be a singleton. In this example, we need CustomerResource to be a singleton because it is going to hold state. It is going to keep a map of Customer objects in memory that our remote clients can access. In a real system, CustomerResource would probably interact with a database to retrieve and store customers and wouldn’t need to hold state between requests. In this database scenario, we could make CustomerResource per-request and thus stateless. Let’s start by looking at the first few lines of our class to see how to start writing a JAX-RS service:

package com.restfully.shop.services;

import ...;

@Path("/customers")

public class CustomerResource {

private Map<Integer, Customer> customerDB =

new ConcurrentHashMap<Integer, Customer>();

private AtomicInteger idCounter = new AtomicInteger();

As you can see, CustomerResource is a plain Java class and doesn’t implement any particular JAX-RS interface. The @javax.ws.rs.Path annotation placed on the CustomerResource class designates the class as a JAX-RS service. Java classes that you want to be recognized as JAX-RS services must have this annotation. Also notice that the @Path annotation has the value of /customers. This value represents the relative root URI of our customer service. If the absolute base URI of our server is http://shop.restfully.com, methods exposed by our CustomerResourceclass would be available under http://shop.restfully.com/customers.

In our class, we define a simple map in the customerDB field that will store created Customer objects in memory. We use a java.util.concurrent.ConcurrentHashMap for customerDB because CustomerResource is a singleton and will have concurrent requests accessing the map. Using a java.util.HashMap would trigger concurrent access exceptions in a multithreaded environment. Using a java.util.Hashtable creates a synchronization bottleneck. ConcurrentHashMap is our best bet. The idCounter field will be used to generate IDs for newly created Customer objects. For concurrency reasons, we use a java.util.concurrent.atomic.AtomicInteger, as we want to always have a unique number generated. Of course, these two lines of code have nothing to do with JAX-RS and are solely artifacts required by our simple example.

Creating customers

Let’s now take a look at how to create customers in our CustomerResource class:

@POST

@Consumes("application/xml")

public Response createCustomer(InputStream is) {

Customer customer = readCustomer(is);

customer.setId(idCounter.incrementAndGet());

customerDB.put(customer.getId(), customer);

System.out.println("Created customer " + customer.getId());

return Response.created(URI.create("/customers/"

+ customer.getId())).build();

}

We will implement customer creation using the same model as that used in Chapter 2. An HTTP POST request sends an XML document representing the customer we want to create. The createCustomer() method receives the request, parses the document, creates a Customer object from the document, and adds it to our customerDB map. The createCustomer() method returns a response code of 201, “Created,” along with a Location header pointing to the absolute URI of the customer we just created. So how does the createCustomer() method do all this? Let’s examine further.

To bind HTTP POST requests to the createCustomer() method, we annotate it with the @javax.ws.rs.POST annotation. The @Path annotation we put on the CustomerResource class, combined with this @POST annotation, binds all POST requests going to the relative URI/customers to the Java method createCustomer().

The @javax.ws.rs.Consumes annotation applied to createCustomer() specifies which media type the method is expecting in the message body of the HTTP input request. If the client POSTs a media type other than XML, an error code is sent back to the client.

The createCustomer() method takes one java.io.InputStream parameter. In JAX-RS, any non-JAX-RS-annotated parameter is considered to be a representation of the HTTP input request’s message body. In this case, we want access to the method body in its most basic form, anInputStream.

WARNING

Only one Java method parameter can represent the HTTP message body. This means any other parameters must be annotated with one of the JAX-RS annotations discussed in Chapter 5.

The implementation of the method reads and transforms the POSTed XML into a Customer object and stores it in the customerDB map. The method returns a complex response to the client using the javax.ws.rs.core.Response class. The static Response.created() method creates a Response object that contains an HTTP status code of 201, “Created.” It also adds a Location header to the HTTP response with the value of something like http://shop.restfully.com/customers/333, depending on the base URI of the server and the generated ID of the Customerobject (333 in this example).

Retrieving customers

@GET

@Path("{id}")

@Produces("application/xml")

public StreamingOutput getCustomer(@PathParam("id") int id) {

final Customer customer = customerDB.get(id);

if (customer == null) {

throw new WebApplicationException(Response.Status.NOT_FOUND);

}

return new StreamingOutput() {

public void write(OutputStream outputStream)

throws IOException, WebApplicationException {

outputCustomer(outputStream, customer);

}

};

}

We annotate the getCustomer() method with the @javax.ws.rs.GET annotation to bind HTTP GET operations to this Java method.

We also annotate getCustomer() with the @javax.ws.rs.Produces annotation. This annotation tells JAX-RS which HTTP Content-Type the GET response will be. In this case, it is application/xml.

In the implementation of the method, we use the id parameter to query for a Customer object in the customerDB map. If this customer does not exist, we throw the javax.ws.rs.WebApplicationException. This exception will set the HTTP response code to 404, “Not Found,” meaning that the customer resource does not exist. We’ll discuss more about exception handling in Chapter 7, so I won’t go into more detail about the WebApplicationException here.

We will write the response manually to the client through a java.io.OutputStream. In JAX-RS, when you want to do streaming manually, you must implement and return an instance of the javax.ws.rs.core.StreamingOutput interface from your JAX-RS method.StreamingOutput is a callback interface with one callback method, write():

package javax.ws.rs.core;

public interface StreamingOutput {

public void write(OutputStream os) throws IOException,

WebApplicationException;

}

In the last line of our getCustomer() method, we implement and return an inner class implementation of StreamingOutput. Within the write() method of this inner class, we delegate back to a utility method called outputCustomer() that exists in our CustomerResource class. When the JAX-RS provider is ready to send an HTTP response body back over the network to the client, it will call back to the write() method we implemented to output the XML representation of our Customer object.

In general, you will not use the StreamingOutput interface to output responses. In Chapter 6, you will see that JAX-RS has a bunch of nice content handlers that can automatically convert Java objects straight into the data format you are sending across the wire. I didn’t want to introduce too many new concepts in the first introductory chapter, so the example only does simple streaming.

Updating a customer

The last RESTful operation we have to implement is updating customers. In Chapter 2, we used PUT /customers/{id}, while passing along an updated XML representation of the customer. This is implemented in the updateCustomer() method of our CustomerResource class:

@PUT

@Path("{id}")

@Consumes("application/xml")

public void updateCustomer(@PathParam("id") int id,

InputStream is) {

Customer update = readCustomer(is);

Customer current = customerDB.get(id);

if (current == null)

throw new WebApplicationException(Response.Status.NOT_FOUND);

current.setFirstName(update.getFirstName());

current.setLastName(update.getLastName());

current.setStreet(update.getStreet());

current.setState(update.getState());

current.setZip(update.getZip());

current.setCountry(update.getCountry());

}

We annotate the updateCustomer() method with @javax.ws.rs.PUT to bind HTTP PUT requests to this method. Like our getCustomer() method, updateCustomer() is annotated with an additional @Path annotation so that we can match /customers/{id} URIs.

The updateCustomer() method takes two parameters. The first is an id parameter that represents the Customer object we are updating. Like getCustomer(), we use the @PathParam annotation to extract the ID from the incoming request URI. The second parameter is anInputStream that will allow us to read in the XML document that was sent with the PUT request. Like createCustomer(), a parameter that is not annotated with a JAX-RS annotation is considered a representation of the body of the incoming message.

In the first part of the method implementation, we read in the XML document and create a Customer object out of it. The method then tries to find an existing Customer object in the customerDB map. If it doesn’t exist, we throw a WebApplicationException that will send a 404, “Not Found,” response code back to the client. If the Customer object does exist, we update our existing Customer object with new updated values.

Utility methods

The final thing we have to implement is the utility methods that were used in createCustomer(), getCustomer(), and updateCustomer() to transform Customer objects to and from XML. The outputCustomer() method takes a Customer object and writes it as XML to the response’s OutputStream:

protected void outputCustomer(OutputStream os, Customer cust)

throws IOException {

PrintStream writer = new PrintStream(os);

writer.println("<customer id=\"" + cust.getId() + "\">");

writer.println(" <first-name>" + cust.getFirstName()

+ "</first-name>");

writer.println(" <last-name>" + cust.getLastName()

+ "</last-name>");

writer.println(" <street>" + cust.getStreet() + "</street>");

writer.println(" <city>" + cust.getCity() + "</city>");

writer.println(" <state>" + cust.getState() + "</state>");

writer.println(" <zip>" + cust.getZip() + "</zip>");

writer.println(" <country>" + cust.getCountry() + "</country>");

writer.println("</customer>");

}

As you can see, this is a pretty straightforward method. Through string manipulations, it does a brute-force conversion of the Customer object to XML text.

The next method is readCustomer(). The method is responsible for reading XML text from an InputStream and creating a Customer object:

protected Customer readCustomer(InputStream is) {

try {

DocumentBuilder builder =

DocumentBuilderFactory.newInstance().newDocumentBuilder();

Document doc = builder.parse(is);

Element root = doc.getDocumentElement();

Unlike outputCustomer(), we don’t manually parse the InputStream. The JDK has a built-in XML parser, so we do not need to write it ourselves or download a third-party library to do it. The readCustomer() method starts off by parsing the InputStream and creating a Java object model that represents the XML document. The rest of the readCustomer() method moves data from the XML model into a newly created Customer object:

Customer cust = new Customer();

if (root.getAttribute("id") != null

&& !root.getAttribute("id").trim().equals("")) {

cust.setId(Integer.valueOf(root.getAttribute("id")));

}

NodeList nodes = root.getChildNodes();

for (int i = 0; i < nodes.getLength(); i++) {

Element element = (Element) nodes.item(i);

if (element.getTagName().equals("first-name")) {

cust.setFirstName(element.getTextContent());

}

else if (element.getTagName().equals("last-name")) {

cust.setLastName(element.getTextContent());

}

else if (element.getTagName().equals("street")) {

cust.setStreet(element.getTextContent());

}

else if (element.getTagName().equals("city")) {

cust.setCity(element.getTextContent());

}

else if (element.getTagName().equals("state")) {

cust.setState(element.getTextContent());

}

else if (element.getTagName().equals("zip")) {

cust.setZip(element.getTextContent());

}

else if (element.getTagName().equals("country")) {

cust.setCountry(element.getTextContent());

}

}

return cust;

}

catch (Exception e) {

throw new WebApplicationException(e,

Response.Status.BAD_REQUEST);

}

}

}

I’ll admit, this example was a bit contrived. In a real system, we would not manually output XML or write all this boilerplate code to read in an XML document and convert it to a business object, but I don’t want to distract you from learning JAX-RS basics by introducing another API. InChapter 6, I will show how you can use JAXB to map your Customer object to XML and have JAX-RS automatically transform your HTTP message body to and from XML.

JAX-RS and Java Interfaces

In our example so far, we’ve applied JAX-RS annotations directly on the Java class that implements our service. In JAX-RS, you are also allowed to define a Java interface that contains all your JAX-RS annotation metadata instead of applying all your annotations to your implementation class.

Interfaces are a great way to scope out how you want to model your services. With an interface, you can write something that defines what your RESTful API will look like along with what Java methods they will map to before you write a single line of business logic. Also, many developers like to use this approach so that their business logic isn’t “polluted” with so many annotations. They think the code is more readable if it has fewer annotations. Finally, sometimes you do have the case where the same business logic must be exposed not only RESTfully, but also through SOAP and JAX-WS. In this case, your business logic would look more like an explosion of annotations than actual code. Interfaces are a great way to isolate all this metadata into one logical and readable construct.

Let’s transform our customer resource example into something that is interface based:

package com.restfully.shop.services;

import ...;

@Path("/customers")

public interface CustomerResource {

@POST

@Consumes("application/xml")

public Response createCustomer(InputStream is);

@GET

@Path("{id}")

@Produces("application/xml")

public StreamingOutput getCustomer(@PathParam("id") int id);

@PUT

@Path("{id}")

@Consumes("application/xml")

public void updateCustomer(@PathParam("id") int id, InputStream is);

}

Here, our CustomerResource is defined as an interface and all the JAX-RS annotations are applied to methods within that interface. We can then define a class that implements this interface:

package com.restfully.shop.services;

import ...;

public class CustomerResourceService implements CustomerResource {

public Response createCustomer(InputStream is) {

... the implementation ...

}

public StreamingOutput getCustomer(int id)

... the implementation ...

}

public void updateCustomer(int id, InputStream is) {

... the implementation ...

}

As you can see, no JAX-RS annotations are needed within the implementing class. All our metadata is confined to the CustomerResource interface.

If you need to, you can override the metadata defined in your interfaces by reapplying annotations within your implementation class. For example, maybe we want to enforce a specific character set for POST XML:

public class CustomerResourceService implements CustomerResource {

@POST

@Consumes("application/xml;charset=utf-8")

public Response createCustomer(InputStream is) {

... the implementation ...

}

In this example, we are overriding the metadata defined in an interface for one specific method. When overriding metadata for a method, you must respecify all the annotation metadata for that method even if you are changing only one small thing.

Overall, I do not recommend that you do this sort of thing. The whole point of using an interface to apply your JAX-RS metadata is to isolate the information and define it in one place. If your annotations are scattered about between your implementation class and interface, your code becomes a lot harder to read and understand.

Inheritance

The JAX-RS specification also allows you to define class and interface hierarchies if you so desire. For example, let’s say we wanted to make our outputCustomer() and readCustomer() methods abstract so that different implementations could transform XML how they wanted:

package com.restfully.shop.services;

import ...;

public abstract class AbstractCustomerResource {

@POST

@Consumes("application/xml")

public Response createCustomer(InputStream is) {

... complete implementation ...

}

@GET

@Path("{id}")

@Produces("application/xml")

public StreamingOutput getCustomer(@PathParam("id") int id) {

... complete implementation

}

@PUT

@Path("{id}")

@Consumes("application/xml")

public void updateCustomer(@PathParam("id") int id,

InputStream is) {

... complete implementation ...

}

abstract protected void outputCustomer(OutputStream os,

Customer cust) throws IOException;

abstract protected Customer readCustomer(InputStream is);

}

You could then extend this abstract class and define the outputCustomer() and readCustomer() methods:

package com.restfully.shop.services;

import ...;

@Path("/customers")

public class CustomerResource extends AbstractCustomerResource {

protected void outputCustomer(OutputStream os, Customer cust)

throws IOException {

... the implementation ...

}

protected Customer readCustomer(InputStream is) {

... the implementation ...

}

The only caveat with this approach is that the concrete subclass must annotate itself with the @Path annotation to identify it as a service class to the JAX-RS provider.

Deploying Our Service

It is easiest to deploy JAX-RS within a Java EE–certified application server (e.g., JBoss) or standalone Servlet 3 container (e.g., Tomcat). Before we can do that, we need to write one simple class that extends javax.ws.rs.core.Application. This class tells our application server which JAX-RS components we want to register.

package javax.ws.rs.core;

import java.util.Collections;

import java.util.Set;

public abstract class Application {

private static final Set<Object> emptySet = Collections.emptySet();

public abstract Set<Class<?>> getClasses();

public Set<Object> getSingletons() {

return emptySet;

}

}

The getClasses() method returns a list of JAX-RS service classes (and providers, but I’ll get to that in Chapter 6). Any JAX-RS service class returned by this method will follow the per-request model mentioned earlier. When the JAX-RS vendor implementation determines that an HTTP request needs to be delivered to a method of one of these classes, an instance of it will be created for the duration of the request and thrown away. You are delegating the creation of these objects to the JAX-RS runtime.

The getSingletons() method returns a list of JAX-RS service objects (and providers, too—again, see Chapter 6). You, as the application programmer, are responsible for creating and initializing these objects.

These two methods tell the JAX-RS vendor which services you want deployed. Here’s an example:

package com.restfully.shop.services;

import javax.ws.rs.ApplicationPath;

import javax.ws.rs.core.Application;

import java.util.HashSet;

import java.util.Set;

@ApplicationPath("/services")

public class ShoppingApplication extends Application {

private Set<Object> singletons = new HashSet<Object>();

private Set<Class<?>> empty = new HashSet<Class<?>>();

public ShoppingApplication() {

singletons.add(new CustomerResource());

}

@Override

public Set<Class<?>> getClasses() {

return empty;

}

@Override

public Set<Object> getSingletons() {

return singletons;

}

}

The @ApplicationPath defines the relative base URL path for all our JAX-RS services in the deployment. So, in this example, all of our JAX-RS RESTful services will be prefixed with the /services path when we execute on them. For our customer service database example, we do not have any per-request services, so our ShoppingApplication.getClasses() method returns an empty set. Our ShoppingApplication.getSingletons() method returns the Set we initialized in the constructor. This Set contains an instance of CustomerResource.

In Java EE and standalone servlet deployments, JAX-RS classes must be deployed within the application server’s servlet container as a Web ARchive (WAR). Think of a servlet container as your application server’s web server. A WAR is a JAR file that, in addition to Java class files, also contains other Java libraries along with dynamic (like JSPs) and static content (like HTML files or images) that you want to publish on your website. We need to place our Java classes within this archive so that our application server can deploy them. Here’s what the structure of a WAR file looks like:

<any static content>

WEB-INF/

web.xml

classes/

com/restfully/shop/domain/

Customer.class

com/restfully/shop/services/

CustomerResource.class

ShoppingApplication.class

Our application server’s servlet container publishes everything outside the WEB-INF/ directory of the archive. This is where you would put static HTML files and images that you want to expose to the outside world. The WEB-INF/ directory has two subdirectories. Within the classes/directory, you can put any Java classes you want. They must be in a Java package structure. This is where we place all of the classes we wrote and compiled in this chapter. The lib/ directory can contain any third-party JARs we used with our application. Depending on whether your application server has built-in support for JAX-RS or not, you may have to place the JARs for your JAX-RS vendor implementation within this directory. For our customer example, we are not using any third-party libraries, so this lib/ directory may be empty.

We are almost finished. All we have left to do is to create a WEB-INF/web.xml file within our archive.

<?xml version="1.0" encoding="UTF-8"?>

<web-app 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_3_0.xsd"

version="3.0">

</web-app>

Because this example deploys within a Java EE application server or standalone Servlet 3.x container, all we need is an empty web.xml file. The server will detect that an Application class is within your WAR and automatically deploy it. Your application is now ready to use!

Writing a Client

If you need to interact with a remote RESTful service like we just created, you can use the JAX-RS 2.0 Client API. The Client interface is responsible for managing client HTTP connections. I discuss the Client API in more detail in Chapter 8, but let’s look at how we might create a customer by invoking the remote services defined earlier in this chapter.

import javax.ws.rs.client.ClientBuilder;

import javax.ws.rs.client.Client;

import javax.ws.rs.client.Entity;

import javax.ws.rs.core.Response;

public class MyClient {

public static void main(String[] args) throws Exception {

Client client = ClientBuilder.newClient();

try {

System.out.println("*** Create a new Customer ***");

String xml = "<customer>"

+ "<first-name>Bill</first-name>"

+ "<last-name>Burke</last-name>"

+ "<street>256 Clarendon Street</street>"

+ "<city>Boston</city>"

+ "<state>MA</state>"

+ "<zip>02115</zip>"

+ "<country>USA</country>"

+ "</customer>";

Response response = client.target(

"http://localhost:8080/services/customers")

.request().post(Entity.xml(xml));

if (response.getStatus() != 201) throw new RuntimeException(

"Failed to create");

String location = response.getLocation().toString();

System.out.println("Location: " + location);

response.close();

System.out.println("*** GET Created Customer **");

String customer = client.target(location).request().get(String.class);

System.out.println(customer);

String updateCustomer = "<customer>"

+ "<first-name>William</first-name>"

+ "<last-name>Burke</last-name>"

+ "<street>256 Clarendon Street</street>"

+ "<city>Boston</city>"

+ "<state>MA</state>"

+ "<zip>02115</zip>"

+ "<country>USA</country>"

+ "</customer>";

response = client.target(location)

.request()

.put(Entity.xml(updateCustomer));

if (response.getStatus() != 204)

throw new RuntimeException("Failed to update");

response.close();

System.out.println("**** After Update ***");

customer = client.target(location).request().get(String.class);

System.out.println(customer);

} finally {

client.close();

}

}

}

The Client API is a fluent API in that it tries to look like a domain-specific language (DSL). The Client API has a lot of method chaining, so writing client code can be as simple and compact as possible. In the preceding example, we first build and execute a POST request to create a customer. We then extract the URI of the created customer from a Response object to execute a GET request on the URI. After this, we update the customer with a new XML representation by invoking a PUT request. The example only uses Strings, but we’ll see in Chapter 6 that JAX-RS also has content handlers you can use to marshal your Java objects automatically to and from XML and other message formats.

Wrapping Up

In this chapter, we discussed how to implement a simple customer database as a JAX-RS service. You can test-drive this code by flipping to Chapter 18. It will walk you through installing JBoss RESTEasy, implementing JAX-RS, and running the examples in this chapter within a servlet container.