What is OSGi? A different approach to Java modularity

The OSGi approach to developing and deploying modular software in Java offers an interesting alternative to standard runtimes and frameworks, especially for mobile and IoT applications

What is OSGi? A different approach to Java modularity
Raphael Koh

OSGi facilitates creating and managing modular Java components (called bundles) that can be deployed in a container. As a developer, you use the OSGi specification and tools to create one or more bundles. OSGi defines the lifecycle for these bundles. It also hosts them and supports their interactions in a container. You can think of an OSGi container as roughly analogous to a JVM, with additional powers. Likewise, think of bundles as Java applications with unique abilities. Bundles run inside the OSGi container as client and server components.

The OSGi alliance

OSGi began in 1999, and unlike many other specs the standard is not managed by Oracle, the Java Community Process, or the Eclipse Foundation. Instead, it is managed by the OSGi alliance.

How OSGi is different

OSGi’s philosophy differs from that of other Java-based frameworks, most notably Spring. In OSGi, multiple applications can exist within the same container: the OSGi bundle runtime environment. The container ensures each component is sufficiently isolated, and also has access to any dependencies it requires. OSGi can support dependency injection, which is standardized by the Aries Blueprint project. In addition to providing OSGi’s inversion of control (IoC) container, Aries supports standard Java frameworks like the Java Persistence API (JPA).

In OSGi, bundles can expose services that other bundles use. A bundle can also declare a version, and can define what other bundles it depends upon. The runtime will then automatically load all of its bundles in order of dependency. In OSGi, multiple versions of the same bundle can exist side by side, if that is required by bundle dependencies.

OSGi in Eclipse IDE and Equinox

OSGi has been around in some form for a couple of decades. It’s used for many well-known applications, from embedded mobile devices to application servers and IDEs.

The popular Eclipse IDE is built on top of OSGi. Eclipse’s implementation of the OSGi container is called Equinox. It’s a great example for understanding OSGi. Being based on OSGi means that Equinox is a modular platform. It hosts a variety of services that developers can add at will. Each of these offers a capability that a developer might need in their IDE. You might add editors for Java and JavaScript, an app server, and a database connector. Each of these is implemented as an OSGi bundle that is added to the container and can interact with other services in the container.

Recently, there’s been an uptick of interest in using OSGi for the Internet of Things (IoT). OSGi is a natural fit for this type of development, which has a variety of software components running side-by-side on devices, without necessarily knowing about each other. An OSGi container provides a simple and standardized way to host these dynamic software components.

Using OSGi in a Java project: Knoplerfish OSGi

We’ll work through an example application that will make OSGi concepts more concrete. Our example is based on the Knoplerfish OSGi runtime, which is used in many production deployments. Knoplerfish includes a GUI and command-line interface (CLI) for managing the OSGi container and its bundles.

The first thing you’ll do is download Knoplerfish. The current version at the time of this writing is Knoplerfish OSGi 6.1.3. You can replace that version with whatever is most current when you read this article.

After you’ve downloaded and installed Knoplerfish, use the CLI to drop into the directory where you downloaded the JAR file, and enter: java -jar framework.jar. That will run the executable JAR and you should be greeted with a GUI window.

The Knoplerfish OSGi GUI

Knoplerfish OSGi’s GUI can seem overwhelming at first, but the basics are simple:

  • At the top of the screen is the menu.
  • To the left is the set of bundles that have been loaded into the runtime.
  • To the right is an information window.
  • At the bottom is a text output console.
  • At the very bottom is an input console.
A screenshot of the Knoplerfish OSGi GUI. Matthew Tyson

Figure 1. A screenshot of the Knoplerfish OSGi GUI (click to enlarge)

Type help into the input console if you want to see the help options.

Before we move into the example, take a look at the set of running bundles. You’ll see a bundle called HTTP Server, which means that a bundle running an HTTP server is up. Go to your browser, and check out http://localhost:8080. Sure enough, you will see a Knoplerfish web page.

The ‘Hello JavaWorld’ bundle

Let’s use the OSGi runtime to build a simple bundle, which I will call Hello JavaWorld. This bundle outputs a message to the console.

In Listing 1, we use Maven to build the bundle. It has only one dependency, which is provided by the OSGi alliance.

Listing 1. OSGi dependency in the Maven POM


<dependencies>
  <dependency>
    <groupId>org.osgi</groupId>
    <artifactId>org.osgi.core</artifactId>
  </dependency>
</dependencies>

Now, we’re also going to use a plug-in, courtesy of the Apache Felix project. This plug-in takes care of packaging the app as an OSGi bundle for use. Listing 2 shows the configuration we’ll use.

Listing 2. OSGi Felix plug-in in the Maven POM


<plugins>
  <plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <extensions>true</extensions>
    <configuration>
      <instructions>
        <Private-Package>org.javaworld.osgi</Private-Package>
        <Bundle-Activator>org.javaworld.osgi.Hello</Bundle-Activator>
      </instructions>
    </configuration>
  </plugin>
</plugins>

Now we can take a look at the simple class that will output a “Hello.”

Listing 3. Hello JavaWorld OSGi bundle


package com.javaworld.osgi;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class HelloJavaWorld implements BundleActivator {
  public void start(BundleContext ctx) {
    System.out.println("Hello JavaWorld.");
    }
    public void stop(BundleContext bundleContext) {
    }
}

