Main | Next page »
20091022 Thursday October 22, 2009

Curing ADD with Roo, Blueprints, and Karaf

Yesterday was a big day for me at SpringOne/2GX. It was the day that I gave my talk on the Spring Expression Language (SpEL). I've been excited about giving that talk, but also excited about attending so many of the great sessions by other speakers. So, I started the day by attending a session presented by Stefan Schmidt on web productivity with Roo.

Unfortunately, due to lack of sleep and anxiety for my own presentation, I was unable to focus well on Stefan's talk. He was doing a great job presenting Roo, but my brain just couldn't focus. So to entertain my brain with something that at least was close to the topic at hand, I decided to tinker a bit with Roo's support for SpringSource's Bundlor to generate OSGi manifests for Roo artifacts.

The first thing I did was to start up the Roo shell and issue the project command:

roobundle% roo
    ____  ____  ____  
   / __ \/ __ \/ __ \ 
  / /_/ / / / / / / / 
 / _, _/ /_/ / /_/ /  
/_/ |_|\____/\____/    1.0.0.RC2 [rev 321]


Welcome to Spring Roo. For assistance press TAB or type "hint" then hit ENTER.
roo> project --topLevelPackage com.habuma.numbers
Created /Users/wallsc/Projects/roofun/roobundle/pom.xml
Created SRC_MAIN_JAVA
Created SRC_MAIN_RESOURCES
Created SRC_TEST_JAVA
Created SRC_TEST_RESOURCES
Created SRC_MAIN_WEBAPP
Created SRC_MAIN_RESOURCES/META-INF/spring
Created SRC_MAIN_RESOURCES/META-INF/spring/applicationContext.xml
roo> 

From here it looks like Roo has created the basic project structure. Nothing special so far. But what I want to do with Roo is build a simple OSGi bundle that publishes a service to the OSGi service registry. So, let's start by creating the service's interface. There's lots of ways I could do this, but as long as I'm in Roo...

roo> interface --name NumberToEnglishService
Created SRC_MAIN_JAVA/com/habuma/numbers
Created SRC_MAIN_JAVA/com/habuma/numbers/NumberToEnglishService.java
roo> 

Notice that Roo's interface command creates the interface in the base directory without me having to explicitly specify that package. If we dig around in the project directory, you'll see that Roo has created NumberToEnglishService.java in src/main/com/habuma/numbers. But it's empty, so let's give it something to do. I could find no way to do this directly in Roo (and I can't imagine how that'd work anyway), so I hopped out of Roo and opened up NumberToEnglishService.java in my text editor and made it look like this:

package com.habuma.numbers;

public interface NumberToEnglishService {
    String translate(int number);
}

The idea behind NumberToEnglishService is to take a number as an int and to return a String that spells it out in English. For example, given 6, the translate() method should return "six". Simple enough. Now we need an implementation. So, trying to do as much work in Roo as possible, I issue the class command in the Roo shell:

roo> class --name ~.internal.NumberToEnglishServiceImpl
Created SRC_MAIN_JAVA/com/habuma/numbers/internal
Created SRC_MAIN_JAVA/com/habuma/numbers/internal/NumberToEnglishServiceImpl.java
roo> 

Since I'm ultimately going to export com.habuma.numbers in the OSGi manifest, I want to put the implementation of NumberToEnglishService in a different package. This keeps it private to this bundle so that no other bundle will try to use it directly. In Roo, the tilde (~) is a shortcut for the project's base directory, so "~.internal" will be expanded into "com.habuma.numbers.internal"...and that's where NumberToEnglishServiceImpl.java was created.

We're almost ready to create our bundle, but we need to flesh out the contents of NumberToEnglishServiceImpl.java. Again I turn to my text editor to have it implement the NumberToEnglishService interface as follows:

package com.habuma.numbers.internal;
import com.habuma.numbers.NumberToEnglishService;

public class NumberToEnglishServiceImpl implements NumberToEnglishService {
    public String translate(int number) {
        if(number == 6) {
            return "six";
        }

        return "unknown";
    }
}

So this implementation of NumberToEnglishService is a bit short-sighted, but it does satisfy the aforementioned requirement of translating 6 to "six", so it's a good start.

Now we have all of the code in place for our service, so let's build it. Roo produces a Maven project, so all we need to do is run Maven with the package goal:

roobundle% mvn package
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building numbers
[INFO]    task-segment: [package]
[INFO] ------------------------------------------------------------------------
...
[INFO] Building jar: /Users/wallsc/Projects/roofun/roobundle/target/numbers-0.1.0-SNAPSHOT-sources.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 16 seconds
[INFO] Finished at: Wed Oct 21 22:09:43 CDT 2009
[INFO] Final Memory: 19M/34M
[INFO] ------------------------------------------------------------------------
roobundle% 

So far, so good. Let's crack open the JAR artifact to see what its manifest looks like. Unfortunately, it looks like this:

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: wallsc
Build-Jdk: 1.5.0_20

There's no Bundle-SymbolicName, Bundle-Version, Export-Package or any other header that would indicate that this is an OSGi bundle. This is just a plain old JAR file. We need it to be an OSGi bundle.

We could edit our own manifest file, but that's no fun. Fortunately, Roo has support for the Bundlor tool to automatically generate a proper OSGi manifest. All we need to do is tell Roo that we want it to use Bundlor. To do that, issue bundlor setup in the Roo shell:

roo> bundlor setup 
Managed ROOT/pom.xml
Created ROOT/template.mf
roo> 

Now our Roo project is setup with Bundlor, so let's build it again and the review the manifest to see if it looks any better. This time the manifest looks like this:

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: wallsc
Created-By: Apache Maven
Import-Package: javax.sql;version="0.0.0",org.springframework.stereoty
 pe;version="[3.0.0,3.1.0)"
Export-Package: com.habuma.numbers;version="0.1.0.BUILD-SNAPSHOT"
Bundle-Version: 0.1.0.BUILD-SNAPSHOT
Bundle-Name: numbers
Bundle-Classpath: .
Build-Jdk: 1.5.0_20
Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.habuma.numbers
Tool: Bundlor 1.0.0.M5

Now that looks like an OSGi manifest. We have a Bundle-SymbolicName and a Bundle-Version, among other things. And notice that the Export-Package header exports our base package. It all looks good...except...

The Import-Package header imports javax.sql and org.springframework.stereotype. We're not really going to use those in our bundle, so I'm not sure that we need them. So we need to tell Bundlor to not import them. If you paid close attention when doing the Bundlor setup, you saw that Roo added a file called template.mf to the project. This file contains instructions to guide Bundlor in producing a manifest. We'll need to edit this file to remove the Import-Package instructions for javax.sql and org.springframework.stereotype. If you open up template.mf, you'll see a line that looks like this:

Import-Package: org.springframework.stereotype;version="[3.0.0,3.1.0)",
  javax.sql;version="0.0.0"

Just remove it and then run the build again. Then have another look at the produced bundle's manifest. It should look a little like this:

Manifest-Version: 1.0
Bundle-Name: numbers
Bundle-Classpath: .
Archiver-Version: Plexus Archiver
Build-Jdk: 1.5.0_20
Built-By: wallsc
Created-By: Apache Maven
Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.habuma.numbers
Tool: Bundlor 1.0.0.M5
Export-Package: com.habuma.numbers;version="0.1.0.BUILD-SNAPSHOT"
Bundle-Version: 0.1.0.BUILD-SNAPSHOT

Much better. Now the manifest doesn't import anything we don't need. But we're not quite ready to deploy our bundle yet. Even though we have a service interface and implementation and our bundle's manifest is in shape, there's nothing that actually publishes the service to the OSGi service registry.

There are a lot of ways to publish services to the OSGi service registry. In Modular Java I wrote about using both the core OSGi API and Spring-DM to publish and consume OSGi services. But recently, the OSGi R4.2 specification was released including the OSGi Blueprint Services specification and I've been wanting to try it out. OSGi Blueprint Services is a formalization of the service model from Spring-DM. It's slightly different than Spring-DM, but if you've already used Spring-DM, then it should be quite easy to figure out. Even if you've not used Spring-DM before, I think that the Blueprint model is quite intuitive.

All we need to do to publish our service in the OSGi service registry using OSGi Blueprint Services is to add an XML file containing the blueprint specifications to the bundles OSGI-INF/blueprint folder. Since our Roo-created project is a Maven project, that means creating the blueprint XML file in src/main/resources/OSGI-INF/blueprint. This one ought to do fine:

<?xml version="1.0" encoding="UTF-8"?>

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
    xmlns:ext="http://geronimo.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0"
    default-activation="lazy">

   <bean id="numberService" 
       class="com.habuma.numbers.internal.NumberToEnglishServiceImpl" />

   <service ref="numberService" 
       interface="com.habuma.numbers.NumberToEnglishService" />

</blueprint>

I named the file numbers.xml, but the name really isn't important. Any filename with a ".xml" extension will work.

The root of the blueprint specification XML is <blueprint>. Within that the <bean> element declares the NumberToEnglishServiceImpl class as a bean in the blueprint context (just like Spring). Finally, the <service> element refers to that bean and publishes it in the OSGi service registry under the com.habuma.numbers.NumberToEnglishService package (just like Spring-DM).

There's only one more bit of bookkeeping to be done before we're ready to build and deploy our bundle. When Roo first created the project, it assumed that we'd be doing some Spring stuff, so it created a Spring configuration XML file at src/main/resources/META-INF/spring/applicationContext.xml. Since we're using OSGi Blueprint Services and no plain-old Spring, we'll not need that file anymore. And, if our bundle is deployed in an OSGi framework with the Spring-DM extender installed, this file will actually keep our bundle from starting successfully because it refers to stuff in org.springframework.stereotype, which we no longer import in our manifest. The simple solution to this problem is to simply remove applicationContext.xml.

Now after we build the project one last time, we'll have an OSGi bundle with a service that is published to the OSGi service registry using OSGi Blueprint Services. All we need now is an OSGi runtime that supports Blueprint Services.

Even though OSGi R4.2 is rather new, there are already a few options available for using Blueprint Services. Spring-DM 2.0.0.M1, for instance, includes support for Blueprint Services and is, in fact, the reference implementation. But I've been tinkering with Karaf lately, which includes Blueprint out of the box. And just to prove that this will work even without Spring, let's deploy it to Karaf.

Assuming that Karaf is running, all I need to do is to copy numbers-0.1.0-SNAPSHOT.jar from our Roo project's target directory into Karaf's deploy directory. Upon arrival in Karaf's deploy directory, Karaf will install and start the bundle. Then Karaf's Blueprint deployer will pick it up and publish the service to the OSGi service registry.

Once it's deployed, you can verify that the service is being deployed by using the ls command in the Karaf shell:

karaf@root> ls

...

numbers (46) provides:
----------------------
com.habuma.numbers.NumberToEnglishService
org.osgi.service.blueprint.container.BlueprintContainer
karaf@root> 

The ls command lists all of the bundles installed in the OSGi framework along with any services that they provide or consume. In the case of the number service bundle, we can see that it's deployed with the ID of 46 (this will vary depending on what else you've installed before) and provides two services: The NumberToEnglishService and the bundle's blueprint container.

Karaf's list command tells a similar story:

karaf@root> list
START LEVEL 100
   ID   State         Blueprint      Level  Name
...
[  46] [Active     ] [Created     ] [   60] numbers (0.1.0.BUILD-SNAPSHOT)
karaf@root>        

From here I can see that bundle 46 has been started and that a blueprint has been created for it.

Clearly the Blueprint Services have published our service for us. But if you want one more bit of evidence, then we can also look in the web console. If you've not already done so, we'll need to install the web console in Karaf:

karaf@root> features:install webconsole

Simple enough. Now point your browser at http://localhost:8181/system/console. When prompted for a username and password, use "karaf" for both. Once in, find our bundle and drill into its details. Click here to see what I saw in Karaf. I could see (among other things) that my service was published with service ID 118 and the Blueprint container was published as a service with ID 119.

That wraps up my excursion into Roo, Blueprints, and Karaf for today. Note that from start to finish I did all of this in just under 30 minutes, including time to stop and take notes of what I had done. It was simple work putting together this bundle. Part of the simplicity is owed to Blueprint Services--to me the Blueprint model is quite natural and straight-forward. And Roo helped out a ton at the beginning by generating the project structure and (along with Bundlor) generating the manifest.

