OSGi for Beginners

Java Development News:

OSGi for Beginners

By Joseph Ottinger

01 May 2008 | TheServerSide.com

Introduction

The Ig Nobel prize ceremony features a few very interesting concepts, apart from the Ig Nobel prizes themselves. One of them is a chance for the literati to explain a concept in twenty-four seconds, followed by a seven word summary.

Read Peter Kriens article on why OSGi trumps Spring IoC for de-coupling and control 

This is a brilliant idea, and one every project should consider as a challenge to fulfill.

OSGi has been mentioned quite a bit in the industry lately, with Equinox becoming a top-level project for Eclipse, Felix being used as a container for Sling and Glassfish V3, and Spring-Modules being released. That said, a lot of people who are unfamiliar with OSGi ... are still unfamiliar with it, no matter how much the people who use OSGi regularly know about it.

So I, as a non-OSGi advocate, would like to take some time to try to explain OSGi to the people who don't know about it – a novel idea, apparently – along with some example code to help people get started. Again, I'm not an OSGi advocate (well, not from the standpoint of being part of OSGi.) I'm just someone who thinks OSGi has viability, except that it's got really poor documentation unless you're so good at it that you don't need the introductory materials.

First, I'll give you the seven-word explanation, then the twenty-four second explanation (transcripted), and then I'll try to explain the whole thing.

The Seven-Word and Twenty-Four-Second explanations of OSGi

OSGi is a component framework for Java.  

The twenty-four second explanation: OSGi is a framework for Java in which units of resources called bundles can be installed. Bundles can export services or run processes, and have their dependencies managed, such that a bundle can be expected to have its requirements managed by the container. Each bundle can also have its own internal classpath, so that it can serve as an independent unit, should that be desireable. All of this is standardized such that any valid OSGi bundle can theoretically be installed in any valid OSGi container.

Rats. Twenty-seven seconds, no matter how fast I run through - I just can't talk quickly enough. What's worse, the explanation doesn't explain why one wants a module system in the first place.

Why a Module System? 

