Jan
12
Tapestry and Restlet – Updated
By Taylor
Filed Under Software
As one of our last big initiatives on Tapestry before we migrate platforms we’re starting to build a REST api.
REST stands for Representational Entity State Transfer, which explains nothing to someone not already familiar with it. Essentially, one uses URL’s to define resources (http://mysite.com/users/trails for example), and leverages existing HTTP features instead of overloading parameters. Specifically, using things like the HTTP method instead of a custom “action” parameter to specify the desired action to be performed on the resource specified by the url.
A former employer of mine, Gavin Terrill once pointed me to this introduction to REST article which is highly informative, and immediately sold me on the concept.
I have been for some time been a big fan of standards-based HTML. Using things like <strong> instead of <span style=”font-weight:bold”> always struck me as an improvement in elegance; to leverage the tooling provided by a medium means others have an easier time understanding what your app is talking about, be it screen readers, browsers, search engines, etc…
REST strikes me as an analogue to standards-compliant HTML, the intent being to leverage things already defined by HTTP rather than re-inventing the wheel. It turns out that HTTP is fairly feature-rich, much more so than the POST-and-GET-with-cookies extent to which it used by most webapps today.
REST certainly has a couple points of ambiguity, for example batching, but there are options here with various tradeoffs.
Even though we plan to decommission Tapestry in our application, we’re going ahead with this REST implementation because a) business needs dictate that we need this API b) the implementation is fairly independent of Tapestry.
There are a great many REST packages out there, we landed on Restlet simply because it seemed to require minimal integration, has a great set of features, and had the recommendation of a colleague.
We’re using their 2.0 package which is in a fairly high state of flux at the moment, but offers improvements over their 1.X version that make this worthwhile. The code herein is based on their 2.0-M6 milestone release.
You’ll need to setup the dependencies in your pom (I assume you’re using maven), for the following artifacts which can be retrieved from Restlet’s public maven repo at http://maven.restlet.org/:
<dependency> <groupId>org.restlet.jee</groupId> <artifactId>org.restlet</artifactId> <version>2.0-M6</version> </dependency> <dependency> <groupId>org.restlet.jee</groupId> <artifactId>org.restlet.ext.servlet</artifactId> <version>2.0-M6</version> </dependency>
Note the jee in the group id. This can be swapped for jse if you’re operating in a jse environment.
Restlet at its simplest is built based on the idea of an “application” which manages “resources”. While it can certainly do much more complex stuff, this is sufficient for now. We are familiar with resources as a core concept to REST, but the idea of adding on object called “Application” into your application might feel a little counter intuitive. Your restlet “Application” can more easily be thought of as a REST url mapper. It is the central object for your REST API.
You’ll need to build a basic one and an initial resource. For these, please look at the resource and application in this restlet tutorial.
You’ll need to tweak the web.xml for your webapp to add the following snippet:
<context-param> <param-name>org.restlet.application</param-name> <param-value> firstSteps.FirstStepsApplication </param-value> </context-param> <servlet> <servlet-name>RestletServlet</servlet-name> <servlet-class> org.restlet.ext.servlet.ServerServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>RestletServlet</servlet-name> <url-pattern>/api/*</url-pattern> </servlet-mapping>
I’ve mapped my REST stuff to appear under [application context]/api , [application context] being whatever you’ve setup your Tapestry application to run under.
We need to tell Tapestry to ignore the rest api, so we need to add the following to our main AppModule:
public static void contributeIgnoredPathsFilter( Configuration<String> configuration){ // Tell tapestry to ignore this subpath, // which will be handled by the rest application configuration.add("/api/.*"); }
This will prevent tapestry from trying to service requests to the REST api.
The final piece of the puzzle is to hook up Resources to url templates, and have those Resources interact with Tapestry-managed services. Typically, the approach would be to create singleton resources, and retrieve them through Tapestry’s IOC via a strategy pattern, mapping url templates to singleton Tapestry-managed Resources or something similar. The challenge here is that restlet’s resources are necessarily one-off and restlet handles the instantiation. Specifically, from the ServerResource javadoc:
Concurrency note: contrary to the
Uniformclass and its mainRestletsubclass where a single instance can handle several calls concurrently, one instance ofServerResourceis created for each call handled and accessed by only one thread at a time.
This could, I’m sure, be overridden, but this requires more plumbing code than I care to write.
To get our Resources talking to our Tapestry services, the restlet Resource will ask the restlet Application for services, hence the restlet Application needs access Tapestry’s registry, accomplished using the following:
package firstSteps; import org.restlet.Application; import org.restlet.Restlet; import org.restlet.routing.Router; import javax.servlet.ServletContext; import org.apache.tapestry5.TapestryFilter; import org.apache.tapestry5.ioc.Registry; public class FirstStepsApplication extends Application { private Registry registry; /** * Creates a root Restlet that will receive all incoming calls. */ @Override public synchronized Restlet createRoot() { if ( this.registry==null ) { //This only works on RESTLET versions 2.0m6 or above //http://restlet.tigris.org/ds/viewMessage.do?dsForumId=4447&dsMessageId=2426537 ServletContext ctx = (ServletContext) getContext().getAttributes().get( "org.restlet.ext.servlet.ServletContext" ); registry = (Registry)ctx.getAttribute(TapestryFilter.REGISTRY_CONTEXT_NAME); if ( registry==null ) throw new RuntimeException( "Failed to obtain a reference to Tapestry`s registry"); } // Create a router Restlet that routes each call to a // new instance of HelloWorldResource. Router router = new Router(getContext()); // Defines only one route router.attach("/hello", HelloWorldResource.class); return router; } public <T> T getService(Class<T> service) { return registry.getService(service); } }
Inside the createRoot method, we retrieve the registry from the ServletContext, and store it in an instance member. We also add a method for service retrieval to be used by resources. One could also add an additional method to retrieve Tapestry services by id, if needed.
Finally, we augment our resources:
package firstSteps; import org.restlet.resource.Get; import org.restlet.resource.ServerResource; import com.myApp.service.MyService; /** * Resource which has only one representation. * */ public class HelloWorldResource extends ServerResource { private MyService myService; public HelloWorldResource(){ myService = ((FirstStepsApplication)getApplication()).getService( MyService.class); if ( myService ==null ) throw new RuntimeException("Failed to obtain a reference to the MyService"); } @Get public String represent() { return myService.getRepresentation(); } }
That should be enough to get you rolling on Restlet and Tapestry.
UPDATE: I realized I missed a couple of things in this post. Firstly, and most critically I missed triggering the cleanup of Tapestry resources which get stored in ThreadLocal. This is addressed in a subsequent post. Further, I had based some of the above code off of out of date doumentation, and have since corrected one or two method names. Finally, I have corrected the maven dependencies, one is not required to deploy these artifacts oneself.
Comments
Leave a Reply