This tutorial gives an overview of OSGi and its modularity layer using Eclipse Equinox.

1. Introduction into software modularity with OSGi

OSGi is a set of specifications.

Its core specification defines a component and service model for Java.

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

An application consists of different components and services. A solid and clean software architecture consists of components which 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, which explicitly defines its dependencies to other components and services.

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

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.

Java 9 provides its own module system to describe software component dependencies. But the Java module system has not yet been adapted by the module system used by Eclipse.

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 meta information. The Java specification defines that additional key/values can be added to this file without affecting runtimes which do not understand these key/value combinations. Therefore, OSGi plug-ins can be used without restrictions in other Java environments.

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

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Popup 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-1.6

The following table gives an overview of the OSGi meta-data 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 is providing an extension or an extension point it must be marked as Singleton. You do this by adding the following statement after the Bundle-SymbolicName identifier: ; singleton:=true

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 optional activator class which 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 OSGi activator can be used to configure the plug-in during startup. The execution of an activator may increase the startup time of the application, therefore this functionality should be avoided if possible.

Bundle-RequiredExecutionEnvironment (BREE)

Specify 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 but the Eclipse Plug-in development tooling still uses it, hence this is also used in this description.

Bundle-ActivationPolicy

Setting this to lazy instructs the OSGi runtime that this plug-in should be activated if one of its classes and interfaces are used by other plug-ins. Must be in Equinox if the plug-in provides services.

Bundle-ClassPath

The Bundle-ClassPath specifies where to load classes from the bundle. The default is '.' which allows classes to be loaded 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) which 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 then the symbolic name would start with "com.example".

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

<major>.<minor>.<patch/service>

If you change your plug-in code you increase the version according to the following rule set.

  • <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 if all changes are backwards compatible.

For more information on this version scheme see the Version Numbering Wiki.

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 to these plug-ins or their packages. In additional the plug-in must define the used packages as API.

The only exception are packages from the Java runtime environment which by default can be accessed.

All these restrictions are enforced via a specific OSGi classloader. Each plug-in has its own classloader. Access to restricted classes is not possible without using reflection.

Unfortunately OSGi can not prevent you from using Java reflection to access these classes. This is because OSGi is based on the Java runtime which does not yet support a modularity layer.

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 which are not explicitly exported are not visible to other plug-ins.

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

Dependency management

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 to plug-in A or the package

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

Dependency management

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

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

For legacy reasons OSGi supports a dynamic import of packages. You should not use 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

The OSGi runtime reads the manifest file of a plug-in during startup. It ensures that all dependencies are present and can also check their dependencies.

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

In case several plug-ins exist which 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 unique identifier (ID) is used. Every plug-in gets this ID assigned by the framework during the installation.

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

Other OSGi runtimes behave differently, for example Felix always activates all plug-ins.

This life cycle is depicted in the following graphic.

OSGi life cycle

3.5. Defining provisional API via the x-internal and x-friends

OSGi allows you to define provisional API, i.e. packages which can be accessed by other plug-ins but are not yet defined as API. This can be done via the x-internal or the x-friends flag.

Dependency management

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

Setting the x-internal flag

Such a setting would result in the package 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-17
Export-Package: com.vogella.tasks.model;x-internal:=true

x-friends also defines provisional API but also allows you to define a set of plug-ins which can access the provisional API without a warning or error message. x-friends replaces automatically 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-17
Export-Package: com.vogella.tasks.model;x-friends:="another.bundle"

You can configure the Java editor to show an error, warning or to ignore usage of provisional API. The default is to display a warning message. You can adjust this in the Eclipse preferences via the Window  Preferences  Java  Compiler  Errors/Warnings preference setting.

Settings in Eclipse for warnings for deprecated API usage

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, the platform project plans to keep this API stable for as long as possible.

If API is marked as provisional, the platform team can change this API in the future. If you use such API, you must be prepared that you might have to make some adjustments to your application in a future Eclipse release.

If you use unreleased API, 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, via right-click on the project Properties  Java Compiler and afterwards 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 Error/Warnings preference page.

4. OSGi Services

Services provide functionality to 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 the service interface.

During the declaration of a service it is possible to specify key / values which can be used to configure the service.

A service can be dynamically started and stopped, and plug-ins which 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 life cycle 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)

The OSGi declarative services (DS) allows you to define and consume service via meta-data. Tooling allows you to generate service metadata based on annotations in your source code, we cover this in the exercises for the Eclipse IDE.

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 stuff

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

