1. Introduction to software modularity with OSGi

OSGi is a set of specifications. Its core specification defines a component and service model for Java applications.

A software component in OSGi is called a bundle or plug-in, both terms are interchangeable. Services are Java implementations that OSGi allows you to start and access.

An application consists of different components and services. A solid and clean software architecture consists of components that can change their internal implementation without affecting other components. OSGi supports creating such an architecture.

The OSGi specification defines a way for plug-ins to define:

  • their API

  • their dependencies

  • provided and required services

A plug-in is therefore a cohesive, self-contained unit that explicitly defines its dependencies on other components and services.

OSGi defines that plug-ins and services can be dynamically installed, activated, deactivated, updated and uninstalled.

The OSGi specification has several implementations, for example, Eclipse Equinox, Knopflerfish OSGi or Apache Felix.

Eclipse Equinox is the reference implementation of the base OSGi specification. It is also the runtime environment on which Eclipse applications are based.

Since Java 9, the Java Platform Module System (JPMS) also describes software component dependencies. Eclipse continues to use OSGi as its module system.

2. OSGi configuration files

2.1. The manifest file (MANIFEST.MF)

OSGi uses the META-INF/MANIFEST.MF file (called the manifest file) from the standard Java specification to define its metadata. The Java specification allows additional key-value pairs in this file, which runtimes that do not understand them ignore. Therefore, OSGi plug-ins can be used without restrictions in other Java environments.

The following listing is an example of a manifest file containing OSGi metadata.

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: My OSGi Plug-in
Bundle-SymbolicName: com.example.myosgi; singleton:=true
Bundle-Version: 1.0.0
Bundle-Activator: com.example.myosgi.Activator
Require-Bundle: org.eclipse.ui,
 org.eclipse.core.runtime
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-25

The following table gives an overview of the OSGi metadata used in the manifest file.

Table 1. OSGi identifiers in the manifest file
Identifier Description

Bundle-Name

Short description of the plug-in.

Bundle-SymbolicName

The unique technical name of the plug-in. If this plug-in provides an extension or an extension point, it must be marked as singleton. You do this by adding ; singleton:=true after the Bundle-SymbolicName value.

Bundle-Version

Defines the plug-in version and must be incremented if a new version of the plug-in is published.

Bundle-Activator

Defines an activator class that implements the BundleActivator interface. An instance of this class is created when the plug-in gets activated. Its start() and stop() methods are called whenever the plug-in is started or stopped. An activator can be used to configure the plug-in during startup. An activator may increase the startup time of the application, therefore avoid it if possible.

Bundle-RequiredExecutionEnvironment (BREE)

Specifies which Java version is required to run the plug-in. If this requirement is not fulfilled, then the runtime does not load the plug-in. The OSGi specification deprecates it, but the Eclipse plug-in development tooling still uses it, hence this book uses it too.

Bundle-ActivationPolicy

Setting this to lazy instructs the OSGi runtime to activate the plug-in once one of its classes or interfaces is loaded. In Equinox, a plug-in that provides services needs this setting.

Bundle-ClassPath

Specifies where to load classes from within the bundle. The default is ., which loads classes from the root of the bundle. You can also add JAR files to it, these are called nested JAR files.

2.2. Unique identifier for plug-ins

The combination of Bundle-SymbolicName and Bundle-Version uniquely identifies a plug-in.

Each plug-in has a unique name (ID) that is defined via the Bundle-SymbolicName property. By convention, this name uses the reverse domain name of the plug-in author. For example, if you own the "example.com" domain, the symbolic name starts with "com.example".

Each plug-in defines its version number in the Bundle-Version property. OSGi recommends the following schema for versions in the Bundle-Version field.

<major>.<minor>.<service>.<qualifier>

The qualifier is optional, Eclipse projects typically use it for a build timestamp.

If you change your plug-in code, increase the version according to the following rules.

  • <major> is increased if changes are not backwards compatible.

  • <minor> is increased if public API has changed but all changes are backwards compatible.

  • <service> is increased for bug fixes and other changes that do not affect the public API.

For more information on this version scheme, see the Eclipse version numbering guide.

3. Specifying the API of a plug-in and its dependencies

An OSGi runtime restricts access to classes from other plug-ins. To access classes from other plug-ins, a plug-in needs to define dependencies on these plug-ins or their packages. In addition, the other plug-in must export the used packages as API. The only exception are the java.* packages of the Java runtime environment, which can be accessed by default.

All these restrictions are enforced via a specific OSGi class loader. Each plug-in has its own class loader.

OSGi cannot prevent you from using Java reflection to access these classes.

3.1. Defining the API of a plug-in

A plug-in defines its API via the Export-Package identifier in its manifest file. All packages that are not explicitly exported are not visible to other plug-ins.

The following screenshot shows a plug-in which exports one package as API in the Eclipse manifest editor.

Runtime tab with the exported package com.vogella.tasks.model

3.2. Defining dependencies of a plug-in

To access classes from another plug-in A in the source code of your plug-in B, you need to:

  • export the corresponding package in plug-in A

  • define a dependency in plug-in B on plug-in A or on the package

The following screenshot shows an example dependency definition in the Eclipse manifest editor.

Dependencies tab with required plug-ins and imported packages

A plug-in dependency allows a plug-in to access all exported packages of the other plug-in. A package dependency allows your plug-in to use the classes in this package, independent of which plug-in provides it.

Using package dependencies allows you to exchange the plug-in that provides this package. The OSGi specification promotes the usage of package dependencies. If you require this flexibility, prefer package dependencies.

For legacy reasons, OSGi supports a dynamic import of packages. Avoid this feature, it is a symptom of a non-modular design.

3.3. Dependencies using versions

A plug-in can define that it depends on a certain version (or a range) of another plug-in. For example, plug-in A can define that it depends on plug-in C in version 2.0. Another plug-in B can define that it depends on version 1.0 of plug-in C.

3.4. Life cycle of plug-ins

During startup, the OSGi runtime reads the manifest file of the plug-in. The runtime validates if all dependencies are present and can be resolved.

If all required dependencies are resolved, the plug-in is in the RESOLVED status, otherwise it stays in the INSTALLED status.

If several plug-ins can satisfy the dependency, the plug-in with the highest valid version is used. If the versions are the same, the plug-in with the lowest bundle ID is used. The framework assigns this ID to every plug-in during the installation.

When the plug-in starts, its status is STARTING. If a plug-in sets the lazy activation policy in its manifest, the Equinox runtime moves that plug-in to the ACTIVE state once one of its classes or interfaces is used. In this state, the plug-in can provide OSGi services in Equinox.

Other OSGi runtimes and launchers behave differently, for example, many of them start all installed plug-ins.

This lifecycle is depicted in the following graphic.

OSGi bundle lifecycle states diagram

3.5. Defining a Provisional API using x-internal and x-friends

OSGi allows you to define provisional APIs, that is, packages that can be accessed by other plug-ins but are not yet considered stable. This can be done via the x-internal or the x-friends flag.

Diagram of public, provisional and internal API

The following screenshot shows how to define a package as provisional API.

