What is the intended use case for Bundle-Classpath in OSGI bundles - osgi

I am trying to understand the intended use case for Bundle-Classpath in OSGI bundles.
Here is my understanding, please help me understand if this is correct.
Let's say I am working on creating an OSGI bundle which will be deployed in an ecosystem of other bundles. The bundle I am working on needs some other bundles, but they are not loaded/exported in this ecosystem, and I do not have control on what the ecosystem exports. In such a scenario, I can put these bundles inside some directory (say 'lib') which becomes part of my bundle. These bundles should also be referenced from the Bundle-Classpath, so they can be loaded.
Is this a correct use case for Bundle-Classpath ?
Will these additional bundles also be loaded in the OSGI container and will packages exported by them be available to other bundles ?

Bundle-ClassPath is intended for including dependencies in our bundle, so that our bundle can be deployed standalone.
Let's take an example. Suppose the code in my bundle uses a library, e.g. Google Guava. I have two choices for packaging my bundle:
Simply create my bundle with only my own code inside it. The bundle will now have the Import-Package statements that declare a dependency on Guava, and anybody who wants to deploy my bundle into his application will also have to deploy Guava.
Alternatively I can include a copy of Guava inside my bundle and reference it from my Bundle-ClassPath. Whoever deploys my bundle can deploy just my bundle, and doesn't need to worry about where to get Guava from. In fact, the existence of Guava inside my bundle is an implementation detail, and the deployer doesn't even need to know that I am using it.
The choice between these two options is a trade-off. Option 2 has the advantage that my bundle is easier to deploy because it is standalone -- everything it needs is right there inside it. On the other hand my bundle is much bigger than it needs to be, which could become a problem if lots of other bundles also embed their own copy of Guava.
A more severe problem with option 2 is that all of the dependencies of the library now become my dependencies as well. Actually Guava is a rare example of a Java library with no dependencies of its own... but many other Java libraries drag in a huge tree of transitive dependencies. If you use this approach with, say, Hibernate then your own bundle will also have that large dependency set. This gets very ugly, very quickly.
So, you should be cautious not to overuse Bundle-ClassPath/Embed-Dependency. You should only consider using it if the dependency is (a) small, and with no transitive dependencies, and (b) your bundle uses the library as an internal implementation detail, i.e. it is not part of your public API.
UPDATE
I forgot to answer your second question about the exports. The answer is NO, the exports of any "bundles" you put on your Bundle-ClassPath will NOT become exports of your own bundle. In fact the JARs we put on Bundle-ClassPath are not treated as bundles at all, they are just JARs.
You can choose to export packages that come from within the JARs on your Bundle-ClassPath but you have to do this in the MANIFEST.MF of your own bundle.

The most common use case for this header is the packaging of external libraries. Let's say you have some library foo.jar, and want to use its classes in your bundle.
You put the jar into your bundle like so,
/
com/company/Activator.class
foo.jar
META-INF/MANIFEST.MF
In you manifest, you can now use
Bundle-ClassPath: foo.jar,.
Remember to include the . on the classpath, or you will not be able to find the classes in your bundle.
When classes are on the Bundle-ClassPath, you can use them like any other class: use them in your code, or export them.

I think you may be a bit off here.
A Bundle-Classpath is an ordered, comma-separated list of relative
bundle JAR file locations to be searched for class and resource
requests.
What this means is that when some bundle class needs another class in
the same bundle, the entire bundle class path of the containing bundle
is searched to find the class.
From OSGI in Action.
Let's consider a concrete case. Imagine a bundle (JAR file) with the following structure:
src/a/A.class
src2/b/B.class
src3/c/C.class
if you wanted a.A, b.B and c.C to be available to each other, you'd have to define src, src2 and src3 as pertaining to the bundle classpath. That would mean you'd have to add to your manifest file the following line:
Bundle-ClassPath: src,src2,src3

Related

OSGi - modify a class in a bundle without modifying the original bundle

