A Simple “Restlet” Demo application

I’m assuming that if you’re here you already know the basics of REpresentational State Transfer.  I’m also going to assume that you’ve looked at all the goodies at the Restlet community site.  Further, I’m thinking that you want a quick start guide to show you how to implement it.  The following tutorial shows how I “found a home” with Restlet, what worked for me, and hopefully something that will work for you, too.

The Restlet API is a Java framework for the REST architectural style. The Noelios Restlet Engine (NRE) is available as the reference implementation, as well as several extensions to the API and to NRE.

Why is it cool?

To Quote Jérôme Louvel, founder and lead developer of Restlet:

“After several iterations, it became clear that it would be beneficial for developers to separate the Restlet project into two parts. The first part is a generic set of classes, called the Restlet API, including a few helper classes and a mechanism to register a Restlet implementation. The second part is a reference implementation, called the Noelios Restlet Engine, including several server and client connectors (HTTP, JDBC, SMTP, etc.), a set of representations (based on strings, files, XML documents, templates, streams, etc.) and a Directory able to serve static files from a tree of directories with automatic content negotiation based on file extensions. Since the first public release in November 2005, the project keeps maturing thanks to the help of an active community of users and developers.”

It’s cool because it does all that.  I love that fact that you can send a request to a Restlet engine, and you can return back this amazing variety of Representations based upon it.  The first exposure to Restlet for me came when I put forth a design for a beacon tracking system that could be triggered via HTML pages, Server calls, log-file digestion and anything else I could think of.  The lead developer turned me on to Restlet.

I later found ways to hook it up to a JBoss-Rules system, a Low-Latency Data Store for delivering customer demographic information, and a Affiliate Network lead quality validation system.  Each application had completely different requirements except that they needed to be a service.  I decided on REST instead of  SOAP, and the “rest” is in the implementations.  It subscribes to my basic requirements for an application:

 Any application that represents good design gives those that interact
with it the feeling that “it just works”.

Once you work with Restlet a bit, get an application running, manipulate what you’ve built into other things, and get real expertise around it, you’ll find, like I have, that it just works

The Jars you need for this application.

To run this application, you’ll need the following jar files:

  • com.noelios.restlet.ext.servlet_2.5.jar
  • com.noelios.restlet.jar
  • org.restlet.jar
  • xalan-2.7.0.jar
  • xrcesImpl.jar
This is the complete listing of libraries. 

Running Restlet from Tomcat — Configure the web.xml file.

You’ll want to run Restlet from a WAR file in most production cases, but you really don’t have to.  I like to use it because it creates homogeneous environments for my ITOPs people to work with; nice, similar stacks for maintenance purposes, so we’ll do it this way here.  This is the web.xml file used in the web application that we’re deploying our service through:

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
                 http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <display-name>first steps servlet</display-name>
    <!-- Application class name -->
    <context-param>
        <param-name>org.restlet.application</param-name>
        <param-value>
            com.dg.demo.restlet.application.DemoApplication
        </param-value>
    </context-param>

    <!-- Restlet adapter -->
    <servlet>
        <servlet-name>ServerServlet</servlet-name>
        <servlet-class>
            com.noelios.restlet.ext.servlet.ServerServlet
        </servlet-class>
    </servlet>

    <!-- Catch all requests -->
    <servlet-mapping>
        <servlet-name>ServerServlet</servlet-name>
        <url-pattern>/demo/*</url-pattern>
    </servlet-mapping>
</web-app>

Note that com.dg.demo.restlet.application.DemoApplication will be the Application class file that you will write.  ServerServlet comes with the libraries.  Note that the “servlet-mapping” that I’m using here is “/demo/*”.  This means that all URLs directed to the Restlet must have “/demo/” as their root, (i.e. http://localhost:8080/demo/&#8230;).

The Application Class

The Application Class is the “manager” for the Restlet Application. This class will find the proper resource with respect to the URL that is presented to it.  In the case of this application, we’ll be mapping incoming URLs to:

  • a “System up” Resource that shows that the Restlet is actually running and mapping correctly.
  • a “Mapping” Resource that will allow for a list of cities and states to be shown based upon given inputs.
  • an “XML” Resource that shows mappings of Cities and States as an XML Representation.

Here is the application Class:

package com.dg.demo.restlet.application;

import com.dg.demo.restlet.resource.CityXMLResource;
import com.dg.demo.restlet.resource.MapResource;
import com.dg.demo.restlet.resource.SystemResource;
import org.restlet.Application;
import org.restlet.Context;
import org.restlet.Restlet;
import org.restlet.Router;
import org.restlet.data.MediaType;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.resource.StringRepresentation;

/**
 * The Application Root Class.  Maps all of the resources.
 */