Runtime tab with the provisional API option selected

This results in the following manifest, in which the package is flagged as x-internal.

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Model
Bundle-SymbolicName: com.vogella.tasks.model
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: VOGELLA
Automatic-Module-Name: com.vogella.tasks.model
Bundle-RequiredExecutionEnvironment: JavaSE-25
Export-Package: com.vogella.tasks.model;x-internal:=true

x-friends also defines provisional API and allows you to define a set of plug-ins that can access the provisional API without a warning or error message. x-friends automatically replaces x-internal if you use the Eclipse manifest editor and add a plug-in to the Package Visibility section on the Runtime tab.

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Model
Bundle-SymbolicName: com.vogella.tasks.model
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: VOGELLA
Automatic-Module-Name: com.vogella.tasks.model
Bundle-RequiredExecutionEnvironment: JavaSE-25
Export-Package: com.vogella.tasks.model;x-friends:="another.bundle"

You can configure the Java compiler to show an error or a warning for the usage of provisional API, or to ignore it. The default is a warning. You can adjust this via Window  Preferences  Java  Compiler  Errors/Warnings.

Errors/Warnings preference page with the deprecated and restricted API section

3.6. Eclipse platform API

The Eclipse platform project marks almost all packages either as public API or as provisional API. If the Eclipse platform project releases an API, it plans to keep this API stable for as long as possible.

If an API is marked as provisional, the platform team may change it in future releases. If you use such an API, be prepared to make adjustments to your application when you move to a newer Eclipse release.

If you use non-API code, you see a Discouraged access: The …​ is not API (restriction on required project …​) warning in the Java editor.

You can turn off these warnings for your workspace via Window  Preferences  Java  Compiler  Errors/Warnings and by setting the Discouraged reference (access rules) flag to Ignore.

Alternatively, you can turn off these warnings on a per-project basis. To do so, right-click on the project, select Properties  Java Compiler, and then use the same path as for accessing the global settings. You might have to activate the Enable project specific settings checkbox at the top of the Errors/Warnings page.

3.7. Understanding Eclipse-BuddyPolicy: Registered

In Eclipse plug-in development, the Equinox-specific Eclipse-BuddyPolicy header specifies how a bundle (plug-in) can get class-loading assistance from other bundles. The Registered policy requires explicit registration of "buddies" (other bundles) using the Eclipse-RegisterBuddy header.

A bundle that declares Eclipse-BuddyPolicy: Registered in its MANIFEST.MF allows other bundles (buddies) to assist with its class loading, but only if those buddies register themselves via the Eclipse-RegisterBuddy header in their MANIFEST.MF. When the bundle with the Registered policy tries to load a class that is not available in its own classpath, it consults its registered buddies. The buddies are then allowed to attempt to load the class.

The process of class loading is the following:

  1. Bundle B registers itself as a buddy for bundle A.

  2. When bundle A encounters a class that it cannot resolve, it consults its registered buddies.

  3. If bundle B can resolve the class, it loads it on behalf of bundle A.

3.7.1. Example scenario

The MANIFEST.MF of bundle A allows buddy registration via the Eclipse-BuddyPolicy: entry.

Bundle-SymbolicName: com.example.bundleA
Eclipse-BuddyPolicy: Registered

The MANIFEST.MF of bundle B registers a buddy via the following Eclipse-RegisterBuddy: entry.

Bundle-SymbolicName: com.example.bundleB
Eclipse-RegisterBuddy: com.example.bundleA

Bundle B must also depend on bundle A, otherwise the registration is ignored.

3.7.2. Key notes

Unlike other policies (for example, Global or Dependent), the Registered policy requires an explicit setup for buddy relationships. This makes it more predictable and avoids the risks of overly permissive class loading. The buddy relationship is resolved at runtime, not at compile time.

The Registered policy is often used in scenarios where specific bundles must assist with loading classes (for example, frameworks which load classes by name, such as logging or serialization libraries).

3.7.3. Best practices

Buddy policies, including Registered, should be used sparingly. Relying on OSGi’s modular class loading and proper exports/imports is generally preferable.

4. OSGi Services

Services enable functionality for other software components. A service interface in OSGi is defined in a plug-in by a standard Java class or an interface. Service implementations for a service interface are published to the OSGi service registry so that other plug-ins can access them.

Multiple plug-ins can provide a service implementation for the service interface. Plug-ins can access the service implementation via the service interface.

During the declaration of a service, you can specify key-value pairs that configure the service.

A service can be dynamically started and stopped, and plug-ins that use services must be able to handle this dynamic behavior. The plug-ins can register listeners to be informed if a service is started or stopped.

To provide a service, a plug-in needs to be in the ACTIVE lifecycle status.

OSGi provides several ways of defining, providing and consuming services. The following description focuses on the usage of declarative services.

4.1. OSGi declarative services (OSGi DS)

OSGi declarative services (DS) allow you to define and consume services via metadata. Tooling generates this metadata based on annotations in your source code.

The following is an example of using an annotation to define an OSGi service. It provides a service implementation for the TaskService interface.

import org.osgi.service.component.annotations.Component;
// more imports

@Component
public class TransientTaskServiceImpl implements TaskService {
    // as before ...
}

The preferred way of defining OSGi services is to use the annotations, because this is the simplest way. Alternatively, you can define the service component manually via an XML file or use the OSGi API for registering and tracking services. The OSGi API is covered later.

The annotation is used to generate an XML file, which is read once the plug-in gets activated.

OSGi components can be:

  • delayed component: provides a service and is not activated until the service is requested. Class loading and instantiation are delayed until that time.

  • immediate component: is activated as soon as its dependencies are satisfied, does not need to provide a service

  • factory component: creates and activates a new component configuration on request via a ComponentFactory

The following table gives an overview of the OSGi DS terminology. You can skip it and return to it when you need to look up a definition.

Table 2. OSGi DS terminology
Term Definition

Service component

A Java class inside a bundle that is declared via a Component Description and managed by a Service Component Runtime.

Component Description

The declaration of a Service Component, contained in an XML document or defined via annotations that are used to generate the XML document.

Component Configuration

A Component Description that is parameterized with component properties. It is used to track the component dependencies and manages the Component Instance.

Component Instance

The instance of the component implementation class. It is created when a Component Configuration is activated and discarded if the Component Configuration is deactivated.

4.2. Accessing the BundleContext

Sometimes you need to access information about the bundle, for example, the bundle version or the bundle name. Use the FrameworkUtil class from the OSGi framework to access the Bundle and its BundleContext for a class.

Bundle bundle = FrameworkUtil.getBundle(getClass());
BundleContext bundleContext = bundle.getBundleContext();

4.3. Immediately activate service

To activate a service immediately after its dependencies are satisfied, use @Component(immediate = true).

4.4. Setting the start level for declarative services

You need to ensure that the org.apache.felix.scr plug-in is started before any application plug-in that wants to consume a service.

You can ensure this in your run configuration by setting the auto-start field to true and the start level to a value lower than 4 (4 is the default) for the org.apache.felix.scr plug-in.

