TOTD #124: OSGi Declarative Services in GlassFish - Accessed from a Java EE client

The OSGi R4 compendium specification enables declaration of "Declarative Services" in configuration files. The specification says:

The service component model uses a declarative model for publishing, finding and binding to OSGi services. This model simplifies the task of authoring OSGi services by performing the work of registering the service and handling service dependencies.

There are several advantages of OSGi declarative services and they are well defined in the specification.

Neil Bartlett provided history and introduction to Declarative Services. Jerome blogged about OSGi Declarative Services in GlassFish v3 a while back. As mentioned in his post, this "curious reader" decided to experiment with adding more than one service implementation.

This Tip Of The Day shows how to use Maven Bundle Plugin and Maven SCR Plugin to create an OSGi bundle with two declarative services. Then it shows how to create a Java EE client, inject the declared services, and invoke them.

Lets get started!

For those who want want to see the results first:

  • Download service project and build as "mvn clean install". Install the service as "cp target/helloservice-1.0-SNAPSHOT.jar" to "domains/domain1/autodeploy/bundles".
  • Download client project and build as "mvn clean package"
  • Deploy the client to GlassFish v3 as "asadmin deploy target/helloclient-1.0-SNAPSHOT.war"
  • Invoke the client as "curl http://localhost:8080/helloclient-1.0-SNAPSHOT/HelloClient"

Now lets try to understand and create the projects from scratch.

First, create the service project as:

mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes 
     -DgroupId=org.glassfish.samples.osgi.helloservice -DartifactId=helloservice

Lets first look at the completed project structure:

pom.xml
src
src/main
src/main/java
src/main/java/org
src/main/java/org/glassfish
src/main/java/org/glassfish/samples
src/main/java/org/glassfish/samples/osgi
src/main/java/org/glassfish/samples/osgi/helloservice
src/main/java/org/glassfish/samples/osgi/helloservice/api
src/main/java/org/glassfish/samples/osgi/helloservice/api/HelloService.java
src/main/java/org/glassfish/samples/osgi/helloservice/impl
src/main/java/org/glassfish/samples/osgi/helloservice/impl/HelloImpl.java
src/main/java/org/glassfish/samples/osgi/helloservice/impl/HowdyImpl.java

The three source files are one service API and two implementations.

HelloService.java

package org.glassfish.samples.osgi.helloservice.api;

/\*\*
 \* @author arungupta
 \*/
public interface HelloService {
  public String sayHello(String name);
}

A very simple interface with one method that takes a String parameter and returns a String response.

HelloImpl.java

package org.glassfish.samples.osgi.helloservice.impl;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.glassfish.samples.osgi.helloservice.api.HelloService;

/\*\*
 \* @author arungupta
 \*/
@Component(name="hello-service")
@Service
public class HelloImpl implements HelloService {

  public String sayHello(String name) {
    return "Hello " + name;
  }
}

This class is an implementation of the service interface. Notice this class is in "impl" package, different from the "api" package where interface was defined.

The business method implementation appends the greeting "Hello " to name parameter and generates the response message. The @Component and @Service annotations help in generation of the component descriptors as defined by the Maven SCR Plugin. This plugin provides many other annotations to customize the generation of metadata in "OSGI-INF/servicesComponent.xml". The "name" attribute will be used later by the Java EE client to access this service.

HowdyImpl.java

package org.glassfish.samples.osgi.helloservice.impl;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.glassfish.samples.osgi.helloservice.api.HelloService;

/\*\*
 \* @author arungupta
 \*/
@Component(name="howdy-service")
@Service
public class HowdyImpl implements HelloService {

  public String sayHello(String name) {
    return "Howdy " + name;
  }
}

Another implementation of HelloService interface and uses "Howdy " for the greeting. Notice the name attribute has a different value.

Here is the complete pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.glassfish.samples.osgi.helloservice</groupId>
  <artifactId>helloservice</artifactId>
  <packaging>bundle</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>helloservice</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.felix</groupId>
      <artifactId>org.apache.felix.scr.annotations</artifactId>
      <version>1.2.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <version>2.0.1</version>
        <extensions>true</extensions>
        <configuration>
          <instructions>
            <Export-Package>${pom.groupId}.api</Export-Package>
            <Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
          </instructions>
        </configuration>
      </plugin>
    <plugin>
      <groupId>org.apache.felix</groupId>
      <artifactId>maven-scr-plugin</artifactId>
      <executions>
        <execution>
          <id>generate-scr-scrdescriptor</id>
          <goals>
            <goal>scr</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
 </build>
