Tuesday, August 4, 2009

IntelliJ IDEA 9 OSGi Support Explored

IntelliJ IDEA 9 (Maia) purports to support OSGi bundles... so I dug in the weekend to find out just what this means.

This blog posts provides a quick start guide for two things: 1) getting the IDEA 9 EAP to publish your library as an OSGi bundle, and 2) getting a second bundle published and running to test your library.

General Impressions
The IDEA 9 OSGi support is a port/conversion of the Osmorc plugin which was available in version 8. In general, the functionality seems stable but sparsely documented. I could find neither a discussion group nor documentation for Osmorc. A lot of the configuration was a process of trial and error. But it does all seem to work, and after investing a few hours it has started to seem straight-forward in hindsight. You'll have to judge for yourself how it compares to Eclipse (that's next on my list!). Osmorc tries to do a lot of auto-magic generation for you, and when I stepped outside the norm (like including Groovy as a test-only dependency) I was confronted with hard to fix failures. However, this may not be a fair criticism. For any serious project, you will want to write a bnd script, not simply use an IDE, and IDEA lets you use a bnd script to drive the OSGi configuration. So at points where the config wizard started to get difficult, I really should have stepped back and created a bnd script.

Getting a Bundle Published
While Eclipse seems to be focused on creating a Plugin (ie Bundle) from scratch using a wizard, IDEA is more focused on including OSGi support on one of your existing libraries using an OSGi facet. There is no "Create OSGi Project" wizard. You simply need to add an OSGi facet to one of your projects. I find this approach more reasonable. But let's not get too far before describing the basics of publishing a bundle:

1. Installing IntelliJ IDEA 9 and OSGi

  • Download and install the Maia EAP
  • Download and install an OSGi SDK (not just the framework jar). I had trouble with Eclipse Equinox, but KnopplerFish worked just fine
2. Configuring IntelliJ IDEA for OSGi
The next step is to tell IDEA about your OSGi SDK. In Settings (Ctrl+Alt+S), go to OSGi->IDE Settings->Framework Definitions and add your framework:
Now tell IDEA to use the framework you just registered. In Settings (Ctrl+Alt+S), go to OSGi->Project Settings and select your framework in the dropdown:

Done!

