Chapter 23. Advanced Topics

Throughout the book we have talked about the essential parts of the OSGi model and the Equinox implementation. That discussion covers 90 percent of what you need to know to build a comprehensive OSGi-based system. This chapter is about the remaining 10 percent of core concepts that crop up infrequently but in key places.

You should think of this chapter as reference material and use it as needed. We cover many advanced topics and explain exactly what your application does from start to finish—the kind of information you need when you have problems and are up late at night troubleshooting. Of course, you are free to read through the chapter and pick up background information and helpful tips and tricks that can be applied every day. In particular, it is useful for people who are

• Curious about how the bundle constructs relate to one another

• Troubleshooting their application, for example, tracking down ClassNotFoundExceptions

• Designing a set of bundles and fragments

• Looking to understand more about how Equinox starts, runs, and stops

It is worth pointing out here that the OSGi framework specification is just that, a specification for a framework. The framework is intended to be implemented and run on a wide range of platforms and environments. As such, it does not say anything about, for example, how bundles are installed, how they are started, how they are laid out on disk, or even if they are laid out on disk. It is up to implementations to define these characteristics.

Much of this chapter is devoted to mapping the OSGi specification onto the Equinox use case. Readers are encouraged to read the current OSGi Framework Specification from http://osgi.org and treat this chapter as a guide to some advanced detail and the Equinox implementation and use of that specification.

23.1 The Equinox Console

It is quite curious in this day and age of GUIs, web applications, and the like that a simple command-line UI such as the textual Equinox console should be considered a merit. When people first see that there is a console under the covers of the system they are running, the geek in them comes out and they succumb to the need to manually install, start, stop, and uninstall bundles.

This is good fun, but the console is actually quite powerful and useful for both controlling and introspecting a running system. In addition to controlling bundles, you can investigate specific bundles, diagnose problems with bundles not being resolved, find various contributed services, and so on. Figure 23-1 shows an example of the console and a number of bundles in different states.

Figure 23-1 Sample console output

image

In Equinox the console is not started by default. To get a console, start with the -console command-line argument and look for the console’s osgi> prompt in either the shell you used to launch Equinox or the new shell created if you launched from a desktop icon. Typing help displays a complete list of available commands.

Of course, the console is extensible, so you can add your own commands. A number of particularly useful built-in commands are listed here:

ssDisplays a short status report of the known bundles, showing their bundle ID and current state. Bundles in the INSTALLED state are missing prerequisites. Use the diag command to find out more.

servicesDisplays the list of registered services that match the supplied Lightweight Directory Access Protocol (LDAP) filter. For example, to find all registered ICrustShell services in the client, type services (objectclass=*Shell).

diagDisplays a diagnosis of why a bundle has failed to resolve. Passes as a parameter either the bundle ID or the bundle symbolic name of the bundle of interest. Figure 23-2 shows an example of this.

Figure 23-2 Console diag output

image

23.1.1 Extending the Equinox Console

The Equinox console can be extended by registering an instance of the org.eclipse.osgi.framework.console.CommandProvider service. This interface defines just one method, getHelp, that returns an appropriately formatted String describing the commands it contributes to the console.

Each command that the CommandProvider contributes to the console must be implemented as a method with the following signature:

public void _<name>(CommandInterpreter interpreter);

The <name> portion of the method signature is the name of the command that the user enters at the console’s osgi> prompt. Implementing a CommandProvider is straightforward. Here’s how you might implement a listBundles command that simply lists, in alphabetical order, the names of the installed bundles:

image

Notice that by defining the method _lb we get the command lb as an alias for the listBundles command. This CommandProvider is intended to be registered as a service by DS, for which a service component XML document is required:

image

Launching Equinox with this component installed allows the new command to be used as follows:

image

Also, the help command displays the usage for our new listBundles command:

image

When the user enters a command at the osgi> prompt, the console interprets it by querying the registered CommandProvider services, which it sorts in descending order by their service.ranking property and then in ascending order by their service.id property. Once the providers have been sorted, the console searches for one that can handle the command.

The console uses Java reflection to find a provider that has a method that matches the name of the command entered by the user. While this scheme allows a command to be handled by a single provider, it is often possible for a bundle to override another provider by registering a CommandProvider service with a higher service.ranking property value. The service.ranking property must be of type Integer with a value up to Integer.MAX_VALUE (2147483647), for example,

<property name="service.ranking" type="Integer" value="10"/>

To display all the registered CommandProvider services, and to see their service.ranking properties, enter the following console command. Note that there must be no spaces within the parentheses.

osgi>services (objectClass=*CommandProvider)

The Equinox framework’s System Bundle, org.eclipse.osgi, registers two CommandProvider services that contribute the core set of console commands, such as help, ss, and diag. These services have service.ranking property values of 2147483647 and, since the System Bundle is always installed first, will always have some of the lowest service.id property values in the registry. This is a clever trick that guarantees that the commands contributed by the System Bundle cannot be overridden.

23.2 Roles in OSGi

Throughout the Toast example we saw elements of the OSGi API—BundleActivator and BundleContext, for example—showing up. Actually, it is a testament to our POJO and dependency injection approach that these classes have seldom surfaced. Here we look at the various roles at play under the OSGi module system:

Identity to others—A module needs some representation in the system so that it can be started, stopped, and otherwise accessed.

Context of the system—Modules need a means of interacting with the system from their point of view so, for example, they can find the services that they are allowed to see.

Lifecycle handler—Modules start and stop and must do initialization and cleanup.

Handy access point—Modules often need local globals—collections of constants, values, and functionality that are internal to, but useful across, the bundle.

OSGi separates these roles into different objects, as described here:

Bundle == identity to others—Other bundles can ask the system for a Bundle object, query its state (e.g., started, stopped, etc.), look up files using getEntries, and control it using start and stop. Developers do not implement Bundle—the OSGi framework supplies and manages Bundle objects for you.

BundleContext == context of the system—At various points in time, bundles need to ask the system to do something for them, for example, install another bundle or register a service. Typically, the system needs to know the identity of the requesting bundle, for example, to confirm permissions or attribute services. The BundleContext fills this role.

A BundleContext is created and managed by the system as an opaque token. You simply pass it back or ask it questions when needed. This is much like ServletContext and other container architectures.

A BundleContext is given to a bundle when started, that is, when the BundleActivator method start is called. This is the sole means of discovering the context. If the bundle code needs the context, its activator should cache the value.

BundleActivator == lifecycle handler—Some bundles need to initialize data structures or register listeners when they are started. Similarly, they need to clean up when they are stopped. Implementing a BundleActivator allows you to hook these start and stop events and do the required work.

OSGi does not have explicit support for the role of “handy access point” as outlined here. This role can be filled by any class.

Identity Theft

Of the three OSGi objects outlined here, only your Bundle object is meant for others to reference. That, in fact, is its role. Since your BundleContext is your identity to the system, you do not really want to hand it out to others and allow them to pretend to be you. Hold your context near and dear, and be careful not to share it via convenience methods or exposed fields.

Similarly, your BundleActivator controls your initialization state. Normally, it is invoked solely by the system. If you give others access to your activator, they can call its start and stop directly, rather than the corresponding Bundle methods, and circumvent any checks and management that the system does.

23.3 The Shape of Bundles

On disk, a bundle is a JAR containing a set of Java-related files. Figure 23-3 shows the example bundle you saw in Section 2.3, “The Anatomy of a Bundle.”

Figure 23-3 Standard JAR’d bundle layout

image

Figure 23-4, on the other hand, shows the bundle as a directory. Notice that everything is the same, except the code is in junit.jar rather than in the various org.* directories.

Figure 23-4 Directory bundle layout

image

These two forms are equivalent—Equinox and tooling such as PDE and p2 manage both forms equally well. All frameworks support JAR’d bundles, and several support directory-based bundles.

So what’s the difference and why choose one over the other? Following are some useful tips to help you decide which format is better for your bundles:

Use JARs

• Ninety-five percent of the time people use JARs. It is the de facto standard form of bundles.

• JARs are useful if the bundle contains many small files that would otherwise fragment the disk and slow installation and searching.

• JARs are compressed, so sparse files take up much less space.

• Some systems, such as Java WebStart, require JARs.

• Standard code signing tools work only on JARs. The Eclipse JAR signer, however, handles both form and nested JARs and directories.

Use directories

• Use directories if the bundle contains many files that must be directly on the native file system, for example, shared libraries and program executables.

While the shape of a bundle is transparent to both the user and the developers coding to the API of the bundle, there are a few considerations to note for the developers of JAR’d bundles:

• JAR’d bundles should generally have a Bundle-Classpath of “.” or no classpath specification at all—this implies “.”. A “.” signifies that the JAR itself is the classpath entry since the JAR directly contains the code.

• It is technically possible for a JAR’d bundle to have nested JARs on the classpath. Such nested JARs are automatically extracted and cached by the OSGi framework at runtime. As noted earlier, this effectively duplicates the amount of disk space required for the bundle. More significantly, however, tools such as javac and PDE are unable to manage classpaths that include nested JARs. The net result is that while your bundle runs, it takes more space and may not work with your tooling.

• Similarly, Equinox is able to run JAR’d bundles containing code that is not at the root of the JAR, for example, code in a bin directory such as /bin/org/eclipse/.... Again, tooling is not generally set up for that structure. In particular, standard Java compilers recognize only package structures that are directly at the root of a JAR. As such, developers may not be able to code against JARs structured in this way.

• The PDE export operations automatically JAR bundles that have “.” on the classpath and create directory structures for those that do not.

23.4 Fragments

Sometimes it is not possible to package a bundle as one unit. There are three common scenarios where this occurs:

Platform-specific content—Some bundles need different implementations on different OSs or window systems. You could package the code for all platforms in the bundle, but this is bulky and cumbersome to manage when you want to add another platform. Splitting the bundle into one for common code and others for platform-specific code is another possibility. This is problematic since implementation objects often need to access one another’s package-visible members. This is not possible across bundle boundaries.

Locale-specific content—Bundles often need locale-specific text messages, icons, and other resources. Again, it is possible to package the required resources for all locales together in the bundle, but this is similarly cumbersome and wasteful. It would be better to package locale content separately and deploy only what is needed.

Testing—White- and gray-box testing require access to the internals and implementation of the object under test. Here the strong isolation and information hiding of OSGi present a problem.

