Understanding Eclipse's new bundle-management mechanism

Save time when testing RCP-based applications using Eclipse-LazyStart

Learn how to fill the gap between the IBM® Rational® Functional Tester and the console of Eclipse-based products by supporting the OSGi commands install, ss, start, stop, headers, active, update, and uninstall. The solution offers an effective approach for automation test-case support when the manifest of an Eclipse-AutoStart header has been upgraded to Eclipse-LazyStart. This article presents test scenarios to verify that the bundle-management mechanisms work well.

Xing Xing Li (lixx@cn.ibm.com), Software Engineer, IBM

Xing Xing LiXing Xing Li joined the IBM China Software Development Lab in Beijing China in 2004 as a software engineer for IBM Lotus. He focuses on software development and testing on Eclipse platform and Java technologies. He is also interested in user interface design, design pattern and algorithmic research. He received his master's degree in computer science from Chongqing University.



Yu Peng (pengyu@cn.ibm.com), Staff Software Engineer, IBM

Yu PengPeng Yu joined the IBM Lotus branch of the China Software Development Lab in Bejing in 2004 as a software engineer. He focuses on software testing on the Eclipse platform and Java technologies. He is also interested in user-interface design, design pattern and algorithmic research. He received his master's degree in computer science from NanKai University.



Jian Lin (jianlin@cn.ibm.com), Manager, IBM

  Jian LinJian Lin is the Manager of Pervasive Computing Device Software Development and Services, IBM China Software Development Lab. He can be reached at jianlin@cn.ibm.com.



13 May 2008

Also available in Chinese

Soon after our test team upgraded to Eclipse V3.2, we discovered that Eclipse no longer supported the AutoStart header in our test cases. The Eclipse Foundation had replaced AutoStart with LazyStart, an adoption of the lazy-activation policy in the OSGi R4.1 specification. A downside of this change is that we found it difficult to trigger the automated objective bundles in LazyStart. Legacy automation test cases need to expose a resource to be loaded by a trigger bundle. Since updating all of our legacy test cases would be clumsy and time-consuming, we decided to take a different approach.

One possibility was the Eclipse console, a powerful tool to manipulate a bundle's life-cycle management. However, Rational Functional Tester does not recognize the Eclipse console. Our solution was to design and implement the GUI Console.

A gap in the automated testing

Eclipse is a popular IDE for developing applications and, thanks to the Rich Client Platform (RCP), Eclipse is a runtime platform for a growing list of applications, including IBM Notes® Client, Sametime®, and Expeditor. (See Resources if you are new to Eclipse and need background information on Eclipse's capabilities.) During an automated test-case practice, however, testers confront a serious problem trying to leverage automation tools, such as IBM Rational Functional Tester, to develop automation test cases for Eclipse RCP-based products. (As for Rational Functional Tester's object-recognition details, download "Grabbing GUI objects with IBM Rational Functional Tester.")

IBM Rational Functional Tester can't recognize the console of Eclipse or Eclipse-based products. When automation testers try to grab the console object of Eclipse by IBM Rational Functional Tester, it fails to recognize the console. Consequently, automation testers can't continue with subsequent tasks. Figure 1 is a screenshot of IBM Rational Functional Tester failing to recognize the console of Eclipse. The expected action of the recognition of the console by Rational Functional Tester is a red rectangle around the console in the Figure 1. We would see something like this when Rational Functional Tester recognizes other widgets in Eclipse, such as the task window, menu bar, and combo list.

Figure 1. Inability of Rational Functional Tester to recognize the Eclipse console
Inability of Rational Functional Tester to recognize the console of Eclipse

Consequently, the Rational Functional Tester's failure to recognize the Eclipse console is fatal because automated tools need the console to perform the following two tasks.

