OSGi at REST

Introduction

I’ve been working on a project to make building RESTful OSGi web applications as simple as creating a basic OSGi service, and I think I have a solution that comes pretty close to this goal. The project is currently called restlet-integration-with-equinox as hosted on EclipseLabs. I am in the process of moving the project to GitHub, and it will be renamed to Restlet Extension for OSGi Environments.  This framework integrates the Restlet RESTful web framework with OSGi allowing you to dynamically register resources, filters, routers, and applications as part of a RESTful web application.

The project originally started out supporting the registration of applications and resources as OSGi services or with an Equinox extension point.  This all worked fine until I started adding the ability to register routers and filters.  The code became complicated and ugly, so I branched the repository and started over.  As the second iteration progressed, it too became complicated, and it occurred to me that the core of the problem (delayed dynamic binding) was identical to the problem that OSGi declarative services has to solve.  That’s when I had that AH-HA moment where the problem becomes clear, and you get to delete all of that ugly, complicated, code.  The solution is to use OSGi declarative services and its dynamic binding capabilities to stitch together the resources, filters, routers, and applications.  And, since you are using OSGi services, you can add and remove resources at runtime and it all just works.

Framework

The Restlet Extension for OSGi Enironments framework is organized as a set of service components and interfaces for you to register as declarative services to form your RESTful web application.  Using these service components, it’s possible to implement a simple application with a router and some resources, or a very complex graph of filters, routers, and resources as demonstrated below.

Application

Here is the API specification for the service interfaces:

Service Interfaces

Here is the API specification for the service components (I have omitted the service interfaces that each provider implements):

Service Components

FilterProvider and ResourceProvider are abstract and must be extended as described below.  RouterProvider and ApplicationProvider can be used without extending them.  The remainder of this post will walk you through examples of creating RESTful web applications using this technology.

Setup

Before you begin development of your application, the target platform needs to be crated with the following frameworks:

If you would like to minimize your target platform, the following bundles are the minimum required:

  • javax.servlet
  • org.eclilpse.equinox.common
  • org.eclipse.equinox.ds
  • org.eclispe.equinox.http.jetty
  • org.eclipse.equinox.http.servlet
  • org.eclipse.equinox.util
  • org.eclipse.osgi
  • org.eclipse.osgi.services
  • org.mortbay.jetty.server
  • org.mortbay.jetty.util
  • org.restlet
  • org.restlet.ext.servlet
  • org.eclipselabs.restlet
  • org.eclipselabs.restlet.servlet

If you are not familiar with setting up your target platform, here are the detailed steps:

  1. Download Restlet JEE 2.0.8 and Restlet Extensions for OSGi Environments 0.3.0.  You do not need to download Equinox – we’ll get that from a p2 repository.
  2. Uncompress the downloads into a targets directory.
  3. Launch Eclipse (I’m using Indigo) and select Preferences -> Plug-in Development -> Target Platform.
  4. Click Add…
  5. Choose Nothing: Start with an empty target definition.
  6. Click Next
  7. Name the target platform.
  8. Click Add…
  9. Choose Directory and click Next.
  10. Add <target directory>/restlet-jee-2.0.8/lib
  11. Click Finish.
  12. Click Add…
  13. Choose Directory and click Next.
  14. Add <target directory>/restlet-ext-osgi
  15. Click Finish.
  16. Click Add…
  17. Choose Software Site.
  18. Work with: http://download.eclipse.org/releases/indigo.
  19. Expand EclipseRT Target Platform Components.
  20. Check Equinox Target Components.
  21. Click Finish.
  22. Click Finish.
  23. Check the target definition you just created to make it Active.
  24. Click OK.

Edit Target Definition 1

Let’s test the target definition.  Create an OSGi launch configuration with the following bundles:

  • javax.servlet
  • org.eclipse.equinox.http.jetty
  • org.eclipse.equinox.http.servlet
  • org.eclipse.osgi
  • org.eclipse.osgi.services
  • org.mortbay.jetty.server
  • org.mortbay.jetty.util

Set Default Auto-Start: true (not ideal, but it gets you up and running quickly) and add the following VM argument: -Dorg.osgi.service.http.port=8080.

Run Configurations 3

