How to write a WSO2 custom OSGi component?

Dewni Weeraman
7 min readNov 5, 2017

--

Want to learn how to develop an OSGi service? Here we’ll develop a simple application with OSGi and test it in WSO2 Carbon platform which is a modular, light-weight, OSGi-based server development framework. Already confused about what’s OSGi? Don’t worry I will be explaining it soon. Readers are expected to have prior experience in Java development and Apache Maven. Lets dive in :)

First of all, before putting our fingers on the keyboard and start coding let me explain what is the real idea behind using OSGi in applications. OSGi (Open Services Gateway initiative) is a component framework for Java which defines an architecture for modular application development. That is it gives us the flexibility to remotely install, start, stop, update and uninstall applications coming in the form of bundles for deployment without requiring a reboot. Super cool right? There are different OSGi container implementations such as Equinox, Knopflerfish and Felix. In this tutorial as the OSGi container we will use Eclipse OSGi container implementation, Equinox (Eclipse Equinox is used as the OSGi runtime in WSO2 Carbon Platform). Still not clear? Don’t worry. Continue reading you will understand it soon. Let’s move on to coding.

In this article I will be first creating two Maven projects; osgiproducer and osgiconsumer. In osgiproducer project, the OSGi service is written and in the osgiconsumer project, the previously written OSGi service will be consumed.

Let’s get our hands on creating the osgiproducer project as the first stage by following the given below simple steps.

1. pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.wso2</groupId>
<artifactId>wso2</artifactId>
<version>1</version>
</parent>

<groupId>com.example.osgiproducer</groupId>
<artifactId>osgiproducer</artifactId>
<version>1.0.0-SNAPSHOT</version>

<packaging>bundle</packaging>

<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-scr-plugin</artifactId>
<version>1.9.0</version>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Bundle-Name>${project.artifactId}</Bundle-Name>
<Private-Package>com.example.osgiproducer.package01.internal,</Private-Package>
<Import-Package>
org.osgi.framework,
org.osgi.service.component,
</Import-Package>
<Export-Package>
!com.example.osgiproducer.package01.internal.ProducerComponent,
com.example.osgiproducer.package01.*;version="1.0.0-SNAPSHOT"
</Export-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId>
<version>3.9.1.v20130814-1242</version>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi.services</artifactId>
<version>3.3.100.v20130513-1956</version>
</dependency>
</dependencies>

</project>

Let’s closely look at the aforementioned POM configurations.

<packaging>bundle</packaging>

This is where we should configure maven to build an OSGi bundle by defining the packaging element as bundle. Bundle is a JAR with additional manifest entries which is used by the OSGi framework to identify services.

<Export-Package>
!com.example.osgiproducer.package01.internal.ProducerComponent,
com.example.osgiproducer.package01.*;version="1.0.0-SNAPSHOT"
</Export-Package>

The <Export-Package> represents the list of packages for the bundle to export. These packages will be copied to the resulting bundle JAR. We can exclude packages we don’t want to import by using negation (!). Note that it is mandatory to use the fully qualified names when calling the relevant classes and packages. The purpose of exporting packages will be explained later when adding the POM configurations to osgiconsumer project.

Another key fact I should mention about is the <Private-Package>.

<Private-Package>com.example.osgiproducer.package01.internal,</Private-Package>

Here we have added the fully qualified name of the package where the service component is written. It is a commonly practiced procedure at WSO2 to have the service component in a sub-package called internal and not to have that service class exported by the bundle.

<Import-Package>
org.osgi.framework,
org.osgi.service.component,
</Import-Package>

<Import-Package> instruction denotes the packages that are required by the bundle’s contained packages.

2. Producer interface

package com.example.osgiproducer.package01;

public interface Producer {

void produce(String name);
}

3. ProducerImpl class

package com.example.osgiproducer.package01;

import java.util.logging.Logger;

public class ProducerImpl implements Producer{

private static final Logger LOGGER = Logger.getLogger(ProducerImpl.class.getName());

@Override
public void produce(String name) {

LOGGER.info("Successfully produced: "+ name);

}
}

4. ProducerComponent class

package com.example.osgiproducer.package01.internal;

import com.example.osgiproducer.package01.Producer;
import com.example.osgiproducer.package01.ProducerImpl;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;

@Component(name = "com.example.osgiproducer.package01.internal.ProducerComponent",
immediate = true)
public class ProducerComponent {

@Activate
protected void activate(BundleContext bundleContext) {

bundleContext.registerService(Producer.class, new ProducerImpl(), null);
}
}

Note that as mentioned earlier the ProducerComponent class should be inside a package named as internal.