If you start an Eclipse RCP application via a product, the required plug-ins for declarative services are started automatically. In this case, you do not need to set a start level in your product configuration file.

4.5. Good practice for defining services

It is good practice to define a service via a plug-in which only contains the interface definition. Another plug-in provides the implementation for this service. This allows you to change the implementation of the service via a different plug-in.

4.6. Life cycle

A plug-in that provides a service must be in its ACTIVE lifecycle status. Therefore, ensure that the Activate this plug-in when one of its classes is loaded flag is set in the MANIFEST.MF file. The DS annotation support in Eclipse sets this flag automatically, if you use the default settings.

This flag sets the lazy activation policy via the Bundle-ActivationPolicy header in the manifest. It ensures that the bundle is activated as soon as one of its classes is accessed. This is necessary for the Eclipse runtime (Equinox), while other OSGi launchers often start all bundles.

OSGi services have their own lifecycle, the following states are possible:

State Definition

Enabled/Disabled

The initial enabled state of a component is specified via its component description. All components are disabled when the bundle is stopped. You can change the enabled state programmatically at runtime via the ComponentContext.

UNSATISFIED

The component is not ready to be started, see SATISFIED for the necessary criteria. A component returns to this state if it becomes unsatisfied.

SATISFIED

A component is in this state when it is enabled and the required referenced services are available. If the ConfigurationAdmin is used and configuration-policy=required is specified, a configuration object also needs to be available to bring a component to a satisfied state.

REGISTERED

A component is in the REGISTERED state if it is SATISFIED and not yet requested. Only applies to delayed components.

ACTIVE

The component was activated due to immediate activation or, in case of a delayed component, it was requested.

If the bundle is started, the Service Component Runtime (SCR) checks if the bundle contains component descriptions. It does this by searching for the Service-Component header in the MANIFEST.MF file. If it finds one, it processes the component description and creates a component configuration. If the configuration is enabled, it checks for required references and configurations if necessary. If all of these conditions are met, the component can be activated.

An immediate component is activated as soon as its requirements are fulfilled.

In contrast, a delayed component moves to the REGISTERED state and waits for the first request to its provided service. If a component configuration contains dynamic references, the references can rebind in the ACTIVE state, otherwise it is reactivated. If a component configuration becomes unsatisfied (for example, a bound service becomes unavailable), the component configuration is deactivated. A delayed component is also deactivated and returns to the REGISTERED state if no other bundle uses it anymore.

5. More information on OSGi ds services

5.1. References to other services

OSGi service can define dependencies to other services. You can use the @Reference annotation to define (so-called event) methods to bind to these services and to update them. You can also use it on fields.

Method Description

bind

Called to bind a new service to the component. For static references this method is called before the activate method. For dynamic references this method can also be called while the component is active.

updated

Called when the configuration properties of a bound service are modified.

unbind

Called when the SCR needs to unbind the service. For static references this method is called after the deactivate method. For dynamic references this method can also be called while the component is active.

The bind event methods are typically prefixed with either bind, set or add. The unbind method should have the corresponding prefixes unbind, unset or remove, so they are automatically picked up by @Reference. T

5.2. Defining the service priority

OSGi allows defining multiple service implementations. To define the importance of them, it is possible to define a ranking for a service.

This is done via a service property, called service.ranking. By default, the service ranking is zero, the higher the ranking the more important is the service. The ranking order is defined as follows:

Sorted on descending ranking order (highest first) If the ranking numbers are equal, sorted on ascending service.id property (oldest first)

You can specify these properties via the @ServiceRanking annotation or the property value.

import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.propertytypes.ServiceRanking;

@Component()
@ServiceRanking(5)
public class TestServiceB implements TestService, TestOtherService {

}
@Component(
    property = {
        "service.ranking:Integer=7",
         "another.customproperty=online"
    }
)
public class OnlineDataService implements DataService {

Frameworks like the Eclipse dependency injection framework automatically inject the service with the highest service ranking.

5.3. Configuration

As OSGi service component can be configured via key-value pairs (properties). These are accessed via a Map<String, Object>. These properties can be defined via the following ways:

  • inline

  • Java properties file

  • OSGi Configuration Admin

  • via argument of the ComponentFactory.newInstance method if a factory is used

These configurations options are processed in the above order, so a Java properties file can override inline configuration. The first three options are demonstrated in a later exercise.

5.3.1. Inline

You can add properties to a declarative service component via the @Component annotation property type element. The value of that annotation type element is an array of Strings, which need to be given as key-value pairs in the format <name>(:<type>)?=<value> where the type information is optional and defaults to String.

5.3.2. Java properties file

Another way to configure you OSGi services is to use a Java Properties File that is located inside the bundle.

It can be specified via the @Component annotation properties type element, where the value needs to be an entry path relative to the root of the bundle.

5.3.3. Using the OSGi configuration admin

The configuration admin service allows you to configure properties of services. It uses the PID (Persistent IDentity) of the service, which default to the fully qualified class name. You can also configure it via the configurationPid type element of the @Component annotation.

The configurationPolicy property of the @Component annotation allows you to configure whether a configuration is optional, required, or ignored.

  • ConfigurationPolicy.OPTIONAL - Use the corresponding configuration object if present. This is the default value.

  • ConfigurationPolicy.REQUIRE - Configuration object must be present for the service to satisfy its requirements

  • ConfigurationPolicy.IGNORE - Ignore any corresponding configuration object even if it is present. This means that the component properties cannot be changed dynamically using the configuration admin.

To be able to handle changes in the configuration, you can use a method annotated with @Modified. If you do not do this, the service is stopped and restarted with the new configuration.

6. OSGi Http whiteboard pattern

The OSGi specification allows you to register servlets (web components) via the whiteboard pattern. Deployment descriptors or deployment annotations are not supported.

Via Http Whiteboard it is possible to register:

  • Servlets

  • Servlet Filters

  • Resources

  • Servlet Listeners

The following is an example for a servlet registration via the whiteboard pattern.

@Component(service = Servlet.class ,
           property= "osgi.http.whiteboard.servlet.pattern=/hello",
           scope=ServiceScope.PROTOTYPE)
public class HelloWorldServlet extends javax.servlet.http.HttpServlet {

    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
            resp.getWriter().println("Hello World");
        }
    }

With the above coding a servlet is registered for the /hello path via the predefined osgi.http.whiteboard.servlet.pattern property. The first call will call init(), following calls with call only the servlet via the request and the once the servlet is not used anymore, destroy is called.

Webcomponents should always be registered with the scope Prototype, this ensures that init() and destroy() is never call twice on the same instance. If more than one Http Whiteboard implementation is available in a runtime, the init() and destroy() calls would be executed multiple times, which violates the Servlet specification. It is therefore recommended to use the PROTOTYPE scope for servlets to ensure that every Http Whiteboard implementation gets its own service instance.

7. Using the OSGi console

7.1. Access to the OSGi console for the Eclipse application

If you specify the -console parameter in your run configuration, you can use a console or terminal to interact with the OSGi console.

The OSGi console is like a command-line shell. In this console, you can type commands to perform OSGi actions. This is useful to analyze problems on the OSGi layer of your application.

