3.6. Cooperation among Bundles and Services

We mentioned that bundles are insulated from each other because they have their own class loaders. However, bundles and their services also need to cooperate with one another to achieve some overall functionality. One practice is to build an application using bundles as the building blocks: Common operations are factored out and programmed into a set of basic modules; more sophisticated and high-level bundles then rely on the basic modules to get their work done.

For example, imagine that we want to develop a streaming audio service that may depend on, say, an HTTP service to make MP3 files available for remote players.[3] However it is not desirable to package the HTTP service into the streaming audio bundle because, as it turns out, a remote home appliance diagnostic service may also rely on it. Such an application scenario calls for three bundles activated on the gateway: the remote diagnostic bundle and the streaming audio bundle, both of which require a third HTTP bundle to work properly.

[3] Incidentally, if you wonder why a residential gateway would want to serve MP3s, Napster-like applications should come to mind.

Sharing is realized by exporting and importing packages for bundles and registering and obtaining services.

3.6.1. Exporting and Importing Packages

A bundle can earmark a set of packages within its possession for export, which means the bundle informs the framework to make the classes in the exported packages available for any other bundles that need to use them. The bundle can export none, some, or all of its packages.

A bundle expresses its intention to export packages by declaring the packages with the Export-Package header in its manifest. Multiple packages are separated by commas. Each package can be optionally followed by a semicolon and a version specification. For example,

Export-Package: com.acme.foo; specification-version=2.0,
   com.acme.bar

declares that the bundle wants to export version 2.0 of the com.acme.foo package and version 0.0 of the com.acme.bar package (because no version is given). The version number has the following components:

major.minor.micro

as defined by the Package Versioning Specification in the Java™ 2 platform [9]. For instance, version 2.0.3 is higher than 1.5.0, but lower than 2.1.0. Version 1.0 is the same as version 1.0.0.

A bundle can also declare to import packages, which indicates that the bundle needs to use the specified classes in order to run. The framework does the matchmaking and ensures that the importer finds the exporter before the former can be started. This process is called resolution, and a bundle is said to be resolved if the packages it needs to import have already been exported by some other bundles present in the framework.

A bundle declares to import packages using the Import-Package header in its manifest. For example,

Import-Package: com.acme.foo; specification-version=1.0,
   com.acme.bar

indicates that the bundle needs to import at least version 1.0 of the com.acme.foo package and any version of the com.acme.bar package.

You cannot export or import individual classes or interfaces. The package is the smallest unit for this purpose.

When packages are exported by a bundle, they are said to be exported to the framework. They are not exported to individual importers in a peer-to-peer fashion. Figure 3.6 and Figure 3.7 illustrate the package exporting and importing process within the framework respectively.

Figure 3.6. The HTTP bundle exports package http.service to the framework. util.data.struct and player.service are examples of packages that have been exported by other bundles (not shown).


Figure 3.7. Streaming audio and diagnostic bundles import http.service. The same package has been automatically imported back by its exporter, the HTTP bundle, at the time of the export.


Internally, the framework interprets the package importing and exporting directives from the bundles' manifests and links up appropriate data structures in their class loaders. Therefore, when the streaming audio bundle tries to access the http.service.HttpService interface, the bundle's class loader tries to load the class from the following places, in this order:

  1. The system classes and the classes on the CLASSPATH

  2. The exported packages that the bundle imports

  3. The bundle itself

In this case, it doesn't find the interface in step 1, but it has better luck with step 2, because the HTTP bundle has declared to export the package, and the framework has arranged to add it to the set of the exported packages accordingly.

Suppose two bundles attempt to export the same package. The framework then consults the version specification of the packages offered by the two bundles and picks the bundle with the higher version of the package as the exporter. For example, if bundle A has the following declaration:

Export-Package: http.service; version-specification=1.0

and bundle B has the following declaration:

Export-Package: http.service; version-specification=1.1

then bundle B is chosen as the exporter for the package service.http. If neither A nor B bothers to specify versions, or if both specify the same version, then the framework arbitrarily selects one.