The preferred way of defining OSGi services is using the annotations, as this is the simplest way possible. Alternatively, you can also define and register the service component definition manually via an XML file or use the OSGi API for starting, stopping and tracking services. This is covered later.

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

OSGi service definitions can be:

  • delayed component - service is not activated until requested, this means class and implementation loading is delayed until this time. This type must define a OSGi service

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

  • factory component - creates and activates a new service an request, service is not re-used if it become unsatisfied or unregistered

The following table gives a brief overview of the OSGi ds terminology, you can skip this table and return to it, in case you want to lookup a definition.

Term Definition

Service component

A Java class inside a bundle that is declared via 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 which 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, e.g., the bundle version or the bundle name. Access to the bundle and its bundleContext is performed via the Bundle and BundleContext class.

You can use the FrameworkUtil class from the OSGi framework to access the BundleContext for a class.

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

4.3. Immediately activate service

If you want to activate a service immediately after its dependencies are satisfied, the immediate parameter to the @Component annotation can be used.

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 which 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 lower than 4 (4 is the default value) for the org.apache.felix.src plug-in

The Eclipse RCP framework automatically starts the required plug-ins for using declarative OSGi service. It is not required to manually 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 would provide the implementation for this service. This allows you to change the implementation of the service via a different plug-in.

4.5.1. Life cycle

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

The above will set the Lazy Activation Policy setting via the Bundle-ActivationPolicy key in the manifest. This is necessary for the Eclipse runtime (Equinox), other OSGi runtimes will activate bundle always if their dependencies are fulfilled. This flag ensures that the bundle is activated (auto-started) as soon as one of its classes is accessed.

OSGi Services have their own life cycle, the following states are possible:

State Definition

Enabled/Disabled

The initial enabled state of a component is specified via Component Description. All components are disabled when the bundle is stopped. It is possible to change the enabled state programmatically at runtime via ComponentContext.

UNSATISFIED

The component is not ready to be started. See Satisfied for the necessary criteria. This status can also be reached again if a component is not Satisfied anymore.

Satisfied

A component is in a Satisfied 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 satisfied state.

REGISTERED

A component is in REGISTERED state if it Satisfied and not yet requested. Only applies for 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, an OSGi component called SCR checks if the bundle contains component descriptions. It does that by reading the MANIFEST.MF and searching for Service-Components. If it finds one it will start to process the componentDescription and create a component configuration. If the configuration is enabled, it checks for required references and configurations if necessary. If all of these are satisfied, the component can be activated.

An Immediate Component will activate immediately, a Delayed Component moves to the REGISTERED state, awaiting the first request to the provided service. If a Component Configuration contains dynamic references, the references can rebind in ACTIVE state, otherwise it will be re-activated. If a Component Configuration becomes unsatisfied (e.g. a bound service becomes unavailable), the Component Configuration will be deactivated. Note that a Delayed Component will also be deactivated and gets back to REGISTERED state if no other bundle references 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 to define 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 property.

@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. The Constants class from the org.osgi.framework package defines the service.ranking value via the Constants.SERVICE_RANKING constant.

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 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 to configure, if a configuration is optional, required for 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 can not 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 to register servlets (webcomponets) 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 pre-defined 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 your Eclipse application

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

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

Use, for example, the command ss to get an overview of all bundles, their status and bundle-id. The following table is a reference of the most important OSGi commands.

Table 2. OSGi commands
Command Description

help

Lists the available commands.

ss

Lists the installed bundles and their status.

ss vogella

Lists bundles and their status that have vogella within their name.

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. It lists all 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. Filter is an optional LDAP filter, e.g., to see all services which provide a ManagedService implementation use the "services (objectclass=*ManagedService)" command.

scr:list

lists all DS components

scr:info <id>

dump detailed information for a selected DS component

Add the -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 to add them directly via the runtime 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

If you want to access your application via a Telnet client, you can add another parameter to the -console parameter. This specifies the port to which you can connect via the telnet protocol.

-console 5555

To access such a application, use a telnet client, for example on Linux: telnet localhost 5555 from the command line. In an OSGi console accessed via telnet, you can use tab completion and a history of the commands similar to the Bash shell under Linux.

The specification of the port must be used, 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 you find a menu entry with the tooltip Open Console. If you select Host OSGi Console, you will have access to your running OSGi instance.

Please note that interfering with your running Eclipse IDE via the OSGi console, may put the Eclipse IDE into a bad state.

This requires that the plug-in development tooling (PDE) is installed in your Eclipse IDE. If you are able to create plug-ins via the wizard than the PDE tooling is installed.

8. Exercise overview

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

8.1. Download the Eclipse IDE