Bundle operations
For obvious reasons, we need to check and track the status of target bundles in Eclipse RCP-based products. But without the console's help, automation testers must develop and deploy new manual tests. Testers must translate the old automated tests into manual test cases. The test scenarios must install, uninstall, start, stop, and target bundles in RCP applications. This can be accomplished with the console's support. Commonly, usage scenarios invoke these commands in the console, which offers a mechanism for retrieving echoed results.
Collect diagnostic information
When Eclipse runs into an error, it should print exceptions and errors to the console. Such information is critical in diagnosing the root cause of a problem. Developers always want testers to provide such info for each software problem report, but Rational Functional Tester fails to collect them during the automated test. It means the tester needs to rerun the test manually to collect this information. This is virtually impossible for long-running and stress tests.

To fix the gap, we proposed a solution we call the GUI Console.

Requirement identification

Bundle life-cycle management: AutoStart vs. LazyStart

In the MANIFEST.MF headers, AutoStart and LazyStart may be considered synonyms for the same bundle-manifest header, except that the former is deprecated and the latter is preferred. During the fixing of the 537 bugs in Eclipse V3.1.2, which brought us to Eclipse Europa, AutoStart fell to the wayside. Today, it's all about OSGi V3.2 compliance and LazyStart (see Resources to learn more about how a bundle carries descriptive information about itself in the MANIFEST.MF file).

In Eclipse V3.2, the LazyStart header defines whether a bundle will be started automatically before its class or if a resource is accessed by other bundles. By setting the value to true, we can activate a bundle lazily — in other words, automatically — the first time someone tries to load a class or resource.

As we described, even though it's logical to migrate all legacy test cases to LazyStart, doing so would be a huge, clumsy effort. Updating all test cases to support AutoStart would expose one resource to them and load them by a trigger bundle. Besides, starting a bundle does not cover all requirements of our automation test cases. Bundling state management — in other words "bundling life-cycle manipulating" — involves the verification points in our test cases.

Bundle life-cycle management: Bundle state

A bundle, including bundles in our automation test cases, can be in only one of the following six states at a time.

INSTALLED
A bundle can enter the INSTALLED state when it's successfully installed.
RESOLVED
A bundle can enter the RESOLVED state when its needed Java classes are available. In other words, this state indicates that the framework has successfully resolved the bundle's dependencies as described in the manifest. The RESOLVED state comes from the INSTALLED or ACTIVE state and leads to ACTIVE.
STARTING
A bundle can enter the STARTING state when it's being started (when the BundleActivator.start() method has been called, but has not yet returned).
ACTIVE
A bundle can enter the ACTIVE state when the bundle has been launched and is serving.
STOPPING
A bundle can enter the STOPPING state when the bundle is being stopped (when the BundleActivator.stop() method has been called, but has not yet returned).
UNINSTALLED
A bundle can enter the UNINSTALLED state when the bundle has been uninstalled. It cannot move into another state.

The Bundle interface defines a getState() method for returning a bundle's state.

Figure 2 demonstrates all states of a bundle in its life cycle, as well as its transition paths.

Figure 2. OSGi bundle states
OSGi bundle states

Retrieving manifest headers

The headers in MANIFEST.MF are another part of the GUI Console foundation. Eclipse expects bundle developers to provide descriptive information about a bundle in a manifest file named MANIFEST.MF. This is the OSGi file that defines manifest headers, such as Export-Package and Bundle-Classpath descriptions. Table 1 lists the most useful headers in an OSGi bundle.

Table 1. An OSGi bundle's headers
OSGi bundle headerDescription
Bundle-ActivatorSpecifies the name of the class used to start and stop the bundle.
Bundle-ClasspathSpecifies the JAR file or directories containing classes and resources. The period ('.'), the default value, specifies the root directory of the bundle's JAR.
Bundle-ContactAddressContains the contact address of the vendor.
Bundle-CopyrightContains the copyright specification for this bundle.
Bundle-DocURLSpecifies a URL pointing to documentation about this bundle.
Bundle-LocalizationSpecifies the location of the bundle's localization files, whose default value is OSGI-INF/l10n/bundle.
Bundle-ManifestVersionSpecifies that the bundle follows the rules of OSGi specification V3 or OSGi specification V4.
Bundle-NameSpecifies the bundle's readable name with no spaces.
Bundle-SymbolicNameA mandatory header to specify a unique name for this bundle.
Bundle-VendorContains a readable name of the bundle vendor.
Bundle-VersionSpecifies the bundle's version, whose default value is 0.0.0.
Export-PackageSpecifies exported packages from this bundle.
Fragment-HostDefines the host bundle for this fragment.
Import-PackageDeclares the imported packages for this bundle.
Require-BundleSpecifies the required exports from another bundle.
Import-ServiceDeprecated
Export-ServiceDeprecated

