A RESTful Web Service as Restlet Resources

The Restlet web framework supports RESTful web services, and the API is similar to JAX-RS; indeed, a Restlet application can use JAX-RS annotations such as @Produces instead of or in addition to Restlet annotations. This section adapts the earlier JAX-RS adages service to a Restlet implementation, which is published both with a web server and with a standalone Java application. The revised service, adages2, implements the four CRUD operations.

A Restlet web service has three main parts, each of which consists of one or more Java classes:

If the Restlet service is deployed with a web server such as Tomcat or Jetty, then the Restlet ServerServlet acts as an interceptor for requests against the Restlet service. The interceptor servlet interacts with the Restlet Application, which contains the routing table, so that a properly formatted request winds up at the correct Restlet resource. The architecture is simple and clean. Deployment of adages2 service requires a web.xml document (see Example 2-12) that sets up the Restlet interceptor (line 1) and links the interceptor to the Restlet Application (line 2).

Example 2-12. The web.xml file for the adages2 Restlet service

The Adage class (see Example 2-13) is mostly unchanged from the earlier version of the service. There is now an id property (line 1) to support searching for a specified Adage on a GET, PUT, or DELETE request.

Example 2-13. The Adage POJO class in the adages2 Restlet service

The Adages class (see Example 2-14) has a static list (line 1) of the thread-safe type CopyOnWriteArrayList, which substitutes for a persistence store such as database. The class has a toPlain method (line 2) to support a text/plain response on a GET request to the adages2 service. The find method (line 3) supports GET, PUT, and DELETE requests for a specified Adage in the list adages, and the add method (line 4) supports POST requests by adding a newly created Adage to the list.

Example 2-14. The POJO class Adages in the adages2 Restlet service

The AdagesApplication class (see Example 2-15) extends the Restlet Application class (line 1) and interacts with the service’s publisher. In the case of a web server such as Tomcat, this class works with the Restlet ServerServlet to dispatch incoming service requests to the proper resource. The dispatching is done through a pattern-driven routing table at the end of the class.

Example 2-15. The Restlet Application class with the routing table

package adages2;

import org.restlet.Application;
import org.restlet.Restlet;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.routing.Router;
import org.restlet.data.Status;
import org.restlet.data.MediaType;

public class AdagesApplication extends Application {                           1

    @Override
    public synchronized Restlet createInboundRoot() {
        // To illustrate the different API possibilities, implement the
        // DELETE operation as an anonymous Restlet class. For the
        // remaining operations, follow Restlet best practices and
        // implement each as a Java class.
        // DELETE handler
        Restlet janitor = new Restlet(getContext()) {                          2
                public void handle(Request request, Response response) {
                    String msg = null;
                    String sid = (String) request.getAttributes().get("id");
                    if (sid == null) msg = badRequest("No ID given.\n");
                    Integer id = null;
                    try {
                        id = Integer.parseInt(sid.trim());
                    }
                    catch(Exception e) { msg = badRequest("Ill-formed ID.\n"); }
                    Adage adage = Adages.find(id);
                    if (adage == null)
                        msg = badRequest("No adage with ID " + id + "\n");
                    else {
                        Adages.getList().remove(adage);
                        msg = "Adage " + id + " removed.\n";
                    }
                    // Generate HTTP response.
                    response.setEntity(msg, MediaType.TEXT_PLAIN);
                }
            };
        // Create the routing table.
        Router router = new Router(getContext());
        router.attach("/",            PlainResource.class);                     3
        router.attach("/xml",         XmlAllResource.class);                    4
        router.attach("/xml/{id}",    XmlOneResource.class);
        router.attach("/json",        JsonAllResource.class);                   5
        router.attach("/create",      CreateResource.class);                    6
        router.attach("/update",      UpdateResource.class);
        router.attach("/delete/{id}", janitor); // instance of anonymous class
        return router;
    }
    private String badRequest(String msg) {
        Status error = new Status(Status.CLIENT_ERROR_BAD_REQUEST, msg);
        return error.toString();
    }
}

Each adages2 resource could be written as an anonymous class encapsulated inside the Application class; however, under Restlet best practices, these resources should be implemented as relatively small, individual classes such as PlainResource (line 3), XmlAllResource (line 4), JsonAllResource (line 5), CreateResource (line 6), and so on. For illustration, however, the AdagesApplication class does include an anonymous class that implements the Restlet interface by defining the handle method, which has Request and Response parameters; the Response and Request types are quite similar to the HttpServletRequest and HttpServletResponse types, respectively. The reference to an instance of this anonymous class is named janitor (line 2) because the class handles DELETE requests by removing a specified Adage from the list of adages.

