Thursday, June 24, 2010

Getting Started with OSGi

Suppose we have a thirdparty-impl, one is version 1.0 and the other one is version 1.1 and in my class I need to invoke the method in version 1.0 as well version 1.1. In standard Java environment, this won't be possible since thirdparty-impl-1.0.jar and thirdparty-impl-1.1.jar are both in the same classpath. The one that comes earlier will take precedence. This is where OSGi comes to rescue.

In this example, we will be using Equinox. To start Equinox, run this in command line.
java -jar org.eclipse.osgi_3.5.2.R35x_v20100126.jar -console

Type 'ss' to get the list of bundles installed. To get more details on the bundle, type 'bundle [bundle_id]'.

==============
thirdparty-api
==============
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>myproject</groupId>
  <artifactId>thirdparty-api</artifactId>
  <version>1.0</version>
  <name>thirdparty-api</name>
  <dependencies>
    <dependency>
      <groupId>org.osgi</groupId>
      <artifactId>osgi_R4_core</artifactId>
      <version>1.0</version>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.osgi</groupId>
      <artifactId>osgi_R4_compendium</artifactId>
      <version>1.0</version>
      <optional>true</optional>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <executions>
          <execution>
            <id>manifest</id>
            <phase>process-classes</phase>
            <goals>
              <goal>manifest</goal>
            </goals>
          </execution>
          <execution>
            <id>bundle</id>
            <phase>package</phase>
            <goals>
              <goal>bundle</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <instructions>
            <Bundle-ManifestVersion>2</Bundle-ManifestVersion>
            <Export-Package>myproject.api</Export-Package>
            <Import-Package />
          </instructions>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <repositories>
    <repository>
      <id>ops4j</id>
      <url>http://repository.ops4j.org/maven2/</url>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
  </repositories>
</project> 

We need to include the ops4j repository since as of this writing the OSGi libraries aren't available in Maven central repository.

The maven-bundle-plugin is used to create the OSGi bundle. The OSGi bundle contains MANIFEST.MF. The MANIFEST.MF contains metadata information, such as packages to import, export, symbolic name, bundle version, etc.

ThirdPartyService.java
package myproject.api;

public interface ThirdPartyService { 
    
    public void doSomething(); 
}

Run 'mvn install' and deploy the bundle into Equinox by typing 'install file:[path]/thirdparty-api-1.0.jar' in the Equinox console.

Type 'ss' in the Equinox console and you should see something like this.
osgi> ss

Framework is launched.

id State       Bundle
0 ACTIVE      org.eclipse.osgi_3.5.2.R35x_v20100126
2 INSTALLED   myproject.api_1.0.0

===============
thirdparty-impl
===============
Here we will deploy the first version of thirdparty-impl and we will deploy the second version of thirdparty-impl afterwards.

ThirdPartyServiceImpl.java (version 1.0)
package myproject.api.impl;

import myproject.api.ThirdPartyService;

public class ThirdPartyServiceImpl implements ThirdPartyService {

    @Override 
    public void doSomething() { 
        System.out.println("I'm version 1.0"); 
    } 
} 

ThirdPartyPublisher.java
package myproject.api.impl;

import myproject.api.ThirdPartyService;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;

public class ThirdPartyPublisher implements BundleActivator {
    private ServiceRegistration sr;

    public void start(BundleContext context) throws Exception {
        sr = context.registerService(ThirdPartyService.class.getName(),
                new ThirdPartyServiceImpl(), null);
    }

    public void stop(BundleContext context) throws Exception {
        sr.unregister();
    }
}

pom.xml (version 1.0)
<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>myproject</groupId>
  <artifactId>thirdparty-impl</artifactId>
  <version>1.0</version>
  <name>thirdparty-impl</name>
  <dependencies>
    <dependency>
      <groupId>myproject</groupId>
      <artifactId>thirdparty-api</artifactId>
      <version>1.0</version>
    </dependency>
    <dependency>
      <groupId>org.osgi</groupId>
      <artifactId>osgi_R4_core</artifactId>
      <version>1.0</version>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.osgi</groupId>
      <artifactId>osgi_R4_compendium</artifactId>
      <version>1.0</version>
      <optional>true</optional>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <executions>
          <execution>
            <id>manifest</id>
            <phase>process-classes</phase>
            <goals>
              <goal>manifest</goal>
            </goals>
          </execution>
          <execution>
            <id>bundle</id>
            <phase>package</phase>
            <goals>
              <goal>bundle</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <instructions>
            <Bundle-ManifestVersion>2</Bundle-ManifestVersion>
            <Import-Package>org.osgi.framework,myproject.api</Import-Package>
            <Bundle-Activator>myproject.api.impl.ThirdPartyPublisher</Bundle-Activator>
            <Bundle-Version>1.0</Bundle-Version>
          </instructions>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <repositories>
    <repository>
      <id>ops4j</id>
      <url>http://repository.ops4j.org/maven2/</url>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
  </repositories>
</project> 

Run 'mvn install' and deploy the bundle into Equinox by typing 'install file:[path]/thirdparty-impl-1.0.jar' in the Equinox console.

Type 'ss' in the Equinox console and you should see something like this.
osgi> ss

