Beginning Karaf

Few weeks ago I started playing with the Apache Karaf application server. I already had some experience with OSGi containers, namely Eclipse Virgo as part of my regular job and I wanted to see what else is out there. To quickly outline the major differences between the servers, Virgo is using Equinox OSGi implementation provided by Eclipse (and powering the IDE of the same name) while Karaf is a subproject of Apache Felix, another major OSGi implementation. Karaf can use both Felix and Equinox but by default uses the former. Both application servers support web application bundles (the OSGi equivalent of WARs) and other enterprise features.

Getting acquainted with Karaf, I came to appreciate its tooling the most – there is a Maven plugin that makes the creation and maintenance of OSGi bundles a walk in the park. Still the tooling documentation is a bit lacking and having lost around three weeks with the project, cross-checking tutorials, official documentation, mailing threads and Karaf’s codebase I decided that it would be beneficial to have my sample project as a blueprint (pun intended) for other people interested in developing Karaf-based applications.

The sample project demonstrates the following OSGi / Karaf features:

  • Maven bundle packaging.
  • Maven kar packaging.
  • Maven karaf-assembly packaging.
  • Vanilla OSGi bundles.
  • Blueprint-wired bundles.
  • Web bundles.
  • Preparing a Karaf archive a.k.a. a KAR.
  • Building a customized Karaf distribution.

Project Overview

The project has been uploaded under https://github.com/tonyganchev/blog/tree/master/karaf. The project consists of two vanilla OSGi bundles – karaf-bundle-a and karaf-bundle-b to demonstrate how to consume the service exposed from one bundle in another bundle. Then there is a blueprint-based bundle – karaf-bundle-c – that consumes the same service as bundle-b but does so using blueprint declarations instead of hard-coding the service resolution within the bundle activator. karaf-wab demonstrates how to write a web application bundle that has some static content and a single servlet.

Apart from the functional modules, there are two modules that deal with how the example is delivered. First of all is karaf-kar providing a complete archive with all aforementioned bundles consolidated into a Karaf feature and wrapped in a single archive and ready to be deployed in any Karaf distributions that meets the feature’s prerequisites. This is made even easier with karaf-assembly which packages a full Karaf distribution that runs the project’s feature on startup.

All projects except karaf-wab were created using the Karaf Maven archetypes as described in Karaf’s developers guide. Note that the guide does not explain the org.apache.karaf.archetypes:karaf-assembly-archetype but you can see it is available by running:

<br />
mvn archetype:generate -Dfilter=karaf<br />

Vanilla OSGi Bundles

Let’s look at the POM for such a bundle – for example karaf-bundle-a/pom.xml:

</p>
<p>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;<br />
&lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; ...&gt;<br />
...<br />
	&lt;artifactId&gt;karaf-bundle-a&lt;/artifactId&gt;<br />
	&lt;packaging&gt;bundle&lt;/packaging&gt;<br />
	&lt;dependencies&gt;<br />
		&lt;dependency&gt;<br />
			&lt;groupId&gt;org.osgi&lt;/groupId&gt;<br />
			&lt;artifactId&gt;org.osgi.core&lt;/artifactId&gt;<br />
			&lt;scope&gt;provided&lt;/scope&gt;<br />
		&lt;/dependency&gt;<br />
	&lt;/dependencies&gt;<br />
	&lt;build&gt;<br />
		&lt;plugins&gt;<br />
			&lt;plugin&gt;<br />
				&lt;groupId&gt;org.apache.felix&lt;/groupId&gt;<br />
				&lt;artifactId&gt;maven-bundle-plugin&lt;/artifactId&gt;<br />
				&lt;configuration&gt;<br />
					&lt;instructions&gt;<br />
						&lt;Bundle-Activator&gt;com.tonyganchev.blog.karaf.a.Activator&lt;/Bundle-Activator&gt;<br />
						&lt;Export-Package&gt;com.tonyganchev.blog.karaf.a*;version=${project.version}&lt;/Export-Package&gt;<br />
						&lt;Import-Package&gt;*&lt;/Import-Package&gt;<br />
					&lt;/instructions&gt;<br />
				&lt;/configuration&gt;<br />
			&lt;/plugin&gt;<br />
		&lt;/plugins&gt;<br />
	&lt;/build&gt;<br />
&lt;/project&gt;</p>
<p>

Few important things to note. First, there is the packaging (line 4) – bundle. It is provided by the karaf-maven-plugin defined in the root POM:

<br />
	&lt;build&gt;<br />
	...<br />
		&lt;pluginManagement&gt;<br />
		...<br />
			&lt;plugins&gt;<br />
				...<br />
				&lt;plugin&gt;<br />
					&lt;groupId&gt;org.apache.karaf.tooling&lt;/groupId&gt;<br />
					&lt;artifactId&gt;karaf-maven-plugin&lt;/artifactId&gt;<br />
					&lt;version&gt;${karaf.version}&lt;/version&gt;<br />
					&lt;extensions&gt;true&lt;/extensions&gt;<br />
				&lt;/plugin&gt;<br />
				...<br />