</project>

Maven SCR Plugin generates the descriptor file using the metadata specified by the @Component and @Service annotation in service implementations. If this plugin is used with Maven Bundle Plugin then it also adds the generated descriptor (OSGI-INF/serviceComponents.xml) to the bundle and set the required "Service-Component" manifest header.

Giving "mvn clean install" generates "target/helloservice-1.0-SNAPSHOT.jar" and installs the bundle in the local repository. This JAR is used later in the client project for importing the service API definition. The generated JAR has the following manifest:

Manifest-Version: 1.0
Export-Package: org.glassfish.samples.osgi.helloservice.api
Service-Component: OSGI-INF/serviceComponents.xml
Built-By: arungupta
Tool: Bnd-0.0.357
Bundle-Name: helloservice
Created-By: Apache Maven Bundle Plugin
Build-Jdk: 1.6.0_17
Bundle-Version: 1.0.0.SNAPSHOT
Bnd-LastModified: 1268374529666
Bundle-ManifestVersion: 2
Import-Package: org.glassfish.samples.osgi.helloservice.api
Bundle-SymbolicName: helloservice

The key thing to notice is that only "api" package is exported. The generated component descriptor in "OSGI-INF/serviceComponents.xml" looks like:

<?xml version="1.0" encoding="UTF-8"?>
<components xmlns:scr="http://www.osgi.org/xmlns/scr/v1.0.0">
  <scr:component enabled="true" name="hello-service">
    <implementation class="org.glassfish.samples.osgi.helloservice.impl.HelloImpl"/>
    <service servicefactory="false">
      <provide interface="org.glassfish.samples.osgi.helloservice.api.HelloService"/>
    </service>
    <property name="service.pid" value="hello-service"/>
  </scr:component>
  <scr:component enabled="true" name="howdy-service">
    <implementation class="org.glassfish.samples.osgi.helloservice.impl.HowdyImpl"/>
    <service servicefactory="false">
     <provide interface="org.glassfish.samples.osgi.helloservice.api.HelloService"/>
    </service>
    <property name="service.pid" value="howdy-service"/>
  </scr:component>
</components>

Notice that the "Service-Component" manifest header is pointing to this generated descriptor. And so the two declared services "hello-service" and "howdy-service" are available for consumption by other clients.

There are several ways to manage OSGi runtime bundle in GlassFish as described in TOTD #118. Simply copying the bundle to "glassfish/domains/domain1/autodeploy/bundles" is sufficient in this case, so lets do that.

The remote telnet shell (accessible using "telnet localhst 6666") shows status of the deployed bundle and associated service as:

-> find hello
START LEVEL 1
 ID State Level Name
[ 221] [Active ] [ 1] helloservice (1.0.0.SNAPSHOT)
-> scr list 221
 Id State Name
[ 2] [active ] hello-service
[ 3] [active ] howdy-service
-> scr info 2
ID: 2
Name: hello-service
Bundle: helloservice (221)
State: active
Default State: enabled
Activation: delayed
Services: org.glassfish.samples.osgi.helloservice.api.HelloService
Service Type: service
Properties:
 component.id = 2
 component.name = hello-service
 service.pid = hello-service
-> scr info 3
ID: 3
Name: howdy-service
Bundle: helloservice (221)
State: active
Default State: enabled
Activation: delayed
Services: org.glassfish.samples.osgi.helloservice.api.HelloService
Service Type: service
Properties:
 component.id = 3
 component.name = howdy-service
 service.pid = howdy-service

Maven SCR Plugin provides several other annotations to change the default value for each service.

Now create a Java EE client project to invoke the service as:

mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes 
       -DgroupId=org.glassfish.samples.osgi -DartifactId=helloclient

Lets look at the completed project structure:

pom.xml
src
src/main
src/main/java
src/main/java/org
src/main/java/org/glassfish
src/main/java/org/glassfish/samples
src/main/java/org/glassfish/samples/osgi
src/main/java/org/glassfish/samples/osgi/HelloClient.java

There is only one source file which is the Servlet client and looks like:

package org.glassfish.samples.osgi;

import java.io.IOException;
import java.io.PrintWriter;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.glassfish.samples.osgi.helloservice.api.HelloService;

/\*\*
 \* Hello world!
 \*/
@WebServlet(urlPatterns={"/HelloClient"})
public class HelloClient extends HttpServlet {

