Building applications with OSGI framework
OSGI is a framework used in building complex Java applications that contain multiple components. With OSGI, you can build a Java application that can load dynamically. When you are building a basic Java application, you might create a jar file for each module and load it with the classpath. But, with the OSGI framework, you can load OSGI jar files dynamically whenever you needed. Let's discuss some of the terminologies and concepts that you should know about OSGI framework:
Bundle
Bundle is the OSGI module that contains all your logic. The bundle is a jar file that contains your business logic that provides some service. Other than a regular jar file, it also contains information about how it is supposed to work in OSGI runtime in MANIFEST.MF file. This information includes a bundle name, version, exported and imported libraries. Following is a sample definition of OSGI bundle definition:
Bundle-Name: Hello World
Bundle-SymbolicName: org.dhanushka.helloworld
Bundle-Description: A Hello World bundle
Bundle-ManifestVersion: 2
Bundle-Version: 1.0.0
Bundle-Activator: org.dhanushka.Activator
Export-Package: org.dhanushka.helloworld;version="1.0.0"
Import-Package: org.osgi.framework;version="1.3.0"
- Bundle-name is used to identify the bundle. This is just an optional string value.
- Bundle-SymbolicName is a unique identifier for the bundle. This follows the common java package naming standard.
- Bundle-Description is a description of what this bundle does.
- Bundle-ManifestVersion is versioning used by OSGI framework to read bundle manifest.
- Bundle-Version is the version number you allocated for the OSGI bundle.
- Bundle-Activator is the starting point of your module. A bundle can have multiple class implementations. OSGI uses this property to mark what is the class that used as the starting point of the bundle. We will discuss the bundle activator more later.
- Export-Package is a set of packages that you need to expose to other OSGI bundles. If you didn't specify export packages, then other OSGI bundles are unable to access classes in your OSGI bundle.
- Import-Package is a set of packages required for your OSGI bundle. You should import packages here if you need to refer to any other OSGI bundles.
The bundle is simply a module that contains all of your programming logic. You can import another bundle functionality into it and expose your own functionality to other bundles.
Services
Services are the way how each of the bundles are connected together. As I have already explained, multiple bundles can be connected together to provide some service. Check the following sample bundle implementation that has activation method on it:
public class Activator implements BundleActivator {
private BundleContext context;
@Override
public void start(BundleContext context) throws Exception {
System.out.println("Starting");
this.context = context;
}
@Override
public void stop(BundleContext context) throws Exception {
System.out.println("Stopping");
this.context = null;
}
}
In this source code, the bundle activator is the Activator
class. When the OSGI starting up it starts executing the start
method. This is much similar to the main
method. When the OSGI framework stopping, it triggers the stop
method. Whenever you add this bundle into the OSGI runtime, it will start execution by calling the start
method and end its execution by calling the stop
method.
As you can see, this bundle does not have any dependency on other OSGI bundles. It can start its execution independently. In some scenarios, you might need to start this bundle when some conditions are met. Until the condition satisfies, OSGI runtime does not call the start
method.
This can be achieved with references. References let you call start
method once given conditions are met. Check the following modification done to the Activator
class:
public class Activator implements BundleActivator {
private BundleContext context;
@Override
public void start(BundleContext context) throws Exception {
System.out.println("Starting");
this.context = context;
}
@Reference(
name = "activator.service",
service = ActivatorService.class,
cardinality = ReferenceCardinality.MANDATORY,
policy = ReferencePolicy.DYNAMIC,
unbind = "unsetActivatorService")
protected void setActivatorService(ActivatorService activatorService) {
// Set values
}
protected void unsetActivatorService(ActivatorService activatorService) {
// Unset values
}
@Override
public void stop(BundleContext context) throws Exception {
System.out.println("Stopping");
this.context = null;
}
}
In this code, other than the previous start
and stop
methods, there are another two methods. @Reference
annotation is used here to mark these dependencies. Here, we have given a service name and service class. Under the reference annotation, we defined the cardinality
as mandatory. Since the cardinality is marked as mandatory, to trigger start
function, setActivatorService
should be satisfied. If the cardinality is optional, then it is not mandatory to call setActivatorService
to start this bundle. This method can be triggered from another bundle. Following is a sample code, how to trigger setActivatorService
method.
context.getBundleContext().registerService(ActivatorService.class.getName(), activatorService, null);
Here, the activatorService
is the object that contains details that need to send to the setActivatorService
. Once this method gets called, OSGI bundle will start the ActivatorService
start
method.
You can build more advanced applications by using the OSGI framework. You build dynamically pluggable components with dependency injection design patterns.