Home Tutorials Training Consulting Products Books Company Donate Contact us









Get more...

Training Events

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

1. Introduction into software modularity with OSGi

1.1. What is software modularity?

An application consists of different parts, these are typically called software components or software modules.

These components interact with each other via an Application Programming Interface (API). The API is defined as a set of classes and methods which can be used from other components. A component also has a set of classes and methods which are considered as internal to the software component.

If a component uses an API from another component, it has a dependency to the other component, i.e., it requires the other component exists and works correctly.

A component which is used by other components should try to keep its API stable. This avoids that a change affects other components. But it should be free to change its internal implementation.

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. The OSGi specification defines its own module system, which is a bit more flexible than the Java module system.

1.2. The OSGi specification and OSGi implementations

OSGi is a set of specifications. Its core specification, defines a component and service model for Java. A practical advantage of OSGi is that every software component can define its API via a set of exported Java packages and that every component can specify its required dependencies.

The components 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.

1.3. Plug-in or bundle is a software component

The OSGi specification defines a bundle as the smallest unit of modularization, i.e., in OSGi a software component is a bundle. The Eclipse programming model typically calls them plug-in but these terms are interchangeable. In this tutorial the usage of plug-in is preferred.

A plug-in is a cohesive, self-contained unit, which explicitly defines its dependencies to other components and services. It also defines its API via Java packages.

2. OSGi metadata

2.1. The manifest file (MANIFEST.MF)

Technically, OSGi plug-ins are .jar files with additional meta information in the META-INF/MANIFEST.MF file. This file is called the manifest file. It is part of the standard Java specification. The OSGi specification defines additional metadata for this file. According to the Java specification, any Java runtime must ignore unknown metadata. Therefore, OSGi plug-ins can be used without restrictions in other Java environments.

The following listing is an example for a manifest file.

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 increases the startup time of the application, therefore this functionality should be used carefully.

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.

Bundle-ActivationPolicy

Setting this to lazy instructs the OSGi runtime that this plug-in should only be activated if one of its components, i.e. classes and interfaces are used by other plug-ins. If not set, the Equinox runtime does not activate the plug-in, i.e., services provided by this plug-in are not available.

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. Bundle-SymbolicName and Version

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 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 Eclipse Version Numbering Wiki.

2.3. 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();

2.4. Specifying plug-in dependencies via the manifest file