For example, use the ss command to get an overview of all bundles, their status and their bundle ID. The following table is a reference of the most important OSGi commands.

Table 3. OSGi commands
Command Description

help

Lists the available commands.

ss

Lists the installed bundles and their status.

ss vogella

Lists the bundles that have vogella in their name, and their status.

start <bundle-id>

Starts the bundle with the <bundle-id> ID.

stop <bundle-id>

Stops the bundle with the <bundle-id> ID.

diag <bundle-id>

Diagnoses a particular bundle and lists its missing dependencies.

install URL

Installs a bundle from a URL.

uninstall <bundle-id>

Uninstalls the bundle with the <bundle-id> ID.

bundle <bundle-id>

Shows information about the bundle with the <bundle-id> ID, including the registered and used services.

headers <bundle-id>

Shows the MANIFEST.MF information for a bundle.

services filter

Shows all available services and their consumers. The filter is an optional LDAP filter, for example, services (objectClass=*ManagedService) shows all services that provide a ManagedService implementation.

scr:list

Lists all DS components.

scr:info <id>

Shows detailed information for a selected DS component.

Add -noExit to keep the OSGi console running, even if the application fails to start. This is useful for error analysis.

7.2. Required bundles

The following plug-ins are necessary to use the OSGi console. The easiest way to add them to your Eclipse application is via the run configuration.

  • org.eclipse.equinox.console

  • org.apache.felix.gogo.command

  • org.apache.felix.gogo.runtime

  • org.apache.felix.gogo.shell

7.3. Accessing the OSGi console via a telnet client

To access your application with a Telnet client, add a port to the -console parameter.

-console 5555

Connect to this port with a Telnet client, for example, with telnet localhost 5555. In an OSGi console accessed via Telnet, you can use tab completion and a command history similar to the Bash shell on Linux.

Do not specify a port if you want to access the OSGi console via the Console view of the Eclipse IDE. Use only -console for this.

7.4. Access to the Eclipse OSGi console

You can also access the OSGi console of your running Eclipse IDE. In the Console view, use the Open Console drop-down button in the view toolbar and select Host OSGi Console.

Interfering with your running Eclipse IDE via the OSGi console may put the Eclipse IDE into a bad state.

The plug-in development tooling (PDE) must be installed in Eclipse. If you can create plug-ins using the wizard, PDE is installed.

8. Exercise overview

OSGi bundles / plug-ins can be used in standalone OSGi runtime or in an Eclipse RCP application. This text focuses on the usage of OSGi in a standalone environment. If you want to learn about OSGi in RCP, please see our commercial training at Eclipse RCP training.

8.1. Download the Eclipse IDE

To develop Eclipse plug-ins and applications, you need to download an Eclipse IDE suitable for this task. The Eclipse project offers multiple downloads specifically for this purpose. The most commonly used options are:

  • The Eclipse SDK (Software Development Kit) provides the minimal functionality needed for Eclipse-based development.

  • The Eclipse IDE for Eclipse Committers package provides additional functionality, such as Git support.

This chapter describes both alternatives, with the SDK download being the recommended one.

The appearance of the download page may change over time.

8.1.1. Alternative 1: Download the Eclipse SDK

Open https://download.eclipse.org/eclipse/downloads/ and click the link for the most recent release (the highest version number). The download section looks similar to the following screenshot.

Eclipse.org Download Page

The download format depends on your platform:

  • Windows uses the zip format.

  • Linux uses the tar.gz format.

  • macOS uses the DMG (Disk Image) format.

The Eclipse SDK does not include a Java runtime, so you need to provide one separately.

8.1.2. Alternative 2: Download the Eclipse IDE for Eclipse Committers package

Download the Eclipse IDE for Eclipse Committers package from the Eclipse packages website.

Eclipse.org download page entry for the Eclipse IDE for Eclipse Committers package

This package includes a Java runtime, so you do not need to install one separately.

Alternatively, you can install this package with the Eclipse installer. The installer is useful if you want to install several versions of Eclipse, as it uses a shared installation pool for common plug-ins, which reduces the required space.

include::../10_Include/Eclipse/simple-plugin.adoc[]/ == Exercise: Create a plug-in for the task data model

In this exercise, you create a plug-in that contains the data model and exports its package as API for other plug-ins.

8.2. Create the plug-in for the data model

Create a simple plug-in project named com.vogella.tasks.model via File  New  Other…​  Plug-in Development  Plug-in Project.

Creating a simple plug-in

The following screenshot depicts the second page of the plug-in project wizard and its corresponding settings.

Second page of the New Plug-in Project wizard

Press the Finish button on this page to avoid using a template.

8.3. Create the base class

Create the com.vogella.tasks.model package and the following model class.

package com.vogella.tasks.model;

import java.time.LocalDate;

public class Task {

    private final long id;
    private String summary = "";
    private String description = "";
    private boolean done = false;
    private LocalDate dueDate = LocalDate.now();
}
You see an error for your final id field. The next section solves this error.

8.4. Generate two constructors

Generate two constructors:

  • a constructor using only the id field

  • a constructor using all fields

Use the Source  Generate Constructor using Fields…​ menu entry for this.

Ensure that you have created both constructors, because they are required in the following exercises.

8.5. Generate getter and setter methods

Use the Source  Generate Getter and Setter…​ menu to create getters and setters for your fields.

Why is the id field marked as final?

The id field is final, therefore Eclipse generates only a getter method for it. This is intentional. Since this field is used in the equals() and hashCode() methods, it should remain immutable. Changing a field that is used in the equals() and hashCode() methods can create bugs that are hard to identify, for example, an object is contained in a HashSet but not found.

Generate Getters and Setters dialog with the Task fields

At this point, the resulting class should look like the following listing.

package com.vogella.tasks.model;

import java.time.LocalDate;

public class Task {

    private final long id;
    private String summary = "";
    private String description = "";
    private boolean done = false;
    private LocalDate dueDate = LocalDate.now();

    public String getSummary() {
        return summary;
    }

    public void setSummary(String summary) {
        this.summary = summary;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public boolean isDone() {
        return done;
    }

    public void setDone(boolean done) {
        this.done = done;
    }

    public LocalDate getDueDate() {
        return dueDate;
    }

    public void setDueDate(LocalDate dueDate) {
        this.dueDate = dueDate;
    }

    public long getId() {
        return id;
    }

    public Task(long id, String summary, String description, boolean done, LocalDate dueDate) {
        this.id = id;
        this.summary = summary;
        this.description = description;
        this.done = done;
        this.dueDate = dueDate;
    }

    public Task(long id) {
        this.id = id;
    }
}

8.6. Generate the toString() method

Use the Source  Generate toString()…​ menu entry to generate a toString() method for the Task class based on the id and summary fields.

Generate toString dialog with the fields id and summary selected

8.7. Generate the hashCode() and equals() methods

Use the Source  Generate hashCode() and equals()…​ menu entry to generate the hashCode() and equals() methods based on the id field. Select the Use Objects.hash and Objects.equals methods (1.7 or higher) option.

Generate hashCode() and equals() dialog with the id field selected

8.8. Write a copy() method

Add the following copy() method to the class.

public Task copy() {
    return new Task(id, summary, description, done, dueDate);
}

8.9. Add constants for its fields

To refer to the fields of Task later, create the following constants in the class.

public static final String FIELD_ID = "id";
public static final String FIELD_SUMMARY = "summary";
public static final String FIELD_DESCRIPTION = "description";
public static final String FIELD_DONE = "done";
public static final String FIELD_DUEDATE = "dueDate";

8.10. Validate

The resulting Task class should now look similar to the following.

package com.vogella.tasks.model;

import java.time.LocalDate;

public class Task {

