Typesafe injection of dynamic OSGi services in hybrid Java EE applications

Update: Alexis, posted an excellent screencast/walkthrough of this demo here.

During this year's San Francisco JavaOne, Sahoo and I presented  a hybrid approach to enterprise Java application development [slides: here] where applications could use Java EE and OSGi capabilities in the same application. With hybrid applications, as we discussed in the talk and the hands-on-lab , developers can continue to build standard and familiar enterprise application components, such as Java Servlets and EJBs, and take full advantage of:
  • Features such as modularity/dependency management, declarative services, service dynamism, and more provided by OSGi
  • Infrastructure services such as transaction management, security, persistence, and more offered by Java EE.
One of the most interesting features in the OSGi environment is "Service Dynamism". The OSGi Service layer defines a dynamic, collaborative model that is highly integrated with the lifecycle layer of osgi. The OSGi service model is basically a publish-find-and-bind model. A Service is a normal Java Object that is registered under one or more Java interfaces with an OSGi Service Registry. Bundles can register services, search for them or receive notifications when their registration state changes. Key characteristics of the Service layer is that it encourage collaboration and dynamism of services in a secure manner.

When it came to consuming such dynamic services in a Java EE application component, a developer had to write standard boiler-plate, verbose, complex code to find, bind and track service references in the service registry(for example see here and here). The Java EE platform had already simplified injection of resources and depedencies through @Resource, CDI (JSR 299) etc. So, this boilerplate, error-prone code seemed out-of-place :) Other approaches such as OSGi Blueprint Service provide a verbose XML based, non-type-safe mechanisms for specifying dependencies and wiring between services and makes it tedious for a Java EE 6 developer who works with type-safe dependency injection technologies in the platform.

With GlassFish 3.1, application components can express their dependency on an OSGi Service, and have the container handle the discovery and binding of OSGi Services and inject them, by providing an additional Qualifier, @OSGiService, in the injection point. So instead of all the verbose service discovery and binding code,  the application developer states the requirement for an OSGi Service as follows:
    @Inject @OSGiService
    StockQuoteService sqs;

Note that the specification of the OSGi service type in the injection point is type-safe. The developer specifies that the injected service must implement the StockQuoteService interface using the field's type. Type-safety usually implies lesser runtime errors/easier debugging, refactoring etc.

Since the injection is specified through standard @Inject coupled with a custom OSGiService Qualifier, all standard CDI injection capabilities are available (constructor, field, setter method injection, programmatic lookup etc). The container automatically manages service references and ungets them when the component scope is completed.

A standard CDI portable extension (org.glassfish.osgi-cdi) comes pre-installed with GlassFish 3.1, that intercepts deployment of hybrid applications that has components who have expressed dependencies on OSGi services as shown above. The portable extension takes care of discovering the Service from the service registry using the criteria specified in the injection point, bind and track the service and inject the Service. Additional service discovery and injection related metadata could also be specified through annotation elements in the OSGiService Qualifier.For example, here are the current metadata attributes that could be specified:
  • Service Discovery criteria: the standard Filter syntax specified in the OSGi Core Specification can be used to narrow down choices for the Service type in the Service registry
  • Wait timeouts: Waits for the specified amount of time for at least one service that matches the criteria specified to be available in the OSGi Service registry.
  • Dynamic binding: used to handle service-dynamism. Since OSGi services are dynamic, they may not match the lifecycle of the application component that has injected a reference to the service. The developer could indicate that a service reference can be obtained dynamically or not through this attribute. For stateless or idempotent services, a dynamic reference to a service implementation would be useful. The container then injects a proxy to the service and dynamically switches to an available implementation when the current service reference is invalid.
[GlassFish 3.1 is still a work in progress and so, please consider this interface as unstable. Please provide inputs/feedback]

Let us see a simple sample that demonstrates this feature. The sample has a bundle that registers a StockQuoteService implementation on bundle-startup. Then we have another web application bundle(WAB) use the StockQuoteService, by having the container inject the service implementation using the @OSGiService qualifier. The servlet then finds all the symbols for which stock quotes are available, and print their current quotes.

