The Maven Cookbook
1.5. Creating an OSGi Bundle with Maven
You need to create you own OSGi bundle project to integrate with the runtime environment created in the previous recipes.
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.
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”.
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