3. Publishing Your Library
Now let's publish your Java library as an OSGi bundle. In this example, I'm using my filter4osgi library that provides a nice API for LDAP style filters (shameless plug: I'll pay you for bug reports). This step will create the OSGi .jar file and required manifests.
  • Open an existing IDEA project... the fewer dependencies the easier it will be to publish
  • Add the OSGi facet to the project module. Clicking the little plus sign and picking OSGi should do the trick.
A note about the OSGi facet... currently, you're not able to add more than one OSGi facet to a module. For me, this forced me to create a multi-module project in order to produce several OSGi bundles from the same codebase. This seems like a considerable limitation to me but maybe my projects are structured strangely.

The Manifest Generation tab allows you to specify the exported packages for the bundle. I needed to manually add the package I wanted to export, Osmorc did not seem to discover this for me. Again, using bnd is probably the best option here.


There are plenty of other options, but they aren't as important right now. And note, I didn't add an Activator, because my bundle is just a library.

To verify that you published your library correctly, rebuild the application. The jar file should be written to disk. The manifest file with the OSGi headers is pretty simple:
Manifest-Version: 1.0
Export-Package: org.filter4osgi.builder;version="0.0.1"
Bundle-Version: 0.0.1
Tool: Bnd-0.0.337
Bnd-LastModified: 1249412068156
Bundle-Name: filter4osgi
Bundle-ManifestVersion: 2
Created-By: 1.6.0_12 (Sun Microsystems Inc.)
Import-Package: org.filter4osgi.builder;version="0.0"
Bundle-SymbolicName: filter4osgi
Include-Resource: ..\Filter4osgi
4. Running Your Bundle
Running the bundle is simple. Create a new Run/Debug Configuration using the little plus sign:
Just use the Add button to specify your bundle. You can also specify dependent bundles too.
On the Parameters tab, you'll need to specify the OSGi framework to run within:

Now just click Run, and your console output window should have something like this written to it:
Knopflerfish OSGi framework, version 4.1.7
Copyright 2003-2009 Knopflerfish. All Rights Reserved.

See http://www.knopflerfish.org for more information.
Failed to remove existing fwdir C:\Documents and Settings\hdarcy\.IntelliJIdea90\system\osmorc\runtmp1249413066328
Framework launched
Installed: file:///D:/dev/filter4osgi/out/production/Filter4osgi.jar (id#1)
Started: file:///D:/dev/filter4osgi/out/production/Filter4osgi.jar (id#1)
It worked! (Although I have no idea why there is a failure removing "fwdir", whatever that means). Alternately, you can load the jar file into any other container and interact with it that way. One feature that is missing is the ability to interact with the container from IDEA. You cannot issue the stop or uninstall commands. In fact, the only way I found to stop the container is with the big red Stop button. It would be better if you could issue text commands to the container. This doesn't seem like it would be difficult to add.

5. Testing Your Bundle with an OSGi Test Harness
There are a few test utilities for OSGi, such as Pax Exam and the Knopflerfish Junit Support, but all I wanted to do was write a bundle that imported filter4osgi and made sure the classes were available. So I simply added a new module to my project called "filter4osgiIntegrationTest". To this I added the Knopflerfish framework.jar since I needed to create an Activator. Take note, the prior example of exposing a library as a bundle required zero design or runtime dependencies on any OSGi implementation.

Without adding the OSGi facet, I created a new Java class that implemented BundleActivator. IDEA flagged me with a missing facet error and asked if I'd like to create a facet, which I did. Nice!

The Manifest Generation tab was mostly filled in, but again I needed to hand edit the MANIFEST.MF data so that it imported the package from the original filter4osgi bundle and added the root '.' to the bundle classpath (otherwise there were ClassNotFound exceptions).

Now I just implemented the activator to construct a new filter object:
public class ActivatorForTesting implements BundleActivator {
@Override
public void start(BundleContext context) throws Exception {
FilterBuilder filter = or(
and(
eq("mailbox", "default"),
eq("lang", "EN_US")
),
and(
eq("mailbox", "dflt"),
eq("lang", "EN_CA")
)
);
assert filter.toString().equals("(|(&(mailbox=default) (lang=EN_US)) (&(mailbox=dflt) (lang=EN_CA)))");
}

@Override
public void stop(BundleContext context) throws Exception { }
}
Hey, that's not a bad API for working with LDAP filters, is it?

And now it's time to create the Run/Debug Configuration, which was probably the hardest step in all this. In the Run Config, I needed to specify that both the filter4osgi and filter4osgiIntegrationTest need to be started up, and that the filter4osgi must be started first. You do this in the Bundles tab, and ordering is done by indicating a Start Level:


Now, when I run the config, I should see that both bundles were started. And if I don't get a ClassNotFound error then I know that the filter4osgi exports lined up correctly with the filter4osgiIntegrationTest imports. Which they do:
Knopflerfish OSGi framework, version 4.1.7
Copyright 2003-2009 Knopflerfish. All Rights Reserved.

See http://www.knopflerfish.org for more information.
Failed to remove existing fwdir C:\Documents and Settings\hdarcy\.IntelliJIdea90\system\osmorc\runtmp1249414635312
Framework launched
Installed: file:///D:/dev/filter4osgi/out/production/Filter4osgi.jar (id#1)
Started: file:///D:/dev/filter4osgi/out/production/Filter4osgi.jar (id#1)
Installed: file:///D:/dev/filter4osgi/out/production/filter4osgiIntegrationTest.jar (id#2)
Started: file:///D:/dev/filter4osgi/out/production/filter4osgiIntegrationTest.jar (id#2)
Perfect!

All in all, the support was quite usable. I think starting with a bnd file would have made things easier for me, as well as if IDEA had let me interact directly with the container.Definitely looking forward to the official release!

2 comments:

Kasun Indrasiri said...

Hi,

Can't we use Equinox with IntelliJ 9? Would you mind explaining a scenario with a simple 'HelloWorld' bundle ?

Very nice article. keep it up

br,
Kasun

Hamlet D'Arcy said...

Yes, you can use Equinox just fine. I just like Knopplerfish :)