Getting started with bundle DI in an OSGi environment - java

I have been developing an application with Apache Felix as my OSGi runtime for a while and up until now progress has been great. However, I want to now adopt dependancy injection and the "default" mechanism with Apache Felix seems to be iPOJOs.
However, I've found that the documentation and tutorials around about Apache felix are too weak and I've not managed to get anywhere. So I am prepared to change, but first of all I'd better explain the (very simple) think I'm trying to do.
Given two bundles;
Consumer bundle
package bundles.consumer;
class Consumer implements BundleActivator{
#Inject
private Producer producer;
public void bundleActivated(BundleContext con) {
this.producer.getNextItem();
}
}
Producer Bundle
package bundles.producer;
#Singleton
class Producer {
public String getNextItem() {
return "item x";
}
}
I want the OSGi runtime to start up the Consumer bundle, realize that it needs a Producer to work, the framework then starts the Producer bundle and injects and instance into the Consumer. Simples. iPOJOs suggest that this is possible using annotations only (#Singleton, #Inject) or similar, but I simply can't get it to work. OSGi apparently has declarative services, but that means writing a lot of XML, which I really want to avoid.
Anywoo, I'm prepared to adapt JBoss, Equinox or alternative OSGi runtime and an alternative dependancy injection mechanism, be that Peaberry, Spring DM or something completly difference. I'm already using Maven.
What I'm asking, is can somebody create a Hello World using the Producer and Consumer idea to help me get started? I've really spent a tonne of time reading up and find the learning curve insurmountable!
edit, my attempt using SCR: http://tydus.net/codeExamples/mvnScr.tar

It's better to use Declarative Service because it's a OSGi Standard.
You can use Java Annotations to describe DS dependencies, services and components with Maven SCR plugin
Regards,
Dmytro

There are two things to bear in mind; 1) your bundle will probably need some extra metadata for the dependency injection framework and 2) you'll need to deploy the DI framework's runtime.
Dependency injection is not part of core OSGi (i.e. the Felix framework) so you must first deploy the iPOJO bundle(s) before deploying your own. Additionally you'll need to add the metadata required by IPOJO, google "maven-ipojo-plugin" for more info.
If you're looking for a container that is less bare-bones than Felix on it's own, then try Karaf (it comes with lots of enterprise extras).
Personally, I like Declarative Services (so +1 Dmytro) as it's very simple and merely removes the boiler plate code from OSGi (remember to deploy it as well and provide component.xml in your bundle see maven-scr-plugin for more info).
EDIT
(In answer to comment and link to tarball below)
I moved the #Service annotation from Producer to ProducerImpl.
The generated SCR component.xml wasn't quite correct for the Consumer, by adding an unbind() method and changing the #Reference the following works:
//...
import static org.apache.felix.scr.annotations.ReferenceCardinality.MANDATORY_UNARY;
import static org.apache.felix.scr.annotations.ReferencePolicy.DYNAMIC;
//...
#Component
public class Consumer
{
#Reference(policy = DYNAMIC, cardinality = MANDATORY_UNARY)
private Producer producer;
public void unbindProducer() {
System.out.println("Producer unbound.");
this.producer = null;
}
//...
}
Deploying Maven
As you're using maven, install Pax Url for maven, this way you can easily install bundles from local and remote repositories.
First get maven to download a version for you (pax-url-mvn version 1.3.5 is available) then install the file (or copy to Felix bundle dir)
-> install file:/YOUR_PATH_TO_MAVEN_REPO/.m2/repository/org/ops4j/pax/url/pax-url-mvn/1.3.5/pax-url-mvn-1.3.5.jar
Bundle ID: 7
-> start 7
DEBUG: WIRE: [7.0] osgi.wiring.package; (osgi.wiring.package=javax.net.ssl) -> [0]
DEBUG: WIRE: [7.0] osgi.wiring.package; (osgi.wiring.package=javax.xml.parsers) -> [0]
DEBUG: WIRE: [7.0] osgi.wiring.package; (&(osgi.wiring.package=org.osgi.framework)(version>=1.0.0)(!(version>=2.0.0))) -> [0]
DEBUG: WIRE: [7.0] osgi.wiring.package; (&(osgi.wiring.package=org.osgi.service.cm)(version>=1.0.0)(!(version>=2.0.0))) -> [2.0]
DEBUG: WIRE: [7.0] osgi.wiring.package; (&(osgi.wiring.package=org.osgi.service.url)(version>=1.0.0)(!(version>=2.0.0))) -> [0]
DEBUG: WIRE: [7.0] osgi.wiring.package; (osgi.wiring.package=org.w3c.dom) -> [0]
DEBUG: WIRE: [7.0] osgi.wiring.package; (osgi.wiring.package=org.xml.sax) -> [0]
->
You can now install your own bundles from your local repository with the mvn URL handler:
-> install mvn:com.examples/producer/1.0.1
Bundle ID: 10
-> start 10
-> install mvn:com.examples/consumer/1.0.1
Bundle ID: 11
-> start 11
DEBUG: WIRE: [11.0] osgi.wiring.package; (&(osgi.wiring.package=com.examples.producer)(version>=1.0.0)(!(version>=2.0.0))) -> [10.0]
Producer bound.
Consumer activate
Aloha.
->

