Monday, September 28, 2009

Spring DM OSGi demo app

As you will see Spring DM makes life much simpler! It manages all “OSGi aware” pieces of the application for you and you can rid of the Activators.
The bundles we need now are similar as before. I will prefix “spring” to the artifactId just so its easier.
The shared service: We don’t need to change anything in shared so just use the older one as-is.
Creating a service implementation: The ComplexService we defined now needs to be implemented and made available as an OSGi service. Lets create a simple ComplexServiceImpl:
public class ComplexServiceImpl implements ComplexService {
 public String doSomething() {
  return "Hello from Version 1!";
 }
}
To register the service we now use the OSGi extender provided by Spring DM. All we need to do is create some XML configs.
<?xml version="1.0" encoding="UTF-8"?>
<beans ..... >
 
  <!-- Create bean -->
  <bean id="complexService" class="com.irahul.spring.complexapp.ComplexServiceImpl"/>
 
  <!-- register service -->
  <osgi:service ref="complexService" interface="com.irahul.shared.ComplexService" />
 
</beans>
Creating the servlet: In Spring world we just create the handler and then wire it up using XML.
public class MyHandler implements Servlet {
 private HttpService httpService;
 private List<ComplexService> complexServices;
 
 public void setHttpService(HttpService httpService){
  this.httpService = httpService;  
 }
 
 public void setComplexServices(List<ComplexService> complexServices) {
  this.complexServices = complexServices;
 }
 
 //ideally this should be externalized. Have a manager listen for servlets but keeping it simple
 public void init() throws Exception{
  httpService.registerServlet("/", this, null, null); 
 }
 
 public void service(ServletRequest request, ServletResponse response)
  throws ServletException, IOException {
 
  //we may have discovered more than 1 service
  if(complexServices==null|| complexServices.size()==0){
   response.getOutputStream().print("No Service found!");
  }
  else{
   //there are 1...n services, pick one
   //using a random pick
   response.getOutputStream().print(complexServices.get(
     new Random().nextInt(complexServices.size())).doSomething());
  }
 }
 ....
}
Configuring it:
<?xml version="1.0" encoding="UTF-8"?>
<beans ..... >
 
  <!-- Reference list of services in OSGi -->
  <osgi:list id="complexServiceList" interface="com.irahul.shared.ComplexService" cardinality="0..N"/>
 
  <!-- Reference to Http Service -->
  <osgi:reference id="httpService" interface="org.osgi.service.http.HttpService"/>
 
  <!-- Create handler -->
  <bean id="myHandler" class="com.irahul.spring.http.handler.MyHandler" init-method="init">
   <property name="complexServices" ref="complexServiceList"/>
   <property name="httpService" ref="httpService"/>
  </bean>
 
</beans>

Thursday, September 24, 2009

Running demo in Eclipse Equinox

Running in Eclipse Equinox: Look up basic instructions on equinox quickstart.
Add the following to your config.ini file (note two versions of the same complexapp):

