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 a BundleContext—typically provided in the BundleActivator.start method and stored for later use, or resolved dynamically from FrameworkUtil.getBundle().getBundleContext().

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 an instance of BundleContext, which is the bundle's connection back to the hosting OSGi framework.

Note

Although creating an activator may seem a simple way of registering a service, it is better to use declarative services, which are covered later in this appendix.

Create a new plug-in project com.packtpub.e4.timezones, to host a service for supplying ZoneId instances. Create a class called com.packtpub.e4.timezones.internal.TimeZonesActivator that implements the org.osgi.framework.BundleActivator interface.

Add the Bundle-Activator and Import-Package to the META-INF/MANIFEST.MF file as follows:

Import-Package: org.osgi.framework
Bundle-Activator:
  com.packtpub.e4.timezones.internal.TimeZonesActivator

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

public class TimeZonesActivator 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 it as an OSGi framework by going to Run | Run Configurations… | OSGi Framework. Ensure that all the workspace and target bundles are deselected with the Deselect all button. Add the com.packtpub.e4.timezones bundle, the Equinox console org.eclipse.equinox.console and the gogo shell org.apache.felix.gogo.shell. Ensure that the Include optional dependencies checkbox is not selected and click on Add Required Bundles, to build a list of the required steps.

Creating an activator

The required bundles are:

  • com.packtpub.e4.timezones
  • 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.

Note

If the bundle does not start, ss in the console will print a list of bundles and start 2 will start 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 a service

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

The BundleContext provides a method called registerService, which takes an interface, an instance, and an optional Dictionary of key/value pairs. This can be used to register instances of the timezone provider with the runtime.

A TimeZonesService interface needs to be provided, along with an implementation. Create an interface called TimeZonesService in the com.packtpub.e4.timezones package as follows:

package com.packtpub.e4.timezones;
import java.time.ZoneId;
import java.util.Map;
import java.util.Set;

public interface TimeZonesService {
  public Map<String, Set<ZoneId>> getTimeZones();
}

Create an implementation in the TimeZonesProvider class, using the code developed in the Creating an Empty TimeZone View section of Chapter 2, Creating Views with SWT, in the com.packtpub.e4.timezones.internal package as follows:

public class TimeZonesProvider implements TimeZonesService {
  public Map<String, Set<ZoneId>> getTimeZones() {
    Supplier<Set<ZoneId>> sortedZones =
     () -> new TreeSet<>(new TimeZoneComparator());
    return ZoneId.getAvailableZoneIds().stream() // stream
     .filter(s -> s.contains("/")) // with / in them
     .map(ZoneId::of) // convert to ZoneId
     .collect(Collectors.groupingBy( // and group by
      z -> z.getId().split("/")[0], // first part
      TreeMap::new, Collectors.toCollection(sortedZones)));
  }
} 

The TimeZonesComparator allows time zones to be compared by their ID:

public class TimeZonesComparator implements Comparator<ZoneId> {
  @Override
  public int compare(ZoneId o1, ZoneId o2) {
    return o1.getID().compareTo(o2.getID());
  }
}

Now that the provider is available, it can be registered as a service in the TimeZonesActivator method start as follows:

public void start(BundleContext context) throws Exception {
  context.registerService(TimeZonesService.class,
   new TimeZonesProvider(), null);
}

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

osgi> bundles | grep timezones
com.packtpub.e4.timezones_1.0.0.qualifier [5]
  {com.packtpub.e4.timezones.TimeZonesService}={service.id=42}

This shows that the bundle 5 has started a service, using the interface com.packtpub.e4.timezones.TimeZonesService, and with service ID 42.

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.timezones.TimeZonesService)"

{com.packtpub.e4.timezones.TimeZonesService}={service.id=42}
  "Registered by bundle:"
    com.packtpub.e4.timezones_1.0.0.qualifier [5]
  "No bundles using service."

The results displayed represent the service instantiated by the timezones bundle. It can be introspected using the service command by passing the service.id:

osgi> service 42
TimeZones [Africa=[Africa/Abidjan, Africa/Accra, Africa/...

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 equivalent services, regardless of the order in which they are registered. For services with equal service.ranking values, the service.id values are compared.

Note

OSGi R6 provides 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.

To pass a priority into the service registration, create a helper method called priority that takes an int and returns a Hashtable with the key service.ranking using that value. This can be used to pass a priority into the service registration methods:

public class TimeZonesActivator implements BundleActivator {
  public void start(BundleContext context) throws Exception {
    context.registerService(TimeZonesService.class,
     new TimeZonesProvider(), priority(1));
  }
  private Dictionary<String, Object> priority(int priority) {
    Hashtable<String, Object> dict = new Hashtable<>();
    dict.put("service.ranking", Integer.valueOf(priority));
    return dict;
  }
}

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

osgi> services | grep timezones
{com.packtpub.e4.timezones.TimeZonesService}=
 {service.ranking=1, service.id=42, service.bundleid=5,
  service.scope=singleton}
"Registered by bundle:" 
 com.packtpub.e4.timezones_1.0.0.qualifier [5]

Tip

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 back in 1999). 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 can be used to acquire services as well as register them.

One way of obtaining an instance of BundleContext is to store it in the TimeZonesActivator.start method as a static variable. That way, classes elsewhere in the bundle will be able to acquire the context, along with an accessor method. A better way is to use FrameworkUtil.getBundle(class).getBundleContext() instead.

A TimeZonesFactory can be created to acquire the TimeZoneService. OSGi services are represented via a ServiceReference (which is a sharable object representing a handle to the service); this in turn can be used to acquire a service instance:

public class TimeZonesFactory {
  private static final Bundle bundle =
   FrameworkUtil.getBundle(TimeZonesService.class);
  private static final BundleContext context =
   bundle.getBundleContext();
  private static final ServiceReference<TimeZonesService> sr =
   context.getServiceReference(TimeZonesService.class);
}

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. This can be wrapped in a block taking a Consumer<TimeZonesService> (which makes it easy to use with lambdas):

public static void use(Consumer<TimeZonesService> consumer) {
  TimeZonesService service = context.getService(sr);
  try {
    consumer.accept(service);
  } finally {
    context.ungetService(sr);
  }
}

Technically the service is only supposed to be used between the getService and ungetService call as its lifetime may be invalid afterwards; instead of returning a service references, the common pattern is to pass in a unit of work that accepts the service and call the ungetService afterwards.

Note

Note that the preceding example requires better error handling, such as checking for null and responding appropriately to the caller. This is left as an exercise for the reader.

Lazy activation of bundles

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.

Lazy activation of bundles

Comparison of services and extension points

Both mechanisms (using the extension registry and using the services) allow for a list of services 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 that can listen to contributions being added and removed.

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

The separation of how the service is created versus how the service is registered is a key difference between this and the registry approach. Like the registry, the OSGi services system is able to 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 the Bundle-SymbolicName. This means there can only be one version of a bundle that exposes registry entries in a runtime, as opposed to multiple versions in the general services case.

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, which would not be appropriate for storing 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 up quickly as 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 as covered in the next section.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.190.101