13 min read

This article created by Dr Alex Blewitt the author of Mastering Eclipse Plug-in Development will present OSGi services as a means to communicate with and connect applications. Unlike the Eclipse extension point mechanism, OSGi services can have multiple versions available at runtime and can work in other OSGi environments, such as Felix or other commercial OSGi runtimes.

(For more resources related to this topic, see here.)

Overview of services

In an Eclipse or OSGi runtime, each individual bundle is its own separate module, which has explicit dependencies on library code via Import-Package, Require-Bundle, or Require-Capability. These express static relationships and provide a way of configuring the bundle’s classpath.

However, this presents a problem. If services are independent, how can they use contributions provided by other bundles? In Eclipse’s case, the extension registry provides a means for code to look up providers. In a standalone OSGi environment, OSGi services provide a similar mechanism.

A service is an instance of a class that implements a service interface. When a service is created, it is registered with the services framework under one (or more) interfaces, along with a set of properties. Consumers can then get the service by asking the framework for implementers of that specific interface.

Services can also be registered under an abstract class, but this is not recommended. Providing a service interface exposed as an abstract class can lead to unnecessary coupling of client to implementation.

The following diagram gives an overview of services:

This separation allows the consumer and producer to depend on a common API bundle, but otherwise be completely decoupled from one another. This allows both the consumer and producer to be mocked out or exchange with different implementations in the future.

Registering a service programmatically

To register a service, an instance of the implementation class needs to be created and registered with the framework. Interactions with the framework are performed with an instance of BundleContext—typically provided in the BundleActivator.start method and stored for later use. The *FeedParser classes will be extended to support registration as a service instead of the Equinox extension registry.

Creating an activator

A bundle’s activator is a class that is instantiated and coupled to the lifetime of the bundle. When a bundle is started, if a manifest entry Bundle-Activator exists, then the corresponding class is instantiated. As long as it implements the BundleActivator interface, the start method will be called. This method is passed as an instance of BundleContext, which is the bundle’s connection to the hosting OSGi framework.

Create a class in the com.packtpub.e4.advanced.feeds project called com.packtpub.e4.advanced.feeds.internal.FeedsActivator, which implements the org.osgi.framework.BundleActivator interface.

The quick fix may suggest adding org.osgi.framework as an imported package. Accept this, and modify the META-INF/MANIFEST.MF file as follows:

Import-Package: org.osgi.framework
Bundle-Activator:
 com.packtpub.e4.advanced.feeds.internal.FeedsActivator

The framework will automatically invoke the start method of the FeedsActivator when the bundle is started, and correspondingly, the stop method when the bundle is stopped. Test this by inserting a pair of println calls:

public class FeedsActivator implements BundleActivator {
  public void start(BundleContext context) throws Exception {
    System.out.println("Bundle started");
  }
  public void stop(BundleContext context) throws Exception {
    System.out.println("Bundle stopped");
  }
}

Now run the project as an OSGi framework with the feeds bundle, the Equinox console, and the Gogo shell. The required dependencies can be added by clicking on Add Required Bundles, although the Include optional dependencies checkbox does not need to be selected. Ensure that the other workspace and target bundles are deselected with the Deselect all button, as shown in the following screenshot:

The required bundles are as follows:

  • com.packtpub.e4.advanced.feeds

  • org.apache.felix.gogo.command

  • org.apache.felix.gogo.runtime

  • org.apache.felix.gogo.shell

  • org.eclipse.equinox.console

  • org.eclipse.osgi

On the console, when the bundle is started (which happens automatically if the Default Auto-Start is set to true), the Bundle started message should be seen.

If the bundle does not start, ss in the console will print a list of bundles and start 2 will start the bundle with the ID 2. Afterwards, stop 2 can be used to stop bundle 2. Bundles can be stopped/started dynamically in an OSGi framework.

Registering the service

Once the FeedsActivator instance is created, a BundleContext instance will be available for interaction with the framework. This can be persisted for subsequent use in an instance field and can also be used directly to register a service.

The BundleContext class provides a registerService method, which takes an interface, an instance, and an optional Dictionary instance of key/value pairs. This can be used to register instances of the feed parser at runtime. Modify the start method as follows:

public void start(BundleContext context) throws Exception {
  context.registerService(IFeedParser.class,
   new RSSFeedParser(), null);
  context.registerService(IFeedParser.class,
   new AtomFeedParser(), null);
  context.registerService(IFeedParser.class,
   new MockFeedParser(), null);
}

Now start the framework again. In the console that is launched, look for the bundle corresponding to the feeds bundle:

osgi> bundles | grep feeds
com.packtpub.e4.advanced.feeds_1.0.0.qualifier [4]
  {com.packtpub.e4.advanced.feeds.IFeedParser}={service.id=56}
  {com.packtpub.e4.advanced.feeds.IFeedParser}={service.id=57}
  {com.packtpub.e4.advanced.feeds.IFeedParser}={service.id=58}

