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

1. Introduction into software modularity with OSGi

An application consists of different components.

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

If a component uses the API from another component, it has a dependency to the other component.

A component should be free to change its internal implementation and only changes in the API affects other components.

1.1. The OSGi specification and its implementations

OSGi is a set of specifications. Its core specification defines a component and service model for Java. Services are Java implementations which OSGi allows to start and access.

A software component in OSGi is called bundle or plug-in, both terms are interchangeable.

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

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 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. == 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 need to define dependencies to these plug-in 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.

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

2.4. 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 the 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. See OSGi Wiki for dynamic imports for details. You should not use this feature, it is a symptom of a non-modular design.

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

2.6. 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 checks also 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

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

OSGi allows you to define provisional API, i.e. packages which can be used from other plug-ins but are not defined as API. The provisional API maybe used to test non-finalized APIs. This can be done via the x-internal or the x-friends flag.

Dependency management

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

Setting the x-internal flag

The following is an example for a plug-in which exports the de.vogella.osgi.xinternal.provider package as provisional API.

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

x-friends also defines provisional API but also allows 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: 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"

Reminder: The x-friends setting has the same effect as x-internal so every plug-in also access this package. All the plug-ins mentioned in the x-friends setting can access the package without receiving an error or warning message.

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

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

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

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 which contains the the definition of a 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.

Create a second page

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

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

5.3. Generate two constructors

Select Source  Generate Constructor using Fields…​ and generate:

  • a constructor using only the id field

  • a constructor using all fields

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

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

}

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

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

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

5.8. Validate

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

package com.vogella.tasks.model;

import java.time.LocalDate;

public class Task {

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

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

}

5.9. 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
     */


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

    /**
     * Updates existing task or create new task
     *
     * @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);

}

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

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.

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
Bundle-Vendor: VOGELLA
Automatic-Module-Name: com.vogella.tasks.services
Bundle-RequiredExecutionEnvironment: JavaSE-11
Import-Package: com.vogella.tasks.model,
 org.osgi.service.component.annotations
  • the version constraint has been removed to avoid issues with later Eclipse releases

  • Java 11 is used in this example, a higher Java version will also work

6.3. 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.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.Task;
import com.vogella.tasks.model.TaskService;

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 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", "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());
    }

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

}

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

6.5. Define OSGi service

Add the @Component annotation to your TransientTaskServiceImpl class. This triggers the generation of OSGi meta-data to the class available as OSGi services for the TaskService interface.

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

@Component
public class TransientTaskServiceImpl implements TaskService {
    // 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.

package com.vogella.osgi.quote;

public interface IQuoteService {
    String getQuote();
}

Add com.vogella.tasks.model to the dependencies in the MANIFEST.MF file. For this open the manifest file of the com.vogella.osgi.quote plug-in and switch to the Dependency tab.

osgi quote service10

Create the following QuoteService class which implements the interface IQuoteService.

package com.vogella.osgi.quote.internal;

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.osgi.quote.IQuoteService;
import com.vogella.tasks.model.TaskService;
import com.vogella.tasks.model.Todo;

@Component
public class QuoteService implements IQuoteService {

    private TaskService 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 setTaskService(TaskService todoService) {
        this.todoService = todoService;
    }
}

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

7.3. Add the new plug-in to your product

Add the com.vogella.osgi.quote to your feature and start again via the product to ensure that the plug-in is available.

7.4. Use the new service in your part

To use the service in another plug-in, you need to export the package that provides the interface. For this, open the manifest file of the com.vogella.osgi.quote plug-in and switch to the Runtime tab.

osgi quote service20

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

9. Exercise: Extend data model to be taggable

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

9.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.TaskService;
import com.vogella.tasks.model.Tag;
import com.vogella.tasks.model.Todo;

public class MyTagServiceImpl implements TaskService {

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

9.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.TaskService;
import com.vogella.tasks.model.Tag;
import com.vogella.tasks.model.Todo;

public class MyTagServiceImpl implements TaskService {

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

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

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

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

}

11. OSGi Resources

12. OSGi services resources