Knopflerfish 6.1.0
Contents
Release Notes
License
Getting Started
Installing
Running
Building
Programming
Testing
Developer's Doc
Tutorials
Java API docs
Framework and Bundle docs
Bundle jar docs
www.knopflerfish.org
Bug tracker
Bundle repository
Source (git)
Eclipse plugin
Maintained by Makewave
Knopflerfish Pro
Professional Services
Training

OSGi for Business Use

Programming Knopflerfish

Contents

  1. Using Eclipse to create bundles
  2. Using Ant to create bundles
  3. Using Maven to build bundles
  4. The BundleActivator
  5. OSGi Service tutorial
  6. Bundle data access
  7. Win32 tips
  8. Optimizing startup time, memory or disk usage
  9. Details on the included ant build system
  10. Using the GitHub repository

Using Eclipse to create bundles

See the Knopflerfish Eclipse Plugin documentation on the Knopferfish web site.

Using Ant to create bundles

Prerequisites

Creating a new bundle

New bundles can be created using the included ant build system. Naturally, you can create bundles manually, but this requires some knowledge about the OSGi manifest format.

knopflerfish refers to the unpacked knopflerfish directory, typically knopflerfish.org

Using the included ant build system to create a new bundle

1. Create a directory for the new bundle
> cd knopflerfish/osgi/bundles
> mkdir mybundle
      
2. Copy the ant bundle build template
> cp knopflerfish/ant/build_example.xml build.xml
      
3. Change the project name in the build.xml file

Set the impl.pattern and api.pattern properties to the packages that contains implementation and API code.

Depending on where you have put your new bundle, you might have to modify the path to bundlebuild.xml in the <import file="${proj.dir}/../../../ant/bundlebuild.xml"/> element at the end of the build-file.

For details on bundle generation, see knopflerfish/ant/bundlebuild.xml

<project name="mybundle" default="all">
...
<property name  = "impl.pattern"
          value = "example/mybundle/impl/**"/>
..
<property name  = "api.pattern"
          value = "example/mybundle/*"/>

      
4. Create the java source code in src directory.
> mkdir -p src/example/mybundle/impl
      
Example BundleActivator: src/examples/mybundle/impl/Activator.java
package example.mybundle.impl;

import org.osgi.framework.BundleActivator;

public class Activator implements BundleActivator {
  ...
  public void start(BundleContext bc) {
    ...
  }
  public void stop(BundleContext bc) {
    ...
  }
      

5. Compile the bundle.

The ant build system will automatically find the BundleActivator, imports and exports and generate a bundle manifest file.

The resulting file will be generated to knopflerfish/osgi/jars/mybundle/mybundle.jar

Intermediate build results will be placed in knopflerfish/osgi/out/mybundle

> ant
      

6. Install the bundle.

This can be done either by using the text console, dragging the bundle using the Swing desktop UI or by modifying the framework startup file init.xargs

Using the console to install and start the bundle.
> install file:jars/mybundle/mybundle.jar
> start mybundle.jar
      
Drag a bundle into the Desktop UI to install and start the bundle.
7. Using ant commands and telnet console to install/start/stop/update bundles.

The ant build has support for communicating to a running framework and install, start, stop, update and uninstall bundles.

Given that the consoletelnet bundle is running, you can simply use these built-in targets to handle the life cycle of the bundle.

The properties console.host, console.port, console.user and console.pwd controls the details of the console connection.

Note: requires netcomponents.jar in $ANT_HOME/lib. Needs to be separately downloaded from http://www.savarese.org/oro/downloads

> ant install
to install the compiled bundle. Similarly, type
> ant start
to start the bundle. Type
> ant -projecthelp
to see all available targets.
top

Using Maven to build bundles

Prerequisites

Knopflerfish Releases Repository

To simplify building bundles that depends on OSGi or Knopflerfish APIs with Maven Knopflerfish offers a Maven 2 repository with all released artifacts (framework and bundles). This Maven2 repository id called "Knopflerfish Releases Repository" and can be found at
http://www.knopflerfish.org/maven2/
It contains all artifacts from Knopflerfish release builds starting with 2.3.3, 2.4, 3.0, 3.1, ...

To make it easy to find artifacts that belongs to the same Knopflerfish release the repository contains an xml-document for each release that lists all artifacts with group id, artifact id and version. These documents are located in
http://www.knopflerfish.org/maven2/org/knopflerfish/
and are named like KF-VERSION_dependencyManagement.xml.
E.g., the file with URL http://www.knopflerfish.org/maven2/org/knopflerfish/KF-3.1.0_dependencyManagement.xml will list all artifacts from the Knopflerfish 3.1 release.

Each nightly build and release build (as of Knopflerfish 3.2) will also have a build specific Maven2 repository that contains exactly those artifacts that belongs to the build. The URL of these repositories are on the form
http://www.knopflerfish.org/snapshots/current_trunk/maven2/
http://www.knopflerfish.org/releases/3.2.0/maven2/

A sample POM file for building a simple bundle

Here is a sample POM-file that may be used to build a simple bundle. Following Maven conventions the source code of the bundle should be in the subdirectory named src/main/java, resources should be in src/main/resources.
<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>org.knopflerfish.examples</groupId> 
  <artifactId>example1</artifactId> 
  <version>1.0</version> 
  <packaging>bundle</packaging>

