9.5. Administration

The OSGi specification provides APIs for getting various status information and initiating administrative actions. For example, you can find out about all the currently installed bundles, their IDs, states, and locations; you can get a rundown of a bundle's manifest headers; and you can install and start other bundles. The command console is built on these very same APIs, and it allows you to perform administrative tasks interactively. A bundle with sufficient permissions can also assume the same administrative role programmatically.

We do not attempt to address the general issue of management. That job deals with provisioning for and managing numerous gateways running the Java Embedded Server software over the Internet in a distributed fashion and must consider scalability and reliability requirements seriously. This is referred to as macro management. As a contrast, we discuss micro management—local administration of one gateway, and in particular, how to resolve a group of bundles automatically.

9.5.1. Resolving Bundles Dynamically

We have learned that for a bundle to be resolved, the packages it imports must be exported in the framework by some other bundles, and the exported packages must have compatible versions, meaning an exported package's version must be equal to or greater than the version required for importing that package. So far we have been keeping track of the package dependency manually: Before we start admin.jar, for example, we first install and start servlet.jar, http.jar, and param.jar in the console, knowing that the administrative bundle depends on the parameter bundle for accessing the parameter storage area and the HTTP bundle for registering the servlet that presents the Web user interface. The HTTP bundle in turn relies on the servlet bundle for the Java Servlet library classes. These relationships form a treelike graph, as shown in Figure 9.7. Won't it be convenient to automate this process? That is, when a bundle is installed, other bundles that it needs are automatically found, installed, and activated, so that the bundle in question can be resolved successfully.

Figure 9.7. The package dependency tree for the administrative bundle


To begin, installing and activating the needed bundles cannot be done by the bundle itself, because it cannot be started, let alone get anything done, without them in the first place. A separate facilitator must act as the go-between, which provides the following functionalities:

  • Encapsulating the knowledge of interbundle package dependency relationships.

  • Resolving bundles recursively. If bundle A depends on bundle B, we must examine whether B further needs bundle C, C needs D, and so on. In computer science jargon, we need to resolve the transitive closure of bundles starting from A.

  • If the resolution process fails at some point, reporting the package whose exporter either is not found or exports it at an incompatible version, as well as the unsatisfied bundle that is in need of the package.

Package dependency can be expressed as a map, as shown in Table 9.2. It basically answers the following question: Given a package, what is the location of the bundle that exports the package and at what version? We call this relation the package exporter map.

Table 9.2. Package Exporter Map
Package Version Location of the Exporting Bundle
org.osgi.service.http 1.0 file:jes_path/bundles/http.jar
javax.servlet 2.1.1 file:jes_path/bundles/servlet.jar
javax.servlet.http 2.1.1 file:jes_path/bundles/servlet.jar
org.osgi.service.log 1.0 file:jes_path/bundles/log.jar
com.acme.service.param 0.0 file:/home/joe/bundles/param.jar

The map is implemented as a property file. The property name is the package name, the property value contains the location, a comma, and the version. The version can be omitted, in which case, it is assumed to be zero. For example, the first row in Table 9.2 corresponds to this entry in the property file:

org.osgi.service.http=file:jes_path/bundles/http.jar, 1.0

Next, the facilitator is designed to carry out the following resolution algorithm:

1.
Install the bundle whose location is given. Examine its Import-Package header by calling the getHeaders method of the Bundle interface. Parse the header and obtain a set of package names.

2.
Consult the package exporter map and find the locations of the bundles that export the packages. If the map yields nothing, or if the package version for export is incompatible with the one needed for import, the resolution fails.

3.
Install the newly found bundles.

4.
Repeat this process recursively for the bundles installed in step 3.

The termination condition for the recursion is as follows: At step 1, if a bundle's Import-Package header is empty, or the needed packages are available from CLASSPATH, then the bundle does not depend on another and is said to be a leaf in the dependency tree.

Before we look at the facilitator implementation, let's get a sense of how it is typically used. Let's assume we want to resolve admin.jar, whose location is given in url:

Facilitator f = new Facilitator(bundleContext);
if ( ! f.resolve(url)) {
   System.out.println("Failed to resolve bundle from " + url);
   Enumeration unsatisfiedBundles = f.getUnsatisfiedBundles();
   while (unsatisfiedBundles.hasMoreElements()) {
      Bundle b = (Bundle) unsatisfiedBundles.nextElement();
      String[] pkgs = f.getMissingPackages(b);
      System.out.println("Bundle (" + b.getLocation() + ")" +
         " needs the following missing packages:");
      for (int i=0; i<pkgs.length; i++)
         System.out.println("  " + pkgs[i]);
   }
}
f.reset(); // now f is ready to resolve another bundle

The getMissingPackages and getUnsatisfiedBundles methods return why and where the resolution fails. The following is the main part of the implementation. To highlight the resolution portion of the code, we omit the error-reporting methods. The complete listing is in Appendix A (section A.3.3.1):

class Facilitator {
   private BundleContext context;
   private Properties packageExporterMap;
   // Flag a visited bundle to prevent following circular dependency
   // during recursion.
   private Hashtable visited;

   Facilitator(BundleContext ctxt) throws IOException {
      this.context = ctxt;
      this.packageExporterMap = new Properties();
      String resourceName =
         "/com/acme/admin/resolv/package-exporters.map";
      InputStream in =
         this.getClass().getResourceAsStream(resourceName);
      packageExporterMap.load(in);
      this.visited = new Hashtable();
   }

   boolean resolve(String urlstr) throws BundleException {
      if (urlstr == null)
         return false;
      if (visited.get(urlstr) != null) {
         // if a bundle is processed, don't run in circles
         return true;
      }
      Bundle b = context.installBundle(urlstr);
      // flag that the bundle has been processed
      visited.put(urlstr, Boolean.TRUE);
      Dictionary headers = b.getHeaders();
      String imports = (String) headers.get("Import-Package");
      boolean r = true;
      if (imports != null) {
         String[] loc = findExporters(imports, b);
         for (int i=0; i<loc.length; i++) {
            // use bitwise & to prevent short-circuit
            // behavior associated with logical &&
               r = r & resolve(loc[i]);
            }
      }
      if (r) {
            b.start();
      }
      return r;
   }

   private String[] findExporters(String imports, Bundle b) {
      StringTokenizer st = new StringTokenizer(imports, ",");
      Vector v = new Vector(st.countTokens());
      while (st.hasMoreTokens()) {  // enumerate each package
         String token = st.nextToken().trim();
         // separate package name and import version
         int semicolonPos = token.indexOf(';'),
            String pkgname = token;
            String importVersion = "";
            if (semicolonPos != -1) {
               pkgname = token.substring(0, semicolonPos).trim();
               int eqPos = token.indexOf('=', semicolonPos);
               importVersion = token.substring(eqPos + 1);
            }
         if (isFromClasspath(pkgname))
            continue;
         String loc = (String) packageExporterMap.get(pkgname);
         if (loc != null) {
            // separate bundle location and export version
            String exportVersion = "";
            int commaPos = loc.indexOf(','),
            if (commaPos != -1) {
                  exportVersion = loc.substring(commaPos + 1);
                  loc = loc.substring(0, commaPos);
            }
            if (! areVersionsCompatible(importVersion,
               exportVersion))
            {
               loc = null;
            }
         }
         if (loc == null) {
            // save current package 'pkgname'
            // to be returned by getMissingPackages method.
            ...
         }
         v.addElement(loc);
      }  // while
      String[] locations = new String[v.size()];
      v.copyInto(locations);
      // save all missing packages for the bundle 'b'
      ...

      return locations;
   }

   private boolean isFromClasspath(String pkg) {
      URL u = ClassLoader.getSystemResource(
         pkg.replace('.','/') + "/");
      return u != null;
   }

   private boolean areVersionsCompatible(String impVerStr,
      String expVerStr)
   {
      // The package version for export must be equal to or
      // greater than the package version needed for import
      ...
   }
}

In the constructor, the package exporter map is read from a static property file included in the facilitator bundle. The resolve method implements the resolution algorithm just presented. If the current bundle and all the bundles on which it depends have been resolved, then the bundle is started. Notice that resolved bundles are activated on return from recursion to ensure that child bundles in the dependency tree are activated before their parents and ancestors.