    public static final String FIELD_ID = "id";
    public static final String FIELD_SUMMARY = "summary";
    public static final String FIELD_DESCRIPTION = "description";
    public static final String FIELD_DONE = "done";
    public static final String FIELD_DUEDATE = "dueDate";

    private final long id;
    private String summary = "";
    private String description = "";
    private boolean done = false;
    private LocalDate dueDate = LocalDate.now();

    public Task(long id, String summary, String description, boolean done, LocalDate dueDate) {
        this.id = id;
        this.summary = summary;
        this.description = description;
        this.done = done;
        this.dueDate = dueDate;
    }

    public Task(long id) {
        this.id = id;
    }

    public String getSummary() {
        return summary;
    }

    public void setSummary(String summary) {
        this.summary = summary;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public boolean isDone() {
        return done;
    }

    public void setDone(boolean done) {
        this.done = done;
    }

    public LocalDate getDueDate() {
        return dueDate;
    }

    public void setDueDate(LocalDate dueDate) {
        this.dueDate = dueDate;
    }

    public long getId() {
        return id;
    }

    @Override
    public String toString() {
        return "Task [id=" + id + ", summary=" + summary + "]";
    }

    public Task copy() {
        return new Task(id, summary, description, done, dueDate);
    }

    @Override
    public int hashCode() {
        return Long.hashCode(id);
    }

    @Override
    public boolean equals(Object obj) {
        return this == obj || (obj instanceof Task other && id == other.id);
    }
}

8.11. Define a service interface to access tasks

Create the following TaskService interface.

package com.vogella.tasks.model;

import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;

public interface TaskService {

    /**
     * Returns all tasks.
     */
    List<Task> getAll();

    /**
     * Updates an existing task or creates a new task.
     */
    boolean update(Task task);

    /**
     * Returns the task with the given id or an empty Optional if no such task exists.
     */
    Optional<Task> get(long id);

    /**
     * Deletes the task with the given id.
     *
     * @return true if the task existed and was deleted, false otherwise
     */
    boolean delete(long id);

    /**
     * Passes all tasks to the given consumer.
     */
    void consume(Consumer<List<Task>> tasksConsumer);
}

8.12. Define the API of the model plug-in

Export the com.vogella.tasks.model package to define it as API.

For this, open the MANIFEST.MF file and select the Runtime tab. Add com.vogella.tasks.model to the exported packages.

Runtime tab with the exported package com.vogella.tasks.model

8.13. Next steps

In the following exercises, you implement and use an OSGi service that uses this data model.

9. Exercise: Create a run configuration for your OSGi application

In this exercise, you set up a run configuration for your OSGi runtime in the Eclipse IDE.

9.1. Create run configuration

Select Run  Run Configurations…​ from the top-level menu. Select OSGi Framework and press the new button.

osgi runtime setup10

On the Bundles tab, deselect all existing selection and add the following framework plug-ins:

  • org.eclipse.osgi

  • org.eclipse.osgi.util

  • org.osgi.service.component

  • org.osgi.util.function

  • org.osgi.util.promise

  • org.eclipse.equinox.console

  • org.apache.felix.gogo.shell

  • org.apache.felix.gogo.runtime

  • org.apache.felix.gogo.command

  • org.apache.felix.scr

Also add your plug-in to the runtime.

  • com.vogella.tasks.model

As a result the following bundles should be selected.

osgi runtime setup20

9.2. Check arguments

Switch to the Arguments tag.

The generated run configuration uses the -console parameter by default. Also it specifies the VM argument, eclipse.ignoreApp and osgi.noShutdown.

osgi runtime setup30

9.3. Equinox startup parameters

9.3.1. Parallel bundle activation

Equinox supports parallel bundle activation during start-level processing via the equinox.start.level.thread.count system property.

  • By default, this is set to 1, which means bundles are activated sequentially.

  • Set it to a higher value (e.g., 4 or 8) to activate bundles in parallel using that many threads.

  • Set it to 0 to use the number of available CPUs as the thread count.

To enable parallel activation with 4 threads, add the following VM argument:

-Dequinox.start.level.thread.count=4

To let Equinox choose the thread count based on the available CPUs:

-Dequinox.start.level.thread.count=0

Parallel bundle activation can improve startup time for applications with many bundles, but may surface concurrency issues in bundles that are not thread-safe during activation.

9.3.2. Resolver batch size

The equinox.resolver.revision.batch.size system property controls how many bundle revisions are resolved in a single batch during bulk resolution (e.g., at startup when many unresolved bundles need wiring).

  • By default, this is set to 2147483647 (unlimited), meaning all revisions are resolved in one batch.

  • Setting a smaller value (e.g., 50 or 100) breaks the work into smaller chunks.

  • This serves as a memory safety mechanism: if a batch causes an OutOfMemoryError or times out (controlled by equinox.resolver.batch.timeout, in milliseconds), the resolver falls back to resolving bundles one by one, which is slower but uses less memory.

In applications with many bundles that declare a large number of package imports, bulk resolution can consume significant memory. In such cases, setting a smaller batch size may be required to avoid OutOfMemoryError during startup.

To limit batch resolution to 100 revisions at a time:

-Dequinox.resolver.revision.batch.size=100

9.4. Run your OSGi application

Press the Run button to start your OSGi runtime.

By default, this starts an OSGi runtime with your modules.

As you specified the `-console ` parameter, you can use OSGi console commands.

Type ss into the console view to get an overview of the plug-ins and their status. The model plug-in should be listed and in RESOLVED or in the ACTIVE status, if the Default Auto-Start setting on the Bundles tab is set to true.

osgi runtime setup40

In the next exercise you define an OSGi service that uses this plug-in.

9.5. Save a shared file

To share your run configuration, you can also save the file.

osgi runtime setup50

9.6. Terminate running OSGi instance

As you specified the osgi.noShutdown=true flag, the OSGi application will not shutdown. This is useful if the OSGi instance provides services to the outside.

Use the exit command or the terminate button to terminate the running instance.

osgi runtime setup60

If you have multiple instance running, you can use the drop-down button in Eclipse to switch to them.

osgi runtime setup70

10. Exercise: Implementing and providing an OSGi service

In this exercise, you create a plug-in for a service implementation. This implementation provides access to the task data.

This service implementation uses transient data storage, that is, the data is not persisted between application restarts. A later exercise persists the data as JSON on the file system.

10.1. Create a new plug-in for the OSGi service

Create a new simple plug-in project called com.vogella.tasks.services.

macOS treats folders ending with .service as special, therefore this exercise uses the .services ending.

10.2. Define the dependencies in the service plug-in

To use classes from other plug-ins in a plug-in, you need to add a dependency on them.

Open the MANIFEST.MF file of the com.vogella.tasks.services plug-in. Select the Dependencies tab and add the com.vogella.tasks.model package to the Imported Packages section.

Dependencies tab of the services plug-in importing the com.vogella.tasks.model package

On the MANIFEST.MF tab, the file should look similar to the following listing.

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Services
Bundle-SymbolicName: com.vogella.tasks.services
Bundle-Version: 1.0.0.qualifier
Import-Package: com.vogella.tasks.model
Bundle-Vendor: VOGELLA
Automatic-Module-Name: com.vogella.tasks.services
Bundle-RequiredExecutionEnvironment: JavaSE-25

10.3. Enable annotation processing and plug-in activation

To generate OSGi service metadata based on annotations in classes, enable the Generate descriptors from annotated sources option under Window  Preferences  Plug-in Development  DS Annotations.

Activate DS annotation processing

10.4. Provide an implementation of the TaskService interface

The implementation uses the @Component annotation. PDE adds the DS annotations to the build path of the plug-in automatically, so you do not need to import their package in the MANIFEST.MF file.

Create the com.vogella.tasks.services.internal package in your service plug-in and add the following class.

package com.vogella.tasks.services.internal;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

import org.osgi.service.component.annotations.Component;

import com.vogella.tasks.model.Task;
import com.vogella.tasks.model.TaskService;

@Component (1)
public class TransientTaskServiceImpl implements TaskService {