  <name>Example 1</name>
  <description>A bundle that exports a package.</description>

  <properties>
    <bundle.namespace>${pom.groupId}.${pom.artifactId}</bundle.namespace>
  </properties>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <extensions>true</extensions>
        <configuration>
          <instructions>
            <Export-Package>${bundle.namespace}</Export-Package>
            <Private-Package>!${bundle.namespace}</Private-Package>
          </instructions>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <repositories>
    <repository>
      <releases>
        <enabled>true</enabled>
        <updatePolicy>never</updatePolicy>
        <checksumPolicy>fail</checksumPolicy>
      </releases>
      <snapshots>
        <enabled>false</enabled>
        <updatePolicy>never</updatePolicy>
        <checksumPolicy>fail</checksumPolicy>
      </snapshots>
      <id>Knopflerfish</id>
      <name>Knopflerfish Releases Repository</name>
      <url>http://www.knopflerfish.org/maven2</url>
      <layout>default</layout>
    </repository>
  </repositories>
  <dependencyManagement>
    <dependencies> 
      <!-- KF 3.1.0 -->

      <!--framework.jar-->
      <dependency>
        <groupId>org.knopflerfish</groupId>
        <artifactId>framework</artifactId>
        <version>5.1.6</version>
      </dependency>

      <!--log/log_api-3.0.5.jar-->
      <dependency>
        <groupId>org.knopflerfish</groupId>
        <artifactId>log-API</artifactId>
        <version>3.0.5</version>
      </dependency>

      <!--cm/cm_api-3.0.1.jar-->
      <dependency>
        <groupId>org.knopflerfish.bundle</groupId>
        <artifactId>cm-API</artifactId>
        <version>3.0.1</version>
      </dependency>

    </dependencies> 
  </dependencyManagement>
</project>
Note: The <repository>-element is normally placed in the top-level parent POM or in the settings-file. In this example it has been added to the bundles POM-file to keep it all in one file. The same applies to the <dependencyManagement>-element.

Coding Style guides

When writing OSGi bundles, all normal java guidelines apply, but some things might be worth noting. The list below is intended to give some hints on writing stable bundles, but feel free to use your own judgment.

The BundleActivator

The BundleActivator can be considered your application's main class. A bundle which is expected to start, register or use other service, must have a BundleActivator implementation and a reference to the BundleActivator's class name in its manifest file.
Naming the BundleActivator
Preferably name the BundleActivator class Activator. This makes it easy to find for other developers.
public class Activator implements BundleActivator {
  ...
}
Use package names

Do use normal package name conventions. One convention is to suffix the implementation parts of bundle with .impl

However, the implementation parts can theoretically have any package name you want, since the framework keeps separate name spaces for separate bundles.

// This is the not exported implementation part of a bundle
package com.acme.osgi.test.impl;

// from the bundle's API part
import com.acme.osgi.test.TestService;

public class Activator implements BundleActivator {
  TestService testService = ...
  ...
}
Store the BundleContext
You can use a static BundleContext in the Activator class.
There is really no need for passing around bc by parameter to all internal classes. The same trick should be used for other singular common objects as the log service tracker.
// Static bundle context
public class Activator implements BundleActivator {