The other interesting piece of configuration is the invocation of the Felix maven bundle plugin which is a maven interface to the bnd tool. It is in charge of generating a proper OSGi META/MANIFEST.MF for the bundle. The instructions shown tell bnd what is the bundle activator (i.e. the class that will act on events such as bundle start/stop), What packages to allow for export (namely everything under com.tonyganchev.blog.karaf.a using the project’s version as the package version) and what to import (everything from the classpath).

The resulting META/MANIFEST.MF file looks like this:

<br />
Manifest-Version: 1.0<br />
Bnd-LastModified: 1452520995636<br />
Build-Jdk: 1.8.0_25<br />
Built-By: tony<br />
Bundle-Activator: com.tonyganchev.blog.karaf.a.Activator<br />
Bundle-DocURL: http://www.tonyganchev.com/<br />
Bundle-ManifestVersion: 2<br />
Bundle-Name: karaf-bundle-a<br />
Bundle-SymbolicName: karaf-bundle-a<br />
Bundle-Vendor: Tony Ganchev<br />
Bundle-Version: 1.0.0.SNAPSHOT<br />
Created-By: Apache Maven Bundle Plugin<br />
Export-Package: com.tonyganchev.blog.karaf.a;version=&quot;1.0.0.SNAPSHOT&quot;;uses:=&quot;org.osgi.framework&quot;,<br />
 com.tonyganchev.blog.karaf.a.impl;version=&quot;1.0.0.SNAPSHOT&quot;;uses:=&quot;com.tonyganchev.blog.karaf.a&quot;<br />
Import-Package: com.tonyganchev.blog.karaf.a;version=&quot;[1.0,2)&quot;,<br />
 com.tonyganchev.blog.karaf.a.impl;version=&quot;[1.0,2)&quot;,<br />
 org.osgi.framework;version=&quot;[1.8,2)&quot;<br />
Private-Package: com.tonyganchev.blog.karaf.a,<br />
 com.tonyganchev.blog.karaf.a.impl<br />
Require-Capability: osgi.ee;filter:=&quot;(&amp;(osgi.ee=JavaSE)(version=1.7))&quot;<br />
Tool: Bnd-3.0.0.201509101326<br />

The highlighted lines are the result from the instructions discussed above. All other headers get generated based on instructions specified for the bundle plugin in the plugin management section of the root POM.

karaf-bundle-a exposes a single service for consumption by other bundles through the GreetingService interface. Service registration is done in the bundle activator:

<br />
package com.tonyganchev.blog.karaf.a;</p>
<p>import java.util.Hashtable;</p>
<p>import org.osgi.framework.BundleActivator;<br />
import org.osgi.framework.BundleContext;<br />
import org.osgi.framework.ServiceRegistration;</p>
<p>import com.tonyganchev.blog.karaf.a.impl.GreetingServiceImpl;</p>
<p>public class Activator implements BundleActivator {<br />
	@Override<br />
	public void start(final BundleContext context) {<br />
		System.out.println(&quot;A: Starting the bundle&quot;);<br />
		serviceRegistration = context<br />
				.registerService(GreetingService.class, new GreetingServiceImpl(), new Hashtable&lt;String, String&gt;());<br />
	}</p>
<p>	@Override<br />
	public void stop(final BundleContext context) {<br />
		System.out.println(&quot;A: Stopping the bundle&quot;);<br />
		serviceRegistration.unregister();<br />
	}</p>
<p>	private ServiceRegistration&lt;GreetingService&gt; serviceRegistration;</p>
<p>}<br />

Now let’s take a look at the OSGi bundles consuming this service. Let’s start with karaf-bundle-b. It’s pom.xml looks like this:

<br />
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;<br />
&lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; ...&gt;<br />
...<br />
	&lt;artifactId&gt;karaf-bundle-b&lt;/artifactId&gt;<br />
	&lt;packaging&gt;bundle&lt;/packaging&gt;<br />
	&lt;dependencies&gt;<br />
		&lt;dependency&gt;<br />
			&lt;groupId&gt;org.osgi&lt;/groupId&gt;<br />
			&lt;artifactId&gt;org.osgi.core&lt;/artifactId&gt;<br />
			&lt;scope&gt;provided&lt;/scope&gt;<br />
		&lt;/dependency&gt;<br />
		&lt;dependency&gt;<br />
			&lt;groupId&gt;com.tonyganchev.blog&lt;/groupId&gt;<br />
			&lt;artifactId&gt;karaf-bundle-a&lt;/artifactId&gt;<br />
			&lt;version&gt;${project.version}&lt;/version&gt;<br />
		&lt;/dependency&gt;<br />
	&lt;/dependencies&gt;<br />
	&lt;build&gt;<br />
		&lt;plugins&gt;<br />
			&lt;plugin&gt;<br />
				&lt;groupId&gt;org.apache.felix&lt;/groupId&gt;<br />
				&lt;artifactId&gt;maven-bundle-plugin&lt;/artifactId&gt;<br />
				&lt;configuration&gt;<br />
					&lt;instructions&gt;<br />
						&lt;Bundle-Activator&gt;com.tonyganchev.blog.karaf.b.Activator&lt;/Bundle-Activator&gt;<br />
						&lt;Export-Package&gt;com.tonyganchev.blog.karaf.b*;version=${project.version}&lt;/Export-Package&gt;<br />
						&lt;Import-Package&gt;*&lt;/Import-Package&gt;<br />
					&lt;/instructions&gt;<br />
				&lt;/configuration&gt;<br />
			&lt;/plugin&gt;<br />
		&lt;/plugins&gt;<br />
	&lt;/build&gt;<br />
