Maven

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 EquinoxApache 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 the ServiceListener interface (a listener for service events). The framework synchronously delivers a ServiceEvent, when fired, to a ServiceListener.
  • The addServiceListener method of the BundleContext 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 a ServiceEvent raised on the ProviderService.
  • We are handling two types of service events in our callback method: the REGISTERED and the UNREGISTERING 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.

Download
You can download the full source code of this example here: Apache Maven Felix Plugin Tutorial

Anmol Deep

Anmol Deep is a senior engineer currently working with a leading identity security company as a Web Developer. He has 8 years of programming experience in Java and related technologies (including functional programming and lambdas) , Python, SpringBoot, Restful architectures, shell scripts, and databases relational(MySQL, H2) and nosql solutions (OrientDB and MongoDB). He is passionate about researching all aspects of software development including technology, design patterns, automation, best practices, methodologies and tools, and love traveling and photography when not coding.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button