   static BundleContext bc;

   public void start(BundleContext bc) {
     Activator.bc = bc;
     ...
   }

   public void stop(BundleContext bc) {
     Activator.bc = null; // allow for garbage collection
     ...
   }
}
Automatic cleanup of services
There is no need to unregister services in the stop() method. This is done automatically by the framework.
However, static variables should be nulled to allow for garbage collection.
public void stop(BundleContext bc) {
  Activator.bc = null;
}
No automatic cleanup of memory, threads, windows etc
Do deallocate any other resources as threads or (gah!) Swing windows. They are not stopped or closed automatically.
// Cleanup up resources in stop method
public void stop(BundleContext bc) {
  Activator.bc = null;
  if(window != null) {
    window.setVisible(false);
    window = null;
  }
}
Beware of re-using service object
Beware of re-using an object for multiple service registrations.
If you register a public class with one or more public methods, these public methods will become available to all other bundles if they have get permission on the service. Instead, make sure you only implement public methods which are members of the registered service's interface.

A more compact method than multiple interfaces, is to use anonymous inner classes.

Common mistake of re-using the activator as service implementation:
import org.osgi.framework.BundleActivator;
import org.osgi.service.cm.ManagesService;

public class Activator
       implements BundleActivator, ManagedService {

  // implements BundleActivator
  public void start(BundleContext bc) {

    Hashtable props = new Hashtable();
    props.put("service.pid", "myfantasticpid");

    // We can register ourselves as a ManagedService,
    // This is formally OK, but a service
    // that can get this service (as a ManagedService),
    // can also call start() and stop() using
    // reflection trickery or casting. Which hardly
    // was the intention of the bundle programmer.
    bc.registerService(ManagedService.class.getName(),
                       this, props);

  }

  // implements BundleActivator
  public void stop(BundleContext bc) {
    ...
  }

  // implements ManagedService
  // should be avoided.
  public void updated(Dictionary conf) {
    ...
  }
}
Better variant, using anonymous inner class:
public class Activator implements BundleActivator {

  public void start(BundleContext bc) {

    Hashtable props = new Hashtable();
    props.put("service.pid", "myfantasticpid");

    ManagedService mg = new ManagedService() {
      public void updated(Dictionary conf) {
        ...
      }
    };

    // BundleActivator methods now hidden
    // from outside access.
    bc.registerService(ManagedService.class.getName(),
                       mg, props);

  }
  ...
}


Spawning a startup thread
Do not hang in the Activator. Spawn a new thread if the bundle is not completely event-driven.

Nothing about threads is really specified or forbidden in the OSGi spec, so there is currently no need for any special, external thread service.

  public void start(BundleContext bc) {
    new Thread("longTaskName") {
      { start(); } // I like instance initializer blocks ;)
      public void run() {
         ...long operation
      }
    };
  }
Setting the context classloader

Many external libraries, like most JNDI lookup services requires a correctly set thread context classloader. If this is not set, ClassNotFoundException, or similar might be thrown even if you have included all necessary libs.

To fix this, simple spawn a new thread in the activator and do the work from that thread. If you use the lib in any callback methods from the framework, as ServiceListener or BundleListener you should do a similar trick inside of these listeners.

It is not recommended to set the context class loader persistently on the startup thread, since that thread might not be unique for your bundle. Effects might vary depending on OSGi vendor. If you don't spawn a new thread, you must reset the context class loader before returning.

public class Activator implements BundleActivator {
   public void start(BundleContext bc) {

     final ClassLoader classLoader =
        getClass().getClassLoader();

     Thread thread = new Thread() {
       public void run() {
         Thread.currentThread()
           .setContextClassLoader(classLoader);
         ...
         // do any work that uses the context
         // class loader, like :
         context.lookup("QueueConnectionFactory");
       }
     };

     thread.start();
   }
Thanks to Rob Evans for this example.
top

Using services

This is short introduction to service usage, to learn more about it read the service tutorial.

Track all services!

Be prepared that services might not exists, or suddenly disappear.

Use the ServiceTracker if you are interested in using a single service.