public class DemoApplication extends Application {

    /**
     * Creates a new DemoApplication object.
     */
    public DemoApplication() {
        //empty
    }

    /**
     * Public Constructor to create an instance of DemoApplication.
     *
     * @param parentContext - the org.restlet.Context instance
     */
    public DemoApplication(Context parentContext) {
        super(parentContext);
    }

    /**
     * The Restlet instance that will call the correct resource
     * depending up on URL mapped to it.
     *
     * @return -- The resource Restlet mapped to the URL.
     */
    @Override
    public Restlet createRoot() {
        Router router = new Router(getContext());

        router.attach("/sys", SystemResource.class);
        router.attach("/{key}/maps", MapResource.class);
        router.attach("/xml", CityXMLResource.class);

        Restlet mainpage = new Restlet() {
            @Override
            public void handle(Request request, Response response) {
                StringBuilder stringBuilder = new StringBuilder();

                stringBuilder.append("<html>");
                stringBuilder.append("<head><title>Sample Application " +
                        "Servlet Page</title></head>");
                stringBuilder.append("<body bgcolor=white>");
                stringBuilder.append("<table border=\"0\">");
                stringBuilder.append("<tr>");
                stringBuilder.append("<td>");
                stringBuilder.append("<h3>available REST calls</h3>");
                stringBuilder.append("<ol><li>/sys --> returns system " +
                        "up and date string</li>");
                stringBuilder.append("<li>/all/maps --> returns a list" +
                        " of all the cities and states</li>");
                stringBuilder.append("<li>/{key}/maps --> returns a list " +
                        "of cities for a particular state (CA,IL,TX)<br/>");
                stringBuilder.append("using one of the keys from the \"all\" " +
                        "call<br/> pasted in place as the {key}.</li>");
                stringBuilder.append("<li>/xml --> POST or GET URL or web " +
                        "form elements as XML to this</li>");
                stringBuilder.append("</ol>");
                stringBuilder.append("</td>");
                stringBuilder.append("</tr>");
                stringBuilder.append("</table>");
                stringBuilder.append("</body>");
                stringBuilder.append("</html>");

                response.setEntity(new StringRepresentation(
                        stringBuilder.toString(),
                        MediaType.TEXT_HTML));
            }
        };
        router.attach("", mainpage);
        return router;
    }
}

With this Application I tried to show a “default”, “servlet-like” page returned by a Restlet created as an anonymous inner class — you can do that with all your URL requests if you like, but creating different Resource implementations is much easier to manage and write tests for. It is also very important to set the Context of the application in the Constructor (calling the superclass).  I forgot to do this and lost about an hour before I finally found it.

Once the Resources are mapped, You’re ready to rock and roll.  We’ll create a simple Model, and then get into the Resource Classes.

 A Simple Model to Work From:

We need something to do with our application, so I decided to create a simple “persistence layer” that has a map of cities and states.  I could have done more and it could have been more complex, but I just wanted something that you can bang against and get an idea about how it all works.  I created an implementation class that contains a map of cities and states extending java.util.AbstractMap.  I then exposed various structures of this mapping through an interface class that the application will use to return information by implementing an Interface against it, obfuscating anything that I didn’t want to expose:

package com.dg.demo.model;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * A simple interface to return a map of
 * values for the demonstration.
 */
public interface MappedPersistence {

    // returns a formatted string of the cities
    // mapped to a particular state.
    String getCommonValues(String state);

    // returns a set of cities, which are
    // the map keys.
    Set<String> keys();

    // returns a map of cities <K> with states<V>
    // as the value.
    Map<String, String> proxy();

    // returns a set of states available.
    Set<String> valueSet();

    //returns a List of cities.
    List<String> mappedKeys(String s);

}

The implementation of the Interface above is MappedCityPersistence:

package com.dg.demo.model.impl;

import com.dg.demo.model.MappedPersistence;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Implementation of MappedPersistence Interface.
 * Extends Abstract Map, but obfuscates the
 * mappings via the MappedPersistence Interface.
 */
