Loading of OSGi bundle dynamically from a file system - osgi

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

Related

Restarting Apache Felix

I am doing a self updating application using Apache Felix and I can't seem to forcibly restart Felix after update. I did some tricks to simulate the restart process by using some kind of application State.
public class LauncherActivator implements BundleActivator {
public static LauncherState State;
#Override
public void start(BundleContext context) throws Exception {
...
LauncherApplication.State = LauncherState.READY;
Platform.runLater(() -> {
try {
new LauncherApplication().start();
} catch (Exception ex) {
Logger.getLogger(LauncherActivator.class.getName()).log(Level.SEVERE, null, ex);
}
});
}
}
If there are updates detected I would just change the global state to LauncherAppliation.State = LauncherState.RESTART; if the application detects the change in State I will just simply call BUNDLE_MAP.get('application.activator').update();, BUNDLE_MAP is some kind of a HashMap that stores all running bundles on start.
I tested it and it worked, I can see in the logs that the activator bundle is being updated and the simple restart mechanism reruns the activator bundle but there are times that it doesn't rerun the activator bundle and the bundle state is already ACTIVE.
So what would be the right way to restart Apache Felix using the activator bundle or any bundle in general?
It is all in the documentation all this time!
Starting the Framework Instance
The start() method is used to start the framework instance. If the init() method was not invoked prior to calling start(), then it is invoked by start(). The two methods result in two different framework state transitions:
init() results in the framework instance in the Bundle.STARTING state.
start() results in the framework instance in the Bundle.ACTIVE state.
The init() method is necessary since the framework does not have a BundleContext when it is first created, so a transition to the Bundle.STARTING state is required to acquire its context (via Bundle.getBundleContext()) for performing various tasks, such as installing bundles. Note that the Felix framework also provides the felix.systembundle.activators property that serves a similar purpose, but is not standard. After the init() method completes, the follow actions have been performed:
Event handling is enabled.
The security manager is installed if it is enabled.
The framework is set to start level 0.
All bundles in the bundle caches are reified and their state is set to Bundle.INSTALLED.
The framework gets a valid BundleContext.
All framework-provided services are made available (e.g., PackageAdmin, StartLevel, etc.).
The framework enters the Bundle.STARTING state.
A call to start() is necessary to start the framework instance, if the init() method is invoked manually. Invoking init() or start() on an already started framework as no effect.
Stopping the Framework Instance
To stop the framework instance, invoke the stop() method, which will asynchronously stop the framework. To know when the framework has finished its shutdown sequence, use the waitForStop() method to wait until it is complete. A stopped framework will be in the Bundle.RESOLVED state. It is possible to restart the framework, using the normal combination of init()/start() methods as previously described.
Launching a Framework
Launching a framework is fairly simple and involves only four steps:
Define some configuration properties.
Obtain framework factory.
Use factory to create framework with the configuration properties.
Invoke the Framework.start() method.
In reality, the first step is optional, since all properties will have reasonable defaults, but if you are creating a launcher you will generally want to more than that, such as automatically installing and starting bundles when you start the framework instance. The default Felix launcher defines reusable functionality to automatically install and/or start bundles upon framework startup.

Deploying BEAN in OSGi plugin

I am currently deploying my custom controls as OSGi plugins and I wanted to do the same thing with my beans. I have tried putting them into the OSGi plugin and it works fine but the only problem I have is the faces-config.
It seems it has to be called faces-config in the OSGi plugin to work but that means i can't use beans in the NSF anymore because it seems to ignore the local faces-config.
Is there a way to change the name of the faces-config in the OSGi plugin?
Something like FEATURE-faces-config.xml?
In the class in your plugin that extends AbstractXspLibrary, you can override "getFacesConfigFiles", which should return an array of strings representing paths within the plugin to additional files of any name to load as faces-config additions. For example:
#Override
public String[] getFacesConfigFiles() {
return new String[] {
"com/example/config/beans.xml"
};
}
Then you can put the config file in that path within your Java source folder (or another folder that is included in build.properties) and it will be loaded in addition to your app's normal faces-config, beans and all.
The NSFs are running as separate, distinct Java applications. The OSGi plugin is running in the OSGi layer, above all those distinct Java applications, as a single code base. Consequently, the faces-config is only at that level.
It's possible to load them dynamically, by using an ImplicitObjectFactory, loaded from an XspContributor. That's what is done in OpenNTF Domino API for e.g. userScope (which is a bean stored in applicationScope of an NSF). See org.openntf.domino.xsp.helpers.OpenntfDominoImplicitObjectFactory, which is referenced in OpenntfDominoXspContributor, loaded via the extension point of type "com.ibm.xsp.library.Contributor".
A few caveats:
You have no control over what happens if you try to register your bean with a name the developer also uses for a different variable in that scope.
Unless you add code to check if the library is enabled, as we do, you'll be adding the bean to every database on the server.
You still need to add the library to the NSF. Unless you also provide a component that those databases will all use, there's no way you can programmatically add it, as far as I know.
It might be easier to skip the bean approach and just add an instance of the Java class in beforePageLoad, page controller class, or however you're managing the backing to the relevant XPage (if viewScope) or application (if sessionScope / applicationScope).

OSGI Bundle Lazy Activation

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.

OSGI: asking Framework to load class exported by a bundle without visiting each bundle's classloader?

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.

Is there a way for one bundle to get the Bundle instance for another bundle from a ServiceReference?

I'm trying to create a bundle that watches service registrations and, depending on certain metadata embedded in the API bundle for the service interface, performs some additional tasks. The metadata consists primarily of one or more properties files, so my thought was to use Bundle.findEntries() but since the metadata is embedded in the API bundle, I can't just do something like ServiceReference.getBundle().findEntries() as this would try to find the properties in the service implementation bundle, not in the API bundle.
I thought about getting the service API class name from the ServiceReference ObjectClass property and then using either the Package Admin service or FrameworkUtil.getBundle(), but both of these require a Class--but how do I get the Class of the service interface? The bundle that's doing this work probably hasn't imported the Class's package, so Class.forName() won't work.
My other option is to watch for both bundle and service events: the first creates a registry of bundles that contain the metadata, the second using the first when a service is registered. Before going down that path I'm looking to see if there's an easier way.
Disclaimer: I haven't tried this, but I'm reasonably sure it should do the job.
You can get the packagename from the ServiceReference's ObjectClass, so now we have that, we can find the package in the framework. Given a PackageAdmin packageAdmin, you can do something like
public Bundle getExporterOf(String package, ServiceReference ref) {
ExportedPackage[] packages = packageAdmin.getExportedPackages(packageName);
if (packages == null) {
return null;
}
for (ExportedPackage package : packages) {
Bundle[] importers = package.getImportingBundles()) {
if (importers == null) {
continue;
}
for (Bundle bundle : importers) {
if (bundle.getBundleId() == ref.getBundle().getBundleId()) {
return package.getExportingBundle
}
}
}
}
What we're doing here, is find all packages with the given package name (there might be multiple), find the one that the bundle that registered the service imports, and get the bundle that exported that package. You can probably make the method a little nicer.

Resources