OSGi supports these use cases using fragments. Fragments are just like regular bundles except their content is seamlessly merged at runtime with a host bundle rather than being stand-alone. A fragment’s classpath elements are appended to its host’s classpath, and contributions to the service registry or Extension Registry are made in the name of the host. You cannot express dependencies on fragments, just their hosts. Fragments also cannot contribute new Service-Component manifest entries on behalf of the host.

The next two snippets show examples of both platform- and locale-specific fragments. Both fragments have a manifest file that identifies the fragment, its version, and its host ID and version range. Here is an example of the markup found in the translation fragment for the Equinox common bundle:

image

The next snippet is from the Windows SWT fragment’s manifest file. Notice the highlighted platform filter line. This is an Equinox-specific header that identifies the set of environmental conditions that must be met for this bundle to be resolved. In this case, the osgi.os, osgi.ws, and osgi.arch system properties must match the given values. The syntax of the filter is that of standard LDAP filters and is detailed in the OSGi specification.

image

In both cases, the fragments directly contain code and resources that are appended to the host’s classpath. Adding a fragment’s classpath and artifact entries to the host is a vital characteristic of fragments. Fragments generally contain implementation detail for the host. Whether it is code or messages, the contents need to be accessed as though they were part of the host. The only way to do this is to put the fragment on the host’s class loader. This gives bidirectional access to the classes and resources as well as enables Java package-level visibility. At runtime, the host and fragment behave as if they were a single bundle.

Fragments are a powerful mechanism, but they are not for every use case. There are several characteristics to consider when you are looking at using fragments:

Fragments are additive—Fragments can only add to their host; they cannot override content found in the host. For example, their classpath contributions are appended to those of the host, so if the host has a class or resource, all others are ignored. Their files and resources are similarly added to those of the host bundle.

Multiple fragments can be added—A host can have attached multiple fragments. While the order of the attachment and the fragment contributions to the host’s classpath is deterministic, it cannot be controlled. As such, fragments should not have conflicting content.

Fragments cannot be prerequisites—Since they represent implementation detail, their existence should be transparent to other bundles. As such, bundles cannot depend on fragments.

Fragments are not intended to add API—Since you cannot depend on a fragment, they should not expose additional API, since bundle writers are not able to express a dependency on that API.

Fragments can add exports—Normally, fragments are used to supply internal implementation detail. In certain instances, however, such as the previous SWT example and for testing and monitoring, fragments may need to extend the set of packages exported by the host. They do this using normal Export-Package syntax. To support development-time visibility, the host bundle should be marked with the header Eclipse-ExtensibleAPI: true. This tells PDE to expose the additional fragment exports to bundles that depend on the host.

Figure 23-5 shows the OSGi console of a running Toast Client after the short status (ss) command is typed. Notice that the org.eclipse.swt.win32 fragment (number 25) is shown as installed (and resolved) and bound to the SWT host bundle (number 35).

Figure 23-5 Fragment resolution status

image

23.5 Singletons

In general, OSGi is able to concurrently run multiple versions of the same bundle; that is, org.equinoxosgi.toast.core versions 1.0 and 2.0 can both be installed and running at the same time. This is part of the power of the component model. Dependent bundles are bound to particular versions of their prerequisites and see only the classes supplied by them.

There are cases, however, when there really should be only one version of a given bundle in the system. For example, SWT makes certain assumptions about its control over the display and main thread—SWT cannot cohabitate with other SWTs. More generally, this occurs wherever one bundle expects to have exclusive access to a global resource, whether it be a thread, an OS resource, a Transmission Control Protocol (TCP) port, or the Extension Registry namespace.

To address this, OSGi allows a bundle to be declared as a singleton. We saw this in Section 16.4, “Advanced Extension Topics.” The bundle in the following example is marked as a singleton. This tells OSGi to resolve at most one version of the bundle. All other version constraints in the system are then matched against the chosen singleton version.

image

The most common reason to mark a bundle as a singleton is that it declares extensions or extension points. The Extension Registry namespace is a shared resource that is populated by bundle IDs. If we allowed multiple versions of the same bundle to contribute to the registry, interconnections would be ambiguous.

23.6 Bundle Lifecycle

OSGi’s dynamic capabilities are one of its major selling points. The cornerstone of this is the bundle lifecycle. There are a number of players and events in this lifecycle story. This and some subsequent sections detail the elements and flow of the OSGi lifecycle model.

23.6.1 Lifecycle States

Bundles go through a number of states in their lives. In Section 2.6, “Lifecycle,” we introduced the various states and transitions. A deeper understanding of these helps you know when and why various things happen to your bundle and what you can do about them. Section 21.4.2, “Bundle Listeners,” details how to monitor any events arising from bundles changing state.

Deployed—When a bundle is deployed, it is laid down on disk and is physically available. This state is not formally represented in the system, but it is used to talk about a bundle that could become installed.

Installed—An installed bundle is one that has been deployed and presented to the OSGi framework as a candidate for execution. Installed bundles do not yet have a class loader, and their service and Extension Registry contributions are not yet processed.

