4.5. Service Factory

We have seen that after a bundle registers a service, other bundles can use it. So far, all client bundles are using the same instance of the service. Many applications may find this arrangement lacking under the following circumstances:

  • A service-providing bundle may want to provide different instances of a service based on which bundle makes the request. This allows a customized service to be returned for each caller.

  • A service-providing bundle may need the opportunity to perform application-specific logic when a client bundle gets or releases the service.

A service factory can address these issues. We illustrate how to write a bundle that registers a service factory through an example.

4.5.1. Producing a Customized Service for Each Client Bundle

We begin by introducing ParameterService, motivated by the following requirements: Bundles need to store or access some configurable parameters at run-time. Additionally, parameters pertinent to one bundle should not be accessible and modifiable by another. The service interface is defined as

package com.acme.service.param;
/**
 * This service allows a caller to store and retrieve parameters
 * in the form of a key/value pair.
 */
public interface ParameterService {
   /**
    * Stores a parameter.
    * @param key the name of the parameter.
    * @param value the value of the parameter.
    */
   public void set(String key, String value);

   /**
    * Retrieves a parameter.
    */
   public String get(String key);
}

Its implementation is just as straightforward:

package com.acme.impl.param;
import java.util.*;
import com.acme.service.param.ParameterService;

class ParameterServiceImpl implements ParameterService {
   Properties props;

   public void set(String key, String value) {
      props.put(key, value);
   }

   public String get(String key) {
      return props.get(key);
   }

   ParameterServiceImpl(Properties initProps) {
      if (initProps != null) // copy initial parameters if any
         props = (Properties) initProps.clone();
      else
         props = new Properties();
   }
}

At this stage, we could register this service directly as we did before. However, this would mean that all bundles that require ParameterService would share the same ParameterService instance. As a result, its data integrity and security would be brittle: Different client bundles would follow only a “gentlemen's agreement” to access their parameters under their own key namespace, but how do you prevent a careless or malicious bundle from messing around with another's data? The performance of ParameterService itself is also a concern: How do you synchronize concurrent access from multiple bundles while minimizing performance penalty? To address these issues, we introduce the main character of this section, a ServiceFactory implementation:

package com.acme.impl.param;
import org.osgi.framework.Bundle;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceRegistration;

class ParameterServiceFactory implements ServiceFactory {
   public Object getService(Bundle bundle, ServiceRegistration reg)
   {
      return new ParameterServiceImpl(null);
   }

   public void ungetService(Bundle bundle, ServiceRegistration reg,
                        Object service)
   {
      // does nothing
   }
}

In our activator, rather than register an instance of ParameterService, we register the service factory:

ctxt.registerService("com.acme.service.ParameterService",
      new ParameterServiceFactory(), null);

It is important that the service factory be registered under the type of the service it is going to generate. In the previous example, the object being registered is of the type ParameterServiceFactory, but the type under which it is registered is com.acme.service.param.ParameterService.

Now from the client bundle's perspective, it will use the following sequence to retrieve and use ParameterService:

ServiceReference ref = ctxt.getServiceReference(
   "com.acme.service.param.ParameterService");
ParameterService myParams =
      (ParameterService) contxt.getService(ref);

The same APIs are used as in “Obtaining and Calling Registered Services” on page 61. The fact that the registered service is implemented as a service factory is transparent to the client bundles. However, for each calling bundle, the framework delegates its getService call to ParameterServiceFactory's getService method instead of returning an instance directly from the service registry. Consequently, each calling bundle gets its own copy of ParameterService. They can set or get parameters as they wish, without worrying about potential clashes in key names or values compromised by another bundle accidentally or maliciously, because each copy maintains its own instance of the parameter data structure (Properties props of the ParameterServiceImpl class). Figure 4.1 illustrates the difference with or without the service factory.

Figure 4.1. Getting service with (a) and without (b) using a service factory. P is an instance of ParameterService, P-Factory is an instance of ParameterServiceFactory.


4.5.2. Service Cache

When a bundle calls the getService method for the very first time, the framework is required to cache internally the service instance produced by the service factory for the requesting bundle. Subsequently, if the same bundle calls getService again, the framework returns the cached service, rather than invokes the service factory's getService method to generate new ones repeatedly.