If bundle A needs to import packages from bundle B, which directly or indirectly also needs to import packages from bundle A, a circular dependency is formed. The resolution routine runs infinitely if this situation arises and is unchecked. The visited hash table is used to break the cycle by not repeating resolution of a bundle if it has been marked as having been processed.

Much of the findExporters method is responsible for parsing the Import-Package header for a set of package names, looking up the locations of exporting bundles from the package exporter map and ensuring that the package's export version is compatible with its import version.

The isFromClasspath method reports whether a package is available from CLASSPATH. If so, there is no need to locate an exporter for that package. ClassLoader.getSystemResource returns non-null URL for system classes on the CLASSPATH, but null for classes loaded from another bundle. This check is necessary because according to the OSGi specification, packages with names that do not begin with java. must be declared in the Import-Package header, even though they are available from CLASSPATH.

What is not shown is how to interact with the facilitator. In our example, we implement a multithread server so that we can connect to the Java Embedded Server software from another host over a TCP/IP channel, install a bundle, and resolve it remotely. We depart from using our favorite servlet solution, because the facilitator, which is charged with the job of resolving other bundles, should be self-sufficient to be effective. The complete source code is shown in Appendix A, section A.3.3.2.

First, let's install and activate the facilitator bundle, which starts a server listening on port 8082:

> start file:/home/joe/bundles/facilitator.jar
> bundles
ID  STATE   LOCATION
--  ------  ----------------------------
1   ACTIVE  file:/home/joe/bundles/facilitator.jar
>

Then start a session using the standard telnet command[1] as the client. Enter a location string for the bundle you'd like to resolve after the prompt. Note that the location string is referring to a location on the host where the Java Embedded Server software is running:

[1] To see your typing in Windows systems, turn on “Local Echo” of the telnet program from Menu Terminal → Preferences → Local Echo.

% telnet mybox 8082
Trying 129.144.175.65...
Connected to mybox.
Escape character is '^]'.

Bundle Resolution

Enter the bundle's URL: file:/home/joe/bundles/admin.jar

OK

Connection closed by foreign host.

From the console we can verify the result by issuing the bundles command:

> bundles
ID  STATE   LOCATION
--  ------  ----------------------------
1   ACTIVE  file:/home/joe/bundles/facilitator.jar
2   ACTIVE  file:/home/joe/bundles/admin.jar
3   ACTIVE  file:jes_path/bundles/http.jar
4   ACTIVE  file:jes_path/bundles/servlet.jar
5   ACTIVE  file:/home/joe/bundles/param.jar

Just as we expected, the facilitator automatically installed and activated the servlet.jar, param.jar, http.jar, and admin.jar in the right order so that every bundle is resolved and started successfully all at once.

The previous implementation attempts to resolve as many bundles as possible. For instance, suppose the dependency map has the following entry missing, meaning it does not know which bundle exports the com.acme.service.param package:

com.acme.service.param=file:jes_path/bundles/param.jar

The algorithm will install and activate servlet.jar and http.jar in addition to admin.jar before reporting the resolution failure. It allows us to activate all the bundles in a subtree, even though bundles below another branch have resolution problems.

When the resolution fails, a partial set of bundles is left installed or activated in the framework. This is fine because the installation and activation operations are both idempotent: If an installed bundle is to be installed, or an active bundle is to be activated a second time, the framework detects this situation and does nothing. However, if transactional behavior is desired, we can remember the set of installed and activated bundles and undo them by deactivating and uninstalling them if the resolution fails eventually. Another way is to examine the manifest headers directly from the source without installing the bundles at all until the complete set of bundles is resolved.

The package exporter map is at the heart of the resolution algorithm. However, its static nature is limiting for the usefulness of the solution. A tool can be developed to construct the map automatically for a given set of bundles. Assuming the locations of the bundles under inspection are saved in an array locations, the following code fragment shows how to do this:

import java.util.jar.*;
...

