Tag Archives: whiteboard

Simplified REST APIs from karaf using Jersey

I have written the Java class JerseyServlet which is intended as a base class for DS (Declarative Services) components providing Servlet services to the OSGi web whiteboard.

The JerseyServlet simplifies the approach outlined in Use Jersey to provide REST APIs from karaf applications.

The JerseyServlet extends the Jersey ServletContainer to add two things:

  1. A default value for the list of Java packages that are scanned for Jersey resources (classes implementing REST API endpoints)
  2. A map of OSGi services to service implementations that will be used to register DS service injections with the HK2 dependency injection container, so they can be injected into the jersey resources

The default value for resources is the subpackage “.resources” of the package the DS component recides in. I.e. if the DS component is defined like this:

package no.priv.bang.demos.jerseyinkaraf.webapi;
@Component(service=Servlet.class, property={"alias=/jerseyinkaraf/api"})
public class CounterServiceServlet extends JerseyServlet {
...
}

then no.priv.bang.demos.jerseyinkaraf.webapi.resources will be scanned for resources implementing REST API endpoints.

HK2 dependency injection of OSGi services are done in the following way:

  1. The DS component gets OSGi service injections for the services it needs to pass to the resources
    package no.priv.bang.demos.jerseyinkaraf.webapi;
    @Component(service=Servlet.class, property={"alias=/jerseyinkaraf/api"})
    public class CounterServiceServlet extends JerseyServlet {
    @Reference
    public void setCounter(Counter counter) {
    addInjectedOsgiService(Counter.class, counter);
    }
    @Override
    @Reference
    public void setLogService(LogService logservice) {
    super.setLogService(logservice);
    }
    }

    Note: The LogService gets special treatment, since it used by the servlet itself to log problems adding services to HK2, but it is added to HK2 itself as well
  2. The resources uses @Inject annotations to get the services injected when jersey creates the resources in response to REST API invocations
    package no.priv.bang.demos.jerseyinkaraf.webapi.resources;
    @Path("/counter")
    @Produces(MediaType.APPLICATION_JSON)
    public class CounterResource {
    @Inject
    Counter counter;
    @Inject
    LogService logservice;
    ...
    }
    view raw CounterResource.java hosted with ❤ by GitHub

To use JerseyServlet in a web whiteboard DS component residing in an OSGi bundle created by maven add the following dependencies:

<dependencies>
<dependency>
<groupId>no.priv.bang.servlet</groupId>
<artifactId>servlet.jersey</artifactId>
<version>1.1.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>no.priv.bang.servlet</groupId>
<artifactId>servlet.jersey</artifactId>
<version>1.1.2</version>
<type>xml</type>
<classifier>features</classifier>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>2.30</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>2.30</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.30</version>
<scope>provided</scope>
</dependency>
</dependencies>
view raw pom.xml hosted with ❤ by GitHub

The feature xml dependency is used by the karaf-maven-plugin to create a karaf feature file that can be used to pull in an OSGi bundle and all of its runtime dependencies into a karaf instance.

The <provided> dependencies are needed for compilation. Since provided dependencies aren’t transitive, all of the dependencies needed to resolve the classes are needed. E.g. the maven dependency containing the JerseyServlet’s parent class must be listed explicitly, even though that class isn’t used in the java source code. But without this dependency the code won’t compile.