&lt;/project&gt;<br />

It is essentially the same as bundle-a’s but it also adds a compile-time dependency to bundle-a. On starting bundle-b it’s activator seeks a reference to an implementation of GreetingService and calls its greet() method:

<br />
package com.tonyganchev.blog.karaf.b;</p>
<p>import org.osgi.framework.BundleActivator;<br />
import org.osgi.framework.BundleContext;<br />
import org.osgi.framework.ServiceReference;</p>
<p>import com.tonyganchev.blog.karaf.a.GreetingService;</p>
<p>public class Activator implements BundleActivator {<br />
	@Override<br />
	public void start(final BundleContext context) {<br />
		System.out.println(&quot;B: Starting the bundle&quot;);<br />
		greetingServiceRef = context.getServiceReference(GreetingService.class);<br />
		context.getService(greetingServiceRef).greet(&quot;Bundle B started!&quot;);<br />
	}</p>
<p>	@Override<br />
	public void stop(final BundleContext context) {<br />
		System.out.println(&quot;B: Stopping the bundle&quot;);<br />
		context.ungetService(greetingServiceRef);<br />
	}</p>
<p>	private ServiceReference&lt;GreetingService&gt; greetingServiceRef;<br />
}<br />

Blueprint OSGi Bundles

Now let’s see how the same service can be consumed by a blueprint bundle. This is what karaf-bundle-c is for. The pom.xml looks like this:

<br />
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;<br />
&lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;<br />
	xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt;<br />
...<br />
	&lt;artifactId&gt;karaf-bundle-c&lt;/artifactId&gt;<br />
	&lt;packaging&gt;bundle&lt;/packaging&gt;<br />
	&lt;dependencies&gt;<br />
		&lt;dependency&gt;<br />
			&lt;groupId&gt;com.tonyganchev.blog&lt;/groupId&gt;<br />
			&lt;artifactId&gt;karaf-bundle-a&lt;/artifactId&gt;<br />
			&lt;version&gt;${project.version}&lt;/version&gt;<br />
		&lt;/dependency&gt;<br />
		&lt;dependency&gt;<br />
			&lt;groupId&gt;org.osgi&lt;/groupId&gt;<br />
			&lt;artifactId&gt;org.osgi.core&lt;/artifactId&gt;<br />
			&lt;scope&gt;provided&lt;/scope&gt;<br />
		&lt;/dependency&gt;<br />
	&lt;/dependencies&gt;<br />
	&lt;build&gt;<br />
		&lt;plugins&gt;<br />
			&lt;plugin&gt;<br />
				&lt;groupId&gt;org.apache.felix&lt;/groupId&gt;<br />
				&lt;artifactId&gt;maven-bundle-plugin&lt;/artifactId&gt;<br />
				&lt;configuration&gt;<br />
					&lt;instructions&gt;<br />
						&lt;Bundle-Activator&gt;com.tonyganchev.blog.karaf.c.Activator&lt;/Bundle-Activator&gt;<br />
						&lt;Export-Package&gt;com.tonyganchev.blog.karaf.c*;version=${project.version}&lt;/Export-Package&gt;<br />
						&lt;Import-Package&gt;*&lt;/Import-Package&gt;<br />
						&lt;_removeheaders&gt;Import-Service,Export-Service&lt;/_removeheaders&gt;<br />
					&lt;/instructions&gt;<br />
				&lt;/configuration&gt;<br />
			&lt;/plugin&gt;<br />
		&lt;/plugins&gt;<br />
	&lt;/build&gt;<br />
&lt;/project&gt;<br />

Again, nothing particularly interesting apart from the highlighted line. The _removeheaders instruction commands bnd to strip some of the generated MANIFEST.MF headers. Why is it needed? If not added, bnd tends to generate an Import-Service entry for the GreetingService. Unfortunately this fails bundle’s startup since no other bundle exports this service (i.e. there is no corresponding Export-Service entry in karaf-bunle-a). Alternative solution is to add the missing entry by hand (bnd cannot auto-generate it as we register the service in code in bundle-a’s activator) but the OSGi Alliance recommends that Import/Export-Service should not be used.

