Curing ADD with Roo, Blueprints, and Karaf - No Fluff Just Stuff

Curing ADD with Roo, Blueprints, and Karaf

Posted by: Craig Walls on October 22, 2009

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.

Craig Walls

About Craig Walls

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

Why Attend the NFJS Tour?

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

Current Topics:

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