Skip to main content

skip to main content

developerWorks  >  Open source  >

Leave Eclipse plug-in headaches behind with OSGi

Use the Open Services Gateway Initiative API to eliminate Eclipse plug-in dependencies

developerWorks
Document options

Document options requiring JavaScript are not displayed


Rate this page

Help us improve this content


Level: Introductory

Bob Balfe, Senior Software Engineer and Project Leader, Lotus
Chuck Imperato (imperato@us.ibm.com), Advisory Software Engineer, IBM

11 Apr 2006

Find out how to write extensions in code for other plug-ins while not creating a binary dependency on those other plug-ins with the Eclipse V3.2's dynamic-extensions API. Accomplish all of this and more with the Open Services Gateway Initiative (OSGi) services API and the dynamic APIs.

This article shows an example of one plug-in taking in XML to register extensions for a defined extension point. We accomplish this complete decoupling of components by having the plug-ins Extension Registry aware and providing an OSGi service.

Plug-ins, extension points, OSGi -- Come again?

As you know, Eclipse's component architecture is plug-in-based -- meaning that a bunch of code is componentized into a single component, then registered with the Eclipse framework as one of its components others can then bind to or call into. Extension points are ways for plug-ins to allow for others to contribute additional functionality to the plug-in exposing the extension point. Now take all of that and wrap it up into a controlled runtime where plug-ins can come and go dynamically, and you get OSGi (basically).

Our sample plug-in

Let's start with a basic plug-in that exposes an extension point for registering new string mappings for a synonym service. This service allows others to register a word and map it to another word (a synonym). The basic extension contains very simple elements: one word and, of course, a new synonym. The basic constructs of this plug-in extension point are shown in Table 1.


Table 1. Elements of our sample plug-in
Plug-in namecom.company.SynonymRegistry
Extension pointSynonym
Elementsword -- The word for which you want to add a synonym
synonym -- A synonym you want to register

We will also register the plug-in to be an OSGi service. What this means is it will only be loaded when explicitly done so and will be available declaratively by other consumers. The other consumers only need to know the Interface and the OSGi class name in order to use the service. In our example, we are never really calling into the service because of the provided extension point. We are using the OSGi APIs to tell us when this service comes and goes, so we can properly register our extensions.

Now this is only an example and using extension points for this concept is probably not the best approach. What we want to accomplish with this basic example is how to register new extensions dynamically, while accounting for plug-in life cycle events with the OSGi APIs.

The mediator plug-in

The next plug-in will be a third-party plug-in that knows about a well-known service and extension point but does not want to bind to this plug-in because it will then depend on it for runtime resolution. This means the plug-in can live on the machine where the referenced plug-in (com.company.SynonymRegistry) may not exist. Because we now live in the world of OSGi and dynamic runtimes, we want to make sure our plug-in can run without causing a runtime crash or error. Our mediator plug-in will take in an XML file of synonyms and register each one with the SynonymRegistry plug-in through the use of the extension point provided.


Listing 1. Sample XML file for our proof of concept

Synonyms.xml
<?xml version="1.0" encoding="UTF-8"?>
<synonyms>
	<entry word="mediator" synonym="broker"/>
	<entry word="mediator" synonym="go-between"/>
	<entry word="mediator" synonym="interceder"/>
	<entry word="mediator" synonym="intermediary"/>
<synonyms>

The first thing the mediator plug-in will want to do in its start() method is register with the OSGi service as a listener of service initializations. We call the OSGi service method addServiceListener() on the BundleContext object passed into the start() method. The code below shows an example of calling this API by passing in this and the service ID in which we are interested.

context.addServiceListener( this, "com.company.SynonymRegistry" );

By providing a filter, you're telling the OSGi service registry to only notify you of state changes in the specified service. In this case, the filter is simply the class name of our SynonymRegistry class.

You may wonder why we want to do this. The answer is in the start-up sequence. In the world of OSGi, we do not always know when another service is available, and, as a result, we need to account for this. By registering as a service listener, we are notified when that service starts and stops. This allows us to cache our synonyms if the service is not available. When it does become available, we are notified and register our extensions.

Registering new extensions

We are finally getting to the meat of this article. We now have the data (Synonyms.xml) and a well-known extension point (com.company.SynonymRegistry. Synonym) to which we want to provide dynamic extensions. Since we do not know when our plug-in will get initialized or whether the Synonym plug-in is initialized, we will simply attempt to register the entries in our XML file right when our plug-in loads. Remember: This is an example to show the concepts and should not be implemented in production code like this. Normally, we would do as much initialization as possible in a lazy manner (delayed or when needed).