Resolved—Resolved bundles have been installed and all of their prerequisites have been satisfied by other bundles, which are also resolved. If the Equinox Extension Registry is installed and active, the contributions of resolved bundles are added to the Extension Registry. Resolved bundles may have a class loader and may be fully operational. As shown in Figure 23-6, some bundles can stay in the resolved state and never become started.

Figure 23-6 Bundle state transitions

image

Starting—A resolved bundle that is started and whose start level has been met transitions to the active state through the starting state. Bundles remain in the starting state while their activator’s start method is executing. Bundles that use DS and the lazy activation policy may remain in this state for some time.

Active—An active bundle is one that has been resolved and whose start method has been successfully run. Active bundles have a class loader and are fully operational.

Stopping—An active bundle that is transitioning back to the resolved state passes through the stopping state. Bundles remain in the stopping state while their activator’s stop method is executing.

Uninstalled—An uninstalled bundle is a bundle that was previously in the installed state but has since been uninstalled. Such bundles are still present in the system but may behave in unexpected ways.

Figure 23-6 shows the details of the state transitions for bundles. Notice that the deployed state is not shown, as it is outside the scope of OSGi itself. Being deployed simply means that the bundle is available to be installed.

The transition between each state is marked by the broadcasting of a BundleEvent to registered BundleListeners. The event identifies the transition or type and the bundle affected. The use of these events, BundleListeners, and BundleTrackers is discussed in Chapter 21, “Dynamic Best Practices.”

23.6.2 BundleActivator

In support of the starting and stopping lifecycle transitions, a bundle can supply an activator class. Activators are instantiated by the OSGi framework, and their start and stop methods are called when a bundle is started or stopped.

If you define an activator class, you need to tell the framework about it. Use the Activator field in the General Information section on the bundle editor’s Overview page, or directly edit the manifest. Clicking the Activator link opens the identified class or the New Java Class wizard if no class is specified. In the end, this entry corresponds to the following entry in the bundle’s manifest:

Bundle-Activator: org.equinoxosgi.crust.internal.display.bundle.Activator

23.6.3 The Downside of Activators

Having given details and examples of how to write and identify activators, we caution you against using them. Running code on activation is an overused capability. One of the basic tenets of a large system is that laziness is good—“Run no code before its time.” In our experience, the start and stop methods rarely need to be implemented.

Bundles can be activated for many different reasons in many different contexts. We commonly see bundles that load and initialize models, open and verify caches, and do all manner of other heavyweight operations in their start methods. The cost of this is added to the startup time and footprint of your application and is not always justified.

We once found a bundle that was loading 11MB of code as a side effect of being activated. First of all, that’s a lot of code. More critically, however, there were several cases where activation occurred as a result of some trivial processing of some optional UI decorations. This long chain of events caused various bundles to assume that their full function was needed and that they should initialize their data model and do lots of hard work. A better approach is to initialize your caches and data structures as required with more precision.

23.6.4 Uses for Activators

With the advent of Declarative Services components, the need for bundle activators has diminished significantly. Throughout the 100 or so bundles involved in Toast, only a handful have activators. So, if activators are bad and not really needed, why do they exist and when should they be used? Traditionally activators were used to register services, start threads, and the like. These tasks are now typically done using DS component classes. See Section 15.2.1, “The Simplest Component,” for more details.

In fact, Toast has only two activators: one in Crust and one in the auto-starter. The Crust activator is required solely to capture the org.equinoxosgi.crust.display bundle’s BundleContext for use in registering the ICrustDisplay service. This service must be created by the application in the display bundle to ensure that it is on the right thread, so DS cannot be allowed to instantiate the service.

The auto-starter hooks a BundleTracker in its activator and then allows the system to run its code whenever bundles change—no services are involved so DS cannot help.

23.7 Bundle Activation Policy

The previous section showed how to control when a bundle’s start method is called. Traditionally the activator for a started bundle was called as soon as the bundle became RESOLVED. In more recent versions of the specification, each bundle is able to declare an activation policy that dictates how and when its activator is called.

Currently the only activation policy defined by the specification is lazy:

Bundle-ActivationPolicy: lazy

The lazy policy works by deferring the call to BundleActivator.start until the first time a bundle is asked to supply a class. A lazy bundle simply exists in the system—it has a BundleContext but does not have a class loader and does not execute any code. When the bundle is asked to load a class, its class loader is created, the bundle’s activator is instantiated, and its start method called. This is all done before attempting to load the initially requested class. After a successful start call, the requested class is loaded and returned as normal.

Given that lazily started bundles have a BundleContext, they are able to register services. More accurately, some other bundle is able to register services on their behalf. DS does exactly that—it registers service references on behalf of the lazy bundle. When the service is eventually discovered, the bundle is activated and the service object created.

The net effect is that you can always be sure that your bundle has been activated by the time its code is running (except, of course, the code involved in evaluating the activator’s start). This frees you from continually having to check the activation state of your bundle. It also means that the system as a whole can be lazier. There is no need for a central management agent or complicated policy to determine when bundles should be activated—they are simply activated as needed.

It’s worth highlighting some of the typical scenarios that do, and do not, cause activation:

• Using IConfigurationElement.createExecutableExtension does cause the bundle supplying the specified class to be activated. Note the subtlety here. It is not the bundle defining the extension but rather the one defining the class specified in the extension. Typically, these are the same but not always.

