Restlet 2.0 - First resource
Table of contents
This page illustrates how a Resource can handle the GET, POST, PUT and DELETE methods.
- Introduction
- The sample application
- Implementation of the Items Resource
- Implementation of the Item Resource
- Implementation of the base Resource
- Running the application
- The client application
- Conclusion
Introduction
Before starting to develop, we need to briefly introduce the idea of resource as implemented by the Restlet Framework. REST tells us that a resource is identified by a URI, can have one or more representations (also called variants) that may vary over time (e.g. the "current weather in Oaxaca"), and can handle method calls.
In the Restlet Framework, instances of the ServerResource classes are the final handlers of calls received by server connectors. A resource is responsible for declaring the list of supported representation types (instances of the Variant class) and for implementing the REST methods you want to support. The simplest way to do that is via a series of small annotations supported by the ServerResource subclasses :
In addition, each incoming call is handled by a dedicated resource instance, which means that you you don't need to make your resource subclasses thread-safe.
We assume that you have read the firstSteps page, and have some notions of what Component and Application are.
The sample application
Let's begin with the description of our sample application. It manages a list of items with create, read, update, and delete actions like a simple CRUD application. An item is simply characterized by a name and a description. After a short analysis, we define two Resources:
- Items : resource that identifies the collection of all available items.
- Item : resource that identifies a single item.
Now, let's define the URIs that will identify the resources. Assuming that our application is hosted on your own computer known as "localhost" and is listening to port 8182:
- http://localhost:8182/firstResource/items : URI of the "items" resource.
- http://localhost:8182/firstResource/items/{itemName} : URI of the "item" resource where {itemName} represents the name of an item.
Next, it's time to define the list of methods allowed on each Resource.
- The "items" resource responds to GET requests with an XML document that lists the currently registered items. In addition, this resource supports the creation of new items via POST requests. The POSTed entity contains both name and description of the new item and adopts the format of a Web form. If the resource succeeds in creating the new item, it answers with a "Success - resource created" status (HTTP 201 status code) and tells the client where the new resource can be found (HTTP "Location" header). Otherwise, it answers with a "Client error" status (HTTP 404 status code) and a simple error message.
- The "item" resource answers to GET requests with an XML document that shows the name and description of an item. It can also update and delete the item with PUT and DELETE requests.
Before describing the two Resources classes, here is the code of our Application. For the sake of simplicity the list of registered items is simply kept in memory as an attribute of the application and not in a real database. However, we assume that you may invit your friends to test the application, all at the same time. Since only one instance of our "FirstResourceApplication" will be used at runtime, we have to take care of thread-safety. That's why you will notice that the map of items is a final reference to a ConcurrentMap, rather than a plain Map.
package firstResource;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.restlet.Application;
import org.restlet.Restlet;
import org.restlet.routing.Router;
public class FirstResourceApplication extends Application {
/** The list of items is persisted in memory. */
private final ConcurrentMap<String, Item> items =
new ConcurrentHashMap<String, Item>();
/**
* Creates a root Restlet that will receive all incoming calls.
*/
@Override
public Restlet createInboundRoot() {
// Create a router Restlet that defines routes.
Router router = new Router(getContext());
// Defines a route for the resource "list of items"
router.attach("/items", ItemsResource.class);
// Defines a route for the resource "item"
router.attach("/items/{itemName}", ItemResource.class);
return router;
}
/**
* Returns the list of registered items.
*
* @return the list of registered items.
*/
public ConcurrentMap<String, Item> getItems() {
return items;
}
}
Implementation of the Items Resource
Let's begin with the Items resource. As seen above, it allows GET and POST requests. The support of POST requests is denoted by the implementation of the annotated method "acceptItem(Representation)" which lets you process the posted entity. Please note also that resources reject any methods that are not implemented with the "Method not allowed" status (HTTP 405 status code).
In the same way, the support of GET requests is denoted by the implementation of the annotated "toXml()" method which lets you generate the entity as an XML representation with the media type ("text/xml").
package firstResource;
import java.io.IOException;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Status;
import org.restlet.ext.xml.DomRepresentation;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.restlet.resource.Get;
import org.restlet.resource.Post;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Resource that manages a list of items.
*
*/
public class ItemsResource extends BaseResource {
/**
* Handle POST requests: create a new item.
*/
@Post
public Representation acceptItem(Representation entity) {
Representation result = null;
// Parse the given representation and retrieve pairs of
// "name=value" tokens.
Form form = new Form(entity);
String itemName = form.getFirstValue("name");
String itemDescription = form.getFirstValue("description");
// Register the new item if one is not already registered.
if (!getItems().containsKey(itemName)
&& getItems().putIfAbsent(itemName,
new Item(itemName, itemDescription)) == null) {
// Set the response's status and entity
setStatus(Status.SUCCESS_CREATED);
Representation rep = new StringRepresentation("Item created",
MediaType.TEXT_PLAIN);
// Indicates where is located the new resource.
rep.setIdentifier(getRequest().getResourceRef().getIdentifier()
+ "/" + itemName);
result = rep;
} else { // Item is already registered.
setStatus(Status.CLIENT_ERROR_NOT_FOUND);
result = generateErrorRepresentation("Item " + itemName
+ " already exists.", "1");
}
return result;
}
/**
* Generate an XML representation of an error response.
*
* @param errorMessage
* the error message.
* @param errorCode
* the error code.
*/
private Representation generateErrorRepresentation(String errorMessage,
String errorCode) {
DomRepresentation result = null;
// This is an error
// Generate the output representation
try {
result = new DomRepresentation(MediaType.TEXT_XML);
// Generate a DOM document representing the list of
// items.
Document d = result.getDocument();
Element eltError = d.createElement("error");
Element eltCode = d.createElement("code");
eltCode.appendChild(d.createTextNode(errorCode));
eltError.appendChild(eltCode);
Element eltMessage = d.createElement("message");
eltMessage.appendChild(d.createTextNode(errorMessage));
eltError.appendChild(eltMessage);
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
/**
* Returns a listing of all registered items.
*/
@Get("xml")
public Representation toXml() {
// Generate the right representation according to its media type.
try {
DomRepresentation representation = new DomRepresentation(
MediaType.TEXT_XML);
// Generate a DOM document representing the list of
// items.
Document d = representation.getDocument();
Element r = d.createElement("items");
d.appendChild(r);
for (Item item : getItems().values()) {
Element eltItem = d.createElement("item");
Element eltName = d.createElement("name");
eltName.appendChild(d.createTextNode(item.getName()));
eltItem.appendChild(eltName);
Element eltDescription = d.createElement("description");
eltDescription.appendChild(d.createTextNode(item
.getDescription()));
eltItem.appendChild(eltDescription);
r.appendChild(eltItem);
}
d.normalizeDocument();
// Returns the XML representation of this document.
return representation;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
Implementation of the Item Resource
Let's continue with the Item resource. As seen above, it allows GET, PUT and DELETE requests. The support of PUT requests is denoted by the implementation of the "storeItem(Representation)" method which lets you process the received entity.
In the same way, the support of DELETE requests is denoted by the implementation of the "removeItem()" method.
package firstResource;
import java.io.IOException;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Status;
import org.restlet.ext.xml.DomRepresentation;
import org.restlet.representation.Representation;
import org.restlet.resource.Delete;
import org.restlet.resource.Get;
import org.restlet.resource.Put;
import org.restlet.resource.ResourceException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
public class ItemResource extends BaseResource {
/** The underlying Item object. */
Item item;
/** The sequence of characters that identifies the resource. */
String itemName;
@Override
protected void doInit() throws ResourceException {
// Get the "itemName" attribute value taken from the URI template
// /items/{itemName}.
this.itemName = (String) getRequest().getAttributes().get("itemName");
// Get the item directly from the "persistence layer".
this.item = getItems().get(itemName);
setExisting(this.item != null);
}
/**
* Handle DELETE requests.
*/
@Delete
public void removeItem() {
if (item != null) {
// Remove the item from the list.
getItems().remove(item.getName());
}
// Tells the client that the request has been successfully fulfilled.
setStatus(Status.SUCCESS_NO_CONTENT);
}
/**
* Handle PUT requests.
*
* @throws IOException
*/
@Put
public void storeItem(Representation entity) throws IOException {
// The PUT request updates or creates the resource.
if (item == null) {
item = new Item(itemName);
}
// Update the description.
Form form = new Form(entity);
item.setDescription(form.getFirstValue("description"));
if (getItems().putIfAbsent(item.getName(), item) == null) {
setStatus(Status.SUCCESS_CREATED);
} else {
setStatus(Status.SUCCESS_OK);
}
}
@Get("xml")
public Representation toXml() {
try {
DomRepresentation representation = new DomRepresentation(
MediaType.TEXT_XML);
// Generate a DOM document representing the item.
Document d = representation.getDocument();
Element eltItem = d.createElement("item");
d.appendChild(eltItem);
Element eltName = d.createElement("name");
eltName.appendChild(d.createTextNode(item.getName()));
eltItem.appendChild(eltName);
Element eltDescription = d.createElement("description");
eltDescription.appendChild(d.createTextNode(item.getDescription()));
eltItem.appendChild(eltDescription);
d.normalizeDocument();
// Returns the XML representation of this document.
return representation;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
Implementation of the base Resource
Since our resources share the same need for access to the map of items hosted by the "FirstResourceApplication" instance, this method has been factored in a parent class called "BaseResource".
package firstResource;
import java.util.concurrent.ConcurrentMap;
import org.restlet.resource.ServerResource;
/**
* Base resource class that supports common behaviours or attributes shared by
* all resources.
*
*/
public abstract class BaseResource extends ServerResource {
/**
* Returns the map of items managed by this application.
*
* @return the map of items managed by this application.
*/
protected ConcurrentMap<String, Item> getItems() {
return ((FirstResourceApplication) getApplication()).getItems();
}
}
Running the application
Please refer to the corresponding sections of the firstSteps page to learn how to launch the application either in a servlet container or as a standalone application.
The client application
Once our Application is running, in a servlet container or a standalone application, we propose you to experiment our Resources with a simple client application. It simply creates, reads, updates then deletes an Item resource and prints on the standard output the result of each operation. You can run it from command line or inside your IDE. Here is the code:
package firstResource;
import java.io.IOException;
import org.restlet.data.Form;
import org.restlet.representation.Representation;
import org.restlet.resource.ClientResource;
import org.restlet.resource.ResourceException;
public class FirstResourceClientMain {
public static void main(String[] args) throws IOException,
ResourceException {
// Define our Restlet client resources.
ClientResource itemsResource = new ClientResource(
"http://localhost:8182/firstResource/items");
ClientResource itemResource = null;
// Create a new item
Item item = new Item("item1", "this is an item.");
Representation r = itemsResource.post(getRepresentation(item));
if (itemsResource.getStatus().isSuccess()) {
itemResource = new ClientResource(r.getIdentifier());
}
if (itemResource != null) {
// Prints the representation of the newly created resource.
get(itemResource);
// Prints the list of registered items.
get(itemsResource);
// Update the item
item.setDescription("This is an other description");
itemResource.put(getRepresentation(item));
// Prints the list of registered items.
get(itemsResource);
// delete the item
itemResource.delete();
// Print the list of registered items.
get(itemsResource);
}
}
/**
* Prints the resource's representation.
*
* @param clientResource
* The Restlet client resource.
* @throws IOException
* @throws ResourceException
*/
public static void get(ClientResource clientResource) throws IOException,
ResourceException {
clientResource.get();
if (clientResource.getStatus().isSuccess()
&& clientResource.getResponseEntity().isAvailable()) {
clientResource.getResponseEntity().write(System.out);
}
}
/**
* Returns the Representation of an item.
*
* @param item
* the item.
*
* @return The Representation of the item.
*/
public static Representation getRepresentation(Item item) {
// Gathering informations into a Web form.
Form form = new Form();
form.add("name", item.getName());
form.add("description", item.getDescription());
return form.getWebRepresentation();
}
}
You can find the sources of this sample application (built with the release 2.0 m5) in the "First resource application" files.
Conclusion
This page illustrates the use of one of the central concepts of the Restlet framework. When thinking about your Resources, keep in mind some important questions:
- How do I want to identify my resources?
- What kind of representations are they able to generate?
- What methods can they handle?
- Do I need to put my resources in a hierarchy helping to share some common behaviour and attributes?
We hope you that enjoyed these simple steps and we now encourage you to dive deeper in the Restlet tutorial.
Notes
- Thanks to Tim Peierls for the feed-back especially about thread-safety considerations.