The Maven Cookbook

1.5. Creating an OSGi Bundle with Maven

1.5. Creating an OSGi Bundle with Maven

1.5.1. Task

You need to create you own OSGi bundle project to integrate with the runtime environment created in the previous recipes.

1.5.2. Action

Run the pax:create-bundle goal. The following screen listing runs the pax:create-bundle to create a new Maven project for an OSGi bundle with a package org.sonatype.mcookbook and a name osgi-bundle. Run this command from the ~/examples/osgi/osgi-project directory:

~/examples/osgi/osgi-project $ mvn pax:create-bundle \
  -Dpackage=org.sonatype.mcookbook \
  -Dname=osgi-bundle \
  -Dversion=1.0-SNAPSHOT

Once this command has been completed, run mvn install pax:provision and then verify that the component is present by running ps at the Felix administrative console:

~/examples/osgi/osgi-project $ mvn install pax:provision
...
[INFO] Reactor build order: 
[INFO]   org.sonatype.mcookbook.osgi-project (OSGi project)
[INFO]   osgi-project - plugin configuration
[INFO]   osgi-project - wrapper instructions
[INFO]   osgi-project - bundle instructions
[INFO]   osgi-project - imported bundles
[INFO]   org.sonatype.mcookbook
..
[INFO] ------------------------------------------------------------------------
[INFO] Building org.sonatype.mcookbook
[INFO]    task-segment: [install]
[INFO] ------------------------------------------------------------------------
[INFO] [resources:resources]
[WARNING] Using platform encoding (MacRoman actually) to copy filtered resources, i.e. build is platform \
          dependent!
[INFO] Copying 1 resource
[INFO] Copying 0 resource
[INFO] [pax:compile]
[INFO] Nothing to compile - all classes are up to date
[INFO] [pax:testCompile]
[INFO] No sources to compile
...
[INFO] [install:install]
[INFO] Installing ~/examples/osgi/osgi-project/org.sonatype.mcookbook/target/\
       org.sonatype.mcookbook-1.0-SNAPSHOT.jar to \
       ~/.m2/repository/.m2/repository/org/sonatype/mcookbook/osgi-project/org.sonatype.mcookbook/\
       1.0-SNAPSHOT/org.sonatype.mcookbook-1.0-SNAPSHOT.jar
[INFO] [bundle:install]
[INFO] Parsing file:~/.m2/repository/.m2/repository/repository.xml
[INFO] Installing org/sonatype/mcookbook/osgi-project/org.sonatype.mcookbook/1.0-SNAPSHOT/\
       org.sonatype.mcookbook-1.0-SNAPSHOT.jar
[INFO] Writing OBR metadata
[INFO] ------------------------------------------------------------------------
[INFO] Building org.sonatype.mcookbook.osgi-project (OSGi project)
[INFO]    task-segment: [pax:provision] (aggregator-style)
[INFO] ------------------------------------------------------------------------

The Pax plugin bundles the new OSGi bundle into a JAR file which is deployed to the local Maven repository in ~/.m2/repository. The resulting artifact has the following identifiers:

  • groupId: org.sonatype.mcookbook.osgi-project

  • artifactId: org.sonatype.mcookbook

  • version: 1.0-SNAPSHOT

Continuing on with this particular execution of Maven, lines corresponding to this new custom OSGi bundle have been highlighted. The mvn:org.sonatype.mcookbook.osgi-project/org.sonatype.mcookbook/1.0-SNAPSHOT is provisioned to Apache Felix and after running the ps command we see that the org.sonatype.mcookbook bundle is deployed with ID 5.

[INFO] [pax:provision]
[INFO] Installing ~/examples/osgi/osgi-project/runner/target/pom-transformed.xml to \
       ~/.m2/repository/.m2/repository/org/sonatype/mcookbook/osgi-project/build/deployment/1.0-SNAPSHOT/\
       deployment-1.0-SNAPSHOT.pom

Pax Runner (1.0.0) from OPS4J - http://www.ops4j.org
----------------------------------------------------

 -> Using config [classpath:META-INF/runner.properties]
 -> Using only arguments from command line
 -> Scan bundles from [~/examples/osgi/osgi-project/runner/deploy-pom.xml]
 -> Scan bundles from [scan-pom:file:~/examples/osgi/osgi-project/runner/deploy-pom.xml]
 -> Provision bundle [mvn:org.apache.felix/org.apache.felix.webconsole/1.2.8, at default start level, \
    bundle will be started, bundle will be loaded from the cache]
 -> Provision bundle [mvn:org.apache.felix/javax.servlet/1.0.0, at default start level, bundle will be \
    started, bundle will be loaded from the cache]
 -> Provision bundle [mvn:org.apache.felix/org.apache.felix.scr/1.0.8, at default start level, bundle will be \
    started, bundle will be loaded from the cache]
 -> Provision bundle [mvn:org.apache.felix/org.apache.felix.http.jetty/1.0.1, at default start level, bundle \
    will be started, bundle will be loaded from the cache]
 -> Provision bundle [mvn:org.sonatype.mcookbook.osgi-project/org.sonatype.mcookbook/1.0-SNAPSHOT, at default \
    start level, bundle will be started, bundle will be loaded from the cache]
 -> Preparing framework [Felix 1.8.0]
