Mittwoch, 8. Oktober 2008

OSGi Framework Extension as a Maven Project

In a previous post I explained how to extend an OSGi framework such that more classes are visible inside the framework from the outside world. This posting provided a very simple example to generate Framework Extension Bundle using a single file and calling the JAR tool from the command line. Here I will expand on this example by creating a Maven 2 project, which may be used in environments where Maven 2 is used for project builds.


As a first step, we need a Maven 2 project descriptor. To create this beast, we have to decide, how we build the project. I generally use the Apache Felix Maven Bundle Plugin to build my projects as OSGi bundles. This great plugin comes with support for Maven 2 packaging but also provides goals to call if the packaging functionality should not be used.

Before talking too much, let me just present a project descriptor, which I know works. After that I will explain why I did it, like I did it.


<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>ch.meschberger.sample</groupId>
<artifactId>ch.meschberger.sample.sysext</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>System Bundle Extension Sample</name>
<description>
This sample bundle extends the System Bundle export
list with the com.sun.image.codec.jpeg package such
that OSGi bundles may refer to Sun's JPEG implementation
without the OSGi framework itself to provide it in a
non-portable way.
</description>

<build>
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<forceCreation>true
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
<manifestEntries>
<Export-Package>com.sun.image.codec.jpeg</Export-Package>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>1.4.3</version>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
<configuration>
<instructions>
<Bundle-Category>sample</Bundle-Category>
<Fragment-Host>system.bundle; extension:=framework</Fragment-Host>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>


First of all, I use plain old jar packaging. This allows me to have more control over the manifest generation. The problem is, that the Bundle plugin removes any packages from the Export-Package header of the generated manifest which happen to not be present in the final JAR file. This is very good in all cases except in our case, which may of course be considered a special case.

So we use jar packaging. Still I like the wonderful setup of the Bundle plugin when it comes to setting the required OSGi manifest headers. Fortunately, the Bundle plugin allows me to generate a manifest file as if the packaging would be bundle. This is the manifest goal.

Consequently, in the sample above, packaing is set to jar and the Bundle plugin is configured to generate a manifest file with the standard headers plus the Fragment-Host header required to mark the bundle as Framework Extension Bundle. For the Jar plugin to include the generated manifest we refer to it with the manifestFile configuration element, which requires us to add the explicit Jar plugin configuration not normally needed.

Now, we have the Bundle plugin generate the standard headers, we have the Jar plugin use that manifest but still we have two issues: First the Bundle plugin does not have the Export-Package header definition, which would be useless because it would be removed anyway. And second, the Jar plugin would complain, because it has nothing to package.

The first issue is fixed by adding a manifestEntries element to the Jar plugin configuration, which lists the missing Export-Package header.

The second issue is just as easy to solve as the first: We just force the creation of the JAR file by setting the forceCreation parameter.

Unfortunately the Bundle plugin does not create the destination folder into which the generated manifest file is placed. To have this folder created, just create an empty src/main/resources folder. This causes the destination to be created.

That's it. We now just have to use Maven to build that project and we are done and can use it.

OSGi Bundles require Classes from the Environment

Imagine your great application running inside an OSGi framework such as Apache Felix or Eclipse Equinox and requiring a class, which you know is provided by the Java Platform but not provided inside the framework.

For example, you provide functionality for the general user on the Internet to submit comments or register themselves but you want to protect this functionality from robots. To do this, you include a Captcha and decided to use the simple java captcha library from SourceForge. This library requires classes from the com.sun.image.codec.jpeg package. This package is generally not made visible inside the framework.

So, what can you do ? Of course you could configure your OSGi framework to just make the package visible by configuring either the org.osgi.framework.system.packages or the org.osgi.framework.bootdelegation property accordingly. But what if you can't do that ?

Here come the so-called Extension Bundles. Extension Bundles are actually Fragment Bundles, which attach to the System Bundle and extend the framework either by providing more exports from the System Bundle or by even appending to the class loader which loaded the OSGi framework.


How can an OSGi Framework be extended ?

A framework can be extended in two ways: Either by extending the list of packages available in the framework itself by ammending the System Bundle export list or by adding classes to the framework boot class loader.

To extend the framework the bundle must have the Fragment-Host manifest header set to either the actual bundle symbolic name of the system bundle as defined by the framework vendor. Alternatively the alias of the system bundle name, which is system.bundle may be used. I prefer the latter case because it would not create vendor lock-in on the Extension Bundle.