In hindsight, it was fun to say that I developed an OSGi bundle using Roo. However, I'm not sure that Roo brought a lot to the table aside from the initial project creation. I still prefer Pax Construct for rapidly creating bundle projects. Don't get me wrong, I like Roo...I like it a lot. But in this case I'm not sure that Roo offered a lot once the project was created.

Perhaps in a more interesting example where I'm developing a data access service, Roo would be helpful in setting up the persistence with Hibernate or EclipseLink. Or maybe if I were creating a web bundle, Roo would help out with the controllers.

Nevertheless, the exercise was well worth it. It helped me get comfortable with Roo (albeit in an off-beat kinda way). I got to tinker with Blueprint Services. I had fun fiddling around with Karaf. And ultimately, it gave my unfocused brain something to do that was at least somewhat related to the topic of the session I was sitting in.

By the way, the anxiety I was feeling for my own presentation was unnecessary. From my perspective, it was one of the best presentations that I've ever given. And I've had a few of the attendees tell me that they enjoyed it. It was a great presentation because everyone in the room learned something new. That includes me and even Andy Clement (the creator of SpEL). The crowd suggested several tricks to try with SpEL and we were all uncertain that any of them would work. As it turns out, SpEL's even more capable than any of us knew. Keep an eye on this blog, as I hope to soon post my SpEL presentation in the form of an article.

(2009-10-22 09:00:00.0) Permalink

20090824 Monday August 24, 2009

A bit of Modular Java errata

There has been more than one comment in the errata for Modular Java that mention a strange NoClassDefFoundError with regard to org.compass.core.util.reflection.ReflectionMethod.

I started to address these concerns on the errata page, but decided to talk about it here because (1) the comment button on the errata page seems to be missing right now and (2) I want to address it once instead of once for each errata item. Once I get the comment button back, I'll point the readers here for more info.

As it turns out, when I run my local instance of Dude, Where's My JAR?, I get that very same exception. I never really noticed it before, but sure enough it's there. Why didn't I notice it? Didn't I test this? Am I, as one reviewer incorrectly suggested, asking my readers to do my QA for my book?

Absolutely not! Believe me when I say that I walked through the instructions for Dude, Where's My JAR? no less than a half-dozen times before the book went to print. My editor will attest to the fact that I've put in far more testing time on this book's examples than for any of my previous books. I am confident that the examples in the book were working before they were published. Had they not been, I would've corrected them before they ended up in your hands. Furthermore, I know of several people who have also successfully worked through the examples.

Are they perfect? Probably not. I'd be shocked if I were ever able to, on my own, produce a flawless example. But I'm certain that I didn't just toss the example over the wall for the readers to sort through.

But the question still remains: How did I not notice the NoClassDefFoundError? Well, even though I witnessed that error when I ran my own instance of Dude, Where's My JAR?, the application still works. The spider crawls the repository, deposits its findings in the index, and the web lets me search for JAR files. The example works in spite of the error. The error itself happens very early in the startup of the application, so unless I'm paying very close attention, it scrolls off the screen unnoticed. Since the application works, I never felt compelled to review the log files for the error.

But this doesn't mean that the case is closed. I do not think such an exception should go unanswered. I will try to figure out why the error happens (I suspect it has something to do with the way OSGi class loaders work) and once I find a way to keep it from appearing, I'll let everyone know how to fix it. In the meantime, feel free to ignore that error--as far as I can tell it is innocuous.

(2009-08-24 21:49:41.0) Permalink Comments [4]

20090803 Monday August 03, 2009

Testing OSGi...Spring Style

Last week I showed you how to test OSGi bundles. We used Pax Exam to fire up an OSGi runtime of our choosing, install and start a selection of bundles, and to make assertions against the BundleContext and services registered in the OSGi service registry. This week, we're going to repeat that exercise, this time using Spring-DM's testing support.

Spring-DM's testing support works very much like Pax Exam. When a test is run, it will start an OSGi runtime, install a selection of bundles, create an on-the-fly bundle containing the test class, and then install the test bundle into the OSGi runtime so that it can perform its assertions from the inside.

At the center of Spring-DM's OSGi testing framework is AbstractConfigurableBundleCreatorTests, a JUnit 3 base class that all bundle tests will extend. It provides the basic functionality needed to access the OSGi BundleContext, consume services, and make assertions about your bundles.

I know what you're thinking...JUnit 3? Really? Yes, Spring-DM's testing support is currently based on JUnit 3. But there's plans to move it to JUnit 4 in Spring-DM 2.0.0. If you're interested in following the progress, then have a look at OSGI-410. In the meantime, we'll have to work with what we've got.

So let's get started writing a Spring-DM bundle test. We'll start with the basics and build it up as we go.

public class PigLatinTranslatorBundleTest 
      extends AbstractConfigurableBundleCreatorTests {

   @Override
   protected String[] getTestBundlesNames() {
      return new String[] {
          "com.habuma.translator, interface, 1.0.0",
          "com.habuma.translator, pig-latin, 1.0.0"
        };
   }   

   // test methods go here
}

Aside from extending AbstractConfigurableBundleCreatorTests, the first thing we're going to do is override the getTestBundlesNames. AbstractConfigurableBundleCreatorTests doesn't require us to override this method (or any method, for that matter), but if we don't override this method, the OSGi runtime will be started without much to test. So, I've overridden this method to identify the bundles we want installed in our test scenario.

Much like how we started our Pax Exam test last week, here we're specifying the bundles as Maven dependencies. But instead of using a fluent API, as with Pax Exam, we return an array of String where each member is a comma-separated list of group ID, artifact ID, and version number (and optionally type). Also unlike the Pax Exam version, we didn't have to explicitly ask for Spring-DM bundles to be installed. That's because Spring-DM is assumed and will be installed by default.

Now we can write the first test method. Just like we did with Pax Exam, we'll start by testing that the OSGi runtime starts okay by asserting that we have a BundleContext:

public void testOsgiPlatformStarts() {
   assertNotNull(bundleContext);
}

The bundleContext variable is a given for test classes that extend AbstractConfigurableBundleCreatorTests. If the OSGi runtime fails to start for any reason, bundleContext will be null. But if there aren't any problems, we'll have a reference to the BundleContext through which we can make some assertions.

Next up, we want to be sure that the Pig Latin bundle registers a Translator service in the OSGi service registry and that the service has its "translator.language" property set to indicate that it is a Pig Latin translator. There are a couple of ways to accomplish this using AbstractConfigurableBundleCreatorTests, but let's first try by mimicking the style of test we wrote last time with Pax Exam:

public void testServiceReferenceExists() {
   ServiceReference serviceReference = 
      bundleContext.getServiceReference(Translator.class.getName());
   assertNotNull(serviceReference);
   assertEquals("Pig Latin", 
         serviceReference.getProperty("translator.language"));
}

This method should be familiar, as it is almost identical to the serviceReferenceShouldExist() method from the Pax Exam example (the name has been changed to accommodate JUnit 3's conventions). Here, it goes directly to the BundleContext to get a ServiceReference for our service. From that ServiceReference, it makes the assertions necessary to satisfy the requirements.

The testServiceReferenceExists() method works using a more conventional programmatic approach to working with OSGi services. But this is a Spring-DM test--we can leverage Spring-DM to declaratively get a reference to the Translator service. To do that, we'll need to create a Spring context configuration that references the service:

<?xml version="1.0" encoding="UTF-8"?> 
<beans:beans
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/osgi"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/osgi
http://www.springframework.org/schema/osgi/spring-osgi.xsd">

<reference id="translator"
    interface="com.habuma.translator.Translator"
    filter="(translator.language=Pig Latin)" />

</beans:beans>

The <reference> element from Spring-DM's configuration namespace creates a bean in the Spring application context that is a proxy to the OSGi service. In this case, the service we're looking for is one that implements com.habuma.translator.Translator and whose "translator.language" property is set to "Pig Latin".

Next, we'll need to override AbstractConfigurableBundleCreatorTests's getConfigLocations() method to tell our test about the Spring configuration file:

@Override
protected String[] getConfigLocations() {
   return new String[] { "bundle-test-context.xml" };
}

The getConfigLocations() method identifies one or more Spring configuration files so that they can be included in the on-the-fly bundle. When a Spring-DM bundle test starts, it will load a Spring application context using the configuration files. In this case, that Spring context will include a reference to a Translator service.

Now we have a Spring context with a reference to a Translator service and our test class knows about the Spring context. The only setup work left to do is to create a property in the test class to hold the reference to the service:

public class PigLatinTranslatorBundleTest 
      extends AbstractConfigurableBundleCreatorTests {

   private Translator translator;
   public void setTranslator(Translator translator) {
      this.translator = translator;
   }

   // ...
}

When the Spring-DM test bundle starts and when its Spring application context is created, the translator property will automatically get set through the setTranslator method, wiring it with the service reference from bundle-test-context.xml. With the Translator service reference in hand, we're ready to test the service:

public void testTranslatorService() {     
   assertNotNull(translator);
   assertEquals("id-DAY is-thAY ork-wAY", 
         translator.translate("Did this work"));
}

The test method starts by asserting that we actually have a translator to work with. Then, we toss it some text to be sure that the translator can properly translate it into Pig Latin.

And that's it. We now have a Spring-DM bundle test that's roughly equivalent to what we wrote last week using Pax Exam. The finished test class, in its entirety, is as follows:

package com.habuma.translator.test;
import org.osgi.framework.ServiceReference;
import org.springframework.osgi.test.AbstractConfigurableBundleCreatorTests;
import com.habuma.translator.Translator;

public class PigLatinTranslatorBundleTest 
      extends AbstractConfigurableBundleCreatorTests {
   private Translator translator;

   public void setTranslator(Translator translator) {
      this.translator = translator;
   }
   
   @Override
   protected String[] getTestBundlesNames() {
      return new String[] {
          "com.habuma.translator, interface, 1.0.0",
          "com.habuma.translator, pig-latin, 1.0.0"
        };
   }   
    
   @Override
   protected String[] getConfigLocations() {
      return new String[] { "bundle-test-context.xml" };
   }
   
   public void testOsgiPlatformStarts() {
      assertNotNull(bundleContext);
   }
   
   public void testServiceReferenceExists() {
      ServiceReference serviceReference = 
         bundleContext.getServiceReference(Translator.class.getName());
      assertNotNull(serviceReference);
      assertEquals("Pig Latin", 
            serviceReference.getProperty("translator.language"));
   }
   
   public void testTranslatorService() {     
      assertNotNull(translator);
      assertEquals("id-DAY is-thAY ork-wAY", 
            translator.translate("Did this work"));
   }
}

Testing options

With Pax Exam, we were able to resolve test bundles using any means available to Pax Runner, including Maven, filesystem, and HTTP URLs. Unfortunately, Spring-DM's testing support is limited to resolving bundles as Maven artifacts. (Although you can opt to override getTestBundles() instead of getTestBundlesNames() to resolve bundles yourself using whatever means you'd like.)

But, we can tweak the OSGi runtime. By default, Spring-DM testing will use Equinox, but you can switch to Felix by overriding the getPlatformName() method:

protected String getPlatformName() {
   return Platforms.FELIX;
}

Or, if you're more of a Knopflerfish kind of person, then:

protected String getPlatformName() {
   return Platforms.KNOPFLERFISH;
}

Unlike Pax Exam, however, Spring-DM testing currently only supports a single version of these frameworks (specifically, Spring-DM 1.2.0 uses Equinox 3.2.2, Felix 1.4.1, or Knopflerfish 2.2.0). And, also unlike Pax Exam, you must choose a single runtime...you can't ask Spring-DM to run your tests on more than one OSGi runtime. (Although you could write separate tests that use different runtimes.)

Should I use Pax Exam or Spring-DM testing?

I'm not going to tell you which testing framework to use--I'll leave it up to you to pick which one suits you best. But I will share my opinions on the subject...maybe it will help you decide.

