Writing Bundles

This section discusses bundle writing. The OSGi Service Platform Core Specification defines the main structural elements that a bundle must contain. This document introduces you to the main steps of how to create your own bundles.

Note: It is assumed that you have basic knowledge of the Java programming language and you are acquainted with the OSGi Framework Specification.

Contents:


Overview of the Bundle Development Process

The paragraphs below discuss the steps that a bundle developer can follow to create a bundle for an OSGi framework. Note that this is only a short guide for developing and running OSGi bundles.

A bundle can provide some functionality to other bundles in the form of services. In such case you go through all steps below.

If your bundle contains Java code, for example for starting and stopping threads, but this code is not intended to be used by other bundles as a service, you can ignore steps 1 and 2.

A bundle can simply export a library to other bundles in the framework - it does not need a Bundle Activator nor should it register services. Hence, you may skip steps 1, 2 and 3.

Step Description
1 Create a service interface.
2 Create a service interface implementation.
3 Create the Bundle Activator.
4 Compile the bundle Java source code.
5 Write a manifest file.
6 Generate a bundle JAR file.
7 Install and start the bundle.
8 Manage and reconfigure the bundle.

The figure that follows illustrates the stages of the bundle development process.

Developing Bundles Step 1 Step 2 Step 3 Step 4 Step 6 Step 5 Step 5 Step 6 Step 7

Figure 1: Process for developing bundles.

Development Stages


Step 1. Create the Service Interface

Bundle developers register and access a service in a bundle through the service interface. The service interface defines the methods that the consumers of the service can call.

To clarify, consider the Test service interface. Test declares a single method, showHello. The service interface, in particular its Java package, is exported to other bundles through the Export-Package header in the bundle manifest file.

  package bundles.test.serv;
  public interface Test {
  public void showHello();
}
Listing 1: Defining the service interface.

Development Stages


Step 2. Create the Service Interface Implementation

You must provide an implementation of the service interface. Later, you should provide an instance of the implementation class when registering the service in the framework.

The Test interface implementation is the TestImpl class. The showHello method simply prints a string in the system output. Since the Test interface is small, there are no additional classes in this implementation.

  package bundles.test.serv; 
  public class TestImpl implements Test { 
public void showHello() {
  System.out.println("Hello framework!");
   }
}
Listing 2: Implementing the service interface.

Development Stages


Step 3. Create the Bundle Activator

The Bundle Activator object implements the org.osgi.framework.BundleActivator interface. In the start/stop method of the Bundle Activator, the programmer encloses the operations that the bundle executes when started/stopped. Such operations are:

If the operation mode of your bundle does not require any special settings at startup, such as service registration or consumption, it is not necessary to have a Bundle Activator.

Registering a Service

After you provide the service interface and its implementation, in the body of the BundleActivator.start() method you can register the service with the framework. For this purpose, you use the org.osgi.framework.BundleContext object passed by the framework. Of course, you can register the service in another bundle class.

You register a service under the class name of the interface. You pair this class name with an instance of the interface implementation (the so-called service object).

Listing 3.1. contains the TestActivator class that registers a service under the Test interface class name. TestActivator provides a service object, implementing the Test interface (a TestImpl instance).

package bundles.test.serv;
 
import java.util.*; 
import org.osgi.framework.*;
  public class TestActivator implements BundleActivator {
// A class field
ServiceRegistration servReg;

public void start(BundleContext bc) throws BundleException {
try {
TestImpl servImpl = new TestImpl();
Hashtable properties = new Hashtable();
properties.put("description", "simple test service");
servReg = bc.registerService("bundles.test.serv.Test", servImpl, properties);
} catch (Exception exc) {
throw new BundleException(exc.getMessage(), exc);
}
}

public void stop(BundleContext bc) throws BundleException {
  servReg.unregister(); 
  }
}
Listing 3.1: Registering a service using its implementation.

You can send a specific service object to each requesting bundle by means of a Service Factory. A Service Factory must implement the org.osgi.framework.ServiceFactory interface. This interface defines the getService and ungetService methods. The getService method is invoked by the framework the first time the specified bundle requests a service object by using the BundleContext.getService(ServiceReference) method. The ungetService method is invoked by the framework when a service has been released by a bundle.