  ServiceTracker logTracker =
    new ServiceTracker(bc,
                       LogService.class.getName(),
                       null);
  logTracker.open();

  // this might throw a NullPointerException
  // if the log is not available
  (LogService)logTracker
    .getService().log(LogService.LOG_INFO,
                      "Hello log");

Track service using ServiceListener
If you really need to act on multiple service appearing and disappearing, a ServiceListener is preferred.

Act on every http service that appears and disappears.
  ...
  ServiceListener listener = new ServiceListener() {
    public void serviceChanged(ServiceEvent ev) {
     ServiceReference sr = ev.getServiceReference();

      switch(ev.getType()) {
        case ServiceEvent.REGISTERED:
          ...
          break;
        case ServiceEvent.UNREGISTERING:
          ...
         break;
      }
    }
  };

  String filter =
   "(objectclass=" + HttpService.class.getName() + ")";

  try {
    bc.addServiceListener(listener, filter);

    // get all already registered services.
    ServiceReference[] srl =
      bc.getServiceReferences(null, filter);

    for(int i = 0; srl != null && i < srl.length; i++) {
      listener.serviceChanged(
          new ServiceEvent(ServiceEvent.REGISTERED,
          srl[i]));
    }
  } catch (Exception e) {
     log.error("Failed to set up listener for http",
               e);
  }
top

Bundle data access

Access bundle data as streams
You can access bundle data using the getResourceAsStream method
// Get the bundle's manifest file using the
// bundle class loader
InputStream in =
  getClass()
   .getResourceAsStream("/META-INF/MANIFEST.MF");
..
Access bundle data as URLs
You can access bundle data using the bundle: URL syntax.
 // Get the bundle's raw manifest file using an URL
 // Syntax: (jar number represents internal jars,
 // where 0 is the top level jar)
 //  "bundle://<bundle id>:<jar number><path>"
 String s =
   "bundle://" + bc.getBundle().getBundleId() + ":0" +
   "/META-INF/MANIFEST.MF";

 URL url = new URL(s);

 InputStream in = url.openStream();

 ..
Bundles must be refreshed after an update
Bundle update does not automatically release all wired capabilities (exported packages).

This means that a bundle that has a wire from a provided capability (e.g., exported package) to another bundle requiring that capability needs to be explicitly refreshed before that capability (e.g., classes in the exported package) is released.

If you happen to have exported the package that your BundleActivator resides in, this could mean that the "old" class will be used and not the newly installed one.

Both the console and desktop provides means of calling refresh:

Console:

 > /framework refresh

Desktop:
Bundles -> Refresh bundle packages
or CTRL + R
or press the refresh link in the wiring displayer on a capability that is pending removal on refresh.

Both of these methods uses the org.osgi.wiring.FrameworkWiring API to refresh bundles:

  // refresh all bundles
  frameworkWiring.refreshBundles(null)
top

Win32 tips

Using the Java Media framework
The Java Media Framework 2.1 is tricky to install. If you fail to install properly it may still work, but start very slowly.
  1. Install all the .dlls and jars in the JRE runtime ext/ directory.
    Common installation directory:
    C:\Program Files\JavaSoft\JRE\1.3\lib\ext\
        