The routing table at the end of AdagesApplication class follows a popular idiom for RESTful frameworks such as Restlet: a URI pattern maps to a specified resource. A routing table entry such as:

router.attach("/", PlainResource.class);

maps the URI /, the single slash, to the PlainResource, thereby making this resource the default one. By contrast, the entry:

router.attach("/xml/{id}", XmlOneResource.class); // {id} is a parameter

includes the parameter id in braces, where the numerical id identifies the desired Adage. The URI for the UpdateResource does not include the identifier for the Adage in question because this information is supplied in the body of the PUT request.

The resource classes are quite short because each has very specialized, hence limited, functionality. For example, the XmlAllResource is Example 2-16.

Example 2-16. The XmlAllResource Restlet resource

There are various Java ways to generate XML, including the DOM (Document Object Model) or tree-based method shown in the XmlAllResource (line 2), which builds the XML tree out of the Adages. This resource is reachable, as the @Get annotation signals (line 1), only with a GET request.

The CreateResource class (see Example 2-17) highlights some nice features of the Restlet API. The argument to the create method, of Restlet type Representation (line 1), represents the HTTP request body. The Restlet class Form (line 2) makes it easy to search for specified keys, in this case words, with which the corresponding values can be accessed, in this case the text for the new Adage.

Example 2-17. The CreateResource class in the adages2 service

A JAX-RS resource typically encapsulates several methods, each annotated with a distinct combination of HTTP verb (for instance, @GET or @POST) and URI (that is, @Path). In this sense, a JAX-RS resource class is multipurpose: its various methods handle various HTTP requests. The Restlet approach differs. Each resource class is, in best practices, single-purpose: one annotated method is the callback for an HTTP request targeted at a particular URI. The Restlet resource classes thus tend to be small, as the adages2 service illustrates.

The RESTful adages2 service can be deployed to Tomcat in the usual way:

% ant -Dwar.name=adages2 deploy

Once the service is deployed, some curl calls can be used to confirm that the service is behaving correctly. Here is a series of test calls. Each begins with a comment, introduced with two semicolons, followed by the curl call itself, and ending with the output, which is formatted for readability.

;; GET all in plain text
% curl --request GET http://localhost:8080/adages2/
 1: What can be shown cannot be said. -- 7 words
 2: If a lion could talk, we could not understand him. -- 10 words
 ...
;; GET all in XML
% curl --request GET http://localhost:8080/adages2/xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<adages>
<adage> 1: What can be shown cannot be said. -- 7 words</adage>
<adage> 2: If a lion could talk, we could not understand him. -- 10 words</adage>
...
;; GET all in JSON
% curl --request GET http://localhost:8080/adages2/json
[ 1: What can be shown cannot be said. -- 7 words,
  2: If a lion could talk, we could not understand him. -- 10 words,
...
;; GET Adage with id of 2 in XML
% curl --request GET http://localhost:8080/adages2/xml/2
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<adage>
   2: If a lion could talk, we could not understand him. -- 10 words
</adage>
;; Create a new adage
% curl --request POST --data "words=This is a test" \
  http://localhost:8080/adages2/create
The adage 'This is a test' has been added.
;; Delete the newly added Adage
% curl --request DELETE http://localhost:8080/adages2/delete/6
Adage 6 removed.

Restlet is excellent at separating concerns: the web service is one concern and its publication is quite another. The adages2 service can be deployed with Tomcat or Jetty but also—and with ease—using a standard Java application (see Example 2-18).

The two import statements (lines 1 and 2) in Main require only the org.restlet.jar file to compile and run, but the various Restlet resource classes such as XmlAllResource and JsonAllResource require other JARs. The command-line server listens to HTTP connections on port 8182 (line 3) for requests against the URI that begins with /adages (line 4).

The ZIP file with the sample applications includes Main.jar, an executable JAR file that can be used instead of Tomcat or Jetty to publish the service:

% java -jar Main

The built-in server listens for requests indefinitely, and the functionality of the adages2 service is unchanged in this command-line option for publication. For example, here is a request for the adages in plain text together with the response:

% curl localhost:8182/adages/

 1: What can be shown cannot be said. -- 7 words
 2: If a lion could talk, we could not understand him. -- 10 words
 3: Philosophy is a battle against the bewitchment of our intelligence
    by means of language. -- 14 words
 4: Ambition is the death of thought. -- 6 words
 5: The limits of my language mean the limits of my world. -- 11 words

The URI begins with /adages, in effect the counterpart of the WAR filename under Tomcat or Jetty. The URI ends with the slash, giving /adages/, which maps to the PlainResource in the adages2 service.

The command-line option for publishing is nicely suited for development. In a production environment, web servers such as Tomcat and Jetty or application servers such as GlassFish and WebSphere provide levels of support that a standalone Java application simply cannot match.