Chapter 5. Services

This chapter moves beyond the notion of bundles and presents the concept of services. Services further decouple bundles by allowing them to collaborate without depending on a particular implementation or packaging. This in turn makes your systems more flexible and opens the door to dynamic collaboration, updating, extension, and reconfiguration.

The goals of this chapter are to

• Introduce services and their use

• Refactor the implementation of Toast to use services

• Discuss the dynamic behavior of services and show some of the application design points that promote and inhibit dynamism

5.1 Moving to Services

In Chapter 2, “OSGi Concepts,” we talked about how modularizing your system gives you the power to compose functional pieces in different ways to suit the needs at hand. This is not without a cost, however. Decoupling leaves the individual modules isolated—that is part of the power—with no inherent means of interacting or collaborating with others. Since modules can be used in many different scenarios, they cannot rely on a particular scope or concrete context. Similarly, modules need a way of supplying function to other modules. This is where services come in.

Section 5.1.2 of the OSGi Service Platform Core Specification defines a service as

“An object registered with the service registry under one or more interfaces together with properties. This object can be discovered and used by bundles.”

This is a simple yet powerful notion and one that should be familiar to Java programmers. OSGi uses Java interfaces to cleanly separate specification from implementation. For every interface there may be many implementations. Referencing interfaces rather than implementations provides the flexibility to use multiple implementations or change your mind regarding the implementation to use. Figure 5-1 depicts a Bundle A that requires a service, Bundle B that implements a service, and Bundle C that defines a service.

Figure 5-1 An OSGi service

image

So what advantages do OSGi services offer a little three-bundle application such as Toast? For starters, in our current implementation the emergency monitor logic is tightly coupled to the classes that implement the behavior of the airbag and the GPS. Those implementations are fake at the moment, and swapping in new classes would require modification to the emergency monitor. As we’ll see in this chapter, OSGi services offer a better way to discover and acquire these underlying components.

In general, OSGi services should be favored over inter-bundle class dependencies. Whenever a bundle references a class defined in another bundle, a tight coupling is created.

When using a service, a bundle need depend only on the service interfaces required rather than any implementation classes. A service is obtained anonymously via the OSGi service registry and is referenced only via its interface. It is not even possible to cast a service back to its implementation class since that class is typically in a package that is not exported by the providing bundle. Simply put, OSGi services allow a bundle to dramatically reduce its dependencies by eliminating the ability to make assumptions.

Figure 5-2 contrasts the tight coupling created when Class A in Bundle X depends on Class B in Bundle Y with the loose coupling when services are used. In the former case, Bundle Y must always be resolved for Bundle X to be resolved—they are effectively a single functional unit. By contrast, if Class A in Bundle X depends only on an OSGi service interface, it is free to use any bundle, such as Y or Z, that provides the service.

Figure 5-2 Using a service to achieve loose coupling between bundles

image

This is precisely the case for how the emergency monitor instantiates and manages both the airbag and the GPS. The airbag and GPS bundles act merely as runtime libraries for the emergency monitor bundle. While this may be appropriate in some situations, making a bundle responsible for creating, starting, and shutting down its own functionality as services improve encapsulation of code and clarifies responsibilities. Instead of having the emergency monitor configure its own airbag and GPS, it would be better to use dependency injection and have it configured with an available airbag and GPS service. This is enabled using OSGi services.

Finally, the current implementation of the application is brittle. If we wanted to swap the implementation of any of the bundles with a newer version or an alternative implementation, it would require major surgery. Doing so on a live system is altogether impossible. The ability to dynamically replace implementation is a key advantage of using OSGi services.

5.2 Registering the GPS Service

Let’s start taking advantage of OSGi services by refactoring the GPS bundle to instantiate its own GPS object and register it as a service. The first step is to create an interface for the GPS and use it to register a GPS service.

With this point in mind, use some of the handy Java refactoring tools in Eclipse to create the IGps interface and update the GPS-related classes and packages:

• Open a Java editor on the Gps class. From the context menu, select Refactor > Extract Interface....

• Type IGps for the interface name.

• Select all four members in the list.

• Deselect the Generate method comments option and press OK.

The net result of these steps is to create the IGps interface and to update the Gps class to implement IGps and EmergencyMonitor to use the new interface where possible.

image

To support future alternative implementations and to clarify the real state of the GPS service, the name of the current Gps class should more accurately reflect that it is just a trivial and fake implementation, not one that is connected to real GPS hardware:

• Select the Gps class in the Package Explorer and use the Refactor > Rename... option from the context menu to rename Gps to be FakeGps. Note that you can also use Alt+Shift+R or F2 to invoke the rename refactoring.

It’s a good practice to distinguish between API and non-API—that is, code that is intended to be used by others and code that is internal implementation detail. Since OSGi manages code visibility at the package level, you should keep implementation classes in a separate package and clearly indicate that they are not APIs. In the case of the GPS bundle, this means the IGps interface should be in an API package and the FakeGps class in a non-API package.

• Select the Refactor > Move... option from the context menu on FakeGps in the Package Explorer.

• Press the Create Package... button in the top right of the Move dialog and create a package called org.equinoxosgi.toast.internal.dev.gps.fake by entering the name and clicking Finish.

• Complete the refactoring by pressing OK in the Move dialog.

Naming Convention for Internal Packages

Including the word internal in the names of internal packages is a good practice because it makes it easy to identify non-API packages. We prefer to place internal immediately after org.equinoxosgi.toast because it ensures that internal packages are sorted together within the project.

The next step is to create a bundle activator for the GPS bundle. The activator will act as the entry point for the bundle similarly to what we saw in Section 4.2.3, “Emergency Monitor Bundle.” In this case the activator needs to create and register the GPS service.

All bundle activators must implement the BundleActivator interface. This interface is defined in the org.osgi.framework package. In the previous chapter you manually listed the packages that the emergency monitor bundle imported. This is convenient for small numbers of packages but can be overbearing for even modest code bases. Fortunately, the PDE tooling provides a handy feature that automates the management of your bundle’s dependencies. Instead of listing individual packages to be imported, you can list a set of bundles from which your bundle’s dependencies can be computed. Let’s try that out here:

• Open the MANIFEST.MF file in the GPS bundle.

• From the Dependencies tab in the manifest editor, expand the Automated Management of Dependencies section at the bottom left, as shown in Figure 5-3.

Figure 5-3 Automated Management of Dependencies section

image

• Make sure that the Import-Package radio button is selected as opposed to the Require-Bundle radio button. We’ll talk more about that later.

• Add the bundle org.eclipse.osgi to the Automated Management of Dependencies list and save the editor. Notice that the project’s code can now reference types in the bundle org.eclipse.osgi.

• Later we’ll return here and click the add dependencies link to compute the bundle’s imported packages and ensure that its runtime dependencies are satisfied.

In the preceding chapter you created a bundle activator for the emergency monitor bundle. Now, create one for the GPS bundle so it can control its own lifecycle:

• Go to the Overview tab on the GPS bundle manifest editor.

• In the Activator field in the General Information section, type Activator as the name of the bundle activator class.

• Click the Activator link to the left of the field to bring up the New Java Class wizard.

• Type org.equinoxosgi.toast.internal.dev.gps.fake.bundle in the Package field and press Finish. This creates the new bundle activator class with stubs for the start and stop methods.

• Update the stubs to create and register the service in start and unregister the service in stop. They should look something like the code shown in the next snippet. Note that you will likely have to add some Java package import statements to the Activator. Select Source > Organize Imports from the context menu or Ctrl+Shift+O to generate the necessary package import statements.

image

When the GPS bundle starts, the OSGi framework creates the bundle activator and calls its start method. This instantiates a FakeGps object and registers it with the OSGi service registry as an IGps service. Similarly, when the bundle stops, the bundle activator unregisters the service.

Notice that the bundle activator uses the fully qualified name of IGps to register the service. This is the name that other bundles will use to discover the service. The activator also caches the ServiceRegistration returned by the registerService method so that the service can later be unregistered when the bundle is stopped.

The last thing to do for the GPS bundle is to ensure that the bundle manifest accurately captures the imported and exported packages. First check the imported packages:

• Go to the Dependencies tab of the bundle manifest editor for the GPS bundle.

• In the Automated Management of Dependencies section, click the add dependencies link.

• Save the editor.

Clicking this link analyzes the code in the bundle and determines the subset of available packages that need to be imported by the bundle at runtime. Notice in Figure 5-4 that the list of Imported Packages now shows all of the external packages referenced in the code. See the sidebar titled “Automatically Updating Runtime Dependencies” for how to have each bundle’s dependencies automatically updated prior to launching the OSGi framework.

Figure 5-4 Imported Packages section

image

For the exported packages list, notice that IGps is the only type in this bundle that other bundles need to see. Check the manifest to make sure that its package is exposed as API and the other implementation and OSGi-related packages are not.