The Eclipse IDE provides multiple downloads which be be used to develop Eclipse based applications. The most commonly used are:

  • Eclipse SDK - minimal IDE which you need for RCP development

  • Eclipse IDE for Eclipse Committers package provides additional functionality, e.g. Git support for the Eclipse IDE

  • Eclipse IDE for RCP and RAP Developers package provides even more functionality then the committers package

Download either one of them. We describe two alternatives, the SDK and the RCP and RAP Developers download.

The advantage of the using the SDK download is that it is the minimal set of plug-ins needed for RCP development.

The screenshots in this guide are based on the SDK download.

8.1.1. Alternative 1: Download the Eclipse SDK

To Download the latest release of the Eclipse SDK (Software development kit) build from the SDK download page. Click on the link of the latest released version (the release version with the highest number). The download section should look similar to the following screenshot.

Eclipse.org Download Page

The download is a compressed archive of multiple files. This format depends on you platform:

  • Windows uses the zip format

  • Linux uses the tar.gz format.

  • Mac uses the dmg (Disk Image) format

The Eclipse SDK does not include a Java runtime, you need to provide this separately. If you follow Download the RCP package, you do not need an additional Java installation.

8.1.2. Alternative 2: Download the Eclipse RCP (and RAP) package

Eclipse provides pre-packaged packages which can also be used for RCP development. Open https://www.eclipse.org/downloads/packages/ in your browser and download the Eclipse IDE for RCP and RAP Developers package or download the installer and install this package.

The RCP package also includes an Java runtime, you do not need to install one separately.

You can also use the Eclipse installer to installer this package. See Eclipse installer for information about the installer. The installer is useful, if you want to download several flavors of Eclipse. It uses a shared installation pool for common plug-ins, which reduces the required space.

9. Exercise: Create a plug-in for a data model

In this exercise you create a plug-in which contains the definition of a data model. You also make this data model available to other plug-ins to provide certain packages as API.

9.1. Create the plug-in for the data model

Naming convention: simple plug-in

A plug-in can be generated by Eclipse via the File  New  Other…​  Plug-In Development  Plug-In Project menu entry. The corresponding wizard allows specifying several options. This tutorial calls plug-ins generated with the following options a simple plug-in or simple bundle.

  • No Activator

  • No contributions to the user interface

  • Not a rich client application

  • Generated without a template

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

Creating a simple plug-in

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

Create a second page
Screenshot shows Java 21 but Java 17 is also fine to use.

Press the Finish button on this page to avoid the usage of templates.

9.2. 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. This error is solved in the next section.

9.3. Generate two constructors

Generate two constructors:

  • a constructor using only the id field

  • a constructor using all fields

You can do this via the Select Source  Generate Constructor using Fields…​ menu entry.

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

9.4. 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 is final and therefore Eclipse creates only a getter. This is correct and desired. We will use this field to generate the equals() and hashCode() methods therefore it should not be mutable. Changing a field which is used in the equals() and hashCode() methods can create bugs which are hard to identify, i.e., an object is contained in a HashMap but not found.

Getter and setter generation

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 = "";

    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;
    }

    private String description = "";
    private boolean done = false;
    private LocalDate dueDate = LocalDate.now();

}

9.5. Generate the toString() method

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

toStringForTask

9.6. 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. If possible prefer the 1.7 version, but also without this flag the generated methods are fine.

hashCodeEqualsTask

9.7. Write a copy() method

Add the following copy() method to the class.

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

9.8. Add constants for its fields

To access later the fields on Task 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";

9.9. Validate

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

package com.vogella.tasks.model;

import java.time.LocalDate;
import java.util.Objects;

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(this.id, this.summary, this.description, this.done, this.dueDate);
    }

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

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Task other = (Task) obj;
        return id == other.id;
    }



}

9.10. 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 {

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

    /**
     * Updates existing task or create new task
     *
     * @param task
     * @return
     */
    boolean update(Task task);

    /**
     * Returns an Optional wrapping a task or wrapping null if not task exists for the given id
     *
     * @param task
     * @return empty optional if not found, otherwise Optional holder the task
     */

    Optional<Task> get(long id);

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

    /**
     * Allow to specify a consumer which retrieves all tasks
     *
     * @param tasksConsumer
     */

    void consume(Consumer<List<Task>> tasksConsumer);

}

9.11. 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.

Exported API

9.12. Next Steps

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

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

In this exercise you setup an run configuration for your OSGi runtime in the Eclipse IDE.

10.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.eclipse.osgi.services

  • 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

10.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