The provided scope is used to make maven-bundle-plugin and karaf-maven-plugin do the right thing:

  1. The maven-bundle-plugin will create imports for the packages of the classes used from provided dependencies in the bundle’s manifest.mf file
  2. The karaf-maven-plugin will not add provided bundles as bundles to be loaded in the features i generates and attaches to maven bundle projects, and instead add feature dependencies to features that load the bundles (

To use maven-bundle-plugin and karaf-maven-plugin, first add the following config to the <dependencyManagement> section of your top pom:

<project>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>4.2.0</version>
<executions>
<execution>
<id>bundle</id>
<goals>
<goal>bundle</goal>
</goals>
<configuration>
<supportedProjectTypes>
<supportedProjectType>jar</supportedProjectType>
<supportedProjectType>bundle</supportedProjectType>
<supportedProjectType>war</supportedProjectType>
</supportedProjectTypes>
<instructions>
<Bundle-SymbolicName>no.priv.bang.demos.jerseyinkaraf.jerseyinkaraf</Bundle-SymbolicName>
<Import-Package>*</Import-Package>
<Export-Package>!*</Export-Package>
<_removeheaders>Private-Package,
Include-Resource,
Embed-Dependency,
Embed-Transitive</_removeheaders>
<_dsannotations>*</_dsannotations>
<_metatypeannotations>*</_metatypeannotations>
</instructions>
</configuration>
</execution>
</executions>
<configuration>
<supportedProjectTypes>
<supportedProjectType>jar</supportedProjectType>
<supportedProjectType>bundle</supportedProjectType>
<supportedProjectType>war</supportedProjectType>
</supportedProjectTypes>
<instructions>
<Bundle-SymbolicName>no.priv.bang.demos.jerseyinkaraf.jerseyinkaraf</Bundle-SymbolicName>
<Import-Package>*</Import-Package>
<Export-Package>!*</Export-Package>
<_removeheaders>Private-Package,
Include-Resource,
Embed-Dependency,
Embed-Transitive</_removeheaders>
<_dsannotations>*</_dsannotations>
<_metatypeannotations>*</_metatypeannotations>
</instructions>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.karaf.tooling</groupId>
<artifactId>karaf-maven-plugin</artifactId>
<version>4.2.5</version>
<extensions>true</extensions>
<executions>
<execution>
<id>generate-features-file</id>
<phase>package</phase>
<goals>
<goal>features-generate-descriptor</goal>
</goals>
<configuration>
<startLevel>80</startLevel>
<aggregateFeatures>false</aggregateFeatures>
<includeProjectArtifact>true</includeProjectArtifact>
<primaryFeatureName>jerseyinkaraf</primaryFeatureName>
</configuration>
</execution>
</executions>
<configuration>
<startLevel>80</startLevel>
<aggregateFeatures>false</aggregateFeatures>
<includeProjectArtifact>true</includeProjectArtifact>
<primaryFeatureName>jerseyinkaraf</primaryFeatureName>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
view raw pom.xml hosted with ❤ by GitHub

Then reference maven-bundle-plugin and karaf-maven-plugin in all module pom files with packaging jar, to get an OSGi compliant manifest.mf and an attached karaf feature repository:

<project>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.karaf.tooling</groupId>
<artifactId>karaf-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
view raw pom.xml hosted with ❤ by GitHub

Use Jersey to provide REST APIs from karaf applications

Edit: creating a REST API using jersey has been made simpler, see Simplified REST APIs from karaf using Jersey for details.

The sample application https://github.com/steinarb/jersey-demo demonstrates how to use Jersey to provide a REST API from a Declarative Services (DS) component registering with the Pax Web Whiteboard Extender in apache karaf.

The way Jersey works is that it initially scans a package in the classpath for classes with JAX-RS annotations (e.g. @Path, @GET, @POST), and matches @Path annotations to possible endpoints in the REST API. When Jersey receives a REST call to a endpoint that matches a resource, Jersey will instanciate the appropriate resource class, call a method on the instanciated resource and then release the resource to Java garbage collection.

To be able to do something useful in these resource objects we use HK2 to dependency inject OSGi services.

To be able to dependency inject OSGi services into Jersey resources the following is done:

  1. Create a whiteboard DS component exposing a Servlet service from Jersey ServletContainer, configuring the package to scan for API resources:
    @Component(
        service=Servlet.class,
        property={"alias=/jerseyinkaraf/api",
                  HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX+ServerProperties.PROVIDER_PACKAGES+"=no.priv.bang.demos.jerseyinkaraf.webapi.resources"
        } )
    public class CounterServiceServlet extends ServletContainer {
        ...
    }
  2. Make the DS component of ServletContainer depend on OSGi services Counter and LogService
    public class CounterServiceServlet extends ServletContainer {
        ...
        @Reference
        public void setCounter(Counter counter) {
            this.counter = counter;
        }
    
        @Reference
        public void setLogservice(LogService logservice) {
            this.logservice.setLogService(logservice);
        }
        ...
    }
  3. Override the ServletContainer.init(WebConfig) method, and:
    1. Call super.init(WebConfig) to make sure that a ServletConfig containing the information set up by the http whiteboard is created (contains the servletcontext, the servlet name and the package to scan for Jersey resources)
              super.init(webConfig);
    2. Copy the ResourceConfig of the ServletContainer (because that ServletConfig is immutable after the setup, and calling ServletConfig.register() will cause an IllegalOperationException)
              ResourceConfig copyOfExistingConfig = new ResourceConfig(getConfiguration());
    3. On the ServletConfig copy, register an anonymous inner inheriting AbstractBinder that in its configure() method registers the OSGi services injected into the ServletContainer as JSR330 injections in the Jersey resources
              copyOfExistingConfig.register(new AbstractBinder() {
                      @Override
                      protected void configure() {
                          bind(logservice).to(LogService.class);
                          bind(counter).to(Counter.class);
                      }
                  });
    4. Call ServletContainer.reload(ResourceConfig) with the ResourceConfig copy as the argument
              reload(copyOfExistingConfig);

      Note: The copyOfExistingConfig object at this point contains both the initial configuration created by the ServletContainer itself, and the two added OSGi services for dependency injection.

  4. In the resources use JSR330 injections of the registered services
    @Path("/counter")
    @Produces(MediaType.APPLICATION_JSON)
    public class CounterResource {
    
        @Inject
        Counter counter;
    
        @GET
        public Count currentValue() {
            return counter.currentValue();
        }
    
        @POST
        public Count increment() {
            return counter.increment();
        }
    
    }

To try out the application:

  1. Clone this project and build it:
    git clone https://github.com/steinarb/jersey-demo.git
    cd jersey-demo
    mvn clean install
    
  2. Install apache karaf and start it  according to the karaf quick start guide (alternatively, see Develop OSGi applications using karaf)
  3. At the karaf command line give the following commands
    feature:repo-add mvn:no.priv.bang.demos.jerseyinkaraf/jerseyinkaraf/LATEST/xml/features
    feature:install jerseyinkaraf.webapi
    feature:install jerseyinkaraf.webgui
  4. Open http://localhost:8181/jerseyinkaraf in a web browser and click on the button to increment the counter value

The demo consists of the following maven project and modules:

  1. jerseyinkaraf The top project which in addition to containing common configuration, and the list of modules, also creates a karaf feature repository containing the features of all bundles created in the modules, and attaches the karaf feature repository to the maven artifact
  2. jerseyinkaraf.servicedef which is an OSGi bundle containing the interfaces and bean defining the OSGi service for doing the counting
  3. jerseinkaraf.services which is an OSGi bundle containing a DS component implementing the services
  4. jerseyinkaraf.webapi which is an OSGi bundle containing a DS component that defines a REST API that plugs into the Web Whiteboard Extender and exposes the OSGi services
  5. jerseyinkaraf.webgui which is an OSGi bundle containing a DS component that exposes an HTML and JavaScript application that plugs into the Web Whiteboard Extender