To try out this sample
  • Extract the archive to a temporary location (say /tmp). This is a maven project that uses the JavaOne hands-on-lab template. It uses the maven bundle plugin to create the maven bundles and the maven war plugin to create the WAB. More details on the organization of the maven project structure is available in the docs archive here.
  • Extract the attached zip file and execute "mvn install" to create the two artifacts (a service implementation bundle and the WAB that uses the service) discussed below

  • A StockQuoteService API and implementation is in the stockquote_service project. The service interface is as follows:
org/acme/stockquoteservice/api/StockQuoteService.java
public interface StockQuoteService {
    public Double getQuote(String symbol);
    public Set<String> getSymbols();
}
and the service implementation is at org/acme/stockquoteservice/impl/SimpleStockQuoteServiceImpl.java and has a list of a fixed list of symbols and quotes. The service implementation is registered in the start() method in the BundleActivator.
public class SimpleServiceActivator implements BundleActivator {
    public void start(BundleContext context) throws Exception {
        context.registerService(StockQuoteService.class.getName(), new SimpleStockQuoteServiceImpl(), null);
    }
}
  • Install the stock quote service implementation bundle Let use the Apache Felix Gogo shell  to deploy the bundle. Apache Gogo comes with GlassFish 3.1. So telnet localhost 6666 to access it. If the demo archive was exploded is in /tmp, Install the bundle by executing install file:///tmp/stockquote_service/target/stockquote_service.jar
The shell provides a Bundle Id for the installed bundle as follows
Bundle ID: 275
Start the bundle by executing "start 275". Replace 275 in this command with the bundle id provided by the shell above. The Stock Quote service implementation is initialized during bundle start and registered in the OSGi Service Registry. An entry similar to the following must appear in the gogo shell.
Registered:[IBM, MSFT, HPQ, ORCL]

  • The stockquoteweb application bundle: references and uses the StockQuote service
public class StockQuoteServlet extends HttpServlet {
    @Inject
    @OSGiService(/\* wait for 1 min \*/ waitTimeout=60\*1000)
    StockQuoteService sqs;
    ...
}
Note that this WAR is a normal web application bundle [OSGi RFC 66 support in GlassFish], with an empty beans.xml descriptor to indicate that it is a CDI bean archive. The context root is specified as "stock_quote", using the Web-ContextPath manifest header
Web-ContextPath                         /stockquote                             
For simplicity, the service API and one implementation of that service was bundled in the stockquote_servic bundle.
  • Install the stock quote WAB bundle using the shell.  If the demo archive was exploded is in /tmp, Install the bundle by executing install file:///tmp/ stockquote_cdi_wab/target/stockquote_cdi_wab.war in the shell. Start the WAB bundle using the identifier provided by the shell.  For example, here is the sequence of steps:
g! install file:///tmp/stockquote_cdi_wab/target/stockquote_cdi_wab.war
Bundle ID: 276
g! start 276
  • Visit http://localhost:8080/stockquote/list to see the stock quotes provided by the stock quote service. The web application uses the StockQuote service implementation to get the quotes for a set of stock symbols.
  • Now, let us see how service dynamism is handled. Stop the service bundle by executing "stop 275" in the gogo shell. This stops the service bundle and the registered service implementaiton is removed from the service registry and is now unavailable for use. Hit the http://localhost:8080/stockquote/list URL now. Since we have a wait timeout of 30 seconds, the OSGi CDI extension waits for 30 seconds before it bails out and the web application prints "service unavailable". However within the 30 seconds, if you execute "start 275" to start the service bundle, the service bundle would register the service implementation again and the container would get the latest service implementation and provide it to the Servlet.
g! stop 275
SimpleServiceActivator stopped
g! start 275
SimpleServiceActivator::start
SimpleStockQuoteServiceImpl::Initializing quotes
SimpleStockQuoteServiceImpl::getSymbols
Registered:[IBM, MSFT, HPQ, ORCL]
SimpleServiceActivator::registration of Stock quote service successful

Through this sample, we have seen how easy it to consume OSGi services in a hybrid Java EE application in a dynamic, type-safe manner using the OSGi CDI extension in GlassFish 3.1.
Comments:

Hi Sivakumar,

I really appreciate your work at glassfish and this solution seems to be really nice.

However I have a question:
Does this also include OSGi Services exported using the Export-EJB Manifest Header?

Thanx,
Ancoron

Posted by Ancoron on November 25, 2010 at 10:56 PM IST #

Ancoron: Hi, Yes, EJBs exported as OSGi services through the glassfish Export-EJB header could also be injected.