I like the way that Spring-DM's testing automatically wires in service references for my test to consume so that I don't have to look them up myself in my tests. But, given that Spring-DM's testing support is currently based on JUnit 3, I'm a little turned off from it. Furthermore, given the limitations in bundle resolution and platform selection, I find Spring-DM testing to be somewhat inferior to Pax Exam.

That said, I believe both sides could learn a bit from each other. Spring-DM's testing could obviously be bumped up to be based on JUnit 4 and to be more flexible with regard to bundle resolution and platform selection. Meanwhile, Pax Exam could learn a little from Spring-DM testing on how to wire test properties with OSGi services.

In any event, whether you use Pax Exam or Spring-DM testing, we can all agree that testing is a worthwhile practice. And regardless of which one you use, there's no reason to not extend that good practice into OSGi development.

That's it for this week. I'm still scheming on what I want to write about for next time--I won't tell you everything that I've got in mind, but I can say that I'm thinking of showing you a way to test OSGi-based applications in a way that is difficult (or perhaps even impossible) without OSGi's dynamic runtime. Keep an eye on this blog to get the details.

(2009-08-03 10:00:00.0) Permalink Comments [5]

20090728 Tuesday July 28, 2009

Putting OSGi to the test with Pax Exam

After a small delay, I finally bring you the next installment in my series of OSGi-oriented articles. This time we're going to have a look at writing tests around OSGi bundles.

It should go without saying that testing is an important part of software development. Developer-driven tests are often based on JUnit and usually focus on testing application units in isolation. But it's just as important to write integration tests to ensure that those units play well together. With regard to OSGi, it's important to write tests that put one or more bundles in an OSGi runtime to make sure that those bundles behave as expected.

I made the claim a few months ago that testing OSGi is quite easy. Today I'm going to show that to be true by showing you Pax Exam.

Pax Exam is a testing toolkit that addresses the need for bundle-level testing. What's particularly interesting about Pax Exam is how it works. When a Pax Exam test is run, it starts an OSGi framework, installs and starts a selection of bundles, and then makes an OSGi BundleContext available through which you can make assertions about your bundles, the services that they publish, and the effects that they have on each other.

Before we get started, you should know that I've reworked the previous article's Pig Latin translator project quite a bit. The new version is about translator services in general, of which Pig Latin is one implementation. Some package names have changed and it's now a Pax Construct-based project. I'm not going to go over the new code here, but it should be familiar enough that you should be able figure it out. Download the example code to follow along.

To get started with Pax Exam, we're going to create a separate project to house our bundle tests. There are a lot of ways to do this, but knowing that our bundle-test project is only going to contain a Maven pom.xml file and a single test class, I find it easy enough to take advantage of the Unix mkdir command and its -p option:

translators% mkdir -p bundle-tests-exam/src/test/java/com/habuma/translator/test

That sets up all of the directory structure we need. To fill it out, we're going to place a pom.xml file in the bundle-tests-exam directory and a test class in the bottom level test directory. (Sorry Windows users...I don't think your mkdir has an equivalent of the -p, so you'll have to create the directory structure manually.)

Okay, so let's create the pom.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <modelVersion>4.0.0</modelVersion>
  <groupId>com.habuma.translator</groupId>
  <artifactId>bundle-tests-exam</artifactId>
  <version>1.0.0</version>

  <packaging>jar</packaging>

  <build>
      <plugins>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <configuration>
            <source>1.5</source>
            <target>1.5</target>
          </configuration>
        </plugin>
      </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>com.habuma.translator</groupId>
      <artifactId>interface</artifactId>
      <version>1.0.0</version>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>org.ops4j.pax.exam</groupId>
      <artifactId>pax-exam</artifactId>
      <version>1.0.0</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.ops4j.pax.exam</groupId>
      <artifactId>pax-exam-container-default</artifactId>
      <version>1.0.0</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.ops4j.pax.exam</groupId>
      <artifactId>pax-exam-junit</artifactId>
      <version>1.0.0</version>
      <scope>test</scope>
    </dependency>        

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.5</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

There are several things I'd like to highlight in this pom.xml:

With the Maven setup out of the way, we're now ready to write our test class. PigLatinTranslatorBundleTest uses Pax Exam's JUnit4TestRunner to run a series of OSGi bundle tests.

package com.habuma.translator.test;
import static org.junit.Assert.*;
import static org.ops4j.pax.exam.CoreOptions.*;
import static org.ops4j.pax.exam.container.def.PaxRunnerOptions.*;
import java.util.List;
import java.util.Properties;
import org.junit.Test;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.Inject;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;

import com.habuma.translator.Translator;

@RunWith(JUnit4TestRunner.class)
public class PigLatinTranslatorBundleTest {
   @Inject
   private BundleContext bundleContext;

   private Translator translator;

   @Before
   public void setup() throws Exception {
       translator = retrievePigLatinService();       
   }

   @Configuration
   public static Option[] configuration()
   {      
      return options(equinox(), profile("spring.dm"), provision(
        mavenBundle().groupId("com.habuma.translator").artifactId("interface"),
        mavenBundle().groupId("com.habuma.translator").artifactId("pig-latin")
      ));
   }  

   // ... Test methods go here ...

   private Translator retrievePigLatinService() throws InterruptedException {

      ServiceTracker tracker = new ServiceTracker(bundleContext, 
            Translator.class.getName(), null);
      tracker.open();
      Translator pigLatinService = (Translator) tracker.waitForService(5000);
      tracker.close();
      assertNotNull(pigLatinService);
      return pigLatinService;
   }
}

Probably the most interesting piece of PigLatinTranslatorBundleTests is the configuration() method. It is annotated with @Configuration to indicate that this method will return an array of Pax Exam options--effectively configuring the Pax Exam test case.

Within the configuration() method, I use a set of static methods provided by Pax Exam's CoreOptions and PaxRunnerOptions classes. Specifically, I ask Pax Exam to run the tests within the latest version of Equinox, using Pax Runner's Spring-DM profile. As for the bundles I want to test, I ask it to install the translator project's inteface and pig-latin bundles.

When the Pax Exam test starts up, the first thing it will do is start an OSGi framework (in this case, the latest version of Equinox), install the bundles specified by the Spring-DM profile, and then install our bundles that we want to test. At this point, the OSGi runtime is running, loaded, and ready to roll.

But before we can test our Pig Latin bundle, we're going to need a reference to the Pig Latin service and to the BundleContext. To accomodate that, Pax Exam offers an @Inject annotation to automatically provide the BundleContext to the test. With the BundleContext in hand, the setup() method calls retrievePigLatinService() to lookup the service.

Now we're ready to start testing our bundles. The first thing we should do is make sure that the BundleContext is available. If anything goes wrong while starting the OSGi runtime, we won't have a BundleContext to work with and there'd be no point in testing anything else. So, let's write a test that simply asserts that the bundleContext variable is not null:

@Test
public void bundleContextShouldNotBeNull() throws Exception {
   assertNotNull(bundleContext);
}

Great! Assuming that the BundleContext is available, we can now do something more interesting, such as testing that we get a service that implements the Translator interface that we expect:

@Test
public void serviceReferenceShouldExist() {
   ServiceReference serviceReference = 
      bundleContext.getServiceReference(Translator.class.getName());
   assertNotNull(serviceReference);
   assertEquals("Pig Latin", 
         serviceReference.getProperty("translator.language"));
}   

Here, I'm looking up a service reference for the Translator interface and asserting that the service has been registered with its translator.language property set to "Pig Latin".

If that test passes, then we know that the OSGi runtime is up and running and that there's a service that claims to implement the Translator interface. Finally, let's write one more test just to see that the service does what we think it should do:

@Test
public void shouldTranslateText() {     
   assertNotNull(translator);
   assertEquals("id-DAY is-thAY ork-wAY", 
         translator.translate("Did this work"));
}   

This test doesn't have to be a comprehensive test of the translator's abilities--there should be a unit-test that exercises every corner of the test. This test is just a simple smoke test to be sure that we get a service that meets our expectations.

Testing with different OSGi runtimes

One of the things that makes Pax Exam so powerful is that it's very flexible and can be configured to test bundles using virtually any OSGi frameworks. Under the covers, Pax Exam uses Pax Runner to start up the OSGi runtime, so a Pax Exam-based test can run within pretty much any setup that Pax Runner can provide.

Up until now, we've focused our test on running within the latest version of Equinox. That's what the equinox() option is for. But let's suppose that we want to test with the latest version of Felix instead. No problem, just change the configuration() method:

@Configuration
public static Option[] configuration()
{      
   return options(felix(), profile("spring.dm"), provision(
     mavenBundle().groupId("com.habuma.translator").artifactId("interface"),
     mavenBundle().groupId("com.habuma.translator").artifactId("pig-latin")
   ));
}

Testing with either Equinox or Felix is nice, but it might be helpful to test in the latest version of both Felix and Equinox:

@Configuration
public static Option[] configuration()
{      
   return options(equinox(), felix(), profile("spring.dm"), provision(
     mavenBundle().groupId("com.habuma.translator").artifactId("interface"),
     mavenBundle().groupId("com.habuma.translator").artifactId("pig-latin")
   ));
}

Given this arrangement of OSGi frameworks, the three test methods will be executed twice--once for Equinox and once again for Felix.

Maybe the latest version of the framework(s) isn't what you need to test. Maybe you want to hand select a specific version. How about Equinox 3.4.2 and Knopflerfish 2.3.1?

@Configuration
public static Option[] configuration()
{      
   return options(equinox().version("3.4.2"), knopflerfish().version("2.3.1"), 
     profile("spring.dm"), provision(
       mavenBundle().groupId("com.habuma.translator").artifactId("interface"),
       mavenBundle().groupId("com.habuma.translator").artifactId("pig-latin")
   ));
}

Or maybe you don't want to choose. Maybe you need to know that your bundles work equally well regardless of the OSGi framework that they're installed into. In that case, we might as well test it against all versions of all OSGi frameworks:

@Configuration
public static Option[] configuration()
{      
   return options(allFrameworksVersions(), profile("spring.dm"), provision(
       mavenBundle().groupId("com.habuma.translator").artifactId("interface"),
       mavenBundle().groupId("com.habuma.translator").artifactId("pig-latin")
   ));
}  

When I ran the test against all version of all frameworks, the tests were run against 29 varieties of OSGi runtimes in about four and a half minutes. Maybe that's overkill for your needs and all you need is to know that it works in the latest version of all of the popular OSGi runtimes. In that case, allFrameworks() is the way to go:

@Configuration
public static Option[] configuration()
{      
   return options(allFrameworks(), profile("spring.dm"), provision(
       mavenBundle().groupId("com.habuma.translator").artifactId("interface"),
       mavenBundle().groupId("com.habuma.translator").artifactId("pig-latin")
   ));
}  

Or maybe you don't care about Knopflerfish or Felix...but you need to know for certain that it works for all versions of Equinox:

@Configuration
public static Option[] configuration()
{      
   return options(allEquinoxVersions(), profile("spring.dm"), provision(
       mavenBundle().groupId("com.habuma.translator").artifactId("interface"),
       mavenBundle().groupId("com.habuma.translator").artifactId("pig-latin")
   ));
}  