Properties packageExporterMap = new Properties();
for (int i=0; i<locations.length(); i++) {
   Attributes headers = null;
   try {
      URL bundleURL = new URL(locations[i]);
      InputStream in =
         bundleURL.openConnection().getInputStream();
      JarInputStream jis = new JarInputStream(in);
      // get the bundle's manifest
      Manifest manifest = jis.getManifest();
      // get the manifest headers
      Attributes headers = manifest.getMainAttributes();
   } catch (IOException e) {
   } finally {
      try { jis.close(); } catch (IOException e) {}
   }
   if (headers == null)
      continue;
   // get the value for the Export-Package header
   String exports = headers.getValue("Export-Package");
   // parse the header for package names and version spec
   StringTokenizer st = new StringTokenizer(exports, ",");
   while (st.hasMoreTokens()) {
      // enumerate each package for export
      String token = st.nextToken().trim();
      int semicolonPos = token.indexOf(';'),
      String pkgname = token;
      String exportVersion = null;
      if (semicolonPos != -1) {
         // parse the version spec
         pkgname = token.substring(0, semicolonPos).trim();
         int eqPos = token.indexOf('=', semiconlonPos);
         if (eqPos != -1) {
            exportVersion = token.substring(eqPos+1).trim();
         }
      }
      String val = locations[i];
      if (exportVersion != null)
         val += ", " + exportVersion;
      packageExporterMap.put(pkgname, val);
   }
}

We directly read and interpret the manifest of a bundle JAR file from its source URL using the classes in the java.util.jar package available since JDK 1.2.

An optimization can be done by partially applying the resolution logic to the previous process, so that we can determine whether any dependency cannot be satisfied early on. For example, we can additionally read and parse the Import-Package header of a bundle. If it imports certain packages not exported by any other bundles in the set, we already know this group of bundles will not resolve when they are deployed to a framework all by themselves.

Additional complications may occur because many bundles can declare to export the same package with the same version. Until the bundles are actually installed and resolved in the framework at run-time, there is no way of determining which bundle will be picked as the exporter. Moreover, because bundles can be started and stopped in various orders, a selected exporter may not be the winner the next time the framework is restarted and the same package is to be exported. The good news is that as long as at least one bundle exists and offers the needed package at the compatible version, the importers will be able to get resolved.

Let's look at this situation with a concrete example. The DA bundle device.jar imports the org.osgi.service.log package, which is offered by two bundles: the Log bundle and the HTTP bundle. The HTTP bundle does this to ensure it can always be resolved even when the Log bundle is absent. If we attempt to set up the dependency among these three bundles, two equally legal outcomes may emerge. The first version of the package exporter map is presented in Table 9.3.

Table 9.3. First Version of the Exporter Map among the Log, HTTP, and Device Bundles
Package Location of the Exporting Bundle
org.osgi.service.log file:jes_path/bundles/log.jar
org.osgi.service.http file:jes_path/bundles/http.jar
org.osgi.service.device file:jes_path/bundles/device.jar

If you start the Device bundle, the Log bundle is automatically started. This is usually what is expected. However, a second result is also possible (Table 9.4).

Table 9.4. Second Version of the Exporter Map among the Log, HTTP, and Device Bundles
Package Location of the Exporting Bundle
org.osgi.service.log file:jes_path/bundles/http.jar
org.osgi.service.http file:jes_path/bundles/http.jar
org.osgi.service.device file:jes_path/bundles/device.jar

The only difference from the first outcome is that the org.osgi.service.log package is shown to be exported by the HTTP bundle. As a result, if you start the device bundle, the HTTP bundle is automatically started. This is usually not what is intended, because the HTTP bundle does not provide the Log services. Yet as far as bundle resolution is concerned, the Device is resolved and can be started. It is then up to the Device bundle to handle the missing service situation using techniques from Chapter 5. In this case, it may choose simply to display log messages to the display until the Log bundle comes along some time later and registers its services.

9.5.2. Relevant APIs

Now let's summarize the manifest headers and APIs that allow you to provide and retrieve information about the bundles and services, which is essential to administrative applications.

9.5.2.1. Bundle Interface

The Bundle interface represents an installed bundle in the framework. The following APIs return various information about the bundle.

public Dictionary getHeaders()

We have grown familiar with the important manifest headers such as Bundle-Activator, Import-Package, Export-Package, Bundle-ClassPath, and Bundle-NativeCode. Here we look at a group of additional headers that serve informational and advisory purposes (Table 9.5).

