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).