There are numerous requirements for building modular systems. In the Java world, as well as many other fields, the Open Services Gateway initiative (OSGi) has been recognized as a mature framework for modular systems, which include desktop applications, web applications, mobile applications, and middleware. OSGi offers an underlying infrastructure to develop modular, dynamic, and service-oriented applications. Most features and abilities are defined and offered through services in OSGi specification and implementation. By understanding the concepts and usage of several core OSGi services, we are able to utilize these services, as well as Eclipse IDE, to build lightweight modular applications meeting complicated requirements.
The sample application is a data collector that retrieves data from different kinds of data sources and parses them into a unified, pre-defined data format for further processing. There are multiple systems with quite different data format definitions and ways to retrieve them. In one example, the application producer or owner expects third-party vendors to implement process logic for particular data sources based on API published by the application. Ideally, the data collector client is expected to integrate third-party code and run them without any changes on existing code, configuration, or deployment structure. This is a typical requirement in building a modular system and we will introduce how to accomplish it with OSGi core services.
To unlock the full power of OSGi, careful design of the application architecture is necessary, although it is not complicated. There have been multiple articles published discussing the design principles of OSGi applications and I encourage you to read through them. (See Resources.)
Figure 1 shows the architecture of a sample data collector application. This application is made up of three types of bundles: the data collector framework bundle, text parser bundles, and collector bundles.
Figure 1. Sample data collector application architecture
The framework bundle acts as the core component of the whole application. It is either a bundle (providing collector and wizard UI APIs for other bundles), or a client (consuming services from other functional bundles). By merging API and client bundles into one bundle, third-party data collector developers can import a minimum number of bundle jars into their IDE to implement APIs, then run and test the whole application in Eclipse. Collector bundles provide data collecting services and wizard page services. On the other hand, collector bundles also act as a client, invoking the text parser API provided by the framework bundle, for which a separate parser bundle will provide the service.
All these bundles are loosely coupled and interact with each other through OSGi core services. As a result, all the bundles except for the framework bundle could be added, removed, suspended, or upgraded easily even after the application is deployed. This is an important feature for an automated software delivery process.
Let's demonstrate the process and practices to implement and deploy a small but completely formed OSGi application.
Define the OSGi project layout
We need to create a new Plug-in Project for each bundle in Eclipse. The following assumes previous experience or knowledge regarding creating OSGi bundles in Eclipse. Additionally, be sure to download the latest release of Eclipse IDE as well as separate Equinox SDK. Eclipse 3.4 or above is recommended.
As described for Figure 1, there are at least three bundle projects required – the framework bundle, a text parser bundle, and a data collector bundle. Defining the project layout, such as package and folder hierarchy design, is very important. Let's take the framework bundle project as an example in Figure 2.
Figure 2. Sample project layout
The OSGI-INF folder is created to store all OSGi
component definition files in this bundle. (The component concept will be
introduced later.) Packages are created according to function categories.
We need to plan packages that are visible to other bundles in the early
stage to avoid bundles coupling heavily. In the sample framework bundle,
the following two packages are exported.
-
dw.sample.dc.api– contains all APIs to be implemented. -
dw.sample.dc.consts– contains constants and enums shared between bundles such as event parameters.
Inject services with OSGi Declarative Services
Modularity and service-oriented are key concepts in OSGi specifications. Exposing implementation classes is supported by the OSGi framework, but not recommended. Instead, from the programming perspective, bundles are expected to interact with each other through publishing and consuming services, or instances of classes. OSGi provides two approaches to accomplish this:
- Registering and retrieving services in the service registry via
BundleContextobject provided by the OSGi service layer. - Leveraging the Declarative Services in the OSGi R4 specification.
Declarative Services (DS) declares the Component Model concept. A component is a Java object defined with a component definition file, which is used to provide services and retrieve referenced services.
The lifecycle of components is managed by the OSGi container. What's more, referenced service instances are dynamically injected into components. DS will handle service look-up, binding or unbinding to the component according to pre-defined strategy. As a result, developers are able to produce a highly dynamic system with neat code.
Create the first service component
Let's start with a simple case: the data collector framework bundle defines a text parser API and a text parser bundle provides default parser service.
In the framework bundle, define the service API in the exported
dw.sample.dc.api package. See Listing 1.
Listing 1. Text parser API
public interface ITextParser {
public String parseText(String input);
}
|
In the text parser bundle project, the
dw.sample.dc.api package must first be imported
into the manifest in order to ensure the interface class can be loaded by
the current bundle class loader. Second, create a class to implement the
above interface.
The last, most important step is to define the implementation class as a component, which requires creating a component definition file and registering it in the bundle manifest. To simplify the process, let's make use of the wizard provided by Eclipse (it might not be available in old releases):
- Right click the OSGI-INF folder in the parser bundle and select
New>Component Definition. - Specify the implementation class file just created as the component class, as well as definition file and component names, then click Finish.
- In the graphic component definition configuration window, switch to the Services tab and specify the service interface class of the framework bundle as the provided service of this component. See Figure 3. below.
Figure 3. Specify the provided services
The wizard will generate a new textParserComponent.xml and register the component in the bundle manifest, as seen in Listing 2.
Listing 2. New generated component configuration
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
name="dw.sample.textparser.defaultparser">
<implementation class="dw.sample.textparser.service.DataCollectorDefaultTextParser"/>
<service>
<provide interface="dw.sample.dc.api.ITextParser"/>
</service>
</scr:component>
MANIFEST.MF
...
Service-Component: OSGI-INF/textParserComponent.xml
|
We have now implemented a simple but complete component that provides service at runtime when a client invokes the API of the framework bundle.
Next, we will demonstrate a more complicated component and introduce how to consume services from other components.
Let's consider a scenario where users are supposed to interact with the data collector application via GUI, which means they are able to input required data following a series of wizard pages. They then launch the collecting process and watch the progress on the UI. Obviously, the framework does not know what data is required by third-party data collectors. To meet the requirement, data collector bundles also need to provide at least one configuration wizard page. It also requires the framework to integrate and present all the third-party wizards seamlessly and dynamically after the application is deployed on the user environment. Sounds cool, right? Let's see how to realize it by leveraging the power of OSGi Declarative Services. (See Figure 4.)
Figure 4. Dynamic wizard sequence
Like the previous case, an API needs be defined. For this case, we need to carefully design the interface to avoid enlarging the complexity of the wizard system while ensuring the API is capable of covering key functional requirements from the data collector side. Therefore, the API design needs to at least satisfy the following:
- Do not allow any communication and dependencies among wizard services from different data collectors, but do not restrict them among ones within the same data collector.
- Saving the user input at the stage when the user steps out of the last wizard page of a data collector.
This is the most important step in this case. A well-formed API design is to provide clear interfaces while keeping the service highly cohesive, which doesn't care about the logic and code complexity inside an implementation.
SWT and JFace are used as underlying libraries of the application wizard as
well as other GUI components. If we plan to use resources and classes
across multiple packages in these two libraries, simply add them as the
required bundle in the manifest files of framework and data collector
bundles. Also import the
org.osgi.service.component package because we
will invoke classes in this package later.
The wizard API defined in the framework bundle is made up of two classes as shown in Listing 3.
Listing 3. Define the wizard service API
public interface ICollectorWizardGenerator {
public List<CollectorWizardPage> getWizardSequence();
public int getWizardPositionIndex();
}
---
/**
* Inherit the jface WizardPage by adding two customized methods
*/
public abstract class CollectorWizardPage extends WizardPage {
…
/**
* Trigger the customized logic when the next button on the wizard is
* pressed
*
* @return whether to jump to next page.
*/
public abstract boolean nextPressedTrigger();
/**
* Save user input in the current wizard.
*/
public abstract void saveWizardInput();
}
|
In the data collector bundle, implement the wizard API and define the class
which implements the ICollectorWizardGenerator
interface as the service component. (See Listing
4.)
Listing 4. The wizard service component
public class ProfileWizardGeneratorImpl implements ICollectorWizardGenerator {
private int wizardPosition = 10;
protected void activate(ComponentContext context) {
try {
wizardPosition = ((Integer) context.getProperties().get(
"wizardPosition")).intValue();
} catch (NumberFormatException e) {
wizardPosition = 10;
}
}
public List<CollectorWizardPage> getWizardSequence() {
List<CollectorWizardPage> wizardPageSeq = new ArrayList<CollectorWizardPage>();
wizardPageSeq.add(new ProfileCollectorWizardPage1());
wizardPageSeq.add(new ProfileCollectorWizardPage2());
return wizardPageSeq;
}
public int getWizardPositionIndex() {
return wizardPosition;
}
}
---
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
name="dw.sample.dc.profile.wizardgenerator">
<implementation class="dw.sample.dc.profile.service.ProfileWizardGeneratorImpl"/>
<service>
<provide interface="dw.sample.dc.api.ICollectorWizardGenerator"/>
</service>
<property name="wizardPosition" type="Integer" value="2"/>
</scr:component>
|
Notice that the activate method is overridden in
the component class. The method as well as the
deactivate method are invoked by the OSGi
framework when the component is activated or deactivated. The
ComponentContext object is passed to the
activate method so that we can get component
information like component properties defined in the XML file. Here the
data collector vendor can define the wizard position index thus the
framework bundle could arrange the wizard appearance sequence
accordingly.
So far we have made two components as service providers.
In the Declarative Services model, services are dynamically injected into components. The code in Listing 5. below demonstrates how to refer to services and consume them in the bundle.
Listing 5. The parser service loader component
public class TextParserLoader {
private static TextParserLoader instance;
... ...
private ITextParser parser;
public static TextParserLoader getInstance() {
return instance;
}
protected void activate(ComponentContext context) {
try {
locker.lock();
instance = this;
} finally {
locker.unlock();
}
}
public void setTextParser(ITextParser parser) {
this.parser = parser;
}
public void unsetTextParser(ITextParser parser) {
if (this.parser != parser) {
return;
}
this.parser = null;
}
public ITextParser getTextParser() {
return parser;
}
}
---
<implementation class="dw.sample.dc.profile.service.TextParserLoader"/>
<reference bind="setTextParser" cardinality="1..1"
interface="dw.sample.dc.api.ITextParser" name="ITextParser"
policy="dynamic" unbind="unsetTextParser"/>
|
Here the singleton pattern is applied to the component class. Other
non-component objects in the bundle are able to get and consume services
from the unique TextParserLoader instance while
getting rid of OSGi-specific code. The loader class somehow acts as what a
factory class plays in traditional Java applications, but the magic is
that it need not handle any bothersome class loading, reflection and
instance initiation.
DS supports different service reference strategies defined in the component XML file:
- Policy: The
policyproperty has two values – static and dynamic. The static policy is default which means the component will be re-activated once the referred services have any changes. Instead, the dynamic policy just invokes the pre-defined bind and unbind methods so it is more highly recommended. - Cardinality: the property has four available values: 1..1, 1..n, 0..1, 0..n. Given there might be multiple services available for the same API, the end number indicates whether all services or just one of them will be injected into the component and the start number indicates whether available services are mandatory to activate the component.
The 1..1 cardinality is the default value and we have seen how it was used from the above example. For the 0..n case, it can be also well-handled along with the singleton pattern, as shown in Listing 6.
Listing 6. Inject multiple services
public class DataCollectorWizardSequence {
...
private List<ICollectorWizardGenerator> wizardGeneratorSeq =
new ArrayList<ICollectorWizardGenerator>();
...
public void addCollectorWizardGenerator(ICollectorWizardGenerator wizardGenerator) {
wizardGeneratorSeq.add(wizardGenerator);
}
public void removeCollectorWizardGenerator(ICollectorWizardGenerator wizardGenerator)
{
wizardGeneratorSeq.remove(wizardGenerator);
}
public List<CollectorWizardPage> getDataCollectorWizardSequence() {
List<CollectorWizardPage> collectorWizardSequence =
new ArrayList<CollectorWizardPage>();
try {
locker.lock();
/*
* Arrange the wizard sequence according to position indexes
* defined by each wizard services
*/
Collections.sort(wizardGeneratorSeq,
new CollectorWizardSequenceComparator());
for (ICollectorWizardGenerator generator : wizardGeneratorSeq) {
collectorWizardSequence.addAll(generator.getWizardSequence());
}
return collectorWizardSequence;
}
finally {
locker.unlock();
}
}
}
---
<reference bind="addCollectorWizardGenerator" cardinality="0..n"
interface="dw.sample.dc.api.ICollectorWizardGenerator"
name="ICollectorWizardGenerator" policy="dynamic"
unbind="removeCollectorWizardGenerator"/>
|
Once we understand how to leverage components introduced in the Declarative Services to publish and consume services, let's learn how to make effective use of other OSGi core services with this model.
Manage bundle configuration with the Configuration Admin Service
The Configuration Admin Service provides a unified approach for OSGi applications to manage local or remote configuration data either on a service level or on a bundle level. We will begin with the most common case – to store, retrieve, and update bundle configuration data locally.
First, check whether the
org.eclipse.equinox.cm_<version>.jar is in the Eclipse
plugins folder, which is the Equinox implementation for the OSGi
Configuration Admin Service. Otherwise, place it there from the separately
downloaded Equinox SDK. Then import the
org.eclipse.equinox.cm package into the target
bundle manifest.
To use the Configuration Admin Service, first, and most importantly, the
ManagedService API must be implemented and
published as a service by the target bundle. There is only one method
updated to be implemented. Different
ManagedServices are distinguished by SERVICE_PID registered with services,
thus corresponding service will be triggered when its configuration is
changed. To manage the bundle-level configuration, an appropriate object
to provide the ManagedService is the bundle
activator as in Listing 7.
Listing 7. The bundle activator providing ManagedService
public class Activator implements BundleActivator {
private ServiceRegistration cmSvrReg;
public void start(BundleContext context) throws Exception {
cmSvrReg = context.registerService(ManagedService.class.getName(),
this, initMgrServiceProp());
}
public void stop(BundleContext context) throws Exception {
cmSvrReg.unregister();
}
public void updated(Dictionary properties) throws ConfigurationException {
if (cmSvrReg == null) {
return;
}
if (properties == null) {
cmSvrReg.setProperties(initMgrServiceProp());
} else {
cmSvrReg.setProperties(properties);
}
}
public static Dictionary initMgrServiceProp() {
Dictionary result = new Hashtable();
// Suppose that the Activator class name is unique across bundles…
result.put(Constants.SERVICE_PID, Activator.class.getName());
return result;
}
}
|
Next, as shown in Listing 8, create a component loading the Configuration Admin Service published by the OSGi framework.
Listing 8. The ConfigManager component
public class DCFrameworkConfigManager {
private ConfigurationAdmin cm;
...
protected void activate(ComponentContext context) {
...
Configuration config = getFrameworkConfig();
try {
writeLock.lock();
if (config.getProperties() == null) {
config.update(context.getProperties());
}
} finally {
writeLock.unlock();
}
}
public void setConfigAdmin(ConfigurationAdmin cm) {
this.cm = cm;
}
public void unsetConfigAdmin(ConfigurationAdmin cm) {
this.cm = null;
}
public Configuration getFrameworkConfig() throws IOException {
try {
readLock.lock();
return cm.getConfiguration(Activator.class.getName());
} finally {
readLock.unlock();
}
}
public void updateFrameworkConfig(Dictionary<String, String> properties)
throws IOException {
try {
writeLock.lock();
Configuration config = getFrameworkConfig();
if (config != null) {
config.update(properties);
}
} finally {
writeLock.unlock();
}
}
}
---
<reference bind="setConfigAdmin" cardinality="1..1"
interface="org.osgi.service.cm.ConfigurationAdmin" name="ConfigurationAdmin"
policy="dynamic" unbind="unsetConfigAdmin"/>
<properties entry="OSGI-INF/initConfig.properties"/>
|
After activation, the ConfigManager component initiates the bundle configuration from a property file and provides methods which can be used anywhere within the bundle to read and save the configuration at runtime. They are protected by locks to handle the concurrent invoking case.
Handle events with the Event Admin Service
For many systems, it is essential to implement an event handling model. OSGi's service-oriented framework provides the implementation mechanism through the Event Admin Service.
To make use of the Event Admin Service correctly, remember three things:
- The event publisher needs leverage referenced
EventAdminservice to send theEventobject. - The event subscriber provides the
EventHandlerservice by implementing thehandleEventmethod to handle received events. - The
event.topicsproperty of theEventobject determines which event handlers can receive the event.
The OSGi Event Admin Service implements a traditional event handling programming model in a more dynamic way, yet doesn't introduce new concepts into the model. Like other OSGi services introduced in this article, we will retrieve the service through component objects. Declarative Services support a component to be both service provider and service consumer, which makes it possible for a component to become both an event publisher and an event handler at the same time.
Consider this requirement: Users should be able to watch the data collecting progress on the application UI and stop the process at any time via the UI control. For a data collector bundle, technically it means there should be bi-channel events sent and handled at both the application framework bundle and the data collector bundle sides. (See Listing 9 below.)
Let's just take the data collector bundle side as an example. Again, first
check the Eclipse plugins folder, copy the
org.eclipse.equinox.event_<version>.jar
if it isn't there, and import the package into the bundle manifest.
Listing 9. The event publisher and handler component
public class ProfileDataCollectorImpl implements IDataCollector, EventHandler {
public final static String EVENT_TOPIC =
"dw/sample/dc/event/collector/ProfileDataCollectorImpl/status";
private EventAdmin eventAdmin;
...
public int collectAndOutput() {
while (...) {
if (isCancelled()) {
cancelProcess(false);
return -1;
}
...
publishEvent(GUIMsg.STATUS_AND_PROGRESS.flag(),
"Collecting profile data...", 0, true);
...
}
...
}
private void publishEvent(int status, String statusMsg, int progress,
boolean async) {
Dictionary<String, String> props = new Hashtable<String, String>();
props.put(FrameworkEventConsts.PARAM_STATUS, String.valueOf(status));
props.put(FrameworkEventConsts.PARAM_STATUS_MSG, statusMsg);
props.put(FrameworkEventConsts.PARAM_PROGRESS, String.valueOf(progress));
if (!async) {
eventAdmin.sendEvent(new Event(EVENT_TOPIC, props));
} else {
eventAdmin.postEvent(new Event(EVENT_TOPIC, props));
}
}
public void handleEvent(Event event) {
String action = (String) event
.getProperty(FrameworkEventConsts.PARAM_ACTION);
if (FrameworkEventConsts.VAL_CANCEL.equalsIgnoreCase(action)) {
cancelProcess(true);
}
}
...
}
---
<property name="event.topics" type="String"
value="dw/sample/dc/event/framework/UserCmdEventPublisher/uicmd"/>
<service>
<provide interface="org.osgi.service.event.EventHandler"/>
<provide interface="dw.sample.dc.api.IDataCollector"/>
</service>
<reference bind="setEventAdmin" cardinality="1..1"
interface="org.osgi.service.event.EventAdmin" name="EventAdmin" policy="dynamic"
unbind="unsetEventAdmin"/>
|
In this example, notice the format of the
event.topics property value. Also, we can see
the Event Admin Service allows developers defining the data to be
transmitted via the Event object.
Specify the launch model with the Application Admin Service
So far we have covered the major aspects of developing an OSGi application and the sample application is ready to run. As Figure 1. show, a native OSGi application is made up of bundles running on top of the OSGi container. By default, the OSGi console will show up when the application is launched. There are no problems if it runs at the server side. If it is supposed to be delivered as a client application, however, the question arises of how to package the application so that it is able to perform just like a modern application, i.e., to launch in a more 'professional' way while keeping the modularity and dynamic nature inside.
Here we introduce a simple method by implementing the Equinox application model based on the OSGi Application Admin Service.
The first step is to add the
org.eclipse.equinox.app bundle as the
Require-bundle in the manifest of the
application framework bundle. Create a class implementing the
IApplication interface as in Listing 10.
Listing 10. Implement the IApplication interface
public class GUIApplication implements IApplication {
public Object start(IApplicationContext context) {
// Invoke the entry GUI object here
...
return IApplication.EXIT_OK;
}
public void stop() {
// Add the logic when the application quits.
}
}
|
Second, define the application framework bundle as an Eclipse application by declaring the extension in the plugin.xml as in Listing 11.
Listing 11. plugin.xml
<plugin>
<extension id="GUIApp"
point="org.eclipse.core.runtime.applications">
<application cardinality="1">
<run
class="dw.sample.dc.launcher.GUIApplication">
</run>
</application>
</extension>
</plugin>
|
The application cardinality specifies that the application can only have
one instance at a time. Since the bundle has declared an extension, it is
required to append the singleton directive in the
Bundle-Symbolic manifest header like:
Bundle-SymbolicName: dw.sample.dc;singleton:=true |
Next, create a new configuration to launch the OSGi application in the Eclipse IDE. (See Figure 5.)
Figure 5. Create a new configuration
On the Bundles tab, we need click Add Required
Bundles to include all mandatory dependencies as the platform
bundles. On the Arguments tab, we need to add two new
arguments to enable the application model. Notice that the value of
-application argument should be in the form of:
"bundle symbolicname"."extension id".
Once we are able to run the application and verify all the functions work as expected in Eclipse, it indicates we have finished the development work to build an OSGi application. Therefore, we can export each bundle project to a jar file, deploy them, and run integrated tests in the real environment, which is beyond the topic of this article.
This article demonstrates detailed steps for making use of OSGi core services to develop and launch a modular application in Eclipse. Examples in this article show that the Service-Oriented Component Model coming up with Declarative Services is playing an important role of helping developers make effective use of other OSGi core services to improve the dynamics of modular applications.
Learn
- OSGi Alliance
Specifications: Find the specifications for all releases.
- Best practices for developing and working with OSGi applications:
(developerWorks, July 2010) Learn 14 practices for working with OSGi
applications effectively.
- Equinox Documents:
Access Equinox resources from eclipse.org.
- Equinox
Application Model Demo: Watch how Eclipse applications can be managed using the OSGi Application Admin Service specification.
- developerWorks
Eclipse articles: Read other articles about Eclipse.
- developerWorks on
Twitter: Follow us for the latest news.
- developerWorks
Open source zone: Find extensive how-to information, tools, and
project updates to help you develop with open source technologies and use
them with IBM's products.
- Events of interest: Check out upcoming conferences, trade shows,
and webcasts that are of interest to IBM open source
developers.
- developerWorks
podcasts: Tune into interesting interviews and discussions for
software developers
- developerWorks On demand demos: Watch our no-cost demos and learn
about IBM and open source technologies and product functions.
Get products and technologies
- Eclipse
Marketplace is a convenient portal where you can find open source
and commercial Eclipse-related offerings.
- Eclipse and Equinox SDK: Download
the latest releases.
Discuss
- developerWorks
community: Connect with other developerWorks users while exploring
the developer-driven blogs, forums, groups, and wikis. Help build the Real world open source group in the developerWorks
community.

Sun, Yuan Tao is a software performance analyst for the Lotus Shanghai performance team at IBM China Software Development Lab who focuses on measuring and improving performance of Lotus social products including Lotus Connections and LotusLive iNotes. Prior to joining the performance team, he was a software developer experienced in software design pattern, Eclipse RCP, and Java 2 Platform, Enterprise Edition application development.