10.3. 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 which uses this plug-in.

10.4. Save a shared file

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

osgi runtime setup50

10.5. 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

11. 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, i.e., the data is not persisted between application restarts. A later exercise will persist the data as JSON on the file system.

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

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

The MacOS operating system treats folders ending with .service special, therefore we use the .services ending.

11.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 to them.

To achieve this, open the MANIFEST.MF file of the com.vogella.tasks.services plug-in. Select the Dependencies tab and add the following to the Imported Packages selection:

  • com.vogella.tasks.model

  • org.osgi.service.component.annotations

Ensure you have selected the Imported Packages section. Otherwise, the org.osgi.service.component.annotations package is not visible.

osg service manifest dependencies12

Select the org.osgi.service.component.annotations and press Properties…​.

Mark this package as Optional and ensure you specify the highest available number (currently 1.3.0) as minimun requirement.

osg service manifest dependencies13

The result should look similar to the following screenshot.

OSGi service Manifest dependencies

The text file should look similar to the following.

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,
 org.osgi.service.component.annotations;version="1.3.0";resolution:=optional
Bundle-Vendor: VOGELLA
Automatic-Module-Name: com.vogella.tasks.services
Bundle-RequiredExecutionEnvironment: JavaSE-17

11.3. Enable annotation processing and plug-in activation

You want to generate OSGi service metadata based on annotations in classes. For this, enable the option under Window  Preferences  Plug-in Development  DS annotations.

Activate DS annotation processing

11.4. Provide an implementation of the TaskService interface

Create the com.vogella.tasks.services.internal package in your service plug-in and create 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 java.util.stream.Collectors;

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).collect(Collectors.toList());
    }

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


    // create or update an existing instance of a task object
    @Override
    public synchronized boolean update(Task newTask) {
        // hold the Optional object as 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.orElse(new Task(current.getAndIncrement()));
        task.setSummary(newTask.getSummary());
        task.setDescription(newTask.getDescription());
        task.setDone(newTask.isDone());
        task.setDueDate(newTask.getDueDate());

        if (!taskOptional.isPresent()) {
            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(t -> tasks.remove(t));
        return deletedTask.isPresent();
    }

    // Example data, change if you like
    private List<Task> createTestData() {
        List<Task> list = List.of(
                create("Application model", "Dynamics"), create("Application model", "Flexible and extensible"),
                create("DI", "@Inject as programming mode"), create("OSGi", "Services"),
                create("SWT", "Widgets"), create("JFace", "Especially Viewers!"),
                create("CSS Styling", "Style your application"),
                create("Eclipse services", "Selection, model, Part"),
                create("Renderer", "Different UI toolkit"), create("Compatibility Layer", "Run Eclipse 3.x"));
        return new ArrayList<>(list);
    }

    private Task create(String summary, String description) {
        return new Task(current.getAndIncrement(), summary, description, false,
            LocalDate.now().plusDays(current.longValue()));
    }

    private Optional<Task> findById(long id) {
        return tasks.stream().filter(t -> t.getId() == id).findAny();
    }

}
1 The @Component annotation triggers the generation of OSGi meta-data to the class available as OSGi services for the TaskService interface.

11.5. Add the provided capability to your manifest

The service implementation should define the information for the runtime, that is provides the service. You can do this via the following entry in the manifest.

Provide-Capability: osgi.service; objectClass=com.vogella.tasks.model.TaskService

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,
 org.osgi.service.component.annotations;version="1.3.0";resolution:=optional
Bundle-Vendor: VOGELLA
Automatic-Module-Name: com.vogella.tasks.services
Bundle-RequiredExecutionEnvironment: JavaSE-17
Provide-Capability: osgi.service; objectClass=com.vogella.tasks.model.TaskService

11.6. Add your new plug-in to the product

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

11.7. Further steps

By default, the OSGi runtime initializes and provides OSGi services on demand. Hence you need to consume the OSGI service to start it. This is done in the next exercises.

12. Exercise: Consume the OSGi service via an OSGi immediate component

In this exercise you will create another OSGi component which 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 these annotations are used to generated the necessary configuration files.

12.1. Create a new plug-in

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

12.2. Add manifest dependencies

Add the required package dependencies in the manifest to use the TaskService interface and the @Component annotation. See below solution for the correct entry.

  • com.vogella.tasks.model

  • org.osgi.service.component.annotations, ensure to use as minimum version 1.3.0 and make the resolution optional

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

12.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 task to the console. This tasks can be received from the service.