If a package has already been exported, subsequent attempts by other bundles to export the same package are silently ignored by the framework, even if a bundle that arrives later carries a package with a higher version. Thus, if bundle A has exported its http.service package for some time before bundle B arrives and tries do the same, B's version will not be exported even though it's higher than that exported by A.

For these reasons, a bundle that declares to export some packages may not eventually export them, because the framework is the decision maker and it may have chosen another bundle as the exporter. Conversely, an active bundle declaring to import packages must have succeeded in importing them from some exporter, or it would have failed to be resolved and started by the framework.

3.6.2. Registering and Obtaining Services

If a bundle contains some services, it usually registers the services with the service registry maintained by the framework when the bundle is started. Registering a service makes it obtainable for code in other bundles to use, so it is also known as publishing the service. We call the bundle that publishes services the service-providing bundle. For example, in its activator's start method the HTTP bundle may register its service by calling registerService on the BundleContext interface:

// in the HTTP bundle's activator
public void start(BundleContext bundleContext) {
   Properties props = new Properties();
   props.put("port", new Integer(80));
   HttpService[4] http = new HttpServiceImpl();
   bundleContext.registerService("http.service.HttpService", http,
      props);
}

[4] This is our fictional HTTP service. The OSGi Http service is not explained until Chapter 7.

This code registers the HttpServiceImpl instance http under its interface class name http.service.HttpService along with a service property of the port it is going to use.

What does the service registry look like? It is a structure that maps the types and a set of properties of a service to the service object itself.

Bundles are not required to publish services. A bundle can simply export a set of common utility or helper classes as library classes for other bundles to use in its Export-Package manifest header; it does not need to have an activator. We call this type of bundle library bundles. You may also have bundles that provide neither services nor library classes. For instance, a bundle can act as a server, accepting connections at a well-known socket.

Once a service is registered, another bundle—let's refer to it as the client bundle or the calling bundle—can look it up with a set of criteria, one of which is the name of the requested service interface. If it finds what it wants, it can obtain the desired service and invoke the service's methods. For example, the diagnostic bundle may get the HTTP service registered earlier by the HTTP bundle in its activator's start method as follows:

// in the diagnostic bundle's activator
public void start(BundleContext bundleContext) throws Exception {
   ServiceReference ref = bundleContext.getServiceReference(
      "http.service.HttpService");
   HttpService http =
      (HttpService) bundleContext.getService(ref);
   http.post(data);
}

The calling bundle must obtain org.osgi.framework.ServiceReference before it can retrieve the service object itself. This indirection gives the bundle a chance to examine the service properties associated with the service before committing to using it.

Chapter 4 provides abundant details on how to develop, register, obtain, and invoke services.

3.6.3. Package versus Service Dependency

The package dependency as expressed by importing and exporting packages is determined at bundle development time.

Package dependency is static. Once a package has been exported, it remains in effect to all its importers, even when the bundle that has originally exported it is either stopped or uninstalled. For example, when you refer back to Figure 3.7, the uninstallation of the HTTP bundle does not prevent the streaming audio and the diagnostic bundles from continuing to use the package http.service.

Why is the framework designed to leave packages exported when their bundles are stopped or uninstalled? Classes inside the Java virtual machine are subject to garbage collection when they are no longer in use. Thus, their removal cannot be dictated. Explicitly requiring the classes to be kept around defines a deterministic behavior that is highly desirable for applications developed for and running in the framework.

On the other hand, service dependency is established by a bundle's getting a service registered by another at run-time. It happens after the package dependency between the two bundles has already been resolved.

Service dependency is dynamic. While a bundle is active, it can register or unregister its service at any time. When a service is unregistered, we also say it is withdrawn. This dynamic characteristic creates unique challenges for the callers of a service: When they try to obtain the service, it may not have been registered yet, so that the callers are left empty-handed. Even if the callers succeed in obtaining a registered service, it may be withdrawn later, leaving the callers with a stale service. In “Handling the Dynamic Service Dependency” on page 41, we sketch a solution using events.

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

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