Requirement summary

Based on the above requirement analysis, the GUI Console solution is asked to provide the following commands to support OSGi bundle management for our automation test cases.

Table 2. OSGi commands supported by the GUI Console
CommandDescription
install+bundle URLAdds a bundle with the given URL into the current platform.
uninstall+bundle IDRemove a specified bundle from the current platform.
ssLists a short status of all bundles registered in the current platform.
start+bundle ID; start+bundle nameLaunches a bundle with the given bundle ID or symbolic name.
stop+bundle ID; stop+bundle nameTerminates a bundle with the given bundle ID or symbolic name.
headersLists manifest headers for a bundle given an ID or symbolic name.
activeLists all active bundles in the current platform.
updateUpdates a bundle for the current instance.

Design and implementation

In this section, we will show why the BundleContext object and the bundle object will be selected as the pivotal figures in our GUI Console solution, and we will discuss bundle management in OSGi.

The BundleContext

BundleContext is the bridge to connect the Eclipse framework and the installed bundles in it. A BundleContext object represents the execution context of a bundle within the OSGi platform and acts as a proxy to the underlying framework.

When a bundle is started, a BundleContext object will be created by the framework and provided as an argument to the start(BundleContext) method of the bundle's Bundle-Activator. The bundle can use this private BundleContext object for:

  • Installing new bundles into the OSGi environment.
  • Interrogating other installed bundles in the OSGi environment.
  • Obtaining a persistent storage area.
  • Retrieving service objects of registered services.
  • Registering services in the framework service.
  • Subscribing or unsubscribing to events broadcast by the framework.

Each bundle has its own BundleContext object, and they should not be passed between bundles. Why? The the BundleContext object is related to the security and resource-allocation aspects of a bundle. When the Bundle-Activator's stop(BundleContext) method is returned, the BundleContext object is out of service.

The most useful point of BundleContext is that it defines methods to retrieve information about bundles installed in the OSGi Service Platform:

getBundle()
Returns the single bundle object associated with the BundleContext object.
getBundles()
Returns an array of bundles currently installed in the framework.
getBundle(long)
Returns the bundle object specified by the unique identifier, or null if no matching bundle is found.

Because there are no restrictions for bundle access, any bundle can enumerate the set of installed bundles. This allows us to conduct and manipulate our automation test case's bundle life-cycle management operations and bundle information-retrieval operations. The GUI Console will retrieve all active bundle information using the code in Listing 1. It functions the same as the Eclipse console's active command.

Listing 1. Active command implementation code
public static void doActive() throws Exception {
    b = bContext.getBundles();
    for (int i = 0; i > b.length; i++) {
        if (b[i].getState() == b[i].ACTIVE) {
	result = result + b[i].getLocation()+" " + "["+b[i].getBundleId() +"]"
                 +System.getProperty ("line.separator");
		    } 
		}
		result=result+p+" active bundle(s).";
	}

As for the ever-popular ss command, the GUI Console can implement it based on the above code only if we accept all bundles with respective states, including INSTALLED, RESOLVED, and ACTIVE. See the sample code for details.

Bundle object

To manage a bundle's life cycle in the OSGi platform with Eclipse-based products, we must leverage an associated bundle object for our target bundle. The BundleContext interface provides the following methods for installing a bundle.

installBundle(String)
Installs a bundle from the specified location string (a URL).
installBundle(String,InputStream)
Installs a bundle from the specified InputStream object.

When a bundle is installed successfully, a bundle object will be generated for it, and all operations for life-cycle management must be performed with this object, such as start, stop, and uninstall.