    private static AtomicInteger current = new AtomicInteger(1);
    private List<Task> tasks;

    public TransientTaskServiceImpl() {
        tasks = createTestData();
    }

    @Override
    public List<Task> getAll() {
        return tasks.stream().map(Task::copy).toList();
    }

    @Override
    public void consume(Consumer<List<Task>> taskConsumer) {
        // always pass a new copy of the data
        taskConsumer.accept(tasks.stream().map(Task::copy).toList());
    }

    // create or update an existing instance of a task object
    @Override
    public synchronized boolean update(Task newTask) {
        // hold the Optional object as a reference to determine if the object is
        // newly created or not
        Optional<Task> taskOptional = findById(newTask.getId());

        // get the actual object or create a new one
        Task task = taskOptional.orElseGet(() -> new Task(current.getAndIncrement()));
        task.setSummary(newTask.getSummary());
        task.setDescription(newTask.getDescription());
        task.setDone(newTask.isDone());
        task.setDueDate(newTask.getDueDate());

        if (taskOptional.isEmpty()) {
            tasks.add(task);
        }
        return true;
    }

    @Override
    public Optional<Task> get(long id) {
        return findById(id).map(Task::copy);
    }

    @Override
    public boolean delete(long id) {
        Optional<Task> deletedTask = findById(id);
        deletedTask.ifPresent(tasks::remove);
        return deletedTask.isPresent();
    }

    // Example data, change if you like
    private List<Task> createTestData() {
        // due dates relative to today so that every task category has entries
        List<Task> list = List.of(create("Application model", "Dynamics", -2, false),
                create("Application model", "Flexible and extensible", -1, false),
                create("DI", "@Inject as programming mode", 0, false),
                create("OSGi", "Services", 0, false),
                create("SWT", "Widgets", 0, false),
                create("JFace", "Especially Viewers!", 1, false),
                create("CSS Styling", "Style your application", 3, false),
                create("Eclipse services", "Selection, model, Part", 7, false),
                create("Renderer", "Different UI toolkit", -5, true),
                create("Compatibility Layer", "Run Eclipse 3.x", -3, true));
        return new ArrayList<>(list);
    }

    private Task create(String summary, String description, int daysFromToday, boolean done) {
        return new Task(current.getAndIncrement(), summary, description, done, LocalDate.now().plusDays(daysFromToday));
    }

    private Optional<Task> findById(long id) {
        return tasks.stream().filter(t -> t.getId() == id).findAny();
    }
}
1 The @Component annotation triggers the generation of OSGi metadata that registers the class as an OSGi service for the TaskService interface.

The test data uses due dates relative to today and marks some tasks as done, so later exercises have overdue, due, upcoming and done tasks to show.

PDE writes this metadata as an XML file into the OSGI-INF folder of the plug-in. Ensure that OSGI-INF/ is selected in the Binary Build section of the Build tab of the manifest editor, otherwise the exported plug-in does not contain the component description.

10.5. Check manifest

The full manifest looks similar to the following listing.

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Services
Bundle-SymbolicName: com.vogella.tasks.services
Bundle-Version: 1.0.0.qualifier
Import-Package: com.vogella.tasks.model
Service-Component: OSGI-INF/com.vogella.tasks.services.internal.TransientTaskServiceImpl.xml
Bundle-Vendor: VOGELLA
Automatic-Module-Name: com.vogella.tasks.services
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-25

10.6. Add the new plug-in to the product

Add your new plug-ins to your feature.

  • com.vogella.tasks.services

  • com.vogella.tasks.model

Ensure that you use the Included Plug-ins tab of the feature.xml editor. Start your application via your product configuration to ensure that the plug-ins added to the feature are correctly included in the runtime.

Every time you create a new plug-in and refer to it in your MANIFEST.MF file, you have to add it to your feature (feature.xml) to include it in your product.

10.7. Further steps

By default, the OSGi runtime initializes and provides services on demand. Therefore, an OSGi service only starts once it is consumed. This is done in the next exercises.

11. Exercise: Consume the OSGi service using an immediate OSGi component

In this exercise, you will create another OSGi component that consumes the created service. As this OSGi component is not a service, it is called an immediate component and activated as soon as its dependencies are satisfied.

OSGi does not use JSR 330 (with @Inject) for this purpose but uses its custom (compile-time) annotations for this.

Ensure you enabled the DS Annotations setting in the preferences of Eclipse to ensure that these annotations are used to generate the necessary configuration files.

11.1. Create a new plug-in

Create another simple plug-in named com.vogella.osgi.taskconsumer.

11.2. Add manifest dependencies

Add the com.vogella.tasks.model package dependency in the manifest to use the TaskService interface. The @Component annotation needs no manifest entry, PDE adds the DS annotations to the build path automatically.

A dependency to com.vogella.task.services is NOT necessary, the implementation is provided by OSGi without direct dependency.

11.3. Add an immediate OSGi component

Use the @Component annotation to add another OSGi component via a class named TaskConsumer.

Receive the TaskService the @Reference annotation, either on a field or on a method.

Also implement a method annotated with the @Activate annotation to write to the number of tasks to the console. This tasks can be received from the service.

Solution example
package com.vogella.osgi.taskconsumer;

import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

import com.vogella.tasks.model.TaskService;

@Component
public class TaskConsumer {

