Programming Knopflerfish
Contents
- Using Eclipse to create bundles
- Using Ant to create bundles
- The BundleActivator
- OSGi Service tutorial
- Bundle data access
- Win32 tips
- Optimizing startup time, memory or disk usage
- Details on the included ant build system
- Using the Subversion repository
- Performance test
Using Eclipse to create bundles
See Knopflerfish Eclipse Plugin.Using Ant to create bundles
Prerequisites
- JDK 1.3 or later, available from java.sun.com
- Ant 1.7 or later, available from ant.apache.org
- BCEL, available from jakarta.apache.org/bcel
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 import of bundlebuild.xml and topdir references in the build.xml file too. For details on bundle generation, see knopflerfish/ant/bundlebuild_include.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 |
> 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 xargs.init |
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 type us these built-in target 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.
|
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 persistenly on the startup thread, since that 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.
|
Using services
| 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);
}
|
Bundle data access
topWin32 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. |
|
Details on the included ant build system
Properties and elements used in bundlebuild.xml Please read comments in bundlebuild.xml to find out more details than those presented here.| Name | Description |
| Files and directories | |
| ant.dir | Directory containing the Knopflerfish ant build system.
Default is the directory where the file bundlebuild.xml is imported from. See last line of build_example.xml. |
| osgi.dir | Directory containing the Knopflerfish framework and the
jars-directory with all Knopflerfish bundles.
Default is ${ant.dir}/../osgi |
| proj.dir | Directory containing the bundle manifest template file,
bundle.manifest, if any. Also used as default value for
the bundle manifest header Built-From.
Default is . |
| topdir | The default root of the temporary out directory for
compilation output named out and also the default
root for the bundle repository directory, named
jars to place the resulting bundle in.
Default is ${osgi.dir} Setting this property to something different from the default makes it possible to maintain a local bundle repository any where you like in the file system while still compiling against the Knopflerfish bundles in the base repository ${osgi.dir}/jars. |
| jars.dir | The root directory of the local bundle repository.
Default is ${topdir}/jars |
| docs.dir | The root directory of the generated bundle documentation.
Default is ${topdir}/out/doc |
| 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 |
| out.dir | Directory where all intermediate build products are placed.
Default is ${topdir}/out/${ant.project.name} |
| jardir | The sub-directory of the local bundle repository to place
the bundles of this project in.
Default is ${jars.dir}/${ant.project.name} |
| outdocdir | The sub-directory of the local bundle documentation
directory to place the documentation for this bundle in.
Default is ${docs.dir}/${ant.project.name} |
| bundle.build.api | If "true", build API-variant of the bundle. This is a bundle
that only contains classes matching the API-pattern and with
name like name_api-version.jar.
Default is true |
| bundle.build.lib | If "true", build LIB-variant of the bundle. A LIB-bundle
typically re-exports classes from a legacy jar-file. It may
contain a bundle activator class but that is not
required. The name of the resulting bundle will be on the
form name-version.jar.
Default is false |
| bundle.build.impl | If "true", build IMPL-variant of the bundle. An IMPL-bundle
will contain the classes matching the IMPL-pattern, it must
contain a class implementing bundle activator or be a
fragment or a declarative-service client bundle. The name of
the resulting bundle will be on the form
name-version.jar.
Default is true |
| bundle.build.all | If "true", build ALL-variant of the bundle. An ALL-bundle
will conatin classes matching both the API-pattern and the
IMPL-pattern. it must contain a class implementing bundle
activator or be a fragment or a declarative-service client
bundle. The name of the resulting bundle will be on the form
name_all-version.jar.
Default is true |
| bundle.build.doc | If "true", build bundle documentation for this project.
Default is false |
| all.jar | Location and name of the ALL-variant of the bundle jar
file.
Default is ${jardir}/${ant.project.name}${all.suffix}.jar |
| api.jar | Location and name of the API-variant of the bundle jar file.
Default is ${jardir}/${ant.project.name}${api.suffix}.jar |
| impl.jar | Location and name of IMPL-variant of the bundle jar file.
Default is ${jardir}/${ant.project.name}${impl.suffix}.jar |
| lib.jar | Location and name of the LIB-variant of the bundle jar file.
Default is ${jardir}/${ant.project.name}${lib.suffix}.jar |
|
Bundle manifest attributes
All project properties with a name that starts with bmfa. will be used to create main section manifest attributes. The name of the attirbute is the name of the project property without the prefix, the value is the value of the project propery. You may also specify manifest attributes using a template manifest file, named bundle.manifest. This file is loaded and project properties are created for each main-section manifest attribute in it. The mainfest template file must not obey the strict 72-charactes per line rule of a proper manifest file. |
|
| bmfa.Bundle-ManifestVersion | The version that this bundle manifest follows. Must be "2"
for bundles that uses OSGi R4 v4.0, v4.1 features.
Default is 2 |
| bmfa.Bundle-SymbolicName | The bundle symbolic name. Mandatory when manifest
version is "2".
Default is ${domain.reverse}.bundle.${ant.project.name} |
| bmfa.Bundle-Name | Presentation name of bundle.
Default value is ${ant.project.name} |
| bmfa.Bundle-Version | Version of bundle.
Default value is "0.0.0" |
| bmfa.Bundle-Vendor | Vendor of bundle.
Default value is "Knopflerfish" |
| bmfa.Bundle-APIVendor | Vendor of a bundle's API.
Default value is empty. |
| bmfa.Bundle-Description | Description of bundle.
Default value is empty |
| bmfa.Bundle-Application-Icon | Optional URL to bundle icon, used by the desktop.
Default value is empty |
| bmfa.Bundle-Classpath | Bundle classpath.
Default value is "." |
| bmfa.Bundle-DocURL | Bundle doc URL.
Default value is "http://www.knopflerfish.org" |
| bmfa.Bundle-ContactAddress | Bundle contact address.
Default value is "http://www.knopflerfish.org" |
| bmfa.Bundle-Activator | Class name of bundle activator.
Default value is automatically derived from bundle classes in impl code. |
| bmfa.Import-Package | Comma-separated list of packages.
Default value is automatically derived from bundle classes in impl code. |
| bmfa.Export-Package | Comma-separated list of packages.
Default value is automatically derived from bundle classes in API code. |
| bmfa.DynamicImport-Package | Comma-separated list of dynamic import packages.
Default value is empty. |
| bmfa.Bundle-NativeCode | Comma-separated list of native code specifications.
Default value is empty. |
| bmfa.Import-Service | Deprecated, optional comma-separated list of service class names.
Default value is empty. |
| bmfa.Export-Service | Deprecated, optional comma-separated list of service class names.
Default value is empty. |
| bmfa.Bundle-UUID | Optional "Universally Unique ID" for a bundle. Will be stored in
manifest as "Bundle-UUID".
Default is ${domain.reverse}:${ant.project.name}:${bmfa.Bundle-Version} |
| 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 before
building the bundle project.
Default is unset |
| do.bundle.custom.post | If set, run the target bundle.custom.post after
building the bundle project.
Default is unset |
| Build control | |
| api.pattern | Path pattern string for all classes that define the bundle's API. Classes matching this pattern are included in API, ALL and LIB bundle variants and will be exported. |
| impl.pattern | Path pattern string for all classes that define the bundle's implementation. Classes matching this pattern are included in IMPL and ALL bundle variants. |
| impl-api.pattern | Path pattern string for all classes that define the bundle's internal API. Classes matching this pattern are included in IMPL and ALL bundle variants and will be exported by default. |
| ee.check.foundation | If set to true, verify build with CLDC/Foundation
standard OSGi profile.
Default is false. |
| Other elements | |
| Path element bundle.compile.path | Path elements defining the compile-time path for the
bundle. The path element location may either be a full path
or written on the form
bundleName-N.N.N.jar. In the latter case
the build system will search the bundle repositories in
"${jars.dir}" and "${osgi.dir}/jars" for a bundle with the
given name. If the serach finds more than one version of the
bundle the one with the highest version will be used.
Example - include log API when compiling <path id = "bundle.compile.path"> <pathelement location="log_api-N.N.N.jar"/> </path> |
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 - deleting a file
<!-- set do.bundle.custom.post to anything to trigger post target -->
<property name="do.bundle.custom.post" value=""/>
<target name="bundle.custom.post">
<delete file="resources/myfile">
</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 lowe memory or small or slow disks. It also affects the startup time for the farmework.
See Knopflerfish performance test for actual performance data with different methods.
The following apply:- If you need quick startup, rememeber that initial startup is always much slower than a restart, so a avoid deleting the fwdir bundle storage directory, if possible.
- 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=memoryThis 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.
- 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)