osgi.bundles=javax.servlet_2.4.0.jar@start, services-3.1.2.jar@start, org.eclipse.equinox.http.jar@start, shared-1.0-SNAPSHOT.jar@start, http.handler-1.0-SNAPSHOT.jar@start, complexapp-1.0-SNAPSHOT.jar@start, complexapp-2.0-SNAPSHOT.jar@start
Testing it out: Start Equinox with -console so you have the osgi prompt. Here is a log of commands and interesting portions are highlighted (running on Windows).
rahul@lappy /cygdrive/c/osgidemo/runtime
$ java -jar org.eclipse.osgi_3.5.0.jar -console <-- Start with console
osgi> http.handler started! <-- osgi prompt
New Service added
New Service added
osgi> ss
Framework is launched.
id State Bundle
0 ACTIVE org.eclipse.osgi_3.5.0.v20090520
1 ACTIVE javax.servlet_2.4.0.v200806031604
2 ACTIVE org.eclipse.osgi.services_3.1.200.v20070605
3 ACTIVE org.eclipse.equinox.http_1.0.300.v20090520-1800
4 ACTIVE com.irahul.shared_1.0.0.SNAPSHOT
5 ACTIVE com.irahul.http.handler_1.0.0.SNAPSHOT
6 ACTIVE com.irahul.complexapp_1.0.0.SNAPSHOT
7 ACTIVE com.irahul.complexapp_2.0.0.SNAPSHOT
osgi> bundle 5
com.irahul.http.handler_1.0.0.SNAPSHOT [5]
Id=5, Status=ACTIVE Data Root=C:\osgidemo\runtime\configuration\org.eclip
se.osgi\bundles\5\data
No registered services.
Services in use: <-- Services used, noticed multiple ComplexService
{com.irahul.shared.ComplexService}={service.id=30}
{org.osgi.service.http.HttpService}={http.port=80, service.pid=org.eclipse.e
quinox.http.HttpService-http, http.address=ALL, service.vendor=IBM, service.desc
ription=OSGi Http Service – IBM Implementation, http.scheme=http, http.timeout=3
0, service.id=26}
{com.irahul.shared.ComplexService}={service.id=29}
No exported packages
Imported packages <-- Pacakages being imported
com.irahul.shared; version=”0.0.0″
javax.servlet; version=”2.4.0″
org.eclipse.equinox.http; version=”0.0.0″org.eclipse.equinox.http_1.0.300.v  20090520-1800="" [0]="" [3]>="" org.osgi.framework;="" version="”1.5.0″<org.eclipse.osgi_3.5.0.v20090520">
org.osgi.service.http; version=”1.2.0″
osgi> bundle 6
com.irahul.complexapp_1.0.0.SNAPSHOT [6]
Id=6, Status=ACTIVE Data Root=C:\osgidemo\runtime\configuration\org.eclip
se.osgi\bundles\6\data
Registered Services
{com.irahul.shared.ComplexService}={service.id=29}
No services in use.
No exported packages
Imported packages
com.irahul.shared
; version=”0.0.0″
org.osgi.framework; version=”1.5.0″
No fragment bundles
Named class space
com.irahul.complexapp; bundle-version=”1.0.0.SNAPSHOT”[provided]
No required bundles
osgi> bundle 7
com.irahul.complexapp_2.0.0.SNAPSHOT [7]
Id=7, Status=ACTIVE Data Root=C:\osgidemo\runtime\configuration\org.eclip
se.osgi\bundles\7\data
Registered Services
{com.irahul.shared.ComplexService}={service.id=30}
No services in use.
No exported packages
Imported packages
com.irahul.shared;
 version=”0.0.0″
org.osgi.framework; version=”1.5.0″
No fragment bundles
Named class space
com.irahul.complexapp; bundle-version=”2.0.0.SNAPSHOT”[provided]
No required bundles
osgi> stop 6
Service stopped
osgi> stop 7
Service stopped
osgi> ss
Framework is launched.
id State Bundle
0 ACTIVE org.eclipse.osgi_3.5.0.v20090520
1 ACTIVE javax.servlet_2.4.0.v200806031604
2 ACTIVE org.eclipse.osgi.services_3.1.200.v20070605
3 ACTIVE org.eclipse.equinox.http_1.0.300.v20090520-1800
4 ACTIVE com.irahul.shared_1.0.0.SNAPSHOT
5 ACTIVE com.irahul.http.handler_1.0.0.SNAPSHOT
6 RESOLVED com.irahul.complexapp_1.0.0.SNAPSHOT
7 RESOLVED com.irahul.complexapp_2.0.0.SNAPSHOT
osgi> bundle 5
com.irahul.http.handler_1.0.0.SNAPSHOT [5]
Id=5, Status=ACTIVE Data Root=C:\osgidemo\runtime\configuration\org.eclip
se.osgi\bundles\5\data
No registered services.
Services in use: <-- ComplexService disappear
{org.osgi.service.http.HttpService}={http.port=80, service.pid=org.eclipse.e
quinox.http.HttpService-http, http.address=ALL, service.vendor=IBM, service.desc
ription=OSGi Http Service - IBM Implementation, http.scheme=http, http.timeout=3
0, service.id=26}
No exported packages
Imported packages
com.irahul.shared; version="0.0.0"
javax.servlet; version=”2.4.0″
org.eclipse.equinox.http; version=”0.0.0″
org.osgi.service.http; version=”1.2.0″
osgi>
You can access the handler registered at http://localhost. Open it in a browser and hit refresh a few times, you should see a different message as a random service is picked! Then as you stop/start the complexapp bundles you can expect to see different messages.