...
 -> Runner has successfully finished his job!


Welcome to Felix.
=================

-> org.mortbay.log:Logging to org.mortbay.log via org.apache.felix.http.jetty.LogServiceLog
STARTING org.sonatype.mcookbook
REGISTER org.sonatype.mcookbook.ExampleService
org.mortbay.log:Init SecureRandom.
...
org.mortbay.log:started /system/console/res

-> ps
START LEVEL 6
   ID   State         Level  Name
[   0] [Active     ] [    0] System Bundle (1.8.0)
[   1] [Active     ] [    5] Apache Felix Web Management Console (1.2.8)
[   2] [Active     ] [    5] Servlet 2.1 API (1.0.0)
[   3] [Active     ] [    5] Apache Felix Declarative Services (1.0.8)
[   4] [Active     ] [    5] HTTP Service (1.0.1)
[   5] [Active     ] [    5] org.sonatype.mcookbook (1.0.0.SNAPSHOT)
[   6] [Active     ] [    1] osgi.compendium (4.1.0.build-200702212030)
[   7] [Active     ] [    1] Apache Felix Shell Service (1.2.0)
[   8] [Active     ] [    1] Apache Felix Shell TUI (1.2.0)

Note

Instead of executing these goals manually, you can also download and install the Pax-Construct scripts. For more information, see the Pax Construct Quickstart page.

1.5.3. Detail

So you just deployed your own OSGi bundle to a OSGi runtime environment, but where is the code? And, what is in this OSGi bundle anyway? Open up your osgi-project directory and you'll see a directory named org.sonatype.mcookbook/ with a directory structure similar to that shown in Figure 1.5, “Custom Bundle org.sonatype.mcookbook”.

Custom Bundle org.sonatype.mcookbook

Figure 1.5. Custom Bundle org.sonatype.mcookbook


This sample project defines a single interface named ExampleService which offers a single method scramble().

Example 1.1. ExampleService Interface

package org.sonatype.mcookbook;

/**
 * Public API representing an example OSGi service
 */
public interface ExampleService
{
    // public methods go here...

    String scramble( String text );
}

This ExampleService interface is implemented by the class ExampleServiceImpl shown in the following program listing.

Example 1.2. ExampleServiceImpl Implementation of the ExampleService Interface

package org.sonatype.mcookbook.internal;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.sonatype.mcookbook.ExampleService;

/**
 * Internal implementation of our example OSGi service
 */
public final class ExampleServiceImpl
    implements ExampleService
{
    // implementation methods go here...

    public String scramble( String text )
    {
        List charList = new ArrayList();

        char[] textChars = text.toCharArray();
        for( int i = 0; i < textChars.length; i++ )
        {
            charList.add( new Character( textChars[i] ) );
        }

        Collections.shuffle( charList );

        char[] mixedChars = new char[text.length()];
        for( int i = 0; i < mixedChars.length; i++ )
        {
            mixedChars[i] = ( (Character) charList.get( i ) ).charValue();
        }

        return new String( mixedChars );
    }
}

Notice how neither ExampleService nor ExampleServiceImpl have any knowledge of the OSGi environment. For the ExampleService to be made available to the OSGi environment, there needs to be a BundleActivator which can instantiate the service and interact with the OSGi environment. This is included in the project in the form of an ExampleActivator which has a method named start() which takes a BundleContext instance and which calls registerService() on this context with an instance of ExampleServiceImpl.

Example 1.3. The BundleActivator implementation: ExampleActivator

package org.sonatype.mcookbook.internal;

import java.util.Dictionary;
import java.util.Properties;

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

import org.sonatype.mcookbook.ExampleService;

/**
 * Extension of the default OSGi bundle activator
 */
public final class ExampleActivator
    implements BundleActivator
{
    /**
     * Called whenever the OSGi framework starts our bundle
     */
    public void start( BundleContext bc )
        throws Exception
    {
        System.out.println( "STARTING org.sonatype.mcookbook" );

        Dictionary props = new Properties();
        // add specific service properties here...

        System.out.println( "REGISTER org.sonatype.mcookbook.ExampleService" );

        // Register our example service implementation in the OSGi service registry
        bc.registerService( ExampleService.class.getName(), new ExampleServiceImpl(), props );
    }

    /**
     * Called whenever the OSGi framework stops our bundle
     */
    public void stop( BundleContext bc )
        throws Exception
    {
        System.out.println( "STOPPING org.sonatype.mcookbook" );

        // no need to unregister our service - the OSGi framework handles it for us
    }
}

Lastly, the osgi.bnd file in ~/examples/osgi/osgi-project/org.sonatype.mcookbook supplies the bundle configuration which identifies the ExampleActivator class as the appropriate Bundle-Activator.

Example 1.4. osgi.bnd Bundle Configuration

#-----------------------------------------------------------------
# Use this file to add customized Bnd instructions for the bundle
#-----------------------------------------------------------------

Bundle-Activator: ${bundle.namespace}.internal.ExampleActivator