    @Reference
    TaskService taskService;

// The above shows a field reference, you could also use @Reference on a method
//  private void bindTaskService(TaskService taskService) {
//      this.taskService = taskService;
//      System.out.println("Injected " + taskService);
//  }
//
//  @SuppressWarnings("unused")
//  private void unbindTaskService(TaskService taskService) {
//      System.out.println("Removed " + taskService);
//      taskService = null;
//  }

    @Activate
    public void activate() {
        System.out.println("Activate called");
        System.out.println("Number of tasks: " + taskService.getAll().size());
    }
}

Also add Require-Capability: osgi.service;filter:="(objectClass=com.vogella.tasks.model.TaskService)" to your MANIFEST.MF, indicating that such a service is required for this plug-in to work. The resulting MANIFEST should look like the following, Service-Component and Bundle-ActivationPolicy are added by the tooling.

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Taskconsumer
Bundle-SymbolicName: com.vogella.osgi.taskconsumer
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: VOGELLA
Automatic-Module-Name: com.vogella.osgi.taskconsumer
Bundle-RequiredExecutionEnvironment: JavaSE-25
Require-Capability: osgi.service;filter:="(objectClass=com.vogella.tasks.model.TaskService)"
Import-Package: com.vogella.tasks.model
Service-Component: OSGI-INF/com.vogella.osgi.taskconsumer.TaskConsumer.xml
Bundle-ActivationPolicy: lazy

11.4. Add to your product

Add the new plug-in to your product via your feature.

11.5. Validate

Start again via the product and ensure you see the output of the @Activate on the console.

immediate component osgi 10

11.6. Remove the System.out statement

Remove the System.out statements in TaskConsumer, these are only useful to demonstrate the functionality but should be removed to avoid unnecessary output on the console in future exercises.

12. Exercise: Validate

Add your com.vogella.osgi.taskconsumer plug-in to your run configuration. Start your OSGi runtime and observe the output of the your console. You should see the output of the OSGi service consumption.

13. Exercise: Create a feature based product for the runtime configuration

To manage the plug-ins included in the OSGi runtime, you can use a product configuration file. While the product originates from the Eclipse IDE and RCP development point of view and is not perfect for OSGi development, it can still be handy to have one place to configure your run configuration.

This product can also be used for a Maven Tycho command line build.

In this exercise, you create one product based on features.

13.1. Create a feature project

Create a feature project named com.vogella.osgi.feature via File  New  Other…​  Plug-in Development  Feature Project.

osgi feature10

If you have already an existing launch configuration you can use that to populate the feature.

osgi feature20

Open the feature.xml file and switch to the _Included Plug-ins` tag. Ensure the following framework plug-ins are included in your feature.xml file:

  • org.eclipse.osgi

  • org.eclipse.osgi.util

  • org.osgi.service.component

  • org.osgi.util.function

  • org.osgi.util.promise

  • org.eclipse.equinox.console

  • org.apache.felix.gogo.shell

  • org.apache.felix.gogo.runtime

  • org.apache.felix.gogo.command

  • org.apache.felix.scr

Also ensure that your plug-in is part of the feature.

  • com.vogella.tasks.model

13.2. Create a project to host the product configuration file

Create a new project named com.vogella.osgi.runtime of type General via the Menu  File  New  Project  General  Project menu entry.

13.3. Create a product configuration file

Right-click on the com.vogella.osgi.runtime project and select New  Other…​  Plug-in Development  Product Configuration.

Create a product configuration file called osgi-runtime.product inside the main folder of the project using basis settings.

osgi runtime.product10

Press the Finish button. The file is created and opened in an editor.

On the Overview tab ensure the following settings:

  • ID = com.vogella.osgiruntime

  • Version = 1.0.0.qualifier

  • Uncheck The product includes native launcher artifacts

Leave the product and application empty. Product and Application are used in RCP products, and therefore not needed for a headless OSGi command line application.

Also set that the product is based on features.

osgi runtime.product20

13.4. Add your feature as content

On the Contents tab, add your feature as content.

osgi runtime.product30

13.5. Configuration

Configure the Start Levels:

  • org.apache.felix.scr, StartLevel = 2, Auto-Start = true

Equinox does not automatically activate any bundle. Bundles are only activated if a class is directly requested from it. But the service component runtime is never required directly. So you need to configure that it is activated, otherwise, org.apache.felix.scr will never get activated.

Configure the following properties:

Properties Description

eclipse.ignoreApp = true

Tells Equinox to skip trying to start an Eclipse application.

osgi.noShutdown = true

The OSGi framework will not be shut down after the Eclipse application has ended. You can find further information about these properties in the Equinox Framework QuickStart Guide and the Eclipse Platform Help.

osgi runtime.product40

13.6. Adjust launch configuration

The above configuration is used for the exported application. You also have to configure the parameters for the start from the IDE.

Add on the Launching tab:

  • On the Program Arguments: -console

  • On the VM Arguments: -Declipse.ignoreApp=true -Dosgi.noShutdown=true

When adding the parameters in the Launching tab instead of the Configuration tab, the configurations are added to the eclipse.ini in the root folder, not to the config.ini in the configuration folder. When starting the application via the exported jar, the eclipse.ini in the root folder is not inspected.

osgi runtime.product50

13.7. Validate

Click on the Run button to start your OSGi command line application.

If the system shows an validation error that the application is not maintained, you can ignore that.

14. Optional exercise: Use the OSGi console

In this exercise, you learn how to use the OSGi console to analyze the state of your plug-ins at runtime.

Ensure that the -console parameter is set as program argument in your run configuration.

Run Configurations dialog with the console argument

To use the console, the following plug-ins must be included in your runtime:

  • org.eclipse.equinox.console

  • org.apache.felix.gogo.command

  • org.apache.felix.gogo.runtime

  • org.apache.felix.gogo.shell

If you followed this description, this should already be the case.

Click the Validate Plug-ins button to ensure that all dependencies are included. If something is missing, use the Select Required button to add it.

Plug-ins tab with the console plug-ins and the Select Required button

Start your application. In the Console view of Eclipse, use the following commands:

  • ss: shows the short status of your plug-ins, including their IDs

  • bundle <id-of-the-plug-in>: shows an overview of the bundle, including its registered and used services

  • diag <id-of-the-plug-in>: shows unresolved dependencies of the bundle

  • scr:list: shows all OSGi components

  • scr:list <id-of-the-plug-in>: shows the OSGi components of that plug-in

Optional: Run stop <id> for your com.vogella.tasks.services plug-in, check its state with ss and run scr:list again. The TransientTaskServiceImpl component is no longer listed. Run start <id> to start the plug-in again.

15. Exercise: Extend the OSGi console with additional commands

In this exercise, you learn how to add additional commands to the OSGi console.

15.1. Create a new plug-in and its dependencies

Create a new plug-in named com.vogella.osgi.console.

Add the org.eclipse.osgi.framework.console package as imported package dependency to its manifest.

15.2. Create implementation

To extend the OSGi console, provide new class AndQuitCommand implements org.eclipse.osgi.framework.console.CommandProvider as OSGi component via the @Component annotation.

Add methods prefixed with _ are available as additional commands.

For example, you can add the following method to provide the add command that takes two parameters.

import org.eclipse.osgi.framework.console.CommandInterpreter;
// more code
public void _add(CommandInterpreter ci) {
        int a = Integer.parseInt(ci.nextArgument());
        int b = Integer.parseInt(ci.nextArgument());
        ci.println(a+b);
    }
Solution example
package com.vogella.osgi.console;

import org.eclipse.osgi.framework.console.CommandInterpreter;
import org.eclipse.osgi.framework.console.CommandProvider;
import org.osgi.service.component.annotations.Component;


@Component
public class AndQuitCommand implements CommandProvider {

    public AndQuitCommand() {
        System.out.println("command");
    }
    // add prints sum of its two arguments
    public void _add(CommandInterpreter ci) {
        int a = Integer.parseInt(ci.nextArgument());
        int b = Integer.parseInt(ci.nextArgument());
        ci.println(a+b);
    }

    // quit just calls "exit"
    public void _quit(CommandInterpreter ci) {
        ci.execute("exit");
    }

    @Override
    public String getHelp() {
        return "";
    }

}

15.3. Update product

Add the new plug-ins to your product (via your feature configuration). Start via the product to ensure that it is added to the run configuration.

15.4. Validate

Check that you can use your new commands in the OSGi shell, e.g., add 1 2 or quit.

extend osgi console result

16. Exercise: Use fragments for writing tests

This exercise demonstrates how to create and run unit and integration tests for OSGi services from the Eclipse IDE.

You will write simple tests for the com.vogella.tasks.services plug-in which does not export any API but contains the implementation classes.

16.1. Update your target platform

Add the following to your target platform from the https://download.eclipse.org/releases/latest update site: The easiest way to do this is to use the Source tab of the target editor which also provides code completion.

  • org.junit.jupiter.engine

16.2. Create fragment project for your test

Create a new fragment project called com.vogella.tasks.services.tests via the File  New  Other…​  Plug-in Development  Fragment Project menu entry.

osgi testing10
osgi testing12

Enter com.vogella.tasks.services as host plug-in.

osgi testing20

16.3. Update your manifest dependencies

Add the following plug-in dependencies to your test fragment.

  • junit.jupiter.api

  • org.apache.felix.scr

OSGi promotes the usage of package dependencies to make the design more flexible. For the test, we use plug-in dependencies in this example to make the setup faster.

16.4. Write a JUnit unit test

First you implement a unit test, without considering the OSGi environment.

Right-click on the project and select New  Other…​  Java  JUnit Test Case.

osgi testing30

Also add JUnit 5 to your build path.

osgi testing40

Implement the following class.

package com.vogella.tasks.services.tests;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import com.vogella.tasks.services.internal.TransientTaskServiceImpl;

class TransientTaskServiceImplTests {

    @Test
    @DisplayName("TransientTaskServiceImpl can be initialized")
    void assertThatTaskServiceCanBeInitialized() {
        TransientTaskServiceImpl service = new TransientTaskServiceImpl();
        assertNotNull(service);
        assertTrue(service.getAll().size()>0);
    }


    @Test
    @DisplayName("TransientTaskServiceImpl provides at least one task ")
    void assertThatTaskServiceProvidesData() {
        TransientTaskServiceImpl service = new TransientTaskServiceImpl();
        assertTrue(service.getAll().size()>0);
    }

}

16.5. Validate that the JUnit test executes

Right-click the test and select Run-as  JUnit test. The tests should both execute successfully.

osgi testing50

16.6. Write an integration test considering the OSGi service environment

In the above test you did not use the OSGi service layer but initialized the service yourself. In the next test, you will test if the OSGi server layer correctly initialized the service. This is especially necessary if the services to test reference other services or OSGi features are used, like the EventAdmin for event processing or the ConfigurationAdmin to configure components at runtime.

Create the following unit test.

package com.vogella.tasks.services.tests;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import org.osgi.util.tracker.ServiceTracker;

import com.vogella.tasks.model.TaskService;

public class TransientTaskServiceIntegrationTest {

    @Test
    public void assertServiceAccessWithOSGiContextWorks() {
        TaskService taskService = getService(TaskService.class);
        assertNotNull(taskService, "No TaskService found");
    }

    static <T> T getService(Class<T> clazz) {
        Bundle bundle = FrameworkUtil.getBundle(TransientTaskServiceIntegrationTest.class);
        if (bundle != null) {
            ServiceTracker<T, T> st =
                new ServiceTracker<T, T>(
                    bundle.getBundleContext(), clazz, null);
            st.open();
            if (st != null) {
                try {
                    // give the runtime some time to startup
                    return st.waitForService(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }
}

16.6.1. Validate in the IDE

Right-click the test and select Run-as  JUnit Plug-in test.

16.7. Add to the Tycho build

Add your test plug-in as module to an existing Tycho build. In case you are using pomless builds, this should allow to execute the tests on the command.

Maven Tycho support for JUnit 5 did show in the past sometimes issues. If you face such an issue, temporary disable the test and report to https://bugs.eclipse.org/bugs/enter_bug.cgi?product=Tycho

The test plug-in should successfully execute the tests.

17. Using the OSGi service API

The preferred way of defining and providing OSGi services is OSGi DS, but you can also use the OSGi API for this. This chapter describes this API, in case you need it.

17.1. Registering services via the BundleContext

To publish a service in your bundle, use the following code.

BundleContext bundleContext = FrameworkUtil.getBundle(getClass()).getBundleContext();
ServiceRegistration<IMyService> registration =
    bundleContext.registerService(IMyService.class, new MyServiceImpl(), null);

In the registerService() method of the BundleContext class, you can specify arbitrary properties in the dictionary parameter. Consumers read a property via the getProperty() method of the ServiceReference class from the org.osgi.framework package.

To remove the service from the service registry, call unregister() on the returned ServiceRegistration.

registration.unregister();

17.2. Accessing a service via API

A bundle can acquire a service via the BundleContext class. The following code demonstrates this.

BundleContext bundleContext = FrameworkUtil.getBundle(getClass()).getBundleContext();
ServiceReference<IMyService> serviceReference = bundleContext.getServiceReference(IMyService.class);
if (serviceReference != null) {
    IMyService service = bundleContext.getService(serviceReference);
    // use the service
}

The getServiceReference() method returns null if no such service is registered.

OSGi counts the usage of services to enable their dynamic replacement. Once your implementation no longer uses a service, release it as shown in the following code.

bundleContext.ungetService(serviceReference);

17.3. Using an activator for service registration

Avoid using activators as they can slow down the startup of your application.

A bundle can define an activator class via the Bundle-Activator header in its manifest. This class must implement the BundleActivator interface. OSGi passes the BundleContext to its start() and stop() methods.

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class Activator implements BundleActivator {

    @Override
    public void start(BundleContext context) throws Exception {
        System.out.println("Starting bundle");
        // for example, register services
    }

    @Override
    public void stop(BundleContext context) throws Exception {
        System.out.println("Stopping bundle");
        // for example, unregister services
    }
}

18. OSGi Resources

19. OSGi services resources

Home Tutorials Training Consulting Books Company Contact us


Get more...