As we see in Listing 2, we can install a bundle with its location string provided. If the bundle is installed successfully in the platform, it will return the symbolic name of the bundle.

Listing 2. Active command implementation
public static String doInstall(String location) throws Exception{
                ...
		Bundle iBundle = bContext.installBundle(location);
		if(iBundle!=null){
			iBundle.update();
			return iBundle.getSymbolicName();
		}
		return null;
	}

Starting bundles

The start() method is defined by the bundle interface to start a bundle and takes a bundle from the RESOLVED state to the ACTIVE state. The prerequisite is that the bundle must have been resolved successfully; otherwise, a BundleException will be thrown.

To execute a bundle's start() method, we need to inform the OSGi environment of the class name of the Bundle-Activator by the Bundle-Activator header in the bundle's manifest file. The OSGi environment will instantiate a new object of this class and cast it to a Bundle-Activator instance. Then it will call the BundleActivator.start() method to start the bundle.

As a Bundle-Activator, the class in the bundle must implement the Bundle-Activator interface, with it declared as public and a public default constructor. However, it's optional to provide a Bundle-Activator in each bundle. For example, a library bundle exporting a small number of packages does not need to define a Bundle-Activator.

Our GUI Console application invokes the following code to start a bundle when the bundle ID or symbolic name is provided.

