dot CMS

OSGI Explained: Extending Your Software to Embed an OSGi Framework

OSGI Explained: Extending Your Software to Embed an OSGi Framework
Author image

Jason Smith

Co-founder

Share this article on:

You have your software in place, but you need a better way of allowing your technical users to extend the platform. Most Java developers are aware of OSGi, but embedding an OSGi framework into your product seems like a huge undertaking. Further clouding the path is the uncertainty of exactly how OSGi works. For many Java developers OSGi feels more like trying to navigate the open sea rather than something they would want to play with.

What is OSGi?

OSGi (Open Service Gateway Initiative) is a Java framework used for developing and deploying modules and components. It focuses on encapsulation and loose coupling of functions which gives many benefits to the developers, such as modular functionalities that are easily movable between source codes and testing which doesn’t involve endless dependencies.

How does OSGi work?

OSGi is a set of specifications that define a dynamic component system for Java. These specifications allow for a development model in which an application is composed of several components, and then packed into bundles. These components communicate locally and through the network via services.

The goal is to make the applications core code as small as possible. This code itself may be highly coupled to a number of components.

The components are the reusable building blocks for your application. For example shopping cart functionality for your e-commerce website, or a payroll application within your employee management software.

OSGi enables components to hide their implementations from other components whilst exposing only relevant data to other components via services. This stops components from accidentally modifying data they shouldn’t have access to, and also keeps dependencies under control.

In an ideal OSGi application, all links between components are done through services. Services have an API that is defined within a java package. The API consists of classes and/or interfaces that are needed for collaborations between the service providers and consumers.

A bundle is a package for all of the OSGi components, containing them and whichever resources they need to work. Bundles are explicit in their requirements on the environment and the capabilities that they can provide.

OSGi has a layered model as illustrated in the image below:

OSGI_graphic.png

OSGI Terminology Explained

  • OSGI Bundles – OSGI Bundles are the OSGi components made by the developers.

  • Services – The services layer connects bundles in a dynamic way by offering a publish-find-bind model for plain old Java objects.

  • Life-Cycle – The API to install, start, stop, update, and uninstall bundles.

  • Modules – The layer that defines how a bundle can import and export code.

  • Security – The layer that handles the security aspects.

  • Execution Environment – Defines what methods and classes are available in a specific platform.

Why Use OSGi within my Existing Software?

Some of the primary reasons to embed OSGi within your application is so that developers can:

  • Deploy functionality at runtime without having to stop the primary application

  • Run multiple versions of the same library, at the same time, within the same JVM

  • Separate and protect plugins from the core functionality and classpaths

Which Library to Use?

We, at dotcms, decided to use Apache Felix. The Equinox Eclipse project is also a solid option depending on what you are looking to do. Felix stays pretty close to the latest spec but both libraries have similar feature sets. In addition, Felix is better built to be embedded into an existing WAR. Both Equinox and Felix can do this but Equinox recommends, by default, embedding your server into Equinox, not the other way around.

eclipse+screen+grab.png

This is how Eclipse runs, which is what Equinox is primarily built for, so this makes sense.

If you use Jboss or Weblogic you can use the OSGi implementations they ship with, but we prefer to remain more agnostic and not rely on the underneath app server for something like OSGi.  As of the writing of this article, none of the other major implementations implement OSGi framework spec 6.

For more info see:

https://en.wikipedia.org/wiki/OSGi_Specification_Implementations

How to Embed OSGi

At dotCMS we were faced with the issue of needing to provide our users with the ability to deploy what we call Dynamic Plugins. A Dynamic Plugin is a plugin that can be deployed and undeployed at runtime, meaning without a server restart. The plugins needed to be able to override current functionality as well as add new functionality to our existing WAR. This involves adding Servlets, Spring Containers, Filters, Java Classes, REST Endpoints, etc.

In our case, we weren’t starting a new project where the entire app would be centered around OSGi (i.e. Eclipse), we already had an existing WAR on which we needed to add OSGi.

The great news is that OSGi is not difficult to embed. Essentially it boils down to the config and code example shown below.

First you need to add the library dependency to your project. This would be adding the JARs for either Apache Felix or Eclipse Equinox. Next you need something to start OSGi. Being a web application, we used a listener:

<listener>
  <listener-class>my.OSGiListener</listener-class>
</listener>

As an example consider the following:

import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.apache.felix.framework.FrameworkFactory;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.launch.Framework;
public class OSGiListener implements ServletContextListener {

    private final String OSGi_LOAD_DIRECTORY = "/path/to/OSGi/bundles";

    @Override
    public void contextInitialized(ServletContextEvent servletContext) {

        Map<String, String> osgiConfig = new HashMap<>();
        osgiConfig.put(Constants.FRAMEWORK_STORAGE, "/path/to/OSGi/cache");
        osgiConfig.put(Constants.FRAMEWORK_STORAGE_CLEAN, "true");
        try {
            FrameworkFactory frameworkFactory = ServiceLoader.load(FrameworkFactory.class)
                    .iterator().next();
            Framework framework = frameworkFactory.newFramework(osgiConfig);
            framework.start();

            BundleContext bundleContext = framework.getBundleContext();
            BundleManager bundleManager = new BundleManager(bundleContext);
            bundleManager.load();
        } catch (Exception ex) {
            System.out.println("OSGi Failed to Start");
        }
    }
    @Override
    public void contextDestroyed(ServletContextEvent arg0) {
        // stop framework
    }
    public class BundleManager {
        private BundleContext bundleContext = null;
        public BundleManager(BundleContext bundleContext) {
            this.bundleContext = bundleContext;
        }
        public void load() throws Exception {

            ArrayList<Bundle> availableBundles = new ArrayList<Bundle>();
            //get and open available bundles
            for (URL url : getBundles()) {
                Bundle bundle = bundleContext.installBundle(url.getFile(), url.openStream());
                availableBundles.add(bundle);
            }

            //start the bundles
            for (Bundle bundle : availableBundles) {
                try {
                    bundle.start();
                } catch (Exception ex) {
                    System.out.println("Failed to start bundle " + bundle.getSymbolicName());
                }
            }
        }

        private List<URL> getBundles() {
            List<URL> bundleURLs = new ArrayList<>();
            //load into bundleURLs the URLS for OSGi_LOAD_DIRECTORY
            return bundleURLs;
        }
    }
}

For more information see the following links:

http://felix.apache.org/documentation/subprojects/apache-felix-framework/apache-felix-framework-launching-and-embedding.html

http://www.eclipse.org/equinox/server/http_in_container.php

What We Learned

  • BND is your friend. BND http://bnd.bndtools.org is a library to help you build OSGi plugins.

  • The default Gradle plugin for BND stinks. I would recommend looking at this library https://plugins.gradle.org/plugin/biz.aQute.bnd for building BND plugins. I find Gradle good for OSGi plugins, just not its default plugin.

  • Keep OSGi libraries separate from your Web App underneath. When we first got into OSGi, we provided all the libraries we had on the classpath to OSGi. Felix has a mechanism for exporting the libraries to the OSGi bundles. In hindsight, we learned that it is best for bundles to provide their own libraries or to use services from other bundles. This keeps things clean and easier for development.

OSGi is a great way to allow developers to deploy new functionality in an existing web application while keeping it protected and separate from other things within the main application. Following the instructions above, and heeding our lessons learned, it is not difficult to embed OSGi within your own application.