Apache Maven Felix Plugin Tutorial
In this tutorial, we will explore the Apache Maven Felix plugin and see how it can be used to build OSGi bundles.
We will talk about OSGi fundamentals followed by developing an example built using the Maven Felix Plugin. Lastly, we will install and run our bundles on the appropriate platform.
1. Introduction
Writing the MANIFEST.MF
file manually is quite a challenging and tedious task. In most cases getting this file correct is not easy. Therefore, we need a tool.
The Apache Maven Felix plugin is the one that comes to our rescue. This plugin is based on the BND tool from Peter Kriens. We can specify the instructions in the configuration section of the plugin in the pom.xml and the rest will be taken care of by the plugin.
2. Fundamentals of OSGi
Before we start digging deep into the plugin, let’s brush up our concepts of OSGi specification and bundles.
The OSGi Alliance, formerly known as the OSGi (Open Service Gateway Initiative) defines a specification that describes a dynamic module system for Java-based applications. The organization was founded in March 1999 and continues to maintain the OSGi standard.
The most common implementations of this specification are Equinox, Apache Felix, Apache Karaf, and Knoplerfish.
2.1. Why OSGi ?
OSGi provides capabilities to split a Java application into multiple components. In OSGi terminology, such components are knowns as bundles.
A bundle is an independent piece of software that has its own lifecycle. In other words, it can be installed, started, stopped, updated or uninstalled independently without impacting the other bundles or the platform on which the bundles are deployed.
For more information on OSGi bundles, check this article here.
3. The Provider – Consumer Application
Let’s design and develop a simple Provider – Consumer application comprising of two OSGi components.
We will use the Maven Felix Plugin to build these components into OSGi bundles. Finally, we’ll install and run these bundles on an OSGi runtime environment.
3.1. The ProviderService Bundle
Let’s define a simple Provider
interface containing a method named provide
. This method requires a String input and returns back a String.
ProviderService.java
public interface ProviderService { public String provide(String type); }
Next, let’s write an implementation class for our Provider
interface.
ProviderServiceImpl.java
public class ProviderServiceImpl implements ProviderService, BundleActivator { private ServiceReference serviceReference; private ServiceRegistration registration; @Override public String provide(String type) { return this.getClass().getName() + " - Providing - Type " + type; } @Override public void start(BundleContext context) throws Exception { System.out.println("Start method called on the Provider Bundle"); registration = context.registerService( ProviderService.class, new ProviderServiceImpl(), new Hashtable()); serviceReference = registration.getReference(); System.out.println("Started Provider Bundle Successfully with id " + serviceReference.getBundle().getBundleId()); } @Override public void stop(BundleContext context) throws Exception { System.out.println("Stop method called on the Provider Bundle"); registration.unregister(); } }
We can observe that the above service implements the interface BundleActivator
. By doing so it becomes lifecycle-aware and its lifecycle is now managed by the OSGi framework.
The start()
method is invoked by the OSGi framework when the ProviderServiceImpl
bundle is started. Similarly, the stop()
method is invoked before just before the bundle is stopped.
As seen in the above code snippet, the BundleContext
instance injected into the start
method is used to register an object of type ProviderServiceImpl
with the OSGi Framework.
Additionally, the registerService
method of the BundleContext
takes an additional argument for the properties of this service. In this example, we just send an empty Hashtable
.
3.2. Maven Dependencies
The following dependency is required in the pom.xml for the bundle class defined in the previous section.
Dependencies
<dependencies> <dependency> <groupId>org.osgi</groupId> <artifactId>org.osgi.core</artifactId> </dependency> </dependencies>
3.3. The Maven Felix Plugin
Now let’s add some code to the pom.xml to make the ProviderService
an OSGi bundle.
<packaging>bundle</packaging>
The above code explicitly states that the packaging for this project is of type “bundle” and the not of the usual type “jar”. This configures Maven to build an OSGi bundle.
Maven Felix Plugin
<build> <plugins> <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.jcg.felix.sample.service.bundle.impl.ProviderServiceImpl </Bundle-Activator> <Private-Package> com.jcg.felix.sample.service.bundle.impl </Private-Package> <Export-Package> com.jcg.felix.sample.service.bundle </Export-Package> </instructions> </configuration> </plugin> </plugins> </build>
3.3.1. Plugin Configuration Instructions
Let’s discuss the instructions given to the Felix Plugin to build the bundle.
Export-Package
: This instruction exports a list of packages in the bundle. Such packages are copied into the resulting bundle JAR file from the available classes (i.e., project classes, dependencies, and classpath). Therefore, you can include classes into your bundle that are not associated with source files in your project.Private Package
: The packages given to this instruction will not be exported by the bundle.Import-Package
: This instruction lists the packages that are required by the current bundle’s contained packages. The default for this instruction is “*” that imports all referred packages.
Depending on the instructions given to the plugin, it will generate the manifest headers and their values. Below is the generated MANIFEST.MF
file with headers corresponding to the above plugin configuration.
MANIFEST.MF
Manifest-Version: 1.0 Bnd-LastModified: 1594701384296 Build-Jdk: 11.0.7 Bundle-Activator: com.jcg.felix.sample.service.bundle.impl.ProviderServi ceImpl Bundle-ManifestVersion: 2 Bundle-Name: osgi-provider-service Bundle-SymbolicName: com.jcg.osgi-provider-service Bundle-Version: 1.0.0.SNAPSHOT Created-By: Apache Maven Bundle Plugin Export-Package: com.jcg.felix.sample.service.bundle;version="1.0.0.SNAPS HOT" Import-Package: com.jcg.felix.sample.service.bundle,org.osgi.framework;v ersion="[1.8,2)" Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))" Tool: Bnd-3.3.0.201609221906
The MANIFEST.MF
file of the bundle includes the following headers:
- Bundle-SymbolicName: This name uniquely identifies a bundle.
- Bundle-Name: This is a human-readable bundle name.
- Bundle-Version: This header provides information about the version of the bundle. It also enables multiple versions of a bundle to be running concurrently on the OSGi platform.
- Bundle-Activator: This header provides information about the fully qualified name of the
BundleActivator
implementation class. - Import-Package: This header declares the external dependencies to be imported by the current bundle. Specific versions of the package can also be declared.
- Export-Package: This header declares the packages that are visible outside the current bundle. A package not declared here is visible only within the bundle.
3.4. The Consumer Bundle
In this section, let’s define and build the Consumer bundle and understand its contained packages, classes, and the pom.xml.
Client.java
public class Client implements BundleActivator, ServiceListener { private BundleContext ctx; private ServiceReference serviceReference; public void start(BundleContext ctx) { System.out.println("Start method called on the Consumer Bundle"); this.ctx = ctx; try { ctx.addServiceListener(this, "(objectclass=" + ProviderService.class.getName() + ")"); } catch (InvalidSyntaxException ise) { ise.printStackTrace(); } System.out.println("Started Consumer Bundle Successfully with id " + ctx.getBundle().getBundleId()); } public void stop(BundleContext bundleContext) { System.out.println("Stop method called on the Consumer Bundle"); if (serviceReference != null) { ctx.ungetService(serviceReference); } this.ctx = null; } public void serviceChanged(ServiceEvent serviceEvent) { int type = serviceEvent.getType(); switch (type) { case (ServiceEvent.REGISTERED): System.out.println("Consumer Bundle : ServiceEvent.REGISTERED"); serviceReference = serviceEvent.getServiceReference(); ProviderService service = (ProviderService) (ctx.getService(serviceReference)); System.out.println(service.provide("Car")); break; case (ServiceEvent.UNREGISTERING): System.out.println("Consumer Bundle : ServiceEvent.UNREGISTERING"); ctx.ungetService(serviceEvent.getServiceReference()); break; default: break; } } }
Some observations from the above code snippet are:
- The
Client
class implements theServiceListener
interface (a listener for service events). The framework synchronously delivers aServiceEvent
, when fired, to aServiceListener
. - The
addServiceListener
method of theBundleContext
interface allows to register the current bundle as a listener to receive service events about the service that match with the provided filter. - The callback method
serviceChanged
is invoked whenever there is aServiceEvent
raised on theProviderService
. - We are handling two types of service events in our callback method: the
REGISTERED
and theUNREGISTERING
service events.
3.4.1. Plugin Configuration Instructions (Consumer)
Next, let’s discuss the instructions given to the Felix Plugin to build the consumer bundle.
Felix Plugin Instructions
<build> <plugins> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <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.jcg.felix.sample.client.bundle.Client </Bundle-Activator> <Private-Package> com.jcg.felix.sample.client.bundle </Private-Package> </instructions> </configuration> </plugin> </plugins> </build>
3.5. Installing and Running the Bundles
Let’s begin by downloading the software we need to install and run our bundles. You can download the latest version of Apache Karaf from this link.
Apache Karaf is a modern and polymorphic application runtime that can host any kind of applications: WAR, Spring, OSGi, and much more.
It also provides a complete Unix like shell console that allows to manage our container and applications and to interact with the platform.
To install Karaf, you can follow the installation instructions from the official documentation.
3.5.1. Starting the Karaf Runtime
In the bin directory of the KARAF_HOME directory, run the start script to get the shell console as shown below:
adeep-r:bin adeep$ ./karaf ..... Apache Karaf (4.2.9) Hit '<tab>' for a list of available commands and '[cmd] --help' for help on a specific command. Hit '<ctrl-d>' or type 'system:shutdown' or 'logout' to shutdown Karaf. ..... karaf@root()>
3.5.2. Installing the Bundles
Run the following commands to install the provider and the consumer bundles in the Karaf runtime.
karaf@root()> install mvn:com.jcg/osgi-provider-service/1.0-SNAPSHOT Bundle ID: 60 karaf@root()> install mvn:com.jcg/osgi-consumer/1.0-SNAPSHOT Bundle ID: 61
3.5.3. Start and Stop the Bundles
Let’s now start the client and the provider bundle by executing the start
command along with the bundle ID. Lastly, we will stop the bundles by executing the stop
command along with the bundle ID.
The below code snippet shows this:
karaf@root()> start 61 Start method called on the Consumer Bundle Started Consumer Bundle Successfully with id 61 ....................... karaf@root()> start 60 Start method called on the Provider Bundle Consumer Bundle : ServiceEvent.REGISTERED com.jcg.felix.sample.service.bundle.impl.ProviderServiceImpl Providing - Type Car Started Provider Bundle Successfully with id 60 ....................... karaf@root()> stop 60 Stop method called on the Provider Bundle Consumer Bundle : ServiceEvent.UNREGISTERING ...................... karaf@root()> stop 61 Stop method called on the Consumer Bundle
4. Summary
In this tutorial, we demonstrated how to build an OSGi bundle using the Apache Maven Felix Plugin.
Firstly, we discussed the fundamental concepts of OSGi. After that, we designed a Provider-Consumer application and built it using the plugin. Additionally, we explored the various instructions passed to the Felix plugin to build the bundle.
Lastly, we downloaded an OSGi runtime called Apache Karaf and installed and ran our bundles on that platform.
5. Download the source code
All the code examples provided in this tutorial are available in a Maven project and should be easy to import and run.
You can download the full source code of this example here: Apache Maven Felix Plugin Tutorial