How to hide embedded dependencies when using an OSGI bundle with maven? - java

Is there a way to use an OSGI bundle as a maven dependency without getting all the packages from it into the classpath that it doesn't even export?
Background of the question: We just added the org.apache.sling.xss bundle as a maven dependency into our project. As you can see in the pom.xml, it just exports the package org.apache.sling.xss but embeds various dependencies directly into it's jar as Private-Package. While OSGI hides this stuff from other bundles, maven does not. So we get collisions: org.apache.sling.xss embeds e.g. version 1.7.0 of commons-beanutils, which is incompatible with the newer version of commons-beanutils we have been using, so that we now get compile errors.
Is there any good solution to this - both from our perspective, as from the perspective of the maintainers of the org.apache.sling.xss bundle? Ideally, you'd get only the exported org.apache.sling.xss package into the classpath if you use that bundle. So maybe a good solution would be if org.apache.sling.xss provided a separate API jar, containing only that class? Is there a standard way to do this, or another solution? Can we somehow tell maven just to include only that package into the classpath? (I know maven has dependency exclusion, but that doesn't help here, since the problematic stuff is not a transitive dependency of sling.xss, but actually included into the org.apache.sling.xss jar.)

Unfortunately there is no good way to handle such bundles with maven.
The recommended way is to split the bundle into an API bundle that just defines the API and an implementation bundle that imports the API and imports or embeds all implementation dependencies.
This way consumers will only have a maven dependency to the API and the problem does not occur at all.
Can you open an jira issue for sling xss to provide an API bundle?

You could include in your build system a step that performs OSGi resolution, for example using the bnd-export-maven-plugin.
In this way the build as a whole will fail if you write code that depends on a non-exported package of another bundle. This is because bnd will automatically add an Import-Package for the package that you used in your bundle, but the resolve step will be unable to find a corresponding Export-Package in the other bundle.
Of course this is not ideal. You will still be able to write the code in your IDE and compile it with the maven-compiler-plugin. But you at least find out about the problem before you hit the runtime.

Related

Trouble with dependencies in an OSGI bundle

I have an OSGI bundle that is working perfectly, I added, as a maven dependency, unirest a lightweight HTTP library, when deploying to serviceMix I get a missing requirement:
filter:="(osgi.wiring.package=com.mashape.unirest.http)"
Of course I am using that package in my bundle, but as far as serviceMix is concerned, that library is just classes in my classpath like my own classes
I guess I'am missing something Here
I know it is possible to embed a library, but I don't understand why any additional manipulation is needed ? how is that different from just adding that library as a maven dependency
Any answers and pointers to articles/documentation is really appreciated
There is a difference between the way dependencies are treated in maven and osgi.
Maven treats the dependencies as a single classpath/classloader where the classes from every jar can access the classes from every other jar.
This is historical from main apps, and its the way maven runs unit tests.
In the osgi run-time environment each bundle has its own classloader, and by default, only has access to the classes and jars embedded in its own bundle.
The classes in each bundle are isolated unless the package is exported from one bundle and imported to another.
The maven bundle plugin attempts to bridge the gap. Just adding a dependency to maven is not enough, one must also tell the maven bundle plugin how to package and deploy it. The options are 1) embed the dependency in your bundle, or 2) deploy the dependency as a separate bundle.
The maven bundle plugin defaults to option 2: import everything.
To understand the exports from your dependency better, I suggest looking at the manifest file in the bundle jar. (META-INF/manifest.mf)
I just looked at the unirest jar on maven central and found no packages exported in the manifest. In fact unirest has no bundle headers, so its not an osgi bundle.
The error message
missing requirement : filter:="(osgi.wiring.package=com.mashape.unirest.http)"
means your bundle is attempting to import a package, but servicemix has no bundle exporting it.
To understand this better, I suggest looking at the manifest file in your bundle jar. (META-INF/manifest.mf) I expect it will contain an import of com.mashape.unirest.http. Also look at servicemix to see if anything exports it:
From the command from try: exports | grep com.mashape.unirest.http
I expect you will find no bundle exporting it.
I suggest you configure the maven bundle plugin to embed unirest instead of importing it. like this:
<Embed-Dependency>unirest-java</Embed-Dependency>
(if you already have an Embed-Dependency configuration you will need to merge them.)
Ref. http://felix.apache.org/documentation/subprojects/apache-felix-maven-bundle-plugin-bnd.html#detailed-how-to
Another way is deploy unirest-java as a bundle by wrapping it.
That could be as simple as copying it to the deploy folder:
https://karaf.apache.org/manual/latest/#_wrap_deployer
That would have the advantage of sharing unirest-java.
Lastly, if unirest does its own class loading, then that may turn out to be incompatible with osgi.