• Calling Bundle.loadClass does cause activation of the bundle that eventually supplies the requested class. Again, note the subtlety. If, for example, Bundle A asks Bundle B and B asks Bundle C and C eventually loads and returns the class, Bundle B is not activated. B was simply a step along the way.

• Loading a class from Bundle A that depends on a class from Bundle B does activate B. Here, the notion of depends on is derived from the Java language specification. If loading and verifying the class from A requires a class from B, B’s class is loaded and B is activated. This can occur if A’s class extends B’s or references B’s in a method signature.

• Accessing, traversing, or otherwise using a bundle’s extensions, data files, or entries does not cause activation.

• Bundle activation is not transitive; that is, activating a Bundle A that depends on another Bundle B does not in and of itself cause B to be activated. Of course, if classes are loaded from B while activating A, B is activated.

23.8 Controlling Bundle Start

Do not confuse bundle activation with starting bundles—they are related but different. BundleActivators and activation policies all talk about what happens if the framework activates a bundle. They do not control or change when that actually happens.

The distinction is subtle but important. Outside entities such as management agents or users call Bundle.start. Calling start indicates that the bundle can be activated if it is resolved and its activation policy has been satisfied. Calling start has the additional effect of causing a BundleContext to be created. The BundleContext is a key part of the bundle lifecycle, as it is needed when calling the bundle activator and when registering services.

The OSGi specification suggests that there is a management agent of some sort that is starting and stopping bundles according to system requirements. In many modern uses of OSGi this is not the case, and knowing and managing which bundles need to be started and when they should be started is a daunting task. The specification does not include any means of recording or maintaining this information. OSGi programmers then are left with three choices:

Start everything—This is the brute-force approach we saw with the auto-start bundle in Chapter 9, “Packaging.” The auto-start bundle simply starts all bundles as they are installed. This is simple and clear and works for small systems that do not have special requirements. For larger systems, however, this incurs a significant overhead at startup time and may bloat the system.

Start specific bundles—Minimalists and people with specific requirements can manage a targeted list of bundles that need to be started in specific situations. Doing this can be brittle and error-prone but may make sense in some cases.

Use lazy activation—Lazy activation allows you to aggressively start all bundles, as with the auto-start bundle, but since the bundles are marked as lazy, they do not run or load any code until they are needed.

In the following sections we talk about mechanisms and techniques for implementing these approaches.

23.8.1 Persistent Starting

The fact that start has been called on a bundle is, by default, persisted such that subsequent restarts of the same framework configuration restore the same set of bundles and their started state. In this case, Bundle.start is only ever called once on a given bundle, though it may be activated many times, once for each start of the framework.

Recent versions of the OSGi specification added the ability to control this persistence—Bundle.start(int) allows a START_TRANSIENT flag to be specified. In this case the bundle is marked as started only for the current run of the framework. This is useful in systems with fluctuating sets of bundles that need to be started. Without this or some agent explicitly stopping bundles, the framework would slowly accumulate bundles marked as started and get slower and slower to start.

23.8.2 Enabling the Activation Policy

Along with the introduction of transient starting and activation policies came the START_ACTIVATION_POLICY flag. This flag is passed to Bundle.start(int) and controls whether or not the activation policy, if any, defined in the target bundle is considered. The default Bundle.start() method does not consider the activation policy and persists the started marking for the bundle—this is the legacy behavior of the method. So if you would like to accommodate lazy bundles, you should use Bundle.start(START_ACTIVATION_POLICY).

23.8.3 osgi.bundles

Equinox pulls together these various elements with a built-in mechanism for driving the lazy activation approach previously outlined. Rather than having to have an external management agent, Equinox includes a simple facility for specifying a set of bundles to start when the framework is started—the osgi.bundles list.

The osgi.bundles property is a comma-separated list of bundles that are automatically installed and optionally started when Equinox is run. It is maintained as a system property set either as a –D VM argument or in Equinox’s config.ini file, discussed in Section 23.10.1, “config.ini.” You have been unwittingly manipulating these entries in the PDE product editor and launch configurations where various bundles were marked as auto-start and their start level was set. Tweaking those values results in changes to the config.ini used by Equinox. This is a powerful hybrid approach used by most Equinox systems where users are managing their own systems and have no insight into the requirements of the bundles they install.

An example osgi.bundles entry is shown here:

config.ini
osgi.bundles=org.equinoxosgi.core.toast@2:start

Each entry is of the following form:

<URL | simple bundle location>[@ [<startlevel>] [:start]]

Simple bundle locations are interpreted as relative to the OSGi framework’s parent directory. URLs must be of the form file: or the Equinox-specific platform:/base/. In general, the URLs may include a version number (e.g., .../location_1.2.3). If a version is not specified, the system binds to the location that matches exactly or to the versioned location with the latest version number. If a version number is given, only exact matches are considered.

The :start option indicates that Equinox should start the given bundle after it is installed. The framework starts such bundles persistently and with the START_ACTIVATION_POLICY discussed previously.

In the example osgi.bundles property, the startlevel value indicates the OSGi start level at which the bundle should run. Start levels are discussed in more detail in Section 21.7.1, “Start Levels.”

23.9 Class Loading