The project folder structure will look like this,

Finally build the project by invoking the maven command “mvn clean install” and you will obtain the created osgiproducer-1.0.0.-SNAPSHOT.jar inside the target folder.

Now let’s create osgiconsumer project by following the given steps below.

  1. pom file
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.wso2</groupId>
<artifactId>wso2</artifactId>
<version>1</version>
</parent>

<groupId>com.example.osgiconsumer</groupId>
<artifactId>osgiconsumer</artifactId>
<version>1.0.0-SNAPSHOT</version>

<packaging>bundle</packaging>

<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-scr-plugin</artifactId>
<version>1.9.0</version>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Bundle-Name>${project.artifactId}</Bundle-Name>
<Private-Package>com.example.osgiconsumer.package01.internal,</Private-Package>
<Import-Package>
org.osgi.framework,
org.osgi.service.component,
com.example.osgiproducer.package01.*;version="1.0.0-SNAPSHOT"
</Import-Package>
<Export-Package>
!com.example.osgiconsumer.package01.internal.ConsumerComponent,
com.example.osgiconsumer.package01.*;version="1.0.0-SNAPSHOT"
</Export-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi</artifactId>
<version>3.9.1.v20130814-1242</version>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi.services</artifactId>
<version>3.3.100.v20130513-1956</version>
</dependency>
<dependency>
<groupId>com.example.osgiproducer</groupId>
<artifactId>osgiproducer</artifactId>
<version>1.0.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>


</project>

I will not be explaining about the POM configurations again but there is one fact I should point out.

<Import-Package>
org.osgi.framework,
org.osgi.service.component,
com.example.osgiproducer.package01.*;version="1.0.0-SNAPSHOT"
</Import-Package>

Notice that we have to import the package we have exported in the osgiproducer project and I would again like to emphasize on the fact that it should be called using the fully qualified name.

2. ConsumerComponent class

package com.example.osgiconsumer.package01.internal;

import com.example.osgiproducer.package01.Producer;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;


@Component(name = "com.example.osgiconsumer.package01.ConsumerComponent",
immediate = true)
public class ConsumerComponent {
Producer producer = null;

@Activate
protected void activate(BundleContext bundleContext) {
producer.produce("producer component");
}

@Reference(name = "producerService", service = Producer.class,
cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.DYNAMIC, unbind = "unbindService")
protected void registerService(Producer producer){
this.producer = producer;
}

protected void unbindService(Producer producer){

}
}

Notice that ConsumerComponent class is inside the internal sub-package.

The project folder structure will look like this,

Build the project and you will obtain the following jar file.

Now let’s test the OSGi service we have written. As mentioned in the beginning of this article we will be testing our code in WSO2 Carbon platform.

Clone https://github.com/wso2/carbon-kernel. Checkout to branch 4.4.x and build the project using the command “mvn clean install”.

Next go to project location carbon-kernel -> distribution -> product -> modules -> distribution -> target . You will see a binary pack (ZIP file) inside the target directory. Extract this zip to a preferred location. As of the time I’m writing this article I will be using wso2carbon-4.4.18-SNAPSHOT version. I will be referring to it as <PRODUCT_HOME> (directory where you installed the product distribution).

Go to <PRODUCT_HOME> -> repository -> components -> dropins. Copy the 2 created JAR files into the dropins folder.

Now we are all set to test whether our OSGi service is functioning properly. First we have to start the server and then check whether the 2 OSGi service bundles are activated. For this we have to start the server in the OSGi console mode.

Go to <PRODUCT_HOME> -> bin directory and open a command prompt. Execute one of the following commands:

  1. For Windows user:

wso2server.bat --run -DosgiConsole

2. For Linux user:

sh wso2server.sh -DosgiConsole

Now, the server startup logs will get printed. When the server startup is completed, look for the following log message highlighted in the image.

Press the enter button when the server has completed the startup. Then you will get the osgi console. Execute the command “ss”. Then you will obtain an overview of all the OSGi bundles available with their current states and bundle-ids similar to the below image.

Go through the list and you will see the 2 JAR files we copied to the dropins folder with the state as Active.

Execute command “b <bundle-id>” to obtain information about the bundle including the registered and used services.

Finally we have reached the end of this tutorial. Hopefully, this tutorial has provided a start on how to use OSGi.

Refer to the following links for the source code.

So now it’s time to get out there and write some OSGi services!

--

--

Dewni Weeraman
Dewni Weeraman

Written by Dewni Weeraman

Software Engineer at WSO2 | Graduate of University of Westminster

Responses (3)