1. Introduction
Several Java mission-critical and middleware applications have some hard technological requirements.
Some have to support hot deploy, so as not to disrupt the running services – and others have to be able to work with different versions of the same package for the sake of supporting external legacy systems.
The OSGi platforms represent a viable solution to support this kind of requirements.
The Open Service Gateway Initiative is a specification defining a Java-based component system. It’s currently managed by the OSGi Alliance, and its first version dates back to 1999.
Since then, it has proved to be a great standard for component systems, and it’s widely used nowadays. The Eclipse IDE, for instance, is an OSGi-based application.
In this article, we’ll explore some basic features of OSGi leveraging the implementation provided by Apache.
2. OSGi Basics
In OSGi, a single component is called a bundle.
Logically, a bundle is a piece of functionality that has an independent lifecycle – which means it can be started, stopped and removed independently.
Technically, a bundle is just a jar file with a MANIFEST.MF file containing some OSGi-specific headers.
The OSGi platform provides a way to receive notifications about bundles becoming available or when they’re removed from the platform. This will allow a properly designed client to keep working, maybe with degraded functionality, even when a service it depends on, is momentarily unavailable.
Because of that, a bundle has to explicitly declare what packages it needs to have access to and the OSGi platform will start it only if the dependencies are available in the bundle itself or in other bundles already installed in the platform.
3. Getting the Tools
We’ll start our journey in OSGi by downloading the latest version of Apache Karaf from this link. Apache Karaf is a platform that runs OSGi-based applications; it’s based on the Apache‘s implementation of OSGi specification called Apache Felix.
Karaf offers some handy features on top of Felix that will help us in getting acquainted with OSGi, for example, a command line interface that will allow us to interact with the platform.
To install Karaf, you can follow the installation instruction from the official documentation.
4. Bundle Entry Point
To execute an application in an OSGi environment, we have to pack it as an OSGi bundle and define the application entry point, and that’s not the usual public static void main(String[] args) method.
So, let’s start by building an OSGi- based “Hello World” application.
We start setting up a simple dependency on the core OSGi API:
<dependency> <groupId>org.osgi</groupId> <artifactId>org.osgi.core</artifactId> <version>6.0.0</version> <scope>provided</scope> </dependency>
The dependency is declared as provided because it will be available in the OSGi runtime, and the bundle doesn’t need to embed it.
Let’s now write the simple HelloWorld class:
public class HelloWorld implements BundleActivator { public void start(BundleContext ctx) { System.out.println("Hello world."); } public void stop(BundleContext bundleContext) { System.out.println("Goodbye world."); } }
BundleActivator is an interface provided by OSGi that has to be implemented by classes that are entry points for a bundle.
The start() method is invoked by the OSGi platform when the bundle containing this class is started. In the other hand stop() is invoked before just before the bundle is stopped.
Let’s keep in mind that each bundle can contain at most one BundleActivator. The BundleContext object provided to both methods allows interacting with the OSGi runtime. We’ll get back to it soon.
5. Building a Bundle
Let’s modify the pom.xml and make it an actual OSGi bundle.
First of all, we have to explicitly state that we’re going to build a bundle, not a jar:
<packaging>bundle</packaging>
Then we leverage the maven-bundle-plugin, courtesy of the Apache Felix community, to package the HelloWorld class as an OSGi bundle:
<plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <version>3.3.0</version> <extensions>true</extensions> <configuration> <instructions> <Bundle-SymbolicName> ${pom.groupId}.${pom.artifactId} </Bundle-SymbolicName> <Bundle-Name>${pom.name}</Bundle-Name> <Bundle-Version>${pom.version}</Bundle-Version> <Bundle-Activator> com.baeldung.osgi.sample.activator.HelloWorld </Bundle-Activator> <Private-Package> com.baeldung.osgi.sample.activator </Private-Package> </instructions> </configuration> </plugin>
In the instructions section, we specify the values of the OSGi headers we want to include in the bundle’s MANIFEST file.
Bundle-Activator is the fully qualified name of the BundleActivator implementation that will be used to start and stop the bundle, and it refers to the class we’ve just written.
Private-Package is not an OSGi header, but it’s used to tell the plugin to include the package in the bundle but not make it available to other ones. We can now build the bundle with the usual command mvn clean install.
6. Installing and Running the Bundle
Let’s start Karaf by executing the command:
<KARAF_HOME>/bin/karaf start
where <KARAF_HOME> is the folder where Karaf is installed. When the prompt of the Karaf console appears we can execute the following command to install the bundle:
> bundle:install mvn:com.baeldung/osgi-intro-sample-activator/1.0-SNAPSHOT Bundle ID: 63
This instructs Karaf to load the bundle from the local Maven repository.
In return Karaf prints out the numeric ID assigned to the bundle that depends on the number of bundles already installed and may vary. The bundle is now just installed, we can now start it with the following command:
> bundle:start 63 Hello World
“Hello World” immediately appears as soon the bundle is started. We can now stop and uninstall the bundle with:
> bundle:stop 63 > bundle:uninstall 63
“Goodbye World” appears on the console, accordingly to the code in the stop() method.
7. An OSGi Service
Let’s go on writing a simple OSGi service, an interface that exposes a method for greeting people:
package com.baeldung.osgi.sample.service.definition; public interface Greeter { public String sayHiTo(String name); }
Let’s write an implementation of it that is a BundleActivator too, so we’ll be able to instantiate the service and register it on the platform when the bundle is started:
package com.baeldung.osgi.sample.service.implementation; public class GreeterImpl implements Greeter, BundleActivator { private ServiceReference<Greeter> reference; private ServiceRegistration<Greeter> registration; @Override public String sayHiTo(String name) { return "Hello " + name; } @Override public void start(BundleContext context) throws Exception { System.out.println("Registering service."); registration = context.registerService( Greeter.class, new GreeterImpl(), new Hashtable<String, String>()); reference = registration .getReference(); } @Override public void stop(BundleContext context) throws Exception { System.out.println("Unregistering service."); registration.unregister(); } }
We use the BundleContext as a mean of requesting the OSGi platform to register a new instance of the service.
We should also provide the type of the service and a map of the possible configuration parameters, which aren’t needed in our simple scenario. Let’s now proceed with the configuration of the maven-bundle-plugin:
<plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <extensions>true</extensions> <configuration> <instructions> <Bundle-SymbolicName> ${project.groupId}.${project.artifactId} </Bundle-SymbolicName> <Bundle-Name> ${project.artifactId} </Bundle-Name> <Bundle-Version> ${project.version} </Bundle-Version> <Bundle-Activator> com.baeldung.osgi.sample.service.implementation.GreeterImpl </Bundle-Activator> <Private-Package> com.baeldung.osgi.sample.service.implementation </Private-Package> <Export-Package> com.baeldung.osgi.sample.service.definition </Export-Package> </instructions> </configuration> </plugin>
It’s worth noting that only the com.baeldung.osgi.sample.service.definition package has been exported this time, through the Export-Package header.
Thanks to this, OSGi will allow other bundles to invoke only the methods specified in the service interface. Package com.baeldung.osgi.sample.service.implementation is marked as private, so no other bundle will be able to access the members of the implementation directly.
8. An OSGi Client
Let’s now write the client. It simply looks up the service at startup and invokes it:
public class Client implements BundleActivator, ServiceListener { }
Let’s implement the BundleActivator start() method:
private BundleContext ctx; private ServiceReference serviceReference; public void start(BundleContext ctx) { this.ctx = ctx; try { ctx.addServiceListener( this, "(objectclass=" + Greeter.class.getName() + ")"); } catch (InvalidSyntaxException ise) { ise.printStackTrace(); } }
The addServiceListener() method allows the client to ask the platform to send notifications about the service that complies with the provided expression.
The expression uses a syntax similar to the LDAP’s one, and in our case, we’re requesting notifications about a Greeter service.
Let’s go on to the callback method:
public void serviceChanged(ServiceEvent serviceEvent) { int type = serviceEvent.getType(); switch (type){ case(ServiceEvent.REGISTERED): System.out.println("Notification of service registered."); serviceReference = serviceEvent .getServiceReference(); Greeter service = (Greeter)(ctx.getService(serviceReference)); System.out.println( service.sayHiTo("John") ); break; case(ServiceEvent.UNREGISTERING): System.out.println("Notification of service unregistered."); ctx.ungetService(serviceEvent.getServiceReference()); break; default: break; } }
When some modification involving the Greeter service happens, the method is notified.
When the service is registered to the platform, we get a reference to it, we store it locally, and we then use it to acquire the service object and invoke it.
When the server is later unregistered, we use the previously stored reference to unget it, meaning that we tell the platform that we are not going to use it anymore.
We now just need to write the stop() method:
public void stop(BundleContext bundleContext) { if(serviceReference != null) { ctx.ungetService(serviceReference); } }
Here again, we unget the service to cover the case in which the client is stopped before the service is being stopped. Let’s give a final look at the dependencies in the pom.xml:
<dependency> <groupId>com.baeldung</groupId> <artifactId>osgi-intro-sample-service</artifactId> <version>1.0-SNAPSHOT</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.osgi</groupId> <artifactId>org.osgi.core</artifactId> <version>6.0.0</version> </dependency>
9. Client and Service
Let’s now install the client and service bundles in Kafka by doing:
> install mvn:com.baeldung/osgi-intro-sample-service/1.0-SNAPSHOT Bundle ID: 64 > install mvn:com.baeldung/osgi-intro-sample-client/1.0-SNAPSHOT Bundle ID: 65
Always keep in mind that the identifier numbers assigned to each bundle may vary.
Let’s now start the client bundle:
> start 65
Therefore, nothing happens because the client is active and it’s waiting for the service, that we can start with:
> start 64 Registering service. Service registered. Hello John
What happens is that as soon as the service’s BundleActivator starts, the service is registered to the platform. That, in turn, notifies the client that the service it was waiting for is available.
The client then gets a reference to the service and uses it to invoke the implementation delivered through the service bundle.
10. Conclusion
In this article, we explored the essential features of OSGi with a straightforward example that it’s enough to understand the potential of OSGi.
In conclusion, whenever we have to guarantee that a single application has to be updated without any disservice, OSGi can be a viable solution.
The code for this post can be found over on GitHub.