Run the application and point your web browser to http://localhost:8080/.  You should get a 404 error.  This lets you know that jetty is responding to your request, but there are currently no resources to handle the request.  For more information on setting up OSGi as a web server, see my blog post: OSGi as a Web Application Server.

Hello RESTful World

For this tutorial we are going to create a single resource at the URI http://localhost:8080/hello with a plain text representation.  We need to create and wire together three objects: Application, Router, and ServerResource.  Start by creating a new Plug-in Project called org.eclipselabs.restlet.examples.app.  You do not need a bundle activator.

Open the MANIFEST.MF and add the following imported packages:

  • org.eclipselabs.restlet
  • org.restlet
  • org.restlet.resource

Create a new package: org.eclipselabs.restlet.examples.app and add the following HelloResource:

package org.eclipselabs.restlet.examples.app;

import org.restlet.resource.Get;
import org.restlet.resource.ServerResource;

public class HelloResource extends ServerResource
{
  @Get("txt")
  public String sayHello()
  {
    return "Hello RESTful World";
  }
}

In that same package, add the following HelloResourceProvider:

import org.eclipselabs.restlet.ResourceProvider;
import org.restlet.Context;
import org.restlet.resource.Finder;

public class HelloResourceProvider extends ResourceProvider
{
  @Override
  protected Finder createFinder(Context context)
  {
    return new Finder(context, HelloResource.class);
  }
}

At the root of the project create an OSGI-INF folder to hold our service declarations.  In the OSGi-INF folder, create the following declarative service as application.xml:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.eclipselabs.restlet.examples.app">
   <implementation class="org.eclipselabs.restlet.ApplicationProvider"/>
   <service>
      <provide interface="org.eclipselabs.restlet.IApplicationProvider"/>
   </service>
   <property name="alias" type="String" value="/"/>
   <reference bind="bindRouterProvider" cardinality="1..1" interface="org.eclipselabs.restlet.IRouterProvider" name="IRouterProvider" policy="static" unbind="unbindRouterProvider"/>
</scr:component>

Create the following declarative service as router.xml:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.eclipselabs.restlet.examples.app.router">
   <implementation class="org.eclipselabs.restlet.RouterProvider"/>
   <service>
      <provide interface="org.eclipselabs.restlet.IRouterProvider"/>
   </service>
   <reference bind="bindResourceProvider" cardinality="1..n" interface="org.eclipselabs.restlet.IResourceProvider" name="IResourceProvider" policy="dynamic" unbind="unbindResourceProvider"/>
</scr:component>

Create the following declarative service as hello.xml:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.eclipselabs.restlet.examples.app.hello">
   <implementation class="org.eclipselabs.restlet.examples.app.HelloResourceProvider"/>
   <service>
      <provide interface="org.eclipselabs.restlet.IResourceProvider"/>
   </service>
   <property name="paths" type="String">
     /hello
   </property>
</scr:component>

If you are not using the component definition editor to create your declarative services, be sure to add the following to the MANIFEST.MF:

Service-Component: OSGI-INF/application.xml, OSGI-INF/router.xml, OSGI-INF/hello.xml

You are now ready to run and test your web application.  Add the following bundles to the launch configuration you created in the Setup step:

  • org.eclipselabs.restlet.examples.app
  • org.eclipse.equinox.common
  • org.eclipse.equinox.ds
  • org.eclipse.equinox.util
  • org.eclipselabs.restlet
  • org.eclipselabs.restlet.servlet
  • org.restlet
  • org.restlet.ext.servlet

Run the application and point your browser to http://localhost:8080/hello.  You should see “Hello RESTful World”.

Run Configurations 2

One of the neat features of Restlet is that you can specify more than one representation in a resource.  Let’s add an HTML representation to our hello resource:

package org.eclipselabs.restlet.examples.app;

import org.restlet.resource.Get;
import org.restlet.resource.ServerResource;

public class HelloResource extends ServerResource
{
  @Get("txt")
  public String sayHello()
  {
    return "Hello RESTful World";
  }

  @Get("html")
  public String getDocument()
  {
    StringBuilder html = new StringBuilder();
    html.append("<html>\n");
    html.append("  <body>\n");
    html.append("    <h2>Hello RESTful World</h2>\n");
    html.append("   </body>\n");
    html.append("</html>\n");
    return html.toString();
  }
}