One of the things that sets OSGi apart from other systems is the modularity mechanism. The core of this is the class loading strategy. The following is a deep dive into the guts of OSGi class loading and some Equinox extensions. This section is not for everyone, nor is it for the faint of heart. It is included here for those poor lost souls who, for whatever reason, cannot seem to find the classes they need or are finding classes they don’t need.

Traditionally, Java developers put all their code on the classpath and forget about the real dependencies in their systems. This does not make for modular systems. In OSGi, each bundle has its own class loader. This effectively partitions the class namespaces and enables API boundary enforcement, bundle unloading, bundle activation lifecycles, and multiple versions of the same classes being loaded concurrently.

By default, a bundle’s class loader is parented by the standard Java boot class loader. This can be changed globally using the org.osgi.framework.bundle.parent property. The parent can be one of the following:

bootThe default, the standard Java boot class loader

extThe normal Java extension class loader

appThe Java application class loader

frameworkThe framework’s class loader

23.9.1 Class Lookup Algorithm

Enumerated in the following pseudo code are the steps OSGi uses for deciding where to look for classes. Here, we assume that a bundle is trying to load some class C in package P.

image

The next few sections look more closely at how this algorithm works.

23.9.2 Declaring Imports and Exports

The basic premise of the OSGi component model is that all bundles explicitly declare the packages they expose to others and the packages they require from others. Notice that the algorithm outlined in the preceding section does not “search” for class C; the system knows which bundles have which packages, so the exporter is simply looked up. This yields two main benefits:

• When bundle dependencies are explicitly declared, bundles are easier to configure and creating valid configurations is easier. For every import, there must be an export, or the bundle does not resolve.

• After a bundle dependency graph has been resolved, the system knows exactly where to look for any given package. This eliminates costly searching and greatly improves class loading time for large systems.

Step 1 of the class loading algorithm ensures that all java.* packages are implicitly available to all bundles—they need not be explicitly imported. All other packages from the JRE must, however, be explicitly imported. This implies that there is a matching exporter. The OSGi specification states that the System Bundle must export the additional packages from the JRE.

To implement this, the Equinox implementation maintains a set of profiles that lists the standard API packages available in common JRE class libraries such as J2SE1.4, J2SE1.5, and JCL Foundation. These profiles do not include implementation-specific packages such as com.sun.* and sun.*, as they are not standard and are not available in all JREs. The framework automatically finds and uses the profile that matches the current JRE level. You can override this and set your own profile using the osgi.java.profile property.

So, for example, a bundle using the org.xml.sax.Parser class must either import the org.xml.sax package or specifically require the system.bundle. If it does not, the SAX (Simple API for XML) classes are hidden from the bundle. Hiding JRE classes can be useful, for example, if you want to use a particular SAX parser supplied as part of your bundle.

23.9.3 Importing versus Requiring

There is a subtle but important distinction between importing packages and requiring bundles. Imports are undirected in that any suitable bundle exporting the package can be nominated to satisfy the import. This increases flexibility as it separates implementation and API. Typically, you import only API packages and are thus oblivious to what is supplying the implementation—implementations can be replaced without your notice.

Requiring bundles, on the other hand, states that the dependent bundle consumes the packages exported by the specific prerequisite bundle; that is, the consumer is bound to the supplier and its implementation. This is less flexible but is also simpler and more deterministic, as the consumer knows exactly which implementation it is getting.

As you can see, there are benefits and drawbacks to both. Developers from the OSGi world traditionally use only imports, whereas Eclipse developers tend to require bundles—a legacy of the original technologies. As a best practice we recommend importing packages because it encourages looser coupling. As a pragmatic note, using import package without version qualification is, in our opinion, worse than requiring bundles with versions. As there are typically many packages, managing their version numbers can be a daunting task. Some people new to OSGi may find it easier to start by requiring bundles and move to importing packages as their need for flexibility increases.

Either way, the PDE bundle editor allows you to pick how you specify your dependencies. Dependencies are defined using the Imported Packages and Required Bundles sections of the Dependencies page in the bundle editor.

23.9.4 Optionality

OSGi allows prerequisites to be optional. Optional prerequisites, whether imports or requires, do not prevent bundles from resolving. Rather, if the prerequisite element is available at resolution time, the dependent and prerequisite are wired together. Otherwise, the dependency is ignored and you must ensure that your code handles the potential class loading errors.

This property is controlled using the Properties... buttons on the Dependencies page in the bundle editor.

23.9.5 The uses Directive

It is common for API in one package to reference API in another package. For example, say some API method in a class in com.example.test returns a type from the package com.example.util. Bundles calling this method must express a dependency on both the com.example.test and com.example.util packages. Similarly, the bundle defining the test package must import the util package. To ensure class space consistency, the system must ensure that both bundles are wired to the same com.example.util package. If they are not, class cast exceptions will occur.

You can declare this relationship and requirement by specifying a uses directive on the export of com.example.test, as shown here:

Export-Package: com.example.test;uses:=com.example.util

This says that the test package uses the util package, so anyone using test and util should be wired to the exact same supplier of util. Specifying uses directives is an important expression of the complete API contract for a bundle.

23.9.6 Re-exporting