Build the bundle by going to the command line and typing mvn clean install. This will output a JAR file containing the bundle. Now, go to the File menu in the Knoplerfish GUI, and select Add Bundle. This will provide a file browser. Find the JAR we’ve just built and select it.

Managing OSGi bundles in the container

In the output window of the Knoplerfish UI, you’ll see your “Hello, JavaWorld” message appear. Click on the bundle in the Knoplerfish GUI, and you can see the ID the container has assigned to it. When you are ready to stop the bundle, you could click the Stop menu item. Another way is to enter stop [bundle number] on the command line. You can manage bundles in the container using either the GUI or the command line.

Now you have a sense of how a simple bundle works in the OSGi container. Anywhere an OSGi container exists, you will find the same simplicity in starting and stopping bundles. OSGi creates an environment and lifecycle for the bundle.

Bundle Interactions: Services and clients

Next, we’ll look at how bundles communicate with each other.

The first thing we’ll do is create a service bundle. A service bundle is analogous to an EJB session bean: It provides a component that can be accessed by other bundles via a remote interface. To create a service bundle, we need to provide both an interface and an implementation class.

Listing 4. The service bundle interface


package com.javaworld.osgi.service;
public interface WhatIsOsgi {
    public Integer addNum(Integer x, Integer y);
}

Listing 4 is a simple interface. The only method is a addNum() method that will do what it implies: return the addition of two numbers. The implementation shown in Listing 5 is equally simple but adds a couple of OSGi-specific methods.

Listing 5. The service bundle implementation


package com.javaworld.osgi.service;
public class  WhatIsOsgiImpl implements WhatIsOsgi, BundleActivator {

    private ServiceReference<WhatIsOsgi> ref;
    private ServiceRegistration<WhatIsOsgi> reg;

    @Override
    public Integer addNum(Integer x, Integer y){
        return x + y;
    }

    @Override
    public void start(BundleContext context) throws Exception {
        reg = context.registerService(
          WhatIsOsgi.class,
          new WhatIsOsgiImpl(),
          new Hashtable<String, String>());
        ref = reg.getReference();
    }

    @Override
    public void stop(BundleContext context) throws Exception {
        reg.unregister();
    }
}

Let’s look closer at what’s happening in Listing 5:

  1. public class WhatIsOsgiImpl implements WhatIsOsgi, BundleActivator: Here we are implementing the interface we created. Note that we also implement the BundleActivator interface, as we did with the HelloJavaWorld example. The latter is because this bundle will activate itself.
  2. private ServiceReference<WhatIsOsgi> ref; private ServiceRegistration<WhatIsOsgi> reg;: These are variables for the OSGi registration service and the bundle reference for this service, respectively.
  3. public Integer addNum(Integer x, Integer y): This is the simple implementation of the add method.
  4. public void start(BundleContext context): This start method is part of the BundleActivator interface, and is executed by the container. In this example, we obtain a reference to the OSGi registration service and apply it to our WhatIsOsgi interface and implementation. The empty Hashtable is for config params, which we aren’t using here. We also get a reference to the service we have just created.
  5. public void stop(BundleContext context): Here, we simply unregister the service. This simple service just manages the barest elements of its lifecycle. Its main purpose is to expose the addNum method to the OSGi container.

The OSGi client

Next up, let’s write a client that can use the service. This client will again make use of the BundleActivator interface. It will also add the ServiceListener interface, as shown in Listing 6.

Listing 6. The OSGi service client bundle


public class OsgiClient implements BundleActivator, ServiceListener {
  private BundleContext ctx;
  private ServiceReference service;

  public void start(BundleContext ctx) {
      this.ctx = ctx;
      try {
          ctx.addServiceListener(this, "(objectclass=" + WhatIsOsgi.class.getName() + ")");
      } catch (InvalidSyntaxException ise) {
          ise.printStackTrace();
      }
  }
}

Listing 6 has a start method that will add a service listener. This listener is filtered by the class name of the service we created in Listing 5. When the service is updated, it will call the serviceChanged() method, as shown in Listing 7.

Listing 7. serviceChanged method


public void serviceChanged(ServiceEvent event) {
  int type = event.getType();
  switch (type){
    case(ServiceEvent.REGISTERED):
      serviceReference = event.getServiceReference();
      Greeter service = (Greeter)(ctx.getService(service));
      System.out.println("Adding 10 and 100: " + service.addNum(10, 100) );
      break;
    case(ServiceEvent.UNREGISTERING):
      System.out.println("Service unregistered.");
      ctx.ungetService(event.getServiceReference()); // Releases reference to service so it can be GC'd
      break;
    default:
      break;
  }
}

Note that the serviceChanged method is used to determine what event has occurred for a service we are interested in. The service will then respond as specified. In this case, when the REGISTERED event appears, we make use of the addNum() method.

The OSGi alternative

This has been a quick introduction to OSGi, the Open Services Gateway Initiative. As you’ve seen through the Knoplerfish example, OSGi provides a runtime environment where you can define modular Java components (bundles). It provides a defined lifecycle for hosting bundles in the client, and it supports bundles interacting as clients and services within the container. All of these capabilities taken together provide an interesting alternative to standard Java runtimes and frameworks, especially for mobile and IoT applications.

Finally, note that the previous article in the “What is: Java” series introduced the Java Platform Module System, which offers a different approach to the same challenge of Java modularity.

This story, "What is OSGi? A different approach to Java modularity" was originally published by JavaWorld.

Copyright © 2020 IDG Communications, Inc.