(Or, if you'd prefer, allFelixVersions() or allKnopflerfishVersions().)

As you can see, you can test your bundles within almost any OSGi runtime. But what about provisioning? Do you have to provision all of the bundles from Maven?

Provisioning options

Given that our bundles are built with Maven, provisioning them from the Maven repository is awful convenient. But it's not the only way to provision bundles for a test. If you'd rather configure a bundle from an HTTP URL, then no problem:

@Configuration
public static Option[] configuration()
{      
   return options(equinox(), profile("spring.dm"), provision(
       bundle("http://www.habuma.com/osgi/bundles/translator-interface.jar"),
       mavenBundle().groupId("com.habuma.translator").artifactId("pig-latin")
   ));
}  

Or perhaps you'd like to pull in a bundle from the filesystem:

@Configuration
public static Option[] configuration()
{      
   return options(equinox(), profile("spring.dm"), provision(
       bundle("http://www.habuma.com/osgi/bundles/translator-interface.jar"),
       bundle("file:/Users/wallsc/bundles/pig-latin-translator.jar")
   ));
}  

By now you should have a strong appreciation for what Pax Exam brings to the table. Using Pax Exam, you can test a selection of bundles within the OSGi framework(s) of your choice in a relatively tight JUnit test. We've seen how to create a test that asserts some basic expectations about our bundles and that acts as a service consumer to assert that the service works as we'd like.

Next time I plan to show you how Spring-DM supports bundle testing. We'll see how Spring-DM comes with testing support that resembles Pax Exam in many ways, but that has its own special Spring twist. And I promise not to let 2-3 months pass between now and that next blog entry.

(2009-07-28 10:00:00.0) Permalink

20090721 Tuesday July 21, 2009

Meanwhile, back at the ranch...

If anyone's looking for me this week, I'll be at the ranch...JavaRanch, that is.

This week, I'm the guest question-answerer in JavaRanch's Application Frameworks forum. I'm fielding questions on OSGi, Spring-DM, Pax, and other such things.

So, stop by, ask some questions, and get a chance to win a free copy of Modular Java.

(2009-07-21 01:27:01.0) Permalink

20090607 Sunday June 07, 2009

Spring-DM RefCard Available for Download

I'm happy to announce that my latest DZone RefCard, Getting Started with Spring-DM is now available for free download.

This RefCard covers all of the basics of working with Spring-DM (and a few advanced tips). In a succinct 6 pages, you'll learn how to...

I'm excited about this RefCard not only because it represents the blending of two of my favorite topics (Spring and OSGi), but also because it provides you with the essentials for working with the SpringSource dm Server and gives you a foundation for what's to come with Blueprint Services in OSGi R4.2.

After downloading the Spring-DM RefCard, if you find yourself wanting more, then I encourage you to check out Modular Java. As I write this, the book is at the printers and should start shipping from Pragmatic in the next few weeks (and will be in other bookstores shortly after that). Even then, if you can't wait, it's also currently available in beta form from Pragmatic or for pre-order from Amazon.com.

(2009-06-07 10:52:47.0) Permalink Comments [3]

20090511 Monday May 11, 2009

This week's article delayed

For the past month, I've posted a new OSGi-related article every Monday morning at around 9am U.S. central time. And so far, it has had quite a strong following of readers. But today no article was published.

Don't panic. This week's article is running a wee bit late. I spent some time this past week putting final touches on Modular Java, so I got a late start on the article. Then my weekend involved some Mother's Day activities followed by myself being a bit under the weather. Altogether, these events added up to me not having this week's article ready in time.

I'm feeling better now and should have this week's article up in a day or two. Just to give you a heads up: I've decided to write a little bit about testing OSGi bundles using Pax Exam. I may also work in a Spring-DM test for comparison's sake. And, as I mentioned last week, there's going to be a few new Pax Construct tricks involved. Stay tuned and thanks for reading!

(2009-05-11 21:57:27.0) Permalink Comments [1]

20090504 Monday May 04, 2009

Pax Runner Profiles and Distributed OSGi

Last week we looked at how Pax Runner makes simple work of starting an OSGi framework (whichever one you want) and loading it up with a selection of bundles. We also saw how easy it is to switch between different OSGi implementations and versions of those implementations. And we saw how flexible Pax Runner can be with how it provisions bundles, whether it be from the file system, from a Maven repository, or from a ZIP file.

This week's article continues the discussion of Pax Runner with a two-for-one deal. First, we're going to take a look at Pax Runner profiles, one of the handiest Pax Runner features. Then we're going to use a profile to setup an environment for us to try out Distributed OSGi, a new feature of OSGi 4.2.

Using Pax Runner Profiles

As we develop OSGi-based applications, there'll always be several third-party bundles that our bundles will depend upon. Although adding bundles to a Pax Runner provisioning scheme is rather simple, sometimes seeking out the bundles we need and making sure we didn't miss anything can be quite a chore. Profiles offer a way to identify a collection of bundles that we'd like Pax Runner to install and start for us without explicitly identifying them one by one.

For example, most every application needs some form of logging. But what bundles do we need to install to support logging in an OSGi application? With Pax Runner's "log" profile, we needn't worry ourselves with figuring out which bundles to install. Instead, we just tell Pax Runner that we want logging by starting it like this:

sandbox% pax-run.sh --p=e --profiles=log

Here I've given Pax Runner two command line options. The first, --p=e is just a shorthand version of --platform=equinox that we discussed last week. The second option is the one that's the most interesting. It tells Pax Runner that we want it to use its "log" profile to install and start whatever bundles are necessary to support logging in an OSGi application. What bundles does it install? Well, if we issue Equinox's ss command, we'll see them:

osgi> ss

Framework is launched.

id	State       Bundle
0	ACTIVE      org.eclipse.osgi_3.4.3.R34x_v20081215-1030
1	ACTIVE      org.eclipse.osgi.util_3.1.300.v20080303
2	ACTIVE      org.eclipse.osgi.services_3.1.200.v20070605
3	ACTIVE      org.ops4j.pax.logging.pax-logging-api_1.3.0
4	ACTIVE      org.ops4j.pax.logging.pax-logging-service_1.3.0

osgi> 

Bundles 3 and 4 represent Pax Logging, an implementation of the OSGi Logging Service with an API that mimics several popular logging frameworks such as Log4J or Commons Logging. We'll talk more about Pax Logging later. But for now its enough to know that we didn't need need to seek out Pax Logging to enable logging in our application. We just needed to tell Pax Runner to use its "log" profile.

The "log" profile is only one of many profiles available for Pax Runner. For a complete list of available profiles, visit http://paxrunner.ops4j.org/display/paxrunner/Pax+Runner+profiles+list.

Now let's try something a bit more interesting than simply adding a couple of bundles to support logging. The release of OSGi R4.2 is just around the corner and one of the new features in it is a thing called Distributed OSGi. In a nutshell, Distributed OSGi (or D-OSGi for short) offers a way to automatically publish OSGi services as web services. There's already an implementation of D-OSGi available from the Apache CXF project.

Let's say that we want to use D-OSGi to create a web service based in OSGi. To use the CXF implementation of D-OSGi, we'd need several dozen bundles installed as a prerequisite. That could be tedious and prone to human error (what if we forget a bundle or accidentally install an incompatible version?). But with Pax Runner's "cxf.dosgi" profile, it's no big deal to get started with D-OSGi:

sandbox% pax-run.sh --p=e --profiles=cxf.dosgi

Once the OSGi framework has started, we can get a short status (ss) to see what the profile has given us:

osgi> ss

Framework is launched.

id	State       Bundle
0	ACTIVE      org.eclipse.osgi_3.4.3.R34x_v20081215-1030
1	ACTIVE      org.eclipse.osgi.util_3.1.300.v20080303
2	ACTIVE      org.eclipse.osgi.services_3.1.200.v20070605
3	ACTIVE      org.apache.geronimo.specs.geronimo-annotation_1.0_spec_1.1.1
4	ACTIVE      org.apache.geronimo.specs.geronimo-activation_1.1_spec_1.0.2
5	ACTIVE      org.apache.geronimo.specs.geronimo-javamail_1.4_spec_1.2.0
6	ACTIVE      org.apache.geronimo.specs.geronimo-ws-metadata_2.0_spec_1.1.2
7	ACTIVE      com.springsource.org.jdom_1.0.0
8	ACTIVE      org.ops4j.pax.logging.pax-logging-api_1.3.0
9	ACTIVE      org.ops4j.pax.logging.pax-logging-service_1.3.0
10	ACTIVE      org.ops4j.pax.web.service_0.5.2
11	ACTIVE      com.springsource.org.aopalliance_1.0.0
12	ACTIVE      org.springframework.aop_2.5.6
13	ACTIVE      org.springframework.beans_2.5.6
14	ACTIVE      org.springframework.context_2.5.6
15	ACTIVE      org.springframework.context.support_2.5.6
16	ACTIVE      org.springframework.core_2.5.6
17	ACTIVE      com.springsource.org.objectweb.asm_2.2.3
18	ACTIVE      com.springsource.edu.emory.mathcs.backport_3.1.0
19	ACTIVE      com.springsource.net.sf.cglib_2.1.3
20	ACTIVE      org.springframework.osgi.extensions.annotations_1.2.0
21	ACTIVE      org.springframework.osgi.core_1.2.0
22	ACTIVE      org.springframework.osgi.extender_1.2.0
23	ACTIVE      org.springframework.osgi.io_1.2.0
24	ACTIVE      org.apache.servicemix.bundles.jaxb-impl_2.1.6.1
25	ACTIVE      org.apache.servicemix.bundles.wsdl4j_1.6.1.1
26	ACTIVE      org.apache.servicemix.bundles.xmlsec_1.3.0.1
27	ACTIVE      org.apache.servicemix.bundles.wss4j_1.5.4.1
28	ACTIVE      org.apache.servicemix.bundles.xmlschema_1.4.2.1
29	ACTIVE      org.apache.servicemix.bundles.asm_2.2.3.1
30	ACTIVE      org.apache.servicemix.bundles.xmlresolver_1.2.0.1
31	ACTIVE      org.apache.servicemix.bundles.neethi_2.0.4.1
32	ACTIVE      org.apache.servicemix.bundles.woodstox_3.2.7.1
33	ACTIVE      org.apache.servicemix.specs.saaj-api-1.3_1.1.1
34	ACTIVE      org.apache.servicemix.specs.stax-api-1.0_1.1.1
35	ACTIVE      org.apache.servicemix.specs.jaxb-api-2.1_1.1.1
36	ACTIVE      org.apache.servicemix.specs.jaxws-api-2.1_1.1.1
37	ACTIVE      org.apache.cxf.cxf-bundle-minimal_2.2.0.SNAPSHOT
38	ACTIVE      cxf-dosgi-ri-discovery-local_1.0.0.SNAPSHOT
39	INSTALLED   cxf-dosgi-ri-dsw-cxf_1.0.0.SNAPSHOT

osgi> 

Wow! Just because we specified the "cxf.dosgi" profile, we were given three dozen bundles (in addition to the core Equinox bundles)! That was pretty easy, wasn't it?

Well, although it is impressive that we got just over three dozen bundles installed just by asking for the "cxf.dosgi" profile, there is one small problem. Notice that bundle 39 hasn't been started. That's because CXF depends on a new hook functionality that isn't part of the latest released version of Equinox. But, those hooks are part of the latest and greatest snapshot of Equinox and we're only using the latest released version.

This has nothing to do with profiles, but it gives me a chance to show you one more Pax Runner trick. Let's tell Pax Runner to go to the bleeding edge and use the latest snapshot of Equinox:

sandbox% pax-run.sh --p=e --profiles=cxf.dosgi --snapshot

The --snapshot option tells Pax Runner to use the latest snapshot version of the target OSGi framework. In this case, I'm asking for the latest non-released version of Equinox, which is 3.5 M7 as I write this.

A quick review of the installed bundles (using ss) indicates that all of the CXF D-OSGi bundles (and their dependencies) are installed and ready for us to work with D-OSGi.

And that concludes today's fun with Pax Runner profiles. I could put a bow on this article and start thinking about what I'm going to write for next week, but...as long as we have D-OSGi support in place, let's see what we can do with it.

Publishing Distributed OSGi Services

To demonstrate the power of D-OSGi we're going to create two bundles. One will provide a simple OSGi service that we'll ask D-OSGi to publish as a web service. The other bundle will contain the interface for that service.

Although it's not strictly necessary that an OSGi service implementation and its interface be packaged in separate bundles, it makes a lot of sense to do so, especially when using D-OSGi. That's because we'll want to distribute the interface bundle (separate from the implementation) along with any clients that use it.

Let's start with the service interface:

package com.osgiknowhow.sandbox;

public interface PigLatinService {
    String translate(String text);
}

As you can see, we're going to build a service that translates phrases into Pig Latin. It's a fairly simple service with a single method that takes a String and returns a translated String. Don't worry, though...as far as I know, there's no way that you can contract swine flu by working with this example.

If you've downloaded the example code, you'll find the interface bundle in the "piginterface" project. You can build it using Maven like this:

piginterface% mvn install

You've probably noticed that although we're building an OSGi bundle, I've not shown you how to define the META-INF/MANIFEST.MF file. That's because I'm using the Felix Bundle Plugin for Maven to automatically generate the manifest for me. Here's what it produced for the interface bundle:

Manifest-Version: 1.0
Bundle-Name: Pig Latin Service Interface
Built-By: wallsc
Build-Jdk: 1.5.0_16
Created-By: Apache Maven Bundle Plugin
Import-Package: com.osgiknowhow.sandbox
Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.osgiknowhow.sandbox.pig-latin-service-interface
Tool: Bnd-0.0.255
Bnd-LastModified: 1241320637536
Export-Package: com.osgiknowhow.sandbox
Bundle-Version: 1.0.0

Again, just to be clear, we don't have to write the manifest file ourselves. Let Maven and the bundle plugin do all of the work.

Now for the implementation bundle and the service implementation class:

package com.osgiknowhow.sandbox.internal;

import com.osgiknowhow.sandbox.PigLatinService;

public class PigLatinServiceImpl implements PigLatinService {
    private final String VOWELS = "AEIOUaeiou";
    
    public String translate(String text) {
        String[] words = text.split("\\s");
        String result = "";
        
        for(String word : words) {
            result += translateWord(word) + " ";
        }
        
        return result.trim();
    }
    
    private String translateWord(String word) {
        StringBuffer result = new StringBuffer();
        String start = "";
        for(int i=0; i<word.length(); i++) {
            char c = word.charAt(i);
            if(VOWELS.indexOf(c) >= 0) {
                start = word.substring(i);
                break;
            }
            result.append(c);
        }
        
        return start + "-" + result + "AY";
    }
}

Aside from the translation logic, there's nothing very special about the PigLatinServiceImpl. It's just a POJO that implements the PigLatinService interface.

Now we need to publish an instance of PigLatinServiceImpl service into the OSGi service registry. There are several ways we could do this, including writing a bundle activator, using OSGi Declarative Services, or even IPOJO. But since I'm a big fan of Spring-DM, I'm going to use that to publish the service. (Besides, in case you didn't notice, we already have Spring-DM installed for us thanks to the "cxf.dosgi" profile).

To do that, let's create a Spring application context definition that declares PigLatinServiceImpl as a Spring bean:

<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

  <bean id="pigLatinService"
      class="com.osgiknowhow.sandbox.internal.PigLatinServiceImpl" />

</beans>

If you're familiar with Spring, you'll recognize that this is a very basic Spring configuration file that declares a single bean in the Spring context. Now let's create a companion Spring context definition file that declares this bean to be an OSGi service:

<beans:beans xmlns="http://www.springframework.org/schema/osgi" 
       xmlns:beans="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xsi:schemaLocation="http://www.springframework.org/schema/osgi  
            http://www.springframework.org/schema/osgi/spring-osgi.xsd
            http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

  <service ref="pigLatinService" 
       interface="com.osgiknowhow.sandbox.PigLatinService" />

</beans:beans>

The <service> element is part of Spring-DM's configuration schema and simply tells Spring-DM to publish the bean identified by the ref attribute into the OSGi service registry under the interface specified by the interface attribute.

Note that it's a best practice to keep the standard Spring bean declarations in one Spring context definition file and the Spring-DM details in a separate file. That's so that the Spring beans can be used in a non-OSGi context (such as integration tests) without involving Spring-DM or an OSGi framework. In this case, the standard Spring bean declaration is in a file named pig-latin-context.xml and the Spring-DM configuration is in a file named pig-latin-osgi.xml. Both files are placed in the bundle's META-INF/spring folder so that the Spring-DM extender can find them.

As it stands, if we were to build and deploy the service bundle, the bean would be published as an OSGi service, but would not be exposed as a D-OSGi web service. That's because we haven't added the tiny bit of D-OSGi magic to make that happen. Let's add it now:

    <service ref="pigLatinService" 
         interface="com.osgiknowhow.sandbox.PigLatinService">
      <service-properties>
        <beans:entry key="osgi.remote.interfaces" value="*" />
      </service-properties>
    </service>

By setting the osgi.remote.interfaces service property to "*", we're telling the D-OSGi implementation (CXF in our case) that we want this service to be exposed as a web service using all of the interfaces that the service is published under (right now, that's only the PigLatinService interface).

Now we're ready to deploy the bundle to see if it works. The first step is to build it:

pigservice% mvn install

Again, the service bundle project leans on the Felix Bundle Plugin for Maven to automatically produce a manifest for the bundle. In case you're curious, here's what it came up with for the implementation bundle:

Manifest-Version: 1.0
Built-By: wallsc
Created-By: Apache Maven Bundle Plugin
Import-Package: com.osgiknowhow.sandbox
Bnd-LastModified: 1241145464887
Export-Package: com.osgiknowhow.sandbox
Bundle-Version: 1.0.0
Bundle-Name: Pig Latin Service
Build-Jdk: 1.5.0_16
Private-Package: com.osgiknowhow.sandbox.internal
Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.osgiknowhow.sandbox.pig-latin-service
Tool: Bnd-0.0.255

With the bundle having been built, let's start Pax Runner. We'll need to be sure to use the "cxf.dosgi" profile as before and instruct it to load our service and interface bundles:

piglatin% pax-run.sh --p=e --profiles=cxf.dosgi --snapshot pigservice/target/pig-latin-service-1.0.0.jar piginterface/target/pig-latin-service-interface-1.0.0.jar

After it starts up, you should be able to point your browser at http://localhost:9000/com/osgiknowhow/sandbox/PigLatinService?wsdl to see that the service is being published. Notice that the URL of the web service is derived from the exported package name. But we can control that by setting a few more properties:

    <service ref="pigLatinService" 
         interface="com.osgiknowhow.sandbox.PigLatinService">
      <service-properties>
        <beans:entry key="osgi.remote.interfaces" value="*" />
        <beans:entry key="osgi.remote.configuration.type" value="pojo"/>
        <beans:entry key="osgi.remote.configuration.pojo.address" 
            value="http://localhost:9000/piglatin"/>        
      </service-properties>
    </service>

Configured this way, the new URL for the service is http://localhost:9000/piglatin (meaning that you can see its WSDL at http://localhost:9000/piglatin?wsdl).

Consuming Services with D-OSGi

Now that we've seen how to expose OSGi services as web service using D-OSGi, it would seem that our natural next step is to write a client to consume our Pig Latin translating web service. To illustrate client-side D-OSGi, we'll start by creating an all new bundle project for the client. Within that bundle, we'll create the client class:

package com.osgiknowhow.sandbox.client;

import com.osgiknowhow.sandbox.PigLatinService;

public class PigLatinClient {
    public void tryIt() {
        System.out.println("TRANSLATED:  " + 
            pigLatinService.translate("Dogs hate cats"));
    }

    private PigLatinService pigLatinService;
    public void setPigLatinService(PigLatinService pigLatinService) {
        this.pigLatinService = pigLatinService;
    }
}

The tryIt() method is where all of the fun happens. It makes a call to the PigLatinService's translate() to translate a sample set of text. Notice that PigLatinClient is injected (through a setter method) with a PigLatinService. The details of where that PigLatinService are forthcoming, but first let's see how we wire this bean in the Spring application context.

Within the client bundle's META-INF/spring, I've created a pig-latin-context.xml file. This is a simple Spring context definition file that wires up the PigLatinClient bean:

<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

  <bean id="pigLatinClient"
      class="com.osgiknowhow.sandbox.client.PigLatinClient" 
      init-method="tryIt">
    <property name="pigLatinService" ref="pigLatinService" />
  </bean>

</beans>   

The pigLatinClient is fairly straightforward. Its pigLatinService property is wired with a reference to a bean whose ID is "pigLatinService". What makes it a little special is that it has an init-method that triggers the tryIt() method upon bean creation.

As for the "pigLatinService" bean, it is defined in a separate META-INF/spring/pig-latin-osgi.xml file:

<beans:beans xmlns="http://www.springframework.org/schema/osgi" 
       xmlns:beans="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xsi:schemaLocation="http://www.springframework.org/schema/osgi  
            http://www.springframework.org/schema/osgi/spring-osgi.xsd
            http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

  <reference id="pigLatinService" 
       interface="com.osgiknowhow.sandbox.PigLatinService" />

</beans:beans>

Here I'm using Spring-DM's <reference> element to declare a bean whose ID is "pigLatinService" and whose implementation can be found in the OSGi service registry under the com.osgiknowhow.sandbox.PigLatinService interface.

The big question here: Where does the pigLatinService bean come from? Spring-DM assumes that it will be available in the OSGi service registry. But how does it get there? Remember: The "D" in D-OSGi standards for "distributed". That means that our service will likely be in a completely different OSGi runtime than the client. So how does the remote service wind up in the client's OSGi service registry?

Well, that magic is accomplished by adding one more file to the client bundle: OSGI-INF/remote-service/remote-services.xml:

<service-descriptions xmlns="http://www.osgi.org/xmlns/sd/v1.0.0">
 <service-description>
  <provide interface="com.osgiknowhow.sandbox.PigLatinService"/>
  <property name="osgi.remote.interfaces">*</property>
  <property name="osgi.remote.configuration.type">pojo</property>
  <property name="osgi.remote.configuration.pojo.address">http://localhost:9000/piglatin</property>
 </service-description>
</service-descriptions>

This file tells the D-OSGi implementation that this bundle will be consuming a service that is actually a web service hosted elsewhere. D-OSGi will take this file and make sure that there's a proxy to the remote service in the OSGi service registry so that when Spring-DM comes looking for com.osgiknowhow.sandbox.PigLatinService it will be able to find it.

Okay, all that's left to do is build everything and fire up the service and client bundles, each in a separate OSGi runtime. First build it:

pigclient% mvn install

One more time, I'm counting on the Felix bundle plugin to do the heavy lifting with regard to the bundle manifest. This time, it generated the following META-INF/MANIFEST.MF file:

Manifest-Version: 1.0
Bundle-Name: Pig Latin Client
Built-By: wallsc
Build-Jdk: 1.5.0_16
Private-Package: com.osgiknowhow.sandbox.client
Created-By: Apache Maven Bundle Plugin
Import-Package: com.osgiknowhow.sandbox
Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.osgiknowhow.sandbox.pig-latin-client
Tool: Bnd-0.0.255
Bnd-LastModified: 1241320097283
Bundle-Version: 1.0.0

Next we'll start up the client OSGi runtime in a way similar to how we started the service runtime:

piglatin% pax-run.sh --p=e --profiles=cxf.dosgi --snapshot pigservice/target/pig-latin-client-1.0.0.jar piginterface/target/pig-latin-service-interface-1.0.0.jar

If it wasn't clear before, it should be clear now why the interface is kept in a separate bundle. The client bundle needs the interface bundle available so that it can import the service interface package. If it were in the implementation bundle, we'd have to deploy the service bundle alongside the client bundle, which would completely defeat any purpose for using D-OSGi.

At any rate, as the client bundle starts, Spring-DM will try to resolve the service from the OSGi service registry. Thanks to the remote-services.xml file, it will find the service, which is actually just a proxy to the remote service hosted in another OSGi runtime. With a reference to the service in hand, it calls the translate() method and prints the following:

TRANSLATED:  ogs-DAY ate-hAY ats-cAY

And that brings us to the end of this week's adventure in OSGi. To recap, we've looked at two different subjects this week:


I hope that you're enjoying my weekly OSGi articles. I've got a long list of potential topics to write about. Next week, I think I'll revisit Pax Construct, looking at a few features that we didn't see the last time I wrote about it. But I'm also open to suggestions. If there's any OSGi-related topic that you'd like me to discuss here, feel free to ask. I make no promises, but I'll try to add it to my list for a future article.

(2009-05-04 10:00:00.0) Permalink Comments [5]

20090427 Monday April 27, 2009

Launching OSGi with Pax Runner

In last week's article, we saw how to go from zero to working OSGi bundles quickly using Pax Construct. We've only scratched the surface of Pax Construct and we'll look at it some more in a future article. This week, however, we're going to uncover the magic behind Pax Construct's pax-provision script: Pax Runner.

OSGi Without Pax Runner

In the classic Christmas movie, It's a Wonderful Life, the protagonist George Bailey gets a chance to see what the world would've been like without him. Similarly, to gain an appreciation of what Pax Runner offers, we're going to start by seeing what a world without Pax Runner is like.

Let's say that you wanted to start Equinox and to install and start our hello world bundle from last week. First, you'd need to download Equinox. Assuming that you choose Equinox 3.4 (the latest release), then we'd start Equinox on the command line like this:

~/equinox% java -jar org.eclipse.osgi_3.4.0.v20080605-1900.jar 
~/equinox% 

Oops! That didn't do much of anything. That's because Equinox has its console turned off by default and without any other bundles installed yet, it shuts down almost as soon as it starts. No problem--we'll just start it with the console turned on:

~/equinox% java -jar org.eclipse.osgi_3.4.0.v20080605-1900.jar -console

osgi> 

Okay, this time we get the osgi> prompt, which is the Equinox console. From here we can do all sorts of things, including getting a short status of all of the bundles installed:

osgi> ss

Framework is launched.

id	State       Bundle
0	ACTIVE      org.eclipse.osgi_3.4.0.v20080605-1900

osgi> 

As you can see, there's not a lot installed. The only bundle available is the Equinox runtime itself. So that means that our next step will be to install our hello world bundle. For that, Equinox provides the install command:

osgi> install file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar
Bundle id is 1

osgi>  

Of course, unless you coincidentally have your path setup exactly like mine, you'll need to adjust the file: URL appropriately. Optionally, you can install the bundle using an http: URL:

osgi> http://www.habuma.com/osgi/bundles/hello-simple-1.0-SNAPSHOT.jar
Bundle id is 1

osgi>  

The install command takes a URL that points to a bundle JAR file to install. The URL can either be a file: or http: URL. In case you're wondering...yes, the bundle is available at that http: URL.

Now the hello world bundle is installed, but it hasn't been started. Let's start it:

osgi> start 1
Hello World!

osgi> 

There's our "Hello World!" greeting, indicating that it worked. Now let's review the steps we took to get to this point:

  1. Downloaded Equinox
  2. Started Equinox (with console on)
  3. Installed a bundle
  4. Started a bundle

That wasn't so difficult. But then again, we only installed one bundle. Imagine what it may have been like to install several dozen bundles. And what would it take to try this all over again with a different version of Equinox or even a different OSGi implementation like Felix or Knopflerfish? It seems like we'd have to go through most or all of those same steps again while dealing with the slight differences between the various OSGi implementations.

And that's where Pax Runner shines.

Quick start OSGi with Pax Runner

Pax Runner, put simply, is a OSGi framework launcher. But don't let that concise description fool you--There's a lot more to Pax Runner than meets the eye. Per the Pax Runner home page, Pax Runner is great if you want to change from OSGi platform to another, you're new to OSGi and want to spend a short time checking it out, you don't want to be bothered with the setup and requirements of each OSGi platform. In short, Pax Runner makes it easy to start any OSGi platform with any selection of bundles...and then switch to another platform and bundle selection painlessly.

To get started with Pax Runner, first download it (I'm using version 0.18.0) and unzip it. On my MacBook Pro, that looks like this:

pax% unzip ~/Downloads/pax-runner-assembly-0.18.0-jdk15.zip 
Archive:  /Users/wallsc/Downloads/pax-runner-assembly-0.18.0-jdk15.zip
   creating: pax-runner-0.18.0/
   creating: pax-runner-0.18.0/bin/
  inflating: pax-runner-0.18.0/bin/pax-runner-0.18.0.jar  
  inflating: pax-runner-0.18.0/bin/pax-run.bat  
  inflating: pax-runner-0.18.0/bin/pax-run.sh  
pax%

As you can see, Pax Runner comes with a JAR file, an MS-DOS batch file, and a Unix shell script. I'll be using the Unix shell script in this article, but if you're on Windows then the batch file is for you. In either event, add the bin directory to your system path and you're ready to roll with Pax Runner.

We'll start with baby steps. Let's run Pax Runner to see what we get out of the box:

~/osgi-fun% pax-run.sh 
    ______  ________  __  __
   / __  / /  __   / / / / /
  /  ___/ /  __   / _\ \ _/
 /  /    /  / /  / / _\ \
/__/    /__/ /__/ /_/ /_/

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

 -> Using config [classpath:META-INF/runner.properties]
 -> Using only arguments from command line
 -> Preparing framework [Felix 1.6.0]
 -> Downloading bundles...
 -> Felix 1.6.0 : 368418 bytes @ [ 601kBps ]
 -> org.osgi.compendium (4.1.0) : 514214 bytes @ [ 5194kBps ]
 -> org.apache.felix.shell (1.2.0) : 59114 bytes @ [ 687kBps ]
 -> org.apache.felix.shell.tui.plugin (1.2.0) : 12455 bytes @ [ 1245kBps ]
 -> Using execution environment [J2SE-1.6]
 -> Runner has successfully finished his job!


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

-> 

As you can see, Pax Runner start Felix 1.6.0. If you issue the ps command, you'll see that it's a fairly basic Felix runtime:

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

At this point, we could use the install command to install a bundle. But that wouldn't be any different than how we'd do it without Pax Runner. So, get out of Felix (issue the shutdown command) and then run Pax Runner like this:

~/osgi-fun% pax-run.sh file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar
    ______  ________  __  __
   / __  / /  __   / / / / /
  /  ___/ /  __   / _\ \ _/
 /  /    /  / /  / / _\ \
/__/    /__/ /__/ /_/ /_/

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

 -> Using config [classpath:META-INF/runner.properties]
 -> Using only arguments from command line
 -> Scan bundles from [file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar]
 -> Scan bundles from [scan-bundle:file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar]
 -> Provision bundle [file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar, at default start level, bundle will be started, bundle will be loaded from the cache]
 -> Preparing framework [Felix 1.6.0]
 -> Downloading bundles...
 -> file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar : 3677 bytes @ [ 1838kBps ]
 -> Using execution environment [J2SE-1.6]
 -> Runner has successfully finished his job!


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

-> Hello World!

With one simple command line entry, we were able to start Felix, install our hello world bundle, and start that bundle.

Now let's try that with Equinox instead of Felix. To do that, all we need to do is add a new parameter to the command line:

~/osgi-fun% pax-run.sh --platform=equinox file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar
    ______  ________  __  __
   / __  / /  __   / / / / /
  /  ___/ /  __   / _\ \ _/
 /  /    /  / /  / / _\ \
/__/    /__/ /__/ /_/ /_/

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

 -> Using config [classpath:META-INF/runner.properties]
 -> Using only arguments from command line
 -> Scan bundles from [file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar]
 -> Scan bundles from [scan-bundle:file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar]
 -> Provision bundle [file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar, at default start level, bundle will be started, bundle will be loaded from the cache]
 -> Preparing framework [Equinox 3.4.2]
 -> Downloading bundles...
 -> Equinox 3.4.2 (v20081215-1030) : 997240 bytes @ [ 493kBps ]
 -> Eclipse utilities : 22755 bytes @ [ 948kBps ]
 -> Eclipse compendium services : 63704 bytes @ [ 2275kBps ]
 -> Using execution environment [J2SE-1.6]
 -> Runner has successfully finished his job!


osgi> Hello World!

And just like that, Pax Runner started Equinox 3.4.2 then installed and started the hello world bundle.

Wanna try Knopflerfish?

~/osgi-fun% pax-run.sh --platform=knopflerfish file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar
    ______  ________  __  __
   / __  / /  __   / / / / /
  /  ___/ /  __   / _\ \ _/
 /  /    /  / /  / / _\ \
/__/    /__/ /__/ /_/ /_/

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

 -> Using config [classpath:META-INF/runner.properties]
 -> Using only arguments from command line
 -> Scan bundles from [file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar]
 -> Scan bundles from [scan-bundle:file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar]
 -> Provision bundle [file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar, at default start level, bundle will be started, bundle will be loaded from the cache]
 -> Preparing framework [Knopflerfish 2.2.0]
 -> Downloading bundles...
 -> Knopflerfish 2.2.0 : 335103 bytes @ [ 246kBps ]
 -> org.osgi.compendium : 689150 bytes @ [ 1534kBps ]
 -> org.knopflerfish.bundle.console : 36334 bytes @ [ 46kBps ]
 -> org.knopflerfish.bundle.consoletty : 6170 bytes @ [ 1028kBps ]
 -> org.knopflerfish.bundle.frameworkcommands : 26090 bytes @ [ 147kBps ]
 -> Using execution environment [J2SE-1.6]
 -> Runner has successfully finished his job!

Knopflerfish OSGi framework, version 4.0.12
Copyright 2003-2008 Knopflerfish. All Rights Reserved.

See http://www.knopflerfish.org for more information.
Loading xargs url file:knopflerfish/config.ini
Installed and started: file:bundles/osgi.compendium_4.0.1.jar (id#1)
Installed and started: file:bundles/org.knopflerfish.bundle.console_2.0.1.jar (id#2)
Installed and started: file:bundles/org.knopflerfish.bundle.consoletty-IMPL_2.0.0.jar (id#3)
Installed and started: file:bundles/org.knopflerfish.bundle.frameworkcommands-IMPL_2.0.5.jar (id#4)
Installed and started: file:bundles/com.habuma.osgi.helloworld.hello-simple_1.0.0.SNAPSHOT.jar (id#5)
> Hello World!
Framework launched

> 

How about Concierge?

~/osgi-fun% pax-run.sh --platform=concierge file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar
    ______  ________  __  __
   / __  / /  __   / / / / /
  /  ___/ /  __   / _\ \ _/
 /  /    /  / /  / / _\ \
/__/    /__/ /__/ /_/ /_/

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

 -> Using config [classpath:META-INF/runner.properties]
 -> Using only arguments from command line
 -> Scan bundles from [file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar]
 -> Scan bundles from [scan-bundle:file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar]
 -> Provision bundle [file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar, at default start level, bundle will be started, bundle will be loaded from the cache]
 -> Preparing framework [Concierge 1.0.0]
 -> Downloading bundles...
 -> Concierge 1.0 (RC2) : 86089 bytes @ [ 87kBps ]
 -> ch.ethz.iks.concierge.service.tracker : 8635 bytes @ [ 1233kBps ]
 -> ch.ethz.iks.concierge.shell : 16007 bytes @ [ 1000kBps ]
 -> Using execution environment [J2SE-1.6]
 -> Runner has successfully finished his job!

---------------------------------------------------------
  Concierge OSGi 1.0_RC1 on Mac OS X 10.5.6 starting ...
---------------------------------------------------------
INSTALLING file:bundles/1296071065_1.0.0.RC2.jar
INSTALLING file:bundles/1441349545_1.0.0.RC2.jar
INSTALLING file:bundles/com.habuma.osgi.helloworld.hello-simple_1.0.0.SNAPSHOT.jar
STARTING file:bundles/1296071065_1.0.0.RC2.jar
STARTING file:bundles/1441349545_1.0.0.RC2.jar

Concierge> STARTING file:bundles/com.habuma.osgi.helloworld.hello-simple_1.0.0.SNAPSHOT.jar
Hello World!
---------------------------------------------------------
  Framework started in 0.042 seconds.
---------------------------------------------------------


Concierge> 

As you can see, switching between OSGi platforms is a piece of cake with Pax Runner. Switching between versions of a platform is just as easy using the --version parameter. Let's say that we wanted to try this out with an older version of Equinox:

~/osgi-fun% pax-run.sh --platform=equinox --version=3.2.1 file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar
    ______  ________  __  __
   / __  / /  __   / / / / /
  /  ___/ /  __   / _\ \ _/
 /  /    /  / /  / / _\ \
/__/    /__/ /__/ /_/ /_/

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

 -> Using config [classpath:META-INF/runner.properties]
 -> Using only arguments from command line
 -> Scan bundles from [file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar]
 -> Scan bundles from [scan-bundle:file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar]
 -> Provision bundle [file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar, at default start level, bundle will be started, bundle will be loaded from the cache]
 -> Preparing framework [Equinox 3.2.1]
 -> Downloading bundles...
 -> Equinox 3.2.1 (R32x_v20060717) : 847974 bytes @ [ 438kBps ]
 -> Eclipse utilities : 15016 bytes @ [ 1001kBps ]
 -> Eclipse compendium services : 51712 bytes @ [ 1149kBps ]
 -> Using execution environment [J2SE-1.6]
 -> Runner has successfully finished his job!


osgi> Hello World!

Provisioning bundles from the URLs on the command line is only one option that Pax Runner offers. Altogether, there are 8 ways that Pax Runner can be provisioned:

I'm not going to go into detail on all of these provisioning options. But let's have a look at a few of the ones I use most often. We've already seen how to provision from a URL, so let's see how to provision from the file system, from a ZIP file, and from a text file.

Provisioning from the file system

Rather than explicitly listing all of the bundles that we want provisioned on the command line, we can point Pax Runner at a directory in the file system and have it load all bundles from that directory.

For example, let's create a directory called mybundles and copy our hello world bundle into the directory:

~/osgi-fun% mkdir mybundles
~/osgi-fun% cp ~/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar mybundles
~/osgi-fun% 

Now we can start Pax Runner like this:

~/osgi-fun% pax-run.sh mybundles
    ______  ________  __  __
   / __  / /  __   / / / / /
  /  ___/ /  __   / _\ \ _/
 /  /    /  / /  / / _\ \
/__/    /__/ /__/ /_/ /_/

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

 -> Using config [classpath:META-INF/runner.properties]
 -> Using only arguments from command line
 -> Scan bundles from [scan-dir:mybundles]
 -> Provision bundle [file:/Users/wallsc/osgi-fun/mybundles/hello-simple-1.0-SNAPSHOT.jar, at default start level, bundle will be started, bundle will be loaded from the cache]
 -> Preparing framework [Felix 1.6.0]
 -> Downloading bundles...
 -> Using execution environment [J2SE-1.6]
 -> Runner has successfully finished his job!


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

-> Hello World!

-> 

Starting Pax Runner this way tells it to look in the mybundles directory and to install/start all bundles it finds in there. In this case, that's only our hello world bundle. But if that directory were chock full of bundles, Pax Runner would install and start every one of them.

Provisioning from a ZIP file

Provisioning from a ZIP file isn't much different than provisioning from a directory. The main difference is that all of the bundles are packaged into a single ZIP file. This makes it convenient to distribute a selection of bundles for easy installation into an OSGi platform.

To try it out, let's create a ZIP file containing the hello world bundle:

~/osgi-fun% zip mybundles.zip hello-simple-1.0-SNAPSHOT.jar 
  adding: hello-simple-1.0-SNAPSHOT.jar (deflated 43%)
~/osgi-fun% 

Now we can kick off Pax Runner like this:

~/osgi-fun% pax-run.sh mybundles.zip
    ______  ________  __  __
   / __  / /  __   / / / / /
  /  ___/ /  __   / _\ \ _/
 /  /    /  / /  / / _\ \
/__/    /__/ /__/ /_/ /_/

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

 -> Using config [classpath:META-INF/runner.properties]
 -> Using only arguments from command line
 -> Scan bundles from [mybundles.zip]
 -> Scan bundles from [scan-dir:file:/Users/wallsc/osgi-fun/mybundles.zip]
 -> Provision bundle [jar:file:/Users/wallsc/osgi-fun/mybundles.zip!/hello-simple-1.0-SNAPSHOT.jar, at default start level, bundle will be started, bundle will be loaded from the cache]
 -> Preparing framework [Felix 1.6.0]
 -> Downloading bundles...
 -> jar:file:/Users/wallsc/osgi-fun/mybundles.zip!/hello-simple-1.0-SNAPSHOT.jar : 3677 bytes @ [ 306kBps ]
 -> Using execution environment [J2SE-1.6]
 -> Runner has successfully finished his job!


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

-> Hello World!

-> 

Again, the mybundles.zip file contains only a single bundle. But like directory scanning, Pax Runner's ZIP file scanning can pull in and start as many bundles as are contained in the ZIP file.

Provisioning from a text file

So far we've started Pax Runner by specifying a URL to a bundle JAR, a directory, and a ZIP file. We can also start it by giving it a flat text file containing a list of URLs to install bundles from. For example let's create a file named mybundles.txt and put a reference to our hello world bundle in it:

file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar

Then, let's start Pax Runner, giving it this text file:

~/osgi-fun% pax-run.sh mybundles.txt 
    ______  ________  __  __
   / __  / /  __   / / / / /
  /  ___/ /  __   / _\ \ _/
 /  /    /  / /  / / _\ \
/__/    /__/ /__/ /_/ /_/

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

 -> Using config [classpath:META-INF/runner.properties]
 -> Using only arguments from command line
 -> Scan bundles from [mybundles.txt]
 -> Scan bundles from [scan-file:file:/Users/wallsc/osgi-fun/mybundles.txt]
 -> Provision bundle [file:/Users/wallsc/Projects/sandbox/helloworld/hello-simple/target/hello-simple-1.0-SNAPSHOT.jar, at default start level, bundle will be started, bundle will be loaded from the cache]
 -> Preparing framework [Felix 1.6.0]
 -> Downloading bundles...
 -> Using execution environment [J2SE-1.6]
 -> Runner has successfully finished his job!


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

-> Hello World!

-> 

Note that although the text file contained a file: URL, it could've just as easily have been an http: URL. And, thanks to Pax URL, it can also be a Maven URL, referencing an artifact ID, group ID, and version of the bundle in a Maven repository:

mvn:com.habuma.osgi.helloworld/hello-simple/1.0-SNAPSHOT

As with the other options, we can list multiple bundles in the text file to be provisioned. For example, a more interesting OSGi bundle-based application might include several bundles in its provisioning text file:


To recap, this week we saw how to use Pax Runner to fire up an OSGi platform with one or more bundles already installed and started. We've kept it simple so far, installing a single bundle. But as I've mentioned, adding more bundles to the mix is a simple matter of adding entries to a provisioning file, dropping the bundles in a directory, or packaging them in a ZIP file.

We're just getting started with Pax Runner. I assure you that you'll see more Pax Runner goodness in future blog entries.

In fact, next week I plan to show you how to use profiles, a Pax Runner feature that is based on composite provisioning. You'll see how profiles makes it easy to install several dozen bundles all at once by referring to them by a single logical name. While we're at it, we'll also tinker with Distributed OSGi, a new feature of the upcoming OSGi R4.2 specification. See ya then!

(2009-04-27 10:00:00.0) Permalink Comments [2]

20090420 Monday April 20, 2009

Pax Construct: From zero to OSGi

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:

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:

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

(2009-04-20 10:00:00.0) Permalink Comments [6]

20090413 Monday April 13, 2009

A Dozen OSGi Myths and Misconceptions

Anybody who knows me or has been following me lately knows that I have an interest in OSGi. And I'm not alone--there has a lot of buzz about OSGi in the past few years.

But as I talk about OSGi with other developers, I often hear them say things about OSGi that simply aren't true. It's not their fault, though. Unfortunately, there's a lot of misinformation and misconceptions about OSGi out there. It's hard to sort out the good information from the bad. And more often than not, it's the bad information that sticks with people.

There's no perfect technology and OSGi isn't going to be nominated as the first one. But before you give OSGi the brush off, let me try to set the record straight on a few of the OSGi myths and misconceptions that I've heard.

OSGi is too complex

I've been told that OSGi is too complex because it requires rigorous management of the META-INF/MANIFEST.MF file (you know...that file that's in every JAR file...the on you never look at).

It is true that OSGi leans on the MANIFEST.MF file and that you'll need to make sure that the manifest contains entries that you probably wouldn't normally put in there. But it's also true that OSGi only demands one additional entry in MANIFEST.MF for a JAR file to qualify as an OSGi bundle: Bundle-SymbolicName.

Bundle-SymbolicName is used to identify a bundle (the unit of modularization in OSGi) to an OSGi framework. It typically follows an inverted domain name pattern (similar to how Java packages are named). For example, the Bundle-SymbolicName header for a simple hello world bundle might look like this:

Bundle-SymbolicName: com.habuma.osgi.helloworld.hello-simple

Of course, if all you did was add a Bundle-SymblicName header, you'd have a perfectly valid OSGi bundle that is also perfectly useless. For it to be useful, the bundle needs to...

Although there are several headers that you could include in a bundle's MANIFEST.MF, there are only a few that you'll probably ever use. And even then, much of the management of the manifest can be automated using tools such as Peter Kriens' BND (more on this later when I talk about OSGi tools).

Maybe it's not manifest management that makes you think OSGi is complex--perhaps the OSGi API causes you some concern. In that case, consider this: The OSGi API is made up of only a half-dozen different packages and mostly interfaces. Of those, you'll likely only ever need a handful of them.

Even then, you'll probably never even need to use the OSGi API at all. But I'll continue that discussion in the next item...

OSGi is too heavyweight

Whenever I hear the word "heavyweight" in the context of a technology or framework, it usually means that the framework imposes itself and its APIs too heavily upon an application's code. EJB 2.x, for instance, required that EJB implementation classes implement base EJB interfaces and implement a number of lifecycle methods. Consequently, EJBs were considered heavyweight.

On the surface, the OSGi API does appear to be a bit heavyweight. Bundle activators, for instance, are classes that implement the BundleActivator interface, providing start() and stop() methods. For example, here's a simple hello world bundle activator:

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!" );
    }
}

The fact that this class imports org.osgi.framework.* interfaces and classes should tip you off that we're not dealing with a POJO. But before you dismiss OSGi as heavyweight, consider this:

Using declarative OSGi, iPOJO, and Spring-DM are topics that are beyond the scope of this article. But I do cover Spring-DM in Modular Java. And watch this blog space for more discussion on it soon.

OSGi isn't a "standard"

Personally, I'm not sure that it should matter much whether something has been stamped as a "standard" or not. After all, Spring isn't a standard. Hibernate isn't a standard, either (although JPA was heavily influenced by it). For that matter, Ruby on Rails isn't a standard. Despite their lack of standardization these frameworks are rarely questioned anymore for their lack of a JSR. Solutions, after all, are more important than standards.

But, if it standards are something that's important to you or your organization, then rest easy knowing that OSGi is, in fact, a recognized Java standard.

Aside from self-standardization applied by the OSGi Alliance, OSGi R4.1 is also the subject of JSR-291 from the Java Community Process. This JSR was given final approval and was released in mid-2007. The ballot included 12 votes in favor (The Apache Software Foundation, SAP AG, BEA, Hewlett-Packard, Nortel, SAS Institute Inc, Borland Software Corporation, IBM, Oracle, Fujitsu Limited, Intel Corp., and Red Hat Middleware LLC), two votes against Those voting against it were (Sun Microsystems Inc. and Hani Suleiman) and two members did not vote (Google and Doug Lea).

In addition, the mobile application of OSGi is the subject of JSR-232 (which is also in final release/maintenance release status).

OSGi is too new

Despite the fact that OSGi has only recently been grabbing the attention of Java developers, OSGi has been around a lot longer than many other readily-accepted technologies. In fact, OSGi celebrated its 10th birthday last month. In its time, OSGi has reached many milestones:

To put OSGi's age in perspective, consider the "birthdates" of these other technologies and where OSGi falls in:

OSGi has been around nearly as long as several established technologies and has been around much longer than many others.

OSGi isn't ready for the "real world"

The precise definition of "cutting-edge" is up for some debate. Regardless of how you define "cutting edge", there are several projects out there that have been comfortable enough with OSGi to use it. Considering just a few examples...

For more examples of projects that have placed their faith in OSGi, have a look at OSGi Markets and Solutions. And I'm certain that the list isn't complete--let me know if you hear of an interesting use of OSGi that isn't listed.

There aren't many OSGi tools

Perhaps my problem with this myth is that I'm unclear as to how many tools are needed to negate it. Certainly, I will acknowledge that there are some tools that I wish I had when working with OSGi. But I also do not believe that there is a dearth of OSGi tools as some have suggested.

When it comes to OSGi tooling, the very first tool that comes to mind is Peter Kriens' Bnd. Bnd is a remarkable little tool for creating and diagnosing OSGi bundles. Using BND, you can view the manifest and JAR contents of a bundle, wrap a non-bundle JAR file so that it becomes an OSGi-ready bundle, create a bundle given a specification and class path, and verify the validity of a bundle's manifest.

What's particularly interesting about Bnd is that although it comes as a single JAR file, it is actually three different tools in one. If you run it with java -jar bnd.jar, it is a command-line tool. If you add it to the Eclipse plugins directory, it is an Eclipse plugin. And, it has an Ant task built into it, so you can use it in an Ant build.

A fairly recent addition to the OSGi toolbox is SpringSource's Bundlor. Bundlor seems to address many of the same problems as BND, but is supposed to overcome some of BND's limitations. I haven't had a chance to try Bundlor out yet, but from what I read, it looks very powerful. When I get around to trying it out, I'll be sure to blog about it here.

There's also a huge set of OSGi tools available from the OPS4J Pax project. I've already reviewed these tools on this blog, so I won't go into it again here. Suffice it to say that many of the Pax tools have become indispensable in my OSGi development toolbox. I'm particularly fond of Pax Construct, Pax Runner, Pax Exam, and Pax URL. You can count on me blogging more about the Pax tools soon.

Apache Felix is known as one of the leading OSGi frameworks. But Apache Felix also has several subprojects that provide OSGi tools. The vast majority of the Felix subprojects are runtime tools, such as File Install (which automatically installs and uninstalls bundles from a watched directory) and Web Console (which provides a web-based administration console for an OSGi framework). Apache Felix subprojects also include the Maven OSGi Plugin, which is a very handy Maven plugin for automatically generating bundles (with proper manifests) in Maven builds. (BTW, Pax Construct leans heavily on the Apache Felix bundle plugin, which itself leans heavily on BND.)

If you take a visit to DynamicJava.org, you'll find several more useful OSGi tools and libraries. I've not tried any of these out yet, but several of them look very interesting and I hope to give them a spin sometime soon. (If I do, I'll be certain to blog about it here.)

These are only a few examples. If you look around, you'll find a lot of useful OSGi tools...even OSGi tools that work within your favorite IDE. And that brings me to the next item...

OSGi is for Eclipse users

If you've spent any time on Google looking for information on OSGi, you'll find several articles that discuss OSGi in the context of the Eclipse Plug-In development environment (PDE). That, coupled with the fact that one of the major OSGi implementations is produced by the Eclipse Foundation, may lead some to believe that you must use Eclipse to develop OSGi-based applications.

The truth, however, is that there's nothing about OSGi that makes it proprietary to any development environment. Certainly, Eclipse has provided the best IDE for OSGi development for a long time. But if you're an IntelliJ IDEA user, then you should check out Osmorc, a plugin for IDEA that provides support for OSGi development. I am also hearing rumors that NetBeans support for OSGi development is in the works.

That said, I frequently find myself working with OSGi bundles using a text editor like TextMate. That's because most of the OSGi-related work doesn't require anything more advanced than a simple text editor (apologies to TextMate...anyone who has used it will attest that it is far more than just a simple text editor).

OSGi is a solution looking for a problem

I almost didn't include this one. I've been in the software development game for a long time and I know that anytime someone wants to discount a technology or framework, the killer phrase that is almost always thrown about is "XXX is a solution looking for a problem" (or its variant: "XXX is a hammer looking for nails"). But just because someone says it doesn't mean that it's true. But I heard someone say this again recently, so I tossed it in.

I've also been doing this thing long enough to know that modularity is a good thing. Developing software in modules increases maintainability, testability, flexibility, and comprehensibility. It also offers benefits in terms of parallel development and (to some degree) reusability. Many languages such as Modula-2 and MIL-75 have been around for a long time and were created with modularity as a primary concern. Ask around...most everyone will agree that modularity is a desirable trait of good software.

Unfortunately, as awesome as Java is, it doesn't offer a lot of help with regard to modularity. JAR files and packages give only the illusion of modularity. And although there's a certain amount of modularity at the object level, that flavor of modularity is at too low of a level to be truly useful for creating modular applications.

So, if modularity is a good thing and Java doesn't offer support for it, then...well...that sounds a lot like a problem to me. A problem begging for a solution, some might say. And modularity is one of the key things that OSGi offers. So, in my opinion, OSGi is a timely solution to the problem of Java's missing modularity.

I can get modularity without OSGi

It's certainly true, to some extent, that you don't need OSGi to achieve modularity. With the right mindset and a lot of discipline, you can develop Java applications that are modular to some degree.

But the problem is that there's nothing in the Java platform to encourage modularity, much less enforce it. And even the most disciplined and well-intentioned Java developers make mistakes. In short, if modularity isn't enforced, then it probably doesn't exist.

OSGi doesn't strictly enforce modularity (with some misguided cleverness, you can develop poorly defined OSGi modules, too), but OSGi does strongly encourage good modular development. With OSGi, it's easier to develop modularly than not to.

So, yes...it is possible to achieve modularity without OSGi. In the same way, you don't need Spring to do dependency injection. And you don't need an ORM solution like Hibernate or JPA to do object persistence. And you don't need an IDE to develop in Java. And you don't need JUnit to write well-designed, working code. You don't need any of that stuff...but I wouldn't want to give any of them up.

Speaking of testing...

OSGi is difficult to test

Inside of every OSGi bundle is a bunch of Java code that, if well-written, can be tested using JUnit or whatever testing tool you prefer. As with any Java codebase, you are free to write tests at the unit level and also to write cross-unit integration tests.

OSGi doesn't make testing harder--you can still test your code the same way as you do without OSGi. But OSGi offers a new level of testing--integration testing at the bundle level.

Bundle-level testing involves deploying one or more OSGi bundles within an OSGi framework (such as Equinox or Felix) and then making assertions about how those bundles work together, what packages they export, and (even more interesting) the services that they expose.

Testing OSGi bundles "in-container" sounds difficult, but thanks to a handful of testing frameworks, it's actually quite easy. These frameworks are:

The first three of these testing frameworks all work pretty much the same way. After installing a collection of bundles in an OSGi framework (or frameworks) of your choosing, the test wraps itself in an on-the-fly OSGi bundle and installs itself in the framework. From within OSGi, the test is able to use and examine the bundle context, consume OSGi services, and assert that the bundles under test are behaving well.

Of the three testing frameworks listed, I tend to favor Pax Exam because it's currently the only one that is based on JUnit 4. Pax Drone is effectively deprecated in favor of Pax Exam, so unless you encounter a project that's already using it, you should disregard it. As for Spring-DM's testing support, it is nice in that it uses Spring autowiring to autowire OSGi services into a test class. But until it's based on JUnit 4, I still prefer Pax Exam.

DA-Testing is a new entry in the OSGi testing toolbox. I haven't had a chance to try it out yet, but it claims to address OSGi testing in a way that's very different than the first three testing frameworks. When I get a chance to try it out, I'll be certain to blog about it here.

OSGi doesn't have a cool name

Okay...you got me there. I'll concede that OSGi doesn't have a cool name like "Spring" or "Hibernate" or "Groovy". In fact, I'll even agree that "OSGi" is probably one of the most boring names ever given to a Java framework or library.

In my opinion, this is one of the worst things OSGi has going for it. I believe that a lot of the misinformation about OSGi stems from its name. It sounds complex. It sounds heavyweight. It sounds like an unpleasant medical procedure.

My apologies to the OSGi Alliance. You guys are awesome and have done some magnificent work. But the name...well...it needs some work. ;-)

There are no books on OSGi

Not true! There are already a handful of OSGi books available. And with OSGi becoming a hot topic, I'm sure that there will be several more on the way.

One such book that I'd like to recommend is Modular Java. Even though I'm obviously biased toward this book, I can recommend it as a pragmatic example-driven guide to building applications with OSGi and Spring-DM.

Here are some other OSGi books that are available or will be available soon:

Of course, I really hope that you'll start your adventures in OSGi with my book.


I hope that this article has dispelled some of your doubts about OSGi. Of course, I suppose that if you're adamantly opposed to OSGi, then there's nothing I can say that will change your mind. But if you're a reasonable person who has been reluctant to look into OSGi because of what you've heard, then perhaps I've cleared things up enough for you to give it a fair look.

This is the first of several articles that I hope to post on this blog over the next several weeks (or months). I have several great articles in mind, but I have no idea where this will go. I'm going to start by covering a few of the tools that make working with OSGi easy. So keep an eye on this blog for more OSGi goodness coming soon.

(2009-04-13 10:00:00.0) Permalink Comments [10]

20090409 Thursday April 09, 2009

Two Four significant OSGi-related releases today!

I'm quite excited to see two fairly significant OSGi-related projects released today: Spring-DM 1.2.0 and Pax Runner 0.18.0.

Most anybody interested in OSGi probably already knows what Spring-DM is, but for those that don't: Spring-DM blends the dynamic runtime and modularity of OSGi with the Spring Framework. This 1.2.0 release brings several improvements, but the one that I was most excited to see was integration with the OSGi Configuration Admin Service. This feature makes it possible to configure Spring bean properties using values taken from the Config Admin--either by autowiring or by using property placholders.

Perhaps you've also heard of Pax Runner. In short, Pax Runner simplifies starting up an OSGi framework (Equinox, Felix, Knopflerfish, or Concierge...any version) with a given set of bundles installed and started. In my opinion, version 0.18.0 is a significant release because of these four features:

Congratulations to Costin Leau and everyone that worked on Spring-DM and also to Alin Dreghiciu and everyone that contributed to Pax Runner!

STOP THE PRESSES!!!

As I was writing this blog entry, I see that two other releases have been made on the Pax project: Pax URL 0.5.0 and Pax Exam 0.4.0 were both released in the last few minutes!

Pax URL is a set of URL handlers that enables loading of OSGi bundles from a variety of sources (such as a Maven or Ivy repository or from OBR). It also provides support for auto-wrapping non-bundle JARs, among other things. Pax Runner uses Pax URL when loading bundles, but it's also handy for loading bundles individually into an OSGi framework. It looks like the version 0.5.0 brings us a new link: protocol (for symbolic-link-like URLs) and the ability to use Pax URL handlers in non-OSGi scenarios.

Pax Exam is a testing framework for OSGi. The new 0.4.0 version seems to include (among other things) tighter integration with Maven, new support for various mocking frameworks, and support for Pax Runner 0.18.0. Toni Menzel has summarized the new features on his blog

Congratulations again to Alin Dreghiciu, Toni Menzel, and everyone who contributes to the Pax projects!

I'll be blogging more here about these frameworks and tools. I also cover Spring-DM, Pax Runner, and Pax Exam in Modular Java.

(2009-04-09 20:47:40.0) Permalink

20090318 Wednesday March 18, 2009

More Modular Java Tonight

Just a reminder that tonight I'll be presenting part 2 of my Modular Java presentation at the Spring Dallas User Group.

Last week, I presented part 1, an introduction to OSGi, at JavaMUG. I'll pick up where I left off last week by showing how to eliminate all of the boilerplate OSGi API code to publish and consume services with Spring Dynamic Modules. We'll also see how to deploy web applications using Spring-DM's web extender.

Should be a bundle of fun...See ya there!

(2009-03-18 10:00:00.0) Permalink Comments [1]

20090311 Wednesday March 11, 2009

Modular Java at JavaMUG tonight

In case you haven't marked it on your calendar already...

Tonight, I'll be speaking about OSGi at JavaMUG in Dallas. This is the first of a two-part presentation, where I'll be going over the basics of building modular applications with OSGi. Next week at Spring Dallas User Group, I'll be presenting part two which will cover declarative OSGi services using Spring-DM.

Drop by...have some pizza...get modular.

(2009-03-11 10:00:00.0) Permalink

20090226 Thursday February 26, 2009

Modular Java in Beta

Just a quick note to let you know that my new book, Modular Java: Creating Flexible Applications with OSGi and Spring is now available in Pragmatic Bookshelf's beta-book program.

Also, if you haven't become a fan of the book yet, be sure to look for Modular Java on Facebook and sign up.

Finally, if you're in the Dallas, TX area and can't get enough Modular Java, I'll be presenting the first part of a two part discussion on Modular Java at JavaMUG two weeks from last night (3/11/2009). Part two will take place at the Spring Dallas User Group three weeks from last night (3/18/2009). Part one will focus on OSGi basics while part two will expand that discussion with Spring Dynamic Modules.

(2009-02-26 11:47:44.0) Permalink