The OSGi dependency mechanism also supports the re-exporting of required bundles. Re-exporting is a structuring where one bundle exposes packages from a prerequisite as its own. For example, the Eclipse UI bundle requires and re-exports JFace and SWT bundles. As a result, UI-related bundles need only to specify a dependency on the UI bundle to get access to all of SWT and JFace.

You should consider using this only when the prerequisite classes somehow form an integral part of your bundle’s API. By re-exporting SWT, the UI bundle is effectively adopting the SWT API as its own; similarly for JFace. In a sense, the UI is acting as a façade or wrapper around these bundles. This hides the structuring details, allowing it to evolve over time.

This property is controlled using the Properties... button in the Required Bundles section of the Dependencies page in the bundle editor.

Note that a bundle can export a package it does not contain but rather gets elsewhere via an import or require statement.

23.9.7 x-internal and x-friends

There is a healthy tension between designing and defining durable API and enabling advanced exploration and experimentation. If you take a very strict API stance, your bundle should export only the packages that contain API. As we have seen, however, that approach means that other bundles can never see the non-API packages—under any circumstances. Depending on your needs and those of your consumers, this approach may be too restrictive in some situations.

Equinox offers two package export directives, x-internal and x-friends, that enable clear API guidance yet still give consumers the power to access internals as needed. We discussed the behavior and uses of these in Section 9.4.2, “Exporting Packages and Friendship.”

23.9.8 Boot Delegation

Step 1 of the class loading algorithm mentions the notion of boot delegation. This is an override mechanism that identifies particular packages whose class loads should come from the parent class loader. This is useful for accessing packages that are not java.* and are not otherwise exported by a bundle. For example, some code libraries reference JRE internals or must be on a certain JRE class loader. To get access to such packages, you can either update the JRE profile or use the org.osgi.framework.bootdelegation property in config.ini to list the set of accessible package prefixes as follows:

image

23.10 Configuring and Running Equinox

Equinox as a framework implementation is extremely flexible. The entire structure is built on a set of hooks and adapters that allow consumers to replace key operating elements, such as disk storage strategies, to better suit their needs. At a higher level there is a large number of command-line and system property settings that you can use to control many aspects of its operation. The Eclipse Help system at http://help.eclipse.org details these settings in the Runtime Options area under Platform Plug-in Developer Guide > Reference > Other reference information.

Some of these options must be specified on the command line and others can be specified as system properties. System properties can in turn be specified using –D VM arguments or by putting the settings in Equinox’s config.ini file.

23.10.1 config.ini

The config.ini file defines the set of system properties to use when running the framework. Typically this is used to specify a small set of bootstrap bundles to get the system going, the application to run, and a few configuration values. The file is formatted as a standard Java properties file and resides in the configuration area, typically the configuration folder in the Equinox install. At startup its key/value pairs are merged into Java’s system properties. By “merged” we mean that if a property with the given key already exists, the value in the config.ini is ignored.

Eclipse uses a number of system properties, and most command-line arguments (e.g., -data, -configuration) have system property equivalents. As such, they can be set in the config.ini file to control how the configuration behaves. As of the Galileo (3.5) release of PDE and Equinox, users generally do not have to edit, see, or touch the config.ini file. Most of the relevant settings can be made in the product or launch configuration editors. Nonetheless, the file is still used at runtime, so it is useful to know what it contains and how to read it. A typical config.ini looks something like this snippet:

image

Let’s look at these in order:

osgi.bundlesWe have already seen the details of this in Section 23.8.3, “osgi.bundles.”

eclipse.productThis is the ID of the product extension to run. In the Toast Client we used the Crust display product as the base platform on top of which the client is built. Specifying that product here causes it to start on launch and begin showing the Toast application.

osgi.instance.areaA running Equinox system often needs to write either user data or internal bundle data. The instance area is one location where this information can be written. In the example here, the Toast Client is told to write all such data in the toast subdirectory of the user’s home directory. Section 23.11, “Data Areas,” contains more information on positioning the instance area.

When editing the config.ini file, it is important to keep the following points in mind:

• If the list spans multiple lines, follow each comma with a backslash, which is known as the continuation character.

• There must not be any whitespace between the continuation character and the end of the line. Trailing whitespace will effectively terminate the property’s value, resulting in a configuration that does not behave as expected.

• Starting the list of bundles with the continuation character, placing each bundle on a separate line, and indenting each line are not necessary, but these are best practices that make the list easier to read and maintain.

23.10.2 The Executable

Having a native OS launcher seems like a minor thing, but it is extremely powerful. The standard Equinox launcher does a number of important tasks in aid of getting your system running:

• It finds a JRE and runs it on the Java code contained in the org.eclipse.equinox.launcher bundle. This is the basic bootstrapping of Equinox.

• It simplifies the running of Equinox. You can just run the executable rather than having to figure out various VM command-line arguments or mess with batch files.

• It manages the splash screen. The splash screen is essentially a hint that provides feedback to users that they did indeed double-click on the program and something is happening. Having the executable display the splash screen means that it’s shown to the user as soon as possible.

• The executable is the interface with the OS and window system. It dictates ownership and permissions as well as how Equinox is presented in the user’s desktop—for example, which icon is shown, the name of the process, and how it shows up in the taskbar or application dock.