  2. This is not enough, copy the file jmutils.dll to the JRE runtime bin/ directory.
    Common installation directory:
    C:\Program Files\JavaSoft\JRE\1.3\bin\
        

Details on the included ant build system

Properties and elements used in bundlebuild_include.xml
Name Description
Files and directories
topdir Must be set to the top directory of build. This should be the same directory where framework.jar is located.
ant.dir Directory containing ant build files.
Default is "${topdir}/../ant"
src.dir Directory where all bundle source is located.
Default is "src"
resources.dir Directory where all bundle data is located. This directory will be copied into the root of the bundle jar file, without the directory name itself.
Default is "resources"
bundle.build.api If "true", build ${jar.api} bundle jar.
Default is "true"
bundle.build.lib If "true", build ${jar.lib} bundle jar.
Default is "false"
bundle.build.impl If "true", build ${jar.impl} bundle jar.
Default is "true"
bundle.build.all If "true", build ${jar.all} bundle jar.
Default is "true"
jar.all Location and name of jar file containing both API and implementation code
Default is ${jardir}/${ant.project.name}${all.suffix}.jar
api.all Location of jar file containing API code
impl.all Location of jar file containing implementation code
all.suffix Suffix string for the ${all.jar} output file
Default is _all-${bundle.version}
Bundle manifest attributes
bundle.name Name of bundle. Will be stored in manifest as "Bundle-Name".
Default value is ${ant.project.name}
bundle.version Version of bundle. Will be stored in manifest as "Bundle-Version".
Default value is "current"
bundle.vendor Vendor of bundle. Will be stored in manifest as "Bundle-Vendor".
Default value is "Knopflerfish"
bundle.apivendor Vendor of a bundle's API. Will be stored in manifest as "Bundle-APIVendor".
Default value is "[bundle.emptystring]"
bundle.description Description of bundle. Will be stored in manifest as "Bundle-Description"
Default value is empty
bundle.icon Optional URL to bundle icon, used by the desktop. Will be stored in manifest as "Application-Icon".
Default value is empty
bundle.classpath Bundle classpath. Will be stored in manifest as "Bundle-Classpath".
Default value is "."
bundle.docurl Bundle doc URL. Will be stored in manifest as "Bundle-DocURL".
Default value is "http://www.knopflerfish.org"
bundle.contactaddress Bundle contact address. Will be stored in manifest as "Bundle-ContactAddress".
Default value is "http://www.knopflerfish.org"
bundle.activator Class name of bundle activator. Will be stored in manifest as "Bundle-Activator".
Default value is automatically derived from bundle classes in impl code.
import.package Comma-separated list of packages. Will be stored in manifest as "Import-Package".
Default value is automatically derived from bundle classes in impl code
export.package Comma-separated list of packages. Will be stored in manifest as "Export-Package".
Default value is automatically derived from bundle classes in API code
dynamicimport.package Comma-separated list of dynamic import packages. Will be stored in manifest as "DynamicImport-Package".
Default value is empty
bundle.nativecode Comma-separated list of native code specifications. Will be stored in manifest as "Bundle-NativeCode".
Default value is empty
import.service Optional comma-separated list of service class names. Will be stored in manifest as "Import-Service".
export.service Optional comma-separated list of service class names. Will be stored in manifest as "Import-Package".
bundle.uuid Optional "Universally Unique ID" for a bundle. Will be stored in manifest as "Bundle-UUID".
Default is org.knopflerfish:${bundle.name}:${bundle.version}
bundle.symbolicname Optional ID used by the Eclipse OSGi framework. Will be stored in manifest as "Bundle-SymbolicName".
Default is ${bundle.uuid}
Note: If any of the manifest attributes above are set to the special value "[bundle.emptystring]", that attribute will not be present at all in the manifest, not even as a zero-length string.
Flow control
do.bundle.custom.pre If set, run the target bundle.custom.pre. Default is unset
do.bundle.custom.post If set, run the target bundle.custom.post. Default is unset
Build control
api.pattern Path pattern string that must match all Java packages that are part of the bundle's API (i.e., that shall be exported). Note that this pattern must not only match all packages in the source tree, but also all packages that shall be exported from the bundle class path (i.e., in nested jars).

The "API"-variant of the bundle will only contain classes that is the result of compiling those java-source files that matches this pattern from the source tree.

impl.pattern Path pattern string that must match all private (implementation specific) Java packages in the source tree that shall be included in the bundle but not exported.

The "IMPL"-variant of the bundle will contain classes that is the result of compiling those java-source files that matches any of the impl.pattern and impl-api.pattern.

impl-api.pattern Path pattern string that must match all Java packages in the source tree and on the bundle class path that shall be included in and exported from the "IMPL" variant of the the bundle but not in the "API"-variant of the bundle. Typically used when the implementation part of the bundle needs to export implementation specific Java packages that is not part of the bundles API.
all.pattern Path pattern string that must match additional private (implementation specific) Java packages in the source tree that shall be included in the all-variant of the bundle but not exported.

The "ALL"-variant of the bundle will contain classes that matches any of the api.pattern, impl.pattern, impl-api.pattern, all.pattern and all-api.pattern.

all-api.pattern Path pattern string that must match additional Java packages that shall be included in and exported from the "ALL" variant of the the bundle but not in the "API"-variant or "IMPL"-variant of the bundle. Typically used to make a self contained "ALL"-variant of the bundle that exports all Java-packages that the "IMPL"-variant needs.

The "ALL"-variant of the bundle will export classes in packages that are matched by one of api.pattern, impl-api.pattern and all-api.pattern. It will contain all classes that are matched by any of api.pattern, impl-api.pattern, impl-pattern, all-api.pattern and all.pattern.

bundle.compile.EE The bundle Execution Environment to use when compiling. The value is the id of a path that will be used as boot class path during the compilation.

Predefined values: ee.minimum, ee.foundation.
Default is unset.

bundleinfo.failOnExports If set, verify that the given Export-Package manifest header is valid. If not valid the build will fail.
Default is true.
bundleinfo.failOnImports If set, verify that the given Import-Package manifest header is valid. If not valid the build will fail.
Default is true.
bundleinfo.failOnActivator If set, verify that the given Bundle-Activator manifest header is valid. E.g., that the class is included in the bundle archive and that it implements org.osgi.framework.BundleActivator. If not valid the build will fail.
Default is true.
bundleinfo.failOnClassPath If set, verify that the given Bundle-Classpath manifest header is valid. E.g., that all files, directories in it existis inside the bundle archive. If not valid the build will fail.
Default is true.
Other elements
Path element bundle.compile.path

This path is used to make other bundles and JAR-files that the bundle depends on availble to the compiler during the compilation of the bundle.

Example - make the log API available during compilation:

 <path id = "bundle.compile.path">
   <pathelement location="log_api-N.N.N.jar"/>
 </path>

The N.N.N notation in the file name tells the build system to look for and use the highest version available in the directory tree starting at ${jars.dir}. It is possible to ask for the highest version available with a fixed major revision by writing a pathelement like log_api-2.N.N.jar.

Special pre and post target

If you need special handling of the bundle build process, you can add the special targets bundle.custom.pre or bundle.custom.post. These will be run just before and just after the standard bundle build.
Example of pre target - copying a file
<!-- set do.bundle.custom.pre to anything to trigger pre target -->
<property name="do.bundle.custom.pre" value=""/>
<target name="bundle.custom.pre">
   <copy file  = "myfile"  todir = "resources"/>
  </target>

Example of post target - a custom manifest attribute
<!-- set do.bundle.custom.post to anything to trigger post target -->
<property name="do.bundle.custom.post" value=""/>

<target name="bundle.custom.post">
  <jar jarfile  = "${impl.jar}"  update   = "true">
   <manifest>
    <attribute name = "myattribyte"       value = "myvalue"/>
   </manifest>
  </jar>
</target>

Optimizing startup time, memory or disk usage

The Knopflerfish framework can be started in either disk-optimizing or memory-optimizing modes. This is useful for systems with lower memory or small or slow disks. It also affects the startup time for the framework.

The following apply:
  1. If you need quick startup, remember that initial startup is always much slower than a restart, so a avoid deleting the fwdir bundle storage directory, if possible.
  2. Jar unpacking to disk costs time and disk space. When you have a slow write-disk (as flash mem) this is really notable.

    As a solution, memory can be used as bundle storage instead of disk. Just start with

         -Dorg.knopflerfish.framework.bundlestorage=memory
    

    This has the side-effect of not making the bundles persistent, so the 1) case does no longer apply. Also, this means that the one part of OSGi compliance is lost.

  3. Jar-files inside of bundles tend to take longer time than unpacked class files. This is related to 2) since they must be extracted from the bundle to be accessed.

    A work-around is to manually unpack a lib jar file and add it to a bundle (instead of referencing the .jar from Bundle-Classpath)