Now, when you reload http://localhost:8080/hello in your browser, you should see “Hello RESTful World” in a Heading 2 style.  The complete solution for this example application can be downloaded from the Restlet Extensions for OSGi Environments downloads page.

Applications

Your OSGi web application will consist of one or more Restlet Application instances.  An application is created by declaring an OSGi service component of type ApplicationProvider which provides the service IApplicationProvider, and has an “alias” property that specifies the root path of the application.  The alias of each Restlet application in your OSGi application must be unique, start with “/” and not end with “/” with the exception of “/” itself.  It should not be necessary to extend ApplicationProvider, but you must declare the service in your own bundle.  The ApplicationProvider has a dependency on the IRouterProvider service.  This dependency should be declared as static with a cardinality of 1..1 and bind functions (un)bindRouterProvider().

Suppose you wanted to create more than one Application.  Each Application needs its own instance of a Router.  How does OSGi declarative services know which IRouterProvider to bind to which ApplicationProvider?  When you declare the dependency from ApplicationProvider to IRouterProvider, there is a target parameter you can set to specify the exact IRouterProvider to bind against.  Here is an example of declaring two applications each binding to a unique router using the component.name property given to the service instance.

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.eclipselabs.restlet.examples.app1">
   <implementation class="org.eclipselabs.restlet.ApplicationProvider"/>
   <service>
      <provide interface="org.eclipselabs.restlet.IApplicationProvider"/>
   </service>
   <property name="alias" type="String" value="/app1"/>
   <reference bind="bindRouterProvider" cardinality="1..1" interface="org.eclipselabs.restlet.IRouterProvider" name="IRouterProvider" policy="static" target="(component.name=org.eclipselabs.restlet.examples.app.router1)" unbind="unbindRouterProvider"/>
</scr:component>
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.eclipselabs.restlet.examples.app2">
   <implementation class="org.eclipselabs.restlet.ApplicationProvider"/>
   <service>
      <provide interface="org.eclipselabs.restlet.IApplicationProvider"/>
   </service>
   <property name="alias" type="String" value="/app2"/>
   <reference bind="bindRouterProvider" cardinality="1..1" interface="org.eclipselabs.restlet.IRouterProvider" name="IRouterProvider" policy="static" target="(component.name=org.eclipselabs.restlet.examples.app.router2)" unbind="unbindRouterProvider"/>
</scr:component>

Routers

Every Restlet Application instance needs at least one Router instance.  Technically speaking, this isn’t entirely true, but for all practical purposes, you are going to want a Router.  A router is created by declaring an OSGi service component of type RouterProvider which provides the service IRouterProvider.  The RouterProvider has a dependency on IResourceProvider and IRouterProvider services.  The dependency on IResourceProvider should be declared as dynamic with a cardinality of 1..n and bind functions (un)bindResourceProvider().  This allows more than one resource to bind to a router and allows the resources to come and go without taking down the application.  The dependency on IRouterProvider is optional and if declared should be dynamic with a cardinality of 0..1 and bind functions (un)bindDefaultRouterProvider().  The default router is asked to handle the URI routing when none of the resources attached to the router can handle the URI.  The default router will make more sense when I discuss filters below.

When an application contains multiple routers, a target filter must be applied to the IResourceProvider services so that the resources are bound to the correct router.  Since there can be many resources attached to a router, and each resource has a unique component.name, it’s not practical to use the component.name as a filter.  I recommend giving each resource a property with a common value that is unique to a router.  For example, if a resource has the property: type = public, then the router provider can bind to resources with the target (type = public).  For the case of multiple applications, you may want to create a property that identifies the application such as: app = app1.  The binding target is an LDAP filter, so you can combine both the app and type properties: (&(app = app1)(type = public)).  Here is an example of declaring two routers for two different applications:

<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.eclipselabs.restlet.examples.app.router1">
   <implementation class="org.eclipselabs.restlet.RouterProvider"/>
   <service>
      <provide interface="org.eclipselabs.restlet.IRouterProvider"/>
   </service>
   <reference bind="bindResourceProvider" cardinality="1..n" interface="org.eclipselabs.restlet.IResourceProvider" name="IResourceProvider" policy="dynamic" target="(app=app1)" unbind="unbindResourceProvider"/>
