I am really new on this one (OSGI), trying to do simple examples. I cant make lazy actication work. I know there a few Blueprint impl out there to resolve such issues, but before proceeding with one, I thought it would be good to learn a few basics.
Bundle DataService:
Manifest-Version: 1.0
Bundle-Version: 1.0.0
Bundle-Name: DataService
Bundle-ManifestVersion: 2
Bundle-Activator: DataService.Activator
Import-Package: org.osgi.framework
Bundle-SymbolicName: DataService
Export-Package: DataService;version="1.0.0"
Bundle-ActivationPolicy: lazy
Bundle DataServiceClient:
Manifest-Version: 1.0
Bundle-Version: 1.0.0
Bundle-Name: DataServiceClient
Bundle-ManifestVersion: 2
Bundle-Activator: DataServiceClient.Activator
Import-Package: org.osgi.framework, DataService;version="[1.0.0,1.0.0]"
Bundle-SymbolicName: DataServiceClient
Ok I have changed my code, but still no luck.
Outer application, install bundles, starts framework and then only starts DataServiceClient bundle.
No access to any bundle class.
File bundleDir = new File("./bundles/");
String[] bundleResources = bundleDir.list();
for(String bundleResourcePath : bundleResources) {
File bundleResource = new File(bundleDir, bundleResourcePath);
InputStream bs =new FileInputStream(bundleResource);
mFramework.getBundleContext().installBundle(bundleResource.getName(), bs);
}
mFramework.start();
bl = mFramework.getBundleContext().getBundles();
for(Bundle b : bl) {
if (b.getBundleId() != 0 && b.getSymbolicName().contains("DataServiceClient")) {
b.start();
}
}
Here is the start of DataServiceClient:
System.out.println("DataServiceClient Start");
IDataService service = new DummyService();
System.out.println(service.getData());
Here is the DummyService class in "DataService" bundle.
public class DummyService implements IDataService {
#Override
public String getData() {
return "DummyService Data";
}
}
Here is the start of "DataService" bundle:
System.out.println("DataService Start");
The output I am getting:
DataServiceClient Start
DummyService Data
However I expect to see:
DataServiceClient Start
DataService Start
DummyService Data
a little quatation from http://www.osgi.org/Design/LazyStart
Lazy Activation
Lazy activation is a life cycle policy that mandates a bundle MUST be activated upon the first successful request to load a class from that bundle.
However since it doesnt work, i guess i completely misunderstand the concept of lazy activation or i am doing something wrong.
Unless I explicitly call start for DataService bundle, it seems it doesnt invoke Activator.start for DataService bundle. This is what I am not getting atm.
Thx for your time
Are you sure, your Activator is really not called. I often had the case that the activator was called but experienced and exception which OSGi swallowed. Can you try a println on the first line in the Activator.start to check this. A try catch with logging also is useful in this case.
Btw. Naming a package with an upper case letter is highly unusual. Not sure if it is a problem but I would avoid that.
It's not clear what's going on when you call DummyClient.GetData(). You say that it invokes a class in the DataService bundle, but how?? DataService is an ordinary bundle and your code is the main Java launcher application, and there is no way in OSGi for the "outer" application to depend statically upon an ordinary bundle.
Anyway, even if you could do this, you execute this line of code before the bundle is started. The bundle activator will certainly not be called before the bundle is started!! I would expect your activator to be called at line 36, i.e. where you call bundle.start() on each bundle.
But really... what on earth are you trying to do?? The Bundle-ActivationPolicy: lazy flag is almost completely useless. I have eight years' experience of OSGi, and have only ever used this setting in Eclipse RCP applications, for legacy reasons. Unless you are writing an Eclipse plug-in or an Eclipse RCP application, you should not use Bundle-ActivationPolicy: lazy in OSGi.
The proper way to get lazy (or "just in time") instantiation in OSGi is to use Declarative Services (DS). All service objects published by DS are instantiated on demand, when a client first tries to invoke them rather than at the time they are registered. You do not need to do anything special to enable this.
Regarding the changed code... you never actually start the bundle DataServiceClient, so its activator cannot be called. You have explicitly excluded it by name from the loop in which you start the bundles. OSGi will only ever call the BundleActivator on bundles that have been started with bundle.start().
This is a very widely misundertood point... OSGi never automatically starts bundles, even with the Bundle-ActivationPolicy: lazy flag enabled.
Probably what you meant to do is start the bundle as follows:
bundle.start(Bundle.START_ACTIVATION_POLICY).
In fact you do this for all bundles rather than arbitrarily starting a subset of bundles.
But again, I must reiterate the point I made in my other answer. Using Bundle-ActivationPolicy: lazy is pointless, except if you are developing Eclipse RCP applications, in which case you sometimes have to use it for stupid legacy reasons.
Related
I have bundle that deals mostly with interfaces and it uses a factory from a thrid party jar to get the instances for the interfaces it is using.
For example,
my-bundle.jar has...
com.oth.itf.Intrface itf = Fctry.getInstance('ABC');
has the
imports-package for com.oth.itf
third-party.jar has..
public static com.oth.itf.Intrface getInstance(String abc) {
if (...) {
return new com.oth.impl.ItfInstance();
}
}
has the exports-package for com.oth.itf and com.oth.impl
Everything works fine and bundles also get deployed but the issue is that I am getting
ClassNotFoundException for ItfInstance
on
my-bundle
when the code gets executed.
I tried adding import-package for com.oth.impl but to no avail. Things started to work when I added a
dummy declaration of com.oth.impl.ItfInstance
some where in my-bundle.jar. Looks like, Karaf gets the imports only if we explicitly use them. Is there a better way? Is there a way to force Karaf to import packages even if we don't use them explicitly?
Neil is right, if it's not used in the code it can't be imported. With Karaf you have the possibility to help. With the command bundle:dynamic-import you can add a dynamical import to the bundle on run-time. With this you're able to find the actually needed imports via bundle:headers you'll find the imported packages of this bundle. Take those and add those missing imports to your manifest generation and your're set.
If you doesn't has bundle: option, uses this dev: command:
dev:dynamic-import <BundleID>
This allows Karaf import dependencies on runtime.
I'm having a problem with service resolution of the equinox service registry implementation and I'm not sure whether this is a bug.
Here is a short description of my bundles and runtime:
Bundle com.foo.api
exports com.foo.base with an interface IFooService
IFooService is not inherited from ManagedService or in any form related to it
Bundle com.foo.impl
imports com.foo.base
imports org.osgi.service.cm
registers FooServiceImpl which implements IFooService and also ManagedService from org.osgi.service.cm
This it the component.xml of FooServiceImpl:
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
immediate="true" name="com.foo.impl.FooServiceImpl">
<implementation class="com.foo.impl.FooServiceImpl"/>
<service>
<provide interface="com.foo.api.IFooService"/>
<provide interface="org.osgi.service.cm.ManagedService"/>
</service>
<property name="service.pid" value="fooservice"/>
</scr:component>
Bundle com.foo.user
imports com.foo.base
imports org.osgi.service.cm
implements a component needing a IFooService (it does only request this interface and not the ManagedService interface)
The component.xml
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
name="com.foo.user.BarComponentImpl"
enabled="true" immediate="true">
<implementation class="com.foo.user.BarComponentImpl" />
<reference interface="com.foo.api.IFooService"
name="fooService" policy="static" cardinality="1..1"
bind="bindFooService" unbind="unbindFooService"/>
</scr:component>
Runtime
I'm using equinox, in config.ini the bundles described above, org.eclipse.osgi.service and org.apache.felix.configadmin are loaded.
(Beside others of course, but they are not of interest right now)
org.eclipse.osgi.service and org.apache.felix.configadmin both provide the package org.osgi.service.cm
org.eclipse.osgi.servce provides version 1.4
org.apache.felix.configadmin provider version 1.5
The problem
Depending on the order of the bundles in config.ini it can happen that com.foo.user does not get a refence to FooServiceImpl.
Debugging into the equinox runtime I found that this happens:
com.foo.impl uses org.osgi.service.com version 1.4 (from the org.eclipse.osgi.service bundle)
com.foo.user uses org.osgi.service.com version 1.5 (from the org.apache.felix.configadmin bundle)
The equinox registry detects that the interface IFooService is known by com.foo.user but the interface ManagedService which is also implemented by IFooServiceImpl is not compatible between com.foo.impl and com.foo.user. The registry therefor does not return a service reference. Although the declarative services implementation lists the dependency as resolved using the comp command at the osgi console.
Is this the desired behaviour in this case? Shouldn't the framework return a reference to FooServiceImpl when requesting a IFooService even if the service implements another interface that is not compatible with the using bundle?
I didn't find any statement in the OSGi specification, but before opening a bug at eclipse I wanted to hear what the experts think.
Update
Thanks to BJ Hargrave the main problem is solved (see below), but I'm still wondering why the framework handles these two cases differently
Bundle A requests IFooService, it imports only com.foo.api
Bundle B requests IFooService, it imports com.foo.api and org.osgi.service.cm (but in a "wrong version")
Bundle A gets a reference to the service, Bundle B doesn't.
Whats the difference between
"an interface is not known by a bundle" and
"an interface is imported in an incompatbile version"
when this interface isn't acutally used when requesting service?
Shouldn't the framework return a reference to FooServiceImpl when requesting a IFooService even if the service implements another interface that is not compatible with the using bundle?
It is specified only to return services which are type compatible with the requester. You don't provide the specifics of how you register and find the service in question to provide a more definitive answer.
The solution here is to not install the org.eclipse.osgi.service bundle. A bundle such as that (a collection of unrelated, exported packages), causes these sorts of problems. org.eclipse.osgi.service is useful at compile time since you have access to a large palette of services. But at runtime, it creates the sort of problems you see. At runtime, it is better to have either the service implementation export the package, as Felix CM does, or have a bundle export just the service package to support the implementation and its clients.
OSGi provides a osgi.cmpn.jar, which is like org.eclipse.osgi.service, for use at compile-time. The latest versions of this jar include an unresolvable requirement to prevent people from using the jar as a bundle at runtime.
I agree with BJ that it is probably the normal behaviour in OSGi. What you could try to solve the problem is to register the service separately for each interface. If that still does not help you could create a spearate class implementing ManagedService that forwards the config updates to the main service.
I have a modular application which uses OSGi framework. Here I'm using org.eclipse.equinox.common_3.4.0 OSGi container. So now the application is already running with all the osgi bundles installed and active and I am displaying all the active OSGi bundles on the UI, by looping though a hash map, based on some action.
Now the requirement is, while the application is already running, I want to instal a new OSGi bundle, from a file system, by giving this new bundle to the application's OSGi container so that it will start this bundle.
How do I achieve this ?
I have tried reading the OSGi bundle as a JarInputstream and read the bundle activator fully qualified class path and tried to instantiate this using Class.forName("") and type casted to BundleActivator interface. But while starting it, it is taking bundle context as a argument to start method.
Is there way where I can just give the OSGi bundle to the container pragmatically so that it will take care of installing and starting the bundle and then my UI will automatically picks up this new bundle name in the display.
Assuming you have the file to load, you can install the bundle like:
void install( BundleContext context, File file) throws Exception {
Bundle b = context.installBundle( file.toURI().toString() );
b.start();
}
And you can uninstall it (if the file is gone):
void uninstall( BundleContext context, File file) throws Exception {
Bundle b = context.getBundle( file.toURI().toString() );
b.uninstall();
}
You get the BundleContext from your activate or Declarative services component's activate method. These are the recommended methods but in dire cases you can also use:
BundleContext context = FrameworkUtil.getBundle( this.getClass() ).getBundleContext();
Though handy it bypasses some mechanism that you might want to use in the future so getting the context in the recommended way is much better
OK. So I have a org.osgi.framework.launch.Framework which I created programmatically in the following way.
framework = ServiceLoader.load(FrameworkFactory.class).iterator().next().newFramework(getFrameworkConfig());
framework.start();
installBundles(BUNDLES_PATH); // installs bundles from a directory, by searching BUNDLES_PATH recursively for JARs
What I want to do, is have a universal loadClass method (as a method in this class) Which will scan the installed bundles, read their Export-Package: declarations, and call the correct bundle's loadClass method, based on the packageName of the class that I'm passing as a parameter.
Is there a smart way to do this? or is it better to just do this:
Class<?> c = null;
// else try every installed bundle one-by-one
for (Bundle bundle : framework.getBundleContext().getBundles()) {
try {
c = bundle.loadClass(className);
} catch (ClassNotFoundException e) {
// OK, move onto next bundle
continue;
}
if (c != null)
break;
}
return c;
I realize I could use services to just have the bundles publish their available services and have the framework query the service with getAllServiceReferences() but this is more work for the programmer, and I'm not sure I wanna go the route of Declarative services.
I would caution against taking this approach. An OSGi framework can contain multiple providers or versions of a package. Trying to make a unified class loader that correctly copes with this is not trivial. However if you want to query the packages being exported you have two options depending on which version of OSGi you are using.
If you are using OSGi prior to OSGi R4.3 you can use PackageAdmin. This is a service registered in the service registry that allows you to query what packages are exported by bundles in the framework. Of specific interest to you will be the getExportedPackage method. This allows you to select a provider of the package and then you can call loadClass on the bundle that provides the package.
PackageAdmin is deprecated in R4.3 (although equinox still implements it). This is more complicated. To do what you desire you need to make use of the new BundleWiring API. This involves getting hold of each bundle in the system and do the following:
BundleWiring bw = bundle.adapt(BundleWiring.class);
List<BundleCapability> capabilities = bw.getCapabilities(BundleRevision.PACKAGE_NAMESPACE);
for (BundleCapability bc : capabilities) {
String pkg = bc.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE);
}
in this you case need to cope with multiple providers and select the correct one. Working out the correct one is the hard part.
I'm in the process of upgrading my application the latest release of Eclipse Equinox 3.7 and associated libraries. I'm excited to have the generic support in OSGi 4.3 and digging the new Bundle.adapt() concepts. I've run into one stumbling block.
Previously, I've used PackageAdmin.getBundle(Class) to find out which bundle loaded which classes. I have to do this due to some RMI object serialization usage.
QUESTION: Is there an way to accomplish the same result, mapping Class to Bundle, using the new BundleWiring API?
I realize that PackageAdmin is probably not going away anytime soon, but I hate deprecation warnings.
Kinda embarrassed that I didn't find this the first time I looked through the document. Answering my own question for completeness.
From the core specification PDF ...
Section 3.9.9 - Finding an Object’s Bundle
There are scenarios where a bundle is required in code that has no access to a Bundle Context. For this
reason, the framework provides the following methods:
Framework Util – Through the FrameworkUtil class with the getBundle(Class) method. The
framework provides this method to allow code to find the bundle of an object without having the
permission to get the class loader. The method returns null when the class does not originate from
a bundle.
Class Loader – An OSGi framework must ensure that the class loader of a class that comes from a
bundle implements the BundleReference interface. This allows legacy code to find an object’s
bundle by getting its class loader and casting it to a BundleReference object, which provides
access to the Bundle. However, this requires the code to have the permission to access the class
loader. The following code fragment shows how to obtain a Bundle object from any object.
ClassLoader cl = target.getClassLoader();
if ( cl instanceof BundleReference ) {
BundleReference ref = (BundleReference) cl;
Bundle b = ref.getBundle();
...
}
In an OSGi system, not all objects belong to the framework. It is therefore possible to get hold of a
class loader that does not implement the BundleReference interface, for example the boot class path
loader.