The consumers are wired trough the blueprint configuration:

<br />
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;<br />
&lt;blueprint xmlns=&quot;http://www.osgi.org/xmlns/blueprint/v1.0.0&quot; ...<br />
	default-activation=&quot;eager&quot;&gt;<br />
	&lt;reference id=&quot;greetingService&quot; interface=&quot;com.tonyganchev.blog.karaf.a.GreetingService&quot; /&gt;<br />
	&lt;bean id=&quot;greetingSender&quot; class=&quot;com.tonyganchev.blog.karaf.c.impl.GreetingSenderImpl&quot;&gt;<br />
		&lt;argument ref=&quot;greetingService&quot; /&gt;<br />
	&lt;/bean&gt;<br />
...<br />
&lt;/blueprint&gt;<br />

Web Application Bundles

I went through a lot of different samples to try and run the cleanest possible WAB and finally settled on the way it is done in the following example. Essentially you can go through the basic org.apache.karaf.archetypes:karaf-bundle-archetype maven archetype and proceed by adding further instructions to the bundle plugin as shown in karaf-wab/pom.xml:

<br />
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;<br />
&lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; ...&gt;<br />
...<br />
	&lt;artifactId&gt;karaf-wab&lt;/artifactId&gt;<br />
	&lt;packaging&gt;bundle&lt;/packaging&gt;<br />
	&lt;dependencies&gt;<br />
		&lt;dependency&gt;<br />
			&lt;groupId&gt;javax.servlet&lt;/groupId&gt;<br />
			&lt;artifactId&gt;javax.servlet-api&lt;/artifactId&gt;<br />
			&lt;scope&gt;provided&lt;/scope&gt;<br />
		&lt;/dependency&gt;<br />
	&lt;/dependencies&gt;<br />
	&lt;build&gt;<br />
		&lt;plugins&gt;<br />
			&lt;plugin&gt;<br />
				&lt;groupId&gt;org.apache.felix&lt;/groupId&gt;<br />
				&lt;artifactId&gt;maven-bundle-plugin&lt;/artifactId&gt;<br />
				&lt;extensions&gt;true&lt;/extensions&gt;<br />
				&lt;configuration&gt;<br />
					&lt;instructions&gt;<br />
						&lt;Import-Package&gt;*&lt;/Import-Package&gt;<br />
						&lt;Web-ContextPath&gt;wab-test&lt;/Web-ContextPath&gt;<br />
						&lt;Webapp-Context&gt;wab-test&lt;/Webapp-Context&gt;<br />
						&lt;_wab&gt;src/main/webapp&lt;/_wab&gt;<br />
					&lt;/instructions&gt;<br />
				&lt;/configuration&gt;<br />
			&lt;/plugin&gt;<br />
		&lt;/plugins&gt;<br />
	&lt;/build&gt;<br />
&lt;/project&gt;<br />

You need to specify the web context path for the bundle. _wab instruction essentially tells bnd that the bundle is a web one and where it web resources are to be found. The resulting MANIFEST.MF looks like this:

<br />
Manifest-Version: 1.0<br />
Bnd-LastModified: 1452687537092<br />
Build-Jdk: 1.8.0_45<br />
Built-By: tony<br />
Bundle-ClassPath: WEB-INF/classes<br />
Bundle-DocURL: http://www.tonyganchev.com/<br />
Bundle-ManifestVersion: 2<br />
Bundle-Name: karaf-wab<br />
Bundle-SymbolicName: karaf-wab<br />
Bundle-Vendor: Tony Ganchev<br />
Bundle-Version: 1.0.0.SNAPSHOT<br />
Created-By: Apache Maven Bundle Plugin<br />
Export-Package: com.tonyganchev.blog.karaf.wab;uses:=&quot;javax.servlet,javax.servlet.http&quot;;version=&quot;1.0.0.SNAPSHOT&quot;<br />
Import-Package: javax.servlet;version=&quot;[3.1,4)&quot;,javax.servlet.http;version=&quot;[3.1,4)&quot;<br />
Private-Package: com.tonyganchev.blog.karaf.wab<br />
Require-Capability: osgi.ee;filter:=&quot;(&amp;(osgi.ee=JavaSE)(version=1.7))&quot;<br />
Tool: Bnd-3.0.0.201509101326<br />
Web-ContextPath: wab-test<br />
Webapp-Context: wab-test<br />

A single servlet named test is defined. it will serve GET requests to http://localhost:8181/wab-test/test.

<br />
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;<br />
&lt;web-app xmlns=&quot;http://java.sun.com/xml/ns/javaee&quot; version=&quot;3.1&quot;&gt;<br />
	&lt;servlet&gt;<br />
		&lt;servlet-name&gt;test&lt;/servlet-name&gt;<br />
		&lt;servlet-class&gt;com.tonyganchev.blog.karaf.wab.DummyServlet&lt;/servlet-class&gt;<br />
	&lt;/servlet&gt;<br />
	&lt;servlet-mapping&gt;<br />
		&lt;servlet-name&gt;test&lt;/servlet-name&gt;<br />
		&lt;url-pattern&gt;/test&lt;/url-pattern&gt;<br />
	&lt;/servlet-mapping&gt;<br />