Framework is launched.

id State       Bundle
0 ACTIVE      org.eclipse.osgi_3.5.2.R35x_v20100126
2 INSTALLED   myproject.api_1.0.0
3 INSTALLED   myproject.api.impl_1.0.0

Now let's build the second version of thirdparty-impl.

ThirdPartyServiceImpl.java (version 1.1)
package myproject.api.impl;

import myproject.api.ThirdPartyService;

public class ThirdPartyServiceImpl implements ThirdPartyService {

    @Override 
    public void doSomething() { 
        System.out.println("I'm version 1.1"); 
    } 
} 

pom.xml (version 1.1)
<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>myproject</groupId>
  <artifactId>thirdparty-impl</artifactId>
  <version>1.1</version>
  <name>thirdparty-impl</name>
  <dependencies>
    <dependency>
      <groupId>myproject</groupId>
      <artifactId>thirdparty-api</artifactId>
      <version>1.0</version>
    </dependency>
    <dependency>
      <groupId>org.osgi</groupId>
      <artifactId>osgi_R4_core</artifactId>
      <version>1.0</version>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.osgi</groupId>
      <artifactId>osgi_R4_compendium</artifactId>
      <version>1.0</version>
      <optional>true</optional>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <executions>
          <execution>
            <id>manifest</id>
            <phase>process-classes</phase>
            <goals>
              <goal>manifest</goal>
            </goals>
          </execution>
          <execution>
            <id>bundle</id>
            <phase>package</phase>
            <goals>
              <goal>bundle</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <instructions>
            <Bundle-ManifestVersion>2</Bundle-ManifestVersion>
            <Import-Package>org.osgi.framework,myproject.api</Import-Package>
            <Bundle-Activator>myproject.api.impl.ThirdPartyPublisher</Bundle-Activator>
            <Bundle-Version>1.1</Bundle-Version>
          </instructions>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <repositories>
    <repository>
      <id>ops4j</id>
      <url>http://repository.ops4j.org/maven2/</url>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
  </repositories>
</project> 

Run 'mvn install' and deploy the bundle into Equinox by typing 'install file:[path]/thirdparty-impl-1.1.jar' in the Equinox console.

Type 'ss' in the Equinox console and you should see something like this.
osgi> ss

Framework is launched.

id State       Bundle
0 ACTIVE      org.eclipse.osgi_3.5.2.R35x_v20100126
2 INSTALLED   myproject.api_1.0.0
3 INSTALLED   myproject.api.impl_1.0.0
4 INSTALLED   myproject.api.impl_1.1.0

================
simple-activator
================
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>myproject</groupId>
  <artifactId>simple-activator</artifactId>
  <version>1.0</version>
  <name>simple-activator</name>
  <dependencies>
    <dependency>
      <groupId>org.osgi</groupId>
      <artifactId>osgi_R4_core</artifactId>
      <version>1.0</version>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.osgi</groupId>
      <artifactId>osgi_R4_compendium</artifactId>
      <version>1.0</version>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>myproject</groupId>
      <artifactId>thirdparty-api</artifactId>
      <version>1.0</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <executions>
          <execution>
            <id>manifest</id>
            <phase>process-classes</phase>
            <goals>
              <goal>manifest</goal>
            </goals>
          </execution>
          <execution>
            <id>bundle</id>
            <phase>package</phase>
            <goals>
              <goal>bundle</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <instructions>
            <Bundle-ManifestVersion>2</Bundle-ManifestVersion>
            <Import-Package>org.osgi.framework,org.osgi.util.tracker,myproject.api</Import-Package>
            <Bundle-Activator>myproject.activator.SimpleActivator</Bundle-Activator>
            <Export-Package />
          </instructions>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <repositories>
    <repository>
      <id>ops4j</id>
      <url>http://repository.ops4j.org/maven2/</url>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
  </repositories>
</project> 

SimpleActivator.java
package myproject.activator;

import myproject.api.ThirdPartyService;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class SimpleActivator implements BundleActivator {

    @Override
    public void start(BundleContext context) throws Exception {
        ThirdPartyService service = (ThirdPartyService) context.getService(context.getBundle(3L)
                .getRegisteredServices()[0]);
        service.doSomething();

        service = (ThirdPartyService) context.getService(context.getBundle(4L)
                .getRegisteredServices()[0]);
        service.doSomething();
    }

    @Override
    public void stop(BundleContext context) throws Exception {
    }
}

Run 'mvn install' and deploy the bundle into Equinox by typing 'install file:[path]/simple-activator-1.0.jar' in the Equinox console.

Type ss in the Equinox console and you should see something like this.
osgi> ss

Framework is launched.

id State       Bundle
0 ACTIVE      org.eclipse.osgi_3.5.2.R35x_v20100126
2 INSTALLED   myproject.api_1.0.0
3 INSTALLED   myproject.api.impl_1.0.0
4 INSTALLED   myproject.api.impl_1.1.0
5 INSTALLED   myproject.activator_1.0.0

Now start all the bundles in Equinox console.
osgi> start 2

osgi> start 3

osgi> start 4

osgi> start 5
I'm version 1.0
I'm version 1.1

Pretty cool, huh? :)

No comments:

Post a Comment