public class MappedCityPersistence
        extends AbstractMap<String, String>
        implements MappedPersistence {

    private Map<String, String> proxyMap;

    public MappedCityPersistence() {
        proxyMap = new HashMap<String, String>();
        loadProxy();
    }
    // this class must be implemented...
    public Set<Entry<String, String>> entrySet() {
        return proxyMap.entrySet();
    }
    //the values for persistence.
    private void loadProxy() {
        proxyMap.put("Los Angeles", "CA");
        proxyMap.put("Santa Barbara", "CA");
        proxyMap.put("San Francisco", "CA");
        proxyMap.put("San Diego", "CA");
        proxyMap.put("Dallas", "TX");
        proxyMap.put("Houston", "TX");
        proxyMap.put("Austin", "TX");
        proxyMap.put("El Paso", "TX");
        proxyMap.put("Chicago", "IL");
        proxyMap.put("Joliet", "IL");
        proxyMap.put("Naperville", "IL");
        proxyMap.put("Oak Park", "IL");
    }

    @Override
    public String toString() {
        StringBuilder builder =
                new StringBuilder("City/State values:\n");
        for (String s : proxyMap.keySet()) {
            builder.append(s).append(" => ")
                    .append(proxyMap.get(s)).append("\n");
        }
        return builder.toString();
    }

    public String getCommonValues(String value) {
        StringBuilder builder =
                new StringBuilder("Cities located in "
                        + value + ":\n");
        for (String s : proxyMap.keySet()) {
            if (proxyMap.get(s).equals(value)) {
                builder.append(s).append("\n");
            }
        }
        return builder.toString();
    }

    public Set<String> keys() {
        return proxyMap.keySet();
    }

    public Map<String, String> proxy() {
        return this.proxyMap;
    }

    public Set<String> valueSet() {
        Set<String> quickAndDirtySet = new HashSet<String>();
        for (String s : proxyMap.values()) {
            quickAndDirtySet.add(s);
        }
        return quickAndDirtySet;
    }

    public List<String> mappedKeys(String s) {
        List<String> quickAndDirtyList = new ArrayList<String>();
        for (String key : proxyMap.keySet()) {
            if (proxyMap.get(key).equals(s)) {
                quickAndDirtyList.add(key);
            }
        }
        return quickAndDirtyList;
    }

    public static void main(String[] args) {
        MappedCityPersistence p = new MappedCityPersistence();
        System.out.println(p.getCommonValues("TX"));
    }
}

Note the extra methods that are now obfuscated by the Mapped Persistence Interface.

Building the Resources that are Mapped to URLs

The first, and easiest to implement of the Resources is the SystemResource that I created to show whether or not I got the mappings right and if the system is running or not.  It returns a basic “text” string that says the system is running and what time the JVM says it is:

SystemResource.java

package com.dg.demo.restlet.resource;
import org.restlet.Context;
import org.restlet.data.MediaType;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.resource.Representation;
import org.restlet.resource.Resource;
import org.restlet.resource.StringRepresentation;
import org.restlet.resource.Variant;

import java.util.Calendar;

/**
 * Simple String retrieval to determine whether the system is up
 * and running.  it adds a timestamp for varification that the
 * message isn't cached and allows for identification of
 * the time zone.
 */
public class SystemResource extends Resource {

    /**
     * Creates a new MapResource object.
     *
     * @param context  -- Restlet Context Instance.
     * @param request  -- Restlet Request Instance.
     * @param response -- Restlet Response Instance.
     */
    public SystemResource(Context context,
                          Request request,
                          Response response) {
        super(context, request, response);
        // Here we add the representation variants exposed
        getVariants().add(new Variant(MediaType.TEXT_PLAIN));
    }

    /**
     * Do not allow post to this resource.
     *
     * @return -- always false for this resource.
     */
    public boolean allowPost() {
        return false;
    }

    /**
     * default method for "get" from Client Application.
     *  Returns wheter or not the system is available,
     * and the current time.
     *
     * @param variant -- Available variants.
     *
     * @return -- A String representing whether or
     *            not the demo system is available, etc.
     */
    public Representation represent(Variant variant) {
        //LQSCache.getInstance();
        String message = "hello!, the Demo " +
                "Service is available." +
                " Time of request is:"
                + Calendar.getInstance()
                .getTime().toString();

        return new StringRepresentation(message,
                MediaType.TEXT_PLAIN);
    }
}