Module systems provide version support for distributed bundles (where "bundle" goes way beyond "OSGi bundle" - I'm using the term to refer to any application.) Dependency hell is also an issue; lifecycle is ... interesting.

All of these things are important; versioning still hasn't made it into web services, EJB versioning is enforced via JNDI, but few use it (nobody in the wild that I know of), jar dependencies are managed normally with parallel jar deployments (except for JCA and WARs, both of which have different ways of managing dependencies).

Java EE has solutions, although not necessarily good ones: WARs and JCA can contain jar files, EJB jars can refer to other jar files through their manifests, and of course app servers can provide a higher-level class repository; versioning is provided through JNDI as long as you're not using different versions of the same web app or web services. Lifecycle exists for webapps (load-on-startup servlets, context listeners) and JCA, but EJB 3.1 might have a lifecycle mechanism - it's not sure yet.

And we all know that Java EE is a hammer that fits everything.

OSGi and JSR-277 are attempts to standardize module deployments for Java, without forcing a Java EE mindset, and without Java EE's weaknesses regarding dependencies and versioning and - for that matter - lifecycle. Since this is an OSGi article and not a module article, we'll focus on OSGi... 

Getting Started with OSGi

Basically, running OSGi is very simple, and fundamentally uninteresting: one grabs one of the OSGi container implementations (Equinox, Felix, Knopflerfish, and ProSyst, among others) and executes the container's boot process, much like one runs a Java EE server. Like Java EE, each container has a different startup environment and slightly different capabilities; check your container of choice for details and options. For the sake of clarity, we'll use Equinox in this article.

Equinox is an OSGi container, of course, and it can be downloaded from http://download.eclipse.org/eclipse/equinox/.  The downloaded bundle is a ZIP file that extracts into a directory called "eclipse, " which shouldn't be surprising: Equinox is the OSGi container Eclipse uses internally. (I'll refer to this directory - "/eclipse," the top-level directory contained in the Equinox distribution - as $EQUINOX.) As documented on the Equinox Quickstart page , looking in $EQUINOX/plugins shows a pretty large collection of jars:

 /opt/tools/eclipse/plugins> ls javax.servlet.jsp_2.0.0.v200706191603.jar org. 
 eclipse.equinox.jsp.jasper_1.0.1.R33x_v20070816.jar javax.servlet_2.4.0. 
 v200706111738.jar org.eclipse.equinox.launcher_1.0.1.R33x_v20070828.jar org. 
 apache.commons.el_1.0.0.v200706111724.jar org.eclipse.equinox.launcher_1.0.1. 
 R33x_v20080118.jar org.apache.commons.logging_1.0.4.v200706111724.jar org. 
 eclipse.equinox.log_1.0.100.v20070226.jar org.apache.jasper_5.5.17.v200706111724. 
 jar org.eclipse.equinox.metatype_1.0.0.v20070226.jar org.eclipse.equinox.app_1. 
 0.1.R33x_v20070828.jar org.eclipse.equinox.preferences_3.2.100.v20070522.jar org. 
 eclipse.equinox.common_3.3.0.v20070426.jar org.eclipse.equinox.preferences_3. 
 2.101.R33x_v20080117.jar org.eclipse.equinox.device_1.0.0.v20070226.jar org. 
 eclipse.equinox.registry_3.3.1.R33x_v20070802.jar org.eclipse.equinox.event_1. 
 0.100.v20070516.jar org.eclipse.equinox.servletbridge_1.0.1.R33x_v20070816.jar 
 org.eclipse.equinox.http.jetty_1.0.1.R33x_v20070816.jar org.eclipse.equinox. 
 source_3.3.1.R33x_r20070918-7n7LECgEKVsLIM1aGBO4b00 org.eclipse.equinox.http. 
 registry_1.0.0.v20070608.jar org.eclipse.equinox.source_3.3.1.R33x_r20070918-7 
 n7LEClEIdwb-bbP_z--EYAO org.eclipse.equinox.http.registry_1.0.1.R33x_v20071231. 
 jar org.eclipse.equinox.useradmin_1.0.0.v20070226.jar org.eclipse.equinox.http. 
 servlet_1.0.1.R33x_v20070816.jar org.eclipse.osgi.services_3.1.200.v20070605.jar 
 org.eclipse.equinox.http.servletbridge_1.0.0.v20070523.jar org.eclipse.osgi. 
 util_3.1.200.v20070605.jar org.eclipse.equinox.http_1.0.100.v20070423.jar org. 
 eclipse.osgi_3.3.1.R33x_v20070828.jar org.eclipse.equinox.http_1.0.101. 
 R33x_v20071016.jar org.eclipse.osgi_3.3.2.R33x_v20080105.jar org.eclipse.equinox. 
 jsp.jasper.registry_1.0.0.v20070607.jar org.mortbay.jetty_5.1.11.v200706111724. 
 jar

Starting Equinox is simple: in this directory, execute java -jar org.eclipse.osgi_3.3.2.R33x_v20080105.jar -console (in a console window), and you'll soon see a prompt:

 osgi>

This is the OSGi console for Equinox. From it, you can install new bundles, start them, stop them, uninstall them, check their dependencies, check registered services, or any number of other things. The first command you should probably try is "ss," for "short status." For a new installation, the interaction looks like this:

 osgi> ss Framework is launched. id State Bundle 0 ACTIVE org.eclipse.osgi_3. 
 3.2.R33x_v20080105 osgi>

This shows that the container is running, with a single bundle installed. This bundle has an id of "0," which is going to be pretty relevant, because you use that id to control the bundle's lifecycle.

So what's a bundle good for? Well, a bundle provides a lifecycle and exported services, as our friendly twenty-seven second summary mentioned.

A Simple Repository to put into a Bundle

So let's create a bundle. I'd like a way to connect to a repository of information. My initial interface should look something like this:

 package repository; public interface RepositoryService { Node put(String path, 
 Node content); Node get(String path); }

It's not much, but it's something to get started: I can use this to put information in, or get information out. My Node class is a simple POJO, which looks something like this:

 package repository; import java.util.ArrayList; import java.util.Date; import 
 java.util.List; public class Node { Date created = new Date(); String source = " 
 unknown"; String author = "unknown"; List contents = new ArrayList< 
 String>(); String path; @Override public String toString() { String s = " 
 node: path=" + getPath() + ", author=" + getAuthor() + ", created=" + getCreated 
 () + ", source=" + getSource() + ", data=["; String separator = ""; for (String 
 d : getContents()) { s += separator + d; separator = ","; } s += "]"; return s; 
 } public Node() { } public Node(String content) { getContents().add(content); } 
 public Node(String content, String context) { this(content); setSource(context); 
 } // .. accessors and mutators go here. }

This is a service that can be implemented and tested independently from OSGi; we have nothing that has anything to do with OSGi yet. With that, we probably should go ahead and have an implementation. I originally coded this as a Map:

 import java.util.HashMap; import java.util.Map; import repository.Node; import 
 repository.RepositoryService; public class MapRepositoryService implements 
 RepositoryService { Map<String, Node> data=new HashMap<String, Node> 
 (); public Node put(String path, Node content) { return data.put(path, content); 
 } public Node get(String path) { return data.get(path); } }

However, a Map provides poor search facilities, and any hierarchy of information is accidental. I'd like to use a DOM for this, instead. So I'll introduce a dependency on XOM for now, and add a new repository service implementation:

 package repository.impl; import java.util.Date; import nu.xom.Attribute; import 
 nu.xom.Document; import nu.xom.Element; import nu.xom.Elements; import nu.xom. 
 Nodes; import nu.xom.ParentNode; import repository.Node; import repository. 
 RepositoryService; public class XMLRepositoryService implements 
 RepositoryService { Document document; Element data; public XMLRepositoryService 
 () { data = new Element("data"); document = new Document(data); } Node toNode(nu. 
 xom.Element node) { if (node.getAttribute("source") == null) { return null; } 
 Node n = new Node(); n.setAuthor(node.getAttributeValue("author")); n.setCreated 
 (new Date(node.getAttributeValue("created"))); n.setSource(node. 
 getAttributeValue("source")); Elements e = node.getChildElements("contents"); n. 
 setPath(node.getAttributeValue("path")); for (int i = 0; i < e.size(); i++) 
 { Element elt = e.get(i); for (int i1 = 0; i1 < elt.getChildCount(); i1++) { 
 n.getContents().add(elt.getChild(i1).getValue()); } } return n; } nu.xom.Element 
 getElement(String path) { while (!path.startsWith("//")) { path = "/" + path; } 
 while (path.endsWith("/")) { path = path.substring(0, path.length() - 1); } 
 Nodes nodes = document.query(path); if (nodes.size() > 0) { return (Element) 
 nodes.get(0); } return null; } public Node get(String path) { Element e = 
 getElement(path); if (e != null) { return toNode(e); } return null; } public 
 Node put(String path, Node content) { Element oldElt = getElement(path); if ( 
 oldElt != null) { // need to remove this node! ParentNode p = oldElt.getParent(); 
 p.removeChild(oldElt); } StringBuilder pathBuilder=new StringBuilder("/"); 
 String[] tree = path.split("/"); Element node = data; for (String t : tree) { if 
 (t.length() != 0) { Element child = node.getFirstChildElement(t); if (child == 
 null) { //System.err.println("creating new "+t); child = new Element(t); node. 
 appendChild(child); } pathBuilder.append('/'); pathBuilder.append(t); node = 
 child; } } node.addAttribute(new Attribute("created", content.getCreated() . 
 toString())); node.addAttribute(new Attribute("source", content.getSource())); 
 node.addAttribute(new Attribute("author", content.getAuthor())); content.setPath 
 (pathBuilder.toString()); node.addAttribute(new Attribute("path", content. 
 getPath())); Element contents = new Element("contents"); node.appendChild( 
 contents); for (String c : content.getContents()) { Element e = new Element(" 
 content"); e.appendChild(c); contents.appendChild(e); } //System.out.println( 
 data.toXML()); return null; } public static void main(String[] args) { 
 XMLRepositoryService s = new XMLRepositoryService(); s.put("/foo/bar/baz", new 
 Node("stuff")); Node n = new Node("bletch"); n.setAuthor("jottinger"); s.put("/ 
 foo/bar/bletch", n); System.out.println(s.get("foo/bar/baz/")); System.out. 
 println(s.get("//foo/bar/baz")); System.out.println(s.get("//foo/bar/bletch")); 
 System.out.println(s.get("foo/bar/")); System.out.println(s.get("//*[@author=' 
 jottinger']")); } }

It's still far from perfect, but it's a good start. However, we still haven't done anything with respect to OSGi - we only have a slightly usable repository class. Let's look at how OSGi modules are built.

Sidetrack: Building an OSGi bundle with a Dependency

An OSGi module is a .jar file. In this, it follows the standard .jar file specification , except it has a set of specific requirements  in its manifest file .

Let's create a simple module, first. This will simple display a message on startup and shutdown, and along the way will include a .jar file as an internal dependency. This jar file will not be visible to any other bundle - it's just a resource for our tutorial bundle. However, this is a fairly simple and common requirement that is poorly documented. (Well, it's poorly documented for me - I looked for a simple example and couldn't find one.) The dependency will be in a jar, baselib.jar, and it will be one class: baselib.BaseService.

 package baselib; import java.util.logging.Logger; public class BaseService { 
 Logger log=Logger.getLogger(this.getClass().getName()); public void sayHello() { 
 log.info("Hello, world!"); } }

Complex, earth-shattering stuff here, to be sure. What we need to do is build this file into a .jar of its very own, one I'll call baselib.jar.

Now, an OSGi bundle requires an "activator," a class that manages the lifecycle of the bundle. An activator implements the org.osgi.framework.BundleActivator interface, which means two methods: start(BundleContext) and stop(BundleContext). These lifecycle methods are where a bundle can register services or start processes, but for us, it's far simpler:

 package tutorial; import baselib.BaseService; import org.osgi.framework. 
 BundleActivator; import org.osgi.framework.BundleContext; import java.util. 
 logging.Logger; public class TutorialActivator implements BundleActivator { 
 Logger log=Logger.getLogger(this.getClass().getName()); public void start( 
 BundleContext bc) { log.info("started"); new BaseService().sayHello(); } public 
 void stop(BundleContext bc) { log.info("stopped."); } }

We can't just stuff this into a jar file and have it work for us, sadly ( Spring-OSGi can help here, but it's far beyond scope for this article.) We need to build a tutorialbundle.jar with a specific set of files and a specific structure. First off, baselib.jar must be in the root directory of our new jar. As well, we need a MANIFEST.MF that includes some OSGi configuration and startup information:

 Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-SymbolicName: com. 
 theserverside.tutorial.osgi.TutorialBundle Bundle-Version: 1 Bundle-Activator: 
 tutorial.TutorialActivator Import-Package: org.osgi.framework;version="1.3.0" 
 Bundle-ClassPath: .,baselib.jar

This manifest works on the assumption that our jar looks like this:

 $ jar tvf tutorialbundle.jar 0 Thu Apr 17 11:57:14 EDT 2008 META-INF/ 391 Thu 
 Apr 17 11:57:12 EDT 2008 META-INF/MANIFEST.MF 0 Thu Apr 17 11:29:56 EDT 2008 
 tutorial/ 714 Thu Apr 17 11:51:02 EDT 2008 tutorial/TutorialActivator.class 902 
 Thu Apr 17 11:15:28 EDT 2008 baselib.jar

The most important things in our manifest are the imported packages (just the OSGi base framework in this case), the activator class name, and the bundle classpath - a comma-separated set of resources in the jar file. If we can duplicate this structure, then we're ready to install this bundle and run it in Equinox.

 $ java -jar org.eclipse.osgi_3.3.2.R33x_v20080105.jar -console osgi> ss 
 Framework is launched. id State Bundle 0 ACTIVE org.eclipse.osgi_3.3.2. 
 R33x_v20080105 osgi> install file:///workspaces/osgi/tutorial/tutorialbundle. 
 jar Bundle id is 4 osgi> start 4 Apr 17, 2008 11:57:29 AM tutorial. 
 TutorialActivator start INFO: started Apr 17, 2008 11:57:29 AM baselib. 
 BaseService sayHello INFO: Hello, world! osgi> stop 4 Apr 17, 2008 1:29:25 
 PM tutorial.TutorialActivator stop INFO: stopped. osgi>

Now we can see how to build a bundle, and how to deploy it with dependent jars (a requirement, since our repository class above relies on XOM.)

Building our Repository Bundle

Now we can work on our repository bundle, which adds some additional functionality in the Activator: it registers the repository as a service and exports it, so that other bundles can use our repository to look stuff up.

 package repository; import java.util.Hashtable; import org.osgi.framework. 
 BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.util. 
 tracker.ServiceTracker; import repository.impl.XMLRepositoryService; public 
 class Activator implements BundleActivator { /** * @see org.osgi.framework. 
 BundleActivator#start(org.osgi.framework.BundleContext) */ public void start( 
 BundleContext context) throws Exception { // register the service context. 
 registerService( RepositoryService.class.getName(), new XMLRepositoryService(), 
 new Hashtable<Object,Object>()); // create a tracker and track the log 
 service ServiceTracker repositoryServiceTracker = new ServiceTracker(context, 
 RepositoryService.class.getName(), null); repositoryServiceTracker.open(); // 
 grab the service RepositoryService repositoryService = (RepositoryService) 
 repositoryServiceTracker.getService(); System.err.println("RepositoryService 
 Activated"); } /* * (non-Javadoc) * @see org.osgi.framework.BundleActivator#stop 
 (org.osgi.framework.BundleContext) */ public void stop(BundleContext context) 
 throws Exception { // close the service tracker System.err.println(" 
 RepositoryService Deactivated"); } }

Note that this hardcodes the use of our XMLRepositoryService, above, which is a little icky. We could use Spring, or the Service Provider Interface, or an environment variable, or something - even an OSGi preference service - to make this a runtime decision, but that's outside of our scope for this article. Let's get this service deployed and we'll show you how to use it from another bundle, which will itself open the door for other options.

Following our first simple bundle, the relevant information for our repository bundle consists of the manifest file and the directory structure. Here's the directory structure:

 $ jar tvf repositorybundle.jar 0 Thu Apr 17 14:08:46 EDT 2008 META-INF/ 553 Thu 
 Apr 17 14:08:44 EDT 2008 META-INF/MANIFEST.MF 0 Thu Apr 17 13:57:12 EDT 2008 
 repository/ 0 Thu Apr 17 13:57:12 EDT 2008 repository/impl/ 1383 Thu Apr 17 13: 
 57:12 EDT 2008 repository/Activator.class 2095 Thu Apr 17 13:57:12 EDT 2008 
 repository/Node.class 205 Thu Apr 17 13:57:12 EDT 2008 repository/ 
 RepositoryService.class 694 Thu Apr 17 13:57:12 EDT 2008 repository/impl/ 
 MapRepositoryService.class 3823 Thu Apr 17 13:57:12 EDT 2008 repository/impl/ 
 XMLRepositoryService.class 895924 Thu Apr 17 14:03:40 EDT 2008 xerces-2.4.0.jar 
 109318 Thu Apr 17 14:05:08 EDT 2008 xml-apis-1.0.b2.jar 431568 Thu Apr 17 13:54: 
 06 EDT 2008 xom-1.1.jar And the manifest file: Manifest-Version: 1.0 Bundle- 
 ManifestVersion: 2 Bundle-Name: Repository Plug-in Bundle-SymbolicName: 
 repository Bundle-Version: 1.0.0 Bundle-Activator: repository.Activator Bundle- 
 Vendor: theserverside.com Import-Package: org.osgi.framework;version="1.3.0", 
 org.osgi.util.tracker;version="1.3.1" Export-Package: repository;uses:="org.osgi. 
 framework" Bundle-ClassPath: .,xom-1.1.jar,xerces-2.4.0.jar,xml-apis-1.0.b2.jar

So what did we just do? We built a jar, using the Activator implementation we offered earlier, and this has a number of dependencies: XOM, an implementation of Xerces (because of XOM), and the ServiceTracker API.

We are also doing one other interesting thing: we're exporting the repository package. This means that other bundles in our OSGi container can import those packages, and look up specific services by name (in this case, the repository is registered under the name of the RepositoryService interface, or "repository.RepositoryService".) We can install this bundle now and start it:

 osgi> install file:///workspaces/osgi/tutorial/repositorybundle.jar Bundle 
 id is 11 osgi> start 11 RepositoryService Activated osgi>

This doesn't look all that exciting yet, but we've actually got a working OSGi infrastructure we can leverage now. It should be noted that we're putting our interface in with our implementation. In a perfect world, RepositoryService would be in its own jar, so we can separate the interface and its implementation. This isn't hard, even from a bundle perspective; in the activator for the interface bundle, you'd just do nothing, and in the implementing bundle, you'd import the interface from the other (interface) bundle. We're just not doing it here because it creates a maze of twisty little passages, all alike, and that slows everyone down.

Using our OSGi Bundle from another Bundle

The last step is to build yet another bundle - but this one will look up the repository service and use it.

Let's show the Bundle Activator first. It's really quite simple, and entirely nonfunctional: when the bundle starts, it looks up the RepositoryService, and stores something into the service just to make sure the repository has something in it. It uses the stop() mechanism to actually look something up in the repository and display it on console. This isn't exactly glamorous work, but it's enough to show the process in motion:

 package testrepouser; 
 import org.osgi.framework.BundleActivator; 
 import org.osgi.framework.BundleContext; 
 import org.osgi.framework.ServiceReference; 
 import java.util.logging.Logger; 
 import repository.Node; 
 import repository.RepositoryService;

 public class SampleActivator implements BundleActivator { 
   
   Logger log=Logger.getLogger(this.getClass().getName()); 

   /* 
    * (non-Javadoc) 
    * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext) 
    */ 

   public void start(BundleContext context) throws Exception { 
     ServiceReference ref = context.getServiceReference(RepositoryService.class.getName()); 
     RepositoryService lookup = (RepositoryService) context.getService(ref); 
     Node testNode=new Node("this is some content"); 
     lookup.put("/foo/bar/baz", testNode); 
     log.info("/foo/bar/baz stored."); 
   } 
 
   /* 
    * (non-Javadoc) 
    * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) 
    */ 
  
   public void stop(BundleContext context) throws Exception {
     ServiceReference ref = context.getServiceReference(RepositoryService.class.getName()); 
     RepositoryService lookup = (RepositoryService) context.getService(ref); 
     log.info(lookup.get("///baz")); 
   } 
 }
The MANIFEST.MF file looks like this: Manifest-Version: 1.0 Bundle- 
 ManifestVersion: 2 Bundle-Name: Repository Sample Plug-in Bundle-SymbolicName: 
 samplerepouser Bundle-Version: 1.0.0 Bundle-Activator: sample.SampleActivator 
 Bundle-Vendor: theserverside.com Import-Package: org.osgi.framework;version=" 
 1.3.0" Require-Bundle: repository

The last line stands out. If you look at the RepositoryService's manifest, the symbolic name there is... "repository." Here, we're saying that this bundle should be able to access exported classes from whatever bundle is referred to here - in other words, since our repository bundle exports the "repository" package, our SampleActivator can import the repository classes directly from the repository bundle instead of having to package them itself.

We build our sample.jar bundle, which should look like this:

 $ jar tvf ../samplebundle.jar 0 Thu Apr 17 14:47:48 EDT 2008 META-INF/ 421 Thu 
 Apr 17 14:47:46 EDT 2008 META-INF/MANIFEST.MF 0 Thu Apr 17 14:47:48 EDT 2008 
 sample/ 1270 Thu Apr 17 14:47:48 EDT 2008 sample/SampleActivator.class

Note the simplicity here: there's nothing in the bundle except our activator. There are no service implementations, not even the interface.

We can install the sample bundle, start it, then stop it, to show everything happening:

 osgi> install file:///workspaces/osgi/tutorial/samplebundle.jar Bundle id is 
 17 osgi> start 17 Apr 17, 2008 2:49:07 PM sample.SampleActivator start INFO: 
 /foo/bar/baz stored. osgi> stop 17 Apr 17, 2008 2:49:09 PM sample. 
 SampleActivator stop INFO: node: path=//foo/bar/baz, author=unknown, created=Thu 
 Apr 17 14:49:07 EDT 2008, source=unknown, data=[this is some content] osgi>

Running our Bundles in another OSGi Container

One of the strengths of OSGi is, of course, the "platform neutrality" of the containers, much as Java EE modules are supposedly deployable to any compatible containers. Let's take just a moment to show our bundles described above being deployed in Felix - Apache's OSGi container - just to show how the bundles look in another container. Felix asks you to name a configuration, so it can reload it if you use the name again - we'll call ours "tutorial01" since this is, after all, a tutorial.

 $ java -jar bin/felix.jar 
 Welcome to Felix. ================= Enter profile name: 
 tutorial01 DEBUG: WIRE: 1.0 -> org.ungoverned.osgi.service.shell -> 1.0 
 DEBUG: WIRE: 1.0 -> org.osgi.service.startlevel -> 0 DEBUG: WIRE: 1.0 -> 
 org.apache.felix.shell -> 1.0 DEBUG: WIRE: 1.0 -> org.osgi.framework -> 
 0 DEBUG: WIRE: 1.0 -> org.osgi.service.packageadmin -> 0 DEBUG: WIRE: 
 2.0 -> org.apache.felix.shell -> 1.0 DEBUG: WIRE: 2.0 -> org.osgi. 
 framework -> 0 DEBUG: WIRE: 3.0 -> org.osgi.framework -> 0 DEBUG: 
 WIRE: 3.0 -> org.osgi.service.obr -> 3.0 DEBUG: WIRE: 3.0 -> org. 
 apache.felix.shell -> 1.0 -> install file:///workspaces/osgi/tutorial/ 
 tutorialbundle.jar Bundle ID: 7 -> start 7 DEBUG: WIRE: 7.0 -> org.osgi. 
 framework -> 0 Apr 18, 2008 10:46:37 AM tutorial.TutorialActivator start 
 INFO: started Apr 18, 2008 10:46:37 AM baselib.BaseService sayHello INFO: Hello, 
 world! -> install file:///workspaces/osgi/tutorial/repositorybundle.jar 
 Bundle ID: 8 -> start 8 DEBUG: WIRE: 8.0 -> org.osgi.util.tracker -> 
 0 DEBUG: WIRE: 8.0 -> org.osgi.framework -> 0 RepositoryService 
 Activated -> install file:///workspaces/osgi/tutorial/samplebundle.jar 
 Bundle ID: 9 -> start 9 DEBUG: WIRE: 9.0 -> org.osgi.framework -> 0 
 DEBUG: WIRE: 9.0 -> module;bundle-symbolic-name="repository";bundle-version= 
 "1.0.0" -> 8.0 Apr 18, 2008 10:47:08 AM sample.SampleActivator start INFO: / 
 foo/bar/baz stored. -> stop 9 Apr 18, 2008 10:47:09 AM sample. 
 SampleActivator stop INFO: node: path=//foo/bar/baz, author=unknown, created=Fri 
 Apr 18 10:47:07 EDT 2008, source=unknown, data=[this is some content] -> 
 shutdown -> RepositoryService Deactivated Apr 18, 2008 10:47:12 AM tutorial. 
 TutorialActivator stop INFO: stopped.

An Actual Application, sort of: an IRC Bot

It's all very easy to see the sample repository working as a test - but a test isn't very interesting. Let's take our repository one step further, and hook it into an IRC bot. Our IRC bot will use PircBot , mostly because of its trivial API, will join only one channel on one IRC network (irc.freenode. net's, "#pircbot" channel), and will respond to only two external commands: ~set and ~get. ~set will take a path and some text, and will add the text to that path; ~get will retrieve information from that path. As such, it'll be a sort of painfully simple and uncorrectable infobot ; it'll be left as an exercise to the reader to make it more functional.

One of the first things we want to do is create a generic way to look up services. This is surely not the best way to do this! There are a lot of different patterns for this sort of thing; this is simply a way to get started quickly, not well. We'll create a ServiceLookup class, and then an implementation of this ServiceLookup for OSGi.

 package service; public interface ServiceLookup { Object getService(String name); 
 } package service.osgi; import org.osgi.framework.BundleContext; import org.osgi. 
 framework.ServiceReference; import service.ServiceLookup; public class 
 OSGIServiceLookupImpl implements ServiceLookup { BundleContext ctx; public 
 OSGIServiceLookupImpl(BundleContext ctx) { this.ctx = ctx; } public Object 
 getService(String name) { ServiceReference ref = ctx.getServiceReference(name); 
 return ctx.getService(ref); } }

Using the OSGIServiceLookupImpl is simple, once it's constructed with the Activator's BundleContext:

 package ircbot; import org.jibble.pircbot.IrcException; import org.jibble. 
 pircbot.NickAlreadyInUseException; import org.osgi.framework.BundleActivator; 
 import org.osgi.framework.BundleContext; import service.osgi. 
 OSGIServiceLookupImpl; import java.io.IOException; public class BotActivator 
 implements BundleActivator { IRCBot bot = null; public void start(final 
 BundleContext context) throws Exception { try { bot = new IRCBot(new 
 OSGIServiceLookupImpl(context)); bot.setVerbose(true); bot.connect("irc.freenode. 
 net"); bot.joinChannel("#pircbot"); } catch (NickAlreadyInUseException e) { e. 
 printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch ( 
 IrcException e) { e.printStackTrace(); } } public void stop(BundleContext 
 context) throws Exception { bot.disconnect(); bot.dispose(); } }

All this does is create an IRCBot instance, with a ServiceLookup implementation. Now for the IRCBot itself, in all its primitive glory:

 package ircbot; import org.jibble.pircbot.PircBot; import service.ServiceLookup; 
 import repository.RepositoryService; import repository.Node; public class IRCBot 
 extends PircBot { ServiceLookup service; public IRCBot(ServiceLookup service) { 
 super(); this.service=service; setName("OSGIBot"); } @Override protected void 
 onMessage(String channel, String sender, String login, String hostname, String 
 message) { String[] command=message.split(" "); if(command.length>1 && 
 ("~set".equals(command[0]) || "~get".equals(command[0]))) { String path=command[ 
 1]; // we should use a tracker for this! RepositoryService repository= ( 
 RepositoryService) service.getService(RepositoryService.class.getName()); if("~ 
 set".equals(command[0])) { StringBuilder content=new StringBuilder(); for(int i= 
 2;i<command.length;i++) { content.append(" "); content.append(command[i]); } 
 Node node=repository.get(path); if(node==null) { node=new Node(); node.setAuthor 
 (sender); node.setSource("irc"); } node.getContents().add(content.toString(). 
 trim()); repository.put(path, node); } if("~get".equals(command[0])) { Node node 
 =repository.get(path); if(node!=null) { int count=0; // will only do two at most, 
 to be polite for(String content:node.getContents()) { if(count++>2) { break; 
 } sendMessage(channel, sender + ": "+content); } } } } } }

And lastly, our MANIFEST.MF, which specifies our classpath (which has to include pircbot.jar), and the activator name ("ircbot.BotActivator"), and the external dependency on " repository":

 Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: IRCBot Plug-in 
 Bundle-SymbolicName: ircbot Bundle-Version: 1.0.0 Bundle-Activator: ircbot. 
 BotActivator Bundle-Vendor: theserverside.com Import-Package: org.osgi.framework; 
 version="1.3.0" Require-Bundle: repository Bundle-ClassPath: pircbot.jar,.

Installing this bundle and starting it will (noisily) connect to Freenode and join #pircbot. Note that there's no code in there to handle nick collisions; you may want to try to add that yourself, or change the default nickname used. It's also not very good code; anyone can put in anything, and let's just say it wouldn't be too hard to crash in multiple ways. However...

There's nothing here that says that only the IRCBot can use the repository. Theoretically, a Jabber client could use the same repository (and very nearly the same code for managing the commands.) In fact, this sort of thing is where OSGi shines: the code in the IRCBot to handle commands could itself be contained in a bundle, and the IRCBot would simply let the appropriate bundle manage commands as needed, and they'd look up the repository service whenever the repository was required.

Conclusion

Hopefully, this has illuminated readers to some of the potential of OSGi, as well as providing a start on how to use it.

Read Peter Kriens article on why OSGi trumps Spring IoC for de-coupling and control