I'm building a little OSGi application that has a search aspect to it. So I figured Lucene would be a great choice...or better yet, Compass since it's objects that I'll be searching on. In case you're not in the know, Compass is an object-to-Lucene mapping framework. Compass is to Lucene what Hibernate or JPA is to JDBC.
So, I decided to add Compass as a bundle in my application. But first, I need to make sure that it is a proper OSGi bundle. The minimum requirement for an OSGi bundle is that its META-INF/MANIFEST.MF contain a Bundle-SymbolicName: header. Sure enough, the Compass JAR has a Bundle-SymbolicName. So I'm good to go, right?
Private Packages
Hold on! Not quite yet. Even though Compass meets the minimum requirements to be an OSGi bundle, it's all but useless, because its MANIFEST.MF doesn't export any of its packages. What this means is that even though I can install Compass into an OSGi framework (such as Equinox), my other bundles can't see the contents of the Compass bundle. Effectively, Compass is entirely private.
No problem. I can use Peter Kriens' BND tool to wrap Compass with a MANIFEST.MF that does export packages I need. The magic BND instructions are:
Embed-Dependency: *;scope=compile|runtime;type=!pom;inline=true Import-Package: *;resolution:=optional
Or better yet, since I'm using Pax Construct, I can use pax-wrap-jar to do it for me:
% pax-wrap-jar -g org.compass-project -a compass -v 2.0.1
Split Packages
Okay, so now that the newly wrapped Compass bundle exports some packages, I'm ready to roll, right?
Well...not quite. You see, Compass depends on Lucene to do its bidding. So, I need to make Lucene available to Compass. The seemingly obvious way to do that is to install Lucene as a bundle in the OSGi framework. BND or pax-wrap-jar could help me do that, but then I run into a new problem...
A basic rule of OSGi is that no two bundles can export the same package...that is, no split packages. It turns out that within the Compass JAR are some org.apache.lucene.* packages which are also provided by Lucene itself. As a bundle, Compass provides its own org.apache.lucene.* content, so it will never see the packages exported by Lucene.
The solution? I ultimately decided that since I wouldn't be interacting with Lucene directly, it'd be best to embed Lucene within the Compass bundle. Pax Construct to the rescue again with its pax-embed-jar script:
% pax-embed-jar -g org.apache.lucene -a lucene-core -v 2.3.2
This adds a new entry to the Compass bundle's BND instructions:
Embed-Dependency: *;scope=compile|runtime;type=!pom;inline=true,\ lucene-core;groupId=org.apache.lucene;inline=false Import-Package: *;resolution:=optional
Okay, so now Compass is exporting packages for my bundles to use and has all of the Lucene stuff it needs. Surely my Compass woes are behind me now.
Dynamically Imported Packages
Not so fast! I used pax-wrap-jar (which uses BND) to automatically export the packages in the Compass bundle. That works. But when BND (via the Felix Bundle Plugin) generates the manifest for my bundles, it doesn't import all of the packages I need.
You see, BND is great at analyzing code and figuring out what packages are being used, both directly and transitively, and adding those packages the a bundle's Import-Packages: header. The problem, however, is that Compass makes liberal use of Class.forName() to dynamically load classes. Because the classes are loaded dynamically, BND doesn't know that they're needed. This meant that I needed to add instructions to the BND instruction file to ensure that BND would include them (either as more entries in Import-Package: or (even better) using DynamicImport-Package:):
DynamicImport-Package: org.compass.*
Now I think that I've bent Compass to my OSGi will. But it hasn't been easy. I wonder what other surprises Compass has for me as I continue to develop my application. But for now it seems to work.
Did I write this to gripe about Compass? Maybe...a little. I do think it's silly that they included a Bundle-SymbolicName: header but failed to provide any other headers that would make Compass useful in an OSGi context. The split package thing and dealing with dynamically imported packages were annoyances, but I worked through them.
And, let me be clear that I think Compass is great at what it does...kudos to the Compass team for such a great framework.
No, the main reason I write this is to highlight a few of the real-world gotchas you might encounter while working with OSGi and to give some insight on how you might deal with them. I love working with OSGi, but it will do no good if I candy coat the snags or sweep the hassles under the rug. It's better if I meet them head on, address them, and then share the experience with others.
I strongly suspect that Compass isn't the only JAR out there that presents these kind of problems when used in OSGi. I just picked on Compass because it was the one that has kept me on my toes the most lately.