</scr:component>
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.eclipselabs.restlet.examples.app.router2">
   <implementation class="org.eclipselabs.restlet.RouterProvider"/>
   <service>
      <provide interface="org.eclipselabs.restlet.IRouterProvider"/>
   </service>
   <reference bind="bindResourceProvider" cardinality="1..n" interface="org.eclipselabs.restlet.IResourceProvider" name="IResourceProvider" policy="dynamic" target="(app=app2)" unbind="unbindResourceProvider"/>
</scr:component>

Resources

Resources are where you define exactly how an external client interacts with your application by responding to HTTP GET, PUT, POST, and DELETE requests.  You must create two classes for every logical resource your application provides: the resource class itself extending ServerResource from the Restlet framework, and a resource provider extending ResourceProvider from the Restlet Extension for OSGi Environments framework.  A resource is created by declaring an OSGi service component of the type you extended from ResourceProvider which provides the service IResourceProvider.  The ResourceProvider does not require dependencies on any other services, but does require a property named paths which is a string array of paths relative to the application alias.  The resource is registered with the router for each path declared in the paths property.  Note that the PDE component definition editor does not support array properties, so you will have to switch to the Source tab to create the paths property.  It is necessary to extend ResourceProvider to provide the classloading “glue” between Restlet and OSGi.  Your resource provider should override Finder createFinder(Context context) and return an instance of a new Finder passing in the Context and the class of your resource.

Here is an example of two resources that respond to http://localhost:8080/hello and http://localhost:8080/example:

package org.eclipselabs.restlet.examples.app;

import org.restlet.resource.Get;
import org.restlet.resource.ServerResource;

public class HelloResource extends ServerResource
{
  @Get("txt")
  public String sayHello()
  {
    return "Hello RESTful World";
  }
}
import org.eclipselabs.restlet.ResourceProvider;
import org.restlet.Context;
import org.restlet.resource.Finder;

public class HelloResourceProvider extends ResourceProvider
{
  @Override
  protected Finder createFinder(Context context)
  {
    return new Finder(context, HelloResource.class);
  }
}
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.eclipselabs.restlet.examples.app.hello">
   <implementation class="org.eclipselabs.restlet.examples.app.HelloResourceProvider"/>
   <service>
      <provide interface="org.eclipselabs.restlet.IResourceProvider"/>
   </service>
   <property name="paths" type="String">
     /hello
   </property>
</scr:component>
package org.eclipselabs.restlet.examples.resource;

import org.restlet.resource.Get;
import org.restlet.resource.ServerResource;

public class ExampleResource extends ServerResource
{
  @Get
  @Override
  public String toString()
  {
    return "Example";
  }
}
package org.eclipselabs.restlet.examples.resource;

import org.eclipselabs.restlet.ResourceProvider;
import org.restlet.Context;
import org.restlet.resource.Finder;

public class ExampleResourceProvider extends ResourceProvider
{
  @Override
  protected Finder createFinder(Context context)
  {
    return new Finder(context, ExampleResource.class);
  }
}
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.eclipselabs.restlet.examples.resource">
   <implementation class="org.eclipselabs.restlet.examples.resource.ExampleResourceProvider"/>
   <service>
      <provide interface="org.eclipselabs.restlet.IResourceProvider"/>
   </service>
   <property name="paths" type="String">
   	/example
   </property>
</scr:component>

Filters

Filters, just as the name implies, provide a mechanism for filtering a request as it is routed to the resource.  The typical use-case for a filter is to protect resources from unauthorized access.  Filters are created similar to resources by extending both Filter and FilterProvider.  A filter is created by declaring an OSGi service component of the type you extended from FilterProvider which provides the service IFilterProvider.   The FilterProvider does not require dependencies on any other services.  Your filter provider should override Filter createFilter(Context context) and return an instance of a new Filter passing in the Context.

Filters can be wired to routers, resources, and other filters.  The wiring is done by placing a service dependency on IFilterProvider in the component being filtered.  Notice that RouterProvider, ResourceProvider and FilterProvider all extend from RestletProvider which has a bindFilterProvider() function.  When declaring filters, you will probably want to use the component.name property as a target.  Here is an example of an authentication and authorization filter:

package org.eclipselabs.restlet.examples.security;

import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.Cookie;
import org.restlet.security.Authenticator;
import org.restlet.security.User;

public class SessionAuthenticator extends Authenticator
{
  public SessionAuthenticator(Context context)
  {
    super(context, false, new SessionEnroller());
  }

  @Override
  public boolean authenticate(Request request, Response response)
  {
    Cookie sessionCookie = request.getCookies().getFirst("session");
    Account account = AccountManager.getInstance().getAccount(sessionCookie.getSecond());

    if(account != null)
    {
      User user = new User(account.getCredential().getId(), account.getID());
      request.getClientInfo().setUser(user);
      return true;
    }

    return false;
  }
}
package org.eclipselabs.restlet.examples.security.providers;

import org.eclipselabs.restlet.components.FilterProvider;
import org.restlet.Context;
import org.restlet.routing.Filter;

import org.eclipselabs.restlet.examples.security.SessionAuthenticator;

public class SessionAuthenticationFilterProvider extends FilterProvider
{
  @Override
  protected Filter createFilter(Context context)
  {
    return new SessionAuthenticator(context);
  }
}
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="org.eclipselabs.restlet.examples.authentication">
   <implementation class="org.eclipselabs.restlet.examples.security.SessionAuthenticationFilterProvider"/>
   <service>
      <provide interface="org.eclipselabs.restlet.providers.IFilterProvider"/>
   </service>
</scr:component>
package org.eclipselabs.restlet.examples.security;

import org.restlet.Request;
import org.restlet.Response;
import org.restlet.security.Authorizer;
import org.restlet.security.Role;

public class SessionAuthorizer extends Authorizer
{
  @Override
  protected boolean authorize(Request request, Response response)
  {
    String accountID = request.getOriginalRef().getSegments().get(2);
    return accountID != null && accountID.equals(new String(request.getClientInfo().getUser().getSecret()));
  }
}
package org.eclipselabs.restlet.examples.security.providers;

import org.eclipselabs.restlet.components.FilterProvider;
import org.restlet.Context;
import org.restlet.routing.Filter;

import org.eclipselabs.restlet.examples.security.SessionAuthorizer;

public class SessionAuthorizerFilterProvider extends FilterProvider
{
  @Override
  protected Filter createFilter(Context context)
  {
    return new SessionAuthorizer();
  }
}
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.eclipselabs.restlet.examples.authorization">
   <implementation class="org.eclipselabs.restlet.examples.security.providers.SessionAuthorizerFilterProvider"/>
   <reference bind="bindFilterProvider" cardinality="1..1" interface="org.eclipselabs.restlet.providers.IFilterProvider" name="IFilterProvider" policy="static" target="(component.name=org.eclipselabs.restlet.examples.authentication)" unbind="unbindFilterProvider"/>
   <service>
      <provide interface="org.eclipselabs.restlet.providers.IFilterProvider"/>
   </service>
</scr:component>

Service Dependency Injection

The Restlet Extensions for OSGi Environments framework provides support for dependency injection into your resources.  If your resource extends InjectedServerResource and your resource provider creates an instance of InjectedFinder, you may then use @Inject in your resource to obtain references to other services such as the OSGi LogService.  Here is an example of creating a resource that uses dependency injection with the OSGi LogService.

package org.eclipselabs.restlet.examples.resources;

import javax.inject.Inject;

import org.eclipselabs.restlet.di.eclipse.InjectedServerResource;
import org.osgi.service.log.LogService;

public class MyServerResource extends InjectedServerResource
{
  protected void log(int level, String message)
  {
    log(level, message, null);
  }

  protected void log(int level, String message, Throwable exception)
  {
    LogService logService = this.logService;

    if (logService != null)
      logService.log(level, message, exception);
  }

  @Inject
  private LogService logService;
}

References

  1. Restlet – RESTful web framework
  2. Restlet Extension for OSGi Environments – Restlet integration with OSGi
  3. Restlet in Action – Book on Restlet
  4. OSGi as a Web Application Server – Blog post on setting up OSGi as a web application server

Update Sept 28, 2011: Changed the project name to Restlet Extension for OSGi Environments and updated link to GitHub.