2009/11/17
17 Nov, 2009

How to build OSGi bundles using Maven Bundle Plugin - Part 1

  • Sameera Jayasoma
  • Senior Director, Platform Architecture - WSO2

Who Can Benefit from This Tutorial?

If you are struggling to build OSGi [1] bundles, then this tutorial is just for you.  This tutorial will be most beneficial to users who already have some experience with Java development, Maven2 and OSGi, as some terms have been used without any explanation.  It is also assumed that readers have the Sun JDK 1.5 or higher and Apache Maven 2.0.9 or higher already installed on your computer.

 

Table of Contents

Introduction

The most important task in building OSGi bundles is to write the manifest.mf file correctly.  In most cases writing this file manually is not an easy task. Hence you will have to use a tool to generate it, rather than writing it on your own. For this task, Maven bundle plugin is the ideal choice. You just need to specify contents which should be copied from the available set of classes (project code, dependencies), and Maven [2] bundle plugin [3] will do the rest for you. You can specify the packages to be exported or imported, as well as the resources to be copied, as instructions in the plugin configuration section of your POM file.  This is illustrated below:

<plugins>
  <plugin>
      <groupId>org.apache.felix</groupId>
      <artifactId>maven-bundle-plugin</artifactId>
      <version>1.4.0</version>
      <extensions>true</extensions>
      <configuration>
          <instructions>
              <Bundle-SymbolicName>${pom.groupId}.${pom.artifactId}</Bundle-SymbolicName>
              <Bundle-Name>${pom.artifactId}</Bundle-Name>
              <Bundle-Version>1.0.0</Bundle-Version>
              <Private-Package>org.wso2.mbp.helloworld</Private-Package>
              <Bundle-Activator>org.wso2.mbp.helloworld.Activator</Bundle-Activator>
              <Import-Package>
                  org.osgi.framework,
                  *;resolution:=optional
              </Import-Package>
          </instructions>
      </configuration>
  </plugin>
</plugins>

Depending on these instructions, bundle plugin decides the classes and resources which should be copied to your bundle and the manifest headers and their values which should be placed in the bundle's manifest.mf file. Here is a set of manifest headers corresponding to the above plugin configuration:

Created-By: Apache Maven Bundle Plugin 
Private-Package: org.wso2.mbp.helloworld
Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.wso2.mbp.helloworld
Bundle-Name: helloworld
Bundle-Version: 1.0.0
Bundle-Activator: org.wso2.mbp.helloworld.Activator
Import-Package: org.osgi.framework

Next, you need to clearly identify the instructions, manifest headers, and the relationship between them. Maven bundle plugin is based on a tool called BND [4] and has several instructions to configure its behavior; these instructions are recognized using the starting letter.

Types of Maven Bundle Plugin Instructions

Type Starts with Copied to manifest.mf file Description
Manifest Headers Capital letter Yes These instructions are copied to  the manifest file as manifest headers. Values of these instruction are either copied, or generated by the Plugin.
Variables Lowercase letter No These instructions act as variables and can be used for property substitution.
Directives '-' character No These perform some special processing.

 

A Simple Bundle

Let's develop a simple bundle which prints "Hello World" and "Goodbye World" to the console when the bundle is starting and stopping. This bundle consists of a single class and implements the BundleActivator interface.

BundleActivator

BundleActivaror is an interface which has two methods. Bundles can specify a class which implements this interface and the OSGi framework is guaranteed to invoke start() method when the bundle is started and stop() method is invoked when the bundle is stopped. BundleActivator is specified using the Manifest Header Bundle-Activator.

Bundle-Activator:org.wso2.mbp.sample01.Activator

The start() method is invoked when your bundle's state is changing from RESOLVED through STARTING to ACTIVE and the stop() method is called when state is changing from ACTIVE through STOPPING to RESOLVED. In both these methods, the OSGi framework passes the BundleContext object which represents your bundle's execution context within the framework. A BundleContext is created by the OSGi framework when a bundle is started and you can use this BundleContext instance to install new bundles, to register and retrieve OSGi services and to subscribe or unsubscribe to various events broadcast by the framework.