Show Solution
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-17
Require-Capability: osgi.service;filter:="(objectClass=com.vogella.tasks.model.TaskService)"
Import-Package: com.vogella.tasks.model,
 org.osgi.service.component.annotations;version="1.3.0";resolution:=optional
Service-Component: OSGI-INF/com.vogella.osgi.taskconsumer.TaskConsumer.xml
Bundle-ActivationPolicy: lazy

12.4. Add to your product

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

12.5. Validate

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

immediate component osgi 10

12.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.

13. 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.

14. Exercise: Creating 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 is coming 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.

14.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 popular 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.eclipse.osgi.services

  • 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

14.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.

14.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

14.4. Add your feature as content

On the Contents tab, add your feature as content.

osgi runtime.product30

14.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

14.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

14.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.

15. Exercise: Use 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 in your run configuration as runtime parameter.

console required plug ins04

To use the console, you need to have the following components 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.

Use Validate button to ensure all dependencies are included. If something is missing use the Add Required Plug-ins button to add missing requirements.

console required plug ins10

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

  • ss - see short status of your plug-ins, also shows the ids of the plug-ins

  • bundle <id-of-the-plug-in> - get an overview of the bundle

  • diag <id-of-the-plug-in> - get information about the bundle

  • scr:list - show all the OSGi components

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

16. Exercise: Extend the OSGi console with additional commands

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

16.1. Create a new plug-in and its dependencies

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

Add the following imported package dependencies to its manifest:

  • org.osgi.service.component.annotations

  • org.eclipse.osgi.framework.console

As always, mark org.osgi.service.component.annotations as optional.

16.2. Create implementation

To extend the OSGi console, provide new 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 which take 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);
    }
Show Solution
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 "";
    }

}

16.3. Add to your product / runtime

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

16.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

17. Exercise: Write tests for OSGi plug-ins

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.

17.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.api

  • org.junit

  • org.junit.jupiter.engine

  • org.junit.jupiter.params

  • org.junit.jupiter.migrationsupport

  • org.eclipse.jdt.junit5.runtime

17.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

17.3. 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);
    }

}

17.4. Validate that the JUnit test executes

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

osgi testing50

17.5. Update your manifest dependencies

To access the OSGi API for service in your test, add the following plug-ins as plug-in dependencies to your test plug-in.

  • org.junit

  • org.junit.jupiter.api

  • org.eclipse.osgi

  • org.apache.felix.scr

osgi testing60

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.

org.junit is necessary to avoid java.lang.ClassNotFoundException: org.apache.maven.surefire.junitplatform.JUnitPlatformProvider for JUnit 5 tests. See Bug report

The osgi and felix plug-in are required as runtime to provide the OSGi runtime and the OSGi ds component.

17.6. Write an integration test considering the OSGi service environment

In the above test you didn’t 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;
    }
}

17.6.1. Validate in the IDE

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

17.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.

18. Using the OSGi service low-level API

While the preferred way of defining and providing OSGi services is based on OSGi ds, you can also use OSGi API for this. This chapter describes this API, in case you need it.

18.1. Registering services via the BundleContext

A bundle can also register itself for the events(ServiceEvents) of the BundleContext. These are, for example, triggered if a new bundle is installed or de-installed or if a new service is registered.

To publish a service in your bundle use:

BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
bundleContext.registerService(IMyService.class.getName(), new MzServiceImpl(), null);

Once the service is no longer used, you must unregister the service with OSGi. OSGi counts the usage of services to enable the dynamic replacement of services. So once the service is no longer used by your implementation, unregister it. This is demonstrated by the following code:

context.ungetService(serviceReference);

In the registerService() method from the BundleContext class you can specify arbitrary properties in the dictionary parameter.

You can use the getProperty() method of the ServiceReference class from the org.osgi.framework package, to access a specific property.

18.2. Accessing a service via API

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

BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
ServiceReference<?> serviceReference = bundleContext.getServiceReference(IMyService.class.getName());
IMyService service = (IMyService) bundleContext.getService(serviceReference);

18.3. Using an activator for service registration

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

A bundle can define a Bundle-Activator (Activator) in its declaration. This class must implement the BundleActivator interface.

If defined, OSGi injects the BundleContext into the start() and stop() methods of the implementing Activator class.

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


public class Activator implements BundleActivator {

    public void start(BundleContext context) throws Exception {
        System.out.println("Starting bundle");
        // do something with the context, e.g.
        // register services
    }

    public void stop(BundleContext context) throws Exception {
        System.out.println("Stopping bundle");
        // do something with the context, e.g.
        // unregister service
    }

}

19. OSGi Resources

20. OSGi services resources