OSGi prevents access to classes from other plug-in without a defined dependency. The only exception are packages from the Java runtime environment (based on the Bundle-RequiredExecutionEnvironment definition of the 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

If you specify in your plug-in:

  • a plug-in dependency: your plug-in can access all exported packages of this plug-in.

  • a package dependency: your plug-in can access this package.

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.

Dependency management

If you add a dependency to your manifest file, the Eclipse IDE automatically adds the corresponding JAR file to your project classpath.

A plug-in can define that it depends on a certain version (or a range) of another bundle. 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.

The OSGi runtime reads the manifest file of a plug-in during startup. It ensures that all dependencies are present and, if necessary activates the dependency plug-in, before it starts a plug-in.

2.5. Life cycle of plug-ins in OSGi

With the installation of a plug-in in the OSGi runtime the plug-in is persisted in a local bundle cache. The OSGi runtime then tries to resolve its 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. After a successful start, it becomes ACTIVE.

This life cycle is depicted in the following graphic.

OSGi life cycle

For legacy reasons OSGi supports a dynamic import of packages. See OSGi Wiki for dynamic imports for details. You should not use this feature, it is a symptom of a non-modular design.

2.6. API definition of a plug-in

In the MANIFEST.MF file a plug-in also defines its API via the Export-Package identifier. This allows you to define internal API, provisional API and API.

Dependency management

All packages which are not explicitly exported are not visible to other plug-ins.

Dependency management

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.

Via the x-internal flag the OSGi runtime can mark an exported package as provisional. This allows other plug-ins to consume the corresponding classes, but indicates that these classes are not considered as official API.

The following screenshot shows how to set a package as x-internal in the manifest editor.

Setting the x-internal flag

This is how the corresponding manifest file looks like.

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Provider
Bundle-SymbolicName: de.vogella.osgi.xinternal.provider
Bundle-Version: 1.0.0.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Export-Package: de.vogella.osgi.xinternal.provider;x-internal:=true

You can configure how the Eclipse Java editor shows the usage of provisional API. Such an access can be configured to be displayed as, error, warning or if such access should be result in no additional message.

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

You can define that a set of plug-ins can access provisional API without a warning or error message. This can be done via the x-friends directive. This flag is added if you add a plug-in to the Package Visibility section on the Runtime tab of the manifest editor.

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Provider
Bundle-SymbolicName: de.vogella.osgi.xinternal.provider
Bundle-Version: 1.0.0.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Export-Package: de.vogella.osgi.xinternal.provider;x-friends:="another.bundle"

The x-friends setting has the same effect as x-internal but all plug-ins mentioned in the x-friends setting can access the package without receiving an error or warning message.

2.7. Eclipse API and internal API

An OSGi runtime allows the developer to mark Java packages as public, provisional or internal APIs. The internal API is private, therefore not visible. The provisional API are to test non-finalized APIs, therefore are visible but non-stable. The public API, or simply API, are the visible and stable API, that can be reused by other components.

API definition in Eclipse

The Eclipse platform project marks packages either as public API or as provisional API, to make all Java classes accessible to Eclipse developers. 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 internal but accessible, i.e., 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.

3. Using the OSGi console

3.1. Access to the OSGi console for your Eclipse application

If you specify the -console parameter in your launch configuration Eclipse will allow you to interact with the OSGi console. An OSGi launch configuration created with the Eclipse IDE contains this parameter by default.

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

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 MANIFST.MF information for a bundle.

services filter

Shows all available services and their consumer. Filter is an optional LDAP filter, e.g., to see all services which provide a ManagedService implementation use the "services (objectclass=*ManagedService)" command.

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

3.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 cannot not be used, if you want to to access the OSGi console via the Console view of the Eclipse IDE. Use only -console for this.

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

4. Download the Eclipse SDK

If you plan to add functionalities to the Eclipse platform, you should download the latest Eclipse release. Official releases have stable APIs, therefore are a good foundation for adding your plug-ins and features.

The Eclipse IDE is provided in different flavors. While you can install the necessary tools in any Eclipse package, it is typically easier to download the Eclipse Standard distribution which contains all necessary tools for plug-in development. Other packages adds more tools which are not required for Eclipse plug-in development.

Browse to the Eclipse IDE download site and download the Eclipse IDE for Eclipse Committers package.

Download Eclipse Plug-in IDE

Eclipse provides also an Eclipse installer 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.

5. Exercise: Data model plug-in

In this exercise you create a plug-in for the definition of your data model. You also make this data model available to other plug-ins.

5.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 called com.vogella.tasks.model.

Creating a simple plug-in

The following screenshot depicts the second page of the plug-in project wizard and its corresponding settings. Press the Finish button on this page to avoid the usage of templates.

Create a second page

5.2. Create the base class

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

package com.vogella.tasks.model;

import java.util.Date;

public class Todo {

    private final long id;
    private String summary = "";
    private String description = "";
    private boolean done = false;
    private Date dueDate = new Date();
}
You see an error for your final id field. This error is solved in the next section.

5.3. Generate two constructors

Select Source  Generate Constructor using Fields…​ and generate:

  • a constructor using all fields

  • a constructor using only the id field

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

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

5.5. Adjust the generated getter and setter methods

Adjust the generated getter and setter for the dueDate() field to make defensive copies. The Date class is not immutable and we want to avoid that an instance of Todo can be changed from outside without the corresponding setter.

public Date getDueDate() {
    return new Date(dueDate.getTime());
}

public void setDueDate(Date dueDate) {
    this.dueDate = new Date(dueDate.getTime());
}

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

package com.vogella.tasks.model;

import java.util.Date;

public class Todo {

    private final long id;
    private String summary = "";
    private String description = "";
    private boolean done = false;
    private Date dueDate = new Date();

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

    public Todo(long id, String summary, String description, boolean done, Date dueDate) {
        this.id = id;
        this.summary = summary;
        this.description = description;
        this.done = done;
        setDueDate(dueDate);

    }

    public long getId() {
        return 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 Date getDueDate() {
        return new Date(dueDate.getTime());
    }

    public void setDueDate(Date dueDate) {
        this.dueDate = new Date(dueDate.getTime());
    }
}

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

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

5.8. Write a copy() method

Add the following copy() method to the class.

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

5.9. Validate

The result Todo class should now look similar to the following.

package com.vogella.tasks.model;

import java.util.Date;

public class Todo {

    private final long id;
    private String summary = "";
    private String description = "";
    private boolean done = false;
    private Date dueDate = new Date();

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

    public Todo(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 Date getDueDate() {
        return new Date(dueDate.getTime());
    }

    public void setDueDate(Date dueDate) {
        this.dueDate = new Date(dueDate.getTime());
    }
    public long getId() {
        return id;
    }

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

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + (int) (id ^ (id >>> 32));
        return result;
    }

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

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

}

5.10. Create the interface for the todo service

Create the following ITodoService interface.

package com.vogella.tasks.model;

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

public interface ITodoService {

    void getTodos(Consumer<List<Todo>> todosConsumer);

    boolean saveTodo(Todo todo);

    Optional<Todo> getTodo(long id);

    boolean deleteTodo(long id);

}

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

6. Exercise: Provide 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. To persist the data you could extend this class to store the data for example in a database or the file system. As this storage is not special for Eclipse plug-ins and applications, it is not covered in this tutorial.

6.1. Create a data model provider plug-in (service plug-in)

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

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

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

If org.osgi.service.component.annotations is not visible, ensure you have selected the Imported Packages section.

The result should look similar to the following screenshot.

OSGi service Manifest dependencies

6.3. Provide an implementation of the ITodoService 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.util.ArrayList;
import java.util.Date;
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 com.vogella.tasks.model.ITodoService;
import com.vogella.tasks.model.Todo;

public class MyTodoServiceImpl implements ITodoService {

    private static AtomicInteger current = new AtomicInteger(1);
    private List<Todo> todos;

    public MyTodoServiceImpl() {
        todos = createInitialModel();
    }

    @Override
    public void getTodos(Consumer<List<Todo>> todosConsumer) {
        // always pass a new copy of the data
        todosConsumer.accept(todos.stream().map(t -> t.copy()).collect(Collectors.toList()));
    }

    protected List<Todo> getTodosInternal() {
        return todos;
    }

    // create or update an existing instance of Todo
    @Override
    public synchronized boolean saveTodo(Todo newTodo) {
        // hold the Optional object as reference to determine, if the Todo is
        // newly created or not
        Optional<Todo> todoOptional = findById(newTodo.getId());

        // get the actual todo or create a new one
        Todo todo = todoOptional.orElse(new Todo(current.getAndIncrement()));
        todo.setSummary(newTodo.getSummary());
        todo.setDescription(newTodo.getDescription());
        todo.setDone(newTodo.isDone());
        todo.setDueDate(newTodo.getDueDate());

        if (!todoOptional.isPresent()) {
            todos.add(todo);
        }
        return true;
    }

    @Override
    public Optional<Todo> getTodo(long id) {
        return findById(id).map(todo -> todo.copy());
    }

    @Override
    public boolean deleteTodo(long id) {
        Optional<Todo> deleteTodo = findById(id);

        deleteTodo.ifPresent(todo -> {
            todos.remove(todo);
        });

        return deleteTodo.isPresent();
    }

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

    private Todo createTodo(String summary, String description) {
        return new Todo(current.getAndIncrement(), summary, description, false, new Date());
    }

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

}

6.4. Enable annotation processing and plug-in activation

Ensure that OSGi services can be defined via annotations in the class via Window  Preferences  Plug-in Development  DS annotations.

Activate DS annotation processing

6.5. Define OSGi service

Add the @Component annotation to your MyTodoServiceImpl class. Eclipse uses this annotation to generate the necessary files to make the MyTodoServiceImpl class available as OSGi services for the ITodoService interface.

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

@Component
public class MyTodoServiceImpl implements ITodoService {
    // as before ...
}

6.6. Update the product configuration (via your feature)

Add your new plug-ins to your feature:

  • com.vogella.tasks.services

  • com.vogella.tasks.model

Ensure that you use the Plug-ins tab on the feature.xml file.

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

7. Exercise: Using dependencies in OSGi services

In this exercise you learn how to use dependency injection for OSGi services. OSGi DS allows to use the @Reference annotation on a method to indicate that another OSGi service you be used.

7.1. Create implementation

Create a new plug-in project com.vogella.osgi.quote. Do not use a template, do not create an activator.

Create the IQuoteService interface.

include::res/service/IQuoteService.java

Add com.vogella.tasks.model to the dependencies in the MANIFEST.MF file.

Create the QuoteService class which implements the interface IQuoteService.

package com.vogella.osgi.quote;

import java.util.Optional;
import java.util.Random;

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

import com.vogella.tasks.model.ITodoService;
import com.vogella.tasks.model.Todo;

@Component
public class QuoteService implements IQuoteService {

    private ITodoService todoService;
    private Random rand = new Random();

    @Override
    public String getQuote() {
        // create a number between 0 and 2
        int nextInt = rand.nextInt(3);
        Optional<Todo> todo = todoService.getTodo(nextInt);
        return todo.map(Todo::getSummary).orElse("Not found");
    }

    @Reference
    public void setITodoService(ITodoService todoService) {
        this.todoService = todoService;
    }
}

Now use the OSGi service, e.g., get it injected into your a part of your Eclipse application. Use System.out.println to display the result of the getQuote() method call.

8. Exercise: Using dependencies in OSGi services

In this exercise you learn how to use dependency injection for OSGi services. OSGi DS allows to use the @Reference annotation on a method to indicate that another OSGi service you be used.

8.1. Create implementation

Create a new plug-in project com.vogella.osgi.quote. Do not use a template, do not create an activator.

Create the IQuoteService interface.

include::res/service/IQuoteService.java

Add com.vogella.tasks.model to the dependencies in the MANIFEST.MF file.

Create the QuoteService class which implements the interface IQuoteService.

package com.vogella.osgi.quote;

import java.util.Optional;
import java.util.Random;

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

import com.vogella.tasks.model.ITodoService;
import com.vogella.tasks.model.Todo;

@Component
public class QuoteService implements IQuoteService {

    private ITodoService todoService;
    private Random rand = new Random();

    @Override
    public String getQuote() {
        // create a number between 0 and 2
        int nextInt = rand.nextInt(3);
        Optional<Todo> todo = todoService.getTodo(nextInt);
        return todo.map(Todo::getSummary).orElse("Not found");
    }

    @Reference
    public void setITodoService(ITodoService todoService) {
        this.todoService = todoService;
    }
}

Now use the OSGi service, e.g., get it injected into your a part of your Eclipse application. Use System.out.println to display the result of the getQuote() method call.

9. Optional Exercise: Use OSGi console

In this exercise you learn how to use the OSGi console to analyze your running Eclipse instance.

Add the -console parameter to your launch configuration as runtime parameter.

console required plug ins04

Ensure to add the following plug-ins to your runtime configuration.

  • org.eclipse.equinox.console

  • org.apache.felix.gogo.command

  • org.apache.felix.gogo.runtime

  • org.apache.felix.gogo.shell

The Add Required Plug-ins and Validate buttons can be useful here.

console required plug ins10

Start your application. In the Console view of Eclipse, use the ss, bundle and diag commands to learn about your plug-ins and services.

console required plug ins30

Remove one of your plug-ins from the launch configuration, start Eclipse again ignoring the warning popup and use the console to find out which plug-in is missing.

10. Exercise: Extend data model to be taggable

10.1. Create a Tag class

The following Tag class is used to group Todo objects.

package com.vogella.tasks.model;

import java.util.List;

public class Tag<T> {

    public static String LABEL_FIELD = "label";

    public static String TAGGEDELEMENTS_FIELD = "taggedElements";

    private String label;

    private List<T> taggedElements;

    public Tag(String label, List<T> taggedElements) {
        this.label = label;
        this.taggedElements = taggedElements;
    }

    public String getLabel() {
        return label;
    }

    public List<T> getTaggedElements() {
        return taggedElements;
    }

    public void addTaggedElement(T element) {
        taggedElements.add(element);
    }

    public void removeTaggedElement(T element) {
        taggedElements.remove(element);
    }
}

10.2. Create the interface for the tag service

Create the following interface in your com.vogella.tasks.model plug-in.

package com.vogella.tasks.services.internal;

import java.util.ArrayList;
import java.util.Date;
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 com.vogella.tasks.model.ITodoService;
import com.vogella.tasks.model.Tag;
import com.vogella.tasks.model.Todo;

public class MyTagServiceImpl implements ITodoService {

    private static AtomicInteger current = new AtomicInteger(1);
    private List<Todo> todos;

    private Tag<Tag<Todo>> rootTag;

    public MyTodoServiceImpl() {
        todos = createInitialModel();
        createRootTag(todos);
    }

    @Override
    public void getTodos(Consumer<List<Todo>> todosConsumer) {
        // always pass a new copy of the data
        todosConsumer.accept(todos.stream().map(t -> t.copy()).collect(Collectors.toList()));
    }

    protected List<Todo> getTodosInternal() {
        return todos;
    }

    // create or update an existing instance of Todo
    @Override
    public synchronized boolean saveTodo(Todo newTodo) {
        // hold the Optional object as reference to determine, if the Todo is
        // newly created or not
        Optional<Todo> todoOptional = findById(newTodo.getId());

        // get the actual todo or create a new one
        Todo todo = todoOptional.orElse(new Todo(current.getAndIncrement()));
        todo.setSummary(newTodo.getSummary());
        todo.setDescription(newTodo.getDescription());
        todo.setDone(newTodo.isDone());
        todo.setDueDate(newTodo.getDueDate());

        if (!todoOptional.isPresent()) {
            todos.add(todo);
        }
        return true;
    }

    @Override
    public Optional<Todo> getTodo(long id) {
        return findById(id).map(todo -> todo.copy());
    }

    @Override
    public boolean deleteTodo(long id) {
        Optional<Todo> deleteTodo = findById(id);

        deleteTodo.ifPresent(todo -> {
            todos.remove(todo);
        });

        return deleteTodo.isPresent();
    }

    @Override
    public Tag<Tag<Todo>> getRootTag() {
        return rootTag;
    }


    @Override
    public List<Tag<Todo>> getTags(long id) {
        List<Tag<Todo>> tags = new ArrayList<>();

        Optional<Todo> findById = findById(id);
        findById.ifPresent(todo -> findTags(todo, tags, getRootTag()));

        return tags;
    }

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

    private void createRootTag(List<Todo> todos) {
        Tag<Todo> eclipseTag = new Tag<>("Eclipse", todos);
        List<Tag<Todo>> tagList = new ArrayList<>();
        tagList.add(eclipseTag);
        rootTag = new Tag<>("root", tagList);
    }

    private Todo createTodo(String summary, String description) {
        return new Todo(current.getAndIncrement(), summary, description, false, new Date());
    }

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

    private void findTags(Todo todo, List<Tag<Todo>> todosTags, Tag<?> rootTag) {
        List<?> taggedElements = rootTag.getTaggedElements();
        for (Object taggedElement : taggedElements) {
            if (taggedElement instanceof Tag) {
                findTags(todo, todosTags, (Tag<?>) taggedElement);
            } else if (todo.equals(taggedElement)) {
                @SuppressWarnings("unchecked")
                Tag<Todo> foundTag = (Tag<Todo>) rootTag;
                todosTags.add(foundTag);
            }
        }
    }
}

10.3. Provide service implementation

In your com.vogella.tasks.services service plug-in create the implementation of the TagService as an OSGi service.

package com.vogella.tasks.services.internal;

import java.util.ArrayList;
import java.util.Date;
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 com.vogella.tasks.model.ITodoService;
import com.vogella.tasks.model.Tag;
import com.vogella.tasks.model.Todo;

public class MyTagServiceImpl implements ITodoService {

    private static AtomicInteger current = new AtomicInteger(1);
    private List<Todo> todos;

    private Tag<Tag<Todo>> rootTag;

    public MyTodoServiceImpl() {
        todos = createInitialModel();
        createRootTag(todos);
    }

    @Override
    public void getTodos(Consumer<List<Todo>> todosConsumer) {
        // always pass a new copy of the data
        todosConsumer.accept(todos.stream().map(t -> t.copy()).collect(Collectors.toList()));
    }

    protected List<Todo> getTodosInternal() {
        return todos;
    }

    // create or update an existing instance of Todo
    @Override
    public synchronized boolean saveTodo(Todo newTodo) {
        // hold the Optional object as reference to determine, if the Todo is
        // newly created or not
        Optional<Todo> todoOptional = findById(newTodo.getId());

        // get the actual todo or create a new one
        Todo todo = todoOptional.orElse(new Todo(current.getAndIncrement()));
        todo.setSummary(newTodo.getSummary());
        todo.setDescription(newTodo.getDescription());
        todo.setDone(newTodo.isDone());
        todo.setDueDate(newTodo.getDueDate());

        if (!todoOptional.isPresent()) {
            todos.add(todo);
        }
        return true;
    }

    @Override
    public Optional<Todo> getTodo(long id) {
        return findById(id).map(todo -> todo.copy());
    }

    @Override
    public boolean deleteTodo(long id) {
        Optional<Todo> deleteTodo = findById(id);

        deleteTodo.ifPresent(todo -> {
            todos.remove(todo);
        });

        return deleteTodo.isPresent();
    }

    @Override
    public Tag<Tag<Todo>> getRootTag() {
        return rootTag;
    }


    @Override
    public List<Tag<Todo>> getTags(long id) {
        List<Tag<Todo>> tags = new ArrayList<>();

        Optional<Todo> findById = findById(id);
        findById.ifPresent(todo -> findTags(todo, tags, getRootTag()));

        return tags;
    }

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

    private void createRootTag(List<Todo> todos) {
        Tag<Todo> eclipseTag = new Tag<>("Eclipse", todos);
        List<Tag<Todo>> tagList = new ArrayList<>();
        tagList.add(eclipseTag);
        rootTag = new Tag<>("root", tagList);
    }

    private Todo createTodo(String summary, String description) {
        return new Todo(current.getAndIncrement(), summary, description, false, new Date());
    }

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

    private void findTags(Todo todo, List<Tag<Todo>> todosTags, Tag<?> rootTag) {
        List<?> taggedElements = rootTag.getTaggedElements();
        for (Object taggedElement : taggedElements) {
            if (taggedElement instanceof Tag) {
                findTags(todo, todosTags, (Tag<?>) taggedElement);
            } else if (todo.equals(taggedElement)) {
                @SuppressWarnings("unchecked")
                Tag<Todo> foundTag = (Tag<Todo>) rootTag;
                todosTags.add(foundTag);
            }
        }
    }
}

11. Excursion: Using OSGi service low-level API

The preferred way of defining OSGi services is using annotation and generated meta data. But you have more options if you need them. This chapter serves as a reference to other ways of defining OSGi services, feel free to skip them.

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

11.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);

11.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
    }

}

12. OSGi Resources

13. OSGi services resources

14. vogella training and consulting support

Copyright © 2012-2019 vogella GmbH. Free use of the software examples is granted under the terms of the Eclipse Public License 2.0. This tutorial is published under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Germany license.