OSGi: how to ensure classpath consistency?
Asked Answered
T

3

9

According to the OSGi documentation, OSGi is designed to help prevent ClassPath problems.

For example, from "OSGi in action":

ClassNotFoundExceptions when starting your application because the class path wasn't correct. OSGi can help by ensuring that code dependencies are satisfied before allowing the code to execute.

However, since we've switched our java application to OSGi, I see more ClassNotFoundExceptions and especially NoClassDefFoundErrors than ever. Classes that were previously loading fine are not found anymore by the OSGI classloader, and worse: you get the error at run-time, which means that you can't easily check for it except by manually testing every corner of your application.

For example, our application was running happily under OSGi, but we received reports from beta testers that they couldn't export documents to PDF anymore. Indeed, when I looked into it, I found that this functionality caused an exception to be logged:

java.lang.NoClassDefFoundError: org/w3c/dom/Node

So, is OSGi actually creating more classpath problems than it solves?

and more importantly: How can I test that my class-path is consistent, with all necessary Import-Package statements etc. before I read about them in bug reports?

Thermopylae answered 19/9, 2011 at 17:57 Comment(3)
"except by manually testing every corner of your application" - erm automated tests? (the same applies to any application) If you had test suites before you had migrated the you'd have spotted these errors before testers had. To test that your bundles wire up correctly you'll need to do some integration tests - we're using Pax Exam for this and have short integration tests (current bundle and it's collaborators) as well as some longer end-to-end tests (testing the entire app).Woald
For those finding this stack overflow when searching OSGi NoClassDefFound.... keep it mind that the current Java thread executing the OSGi bundle's code determines the "active" ClassLoader aka Thread Context ClassLoader (TCCL). The "active" ClassLoader may not be the ClassLoader the OSGi container creates for the bundle's code. So to make everything work out, sometimes you have to manually switch the thread's TCCL.Tripersonal
See 2012. Neil Bartlett. "The Dreaded Thread Context Class Loader", blogs.paremus.com/2012/10/….Tripersonal
S
13

An OSGi application will not throw ClassNotFoundExceptions and NoClassDefFoundErrors if your manifest is correct. That is, you need to tell OSGi which packages your bundle uses; if you don't do this correctly or honestly, then OSGi cannot help you. In other words: GIGO (Garbage In, Garbage Out).

For example, you use the package org.w3c.dom but you do not list it in your Import-Package statement. Therefore OSGi does not know that you need access to that package, and so when you actually try to load classes from that package, they are not available.

So, you need to make sure your Import-Package statement is accurate with respect to the packages your bundles actually use. The good news is that the "bnd" tool can do this for you by generating the bundle manifest, using bytecode analysis to find all the statically referenced packages.

Update: Your final question asks how to test that your bundles are consistent before discovering problems at runtime. Bnd has a "verify" feature that does exactly this. Actually if you use Bnd to generate the manifest in the first place, then you will not need the verify feature. However, I don't know anything about your build process so you might find it difficult to move to a Bnd-based build in the short term... in this case it would be relatively simple to add the verification task as a post-processing step after the build. However I would still recommend trying to use Bnd earlier in the build process.

Selfevident answered 19/9, 2011 at 19:7 Comment(6)
I didn't know about the bnd verify feature, very good to know!Thermopylae
"if your manifest is correct" .... meaning if your manifest is exactly as the particular implementation you are using expects it to be and you are not doing anything the OSGI designers and the coders of the particular implementation you are using haven't thought about. I agree with amarillion - OSGI is a nightmare of class not found problems, with next to none troubleshooting mechanisms available. It may be valuable to end-users in having more deployment flexibility, but it is HELL for developers.Osteomyelitis
@Osteomyelitis It's easy for anonymous cowards to sling about insults on the internet. If you calm down and decide to learn something, you know where to find sensible people who can help.Selfevident
Please note that if you manually specify an "Import-Package" statement in your Source tab (using bndtools) the bnd will not auto add any packages to the "Import-Package" statement. Took me a little while to figure out.Kennard
@TinusTate Bndtools does explicitly warn you about this, and tell you to add the catch all pattern "*" at the end of your Import-Package list.Selfevident
@NeilBartlett i remember seeing that warning on different occasions, but in this case i had a warningless project but got an ClassCastException when running my application (i didn't have the catch all pattern after my import-package statement though). In the end i simply removed the import-package and gave it another try.Kennard
Y
2

I think the trouble here is that you are not specifying the correct set of packages/classes used by your bundle (using the Import-Packages directive in your MANIFEST). Unless OSGi knows about those, it really cannot help!

Not sure how you are building your OSGi bundles, but you should check out the Maven bundle plugin, that solves the problems of explicitly specifying Import-Packages (at least for the compile-time ones). See http://wso2.org/library/tutorials/develop-osgi-bundles-using-maven-bundle-plugin#Importing%20packages.

Yukikoyukio answered 19/9, 2011 at 19:8 Comment(1)
+1. Just to add: the Maven Bundle Plugin is based on Bnd, so everything I said in my answer about using Bnd also applies to the Maven Bundle Plugin. If your build is not currently Maven-based, there is no need to move to Maven to get this functionality.Selfevident
D
1

I am not answering the central question, but providing my solution for a similar problem.

In my case, I had this class structure:

  • interface javax.script.ScriptEngineFactory
  • class pkga.A implements ScriptEngineFactory
  • class pkgb.B extends A

My primary project code was in class B and this extended class A in a dependent Jar. Using

<Export-Package>pkgb.*</Export-Package> 

was insufficient, I received an error complaining about the lack of javax.script.ScriptEngineFactory. This is odd as it is part of standard JDK 8, note that code in package pkgb did not explicitly reference javax.script.*; perhaps the Manifest writer didn't know that this interface needed to be exported?

I found this addition to the Export-Package directive solved the problem

<Export-Package>pkgb.*,javax.script.*</Export-Package>
Doubletongued answered 11/10, 2016 at 10:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.