4.4. Obtaining and Calling Registered Services

After the dictionary bundle has been installed and activated in the framework, we can witness packages being exported and services being registered using commands in the framework console. However, the service itself is never invoked. Many developers are not comfortable with the notion of activating code that is not run and does nothing. In fact, this is one unique aspect of our programming model: You set up services waiting to be invoked by code from another bundle.

In this section, let's write a caller bundle whose sole purpose is to exercise DictionaryService in the dictionary bundle. To get our terminologies straight, recall that we refer to the caller bundle as the calling bundle, or the client bundle, and we refer to the bundle that registers services as the service-providing bundle.

Follow these three steps to obtain and use a service from a different bundle:

1.
Declare an Import-Package header in the manifest of the client bundle. The value specifies the name of the package that is expected to be exported by the service-providing bundle. The package should contain the service interface needed by the client bundle.

2.
Call BundleContext's getServiceReference method, specifying the name of the service interface to obtain ServiceReference to the service. You may optionally provide a filter to select service references based on service properties.

3.
Call BundleContext's getService method, passing in ServiceReference obtained in step 2, to get the service object itself.

Let's define the manifest of the caller bundle first:

Import-Package: com.acme.service.dictionary
Bundle-Activator: com.acme.caller.dictionary.Activator

Here is the activator that retrieves DictionaryService in its start method and prints the definitions for words starting with the letter “a”:

package com.acme.caller.dictionary;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import com.acme.service.dictionary.DictionaryService;

public class Activator implements BundleActivator {

   public void start(BundleContext ctxt) throws Exception {
      ServiceReference[] ref = ctxt.getServiceReferences(
         "com.acme.service.dictionary.DictionaryService",
         "(description=The Devil's Dictionary)");
      if (ref == null) {
         System.out.println("The Devil's Dictionary " +
            "service has not been registered");
         return;
      }
      DictionaryService ds =
         (DictionaryService) ctxt.getService(ref[0]);
      String[] aDefs = ds.getDefinitions('a'),
      if (aDefs != null) {
         for (int i=0; i<aDefs.length; i++)
            System.out.println(aDefs[i]);
      }
      ctxt.ungetService(ref[0]);
   }

   public void stop(BundleContext ctxt) {
   }
}

We know that the dictionary bundle registers two versions of DictionaryService. In this example, we specifically request DictionaryService implemented by the Devil's dictionary. Because both versions implement the same service interface, we can only distinguish them using their associated service properties, as spelled out with the condition (description=The Devil's Dictionary) in the getServiceReferences call. Had we specified the condition (description=The Webster Dictionary), we would have gotten Webster's version of DictionaryService. The condition is expressed using a filter string. We look at the use of filters in more detail when we explain the getServiceReferences API in “BundleContext Interface” on page 75.

If you do not care which version of DictionaryService is returned, specify null as the condition.

The getServiceReferences method returns an array of service references that satisfy the given criterion. We are happy with the first one in the array, and we use that to retrieve the service itself.

After calling on the service to get the meanings of all the words beginning with the letter “a,” we signify that we are finished using the service by calling ungetService.

Create the caller bundle, install it, and start it in the framework, and you will see the following output:

> bundles
ID  STATE     LOCATION
--  --------- ----------------------------
1   ACTIVE    file:C:/users/joe/bundles/dictionary.jar
2   INSTALLED file:C:/users/joe/bundles/caller.jar
> start 2
admire: Our polite recognition of another's resemblance to ourselves
accuse: To affirm another's guilt or unworth; most commonly as a justification of
 ourselves for having wronged him.
>

4.4.1. Interbundle Dependency and Class Loading Issues

In the previous example we saw that the caller bundle depends on the dictionary bundle in two aspects: It imports the package exported by the dictionary, and it obtains the service registered by the dictionary. It is important to understand that importing the packages in which service interfaces/classes are defined is a prerequisite to using the services successfully. For example, the caller bundle must import the package com.acme.service.dictionary, which contains the interface class DictionaryService (a type), before obtain the DictionaryService object (an instance of the type). What will happen if the dictionary bundle neglects to export com.acme.service.dictionary or the caller bundle does not bother to import the package? Well, as soon as[2] the caller bundle tries to cast the service obtained from the registry to the desired DictionaryService at the following statement

[2] This exception could happen earlier if the Java virtual machine performs eager class resolution.

DictionaryService ds = (DictionaryService) ctxt.getService(ref[0]);

NoClassDefFoundException is raised because without the package exporter and importer correctly set up, the caller bundle's class loader is not able to find the class definition needed.

Another issue concerning type integrity is also worth exploring. It comes up when one service interface is included in two or more bundles with different implementations (see the description of packaging options on page 59). Within the Java virtual machine, a type is uniquely defined by two elements:

  1. Its fully qualified class name

  2. The class loader that has loaded the class

Thus, if two types differ in either of these regards, they are treated as different types by the Java virtual machine.

We explained that although the Devil and the Webster bundles both export the com.acme.service.dictionary package, where the DictionaryService interface resides, only one, say the Devil bundle, prevails. In other words, when the Webster bundle attempts to load DictionaryService, it uses the class loader of the exporter—the Devil bundle—rather than its own. Bear in mind that although the Webster bundle also contains a copy of the DictionaryService class, it is always ignored. As a result, to code inside any bundle, one and only one type is present: com.acme.service.dictionary.DictionaryService class loaded by the Devil bundle's class loader.

What would happen if instead of loading DictionaryService from the exporter (Devil), the Webster bundle loaded its own copy of the service interface class? Chaos. Effectively two distinct types are floating around in the Java virtual machine: the DictionaryServicew loaded by the Webster bundle and the DictionaryServiced loaded by the Devil bundle. Both bundles would register their implementations of the dictionary service, but when the caller bundle tries to obtain an instance, it may very well cast a DictionaryServiced instance to the DictionaryServicew type or vice versa, resulting in ClassCastException being raised.

Although this hypothetical scenario won't happen with a correctly implemented framework, you can experience a similar problem if the copies of DictionaryService classes packaged into the Webster and the Devil bundles differ, which may also lead to class cast problems for the caller bundle. This is why we emphasize earlier that the two copies of the class should be identical.

4.4.2. Service Use Count

A service use count is maintained for each service used by each calling bundle in the framework. Each time the bundle calls getService, the count is incremented; each time the bundle calls ungetService, the count is decremented. When the count is decreased to zero, it means that the calling bundle has finished using the service. At this point we say that the bundle has released the service.

You cannot obtain the service use count directly in your program, but you can check the return value of the ungetService API. It returns false if the count has become zero, and returns true otherwise.

By the time your bundle is stopped, if ungetService is not called as many times as getService for a given service, the framework automatically sets the service use count of the bundle to zero and forcibly releases the service.

4.4.3. Compiling Client Bundles

To build a client bundle, you must put the service-providing bundle's JAR file on the CLASSPATH at compile-time. For instance,

setenv CLASSPATH jes_path/lib/framework.jar:/home/joe/bundles/dictionary.jar
javac com/acme/caller/dictionary/*.java
jar cmf com/acme/caller/dictionary/Manifest caller.jar com

However, make sure the service-providing bundle's JAR file is not on the CLASSPATH at run-time. See “Forget CLASSPATH” in Chapter 3 on page 49.

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

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