&lt;/web-app&gt;<br />

The actual servlet is a trivial affair:

<br />
...<br />
public class DummyServlet extends HttpServlet {<br />
	@Override<br />
	public void doGet(final HttpServletRequest request, final HttpServletResponse response)<br />
			throws ServletException, IOException {<br />
		response.getOutputStream().println(&quot;The servlet works as expected.&quot;);<br />
	}<br />
...<br />
}<br />

Karaf Archive

All Karaf applications are defined by means of features. Features are defined in feature repositories – simple XML manifests – and list all the bundles comprising the application, its configuration, the capabilities provided by the application and all its dependencies i.e. other features or bundles. All of these elements needs to be discovered / indexed by the Karaf/Felix resolver. This means that before installing the feature, all of its bundles need to be resolved as well. For ease of distribution, Karaf introduces the notion of Karaf Archive or KAR.

Each KAR is a zip file similar to a JAR with a custom layout. It comprises of all feature repositories and a repository of all bundles installed by the feature. You can use the kar packaging to define a maven project building a KAR. Here’s the karaf-kar/pom.xml:

<br />
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;<br />
&lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; ...&gt;<br />
...<br />
	&lt;artifactId&gt;karaf-kar&lt;/artifactId&gt;<br />
	&lt;packaging&gt;kar&lt;/packaging&gt;<br />
	&lt;dependencies&gt;<br />
		&lt;dependency&gt;<br />
			&lt;groupId&gt;com.tonyganchev.blog&lt;/groupId&gt;<br />
			&lt;artifactId&gt;karaf-bundle-a&lt;/artifactId&gt;<br />
			&lt;version&gt;${project.version}&lt;/version&gt;<br />
			&lt;type&gt;bundle&lt;/type&gt;<br />
		&lt;/dependency&gt;<br />
		&lt;dependency&gt;<br />
			&lt;groupId&gt;com.tonyganchev.blog&lt;/groupId&gt;<br />
			&lt;artifactId&gt;karaf-bundle-b&lt;/artifactId&gt;<br />
			&lt;version&gt;${project.version}&lt;/version&gt;<br />
			&lt;type&gt;bundle&lt;/type&gt;<br />
		&lt;/dependency&gt;<br />
		&lt;dependency&gt;<br />
			&lt;groupId&gt;com.tonyganchev.blog&lt;/groupId&gt;<br />
			&lt;artifactId&gt;karaf-bundle-c&lt;/artifactId&gt;<br />
			&lt;version&gt;${project.version}&lt;/version&gt;<br />
			&lt;type&gt;bundle&lt;/type&gt;<br />
		&lt;/dependency&gt;<br />
		&lt;dependency&gt;<br />
			&lt;groupId&gt;com.tonyganchev.blog&lt;/groupId&gt;<br />
			&lt;artifactId&gt;karaf-wab&lt;/artifactId&gt;<br />
			&lt;version&gt;${project.version}&lt;/version&gt;<br />
			&lt;type&gt;bundle&lt;/type&gt;<br />
		&lt;/dependency&gt;<br />
	&lt;/dependencies&gt;<br />
&lt;/project&gt;<br />

All bundles we want to include in the KAR are compile-time dependencies to the karaf-kar project.

The sole source of the project is a single feature repository definition – karaf-kar/src/main/feature/feature.xml:

<br />
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;<br />
&lt;features name=&quot;${project.artifactId}-${project.version}&quot; xmlns=&quot;http://karaf.apache.org/xmlns/features/v1.3.0&quot;&gt;<br />
	&lt;repository&gt;mvn:org.apache.karaf.features/standard/${karaf.version}/xml/features&lt;/repository&gt;<br />
	&lt;feature name=&quot;${project.artifactId}&quot; description=&quot;${project.name}&quot; version=&quot;${project.version}&quot;&gt;<br />
		&lt;details&gt;Sample feature details...&lt;/details&gt;<br />
		&lt;feature version=&quot;${karaf.feature.version}&quot; prerequisite=&quot;false&quot; dependency=&quot;false&quot;&gt;aries-blueprint&lt;/feature&gt;<br />
		&lt;feature version=&quot;${karaf.feature.version}&quot; prerequisite=&quot;false&quot; dependency=&quot;false&quot;&gt;war&lt;/feature&gt;<br />
	&lt;/feature&gt;<br />
&lt;/features&gt;<br />

Note: looks like highlighted line 3 is no longer necessary for Karaf plugin in 4.0.5-SNAPSHOT. Previous to that the karaf-assembly project (discussed later) would complain that it cannot resolve any of the two features aries-blueprint or war.

You can take a look at the following thread for further information on the prerequisite and dependency attributes but for now you can safely leave them set to false.