Table 9.5. Informational Manifest Headers
Manifest Headers Meaning
Bundle-Name The name of a bundle
Bundle-Vendor The name of the bundle vendor
Bundle-Version The version of a bundle
Bundle-Description The description of a bundle
Bundle-DocURL The URL to a bundle's documentation
Bundle-ContactAddress The e-mail address to contact about a bundle
Export-Service The services that are to be registered by a bundle
Import-Service The services a bundle is to get

For example, we can add the following to the administrative bundle's manifest definition:

Bundle-Name: The Administrative Bundle
Bundle-Description: This bundle can access and modify parameters for
 other bundles. It requires org.osgi.framework.AdminPermission, and
 provides a web user interface at http://<host>:8080/admin.
Bundle-Vendor: Acme Systems, Inc.
Bundle-Version: 1.0
Bundle-DocURL: http://www.acme.com/bundles/admin/index.html
Bundle-ContactAddress: [email protected]
Import-Service: com.acme.service.param.ParameterAdmin

A management application can retrieve the information as follows, assuming bundle refers to the administrative bundle:

Dictionary headers = bundle.getHeaders();
System.out.println("Bundle: " + headers.get("Bundle-Name") +
   " " + headers.get("Bundle-Version"));
System.out.println("description: " +
      headers.get("Bundle-Description"));

Unlike Import-Package and Export-Package, the Import-Service and Export-Service manifest headers are advisory. Their presence simply indicates that at some point in time, the bundle needs to get or is going to register the specified service. Conceivably, a “resolution” process (similar to what we have just attempted with package dependency) can be implemented based on the Import-Service and Export-Service headers. However, even activating bundles following the order thus generated still has no guarantee that each bundle will register the service before another tries to get it. Therefore, the usefulness of these headers is quite limited, and they are no substitute for the techniques described in Chapter 5.

public long getBundleId()

This method returns the unique ID of the bundle installed in the framework.

public String getLocation()

This method returns the location string from which the bundle is installed. If the bundle has been installed directly from InputStream, the location identifier string passed into BundleContext's installBundle method is returned.

public int getState()

This method returns the current state of the bundle. It will be one of Bundle.INSTALLED, Bundle.UNINSTALLED, Bundle.RESOLVED, Bundle.STARTING, Bundle.STOPPING, and Bundle.ACTIVE. To check whether a bundle is in one of multiple states, use the following statement:

if (bundle.getState() &
      (Bundle.STARTING | Bundle.STOPPING | Bundle.ACTIVE) != 0)

public ServiceReference[] getRegisteredServices()

This method returns an array of service references of services registered by this bundle.

public ServiceReference[] getServicesInUse()

This method returns an array of service references of services that are being used by this bundle.

public boolean hasPermission(Object permission)

This method checks whether this bundle has the given permission. If the given permission is implied by the collection of permissions granted to the bundle, the method returns true.

9.5.2.2. BundleContext Interface
public Bundle getBundle()

If BundleContext is passed to a bundle's activator, calling getBundle returns the Bundle object representing the receiving bundle. Quite a mouthful for a simple operation. It is mostly used by a bundle to find out information about itself. For example,

public class Activator implements BundleActivator {
   public void start(BundleContext ctxt) {
      Bundle self = ctxt.getBundle();
      System.out.println("I was from " + self.getLocation() +
         " and assigned an ID of " + self.getBundleId());
      System.out.println("Am I being started? " +
         (self.getState() == Bundle.STARTING));
}
...

reports the following messages on bundle activation:

I was from file:/somewhere/foo.jar and assigned an ID of 5
Am I being started? true

public Bundle getBundle(long bundleID)

Get the installed bundle with the given bundleID. This is mostly used by an administrative bundle to perform a life cycle operation on another bundle or to obtain its location or state.

public Bundle[] getBundles()

This method gets all currently installed bundles in the framework.

public String getProperty(String name)

This method gets the value of an OSGi framework environment property. We covered the following standard properties in Chapter 4. They reflect various attributes of the underlying native platform:

  • org.osgi.framework.processor

  • org.osgi.framework.os.name

  • org.osgi.framework.os.version

  • org.osgi.framework.language

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

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