I have a need to modify a class in a OSGi bundle. But this bundle comes from an upstream project hence I cannot(/won't) touch it. But I have to modify a class in it to match my project's requirements. So, I'm looking for a solution for this.
So, I created my own bundle which has the patched classes. But there is a complication since the upstream OSGi bundle, and the patch OSGi bundle I created has the same packages. I want to over-ride the classes in the upstream OSGi bundle, without touching the it.
I looked at the osgi.org Fragment page. It suggests a way to patch a host bundle by using a fragment bundle, but for that I have to modify the upstream project bundle's MANIFEST.MF to set Bundle-ClassPath: patch.jar,.
Any suggestions?
You can implement a WeavingHook and catch the class loading of that bundle.
However, do it only if you do not have any other chance to solve your problem. If you have any chance to contribute to that project that contains the jar, send a patch for them and have this only as a temporary solution. With hacks like this, your system will be chaotic for others.

Exporting API from implementation bundle

Can someone explain to me the rationale behind BND's exporting api packages that a bundle doesn't have. It seems just wrong to me, it always (AFAICT) results in ClassNotFoundException
Especifically
bundle A contains com.foo.api.MyInterface
bundle B contains com.foo.impl.InterfaceImpl which implements MyInterface
So if I tell bnd to export package com.foo in bundle A he will correctly export com.foo.api, but if i tell export package com.foo in bundle B it will incorrectly export com.foo.api, so consumers of MyInterface get wired to bundle B and then at runtime get a ClassNotFoundException
Why doesn't BND guarantee that when a bundle exports a package it actually contains it??
I know that if I export com.foo.api and com.foo.impl instead of just com.foo then the problem is solved in my trivial example, but in our real system this is not really a posibility. Also, the actual question is why BND does that, not how to fix the problem. i already know workarounds to solve the problem
This is just a feature of the bnd syntax. Because bnd has the option to embed imported packages, it considers the whole classpath when determining Export-Package and Private-Package headers. Bnd doesn't make any distinction between classes which are physically local to a folder being processed and others elsewhere on the classpath.
So if your Export-Package statement encompasses packages bnd knows about, bnd will assume you want to embed and export them. I'm surprised you're seeing exceptions, though, since bnd should also physically include the exported classes in the packaged bundle.
Did you look in bundle B to see that com.foo.api is not in there? If you tell bnd that bundle B should export com.foo.api, then bnd will actually put that package in bundle B and then export it. bnd has the "power" to include classes from anywhere in the -buildpath in the generated bundle. I think you really don't want bundle B to include and export com.foo.api, so you should modify your bnd file to remove the export statement.
I have not yet worked with bnd directly but through the maven bundle plugin which should be fairly similar. What I found is that if you export a package you do not have then bnd will embed the classes of this package inside your bundle jar.
So at least when using the maven bundle plugin bundle B should have the interface embedded when the export of com.foo.api is defined. Not sure what happens when you define only the parent package as export but I would assume the same happens.
This automatic embedding can be quite useful if you want to deploy the api together with each bundle which I do not do regularly but which seems to be a kind of best practice in OSGi (for example for bundles that implement OSGi apis).

Packing multiple OSGi bundles in the same jar

I am very new to the OSGi platform.
Having an OSGi bundle B1 that has a dependency to another bundle B2, which is very probably not provided by the container, can I package both bundles in the same jar?
If yes: howto do that? what would happen if the jar is installed and the bundle is already installed?
Yes you can do this... kind of.
Normally OSGi bundles are JAR files because they need to have a META-INF/MANIFEST.MF. The OSGi framework cannot directly read your "multi-bundle" JAR.
However, installing bundles in OSGi involves calling the BundleContext.installBundle method. This method has two flavours, one of them takes an InputStream as a parameter; this InputStream should supply the content of the OSGi bundle. Therefore you can take your big JAR and read it with a JarInputStream, passing the individual entries to the installBundle method.
Although OSGi doesn't directly support multiple bundles in the same jar, there is a standard for multiple bundles in the same archive, which solves the distribution problem you're trying to address. Unfortunately, it may introduce an extra problem in your scenario, because it's part of enterprise OSGi, rather than core OSGi, so it wouldn't be part of a bare bones Equinox or Felix framework. If you have more control over the starting platform your users are using, it may do what you need.
The basic idea is that you package all your bundles into a zip with a .esa extension, along with a simple manifest. You can then distribute the esa file. This tutorial includes lots more detail:
http://coderthoughts.blogspot.co.uk/2013/04/osgi-subsystems.html?m=1

Can an OSGi bundle or package depend on multiple versions of another bundle or package?

Can a OSGi bundle have two dependencies, each on a different version of the same OSGi bundle?
Can a OSGi package have two dependencies, each on a different version of the same OSGi package?
(I am trying to learn OSGi from the ground up. This question is just intended to help me understand the basic concepts. From reading online articles about OSGi services, I gather that such dependencies certainly wouldn't be recommended practice. But are they possible at all?)
(Update: rephrased the two questions.)
No. OSGi provides a consistent class space for a bundle. This means that it is only exposed to a single class of a given name. So a bundle cannot simultaneously see more than one version of a package at a time.
This does not mean that ClassCastExceptions are impossible since code your bundle is directly dependent on, can expose objects from their dependencies to your bundle. The proper use of uses constraints on export packages is important to prevent this.
Can a OSGi bundle depend on two different versions of another OSGi bundle at the same time?
Can an OSGi package depend on two different versions of another OSGi package at the same time?
Sort of. You can depend on ranges or specific versions of another OSGI bundle or package like this:
Import-Package: org.osgi.framework;version="[1.3,2.0)"
Not sure if that applies in the first section because bundles should not depend on other bundles, only packages. This is what 'Require-Bundle' does but is suggested you don't use it. Require-Bundle takes versions as well so theoretically it should support version ranges.
Once your OSGi bundle is resolved within OSGi, it will find the package of any of those versions. However, it can't resolve a package (org.osgi.framework) to two separate bundles (one which provides version 1.9 and one which provides 1.8). It will choose the most recent version based on SemVer.
If you try to specify it twice in Import-Package, you will get a 'Duplicate Import' error.

When should I use Import-Package and when should I use Require-Bundle?

OSGi allows for dependencies to be determined via Import-Package, which just wires up a single package (exported from any bundle), and Require-Bundle, which wires up to a specific named bundle's exports.
In building a greenfield OSGi application, which approach should I use to represent dependencies? Most of the bundles will be internal, but there will be some dependencies on external (open-source) bundles.
I believe Require-Bundle is an Eclipse thing (that has now made it in the OSGi spec to accommodate Eclipse). The "pure" OSGi way is to use Import-Package, as it specifically decouples the package from the bundle that provides it. You should be declaring dependencies on functionality that you need (the Java API provided by a certain version of a certain package) instead of where that functionality is coming from (which should not matter to you). This keeps the composition of bundles more flexible.
JavaScript analogy: This is like detecting whether a web browser supports a certain API versus inferring from what the user-agent string says what kind of browser it is.
Peter Kriens of the OSGi Alliance has more to say about this on the OSGi blog.
Probably the only case where you need to use Require-Bundle is if you have split packages, that is a package that is spread across multiple bundles. Split packages are of course highly discouraged.
Favour Import-Package over Require-Bundle.
Require-Bundle:
specifies the explicit bundle (and version) to use. If a requirde bundle needs to be refactored and a package moved elsewhere, then dependents will need changes to their MANIFEST.MF
gives you accesss to ALL exports of the bundle, regardless of what they are, and regardless of whether you need them. If the parts you don't need have their own dependencies you will need those to
bundles can be re-exported
although discouraged, allows the use of split packages, ie: a package that is spread across multiple bundles
can be used for non-code dependencies, eg: resources, Help etc.
Import-Package:
looser coupling, only the package (and version) is specified and the run-time finds the required bundle
Actual implementations can be swaped out
Dependent packages can be moved to different bundles by the package owner
But requires more metadata to be maintained (i.e: each package name) at lower levels of granularity
I believe Import-Package gives you looser coupling and should be preferred. I use it when declaring dependencies on packages that I don't own, such as slf4j, and I can swap implementations as I wish. I use Require-Bundle when the dependency is something I have control over, such as my own bundles, because any important change would have gone through myself anyway.
Avoid Import-Package.
As packages provide many-to-many relationships between bundles, they are prone to dependency cycles that are hard to detect and avoid.
Require-Bundle on the other hand, references a single bundle, making dependency graph protected from cycles by a trivial build-time check.
With Require-Bundle it is much easier to build layered architecture with isolated lower level of abstraction.
Import-Package should be better because, as previously said, you can move a package from one bundle to another without changing existing client's MANIFEST.MF
But...
There is a practical reason to use Require-Bundle if you are using Eclipse to develop your bundles:
Eclipse don't use packages as units of resolution. It uses bundles. That is, if you use one package of a bundle, Eclipse compiles your bundle without reporting any problem with the use of the rest of packages not imported from that bundle.
You could (you are human) think that everything is OK and upload your bundle for deployment but ... your bundle will break at runtime.
I'm sure about it because this problem has happened (to me!) today.
The good solution would be to change the Eclipse classpath container but... if this is not going to be done... you could decide to avoid this kind of problems requiring bundles, instead of packages, paying the mentioned price (no backward compatible code movement between bundles).
I'm not convinced that using Import-Package is better, because my default expectation when working with a bundle is to work with the associated public API. For that reason, Require-Bundle makes more sense.

Resources