• From the bundle manifest editor for the GPS bundle, go to the Runtime tab.

• The Exported Packages list should contain only org.equinoxosgi.toast.dev.gps. If you select that package, the visible to downstream plug-ins option on the right should be selected.

Automatically Updating Runtime Dependencies

Any code changes you make to a bundle might require that you update its runtime dependencies. Since this happens so often and is so easy to forget, consider enabling the Plug-in Development preference to automatically recompute bundle dependencies.

Prior to launching, each bundle’s runtime dependencies are calculated exactly as if you had manually clicked the add dependencies link in the Automated Management of Dependencies section of the manifest editor.

Figure 5-5 Automated Management of Dependencies preferences

image

At this point, you may notice some compiler errors on the org.equinoxosgi.toast.client.emergency project. We’ll clean those up later on.

5.3 Registering the Airbag Service

Like the GPS bundle, the airbag bundle sits at the bottom of the food chain, and the process for exposing it as a service is similar. You may want to refer to the details in Section 5.2, “Registering the GPS Service,” as you follow these abbreviated steps:

• Extract a new interface called IAirbag from the Airbag class. Don’t include the deploy method in the interface, since we’ll be making that method private later on.

• Rename the Airbag class to be FakeAirbag.

• Move the FakeAirbag class into a new internal package called org.equinoxosgi.toast.internal.dev.airbag.fake.

• In the Dependencies tab of the bundle manifest editor, under the Automated Management of Dependencies section, select the Import-Package radio button and add org.eclipse.osgi to the list.

• Also add org.eclipse.core.jobs to the list since we’ll need that later. Rather than implementing our own concurrency utility classes, we make use of the org.eclipse.core.jobs bundle. It is well tested and supports the concurrency function that we need.

• The Jobs API requires types from another bundle, org.eclipse.equinox.common. Add this bundle to the Required Plug-ins section rather than the Automated Management of Dependencies section we’ve used previously. See “Dealing with Split Packages” for an explanation.

• Save the editor.

• In the Overview tab, type Activator into the Activator field and click the Activator link to the left. Create it in the package org.equinoxosgi.toast.internal.dev.airbag.fake.bundle.

• Add code to the activator to create and register the airbag service on start and unregister the service on stop. You can copy and modify the code from the GPS bundle if you like.

• In the Runtime tab of the bundle manifest editor, ensure that only the org.equinoxosgi.toast.dev.airbag package is exported by the bundle.

• In the Dependencies tab under the Automated Management of Dependencies section, click the add dependencies link to recompute the imported packages.

• Save the editor.

To complete the airbag bundle, let’s make it more stand-alone. It does not make sense for an airbag to have a deploy method that others can call—real airbags deploy all by themselves. To make things handy for testing and demonstration, update FakeAirbag to deploy and notify its listeners every five seconds, as shown in the following code snippet:

image

Favor Import-Package over Require-Bundle

The manifest headers Import-Package and Require-Bundle are used to describe a bundle’s dependencies.

Import-PackageThis header is used to express a bundle’s dependency upon packages that are exported by other bundles. At runtime the framework analyzes the constraints and wires the bundles together.

Require-BundleThis header is used to express a bundle’s explicit dependency upon other bundles by specifying a list of bundle symbolic names. A bundle that uses this header automatically has access to the packages exported by its required bundles.

Importing packages is recommended over requiring bundles as it results in a more flexible and loosely coupled system, offering system designers the ability to swap out implementations and deployments of function to suit their needs.

Since FakeAirbag now has a Job that deploys the airbag every five seconds, modify the airbag’s bundle activator to call startup to schedule the Job and shutdown to cancel it:

image

5.4 Acquiring Services

If the GPS and airbag bundles can be said to reside at the bottom of the food chain, then the emergency monitor bundle sits at the top of the food chain; that is, it acquires services but doesn’t register any.

Hooking the emergency monitor into the service mechanism requires very little change to EmergencyMonitor since it was already built to inject the airbag and GPS dependencies via its setGps and setAirbag methods. As we refactored the other elements of Toast, the Gps and Airbag classes were split into interfaces and internal implementation classes, FakeGps and FakeAirbag, which are not API. So the only change needed in EmergencyMonitor is to reference the new interfaces in the setGps and setAirbag signatures and in private fields. But wait, these changes were automatically made by the Refactor > Extract Interface... operation. The result looks like this:

image

Also, since the airbag and GPS bundles are now responsible for registering their respective services, the bundle activator for the emergency monitor bundle no longer needs to do this. Refactor the bundle activator to look like this snippet:

image

In the revised code, the bundle starts, instantiates an EmergencyMonitor, and then injects the airbag and GPS dependencies. Now that we are using services, the bundle discovers the required services using the OSGi service registry. The BundleContext supplied to start is the bundle’s means of interacting with the OSGi framework.

Using the BundleContext, the activator tries to get a ServiceReference for both the IGps and IAirbag services. A ServiceReference is a handle to a service object rather than the service itself. The activator may fail to acquire a ServiceReference if the service has not yet been registered. Given a ServiceReference, you can use the BundleContext’s getService method to dereference it and get the service object it represents. Note that getService may return null if the service has been unregistered since the ServiceReference was acquired. We talk more about this race condition in Chapter 6, “Dynamic Services.”

Having acquired service references and implementations for both the IGps and IAirbag services, the bundle’s activator initializes and starts the emergency monitor using the setGps, setAirbag, and startup methods.

When the bundle stops, the emergency monitor’s shutdown method is called, and the emergency monitor stops using the GPS and airbag services it was given. The activator then calls the BundleContext’s ungetService method to release the services. This is important because the service registry reference counts the bundles using each service. Ungetting the service when you are done with it keeps the system running smoothly.

This refactoring greatly clarifies the modularity boundaries and inter-module interactions. There are a few tweaks we can do to clean things up:

• First, notice that EmergencyMonitor is not API. To reflect this, rename the EmergencyMonitor’s package to org.equinoxosgi.toast.internal.client.emergency.

• Similarly, refactor the emergency monitor bundle’s activator to be consistent with the other bundle activators; that is, move the Activator class to a new internal package called org.equinoxosgi.toast.internal.client.emergency.bundle.

• Since the emergency monitor bundle has no API, ensure that the bundle manifest does not export any packages.

• Use the Automated Management of Dependencies section of the bundle manifest editor to compute the bundle’s imported packages rather than adding them manually. Remove all the packages from the Imported Packages list on the right, then add the following bundles to the Automated Management of Dependencies list:

org.equinoxosgi.toast.dev.airbag
org.equinoxosgi.toast.dev.gps
org.eclipse.osgi

• Save the bundle manifest editor.

• Finally, click the add dependencies link to compute the bundle’s imported packages, and save the bundle manifest editor again.

5.5 Launching

With the three bundles refactored to make use of OSGi services, it’s time to launch the application again:

• Select Run > Run Configurations... from the menu bar.

• Select the Toast run configuration from beneath OSGi Framework in the list at the left.

• Add the two new bundles by checking the box beside the following bundles in the Target Platform section of the list:

org.eclipse.core.jobs
org.eclipse.equinox.common

• Click the Run button.

The OSGi framework is launched and installs and starts the bundles listed in the launch configuration. You should see the following output on the Console view:

image

5.6 Troubleshooting

Given the way the code is written, there is also a chance that when you launch, you might see this output in the Console view instead:

Launching
Unable to acquire GPS or airbag!

In some cases this version of Toast will fail because the emergency monitor’s bundle activator is dependent on the order in which the bundles are started. If the GPS and airbag bundles happen to be started before the emergency monitor bundle, everything works perfectly. If the emergency monitor bundle is started before either or both of the others, the services it needs will not be registered yet and the startup will fail. We structured the exercise to illustrate the pitfalls of decoupling—you can no longer make as many assumptions. Fortunately, there are several facilities in OSGi and Equinox for handling this situation, and the next chapter, “Dynamic Services,” covers this topic in detail.

If you encounter this problem, you can hack around it temporarily by changing the start level of the emergency bundle in the launch configuration to be a number greater than the default start level of 4.

5.7 Summary

At the outset of this chapter, the Toast sample application was made up of three bundles that were tightly coupled—they did not take advantage of OSGi services. We refactored Toast so the airbag and GPS were bona fide OSGi services with the emergency monitor requiring them both. This allowed the three bundles to collaborate, but the emergency monitor optimistically assumes that the GPS and airbag services are available at the time the application started—a first taste of the challenges of dynamic behavior.

In this chapter we saw how to register services and how to acquire them. We also talked about how to write a bundle activator for every bundle that registers or acquires services. Finally, we learned that writing bundles that depend on a specific start order is not a good idea. Simply registering and acquiring services does not take into account the potential dynamic nature of services.

In the next chapter we show you three approaches for handling dynamic services: Service Tracker, Service Activator Toolkit, and Declarative Services.

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

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