A service use count is maintained for each calling bundle in the same manner described in Section 4.4.2. When the count is decreased to zero, the framework removes the service from its internal cache automatically.

4.5.3. Customization for Getting and Releasing Service

In many situations, a service-providing bundle needs to know when a client bundle starts using the service it provides, and it needs to know when the client bundle finishes using the service. A service factory makes this possible by allowing you to customize behaviors in the getService and ungetService methods, respectively.

In our example, when a client bundle releases the service by calling ungetService, the instance of ParameterService it has been using is removed from the framework cache. So the next time the same bundle calls getService, it gets a brand new ParameterService, and loses all its previously stored parameters. To correct this problem, we can save the parameters persistently in the ungetService method.

Every bundle has its own private data storage area, accessible as a java.io.File object returned by the BundleContext method getDataFile. Passing in an empty string ("") returns the root File to the storage area.

Within the parameter bundle's data storage area, a subdirectory is created for each client bundle to save its parameters as a property file. We use the bundle ID as the directory name because it is guaranteed to be unique within the framework and persistent until the bundle is uninstalled. The directory structure is shown in Figure 4.2

Figure 4.2. In the parameter bundle's data area, we currently maintain parameters for three client bundles, whose IDs are 4, 1, and 7 respectively.


We encapsulate the details of accessing the parameter bundle's data storage in a ParameterStore object, whose complete implementation is included in Appendix A, section A.3.1.6. For now, it's sufficient to know that its load method retrieves the parameters for a given bundle, whereas its save method stores them.

The service factory is changed to accommodate this feature as follows:

package com.acme.impl.param;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceRegistration;
import java.util.Properties;
import java.io.IOException;

class ParameterServiceFactory implements ServiceFactory {
   private ParameterStore store;

   ParameterServiceFactory(ParameterStore store) {
      this.store = store;
   }

   // A client bundle starts using ParameterService.
   public Object getService(Bundle bundle, ServiceRegistration reg)
   {
      try {
         // retrieve the parameters for the client bundle
         return new ParameterServiceImpl(store.load(bundle));
      } catch (IOException e) {
      }
      return new ParameterServiceImpl(null);
   }

   // The client bundle finishes using ParameterService.
   public void ungetService(Bundle bundle, ServiceRegistration reg,
                        Object service)
   {
      try {
         // save the parameters for the client bundle
         ParameterServiceImpl ps = (ParameterServiceImpl) service;
         store.save(bundle, ps.props);
      } catch (IOException e) {
      }
   }
}

The Object service argument in the ungetService method may need explanation. When the calling bundle finishes using the service, the framework passes the cached service instance back to the service factory's ungetService method automatically. This is how we know service is the expected ParameterServiceImpl instance for saving the parameters.

We never make use of the ServiceRegistration argument in ServiceFactory's getService and ungetService methods. It is useful when the same service factory is registered multiple times.

The activator registers the service factory as follows:

ParameterStore store = new ParameterStore(ctxt);
ctxt.registerService("com.acme.service.param.ParameterService",
      new ParameterServiceFactory(store), null);

When the client bundle signifies that it has finished using ParameterService by calling ungetService, its parameter set is written to the parameter bundle's persistent storage area. When it calls getService again, the saved parameters are restored. What makes this solution more significant are the following two circumstances:

  1. When the client bundle is stopped without having called ungetService. The framework, as a step in stopping the client bundle, calls ungetService automatically. In our example, the framework “ungets” ParameterService for the client bundle.

  2. When ParameterService is unregistered, perhaps because the parameter bundle is being stopped. The framework also calls ungetService to “unget” ParameterService automatically on behalf of each client bundle.

In either case, the client bundles' parameters get a chance to be properly saved.

A service factory relieves a programmer from keeping track of complicated, dynamic dependency relations. It allows a service-providing bundle to discover the client bundles of its service and to act accordingly.

We have not seen the last of ParameterService. When we discuss events in Chapter 5 and permissions in Chapter 9, we revisit and expand this example. For simplicity's sake, we use system properties to configure parameters for services in the examples that follow. Moreover, ParameterService cannot perform this duty until permission-based security is explained.

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

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