Next: Spring Dynamic Modules (SpringDM) implementation for this same demo app

Pure Java OSGi demo implementation

To interface with OSGi we need a handle to the BundleContext. OSGi provides for an ‘activator’ that is basically the main() method for when a bundle gets started. To create an activator we will implement the BundleActivator interface. The interface has start and stop methods which are invoked at times that their names suggest and you can control the behavior in your implementing class. Then in the Maven plugin (in the POM) add this instruction:
....
<Bundle-Activator>com.irahul.http.handler.Activator</Bundle-Activator>
....
The shared service: The interface for the shared service is one simple method
....
String doSomething();
....
Creating a service implementation: The ComplexService we defined now needs to be implemented and made available as an OSGi service. Here is the Activator:
public class Activator implements BundleActivator{
 public void start(BundleContext context) throws Exception {  
  //register the complex service impl  
  context.registerService("com.irahul.shared.ComplexService",    
    new ComplexService(){
     public String doSomething() {
      return "Hello from Version 1!";
     }   
    }, 
    null);
 }
 .....
}
The service is implemented and registered with the bundle context.
Creating the servlet: This is done by implementing the Servlet interface. The ComplexService does something and the response is sent back to the client. The service to be used is chosen at random in this simple example.
public class MyHandler implements Servlet {
 private List<ComplexService> complexServices = new ArrayList<ComplexService>();
 
 public synchronized void addComplexService(ComplexService service) {
  if(service==null)return;
  complexServices.add(service);
 }
 
 public synchronized void removeComplexService(ComplexService svc) {
  if(svc==null)return;
  complexServices.remove(svc);
 }
 
 public void service(ServletRequest request, ServletResponse response)
  throws ServletException, IOException {
 
  //we may have discovered more than 1 service
  if(complexServices==null|| complexServices.size()==0){
   response.getOutputStream().print("No Service found!");
  }
  else{
   //there are 1...n services, pick one
   //using a random pick
   response.getOutputStream().print(complexServices.get(
     new Random().nextInt(complexServices.size())).doSomething());
  }
 }
 
 ....
}
Creating the http handler: The Activator for the HTTP Handler must now get a reference to this service and in turn register itself with the HTTP Server.
In addition to the BundleActivator I am also going to implement the ServiceListener interface. This allows us to get event notifications as services start/stop. We want to dynamically support multiple versions of the ComplexService. The Activator looks like this:
public class Activator implements BundleActivator, ServiceListener{
 private MyHandler myHandler;
 private BundleContext bundleContext;
 
 public void start(BundleContext context) throws Exception {
  //we need this later
  bundleContext = context;
 
  //we are interested in service updates
  context.addServiceListener(this);
 
  //load referenced services  
  //http service to register handler
  ServiceReference svcRef = context.getServiceReference("org.osgi.service.http.HttpService");
  HttpService httpService = (HttpService)context.getService(svcRef);
 
  //create and register http handler (catch all)
  myHandler = new MyHandler();  
  httpService.registerServlet("/", myHandler, null, null);
 
  //get a list of ComplexService (there may be more than 1)
  ServiceReference[] complexSvcRefs = context.getServiceReferences("com.irahul.shared.ComplexService",null);
  if(complexSvcRefs!=null){
   for(ServiceReference ref:complexSvcRefs){
    myHandler.addComplexService((ComplexService)context.getService(ref));
   }
  }
 
  System.err.println("http.handler started!");
 }
 ....
 public void serviceChanged(ServiceEvent event) {
  //some service has been registred/removed
  Object svc=bundleContext.getService(event.getServiceReference());
 
  if(svc instanceof ComplexService){
   if(event.getType()==ServiceEvent.REGISTERED){
    System.err.println("New Service added");
    myHandler.addComplexService((ComplexService)svc);
   }
   else{
    System.err.println("Service stopped");
    myHandler.removeComplexService((ComplexService)svc);
   }
  }
 }
}
Next: Running this in Eclipse Equinox