A new feature in Eclipse V3.2 is the ability to provide extensions at runtime. For example, one could write an application containing a view that creates a perspective when a button is clicked. The perspective is added to the extension registry and would then be shown in the list of available perspectives. One of the more significant benefits of this capability is that it can alleviate many "hard" dependencies between plug-ins. Plug-in A can make contributions to the platform defined in Plug-in B without having to depend on Plug-in B. Furthermore, by combining this with the OSGi framework, a plug-in can check for the existence of a service and, if present, create an extension from an extension point defined in the service. This promotes a truly dynamic environment, while using the principles of a service-oriented architecture.

Newly exposed in Eclipse V3.2 is the addContribution() method, defined in the IExtensionRegistry interface. The code in Listing 2 shows how an extension can be added through the addContribution() API. The addContribution() method is designed to take in plain XML as an InputStream in the first parameter.


Listing 2. How an extension can be added through the addContribution() API

IExtensionRegistry registry = RegistryFactory.getRegistry( );

Object key = ((ExtensionRegistry) registry).getTemporaryUserToken( );

ByteArrayInputStream is = 
		new ByteArrayInputStream( buffer.toString().getBytes() );
		
try {
	registry.addContribution(is, bundle, null, null, key);	
}	
finally {	
try {
		is.close( );
	}catch (IOException e) {
				
	}
}

As this article was written -- which implies that this has a good chance of changing in future versions of Eclipse -- the user token to allow for public access to the registry is obtained using this internal Eclipse call. The code below shows the use of the internal API (getTemporaryUserToken()).

Object key = ((ExtensionRegistry)registry).getTemporaryUserToken();

However, in the next milestone version of Eclipse V3.2, this token is not publicly accessible. To support dynamic extensions in our application, our launcher will have to provide the following setting to the virtual machine:

-Declipse.registry.nulltoken=true

This definition will now allow us to use null as the User Token in the addContribution() API. Our code will then look like the code below. For the Bugzilla conversation on this problem, see the Bugzilla bug listing.


Listing 3. getTemporaryUserToken() in action

...
try {
	registry.addContribution(is, bundle, null, null, null);	

}	
...

The buffer variable shown above represents an actual XML block. This XML is an exact replica of what we might see within a plugin.xml file. Going back to our SynonymRegistry example, the XML for this extension would look something like Listing 4.


Listing 4. XML for SynonymRegistry

<plugin>
	<extension point="com.company.synonymregistry" id="myExtension">
		<synonyms
			word="mediator"
			synonym="broker"/>
	</extension>
</plugin>

One can envision creating a wrapper factory class that would take in arguments such as the extension point ID, the extension ID, the element name (in this case, synonyms), the actual attributes, and the contributing bundle ID. The wrapper class would take care of formatting the arguments into an XML string resembling the code above. This XML string is then read into a ByteArrayInputStream to be passed to the addContribution() method of the IExtensionRegistry interface. The only other required arguments for this method are the user token and the bundle ID. It is important to note that the bundle ID should be the ID of the bundle making the contribution, not the ID of the bundle in which the extension point is defined.

Caveats and tips

One of the gotchas introduced in M5, the 17 Feb 2006 build of Eclipse, is the call to addContributions() is asynchronous. This means that the extensions are not available immediately because Eclipse kicks off a job to do the actual registrations. In short, you must kick off your own job and synchronize with it to get any type of synchronous behavior.

Here are three tips to make this task easier:

  • Create a new job that registers itself as a RegistryChangeListener.
  • When the job runs, make sure your job code listens for an isRegistered set in your RegistryListener callback.
  • Exit your job once all of your registrations are registered.

Of course, now we must join our calling code with our spawned job to get the synchronous call. This is warranted only if your code demands immediate use of the extensions. With hope, your code is designed to be lazy and this initialization is not important.

Summary

The use of dynamic extensions can be created programmatically. Dynamic extensions enhance decoupling by using the OSGi framework for listening when services are available (loaded or unloaded). Using these technologies together will allow for declarative contributions and 100-percent decoupling between components.



Resources

Learn

Get products and technologies

Discuss


About the authors

Bob Balfe is a senior software engineer for IBM and the portal-managed client architect on the WebSphere Everyplace Deployment team.


Chuck Imperato is an advisory software engineer for IBM and a developer on the WebSphere Everyplace Deployment team.




Rate this page


Please take a moment to complete this form to help us better serve you.



 


 


Not
useful
Extremely
useful
 


Share this....

digg Digg this story del.icio.us del.icio.us Slashdot Slashdot it!



Back to top