The Karaf Maven plugin post processes the file and adds all dependencies from the POM:

<br />
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;yes&quot;?&gt;<br />
&lt;features xmlns=&quot;http://karaf.apache.org/xmlns/features/v1.4.0&quot; name=&quot;karaf-kar-1.0-SNAPSHOT&quot;&gt;<br />
	&lt;repository&gt;mvn:org.apache.karaf.features/standard/${karaf.version}/xml/features&lt;/repository&gt;<br />
	&lt;feature name=&quot;karaf-kar&quot; description=&quot;karaf-kar&quot; version=&quot;1.0.0.SNAPSHOT&quot;&gt;<br />
		&lt;details&gt;Sample feature details...&lt;/details&gt;<br />
		&lt;feature version=&quot;4.0.5.SNAPSHOT&quot; prerequisite=&quot;false&quot; dependency=&quot;false&quot;&gt;aries-blueprint&lt;/feature&gt;<br />
		&lt;feature version=&quot;4.0.5.SNAPSHOT&quot; prerequisite=&quot;false&quot; dependency=&quot;false&quot;&gt;war&lt;/feature&gt;<br />
		&lt;bundle&gt;mvn:com.tonyganchev.blog/karaf-bundle-a/1.0-SNAPSHOT&lt;/bundle&gt;<br />
		&lt;bundle&gt;mvn:com.tonyganchev.blog/karaf-bundle-b/1.0-SNAPSHOT&lt;/bundle&gt;<br />
		&lt;bundle&gt;mvn:com.tonyganchev.blog/karaf-bundle-c/1.0-SNAPSHOT&lt;/bundle&gt;<br />
		&lt;bundle&gt;mvn:com.tonyganchev.blog/karaf-wab/1.0-SNAPSHOT&lt;/bundle&gt;<br />
	&lt;/feature&gt;<br />
&lt;/features&gt;<br />

Note: older versions of the tooling may try to enforce the addition of resolver attribute to the feature elements that failed on deployment into new Karaf containers. It is an issue of Karaf XML schema validation so pay attention to the used schema – the change I mention gets introduced in version 1.3.0.

The resulting karaf-kar/target/karaf-kar-1.0-SNAPSHOT.kar can be copied to the deploy folder of any Karaf installation and it will be deployed provided that the aries-blueprint and war are discoverable (not necessarily installed).

Custom Karaf Distributions

The last bundle is actually the one that proved to be the most troublesome. Two major issues there. The first was briefly mentioned above – the Karaf Maven plugin can be quite cryptic when reporting its inability to resolve features – thus the solution to add a <repository /> entry to the feature.xml manifest. Beforehand I resorted to enumerating the individual bundles my bundles relied on (aries-related and javax.servlet-api mainly). This worked but it was not clean and meant that due to another bug in Karaf I could not install the features that deliver these bundles if the bundles are installed. Now it seems you don’t need to mention the standard repository as it is always available.

The only thing we need in this project is it’s POM:

<br />
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;<br />
&lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; ...&gt;<br />
...<br />
	&lt;artifactId&gt;karaf-assembly&lt;/artifactId&gt;<br />
	&lt;packaging&gt;karaf-assembly&lt;/packaging&gt;<br />
	&lt;dependencies&gt;<br />
		&lt;dependency&gt;<br />
			&lt;groupId&gt;org.apache.karaf.features&lt;/groupId&gt;<br />
			&lt;artifactId&gt;framework&lt;/artifactId&gt;<br />
			&lt;type&gt;kar&lt;/type&gt;<br />
		&lt;/dependency&gt;<br />
		&lt;dependency&gt;<br />
			&lt;groupId&gt;org.apache.karaf.features&lt;/groupId&gt;<br />
			&lt;artifactId&gt;standard&lt;/artifactId&gt;<br />
			&lt;classifier&gt;features&lt;/classifier&gt;<br />
			&lt;type&gt;xml&lt;/type&gt;<br />
			&lt;scope&gt;runtime&lt;/scope&gt;<br />
		&lt;/dependency&gt;<br />
		&lt;dependency&gt;<br />
			&lt;groupId&gt;com.tonyganchev.blog&lt;/groupId&gt;<br />
			&lt;artifactId&gt;karaf-kar&lt;/artifactId&gt;<br />
			&lt;version&gt;${project.version}&lt;/version&gt;<br />
			&lt;type&gt;kar&lt;/type&gt;<br />
			&lt;scope&gt;runtime&lt;/scope&gt;<br />
		&lt;/dependency&gt;<br />
	&lt;/dependencies&gt;<br />
	&lt;build&gt;<br />
		&lt;plugins&gt;<br />
			&lt;plugin&gt;<br />
				&lt;groupId&gt;org.apache.karaf.tooling&lt;/groupId&gt;<br />
				&lt;artifactId&gt;karaf-maven-plugin&lt;/artifactId&gt;<br />
				&lt;configuration&gt;<br />
					&lt;bootFeatures&gt;<br />
						&lt;feature&gt;bundle&lt;/feature&gt;<br />
						&lt;feature&gt;diagnostic&lt;/feature&gt;<br />
						&lt;feature&gt;feature&lt;/feature&gt;<br />
						&lt;feature&gt;log&lt;/feature&gt;<br />
						&lt;feature&gt;package&lt;/feature&gt;<br />
						&lt;feature&gt;service&lt;/feature&gt;<br />
						&lt;feature&gt;shell&lt;/feature&gt;<br />
						&lt;feature&gt;system&lt;/feature&gt;<br />
					&lt;/bootFeatures&gt;<br />
				&lt;/configuration&gt;<br />
			&lt;/plugin&gt;<br />
		&lt;/plugins&gt;<br />
	&lt;/build&gt;</p>