The mapping resource shows all of the cities and states mapped, and also shows the cities mapped to a particular state when that state is input within the url (i.e. http://localhost:8080/demo/all/maps — to show all cities and their city mappings, ./demo/TX/maps to get all cities mapped to Texas):

MapResource.java

package com.dg.demo.restlet.resource;

import com.dg.demo.model.MappedPersistence;
import com.dg.demo.model.impl.MappedCityPersistence;
import org.restlet.Context;
import org.restlet.data.MediaType;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.resource.Representation;
import org.restlet.resource.Resource;
import org.restlet.resource.StringRepresentation;
import org.restlet.resource.Variant;

/**
 * Utility Resource to view the various strings
 * mapped in the Persistence.
 */
public class MapResource extends Resource {

    /**
     * The map requested by the client application
     */
    String mapName;

    /**
     * Creates a new MapResource object.
     *
     * @param context  -- The restlet Context instance.
     * @param request  -- The restlet REquest instance.
     * @param response -- The restlet Response instance.
     */
    public MapResource(Context context,
                       Request request,
                       Response response) {
        super(context, request, response);
        this.mapName = (String) request.getAttributes().get("key");

        // Here we add the representation variants exposed
        getVariants().add(new Variant(MediaType.TEXT_PLAIN));
    }

    /**
     * Do not allow post to this resource.
     *
     * @return -- always false for this resource.
     */
    @Override
    public boolean allowPost() {
        return false;
    }

    /**
     * Collect the values for a particular mapping and return
     * a formatted string.  By inputting "all" into the request,
     * it returns a list of the mappings available. When this
     * service returns, substituting "all" for one of the
     * returned values will return the values for that mapping key
     *
     * @param variant -- The variant describing the return mappings.
     *
     * @return -- a formatted string with the map values as a list.
     */
    @Override
    public Representation represent(Variant variant) {
        //UDCAPICache.getInstance();
        MappedPersistence persistence = new MappedCityPersistence();

        //LQSCache.getInstance();
        String message;
        //String mapName = (String) getRequest().getAttributes().get("key");

        if (mapName.equals("all")) {
            message = persistence.toString();
        } else if (persistence.valueSet().contains(mapName)) {
            message = persistence.getCommonValues(mapName);
        } else {
            message = "NO VALUES FOUND FOR: " + mapName;
        }
        return new StringRepresentation(message, MediaType.TEXT_PLAIN);
    }
}

The XML resource returns a list of cities that are mapped to each state as an xml representation.  It allows a post or get, via the “acceptRepresentation()” (POST) and “represent()” (GET) methods.  I’ve put both of these in bold, along with other things that seemed important to me.  Each calls the same private method that creates the XML representation:

CityXMLResource.java
package com.dg.demo.restlet.resource;

import com.dg.demo.model.MappedPersistence;
import com.dg.demo.model.impl.MappedCityPersistence;
import org.restlet.Context;
import org.restlet.data.MediaType;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.resource.DomRepresentation;
import org.restlet.resource.Representation;
import org.restlet.resource.Resource;
import org.restlet.resource.Variant;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import java.io.IOException;

public class CityXMLResource extends Resource {

    /**
     * @param context  -- Restlet Context Instance.
     * @param request  -- Restlet Request Instance.
     * @param response -- Restlet Response Instance.
     */
    public CityXMLResource(Context context,
                           Request request,
                           Response response) {
        super(context, request, response);
        getVariants().add(
                new Variant(MediaType.TEXT_PLAIN));
        getVariants().add(
                new Variant(MediaType.APPLICATION_XML));
        //handle(request,response);
    }

    /**
     * Flag to allow POST via web form.
     *
     * @return true if POST is allowed, false if not.
     */
    public boolean allowPost() {
        return true;
    }

    /**
     * Represent the requested object in the requested format.
     *
     * @param variant -- the requested variant or default.
     *
     * @return an XML representation or an error string.
     */
    public Representation represent(Variant variant) {
        DomRepresentation representation = null;
        try {
            representation = getFraudXMLRepresentation();
        } catch (IOException e) {
            e.printStackTrace();
            getResponse().setEntity("ERROR " + e.getMessage(),
                    MediaType.TEXT_PLAIN);
        }
        return representation;
    }

    /**
     * Handle a POST Http request.
     *
     * @param entity -- the declared entity
     */
    public void acceptRepresentation(Representation entity) {
        System.out.println("USING POST");

        try {
            if (entity.getMediaType()
                    .equals(MediaType.APPLICATION_WWW_FORM, true)) {
                DomRepresentation representation =
                        getFraudXMLRepresentation();
                // Returns the XML representation of this document.
                getResponse().setEntity(representation);
            }
        } catch (IOException e) {
            e.printStackTrace();
            getResponse().setEntity("ERROR " + e.getMessage(),
                    MediaType.TEXT_PLAIN);
        }
    }

    /**
     * Creates the XML that will be returned to the requesting client.
     *
     * @return - The formatted XML Document
     */
    private DomRepresentation getFraudXMLRepresentation() throws IOException {

        DomRepresentation representation =
                new DomRepresentation(MediaType.TEXT_XML);

        MappedPersistence persistence =
                new MappedCityPersistence();

        // Generate a DOM document representing the list of frauds generated..
        Document d = representation.getDocument();

        //The Root Element, with it's corresponding attribute for the total score.
        Element root = d.createElement("citystatemappings");
        root.setAttribute("numberofstates",
                String.valueOf(persistence.valueSet().size()));
        d.appendChild(root);

        //If there are no frauds, then we skip this and just return the root.
        for (String state : persistence.valueSet()) {
            Element stateElement = d.createElement("state");
            stateElement.setAttribute("name", state);

            for (String city : persistence.mappedKeys(state)) {
                Element cityElement = d.createElement("city");
                cityElement.appendChild(d.createTextNode(city));

                stateElement.appendChild(cityElement);
            }
            //tie the frauds to the root.
            root.appendChild(stateElement);
        }
        d.normalizeDocument();
        return representation;
    }
}

Compile these classes into your web application with your lib jars, and make sure the “web.xml” file is configured as shown earlier in this text.  Create a .war file or run from your IDE.  You should be able to use the URLS that have been configured into the DemoApplication class file to view data in different formats.  If your application is configured to run on http://localhost:8080&#8230;

 

hello!, the Demo Service is available. Time of request is:Tue Sep 23 17:53:42 PDT 2008
City/State values:
Austin => TX
Chicago => IL
El Paso => TX
Los Angeles => CA
Naperville => IL
Houston => TX
San Diego => CA
Santa Barbara => CA
Dallas => TX
Oak Park => IL
Joliet => IL
San Francisco => CA
Cities located in TX:
Austin
El Paso
Houston
Dallas

http://localhost:8080/demo/xml — Finally, you want to get an xml representation of all this (view source):

<?xml version="1.0" encoding="UTF-8"?>
<citystatemappings numberofstates="3">
    <state name="CA">
        <city>Los Angeles</city>
        <city>San Diego</city>
        <city>Santa Barbara</city>
        <city>San Francisco</city>
    </state>
    <state name="TX">
        <city>Austin</city>
        <city>El Paso</city>
        <city>Houston</city>
        <city>Dallas</city>
    </state>
    <state name="IL">
        <city>Chicago</city>
        <city>Naperville</city>
        <city>Oak Park</city>
        <city>Joliet</city>
    </state>
</citystatemappings>

From here you’re on your own. You can see how to further create things that you might need for your projects; there are other representations that you can work with, including some very interesting uses of the java.util.nio libraries that allow for blazing fast file retrieval and delivery. You can even send Java Objects around. It’s worth looking through the Restlet API Docs at this point.

About these ads

9 thoughts on “A Simple “Restlet” Demo application

  1. Pingback: Get Request Attributes in Restlet 2.0 | The Largest Forum Archive

  2. hi,

    I am new here, I am trying to understand this demo. I am new in java, could you please help me to start up quickly?

    Thanks.
    asad

  3. The Javadocs are very complete — you’ll have to run it through your debugger to find out how it works. Keep at it — you’ll get it right. If you’re at the point where you’re having trouble with a debugger, don’t be afraid to ask someone near you for help.

  4. Pingback: Restlet Server: OSGi and Maven « Knowledge Networks

  5. Pingback: HTTP Access Control: programando REST de VERDAD

  6. Hi,

    Thanks for a fab sample. It is straightforward and brings home the point. I was able to use it with Tomcat and got to understand restlets better.

    My next step is however to be able to use it in conjunction with other modules on JBoss.
    I assumed that the war thus created from above example, should just work when deployed in JBoss. But on deploying when I request the urls via browser, JBoss throws an exception:
    java.lang.NoSuchMethodError: org.restlet.Client.get(Ljava/lang/String;)Lorg/restlet/data/Response;
    com.noelios.restlet.ext.servlet.ServerServlet.createComponent(ServerServlet.java:378).

    Have you come across this before? If you have any ideas, do share them.

    Thanks!

  7. It seems DomRepresentation has been deprecated…so the resource class CityXMLResource throws error.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s