Tuesday, September 22, 2009

OSGi Demo app design

The application will consist of the following bundles (what are bundles?):
shared: This bundle exports the service interface for the proposed ComplexService.
http.handler: This bundle creates a HTTP handler that takes in an incoming request, finds the appropriate ComplexService, invokes it and returns the response. The bundle depends on the the service interface exported in the shared bundle.
complexapp: This bundle registers an implementation of the ComplexService with OSGi.
You also need external bundles that may be downloaded: (Look at OSGi HTTP Server)
javax:servlet:2.4 – Download from Eclipse Orbit – This provides the Java Servlets necessary to handle HTTP
org.eclipse.osgi:services:3.1.x – Download from Eclipse Equinox
org.eclipse.osgi:http:1.0.x – Download from Eclipse Equinox – These two provide the HTTP Server services
I will be using the Maven plugin (Felix plugin based on BND) to create the bundles I am writing. For example the POM file for shared is defined as (look at source for all):
<project ....>
  ....
  <groupId>com.irahul</groupId>
  <artifactId>shared</artifactId>
  <packaging>bundle</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>shared</name>
 
  <build>
   <plugins>
    <plugin>
     <groupId>org.apache.felix</groupId>
     <artifactId>maven-bundle-plugin</artifactId>
     <version>1.4.0</version>
      <extensions>true</extensions>
      <configuration>
       <instructions>
        <Export-Package>
         com.irahul.shared*,
         !*
        </Export-Package>
        <Private-Package>
         com.irahul.shared*
        </Private-Package>        
         </instructions>
      </configuration>
    </plugin>
 
    <plugin>
     <groupId>org.apache.maven.plugins</groupId>
     <artifactId>maven-compiler-plugin</artifactId>
     <configuration>
      <source>1.5</source>
      <target>1.5</target>
     </configuration>
    </plugin>
   </plugins>
  </build>
 
  <dependencies>
      ....
  </dependencies>
</project>
This results in a nicely created bundle with the MANIFEST.MF file like this:

Manifest-Version: 1.0
Export-Package: com.irahul.shared
Private-Package: com.irahul.shared*
Built-By: rahul
Tool: Bnd-0.0.238
Bundle-Name: shared
Created-By: Apache Maven Bundle Plugin
Bundle-Version: 1.0.0.SNAPSHOT
Build-Jdk: 1.6.0_16
Bnd-LastModified: 1253219746453
Bundle-ManifestVersion: 2
Import-Package: com.irahul.shared
Bundle-SymbolicName: com.irahul.shared
Correctly exported and imported packages are a key to resolving your bundles correctly in the OSGi container.
Next: Pure Java implementation

Thursday, September 17, 2009

This is the first part in a series for a presentation at the Silicon Valley Code Camp at Foothill college.
For a concise description read through the official OSGi Architecure page.
I’ll present a simple application that shows how to dynamically provide an application upgrade with zero downtime and no JVM restart. It will load a HTTP Server and then register a handler with it. This handler then processes the request using a ComplexService. Multiple bundles of this ComplexService implementation are possible (suppose V1 and V2). We can start the OSGi container with 0 or more of these and let the handler decide which one to use. If we start with suppose V1 then we can load V2 and stop V1, creating an update without a JVM restart.
The following sequence diagram shows the events.
















Sequence diagram for OSGi example
With this simple set of bundles (less than 200 lines of code in all!) I can show the following:

Run multiple versions of your app simultaneously: If your application is designed with this mind, you can continue to run singletons of the http server and your data access layer, however you have multiple versions of your business logic which may be used dynamically.
Dynamic routing: Expanding on the point above you can build a smart routing mechanism to invoke the different versions of the services that are available.
Install new bundles to upgrade features: You may create new services and register them with the http server to provide new features all without a JVM restart.
Upgrade application version: You may install a new version of any service and shutdown and remove a previous version and have existing http services use this dynamically. Again no JVM restart.
Note on versioning: In OSGi it is possible to version packages exported/imported by bundles. For the purpose of this exercise I’ll keep things simple and not use it.
Next: Application design for this demo