Maven OSGi bundling

I am using the maven felix plugin to create OSGi bundles, however suppose you have a package "com.example" that exists in project1 and project2. Additionally project2 has a dependency to project1.
If you export the package in project2, it will have the code from project2 and from project1. This -to me- is really odd behavior. The only reason I can think of that they enabled such behavior is because OSGi requires it somehow? (I have already looked at http://felix.apache.org/site/apache-felix-maven-bundle-plugin-bnd.html but can't seem to find a way to turn it off)
If two jars (A and B) export the same package but different classes in it and a third jar (C) has a dependency to that package, I would assume that C can see both A & B at runtime. Or does OSGi require a different package per jar?
If OSGi is not mandating this, how can I turn this "feature" off?
If OSGi is mandating it, then...why?
UPDATE
The answer provided by Christian clears up the OSGi requirement for the different packages in different jars. However I still have a problem with felix where I have an "api" jar that contains:
com.example.api: the actual interfaces
com.example: a factory class, a utility class,...
And an implementation package that has:
com.example.impl
Now when I build the implementation package with felix and I export "com.example.impl", it will indeed have everything in "com.example.impl" but for some reason it also includes all the classes in "com.example" (not those in .api). Any combination of settings I have tried will not prevent felix from adding the "base" package for some reason...
So basically in the resulting jar of the "impl" project, I actually have the class com.example.MyFactory which is in the api package. How can I block this?
OSGi is not mandating that you have the same package in two projects. In fact you should avoid to have the same package / version combination in two bundles with different contents.
In OSGi the wiring happens when the bundle goes from installed to resolved. In that step the framework matches each Import-Package statement with an exported package that matches name and version range. In OSGi only one package will be wired to each bundle even if several bundles export the same package. This is different to standard java where you would have a mix of the classes from all jars that have the package which can have quite unpredictable results.
In OSGi there is one pattern where you have the same package in several bundles. It is used for official APIs from OSGi a lot. There when you implement an API you also include the API package and have an Import-Package as well as an Export-Package statement for the API package. This allows the implementing bundle to be installed without the need for an additional API bundle. This works well even if there are more than one bundle that includes the API as the framework will select one of the API packages and wire all others to the same package. So they all see the same set of classes and there is not conflict.
You can also do this for you own applications but there it is more common to just have the API package in one bundle and all others just import it.
You can find some info at the apache felix OSGi Frequently Asked Questions
To answer your updated question. I guess you only export the com.example.api package. So the maven bundle plugin knows it can refer to this package using an Import-Package statement. As com.example is not exported the plugin knowns that an Import-Package would not work. So it embeds the classes.
So what you should take away is that you need to export all packages that are needed by other bundles. Btw. you normally do not export an impl package in OSGi. Rather you hide the impl behind a service. The service interface is placed in API. The impl bundle then implements the interface and exports the impl as an OSGi service. So other bundles can bind the service by its interface and the whole impl can stay private.

How to manage dependencies when developing OSGI bundle in Netbeans and Karaf?

I'm struggling to wrap my head around developing OSGI bundles to publish to Karaf. I'm using Netbeans 7.4 and the Create Maven OSGI bundle when creating a new project. I think what I'm wrestling with is dealing with dependencies between different projects and working with them in Netbeans and then publishing to Karaf.
What I'm trying to do is set up a REST web service; I found this tutorial to be a good starting point in getting something basic up and running. So I started setting up a parent project with sub projects (all of these are from the Maven OSGI Bundle template). But after I started deploying bundles to my Karaf instance I started wrestling with the "missing requirement" errors within Karaf. I'd start trying to list each dependency in <Export-Package> or <Embed-Dependency> but it seems like I just keep going farther down and always getting new "missing requirement" messages listing yet another dependency. I then started trying the maven copy-dependencies plugin to export whatever dependencies are used in my project and copying whatever comes out into the Karaf deploy folder, too. That worked for the most part but I'm wrestling with what I think are downstream dependencies of a 3rd party jar, but that's probably a separate question anyway.
So, what I'd like to know is how should I be developing a coherent "application" with this setup? I understand I should separate everything into standalone bundles; i.e., I'll have my REST interface as one, the implementation as another, business logic as another. As a maven project, if I have a dependency for datetime handling or string utilities, maven will handle resolving all of those. But these dependencies won't automatically be included in my OSGI bundle, is that the case? What is the workflow to make sure that everything my project depends on is available to the bundles in Karaf? What about my projects that have other projects as dependencies, i.e., an interface implementation? I would include that project as a dependency so it will compile, but how do I make sure that that dependency is met after it is published?
The mismatch between dependencies at compile time and at deployment time is indeed a problem when working with OSGi.
What you need to know is how these relate to each other. At compile time you have the maven dependencies. When you build your project the maven bundle plugin creates the necessary Import-Package and Export-Package statements for you. Most of the time this simply works and you should avoid tuning too much there by hand.
So after the build the Manifest specifies what the resulting bundle needs but this does not automatically make sure the dependencies are met. So when you deploy the bundle to karaf you get the errors about missing dependencies. This is normal.
Now you need to install other bundles that fulfil these dependencies. Often you can simply install the jars of your maven dependencies (if they are bundles). This does not always work though. So basically you install the bundles and check if the requirements are met now.
The nice thing with karaf is that many bigger dependencies like cxf are already available as features. So it is a good idea to first try to get the dependencies by installing available features before you try to install them bundle by bundle.
So this helps you in getting your dependencies right. Then for "production" use the best solution is to create your own feature file where you refer to the bundles you found.
While installing bundles using the deploy dir seems nice at first it is not a good solution. Better install the bundles directly from maven using the mvn: url syntax karaf provides.
Regarding embedding dependencies. Sometimes it is a good solution but often it only makes things worse as it can lead to package use conflicts which are hard to solve. So better do not embed anything if it is possible.

OSGI - handling 3rd party JARs and their dependencies

I'm new in OSGI development and am struggling to understand how best to handle dependent JARs.
i.e. if I'm creating a bundle the likelihood is that I will need to use a few 3rd party JARs. When I create my bundle JAR to deploy to OSGI, obviously these 3rd party JARs are not included and thus the bundle will not run.
I understand that one option is to turn these JARs into bundles and deploy them to the OSGI container. When I bundled and try deploy third party jar it throws error for their own dependent jars and these dependencies are endless and not possible to fulfill.
What is the best solution to this?
-- Anurag
Finally I resolve third party jar issue actually I forger to add following tag in my POM.xml file
<_exportcontents>*
My issue is resolve, thanks to all of you for your support and giving me valuable clues.
If you are a beginner with osgi then I would not recommend to bundle bigger libs yourself. Often more than defining some import and export headers is required and it can become very difficult. Fortunately a lot of libs are available as bundles.
If the original lobs is not yet a bundle then you can search in maven central for a bundled version from servicemix bundles.
Using apache karaf is also often a big help. There are karaf features with predefined deps for a lot of libs like activemq, cxf, camel, openjpa, ...
In the worst case you can embed the lib and all deps in your own jar. This tends to cause class loading issues though if you try to share some classes between bundles.
What lib is the problem in your case?
A lot of libraries you mention (log4j, quartz, dom4j) have already been converted to OSGi bundles by Springsource. You can find them in the Springsource Enterprise Bundle Repository. All Hibernate libraries > 4.2 are also osgified.
You have three choices:
Find the library in some pre-made bundles (Springsource Repository, Eclipse Orbit Repository, etc.)
Include the libraries into your bundle as an external library (Make a directory lib, copy your jar files there, and reference them from the Bundle-Classpath element in MANIFEST.MF)
Convert the library into an OSGi bundle (e.g. with the bnd wrapconverter, example here)

Avoid duplicating OSGi imports in maven dependancies?

Currently when I am writting a bundle in that depends on a package, I have to "import" or "depend" on a whole other bundle in Maven that contains that package.
This seems like it is counter-productive to what OSGi gives me.
For example let's say I have two bundles: BundleAPI and BundleImpl.
BundleAPI provides the API interfaces:
// BundleAPI's manifest
export-package: com.service.api
BundleImpl provides the implementation:
//BundleImpl's manifest
import-package com.service.api
However, when I am coding BundleImpl in Eclipse, I am forced to "depend" in maven POM on BundleAPI itself - so that eclipse does not complain.
//BundleImpl's POM
<dependency>
<groupId>com.service</groupId>
<artifactId>com.service.api</artifactId>
[...]
</dependency>
So - on one hand, I am depending only on the package com.service.api, while on the other - I need to have the whole bundle - BundleAPI.
Is there a way to make maven or eclipse smart enough to just find the packages somewhere, instead of whole bundles?
I am very much confused as to how this works - any type of clarity here would be great. Maybe I am missing something fundamentally simple?
The key is to distinguish between build-time dependencies and runtime dependencies.
At build time you have to depend on a whole artifact, i.e. a JAR file or bundle. That's pretty much unavoidable because of the way Java compilers work. However at runtime you depend only on the packages you use in your bundle, and this is how OSGi manages runtime substitution. This is the Import-Package statement in your final bundle.
Of course as a developer you don't want to list two parallel sets of dependencies, that would be crazy. Fortunately maven-bundle-plugin is based on a tool called bnd that calculates the Import-Package statement for you based on analysing your code and discovering the actual packages used. Other tools such as bndtools (an Eclipse-based IDE for OSGi development) also use bnd in this way. Incidentally bnd is much more reliable and accurate than any human at doing this job!
So, you define only the module-level dependencies that you need at build time, and the tool generates the runtime package-level dependencies.
I would recommend against using Tycho because it forces you to use Eclipse PDE, which in turn forces you to manually manage imported packages (for the sake of full disclosure, I am the author of bndtools which competes against PDE).
You cannot develop bundles like regular Java projects with Maven and eclipse. You basically have 2 options.
Apache Felix Bundle Plugin: Basically you develop the project as a regular Java project and use Maven as you normally would. This plugin will be used to add all the OSGi specifics to the jar manifest at deployment time to OSGi enable it. The disadvantage of this aproach is that you are using a Java project in your workspace instead of a bundle, which makes running your project in the OSGi container a little extra work since Eclipse doesn't recognize it as a plugin project. Thus you have to add the jar from the Maven build as part of the target platform manually.
Tycho: This is another Maven plugin that attempts to actually bring theses two environments together and does a pretty good job of it. In this scenario, you actually create an Eclipse bundle/plugin project, which obviously makes for seamless integration in Eclipse. The pom then marks the project as being an eclipse-plugin type, which effectively makes Maven resolve the project dependencies (defined in the manifest) via the target platform instead of Maven itself.
I would take the Tycho approach as it gives a much more integrated approach with Eclipse.
Having the whole jar as a dependency shouldn't be a problem, that's how you have to do it with Maven anyway.

Categories

Resources