The example in this guide defines a Service Factory called TestFactory . The getService method extracts the ID of the requesting bundle and prints it to the system output. At each invocation of the service, a new service object is created and subsequently passed to the requesting bundle. The ungetService method has an empty implementation for conciseness.

  package bundles.test.serv; 
 
import org.osgi.framework.*;
 
public class TestFactory implements ServiceFactory {
  public Object getService(Bundle requester, ServiceRegistration servReg) {
  long requesterId = requester.getBundleId();
System.out.println("The Id of the requesting bundle is: " + requester);
return new TestImpl();
}
 
   public void ungetService(Bundle requester,
ServiceRegistration servReg,
Object service) {
   }
}
Listing 3.2: Making a service factory.

To activate a Service Factory, you pair the service interface with the Service Factory object at service registration instead of with the service interface implementation.

In the example below, the TestActivator class registers a service using TestFactory.

                       . . .
try {  
ServiceFactory factory = new TestFactory();
Hashtable properties = new Hashtable();
properties.put("Description", "Simple test service");
servReg = bc.registerService("bundles.test.serv.Test", factory, properties);
 } catch(Exception exc) {
   throw new BundleException(exc.getMessage(), exc);
  }
. . .
Listing 3.3: Registering a service using a service factory.

Obtaining and Releasing a Service

If you need to consume services found in other bundles running in the framework, you can refer them with the BundleContext object assigned to your bundle. First, you should call the BundleContext.getServiceReference() method to obtain org.osgi.framework.ServiceReference for the target service. Next, you can retrieve the service object with BundleContext.getService passing the ServiceReference object as the argument. To release a service object, use BundleContext.ungetService.

You can get the class name(s) under which a service is registered using ServiceReference.getProperty method with the "objectClass" key.

The AnotherActivator class below is a Bundle Activator that uses the BundleContext objects in its start and stop methods to retrieve and free the Test service discussed in this document. It is assumed that the Test service is in a bundle, which is running in the framework.

  import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import bundles.test.serv.Test;

public class AnotherActivator implements BundleActivator {
  ServiceReference servRef;
Test serv;
 
public void start(BundleContext bc) { 
servRef = bc.getServiceReference("bundles.test.serv.Test");
serv = (Test) bc.getService(servRef);
serv.showHello();
}

public void stop(BundleContext bc) {
bc.ungetService(servRef);
}
}
Listing 3.4: Retrieving and releasing a service object.

Troubleshooting:

Development Stages


Step 4. Compile the Java Source Code

To produce class files, which the target Java virtual machine will launch, you should naturally compile the bundle's java source files. You can include in the classpath the lib/frameworklib.jar file, which holds the framework, service and utility APIs, related to the mBS Framework Professional Edition Package .

Assuming that the java files are placed in the bundles/test/serv directory, the compilation command using the JDK compiler can be:

javac -classpath .;../../../lib/frameworklib.jar bundles/test/serv/*.java

Development Stages


Step 5. Write the Manifest File

The bundle developer decides what properties should be put in the manifest file. Bundle properties are reviewed in the OSGi Service Platform Core Specification document. Consider these bundle properties and their values:

Manifest-Version: 1.0
Bundle-Vendor: ProSyst Software
Bundle-Version: 1.6
Bundle-Activator: bundles.test.serv.TestActivator
Bundle-DocURL: http://www.prosyst.com
Bundle-Name: Test Bundle
Export-Package: bundles.test.serv
Export-Service: Test Service
Bundle-SymbolicName: bundles.test.service
Bundle-ManifestVersion: 2
Listing 4: An example manifest file.

Tip: For more information about bundle manifest headers, refer to the OSGi Service Platform Core Specification. You can get more help about manifest files in JARs from Sun Microsystems.

Note: It is best to include all interfaces defined in your bundle and export those packages.

Troubleshooting:

Development Stages


Step 6. Generate the Bundle JAR File

You can generate the bundle JAR file using the jar command of the JDK. Assuming that the class files and the manifest file are located in bundles/test/serv, the command can be:

jar cmf bundles/test/serv/manifest.mf testbundle.jar bundles/test/serv/*.class

Troubleshooting: If the bundle JAR does not include the Bundle Activator specified in the manifest, the framework will throw a ClassNotFoundException at bundle startup.

Development Stages


Step 7. Install and Start the Bundle

To activate the bundle, you must install and start it on the OSGi framework, for example by using the Text Console (see "Text Console" and "Framework Commands") or the mConsole (see "OSGi Framework Administration through mConsole" ).

Development Stages


Step 8. Manage and Reconfigure the Bundle

After running the bundle on the ProSyst mBS, you can manage the bundle life cycle by means of console or visual administration.

Troubleshooting: Usually, defining a synchronized method improperly may bring a deadlock situation and cause the server framework to hang. To achieve greater efficiency, most of the framework job is done within the same thread as the requester that caused the deadlock. You can decrease deadlocks if you do not refer the framework in synchronized methods.

Development Stages


Appendix: Using Native Code Libraries in a Bundle

This section describes how to invoke native methods in bundles. It assumes basic knowledge on the OSGi Framework Specification and Java Native Interface (JNI).

Basically, using JNI in OSGi bundles is the same as in standalone applications. The only difference is the additional information that needs to be added to the bundle manifest file.

We'll use the source code of the Native Demo to illustrate the steps, described further in this section.

Step A1. Write and Compile the Java Source Code

Bundles can use native methods in the conventional way, defined for Java applications. Basically, before invoking a native method, you should declare it with the native keyword and statically load the appropriate library with System.loadLibrary.

package demo.nat;       
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
public class NativeDemoActivator implements BundleActivator{
  static {
    try{
      System.loadLibrary("demonative");
    } catch (Throwable t) {
      t.printStackTrace();
    }
  }
  private native int multiply (int i, int j);
  public void start(BundleContext bc) throws Exception {
    System.out.println("Native call 6x7: " + multiply(6,7));
  }
  public void stop (BundleContext bc) {
    //does nothing
  }
}
Listing A.1: Invoking the native method from the bundle.

Step A2. Create the JNI Header File

For bundles this step is again the same as with standalone applications. Once you have compiled the bundle class file(s), run the javah JDK tool on it. As a result, a header file (with the .h extension) will be produced.

For example:

javah -jni demo.nat.NativeDemoActivator

Step A3. Write the Native Method Implementation

Write the implementation, usually in the C language, of used native methods according to the signatures, generated in the header file.

#include <jni.h>
#include "demo_nat_NativeDemoActivator.h"       
JNIEXPORT jint JNICALL Java_demo_nat_NativeDemoActivator_multiply (JNIEnv * env,
jobject obj,
jint i,
jint j){ return i*j; }
Listing A.2: Implementing the native method.

Note: Make sure the method signatures in the implementation match the ones from the generated header file.

Step A4. Compile a Native Shared Library

Through a C compiler, build a native shared library out of the header and implementation source code.

For example:

Using the LCC compiler for Windows:
lc -A -IC:\jdk1.3.1_10\include -IC:\jdk1.3.1_10\include\win32 demo_nat_NativeDemoActivator.c -dll -o libdemonative.dll *.obj
Using the GCC compiler for Linux:
gcc -I/opt/java/jdk1.3.1/include -I/opt/java/jdk1.3.1/include/linux -I. -o libdemonative.so --shared demo_nat_NativeDemoActivator.c

Step A5. Modify the Bundle Manifest File

In order for the bundle to be able to load and use the native library, the Bundle-NativeCode header needs to be added to the bundle manifest. The header syntax is as follows:

Bundle-NativeCode: <native_clause>[ , <native_clause>] *
<native_clause> ::= <native_path>[ ; native_path; ...] <env_param>; [env_param; ...]

where <native_path> is the full path to the native library within the bundle JAR, and <env_param> can be one of the following environment properties - osname, osversion, processor, or language.

For example:

Bundle-NativeCode: demonative.dll;
                   osname=WindowsXP;
                   osname=Windows2000;
                   osname=Windows95;
                   osname=Windows98;
                   osname=WindowsNT;
                   processor=x86 ,
                   libdemonative.so;
                   osname=Linux;
                   processor=x86

Tip: For more information on Bundle-NativeCode clauses, check the OSGi Service Platform Core Specification.

Environment Matching Algorithm

The framework applies the following algorithm to load the appropriate library for the environment it resides in:

Environment Property Matching Approach
osname

Indicates the target operating system. The native library will be loaded only if the OS name, obtained through the os.name system property, matches the Bundle-NativeCode one (case insensitive).

Tip: You can use the mbs.manifest.osname.ignoreSpaces to make the framework ignore the spaces, contained in the JVM-provided OS name.

osversion Indicates the target OS version. The native library will be loaded if the system's OS version, retrieved through the os.version system property, is equal to or higher than the one, set in the manifest. If this property is not specified, matching will be based on the other environment properties.
processor Provides the framework with information about the target processor architecture this library is compiled for. The match is made against the value in the os.arch system property. If there are more than one properties listed, any match will be enough.
language Shows the language being used. If the property is present in the manifest, it should match the language, set for the active platform locale. If the property is not specified, the framework will base the matching upon the other environment properties.

Mapping Names and Aliases of Operating Systems and Processors

The framework uses the OS and processor names, included in the bundle's Bundle-NativeCode manifest header, for loading the right native libraries for the runtime environment. It is possible that some Java virtual machines return OS and processor names (through standard Java system properties), different from the commercial ones, for example "Windows CE" instead of "WindowsCE". Such non-conventional names are called aliases. In such case, it may happen that a bundle's Bundle-NativeCode native clause, containing commercial names, does not match the environment properties, although they are compatible. As a result the framework won't locate and load the corresponding native code library. For this reason, the OSGi Framework Specification defines that the framework should try to include both aliases and commercial names when determining the native code library, appropriate for the currect execution environment.

It is recommended that Bundle-NativeCode clauses contain commercial names, as they have strict and unified values, while the values returned by different VMs for the characteristics of the same OS and processor may differ.

In case the used virtual machine returns an alias instead of a commercial name, for the mBS framework you can set the os.aliases and processor.aliases system properties (see System Properties) to the OS and processor commercial names, respectively.

The OSGi Framework Specification recommends the following values for OS and processor names and aliases:

Name Alias Description
OS
AIX   IBM
DigitalUnix   Compaq
FreeBSD   Free BSD
HPUX   Hewlett Packard
IRIX   Sillicon Graphics
Linux   Open source
MacOS   Apple
NetBSD   Open source
Netware   Novell
OpenBSD   Open source
OS2 OS/2 IBM
QNX procnto QNX
Solaris   Sun (almost an alias of SunOS)
SunOS    Sun
VxWorks   WindRiver Systems
Windows95 Windows 95 Microsoft
Windows98 Windows 98 Microsoft
WindowsNT Windows NT Microsoft
WindowsCE Windows CE Microsoft
Windows2000 Windows 2000 Microsoft
WindowsXP Windows XP Microsoft
Processor
68k   Motorola 68000
ARM   Intel Strong ARM
Alpha   Compaq (ex DEC)
Ignite psc1k PTSC
Mips   SGI
PArisc   Hewlett Packard PA Risc
PowerPC power ppc Motorola/IBM Power PC
Sparc   SUN
x86 pentium i386 i486 i586 i686 Intel

Step A6. Build the JAR File

You should pack the ready Java class files, shared libraries and manifest in a bundle JAR as described in Step 6. Generate the Bundle JAR File.

Note: Make sure that you have added the native library with the path, defined in the Bundle-NativeCode clause.

Step A7. Install and Start the Bundle

Finally, you can install and start the bundle (see Step 7. Install and Start the Bundle).

Note: If the used Java virtual machine supports the JDK 1.1 storage model, make sure the directory, where native code library will be extracted from the bundle JAR (given with the mbs.storage.native system property), is added to the LD_LIBRARY_PATH (for Linux and similar) or PATH (for Windows) environment variable.

Development Stages


Getting Started