Listing 3. Start command implementation
public static void doStart(int bID) throws Exception {
    if(bContext.getBundle(bID)!=null&&bContext.getBundle(bID).state==
    bContext.getBundle(bID).RESOLVED){
	bContext.getBundle(bID).start();
    }
}
public static void doStart(String s,String matchS) throws Exception {
    b = bContext.getBundles();
    ...
    for (int i = 0; i < b.length; i++) {
	if (b[i].getSymbolicName().indexOf(s) >-1 && (b[i].getState() == 
        b[i].RESOLVED) {
	    boolean isFragment=false;
	    Enumeration eKey = b[i].getHeaders().keys();
	    Dictionary dValue = b[i].getHeaders();
	    Enumeration eValue = dValue.elements();
	    while (eKey.hasMoreElements() && eValue.hasMoreElements()) {
		String sKey = eKey.nextElement().toString();
		if (sKey.equalsIgnoreCase("Fragment-Host")) {
		    isFragment=true;
		 }
	    }
	   if (isFragment==false){
		b[i].start();
	   }
     }				    
}

Stopping bundles

The stop() method is defined by the bundle interface to stop a bundle, resulting in the RESOLVED state. All threads associated with the stopping bundle should be stopped immediately.

Uninstalling bundles

The uninstall() method is provided by the bundle interface to uninstall a bundle from the framework. The framework will inform other bundles that the target bundle is being uninstalled, remove any resources related to the bundle, and set the target bundle's state to UNINSTALLED.

Packages of uninstalled bundles must not be used by newly installed bundles. However, if the uninstalled bundle has ever exported any packages used by other bundles, the framework will continue to make these packages available until the framework is restarted or the org.osgi.service.packageadmin.PackageAdmin.refreshPackages() method has been called.

The code below demonstrates how the GUI Console uninstalls a bundle from the platform with a bundle ID or symbolic name provided.

Listing 4. Uninstall command implementation
public static void doUninstall(String s, String matchS) throws Exception{
    b = bContext.getBundles();
    ...
    if (b[i].getSymbolicName().indexOf(s) >-1) {
	....
	b[i].uninstall();
    }
    ...
}
public static void doUninstall(int bID) throws Exception{	
    if (bContext.getBundle(bID)!=null){
	bContext.getBundle(bID).uninstall();
    }
}

Retrieving manifest headers

Two methods are provided by the bundle interface to return manifest header information:

getHeaders()
Returns a dictionary object containing the bundle's manifest headers and values (key-value pairs).
getHeaders(String)
Returns a dictionary object containing the bundle's manifest headers and values(key-value pairs).

Even when a bundle enters the UNINSTALLED state, the getHeaders method can continue to provide the manifest header information.

Listing 5. Headers command implementation
Enumeration eKey = b[i].getHeaders().keys();
Dictionary dValue = b[i].getHeaders();
Enumeration eValue = dValue.elements();
while (eKey.hasMoreElements() && eValue.hasMoreElements()) {
    String sKey = eKey.nextElement().toString();
    String sValue = eValue.nextElement().toString();
    headers.append(sKey+" = ");
    headers.append(sValue+"\n");
}

Execution

We execute GUI Console test scenarios by using IBM Expeditor, an Eclipse-based product (see Resources). It require us to install the GUI Console into Expeditor or any other Eclipse-based product before our verification scenarios. After that, as shown in Figure 3, we use a sample bundle named PascalTriangle as the manipulated bundle during our execution. With its JAR file exported in the file system, we will explore the bundle-management actions on the GUI Console, including install, start, stop, uninstall, and others.

One advantage of the GUI Console is that the bundle's symbolic name and bundle ID will be automatically supported during command interpretation and execution. When a bundle formatted as an exported JAR file is installed, the symbolic name and the bundle ID are retrieved programmatically. What's more, to provide the maximum amount of flexibility for automation test execution, you can search for a symbolic name using simple regex techniques, including start with, contains, and perfect match.

Figure 3. Manipulated bundle: Pascal bundle JAR file exported for testing
Manipulated bundle: Pascal bundle JAR file exported for testing

To demonstrate the function to support OSGi's install command, we will import the manipulated bundle into Expeditor, or any other Eclipse-based product, already equipped with the GUI Console application. In the GUI Console application, press Browse, navigate to where the PascalTriangle bundle JAR file resides in the file system and press OK. After you have verified the location address of your target bundle in the text field, press Install. If your PascalTriangle bundle can be loaded and resolved successfully by the GUI Console and the OSGi platform, you will see a bundle ID designated.

Figure 4. Install command scenario
Install command scenario

Check to see if the bundle's status is resolved using the ss command (press the ss button), as shown below.

Figure 5. ss command scenario
ss command scenario

To launch the bundle, press Start after the target bundle is installed and its symbolic name is retrieved by the text field automatically. The execution result of starting PascalTriangle bundle can be verified as shown below.

Figure 6. Start command scenario
Start command scenario

To see the headers' details in a bundle's MANIFEST file, press the headers button. Consequently, you can view all available headers and their values in your target bundle. Figure 7 gives a demonstration for the headers command for the PascalTriangle bundle.

Figure 7. Headers command scenario
Headers command scenario

Sometimes you need to list all bundles of ACTIVE status on your platform. For an overview, press Active. See Figure 8 for details.

Figure 8. Active command scenario
Active command scenario

As shown below, we find that the GUI Console bundle and the PascalTriangle bundle are listed at the end of the display, with their bundle IDs provided.

Figure 9. Active command scenario
Active command scenario

Figure 10 also demonstrates the Update command when you substitute the target bundle's JAR file with an updated one. We can find the updated PascalTriangle printed after we update it.

Figure 10. Update command scenario
Update command scenario

The action of uninstalling a bundle is shown below. The result is verified by the ss command to search the PascalTriangle bundle by its symbolic name. As we can see, no more PascalTriangle bundle exists in the OSGi platform after we uninstall it.

Figure 11. Uninstall command scenario
Uninstall command scenario

Summary

IBM Rational Functional Tester does not recognize the Eclipse console. Starting with V3.2, Eclipse no longer supports the Eclipse-AutoStart header in legacy test cases. To fix the gap, we demonstrated a solution we call the GUI Console that works with the new Eclipse-LazyStart.


Download

DescriptionNameSize
Sample codeos-eclipse-bundlemgmt_source.zip34KB

Resources

Learn

Get products and technologies

Discuss

  • The Eclipse Platform newsgroups should be your first stop to discuss questions regarding Eclipse. (Selecting this will launch your default Usenet news reader application and open eclipse.platform.)
  • The Eclipse newsgroups has many resources for people interested in using and extending Eclipse.
  • Participate in developerWorks blogs and get involved in the developerWorks community.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=307159
ArticleTitle=Understanding Eclipse's new bundle-management mechanism
publish-date=05132008