This shows that bundle 4 has started three services, using the interface com.packtpub.e4.advanced.feeds.IFeedParser, and with service IDs 56, 57, and 58.

It is also possible to query the runtime framework for services of a known interface type directly using the services command and an LDAP style filter:

osgi> services (objectClass=com.packtpub.e4.advanced.feeds.IFeedParser)
{com.packtpub.e4.advanced.feeds.IFeedParser}={service.id=56}
  "Registered by bundle:"
    com.packtpub.e4.advanced.feeds_1.0.0.qualifier [4]
  "No bundles using service."
{com.packtpub.e4.advanced.feeds.IFeedParser}={service.id=57}
  "Registered by bundle:"
    com.packtpub.e4.advanced.feeds_1.0.0.qualifier [4]
  "No bundles using service."

{com.packtpub.e4.advanced.feeds.IFeedParser}={service.id=58}
  "Registered by bundle:"
    com.packtpub.e4.advanced.feeds_1.0.0.qualifier [4]
  "No bundles using service."

The results displayed represent the three services instantiated. They can be introspected using the service command passing the service.id:

osgi> service 56
com.packtpub.e4.advanced.feeds.internal.RSSFeedParser@52ba638e
osgi> service 57
com.packtpub.e4.advanced.feeds.internal.AtomFeedParser@3e64c3a
osgi> service 58
com.packtpub.e4.advanced.feeds.internal.MockFeedParser@49d5e6da

Priority of services

Services have an implicit order, based on the order in which they were instantiated. Each time a service is registered, a global service.id is incremented.

It is possible to define an explicit service ranking with an integer property. This is used to ensure relative priority between services, regardless of the order in which they are registered. For services with equal service.ranking values, the service.id values are compared.

OSGi R6 adds an additional property, service.bundleid, which is used to denote the ID of the bundle that provides the service. This is not used to order services, and is for informational purposes only. Eclipse Luna uses OSGi R6.

To pass a priority into the service registration, create a helper method called priority, which takes an int value and stores it in a Hashtable with the key service.ranking. This can be used to pass a priority to the service registration methods. The following code illustrates this:

private Dictionary<String,Object> priority(int priority) {
  Hashtable<String, Object> dict = new Hashtable<String,Object>();
  dict.put("service.ranking", new Integer(priority));
  return dict;
}
public void start(BundleContext context) throws Exception {
  context.registerService(IFeedParser.class,
    new RSSFeedParser(), priority(1));
  context.registerService(IFeedParser.class,
    new MockFeedParser(), priority(-1));
  context.registerService(IFeedParser.class,
    new AtomFeedParser(), priority(2));
}

Now when the framework starts, the services are displayed in order of priority:

osgi> services | (objectClass=com.packtpub.e4.advanced.feeds.IFeedParser) 
{com.packtpub.e4.advanced.feeds.IFeedParser}={service.ranking=2, service.id=58}
"Registered by bundle:" com.packtpub.e4.advanced.feeds_1.0.0.qualifier [4]
"No bundles using service."
{com.packtpub.e4.advanced.feeds.IFeedParser}={service.ranking=1, service.id=56}
"Registered by bundle:"
com.packtpub.e4.advanced.feeds_1.0.0.qualifier [4]
"No bundles using service."
{com.packtpub.e4.advanced.feeds.IFeedParser}={service.ranking=-1, service.id=57}
"Registered by bundle:" com.packtpub.e4.advanced.feeds_1.0.0.qualifier [4]
  "No bundles using service."

Dictionary was the original Java Map interface, and Hashtable the original HashMap implementation. They fell out of favor in Java 1.2 when Map and HashMap were introduced (mainly because they weren’t synchronized by default) but OSGi was developed to run on early releases of Java (JSR 8 proposed adding OSGi as a standard for the Java platform). Not only that, early low-powered Java mobile devices didn’t support the full Java platform, instead exposing the original Java 1.1 data structures. Because of this history, many APIs in OSGi refer to only Java 1.1 data structures so that low-powered devices can still run OSGi systems.

Using the services

The BundleContext instance can be used to acquire services as well as register them. FeedParserFactory, which originally used the extension registry, can be upgraded to refer to services instead.

To obtain an instance of BundleContext, store it in the FeedsActivator.start method as a static variable. That way, classes elsewhere in the bundle will be able to acquire the context. An accessor method provides an easy way to do this:

public class FeedsActivator implements BundleActivator {
  private static BundleContext bundleContext;
  public static BundleContext getContext() {
    return bundleContext;
  }
  public void start(BundleContext context) throws Exception {
    // register methods as before
    bundleContext = context;
  }
  public void stop(BundleContext context) throws Exception {
    bundleContext = null;
  }
}

Now the FeedParserFactory class can be updated to acquire the services. OSGi services are represented via a ServiceReference instance (which is a sharable object representing a handle to the service) and can be used to acquire a service instance:

public class FeedParserFactory {
  public List<IFeedParser> getFeedParsers() {
    List<IFeedParser> parsers = new ArrayList<IFeedParser>();
    BundleContext context = FeedsActivator.getContext();
    try {
      Collection<ServiceReference<IFeedParser>> references = 
        context.getServiceReferences(IFeedParser.class, null);
      for (ServiceReference<IFeedParser> reference : references) {
        parsers.add(context.getService(reference));
        context.ungetService(reference);
      }
    } catch (InvalidSyntaxException e) {
      // ignore
    }
    return parsers;
  }
}

In this case, the service references are obtained from the bundle context with a call to context.getServiceReferences(IFeedParser.class,null). The service references can be used to access the service’s properties, and to acquire the service.

The service instance is acquired with the context.getService(ServiceReference) call. The contract is that the caller “borrows” the service, and when finished, should return it with an ungetService(ServiceReference) call. Technically, the service is only supposed to be used between the getService and ungetService calls as its lifetime may be invalid afterwards; instead of returning an array of service references, the common pattern is to pass in a unit of work that accepts the service and then call ungetService afterwards. However, to fit in with the existing API, the service is acquired, added to the list, and then released immediately afterwards.

Lazy activation of bundles

Now run the project as an Eclipse application, with the feeds and feeds.ui bundles installed. When a new feed is created by navigating to File | New | Other | Feeds | Feed, and a feed such as http://alblue.bandlem.com/atom.xml is entered, the feeds will be shown in the navigator view. When drilling down, a NullPointerException may be seen in the logs, as shown in the following:

!MESSAGE An exception occurred invoking extension:
com.packtpub.e4.advanced.feeds.ui.feedNavigatorContent
 for object com.packtpub.e4.advanced.feeds.Feed@770def59
!STACK 0
java.lang.NullPointerException
  at com.packtpub.e4.advanced.feeds.FeedParserFactory.
   getFeedParsers(FeedParserFactory.java:31)
  at com.packtpub.e4.advanced.feeds.ui.FeedContentProvider.
   getChildren(FeedContentProvider.java:80)
  at org.eclipse.ui.internal.navigator.extensions.
   SafeDelegateTreeContentProvider.
    getChildren(SafeDelegateTreeContentProvider.java:96)

Tracing through the code indicates that the bundleContext is null, which implies that the feeds bundle has not yet been started. This can be seen in the console of the running Eclipse application by executing the following code:

osgi> ss | grep feeds
866 ACTIVE   com.packtpub.e4.advanced.feeds.ui_1.0.0.qualifier
992 RESOLVED com.packtpub.e4.advanced.feeds_1.0.0.qualifier

While the feeds.ui bundle is active, the feeds bundle is not. Therefore, the services haven’t been instantiated, and bundleContext has not been cached.

By default, bundles are not started when they are accessed for the first time. If the bundle needs its activator to be called prior to using any of the classes in the package, it needs to be marked as having an activation policy of lazy. This is done by adding the following entry to the MANIFEST.MF file:

Bundle-ActivationPolicy: lazy

The manifest editor can be used to add this configuration line by selecting Activate this plug-in when one of its classes is loaded, as shown in the following screenshot:

Now, when the application is run, the feeds will resolve appropriately.

Comparison of services and extension points

Both mechanisms (using the extension registry and using the services) allow for a list of feed parsers to be contributed and used by the application. What are the differences between them, and are there any advantages to one or the other?

Both the registry and services approaches can be used outside of an Eclipse runtime. They work the same way when used in other OSGi implementations (such as Felix) and can be used interchangeably. The registry approach can also be used outside of OSGi, although that is far less common.

The registry encodes its information in the plugin.xml file by default, which means that it is typically edited as part of a bundle’s install (it is possible to create registry entries from alternative implementations if desired, but this rarely happens). The registry has a notification system, which can listen to contributions being added and removed.

The services approach uses the OSGi framework to store and maintain a list of services. These services don’t have an explicit configuration file and, in fact, can be contributed by code (such as the registerService calls) or by declarative representations.

The separation of how the service is created versus how the service is registered is a key difference between the service and the registry approach. Like the registry, the OSGi services system can generate notifications when services come and go.

One key difference in an OSGi runtime is that bundles depending on the Eclipse registry must be declared as singletons; that is, they have to use the ;singleton:=true directive on Bundle-SymbolicName. This means that there can only be one version of a bundle that exposes registry entries in a runtime, as opposed to multiple versions in the case of general services.

While the registry does provide mechanisms to be able to instantiate extensions from factories, these typically involve simple configurations and/or properties that are hard-coded in the plugin.xml files themselves. They would not be appropriate to store sensitive details such as passwords. On the other hand, a service can be instantiated from whatever external configuration information is necessary and then registered, such as a JDBC connection for a database.

Finally, extensions in the registry are declarative by default and are activated on demand. This allows Eclipse to start quickly because it does not need to build the full set of class loader objects or run code, and then bring up services on demand. Although the approach previously didn’t use declarative services, it is possible to do this.

Summary

This article introduced OSGi services as a means to extend an application’s functionality. It also shed light on how to register a service programmatically.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here