  @Resource(mappedName="hello-service")
  HelloService helloService;

  @Resource(mappedName="howdy-service")
  HelloService howdyService;

  @Override
  public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException {
    PrintWriter out = response.getWriter();
    out.println(helloService.sayHello("Duke"));
    out.println(howdyService.sayHello("Duke"));
  }
}

This is a Java EE 6-style Servlet, using @WebServlet annotation, and will be accessible at "/HelloClient". The two OSGi services are injected using @Resource annotation and using the name specified in the "OSGI-INF/serviceComponents.xml" descriptor earlier.

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.glassfish.samples.osgi</groupId>
  <artifactId>helloclient</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>helloclient</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.glassfish.samples.osgi.helloservice</groupId>
      <artifactId>helloservice</artifactId>
      <version>1.0-SNAPSHOT</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-api</artifactId>
      <version>6.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.1-beta-1</version>
        <configuration>
          <failOnMissingWebXml>false</failOnMissingWebXml>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Note that the previously generated service bundle is added as a dependency in the "provided" scope as its already deployed to GlassFish.

"mvn clean package" generates "target/helloclient-1.0-SNAPSHOT.war" Deploy this WAR file to GlassFish as:

asadmin deploy target/helloclient-1.0-SNAPSHOT.war

And finally invoke the client as:

curl http://localhost:8080/helloclient-1.0-SNAPSHOT/HelloClient

to see the output as:

Hello Duke
Howdy Duke

This is the expected output after invoking the two services.

So there you go, this blog demonstrated how to access a OSGi declarative service from a Java EE client - both deployed on GlassFish v3.

Are you using OSGi Declarative Services ? How ?

Technorati: totd osgi glassfish javaee declarative service maven bundle bnd scr component dependencyinjection

Comments:

[Trackback] This post was mentioned on Twitter by arungupta: New Blog: TOTD #124: OSGi Declarative Services in GlassFish - Accessed from a Java EE client: The OSGi R4 compendi... http://bit.ly/duITmk

Posted by uberVU - social comments on March 14, 2010 at 10:34 PM PDT #

This looks promising but you skip over the interesting details, i.e. how the injection of the service into the servlet actually works.

It looks like @Resource(mappingName="hello-service") is creating a filter on the component.name property of the service. Is it using the type of the field (i.e. HelloService) to form the objectClass part of the filter? Can I omit the "mappedName" attribute in order to receive any instance of the service? Can I add additional attributes to filter on other service properties besides component.name? What if I want to receive all of the services matching the filter rather than just one of them; i.e. can I inject a collection of services?

How does this approach handle the dynamism inherent in OSGi services? Will it re-inject if the available service is substituted? Can the servlet be deactivated when the services it needs are not available? Are there any kinds of events on injection/uninjection/reinjection of the service?

Many thanks!
Neil

Posted by Neil Bartlett on March 15, 2010 at 03:37 AM PDT #

Hi Arun,

Sorry for using your blog and your latest entry for a twitter reply. I don't want to share my email in a tweet and I couldn't find your email.
So why am I writing this?

On February 26th you asked for my email on twitter. Now you have it in my comment.

Best wishes Daniel

Posted by Daniel on March 16, 2010 at 04:08 AM PDT #

Hi, I was actually following this guide:
http://netbeans.org/kb/docs/javaee/maven-osgi-declarativeservices.html
which is identical to yours except tailored for NetBeans. I think I have a problem with injection and I don't know why. Any help you could offer would be invaluable to me and much appreciated, I've detailed it here:

http://forums.netbeans.org/viewtopic.php?t=40006

Thanks

Posted by dpwr on June 16, 2011 at 09:38 AM PDT #

dpwr,

Have you tried following the instructions given above exclusively (i.e. without using NetBeans) ?

What GlassFish version are you using ?

Posted by guest on June 20, 2011 at 10:34 AM PDT #

It should also be noted that this type of injection works for Servlets but not for JAX-RS Resources, even the newer CDI @OSGiService injection does not work in JAX-RS Resources.

Posted by guest on January 20, 2012 at 09:35 AM PST #

CDI and JAX-RS integration will be cleaned up in JAX-RS 2.0 and then this would work.

Posted by guest on January 24, 2012 at 09:06 AM PST #

Post a Comment:
Comments are closed for this entry.
About

profile image
Arun Gupta is a technology enthusiast, a passionate runner, author, and a community guy who works for Oracle Corp.


Java EE 7 Samples

Stay Connected

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today