Pax Construct: From zero to OSGi - No Fluff Just Stuff

Pax Construct: From zero to OSGi

Posted by: Craig Walls on April 20, 2009

If you were to ask me what I think the best and quickest way to get started with Ruby web development is, I'd tell you to look into Ruby on Rails. Rails isn't the only way to develop web applications in Ruby--but it certainly is the most prominent choice. There are a lot of good reasons for Rails' popularity, among them the script-oriented scaffolding that makes it possible to rapidly get a project up and going.

Similarly, if you were to ask me what the best and quickest way to get started with OSGi development is, I'd tell you to have a look at Pax Construct. While there certainly are other ways to develop OSGi projects, I find Pax Construct to be the quickest approach with the lowest barrier to entry. Pax Construct is script-oriented toolkit for developing OSGi projects that, in many ways, follows the same programming model as Rails. With Pax Construct, you can go from zero to running OSGi-based application in a matter of minutes.

I use Pax Construct quite a bit in Modular Java, but thought I could write a bit more about it here as the first of a series of blog entries that I plan to write on OSGi and OSGi related tools and frameworks. This time we'll start with some Pax Construct basics, but you'll certainly see more Pax Construct on this blog in future entries.

The first step to take with Pax Construct is to download it from http://repo1.maven.org/maven2/org/ops4j/pax/construct/scripts/1.4/scripts-1.4.zip and unzip it to your local hard drive. Then add the bin directory to your system path and you're ready to build your first OSGi bundle project.

Creating a Project

Pax Construct comes with a dozen scripts for working with OSGi projects:

  • pax-create-project
  • pax-add-repository
  • pax-create-bundle
  • pax-import-bundle
  • pax-embed-jar
  • pax-wrap-jar
  • pax-provision
  • pax-create-module
  • pax-move-bundle
  • pax-remove-bundle
  • pax-update
  • pax-clone

The first script that you'll use on any Pax Construct project is pax-create-project:

sandbox% pax-create-project

pax-create-project -g groupId -a artifactId [-v version] [-o] [-- mvnOpts ...]

groupId (examples) ? com.habuma.osgi
artifactId (myProject) ? helloworld
version (1.0-SNAPSHOT) ? 1.0-SNAPSHOT
...
[INFO] Archetype created in dir: /Users/wallsc/Projects/sandbox/helloworld
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5 seconds
[INFO] Finished at: Fri Apr 03 22:41:29 CDT 2009
[INFO] Final Memory: 10M/19M
[INFO] ------------------------------------------------------------------------
sandbox% 

Pax Construct is based on Apache Maven 2 and any project it creates is a Maven 2 project. And, just like any Maven project, a Pax Construct project is identified by a group ID, an artifact ID, and a version. Therefore, pax-create-project prompts us for those three bits of information. In this case, our project will have a group ID of "com.habuma.osgi", an artifact ID of "helloworld", and a version of "1.0-SNAPSHOT".

You'll notice that we could also have provided that information on the command line, as follows:

sandbox% pax-create-project -g com.habuma.osgi \
?                           -a helloworld
?                           -v 1.0-SNAPSHOT

Once pax-create-project is finished, you'll find that it has created a new helloworld directory. If you dig around in that directory you'll find a pom.xml file and a few nested directories--each of which has its own pom.xml file. Don't worry yourself too much with these files just yet. We'll look at a couple of them soon enough.

But first, let's go ahead and kick the tires and see what Pax Construct has given us. To do that, we'll try out our second Pax Construct command: pax-provision. pax-provision's purpose in life is to fire up the OSGi framework of your choosing (Apache Felix by default) with all of your project's bundles installed and started. Let's give it a try from within the helloworld directory:

helloworld% pax-provision
[INFO] Scanning for projects...
[INFO] Reactor build order: 
[INFO]   com.habuma.osgi.helloworld (OSGi project)
[INFO]   helloworld - plugin configuration
[INFO]   helloworld - wrapper instructions
[INFO]   helloworld - bundle instructions
[INFO]   helloworld - imported bundles
[INFO] ------------------------------------------------------------------------
[INFO] Building com.habuma.osgi.helloworld (OSGi project)
[INFO]    task-segment: [org.ops4j:maven-pax-plugin:1.4:provision] (aggregator-style)
[INFO] ------------------------------------------------------------------------
[INFO] [pax:provision]
[INFO] ~~~~~~~~~~~~~~~~~~~
[INFO]  No bundles found! 
[INFO] ~~~~~~~~~~~~~~~~~~~
[INFO] Installing /Users/wallsc/Projects/sandbox/helloworld/runner/deploy-pom.xml to /Users/wallsc/.m2/repository/com/habuma/osgi/helloworld/build/deployment/1.0-SNAPSHOT/deployment-1.0-SNAPSHOT.pom
    ______  ________  __  __
   / __  / /  __   / / / / /
  /  ___/ /  __   / _\ \ _/
 /  /    /  / /  / / _\ \
/__/    /__/ /__/ /_/ /_/

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

 -> Using config [classpath:META-INF/runner.properties]
 -> Provision from [/Users/wallsc/Projects/sandbox/helloworld/runner/deploy-pom.xml]
 -> Provision from [scan-pom:file:/Users/wallsc/Projects/sandbox/helloworld/runner/deploy-pom.xml]
 -> Using property [org.osgi.service.http.port=8080]
 -> Using property [org.osgi.service.http.port.secure=8443]
 -> Using default executor
 -> Downloading bundles...
 -> Felix 1.2.2 : 356815 bytes @ [ 405kBps ]
 -> org.osgi.compendium (4.1.0) : 514214 bytes @ [ 3451kBps ]
 -> org.apache.felix.shell (1.0.2) : 51390 bytes @ [ 744kBps ]
 -> org.apache.felix.shell.tui.plugin (1.0.2) : 12237 bytes @ [ 941kBps ]]
 -> Execution environment [J2SE-1.5]
 -> Starting platform [Felix 1.2.2]. Runner has successfully finished his job!

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

-> 

A lot of stuff happens once you press ENTER, but in the end you are confronted with a Felix shell prompt. At this point, our project doesn't have any bundles to install, so if we type ps, we'll see nothing but the basic Felix and OSGi bundles:

-> ps
START LEVEL 6
   ID   State         Level  Name
[   0] [Active     ] [    0] System Bundle (1.2.2)
[   1] [Active     ] [    1] osgi.compendium (4.1.0.build-200702212030)
[   2] [Active     ] [    1] Apache Felix Shell Service (1.0.2)
[   3] [Active     ] [    1] Apache Felix Shell TUI (1.0.2)
-> 

Now, let's get out of Felix so that we can move on. To do that, issue the "shutdown" command:

-> shutdown
-> [INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 12 minutes 15 seconds
[INFO] Finished at: Fri Apr 03 23:27:01 CDT 2009
[INFO] Final Memory: 8M/18M
[INFO] ------------------------------------------------------------------------
helloworld% 

If you're like me, you may prefer to work with Equinox. Not that there's anything horribly wrong with Felix...but as I write this, Felix doesn't yet support OSGi fragments. Since I use fragments from time to time, I would prefer to work with Equinox. So, to tell pax-provision that we want to use Equinox, we need to edit the pom.xml at the root of the project. In there, you'll find a line that looks like this:

    <param>--platform=felix</param>

Under the hood of pax-provision is another Pax project known as Pax Runner. --platform=felix is a setting for Pax Runner that tells it which OSGi implementation to use. Since I want to use Equinox, I'll change that line to look like this:

    <param>--platform=equinox</param>

Now if we run pax-provision, we'll get Equinox:

helloworld% pax-provision
[INFO] Scanning for projects...
[INFO] Reactor build order: 
[INFO]   com.habuma.osgi.helloworld (OSGi project)
[INFO]   helloworld - plugin configuration
[INFO]   helloworld - wrapper instructions
[INFO]   helloworld - bundle instructions
[INFO]   helloworld - imported bundles
[INFO] ------------------------------------------------------------------------
[INFO] Building com.habuma.osgi.helloworld (OSGi project)
[INFO]    task-segment: [org.ops4j:maven-pax-plugin:1.4:provision] (aggregator-style)
[INFO] ------------------------------------------------------------------------
[INFO] [pax:provision]
[INFO] ~~~~~~~~~~~~~~~~~~~
[INFO]  No bundles found! 
[INFO] ~~~~~~~~~~~~~~~~~~~
[INFO] Installing /Users/wallsc/Projects/sandbox/helloworld/runner/deploy-pom.xml to /Users/wallsc/.m2/repository/com/habuma/osgi/helloworld/build/deployment/1.0-SNAPSHOT/deployment-1.0-SNAPSHOT.pom
    ______  ________  __  __
   / __  / /  __   / / / / /
  /  ___/ /  __   / _\ \ _/
 /  /    /  / /  / / _\ \
/__/    /__/ /__/ /_/ /_/

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

 -> Using config [classpath:META-INF/runner.properties]
 -> Provision from [/Users/wallsc/Projects/sandbox/helloworld/runner/deploy-pom.xml]
 -> Provision from [scan-pom:file:/Users/wallsc/Projects/sandbox/helloworld/runner/deploy-pom.xml]
 -> Using property [org.osgi.service.http.port=8080]
 -> Using property [org.osgi.service.http.port.secure=8443]
 -> Using default executor
 -> Downloading bundles...
 -> Equinox 3.4.1 (v20080826) : 997883 bytes @ [ 5734kBps ]
 -> Eclipse utilities : 22755 bytes @ [ 4551kBps ]
 -> Eclipse compendium services : 63704 bytes @ [ 6370kBps ]
 -> Execution environment [J2SE-1.5]
 -> Starting platform [Equinox 3.4.1]. Runner has successfully finished his job!

osgi> 

Awesome! Now we have Equinox. To see what bundles are installed, let's issue the ss (short status) command:

osgi> ss

Framework is launched.

id	State       Bundle
0	ACTIVE      org.eclipse.osgi_3.4.2.R34x_v20080826-1230
1	ACTIVE      org.eclipse.osgi.util_3.1.300.v20080303
2	ACTIVE      org.eclipse.osgi.services_3.1.200.v20070605

osgi> 

As you can see, we still have nothing but the essential Equinox bundles installed. Let's fix that by creating a bundle subproject. But first, feel free to exit Equinox by issuing the exit command.

Creating a Bundle

To create a bundle subproject, we'll need to add a third Pax Construct command to our repertoire. From within the helloworld directory, run the pax-create-bundle command:

helloworld% pax-create-bundle

pax-create-bundle -p package [-n bundleName] [-g bundleGroupId] [-v version] [-o] [-- mvnOpts ...]

package (org.example.pkg) ? com.habuma.osgi.hello
bundleName () ? hello-simple
bundleGroupId () ? com.habuma.osgi.helloworld
version (1.0-SNAPSHOT) ? 1.0-SNAPSHOT
...
[INFO] Archetype created in dir: /Users/wallsc/Projects/sandbox/helloworld/hello-simple
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5 seconds
[INFO] Finished at: Fri Apr 03 23:43:02 CDT 2009
[INFO] Final Memory: 10M/19M
[INFO] ------------------------------------------------------------------------
helloworld% 

After a lot of activity in the console, pax-create-bundle will have created a new subdirectory called hello-simple. If you dig around in there, you'll find three main items of interest:

  • A Maven pom.xml file. As I said, Pax Construct projects are Maven projects at heart.
  • An osgi.bnd file. Pax Construct leans heavily on the Felix Maven Bundle Plugin (which, in turn, leans on Peter Kriens' BND)to automatically generate manifest files for the bundles it creates. This file includes BND instructions and directives to guide the creation of the manifest file.
  • A src directory containing an example OSGi service and an activator to publish it in the OSGi registry.

That's right, Pax Construct gave us some ready-to-build-and-run code to start with. Even though our ultimate goal for this blog entry is to create a basic hello world bundle, it might be fun to build what Pax Construct has given us to see if it works. So, let's build the hello-simple bundle project. Since it's a Maven project and because pax-provision will try to resolve the bundle from the local Maven repository, we'll need to use Maven's install goal:

hello-simple% mvn install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building com.habuma.osgi.helloworld.hello-simple [com.habuma.osgi.hello]
[INFO]    task-segment: [install]
[INFO] ------------------------------------------------------------------------
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 8 seconds
[INFO] Finished at: Sat Apr 04 10:22:59 CDT 2009
[INFO] Final Memory: 13M/24M
[INFO] ------------------------------------------------------------------------
hello-simple% 

Note that even though I ran mvn install from within the hello-simple directory, I could've also had done it from the top-level helloworld directory. That's because hello-simple is a subproject of helloworld. Running it from helloworld will build the entire Pax Construct project, but running it from hello-simple will just build the subproject.

In any event, now that the hello-simple project has been built and installed in the local Maven repository, we're ready to fire up the OSGi platform and see if it works. To do that, run pax-provision from the top-level helloworld directory:

helloworld% pax-provision
[INFO] Scanning for projects...
[INFO] Reactor build order: 
[INFO]   com.habuma.osgi.helloworld (OSGi project)
[INFO]   helloworld - plugin configuration
[INFO]   helloworld - wrapper instructions
[INFO]   helloworld - bundle instructions
[INFO]   helloworld - imported bundles
[INFO]   com.habuma.osgi.helloworld.hello-simple [com.habuma.osgi.hello]
[INFO] ------------------------------------------------------------------------
[INFO] Building com.habuma.osgi.helloworld (OSGi project)
[INFO]    task-segment: [org.ops4j:maven-pax-plugin:1.4:provision] (aggregator-style)
[INFO] ------------------------------------------------------------------------
[INFO] [pax:provision]
[INFO] Installing /Users/wallsc/Projects/sandbox/helloworld/runner/deploy-pom.xml to /Users/wallsc/.m2/repository/com/habuma/osgi/helloworld/build/deployment/1.0-SNAPSHOT/deployment-1.0-SNAPSHOT.pom
    ______  ________  __  __
   / __  / /  __   / / / / /
  /  ___/ /  __   / _\ \ _/
 /  /    /  / /  / / _\ \
/__/    /__/ /__/ /_/ /_/

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

 -> Using config [classpath:META-INF/runner.properties]
 -> Provision from [/Users/wallsc/Projects/sandbox/helloworld/runner/deploy-pom.xml]
 -> Provision from [scan-pom:file:/Users/wallsc/Projects/sandbox/helloworld/runner/deploy-pom.xml]
 -> Using property [org.osgi.service.http.port=8080]
 -> Using property [org.osgi.service.http.port.secure=8443]
 -> Installing bundle [{location=mvn:com.habuma.osgi.helloworld/hello-simple/1.0-SNAPSHOT,startlevel=null,shouldStart=true,shouldUpdate=false}]
 -> Using default executor
 -> Downloading bundles...
 -> mvn:com.habuma.osgi.helloworld/hello-simple/1.0-SNAPSHOT : 5404 bytes @ [ 1801kBps ]
 -> Execution environment [J2SE-1.5]
 -> Starting platform [Equinox 3.4.1]. Runner has successfully finished his job!

osgi> STARTING com.habuma.osgi.hello
REGISTER com.habuma.osgi.hello.ExampleService

osgi> 

The ExampleActivator class has a few System.out.println() lines that tell us that it's starting the bundle and registering the service. As you can see from the output, it looks like the activator did its job when the bundle was started. Now let's take a deeper look to see if the service was really published in the OSGi service registry. First we need to find out what our bundle's ID is. For that we'll use Equinox' ss command:

osgi> ss

Framework is launched.

id	State       Bundle
0	ACTIVE      org.eclipse.osgi_3.4.2.R34x_v20080826-1230
1	ACTIVE      org.eclipse.osgi.util_3.1.300.v20080303
2	ACTIVE      org.eclipse.osgi.services_3.1.200.v20070605
3	ACTIVE      com.habuma.osgi.helloworld.hello-simple_1.0.0.SNAPSHOT

osgi> 

It looks like our bundle has an ID of 3. Knowing that, we can now issue Equinox' bundle command to see the details of the bundle:

osgi> bundle 3
initial@reference:file:com.habuma.osgi.helloworld.hello-simple_1.0.0.SNAPSHOT.jar/ [3]
  Id=3, Status=ACTIVE      Data Root=/Users/wallsc/Projects/sandbox/helloworld/runner/equinox/org.eclipse.osgi/bundles/3/data
  Registered Services
    {com.habuma.osgi.hello.ExampleService}={service.id=22}
  No services in use.
  Exported packages
    com.habuma.osgi.hello; version="1.0.0.SNAPSHOT"[exported]
  Imported packages
    org.osgi.framework; version="1.4.0"
  No fragment bundles
  Named class space
    com.habuma.osgi.helloworld.hello-simple; bundle-version="1.0.0.SNAPSHOT"[provided]
  No required bundles

osgi> 

It looks like everything's in order. Under the "Registered Services" header, we see that the example service has been registered with a service ID of 22. Awesome! Likewise, com.habuma.osgi.hello is an exported package (it's the package where the ExampleService interface lives).

Customizing the Bundle

That was fun. But what we really wanted to do is create a simple hello world activator. No problem. First, let's delete all of the example code that Pax Construct gave us:

hello-simple% rm src/main/java/com/habuma/osgi/hello/ExampleService.java 
hello-simple% rm src/main/java/com/habuma/osgi/hello/internal/ExampleServiceImpl.java
hello-simple% rm src/main/java/com/habuma/osgi/hello/internal/ExampleActivator.java

In the place of ExampleActivator.java, let's create a new HelloActivator.java file:

package com.habuma.osgi.hello.internal;

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

public final class HelloActivator implements BundleActivator {
    public void start( BundleContext bc ) throws Exception {
        System.out.println( "Hello World!" );
    }

    public void stop( BundleContext bc ) throws Exception {
        System.out.println( "See ya later!" );
    }
}

We'll also need to change the osgi.bnd file to use our new activator class. It's currently written to add a Bundle-Activator: header in the manifest for the ExampleActivator class. We'll need to tweak it to refer to HelloActivator:

Bundle-Activator: com.habuma.osgi.hello.internal.HelloActivator

All of the pieces are in place, so let's build it again (using mvn clean install) and then try provisioning it:

helloworld% pax-provision
[INFO] Scanning for projects...
[INFO] Reactor build order: 
[INFO]   com.habuma.osgi.helloworld (OSGi project)
[INFO]   helloworld - plugin configuration
[INFO]   helloworld - wrapper instructions
[INFO]   helloworld - bundle instructions
[INFO]   helloworld - imported bundles
[INFO]   com.habuma.osgi.helloworld.hello-simple [com.habuma.osgi.hello]
[INFO] ------------------------------------------------------------------------
[INFO] Building com.habuma.osgi.helloworld (OSGi project)
[INFO]    task-segment: [org.ops4j:maven-pax-plugin:1.4:provision] (aggregator-style)
[INFO] ------------------------------------------------------------------------
[INFO] [pax:provision]
[INFO] Installing /Users/wallsc/Projects/sandbox/helloworld/runner/deploy-pom.xml to /Users/wallsc/.m2/repository/com/habuma/osgi/helloworld/build/deployment/1.0-SNAPSHOT/deployment-1.0-SNAPSHOT.pom
    ______  ________  __  __
   / __  / /  __   / / / / /
  /  ___/ /  __   / _\ \ _/
 /  /    /  / /  / / _\ \
/__/    /__/ /__/ /_/ /_/

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

 -> Using config [classpath:META-INF/runner.properties]
 -> Provision from [/Users/wallsc/Projects/sandbox/helloworld/runner/deploy-pom.xml]
 -> Provision from [scan-pom:file:/Users/wallsc/Projects/sandbox/helloworld/runner/deploy-pom.xml]
 -> Using property [org.osgi.service.http.port=8080]
 -> Using property [org.osgi.service.http.port.secure=8443]
 -> Installing bundle [{location=mvn:com.habuma.osgi.helloworld/hello-simple/1.0-SNAPSHOT,startlevel=null,shouldStart=true,shouldUpdate=false}]
 -> Using default executor
 -> Downloading bundles...
 -> mvn:com.habuma.osgi.helloworld/hello-simple/1.0-SNAPSHOT : 3677 bytes @ [ 1838kBps ]
 -> Execution environment [J2SE-1.5]
 -> Starting platform [Equinox 3.4.1]. Runner has successfully finished his job!

osgi> Hello World!

It looks like our "Hello World!" message appeared when the bundle started. Let's stop the bundle and see if we see the goodbye message:

osgi> stop 3
See ya later!

osgi> 

The goodbye message also worked!

It's worth pointing out that although we have developed a fully functional OSGi bundle, we haven't had to manually work with the MANIFEST.MF file. That's because we've let BND (via the Felix Maven Bundle Plugin) automatically generate it for us. All we had to do was include a Bundle-Activator: line in osgi.bnd and BND did the rest. In case you're wondering, here's what the generated MANIFEST.MF file looks like:

Manifest-Version: 1.0
Built-By: wallsc
Created-By: Apache Maven Bundle Plugin
Bundle-Activator: com.habuma.osgi.hello.internal.HelloActivator
Import-Package: org.osgi.framework
Bnd-LastModified: 1238861595780
Bundle-Version: 1.0.0.SNAPSHOT
Ignore-Package: com.habuma.osgi.hello.internal
Bundle-Name: com.habuma.osgi.helloworld.hello-simple [com.habuma.osgi.hello]
Bundle-Description: Generated using Pax-Construct
Build-Jdk: 1.5.0_16
Private-Package: com.habuma.osgi.hello.internal
Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.habuma.osgi.helloworld.hello-simple
Tool: Bnd-0.0.255

And with that I conclude our first excursion into Pax Construct. To recap, Pax Construct brings a Rails-like, script-driven, development model to OSGi. In this article we've used 3 of the Pax Construct scripts: pax-create-project, pax-create-module, and pax-provision to develop and run a simple hello world OSGi bundle.

If we've used only 3 of Pax Construct's scripts, then that means we've only used about 25% of its capability. There's plenty more Pax Construct goodness to explore in a future blog entry. But for next time, we'll set Pax Construct aside and take a deeper look at Pax Runner (the magic behind the pax-provision script).

Craig Walls

About Craig Walls

Craig Walls is a Principal Engineer, Java Champion, Alexa Champion, and the author of Spring AI in Action, Spring in Action, and Build Talking Apps. He's a zealous promoter of the Spring Framework, speaking frequently at local user groups and conferences and writing about Spring. When he's not slinging code, Craig is planning his next trip to Disney World or Disneyland and spending as much time as he can with his wife, two daughters, 1 bird and 2 dogs.

Why Attend the NFJS Tour?

  • » Cutting-Edge Technologies
  • » Agile Practices
  • » Peer Exchange

Current Topics:

  • Languages on the JVM: Scala, Groovy, Clojure
  • Enterprise Java
  • Core Java, Java 8
  • Agility
  • Testing: Geb, Spock, Easyb
  • REST
  • NoSQL: MongoDB, Cassandra
  • Hadoop
  • Spring 4
  • Cloud
  • Automation Tools: Gradle, Git, Jenkins, Sonar
  • HTML5, CSS3, AngularJS, jQuery, Usability
  • Mobile Apps - iPhone and Android
  • More...
Learn More »