I've written a fragment for the Equinox Declarative Services bundle that allows you to mark up your classes with annoations that correspond to DS servicce XML elements; yes, it's a hack with its own limitations, but it's working good so far in projects with 90+ bundles. I could probably manage to clean it up, document and publish it this week if you are interested.

Related

Servicemix Spring 4 wiring issue

Edit:
To better/shorter folmulate my main question;
Is it possible to get an OSGI application running using spring-osgi (1.2.1) and Spring 4 at the same time? And if so, how?
Original:
I am trying to make servicemix 6.1.3 work with Spring 4.
I know support has been officially dropped as of Spring version 3 but I need spring 4 for security reasons and frankly keeping our application up to date with newer functionality.
So I imported the required servicemix spring V4 bundles.
The issue here is that our application also uses the spring-osgi functionality (so not the abovementioned osgi-ified bundles by servicemix but the actual spring-osgi bundles which have V 1.2.1 as seen below)
The error:
Error executing command: Error executing command on bundles:
Unable to execute command on bundle 352: Uses constraint violation. Unable to resolve bundle revision my.app.service [352.7] because it is exposed to package 'org.springframework.context.support' from bundle revisions org.apache.servicemix.bundles.spring-
context [240.0] and org.apache.servicemix.bundles.spring-context [105.0] via two dependency chains.
Chain 1:
my.app.service [352.7]
import: (&(osgi.wiring.package=org.springframework.context.support)(version>=4.2.0)(version<=4.4.0))
|
export: osgi.wiring.package=org.springframework.context.support
org.apache.servicemix.bundles.spring-context [240.0]
Chain 2:
my.app.service [352.7]
import: (&(osgi.wiring.package=org.springframework.osgi.web.context.support)(version>=1.2.1))
|
export: osgi.wiring.package=org.springframework.osgi.web.context.support; uses:=org.springframework.osgi.context.support
org.springframework.osgi.web [252.0]
import: (&(osgi.wiring.package=org.springframework.osgi.context.support)(version>=1.2.1)(version<=1.2.1))
|
export: osgi.wiring.package=org.springframework.osgi.context.support; uses:=org.springframework.context.support
org.springframework.osgi.core [109.0]
import: (&(osgi.wiring.package=org.springframework.context.support)(version>=2.5.6)(!(version>=4.0.0)))
|
export: osgi.wiring.package=org.springframework.context.support
org.apache.servicemix.bundles.spring-context [105.0]
Now as you can see the issue is that I have spring-context twice running in servicemix. Once from the default installation [105] (I expect on the background a lot of servicemix's bundles (camel, activeMQ, etc) are using Spring 3, hence I dont want to remove them in fear of other issues; but if you know better, please enlighten me) and a Spring V4 bundle [240].
Our usage of spring-osgi requires us to use its bundles which are hardcoded to use "org.springframework.context.support version>=2.5.6 !version>=4.0.0".
Now my question;
- Is there a way around this? To force org.springframework.osgi.core to use the spring-context V4 bundle? Or is it just impossible to use Spring 4 at the same time as Spring 3's OSGI bundles?
Additional info:
I used the following in my pom's maven-bundle-plugin import-Package tag to enforce usage of Spring 4: (spring.version=[4.2,4.4])
org.springframework.context.support;version="${spring.version}",
And in the parent pom has the following in the dependency-management section.
<dependency>
<groupId>org.apache.servicemix.bundles</groupId>
<artifactId>org.apache.servicemix.bundles.spring-context</artifactId>
<version>${spring.version}</version>
<scope>provided</scope>
</dependency>
Edit 2:
Maybe I can manually bypass the org.springframework.context.support (version>=2.5.6)(!(version>=4.0.0)) requirement by downloading the spring osgi 1.2.x branch from github, change the in the pom and recompile.
Still obviously this isnt a good solution at all sice im hacking myself through to unsupported versions, so please advise.

Registering services inside OSGi extension bundles

I'm playing with OSGi framework extension bundles in order to fully understand them.
After looking at the OSGi R6 specification (3.15 and 4.2.4.1) I've sucessfully invoked the "start" method of the ExtensionBundleActivator. Now I'm trying to register a service inside such activator. However, when I trying to consume such service, the reference annotation fails to connect the service.
Here's my code ('ve changed the name of the bundle, but it shouldn't matter):
public class ExtensionBundleActivator implements BundleActivator {
#Override
public void start(BundleContext context) throws Exception {
System.out.println("start extension bundle activator!");
context.registerService(
BundleExample.class.getName(),
new BundleExampleImpl(),
new Hashtable<>(new HashMap<>()));
}
#Override
public void stop(BundleContext context) throws Exception {
//service automatically unregistered
}
}
And here's the manifest of such extension bundle:
Manifest-Version: 1.0
Bnd-LastModified: 1476436248622
Build-Jdk: 1.8.0_91
Built-By: massi
Bundle-ClassPath: .
Bundle-ManifestVersion: 2
Bundle-Name: extensionbundleexample
Bundle-SymbolicName: com.massimobono.microsi.extensionbundleexample
Bundle-Version: 0.0.1.SNAPSHOT
Conditional-Package: com.massimobono.microsi.common.*;
Created-By: Apache Maven Bundle Plugin
ExtensionBundle-Activator: com.massimobono.microsi.bundleexample.imp
l.ExtensionBundleActivator
Fragment-Host: system.bundle; extension:=framework
Provide-Capability: osgi.service;objectClass:List<String>="com.massimobo
no.microsi.bundleexample.BundleExample"
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"
Service-Component: OSGI-INF/com.massimobono.microsi.bundleexample.im
pl.ExtensionBundleExample.xml
Tool: Bnd-3.0.0.201509101326
The consuming bundle (part of the bundle):
#Reference(cardinality=ReferenceCardinality.OPTIONAL)
public BundleExample actualBundleExample;
#Activate
public void activate() {
System.out.println("activating " + this.getClass().getSimpleName() + "with actual bundle set to "+ this.actualBundleExample);
}
My question is: What am I doing wrong? Why the consumer can't detect the registered service of the extension bundle? Or maybe I'm just doing something the framework forbids... in this case is it impossible to provide a service from an extension bundle? How can I interact with the code within the extension bundle (aka accessing something inside the extension bundle itself)?
Here some notes:
I'm using felix as OSGi implementation;
Both "ExtensionBundleExample" and "BundleExample" are loaded inside the auto-process folder of felix (the default one is "bundle" but I tweaked the config.properties to use "corebundles" folder;
The output corretly show "start extension bundle activator!" but when it's time to display the reference of actualBundleExample, the output shows "null";
the optional cardinality of BundleExample is used for testing purposing: I just wanted to invoke the "acivator" method the consumer component has (in order to see the System.out.println console;
from my previous question I understand extension bundles are a niche inside the OSGi framework, but I want to understand them nonetheless: I find the lack of examples on the internet regarding this topic quite annoying;
Thanks for any reply!
The primary purpose of extension bundles is for framework extensions, absolutely not for regular usage. I.e. extension bundles are often tightly coupled to a framework. The reason is that many rules do not count for extension bundles because they are on the "wrong" side of the fence. There are few examples for very good reasons. You should not use them unless you really know what you're doing because most of the OSGi rules do not apply.
That said. My expectation is that the package you use for the BundleExample differs between the extension bundle (comes from the class path) and the DS example exported by some bundle. Since they come from different class loaders OSGi considers them different services because you would get a class loader exception when you tried to use it.
You can solve this by letting the framework export this package.
Just a guess.

Apache Felix Host expose dependencies OSGi

I am using Apache Felix to create an embedded OSGi host application. I am using the following code to expose the packages I want to expose:
List<String> extra = new ArrayList<>();
extra.add("some.example.packag.to.expose.1");
extra.add("some.example.packag.to.expose.2");
extra.add("some.example.packag.to.expose.3");
config.put(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, extra.toString().replace("[","").replace("]", ""));
Everything works great and these packages are exposed. However, I need the bundles to have access to ALL the host project declared dependencies. So for example the parent application has Jackson, Apache (various), etc. declared and I need the bundles to have access to these.
I tried adding the packages explicitly but that does not seem to do the trick when they are dependencies. So for example in the bundle I want to use Jacksons com.fasterxml.jackson.core.type.TypeReference; so I added com.fasterxml.jackson.core.type to the above EXTRA list but it does not appear to solve the problem, the package still doesn't get exposed.
In a perfect work I just want to make ALL the host dependencies available without having to explicitly state each one.
You will have to configure each package. In OSGi you would normally install the dependencies as bundles. So the settings do not suppot to mass export system packages.

starting slf4j bundle doesn't start on felix

I have felix-framework-5.0.1 and I'm trying to start slf4j-api-1.6.0.jar bundle into felix isgi container.
in felix console I'm typing install file:bundle/slf4j-api-1.6.0.jar
I'm getting a message Bundle ID: 42
then I'm trying to start the bundle start 42
I'm getting the message
org.osgi.framework.BundleException: Unable to resolve slf4j.api [42](R 42.0): missing requirement [slf4j.api [42](R 42.0)] osgi.wiring.package; (&(osgi.wiring.p
ackage=org.slf4j.impl)(version>=1.5.5)) Unresolved requirements: [[slf4j.api [42](R 42.0)] osgi.wiring.package; (&(osgi.wiring.package=org.slf4j.impl)(version>=
1.5.5))]
g!
Can any body help me? how can I start slf4j bundle into felix?
slf4j-api needs org.slf4j.impl package. This package is included into every slf4j implementations like slf4j-simple, slf4j-logback, etc.
The implementation bundles need org.slf4j package that comes from the API artifact. There is a cross-reference. This can work only due to the reason that implementations are fragment bundles of the API. When the implementation is installed together with the API, they will have a common classloader and they will be resolved together. Both of their requirements will be satisfied.
In short: You must choose one of the implementations and install that as well. E.g.: slf4j-simple.
You should use the same version of API and implementation to satisfy their "cross requirement".
Your library contains the following 3 packages (link):
org.slf4j
org.slf4j.helpers
org.slf4j.spi
but you have to have an additional library that will contain org.slf4j.impl package.
Also, from manifest file you can see what packages are exported (Export-Package) (they are visible by other bundles) and what packages are imported (Import-Package) (they have to be accessible for the given bundle). Sometimes the given package have to be from bundle with the correct version.
If you cannot find correct bundle maybe this page will help you.

OSGI bundle is unable to start

I have an OSGI bundle which gets to the RESOLVED state, but never reaches the ACTIVE state. When I run my application, this is the stacktrace I am obtaining: http://tny.cz/fa949f16
Afterwards, when I try to start the bundle explicitly through the OSGI console, I get this error:
start 53
gogo: BundleException: The activator rsy.home.mac.sm.schedule.service.win.WinServiceActivator for bundle rsy.home.mac.sm.schedule.service.win is invalid
Notice that in the end of the stacktrace ouput, there's the following message:
!ENTRY org.eclipse.osgi 4 0 2014-03-14 10:52:50.984 !MESSAGE Bundle
rsy.home.mac.sm.schedule.service.win_1.0.0 [53] is not active.
Here's the code from WinServiceActivator:
package rsy.home.mac.sm.schedule.service.win;
import java.util.HashMap;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
public class WinServiceActivator implements BundleActivator {
private static BundleContext context;
#Override
public void start(BundleContext context) throws Exception {
ServiceActivator.context = context;
ScheduleService schedServ = new ScheduleService();
schedServ.setTdMapping(new HashMap<String, String>());
schedServ.setHtMapping(new HashMap<String, String>());
schedServ.setDoMapping(new HashMap<String, String>());
context.registerService(ScheduleService.class.getName(),
schedServ, null);
}
#Override
public void stop(BundleContext context) throws Exception {
context.ungetService(context.getServiceReference(ScheduleService.class.getName()));
ServiceActivator.context = null;
}
}
And this is my MANIFEST file:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: SM Schedule Query Service
Bundle-SymbolicName: rsy.home.mac.sm.schedule.service.sm;singleton:=true
Bundle-Version: 1.0.0
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Service-Component: OSGI-INF/sm_schedule_service.xml
Require-Bundle: rsy.home.mac.sm.jaxrs.lib;bundle-version="1.0.0",
rsy.home.mac.sm.config;bundle-version="0.1.0",
rsy.home.mac.log,
rsy.home.mac.sm.model;bundle-version="1.0.0",
rsy.home.mac.sm.schedule.service;bundle-version="1.0.0",
rsy.home.mac.sm.sm.scheduletable;bundle-version="1.0.0",
resources;bundle-version="1.0.0",
rsy.home.mac.portal.utilities,
rsy.home.mac.portal.logging;bundle-version="1.0.0",
org.eclipse.xsd;bundle-version="2.7.0",
org.eclipse.osgi
Import-Package: org.osgi.service.http;version="1.2.1"
Bundle-ActivationPolicy: lazy
Bundle-Activator: rsy.home.mac.sm.schedule.service.sm.SmServiceActivator
Export-Package: rsy.home.mac.sm.schedule.service.sm
While googling the error "Activator start error, ClassCastException: xxxx cannot be cast to org.osgi.framework.BundleActivator", I found this:
That error is telling you that you have two copies of the BundleActivator class loaded into your VM somehow. The framework is using one and your bundle is using another.
Do you have any other bundles exporting org.osgi.framework? Where is your bundle getting this package from? If you issue the following command in the Felix shell you can see the wiring:
inspect package requirement <bundle-id>
or shortened to:
inspect p r <bundle-id>
Where <bundle-id> is the ID of your bundle, then you should see from where it is getting org.osgi.framework. If it is not the system bundle (org.apache.felix.framework), then you have an issue.
When I executed the given command, I obtained this output:
org.osgi.framework; version="1.7.0" -> org.eclipse.osgi_3.9.0.v20130410-1557 [0]
Do you think this is the reason why the bundle doesn't start? If so, how can I fix this? Following this comment I found on the internet and the comments and answers from people below, I tried to replace this line in the MANIFEST file:
Import-Package: org.osgi.service.http;version="1.2.1"
by this one:
Import-Package: org.osgi.framework
And I get a whole bunch of errors in the code, whose reason given by Eclipse is:
Multiple markers at this line
- Access restriction: The type BundleActivator is not accessible due to restriction on required library
rsy.home.mac.sm.jaxrs.lib/lib/org.osgi.core-4.2.0.jar
- Access restriction: The type BundleListener is not accessible due to restriction on required library
rsy.home.mac.sm.jaxrs.lib/lib/org.osgi.core-4.2.0.jar
I would really appreciate some help on this... Thanks!
I had a similar exception some time ago when I tried to get CXF working in an eclipse rcp application. The reason was that I did require bunddle on org.eclipse.osgi. You should avoid that.
Require bundle means that you import all packages of that bundle. The framework bundle (org.eclipse.osgi) exports the OSGi API. Probably another bundle listed in your require bundle list also exports the BundleActivator package. So you get the class from two sources and likely use a different one than the framework itself.
These problems are the reqson why you should avoid require bundle. Espcially try to avoid doing require bundle on org.eclipse.osgi. THis bundle also exports all packages defined in the system exports. So it exports lots of packages and chances are high you run into problems. In my case with cxf I had a similar problem with the jaxb api that is exported by org.eclipse.osgi and also by one of the bundles cxf brings with it.
This article may give you some more details as the problem you observe often surfaces as a uses constraint violation. http://njbartlett.name/2011/02/09/uses-constraints.html
In general if you have the chance use the maven bundle plugin or bndtools to generate the Manifest. Both have good defaults that avoid most of these problems.

Categories