You can download the complete source code of all the samples here [5]. This section is based on sample01. Following code segment shows the implementation of Activator class for sample01.

package org.wso2.mbp.sample01;

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

public class Activator implements BundleActivator {

/**
* Implements BundleActivator.start().
* @param bundleContext - the framework context for the bundle.
**/
public void start(BundleContext bundleContext) {
System.out.println("Hello World");
}

/**
* Implements BundleActivator.stop()
* @param bundleContext - the framework context for the bundle.
**/
public void stop(BundleContext bundleContext) {
System.out.println("Goodbye World");
}
}

Now let's write the plugin configuration of the bundle plugin in pom.xml which contains information about the project and configuration details used by Maven2 to build the project. Please visit this url [6]  if you want to know more about the pom.xml. Following is the pom.xml file of sample01 project:

<project xmlns="https://maven.apache.org/POM/4.0.0"...>
  <groupId>org.wso2.mbp</groupId>
  <modelVersion>4.0.0</modelVersion>
 <artifactId>sample01</artifactId>
  <version>1.0.0</version>
  <packaging>bundle</packaging>
  <name>Sample01</name>
  <description>A Simple Bundle which print "Hello World" and "Goodbye World"</description>
  <url>https://www.wso2.org</url>

  <build>
      <plugins>
          <plugin>
              <groupId>org.apache.felix</groupId>
              <artifactId>maven-bundle-plugin</artifactId>
              <version>1.4.0</version>
              <extensions>true</extensions>
              <configuration>
                  <instructions>
                      <Bundle-SymbolicName>${pom.groupId}.${pom.artifactId}</Bundle-SymbolicName>
                      <Bundle-Name>${pom.name}</Bundle-Name>
                      <Bundle-Version>${pom.version}</Bundle-Version>
                      <Bundle-Activator>org.wso2.mbp.sample01.Activator</Bundle-Activator>
                      <Private-Package>org.wso2.mbp.sample01</Private-Package>
                  </instructions>
              </configuration>
          </plugin>
      </plugins>
  </build>

  <dependencies>
       <dependency>
           <groupId>org.apache.felix</groupId>
           <artifactId>org.osgi.core</artifactId>
       </dependency>
 </dependencies>

</project>

If you look closely at the above pom, you will see that the value of the packaging element is set to "bundle". This is where you configure maven to build an OSGi bundle.

<packaging>bundle</packaging>

You also need to provide values for some OSGi manifest headers such as Bundle-SymbolicName, Bundle-Name, Bundle-Version. Values for these OSGi manifest headers can be specified using Manifest header instructions. The values provided here for these instructions are the default values. If you haven't specified these instructions in the plugin configuration, the bundle plugin will add the corresponding OSGi manifest headers for you, with the same values used by the above instructions.

One important thing to note: since you are not exporting the package "org.wso2.mbp.sample01", you should at least add it to the Private-Package instruction.  Otherwise, the classes inside the package will not be copied to your bundle, as the default value of this instruction is empty.

<Private-Package>org.wso2.mbp.sample01</Private-Package>

You also need to provide the fully qualified class name of your bundle activator as follows:

<Bundle-Activator>org.wso2.mbp.sample01.Activator</Bundle-Activator>

If you have finished writing the pom file, you can then build the sample01 bundle. Go to the directory where the pom.xml is located and invoke the maven command "mvn clean install".  Notice the created sample01-1.0.0.jar file in the target/folder.  The following segment shows some of the manifest headers created in the sample01 bundle's manifest file:

Bundle-SymbolicName: org.wso2.mbp.sample01 
Bundle-Name: Sample01
Bundle-Version: 1.0.0
Bundle-Activator: org.wso2.mbp.sample01.Activator
Bundle-Description: A Simple Bundle which prints "Hello World" and "Goodbye World"
Import-Package: org.osgi.framework
Private-Package: org.wso2.mbp.sample01