<p>&lt;/project&gt;<br />

Lets go though the highlights one by one. Karaf’s Maven plugin introduces additional packaging type – karaf-assembly. This saves us tons of custom (awfully hard to decipher) steps to build a Karaf distribution. All dependencies are taken into account when building the distribution. The first one should always be org.apache.karaf.features:framework:kar and it needs to be a compile-time dependency.

The next dependency tells the plugin to use the standard feature repository to resolve features. Last is our KAR module. Note that it is also runtime-scoped. This is extremely important. The scope will define how the bundles will be started. Runtime scope means that the feature will be installed and started automatically on successful start of Karaf. If you switched to compile-time this would mean that the bundles from the feature would be available on startup without starting the feature. I personally don’t felt comfortable with such behavior which was the second issue I wanted to talk about. The feature is considered uninstalled for the lifetime of the container unless installed manually and event this is not always easy to do. Note: you also have the option of using provided scope which essentially means that the feature is discoverable but not started.

The instructions to the Karaf plugin are left minimal – only the boot features are listed and they are kept to a minimum. Note that the karaf-kar feature is not mentioned neither are the features it depends on – still they are implicitly considered boot features. All boot features eventually make their way to the value of the featuresBoot setting in ${karaf}/etc/org.apache.karaf.features.cfg.

Testing It All Together

Once you’ve build everything successfully you should have a Karaf distribution created under karaf-assembly\target\assembly under the root of the project. I;d refer to this location as ${karaf} from now on. Go ahead and start the bundle.

<br />
karaf-assembly\target\assembly\bin\karaf clean<br />

Note: use the clean option to ensure that no state is preserved to ensure that you’re not testing the results of previous karaf-assembly build.

If everything deploys successfully you should see output such as

<br />
        __ __                  ____<br />
       / //_/____ __________ _/ __/<br />
      / ,&lt;  / __ `/ ___/ __ `/ /_<br />
     / /| |/ /_/ / /  / /_/ / __/<br />
    /_/ |_|\__,_/_/   \__,_/_/</p>
<p>  Apache Karaf (4.0.5-SNAPSHOT)</p>
<p>Hit '&lt;tab&gt;' for a list of available commands<br />
and '[cmd] --help' for help on a specific command.<br />
Hit '&lt;ctrl-d&gt;' or type 'system:shutdown' or 'logout' to shutdown Karaf.</p>
<p>karaf@root()&gt; A: Starting the bundle<br />
C: Starting the bundle<br />
Greeting sender (C) started,<br />
Greeting: test<br />
B: Starting the bundle<br />
Greeting: Bundle B started!<br />

Now, check all of your bundles are deployed successfully:

<br />
karaf@root()&gt; bundle:list<br />
START LEVEL 100 , List Threshold: 50<br />
ID | State  | Lvl | Version        | Name<br />
---------------------------------------------------<br />
12 | Active |  80 | 1.0.0.SNAPSHOT | karaf-bundle-a<br />
13 | Active |  80 | 1.0.0.SNAPSHOT | karaf-bundle-b<br />
14 | Active |  80 | 1.0.0.SNAPSHOT | karaf-bundle-c<br />
15 | Active |  80 | 1.0.0.SNAPSHOT | karaf-wab<br />

All bundles start at level 80 since we have not specified a custom start level. The default start level is defined by the value of property karaf.startlevel.bundle defined in ${karaf}etc/config.properties.

Now let’s see what the feature list looks like.

