Posted onEdited onInprogramming Symbols count in article:24kReading time ≈22 mins.
This article describes the fundamental principles of the OSGi development model, using Apache Karaf or ServiceMix as the container.
Karaf Installation and Startup
Before Apache Karaf can provide you with an OSGi-based container runtime, we’ll have to set up our environment first. The process is quick, requiring a minimum of normal Java usage integration work. Package download and installation can follow the guide from Apache Karaf Project Site. I used 3.0.3 in this tutorial.
After extracting the Apache Karaf distribution kit and setting environment variables, the container can be started by invoking the Karaf script provided in the bin directory:
bin\karaf in Windows Command Shell
bin/karaf in Linux Shell
You can use ctrl-D to exit the shell. However, Karaf process dies when you exit the shell. To make Karaf running in the background, you can use script named start under bin directory or install Karaf as service. After that, we can use SSH client or script client under bin directory to connect already running Karaf.
The easiest way is to run start script:
bin\start in Windows Command Shell
bin/start in Linux Shell
On Linux, the command shows nothing and run in the background. You can use ps -ef | grep karaf to get the JVM process running with Karaf. While for windows, a shell window will appear. Now we can connect to Karaf using SSH client or client provided by Karaf. By default, Karaf opened SSH port 8101 and create user karaf with password karaf.
When connected, using crtl-D will not quit the Karaf process, but only the connected client. If you want to quit the background process, use shutdown command provided by Karaf.
1
karaf@root()> shutdown
To install Karaf as service, we can use the wrapper feature provided by Karaf. Start Karaf shell and run the following commands:
Karaf supports both Felix and Equinox framework for OSGI implementation. You can switch the framework inside kraf.
1 2 3 4
karaf@root()> system:framework equinox Changed OSGi framework to equinox. Karaf needs to be restarted to make the change effective karaf@root()> system:framework felix Changed OSGi framework to felix. Karaf needs to be restarted to make the change effective
Build Karaf OSGI Bundle using Maven
In this tutorial, we are going to build two OSGI bundles. One to subscribe event messages with topic while the other one to generate event. And finally we will create KAR package for easy installation, in case of Karaf in production environment without Maven repository Internet access.
The Open Services Gateway Initiative (OSGi), also known as the Dynamic Module System for Java, defines an architecture for modular application development. OSGi container implementations such as Knopflerfish, Equinox, and Apache Felix allow you to break your application into multiple modules and thus more easily manage cross-dependencies between them.
In OSGi, software is distributed in the form of a bundle. The MANIFEST.MF file acts as deployment descriptor for your bundle. The format for this file is the same as that of a normal JAR file.
Build from scratch for the project
Maven provides archetype template toolkit to generate skelton code. With that we can do less pom.xml configuration work.
For the bundle to generate event, named eventpublisher here, will use both Scala and Java programming languages. That is the reason why we use archetype scala-archetype-simple and add bundle related configuration in pom.xml later. For me I think the configurtaion for Scala compiling is much more complex than the one for bundle. Although there are many others archetype for Scala programming, we prefer this one for our tutorial. com.github.igor-petruk.archetypes:maven-archetype-scala-executable is a good choice for Scala programming and it supports latest Scala version.
The default supported Scala version for scala-archetype-simple is 2.8.0 and that requires JDK version 1.6. Please set JAVA_HOME and PATH environment by yourself.
Now let’s start from the scratch to build the skelton,
There is a basic concept in Maven Multiple Modules Project, which is called reactor. Making KAR package will depend on the eventconsumer and eventpublisher modules. So the module to make KAR package should be after above 2 modules in reactor order. You may ask if we can put KAR package work in root module (eventtutorial in our case). The answer is NO, because root is always the first one to be built in reactor order.
Here is the snippet from the Maven build logs and tells the reactor order.
BundleActivator is an interface that may be implemented when a bundle is started or stopped. And it should be specified through the Bundle-Activator Manifest header.
Next, tell the bundle to use this interface in the META-INF\MANIFEST.MF. Such file is automatic generated by Maven plugin maven-bundle-plugin. But Bundle-Activator header is not included by using archetype karaf-command-archetype, we need to add it back.
We are going add 3 commands: add/remove/list under eventhandler scope. For add operation, we need to register the event topic with its eventhandler. While for remove, we need to unregister them. Thus we need a hashmap to record our event topic and its eventhandler instance.
The event service registration API is provided by bundleContext. We could inject it by Blueprint service, DI like Sprint.
And the Blueprint configuration xml is under eventtutorial/eventconsumer/src/main/resources/OSGI-INF/blueprint. Our skelton already includes file named shell-log.xml. We can use it or rename its name as you like.
@Command(scope = "eventhandler", name = "add", description = "Add an event listener.") publicclassAddEventCommandextendsOsgiCommandSupport { privatestaticfinalStringDESC= "The event topic to listen to (*, org/apache/karaf, org/apache/karaf/*," + "org/apache/karaf/log, org.apache/karaf/log2),\n" + "only one handler per topic will be created." + "The filter is space separated."; @Argument(index = 0, name = "filter", description = DESC, required = true, multiValued = false) String filter; EventRepository repository;
Command list is used to display all the registered event topic. And Command remove is used to unregister the service and remove it from our recorded hashmap.
public Set<String> getFilters() { return eventHandlers.keySet(); }
...... }
Programming with Event Generation
The pom.xml file in skelton does not know about OSGI Bundle working, we can copy back the part like the one in eventconsumer module. The source code directory in skelton only includes Scala codes, we can change it to src/main. Thus Java code will be put under src/main/java while Scala will be put under src/main/scala. They still follow the code directory structure rule.
Add a new Karaf Shell Command named event, still the bundleContext is dependency injection by Blueprint. Sending event requires EventAdmin service. We use bundleContext to check if that OSGI service is ready. Which means if we don’t have EventAdmin service ready, we cannot send event. In Karaf, we could use feature:install eventadmin to make that bundle installed and running.
Eventevent=newEvent(this.topic, properties); eventAdmin.sendEvent(event); } else { System.out.println("eventadmin feature is not installed..."); }
returnnull; } }
Run in the Karaf
First, we do a fresh build and install our bundles in local Maven repository.
1
$ mvn clean install
In Karaf, we add our bundles using Maven repository.
You may notice that when install scala-library 2.8, we use wrap type. It is because that release does not include OSGI descriptions. We can use wrap type to deploy no-OSGI jar files(“classical” jar files). The exception information here is a bug that does not impact our bundle usage.
However, latest Scala-library are already OSGI bundles. The reason why I use version 2.8.0 is that, it is default generated by skelton and I want to show how to use wrap protocol here. If you don’t like it, you can upgrade the Scala version from 2.8.0 to 2.11.7. And remember to remove wrap protocol, because it is already OSGI bundle for 2.11.7.
Hit '<tab>'for a list of available commands and '[cmd] --help'forhelp on a specific command. Hit '<ctrl-d>' or type'system:shutdown' or 'logout' to shutdown Karaf.
karaf@root()> feature:install eventadmin karaf@root()> bundle:install -s mvn:tk.wangkexiong.osgi/eventconsumer/1.0 Karaf :: Shell eventhandler Commands Started ... Bundle ID: 65 karaf@root()> bundle:install wrap:mvn:org.scala-lang/scala-library/2.8.0 java.lang.ArrayIndexOutOfBoundsException: 176 at aQute.bnd.osgi.Clazz.classConstRef(Clazz.java:1862) at aQute.bnd.osgi.Clazz.crawl(Clazz.java:1166) at aQute.bnd.osgi.Clazz.doCode(Clazz.java:1134) at aQute.bnd.osgi.Clazz.doAttribute(Clazz.java:945) at aQute.bnd.osgi.Clazz.doAttributes(Clazz.java:910) at aQute.bnd.osgi.Clazz.parseClassFile(Clazz.java:741) at aQute.bnd.osgi.Clazz.parseClassFile(Clazz.java:494) at aQute.bnd.osgi.Clazz.parseClassFileWithCollector(Clazz.java:483) at aQute.bnd.osgi.Clazz.parseClassFile(Clazz.java:473) at aQute.bnd.osgi.Analyzer.analyzeJar(Analyzer.java:2177) at aQute.bnd.osgi.Analyzer.analyzeBundleClasspath(Analyzer.java:2083) at aQute.bnd.osgi.Analyzer.analyze(Analyzer.java:138) at aQute.bnd.osgi.Analyzer.calcManifest(Analyzer.java:616) at org.ops4j.pax.swissbox.bnd.BndUtils.createBundle(BndUtils.java:161) at org.ops4j.pax.url.wrap.internal.Connection.getInputStream(Connection.java:83) at org.apache.felix.framework.util.SecureAction.getURLConnectionInputStream(SecureAction.java:524) at org.apache.felix.framework.cache.JarRevision.initialize(JarRevision.java:165) at org.apache.felix.framework.cache.JarRevision.<init>(JarRevision.java:77) at org.apache.felix.framework.cache.BundleArchive.createRevisionFromLocation(BundleArchive.java:878) at org.apache.felix.framework.cache.BundleArchive.reviseInternal(BundleArchive.java:550) at org.apache.felix.framework.cache.BundleArchive.<init>(BundleArchive.java:153) at org.apache.felix.framework.cache.BundleCache.create(BundleCache.java:277) at org.apache.felix.framework.Felix.installBundle(Felix.java:2866) at org.apache.felix.framework.BundleContextImpl.installBundle(BundleContextImpl.java:165) at org.apache.karaf.bundle.command.Install.doExecute(Install.java:43) at org.apache.karaf.shell.console.AbstractAction.execute(AbstractAction.java:33) at org.apache.karaf.shell.console.OsgiCommandSupport.execute(OsgiCommandSupport.java:39) at org.apache.karaf.shell.commands.basic.AbstractCommand.execute(AbstractCommand.java:33) at Proxy90baf6b3_ea7e_447e_a609_7de43f4600a4.execute(Unknown Source) at Proxy90baf6b3_ea7e_447e_a609_7de43f4600a4.execute(Unknown Source) at org.apache.felix.gogo.runtime.CommandProxy.execute(CommandProxy.java:78) at org.apache.felix.gogo.runtime.Closure.executeCmd(Closure.java:477) at org.apache.felix.gogo.runtime.Closure.executeStatement(Closure.java:403) at org.apache.felix.gogo.runtime.Pipe.run(Pipe.java:108) at org.apache.felix.gogo.runtime.Closure.execute(Closure.java:183) at org.apache.felix.gogo.runtime.Closure.execute(Closure.java:120) at org.apache.felix.gogo.runtime.CommandSessionImpl.execute(CommandSessionImpl.java:92) at org.apache.karaf.shell.console.impl.jline.ConsoleImpl.run(ConsoleImpl.java:208) at org.apache.karaf.shell.console.impl.jline.LocalConsoleManager$2$1$1.run(LocalConsoleManager.java:109) at java.security.AccessController.doPrivileged(Native Method) at org.apache.karaf.jaas.modules.JaasHelper.doAs(JaasHelper.java:57) at org.apache.karaf.shell.console.impl.jline.LocalConsoleManager$2$1.run(LocalConsoleManager.java:102) java.lang.ArrayIndexOutOfBoundsException: 176 at aQute.bnd.osgi.Clazz.classConstRef(Clazz.java:1862) at aQute.bnd.osgi.Clazz.crawl(Clazz.java:1166) at aQute.bnd.osgi.Clazz.doCode(Clazz.java:1134) at aQute.bnd.osgi.Clazz.doAttribute(Clazz.java:945) at aQute.bnd.osgi.Clazz.doAttributes(Clazz.java:910) at aQute.bnd.osgi.Clazz.parseClassFile(Clazz.java:741) at aQute.bnd.osgi.Clazz.parseClassFile(Clazz.java:494) at aQute.bnd.osgi.Clazz.parseClassFileWithCollector(Clazz.java:483) at aQute.bnd.osgi.Clazz.parseClassFile(Clazz.java:473) at aQute.bnd.osgi.Analyzer.analyzeJar(Analyzer.java:2177) at aQute.bnd.osgi.Analyzer.analyzeBundleClasspath(Analyzer.java:2083) at aQute.bnd.osgi.Analyzer.analyze(Analyzer.java:138) at aQute.bnd.osgi.Analyzer.calcManifest(Analyzer.java:616) at org.ops4j.pax.swissbox.bnd.BndUtils.createBundle(BndUtils.java:161) at org.ops4j.pax.url.wrap.internal.Connection.getInputStream(Connection.java:83) at org.apache.felix.framework.util.SecureAction.getURLConnectionInputStream(SecureAction.java:524) at org.apache.felix.framework.cache.JarRevision.initialize(JarRevision.java:165) at org.apache.felix.framework.cache.JarRevision.<init>(JarRevision.java:77) at org.apache.felix.framework.cache.BundleArchive.createRevisionFromLocation(BundleArchive.java:878) at org.apache.felix.framework.cache.BundleArchive.reviseInternal(BundleArchive.java:550) at org.apache.felix.framework.cache.BundleArchive.<init>(BundleArchive.java:153) at org.apache.felix.framework.cache.BundleCache.create(BundleCache.java:277) at org.apache.felix.framework.Felix.installBundle(Felix.java:2866) at org.apache.felix.framework.BundleContextImpl.installBundle(BundleContextImpl.java:165) at org.apache.karaf.bundle.command.Install.doExecute(Install.java:43) at org.apache.karaf.shell.console.AbstractAction.execute(AbstractAction.java:33) at org.apache.karaf.shell.console.OsgiCommandSupport.execute(OsgiCommandSupport.java:39) at org.apache.karaf.shell.commands.basic.AbstractCommand.execute(AbstractCommand.java:33) at Proxy90baf6b3_ea7e_447e_a609_7de43f4600a4.execute(Unknown Source) at Proxy90baf6b3_ea7e_447e_a609_7de43f4600a4.execute(Unknown Source) at org.apache.felix.gogo.runtime.CommandProxy.execute(CommandProxy.java:78) at org.apache.felix.gogo.runtime.Closure.executeCmd(Closure.java:477) at org.apache.felix.gogo.runtime.Closure.executeStatement(Closure.java:403) at org.apache.felix.gogo.runtime.Pipe.run(Pipe.java:108) at org.apache.felix.gogo.runtime.Closure.execute(Closure.java:183) at org.apache.felix.gogo.runtime.Closure.execute(Closure.java:120) at org.apache.felix.gogo.runtime.CommandSessionImpl.execute(CommandSessionImpl.java:92) at org.apache.karaf.shell.console.impl.jline.ConsoleImpl.run(ConsoleImpl.java:208) at org.apache.karaf.shell.console.impl.jline.LocalConsoleManager$2$1$1.run(LocalConsoleManager.java:109) at java.security.AccessController.doPrivileged(Native Method) at org.apache.karaf.jaas.modules.JaasHelper.doAs(JaasHelper.java:57) at org.apache.karaf.shell.console.impl.jline.LocalConsoleManager$2$1.run(LocalConsoleManager.java:102) Bundle ID: 66 karaf@root()> bundle:install -s mvn:tk.wangkexiong.osgi/eventpublisher/1.0 Karaf :: Shell event Commands Started ... Bundle ID: 67 karaf@root()> eventhandler:list Registered EventHandler Topics
### Event received ### xxx/yyy name->morning! event.topics->xxx/yyy
Generate KAR package
The programming for KAR package is very easy when using Maven karaf-maven-plugin. The Karaf feature file is already generated in skelton, we need to add dependency feature and bundles in the feature file. The plugin will help to include them all in the kar package. Edit the feature file named as eventkar/src/main/feature/feature.xml
Let’s do a fresh build and the kar package is generated under eventkar/target/, which can be placed under $KARAF_HOME/deploy/.
1
$ mvn clean package
Conclusion
Using Maven do a great help for OSGI programming and deployment. However, we did not include basic concepts for OSGI/Bundles/OSGI Service Registry/Blueprint here. Please use Google for more information. And please note, the command shell in Karaf may be different from different version. Reading information from Karaf web site is necessary before your start.
p.s There is a Good PPT include almost everything for Karaf Programming, here is the link.