The executable takes direction from an initialization file that allows you to define default sets of command-line arguments for both Java and Equinox and thus define how your system operates.

The executable looks for an initialization file of the same name as itself but with .ini appended. For example, the initialization file for toast.exe is toast.ini. The following file tells Toast to start the identified JVM using the given VM arguments:

image

The initialization file is essentially a standard command line that has been tokenized such that each token is placed on a line by itself. This syntax is a little strange, but it greatly simplifies the parsing required by the executable’s C code. Since the file represents a standard command line, order matters—the VM arguments must go last.

Since you can put any VM argument here, you can define system properties using the -D notation shown in the snippet. Properties set this way supersede those set using any other technique.

The Configuration page in the product editor has a Launching Arguments section that exposes both the Program Arguments and VM Arguments. An example of its use is shown in Figure 23-7. When the product is exported or launched, PDE creates an executable .ini file named and placed according to the product definition. Note that arguments containing spaces must be quoted accordingly.

Figure 23-7 Adding launching arguments

image

Typically, you should use this mechanism to set VM arguments or supply program command-line arguments that do not have system property equivalents. System properties should be set using the config.ini file described in Section 23.10.1, “config.ini.”

23.11 Data Areas

Applications often need to read or store data. Depending on the use case, this data may be stored in one of many locations. Consider preferences as an example.

Typical products use at least some preferences. The preferences themselves may or may not be defined in the product’s bundles. For example, if you are reusing bundles from different products, it is more convenient to manage the preferences outside the bundle.

In addition, applications often allow users to change preference values or use preferences to store refresh rates, port numbers, and so on. These values might be stored uniquely for each user or shared among users. In scenarios where applications operate on distinct datasets, some of the preferences may even relate to the particular data and should be stored or associated with that data.

Preferences are just one example, but they illustrate the various scopes and lifecycles that applications have for the data they read and write. Equinox defines four data areas that capture these characteristics and allow application writers to properly control the scope of their data:

Install—The install area is where Equinox itself is installed. The install area is generally read-only. The data in the install area is available to all instances of all configurations of Equinox running on the install. See the osgi.install.area system property.

Configuration—The configuration area is where the running configuration of Equinox is defined. Configuration areas are generally writable. The data in a configuration area is available to all instances of the configuration. See the osgi.configuration.area system property.

Instance—The instance area is the default location for user-defined data (e.g., a workspace). The instance area is typically writable. Applications may allow multiple sessions to have concurrent access to the instance area but must take care to prevent lost updates and related problems. See the osgi.instance.area system property.

User—The user area is where Equinox manages data specific to a user but independent of the configuration or instance. The user area is typically based on the Java user.home system property and the initial value of the osgi.user.area system property. See osgi.user.area system property.

In addition to these Equinox-wide areas, OSGi defines a location specifically for each installed bundle:

Data location—This is a location within the configuration’s metadata. See BundleContext.getDataFile.

Each of the Equinox locations is controlled by setting the system properties described before Equinox starts (e.g., in the config.ini). Locations are URLs. For simplicity, file paths are also accepted and automatically converted to file: URLs. For better control and convenience, there are also a number of predefined symbolic locations that can be used. Note that not all combinations of location type and symbolic value are valid. Table 23-1 details which combinations are possible.

Table 23-1 Location Compatibilities

image

@noneIndicates that the corresponding location should never be set either explicitly or to its default value. For example, an OSGi application that has no instance data may use osgi.instance.area=@none to prevent extraneous files being written to disk. @none must not be followed by any path segments.

@noDefaultForces a location to be undefined or explicitly defined (i.e., Equinox does not automatically compute a default value). This is useful when you want to allow for data in the corresponding location, but the Equinox default value is not appropriate. @noDefault must not be followed by any path segments.

@user.homeDirects Equinox to compute a location value relative to the user’s home directory. @user.home can be followed by path segments. In all cases, the string @user.home is replaced with the value of the Java user.home system property. For example, setting

[email protected]/toast

results in a value of

file:/users/fred/toast

@user.dirDirects Equinox to compute a location value relative to the current working directory. @user.dir can be followed by path segments. In all cases, the string @user.dir is replaced with the value of the Java user.dir system property. For example, setting

[email protected]/ABC123

results in a value of

file:/usr/local/toast/ABC123

Since the default case is for all locations to be set, valid, and writable, some bundles may fail in other setups, even if they are listed as possible. For example, it is unreasonable to expect a bundle focused on instance data to do much if the instance area is not defined. It is up to bundle developers to choose the setups they support and design their functions accordingly.

Note that each of the locations can be statically marked as read-only by setting the corresponding property osgi.AAA.area.readonly=true, where AAA is one of the area names.

23.12 Summary

The strength of OSGi lies in its robust bundle model. Bundles bring advantages of scale, composition, serviceability, and flexibility. The costs of this power are the rigor and attention to detail required when defining bundles—poorly defined bundles are hard to compose and reuse in the same way as poorly defined objects.

This chapter exposed the essential details of the OSGi component model and the Equinox implementation of the OSGi specification. We touched on some of the framework’s configuration options and provided a number of guidelines for building your bundles.

With this information, you will design and implement better components that run more efficiently and have more class loading and composition options.

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

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