<br />
karaf@root()&gt; feature:list -i<br />
Name                | Version          | Required | State   | Repository              | Description<br />
-------------------------------------------------------------------------------------------------------------------------------<br />
pax-jetty           | 9.2.14.v20151106 |          | Started | org.ops4j.pax.web-4.2.4 | Provide Jetty engine support<br />
pax-http-jetty      | 4.2.4            |          | Started | org.ops4j.pax.web-4.2.4 |<br />
pax-http            | 4.2.4            |          | Started | org.ops4j.pax.web-4.2.4 | Implementation of the OSGI HTTP Service<br />
pax-http-whiteboard | 4.2.4            |          | Started | org.ops4j.pax.web-4.2.4 | Provide HTTP Whiteboard pattern support<br />
pax-war             | 4.2.4            |          | Started | org.ops4j.pax.web-4.2.4 | Provide support of a full WebContainer<br />
aries-proxy         | 4.0.5.SNAPSHOT   |          | Started | standard-4.0.5-SNAPSHOT | Aries Proxy<br />
aries-blueprint     | 4.0.5.SNAPSHOT   |          | Started | standard-4.0.5-SNAPSHOT | Aries Blueprint<br />
feature             | 4.0.5.SNAPSHOT   | x        | Started | standard-4.0.5-SNAPSHOT | Features Support<br />
shell               | 4.0.5.SNAPSHOT   | x        | Started | standard-4.0.5-SNAPSHOT | Karaf Shell<br />
bundle              | 4.0.5.SNAPSHOT   | x        | Started | standard-4.0.5-SNAPSHOT | Provide Bundle support<br />
diagnostic          | 4.0.5.SNAPSHOT   | x        | Started | standard-4.0.5-SNAPSHOT | Provide Diagnostic support<br />
log                 | 4.0.5.SNAPSHOT   | x        | Started | standard-4.0.5-SNAPSHOT | Provide Log support<br />
package             | 4.0.5.SNAPSHOT   | x        | Started | standard-4.0.5-SNAPSHOT | Package commands and mbeans<br />
service             | 4.0.5.SNAPSHOT   | x        | Started | standard-4.0.5-SNAPSHOT | Provide Service support<br />
system              | 4.0.5.SNAPSHOT   | x        | Started | standard-4.0.5-SNAPSHOT | Provide System support<br />
http                | 4.0.5.SNAPSHOT   |          | Started | standard-4.0.5-SNAPSHOT | Implementation of the OSGI HTTP Service<br />
war                 | 4.0.5.SNAPSHOT   |          | Started | standard-4.0.5-SNAPSHOT | Turn Karaf as a full WebContainer<br />
karaf-kar           | 1.0.0.SNAPSHOT   | x        | Started | karaf-kar-1.0-SNAPSHOT  | karaf-kar<br />

The -i switch specifies that only installed bundles should be shown. Note that the feature karaf-kar is installed together with its two dependencies aries-blueprint and war without having to specify them as boot features.

Now, let’s see if karaf-wab is properly deployed as a web bundle:

<br />
karaf@root()&gt; web:list<br />
ID | State       | Web-State   | Level | Web-ContextPath | Name<br />
-------------------------------------------------------------------------------------<br />
15 | Active      | Deployed    | 70    | /wab-test       | karaf-wab (1.0.0.SNAPSHOT)<br />

We’d also ensure that we have the servlet deployed:

<br />
karaf@root()&gt; http:list<br />
ID | Servlet           | Servlet-Name | State       | Alias | Url<br />
--------------------------------------------------------------------------------------------------------------------------<br />
15 | ResourceServlet   | default      | Deployed    | /     | [/]<br />
15 |                   | test         | Deployed    |       | [/test]<br />
15 | JspServletWrapper | jsp          | Deployed    |       | [*.jsp, *.jspx, *.jspf, *.xsp, *.JSP, *.JSPX, *.JSPF, *.XSP]<br />

Listed are all servlets from bundle with ID 15 that is karaf-wab. This includes the implicit JSP- and resource servlets.

To be completely sure everything is working as expected, open http://localhost:8181/wab-test/test and verify that you can see the “The servlet works as expected.” message.

Last – note that although we supplied the kar dependency to karaf-assembly module, no KAR gets installed. If you decided to install the kar feature you’d be able to check this:

<br />
karaf@root()&gt; kar:list<br />
KAR Name<br />
--------<br />

The assembly explodes the KAR and moves its bundles and feature repository under ${karaf}/system repository.

Last Words

Up until now I’ve only touched the surface of what Karaf is. I didn’t have to deal with classloader issues and I did not port any existing OSGi application. I’m looking forward to doing this next and share the experience in new posts.

My opinion so far. I quite like Karaf for the abundance of command-line tooling. I hit a few of bugs related to not properly handling unsupported configurations (see KARAF-4254 for example). I have to say the level of support for such issues is quite good and the roll off of minor releases is quite good especially when compared to the dying Eclipse Virgo. The other think I much appreciate is the ease with which Karaf is built – there is only a single repository compared to the gazillion ones coming with Equinox and Virgo and the whole thing gets built with the default Maven commands. This turned out to be quite important since for some of the issues I hit I didn’t find any documentation and had to debug both Maven and Karaf. This is the reason I work with snapshot versions of Karaf.

The whole task took a couple of weeks stretched over a four week period so there were a lot of source I consulted. What I know were instrumental are listed below. If I missed something do not hesitate to correct me using the comments.

トニー

Sources

  • 3
  •  
  •  
  •  
  •  
  •  
  •  
    3
    Shares

Leave a Reply

Your email address will not be published. Required fields are marked *

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