Posted by Sivakumar Thyagarajan on November 26, 2010 at 04:24 AM IST #

We now have a separate maven module [1] just containing the API classes, so the sample should be updated to use that in the dependency section:
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>osgi-cdi-api</artifactId>
<version>3.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>

[1] http://download.java.net/maven/glassfish/org/glassfish/osgi-cdi-api/

Posted by Sahoo on November 30, 2010 at 03:35 AM IST #

Thanks Sahoo. Modified the archive at

http://blogs.sun.com/sivakumart/resource/stockquote-cdi-osgi-sample.zip

with the following change:

$ diff stockquote_cdi_wab/pom.xml.old stockquote_cdi_wab/pom.xml76c76
< <artifactId>osgi-cdi</artifactId>
---
> <artifactId>osgi-cdi-api</artifactId>

Posted by Sivakumar Thyagarajan on November 30, 2010 at 05:22 AM IST #

Hi,
Thanks for great tutorial. I have flowing maven project structure

1> testdomain
2> testAPI
3>testCore [ which implement testAPI]

testCore which is osgi bundle requires testdomain,testAPI jar. How can i specify dependency to osgi bundle ?

Posted by Suraj on November 30, 2010 at 09:54 AM IST #

Suraj: Dependency on the testdomain and testapi jar can be specified in the same manner, the wab module states a dependency on the service module in the archive at http://blogs.sun.com/sivakumart/resource/stockquote-cdi-osgi-sample.zip

Look at the pom.xml in the wab module. It states a dependency on the service module by having this:

<dependency>
<groupId>${groupId}</groupId>
<artifactId>stockquote_service</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>

HTH.

Posted by Sivakumar Thyagarajan on November 30, 2010 at 11:48 AM IST #

Thanks for your reply , you i have to put my api jar to glassfish/lib directory ?? I have tried but with out success getting same error message org.osgi.framework.BundleException: Unresolved constraint in bundle

Posted by Suraj on November 30, 2010 at 12:07 PM IST #

Suraj: If testAPI is not an OSGi bundle but a JAR dependency for the testCore bundle, then that jar has to be included with the testCore bundle.
If testAPI is an OSGi bundle, then it can be installed as an osgi bundle through asadmin deploy --type osgi <path-to-api-bundle> or through the gogo shell as discussed in the blog above.

Posted by Sivakumar Thyagarajan on November 30, 2010 at 12:16 PM IST #

testAPI is not an OSGI bundle but a jar.How can i include jar with osgi bundle ??

Posted by Suraj on November 30, 2010 at 12:24 PM IST #

Suraj:
Why not convert testAPI to a bundle. That's easy. You can use bnd tool [1] or maven bnd plugin [2] to do that.
If you don't then, you can bundle it in your testCore bundle as well. It could be as simple as making your dependency on testAPI a compile time dependency and updating the Bundle-ClassPath to include WEB-INF/lib/testAPI.jar in your testCore/pom.xml. To tell the exact answer, we need to know what kind of project testCore is. Why not ask your question in glassfish forum?

[1] http://www.aqute.biz/Code/Bnd
[2] http://felix.apache.org/site/apache-felix-maven-bundle-plugin-bnd.html

Posted by Sahoo on December 02, 2010 at 02:17 PM IST #

we would like to have 2 service implementations at the same time during runtime.
one is e.g. the test the other one is the real implementation.
filter criteria should not be changed for that.. we would simply say now use that service (e.g. the tester) without coding.

is there any possibility to interject the lookup of the services in a way to deside which one is actually choosen as the one to be used ?

Posted by chris s on December 05, 2010 at 05:50 PM IST #

Chris S: The CDI extension performs a lookup of the Service based on the filter criteria specified, and there is no way today to specify the filter criteria at runtime to either the actual or test service implementation.
- Would it be possible to unregister the actual service implementation when the test-Service is intended to be used?
- If that cannot be done, you can look at using Seam XML Configuration extension to modify Bean's configuration through XML at runtime. See http://docs.jboss.org/seam/3/xml-config/latest/reference/en-US/html_single/#xml.installing-beans for more information. I haven't personally tried this though in GlassFish.

Posted by Sivakumar Thyagarajan on December 06, 2010 at 04:37 AM IST #

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

sivakumart

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