Although you can see the manifest header Private-Package, it is not a standard OSGi header and it is discarded by the OSGi framework. Observe the Import-Package OSGi manifest header.  Although we have not specified it in our plugin configuration, it has been included in the manifest file; this will be explained in the next section.

This bundle has been tested using the Equinox [7] OSGi framework; the latest Equinox framework here [8]. You can find org.eclipse.osgi_<version>.jar inside the downloaded zip file. Use the following command to start the OSGi framework:

java -jar org.eclipse.osgi_<version>.jar -console

You should then see the following OSGi console. You can use the "ss" command to view the installed bundles. Since we did a fresh framework start, only the system bundle is there.

osgi> ss
Framework is launched.


id    State       Bundle
0    ACTIVE      org.eclipse.osgi_3.4.0

Use following commands to install, start, stop, update and uninstall bundles in the framework:

  • install - install and optionally start bundle from the given URL
  • uninstall - uninstall the specified bundle(s)
  • start - start the specified bundle(s)
  • stop - stop the specified bundle(s)
  • update - update the specified bundle(s)
osgi> install file:/<path>/sample01-1.0.0.jar
Bundle id is 1

osgi> ss
Framework is launched.

id    State       Bundle
0    ACTIVE      org.eclipse.osgi_3.4.0.v20080605-1900
1    RESOLVED    helloworld_1.0.0

Use the bundle ID for subsequent commands

osgi> start 1
Hello World!!!

osgi> stop 1
Goodbye World!!!

Sharing Packages

OSGi defines a modularization model for java. An OSGi bundle, which is a jar file, can share its packages with other bundles or hide packages from other bundles. This is achieved with the following manifest headers: Export-Package, Import-Package and DynamicImport-Package. A bundle can specify the packages it exports and imports using these manifest headers. There are corresponding bundle plugin instructions for these manifest headers such as Private-Package, Export-Package, Import-Package and DynamicImport-Package. You can specify a list of packages (or patterns) for these instructions, so that different actions can be performed on the package list. Note that the list of packages are ordered. Here are some sample lists of packages:


org.wso2.mbp.echo 
org.wso2.mbp.echo.*
!org.wso2.mbp.echo.string,org.wso2.mbp.echo.*
org.wso2.mbp.echo.*,!org.wso2.mbp.echo.string

Exporting packages

You can specify the list of packages to be exported from a bundle using Export-Package instruction. If these packages are available in the project classes or project dependencies, they will be copied to your bundle and exported. Each package or pattern you specified for this instruction is matched against every available package in project sources and project dependencies. If a match is found, the matched packages will be included or excluded from the bundle. This behavior is dependent on the pattern. For example, if the pattern is a negation pattern which starts with !, then the packages are excluded. Consider the following examples:

 <Export-Package>org.wso2.mbp.echo</Export-Package>

The classes inside the above package will be copied to the target bundle.

 <Export-Package>org.wso2.mbp.echo.*</Export-Package>

The classes inside the above package and classes inside all the sub packages will be copied to the target bundle.

 <Export-Package>org.wso2.mbp.echo.*,!org.wso2.mbp.echo.string</Export-Package>

Since the list of packages is ordered, earlier patterns take effect before later patterns. Therefore all the packages that matched with the pattern org.wso2.mbp.echo.* are selected to include to the target bundle.  The second pattern has no effect since org.wso2.carbon.mbp.echo.* packages have been selected to include. The correct way to achieve the intended result is to change the order of the patterns.

Hiding packages

In certain situations, you may not need to export all the classes inside a bundle. For example, if your bundle contains a Java interface and an implementation of that interface, then the standard practice is to export only the interface while keeping the implementation private. Packages that are not exported or imported are called private packages. There is no standard OSGi manifest header to specify private packages. If you could copy Java classes to a bundle without exporting them, then those packages will be private packages.

To copy classes to a bundle without exporting them, you can use the Private-Package instruction. Both the Private-Package and Export-Package instructions copy classes to bundles, but Export-Package does something extra by exporting packages. If a package is specified by both Export-Package and Private-Package instructions, then Export-Package takes precedence. Consider the following examples:

<Private-Package>org.wso2.mbp.echo</Private-Package> 

The classes inside the above package will be copied to the target bundle.

<Private-Package>org.wso2.mbp.exportsample.internal</Private-Package> 
<Export-Package>org.wso2.mbp.exportsample</Export-Package>

Here, the classes inside the packages org.wso2.mbp.exportsample and org.wso2.mbp.exportsample.internal is copied to the bundle, but only the package org.wso2.mbp.exportsample is exported.  The following is the resulting manifest.mf file:

Manifest-Version: 1.0 
Export-Package: org.wso2.mbp.exportsample
<Private-Package>org.wso2.mbp.exportsample.internal</Private-Package> 
<Export-Package>org.wso2.mbp.exportsample.*</Export-Package>

This is a problematic scenario. Export-Package instruction has specified the pattern org.wso2.mbp.exportsample.*. This pattern matches packages org.wso2.mbp.exportsample and org.wso2.mbp.exportsample.internal. Private-Package instruction also specified the package org.wso2.mbp.exportsample.internal. This package is specified by both instructions. What will be the resulting manifest.mf? Have a look at the following:

Manifest-Version: 1.0 
Export-Package: org.wso2.mbp.exportsample.internal,org.wso2.mbp.exportsample

Export-Packages instruction has taken precedence, so both the packages are exported. This is not the intended result.

<Private-Package>org.wso2.mbp.exportsample.internal.*</Private-Package>
<Export-Package>
    !org.wso2.mbp.exportsample.internal,
    org.wso2.mbp.exportsample.*
</Export-Package>

The correct way to archive the above result is to specify that you do not need to export the org.wso2.mbp.exportsample.internal package by using negation pattern.

The default value of the Export-Package instruction is "<BundleSymbolic-Name>.*". That means if you haven't used the Export-Package instruction, Maven bundle plugin exports the packages that are matched with the "<BundleSymbolic-Name>.*" pattern. But if you have specified the Private-Package instruction, then value of the Export-Package instruction is empty. The default value of the Private-Package instruction is empty.

Importing packages.

The classes contained in a bundle may have referred classes in other bundles. Those dependent packages should be specified using the Import-Package instruction. The default value of this instruction is "*". That means if you haven't explicitly specified this instruction, bundle plugin will import all the referred packages. Hence you don't always need to specify this instruction explicitly. Consider the following examples:

<Import-Package>org.wso2.mbp.exportsample.*</Import-Package>

Import classes in the package org.wso2.mbp.exportsample and all its sub-packages.

In some situations, all referenced packages do not need to be available for a bundle to perform its intended task. There are some code blocks which you never need to execute in your application, and the packages referenced only by these code blocks can be considered unwanted. Also, there are some code blocks which are executed only if certain dependencies are available; packages referenced only by these code blocks can be considered optional. Therefore your bundle should be able resolve correctly, with or without the availability of unwanted or optional packages.  Consider the following examples:

<Import-Package>!org.wso2.carbon.ui,org.wso2.mbp.exportsample.*</Import-Package>

Using a negation pattern, it is possible to remove an unwanted import of org.wso2.carbon.ui package.

Consider a scenario where you need to import a package that has not been referenced by any of your classes. This can be achieved by putting package to the list of imported packages without using wildcards like "*".

<Import-Package>
    org.wso2.mbp.exportsample.*,
    org.wso2.carbon.ui;resolution:=optional
</Import-Package>

Resolution directive is used to indicate that package org.wso2.carbon.ui is optional.  This bundle can successfully resolve even if the org.wso2.carbon.ui is not present. The default value of the resolution directive is "mandatory".

There is another way to specify optional packages using "Dynamic imports". Consider a scenario where classes are loaded using the Class.forName() method. There is no way for bundle plugin to know in advance the required packages. The solution is to use DynamicImport-Package. For this instruction also, a list of packages or patterns can be specified. If the package of a dynamically loaded class is matched with the patterns specified for DynamicImport-Package header and that package is exported from a bundle, this runtime class loading will be successful. Otherwise ClassNotFoundException will be thrown. Consider the following examples:

<DynamicImport-Package>*</DynamicImport-Package>

This bundle allows its classes to load any class from another bundle, if the package containing that class is exported.

Versioning Packages

With OSGi, multiple versions of the same package can be present in a single application. This is one of the main advantages of OSGi. In standard Java this is not possible; even if multiple versions of a package can exist in the system, only the single version will be used. Consider the following examples:

<Export-Package>org.wso2.mbp.exportsample;version="1.0.0"</Export-Package>

Exports the 1.0.0 version of the package org.wso2.mbp.exportsample.

<Import-Package>org.wso2.mbp.exportsample.*;version="[1.0.0, 2.0.0)"</Import-Package>

This instruction would allow a bundle to import the package org.wso2.mbp.exportsample within the specified version range.

There are several sets of samples that demonstrate the aforementioned instructions.  Sample02 and sample03 export the 1.0.0 and 2.0.0 versions of the org.wso2.mbp.exportsample.* packages, respectively. Sample04 and sample05 import the 1.0.0 and 2.0.0 versions of the org.wso2.mbp.exportsample.* packages, respectively. Both sample02 and sample03 export the class Exporter.

package org.wso2.mbp.exportsample;

//Version 1.0.0 of the Exporter class, exported from sample02
public class Exporter {
    public void sayHello(){
        System.out.println("Hello, I am version 1.0.0");
    }
}

package org.wso2.mbp.exportsample;

//Version 2.0.0 of the Exporter class, exported from sample03
public class Exporter {
    public void sayHello(){
        System.out.println("Hello, I am version 2.0.0");
    }
}

Both sample04 and sample05 have BundleActivators and inside those Activators, Exporter.sayHello() method is invoked. Install and start these 4 bundles in Equinox framework and observe the results.

osgi> ss
Framework is launched.

id    State      Bundle
0    ACTIVE      org.eclipse.osgi_3.4.0.v20080605-1900
1    INSTALLED   org.wso2.mbp.sample02_1.0.0
2    INSTALLED   org.wso2.mbp.sample03_2.0.0
3    INSTALLED   org.wso2.mbp.sample04_1.0.0
4    INSTALLED   org.wso2.mbp.sample05_1.0.0

osgi> start 1 2 3 4
Hello, I am version 1.0.0
Hello, I am version 2.0.0

The "packages" command displays the export/import package details. Use this command to observe package sharing.

osgi> packages org.wso2.mbp.exportsample

org.wso2.mbp.exportsample; version="1.0.0"<file:sample02-1.0.0.jar [1]>
  file:sample04-1.0.0.jar [4] imports

org.wso2.mbp.exportsample; version="2.0.0"<file:sample03-2.0.0.jar [2]>
  file:sample05-1.0.0.jar [3] imports

According to the results of the packages command, you can clearly see that the sample04 imports the 1.0.0 version of the org.wso2.mbp.exportsample package exported by sample02, and the sample05 imports the 2.0.0 version of the package exported by sample03. These samples demonstrate the power of OSGi. Now you can start to manage and use multiple versions of a package in your application.

Summary

This tutorial has demonstrated how to build an OSGi bundle using bundle plugin. In addition, several important bundle plugin instructions and their behaviors have been introduced.  The next tutorial in this series will dive deeper into other important bundle plugin instructions such as Include-Resource and Embed-Dependency, which allow you to copy resources other than classes and embed project dependencies inside your bundle.

References

  1. OSGi Alliance
  2. Maven - Apache build manager for Java projects
  3. Apache Felix - Maven Bundle Plugin
  4. BND - Bundle Tool
  5. Samples for this tutorial
  6. Introduction to the POM
  7. Equinox
  8. Equinox OSGi downloads

Author

Sameera Jayasome is a Software Engineer at WSO2 Inc. sameera at wso2 dot com

 

About Author

  • Sameera Jayasoma
  • Senior Director, Platform Architecture
  • WSO2.