The type of extension is indicated by the extension directive of the Fragment-Host manifest header. The directive may take either of the following values:

  • framework -- A framework extension ammends the System Bundle export list
  • bootclasspath -- The Extension Bundle jar file is added to the class path of the class loader loading the OSGi framework. As such, the classes from the Extension Bundle jar file are actually loaded outside of the OSGi framework.



Example 1: Provide the com.sun.image.codec.jpeg Package

To continue our initial example, lets see how the com.sun.image.codec.jpeg package can actually be made visible to our Captcha bundle. To do this, we just need a manifest file which we package into a standard JAR file using the jar tool.

Frist create a file -- say manifest.mf with the following contents:


Bundle-ManifestVersion: 2
Bundle-SymbolicName: ch.meschberger.sample.extension
Bundle-Version: 0.0.1
Fragment-Host: system.bundle; extension:=framework
Bundle-Name: Sample Framework Extension
Bundle-Description: Sample Bundle exporting Sun's JPEG classes
Export-Package: com.sun.image.code.jpeg


Now, create the bundle JAR file:


$ jar -cfm ch.meschberger.sample.extension-0.0.1.jar manifest.mf


That's it. After installing this bundle in your framework you can use the Sun JPEG classes as usual.


Restrictions of Framework Extension Bundles


There are a few restrictions when using Framework Extension Bundles. First of all refreshing an Extension Bundle must cause the OSGi framework to stop and be restarted. An Extension Bundle enters the INSTALLED state but may enter the RESOLVED state only at the discretion of the OSGi framework. An Extension Bundle will never be in the STARTED state, though.

Further restrctions exist for the Bundle Manifest headers. That is, the following headers are not allowed to be used in an Extension Bundle:
  • Import-Package
  • Require-Bundle
  • Bundle-NativeCode
  • DynamicImport-Package
  • Bundle-Activator
This is not generally a restriction per-se, because it is very unlikely that any of these headers is actually required by an Extension Bundle. Nevertheless, you must take care to not set any of these headers.


More Information

More information on the Extension Bundles may be found in section 3.15, Extension Bundles, of the OSGi Service Platform Core Specification which is available for download from the specification page of the OSGi Alliance.

LinkedHashMap's hidden (?) features

Recently I discovered two very nice features of the java.util.LinkedHashMap: accessOrder and removeEldestEntry(Entry). These features combined let you implement simple LRU caches in under two minutes.

accessOrder

The accessOrder flag is set when creating the LinkedHashMap instance using the LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) constructor. This boolean flag specifies how the entries in the map are ordered:


accessOrder=true

The elements are ordered according to their access: When iterating over the map the most recently accessed entry is returned first and the least recently accessed element is returned last. Only the get, put, and putAll methods influence this ordering.

accessOrder=false

The elements are ordered according to their insertion. This is the default if any of the other LinkedHashMap constructors is used. In this ordering read access to the map has no influence on element ordering.



removeEdlestEntry(Entry)

The second feature of interest is the removeEldestEntry(Entry) method. This method is called with the eldest entry whenever an element is added to the map. Eldest means the element which is returned last when iterating over the map. So the notion of eldest is influenced by accessOrder set on the map. The removeEldestElement in its default implementation just returns false to indicate, that nothing should happen. An extension of the LinkedHashMap may overwrite the default implementation to do whatever would be required:


  • If the implementation decides to remove the eldest element for any one reason, say a size limitation, it just returns true and the eldest element is removed from the map

  • The implementation may also decide to modify the map itself in some way or the other. But in this case, the implementation should return false, otherwise the eldest element will still be removed.




A simple LRU Cache

Taking the two features together, a very simple LRU Cache may be implemented in just a few lines of code:


public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int limit;
public LRUCache(int limit) {
super(16, 0.75f, true);
this.limit = limit;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return size() > limit;
}
}


The mechanism is very easy: The LRUCache(int) constructor initializes the map with the default initial size and load factor and sets the map into accessOrder mode. The removeEldestEntry just checks the current map size (after the addition of a new entry) against the limit and returns true if the limit has been reached.

A real world implementation would of course have to check and handle the limit value on the constructor.


To see a LinkedHashMap based LRU Cache in action, have a look at the BundleResourceCache.BundleResourceMap. This implements a simple entry cache to speed access to OSGi Bundle entries. To not waste memory, the size of the cache is limited.