This tutorial gives an overview of OSGi and its modularity layer using Eclipse Equinox.
1. Introduction into software modularity with OSGi
OSGi is a set of specifications.
Its core specification defines a component and service model for Java.
A software component in OSGi is called bundle or plug-in, both terms are interchangeable. Services are Java implementations which OSGi allows to start and access.
An application consists of different components and services. A solid and clean software architecture consists of components which can change their internal implementation without affecting other components. OSGi supports creating such an architecture.
The OSGi specification defines a way for plug-ins to define:
-
their API
-
their dependencies
-
provided and required services
A plug-in is therefore a cohesive, self-contained unit, which explicitly defines its dependencies to other components and services.
OSGi defines that plug-ins and services can be dynamically installed, activated, de-activated, updated and de-installed.
The OSGi specification has several implementations, for example Eclipse Equinox, Knopflerfish OSGi or Apache Felix.
Eclipse Equinox is the reference implementation of the base OSGi specification. It is also the runtime environment on which Eclipse applications are based.
Java 9 provides its own module system to describe software component dependencies. But the Java module system has not yet been adapted by the module system used by Eclipse. |
2. OSGi configuration files
2.1. The manifest file (MANIFEST.MF)
OSGi uses the META-INF/MANIFEST.MF file (called the manifest file) from the standard Java specification to define its meta information. The Java specification defines that additional key/values can be added to this file without affecting runtimes which do not understand these key/value combinations. Therefore, OSGi plug-ins can be used without restrictions in other Java environments.
The following listing is an example for a manifest file containing OSGi metadata.
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Popup Plug-in
Bundle-SymbolicName: com.example.myosgi; singleton:=true
Bundle-Version: 1.0.0
Bundle-Activator: com.example.myosgi.Activator
Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-21
The following table gives an overview of the OSGi meta-data used 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:
|
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 |
Bundle-RequiredExecutionEnvironment (BREE) |
Specify which Java version is required to run the plug-in. If this requirement is not fulfilled, then the runtime does not load the plug-in. The OSGi specification deprecates but the Eclipse Plug-in development tooling still uses it, hence this is also used in this description. |
Bundle-ActivationPolicy |
Setting this to lazy instructs the OSGi runtime that this plug-in should be activated if one of its classes and interfaces are used by other plug-ins. Must be in Equinox if the plug-in provides services. |
Bundle-ClassPath |
The Bundle-ClassPath specifies where to load classes from the bundle. The default is '.' which allows classes to be loaded from the root of the bundle. You can also add JAR files to it, these are called nested JAR files. |
2.2. Unique identifier for plug-ins
The combination of Bundle-SymbolicName
and Bundle-Version
uniquely identifies a plug-in.
Each plug-in has a unique name (id) which is defined via the Bundle-SymbolicName
property.
By convention, this name uses the reverse domain name of the plug-in author.
For example, if you own the "example.com" domain then the symbolic name would start with "com.example".
Each plug-in defines its version number in the Bundle-Version
property.
OSGi recommends to use the following schema for versions in the Bundle-Version
field identifier.
<major>.<minor>.<patch/service>
If you change your plug-in code you increase the version according to the following rule set.
-
<major> is increased if changes are not backwards compatible.
-
<minor> is increased if public API has changed but all changes are backwards compatible.
-
<service> is increased if all changes are backwards compatible.
For more information on this version scheme see the Version Numbering Wiki.
3. Specifying the API of a plug-in and its dependencies
An OSGi runtime restricts access to classes from other plug-ins. To access classes from other plug-ins, a plug-in needs to define dependencies to these plug-ins or their packages. In additional the plug-in must define the used packages as API.
The only exception are packages from the Java runtime environment which by default can be accessed.
All these restrictions are enforced via a specific OSGi classloader
.
Each plug-in has its own classloader.
Access to restricted classes is not possible without using reflection.
Unfortunately OSGi can not prevent you from using Java reflection to access these classes. This is because OSGi is based on the Java runtime which does not yet support a modularity layer. |
3.1. Defining the API of a plug-in
A plug-in defines its API via the Export-Package
identifier in its manifest file.
All packages which are not explicitly exported are not visible to other plug-ins.
The following screenshot shows one plug-in which exports one package as API in the Eclipse manifest editor.
3.2. Defining dependencies of a plug-in
To access classes from another plug-in A in the source code of your plug-in B you need to:
-
export the corresponding package in plug-in A
-
define a dependency in plug-in B to plug-in A or the package
The following screenshots shows an example dependency definition in the Eclipse manifest editor.
A plug-in dependency allows a plug-in to access all exported packages from this plug-in. A package dependency allows your plug-in to use the classes in this package, independent which plug-in provides it.
Using package dependencies allows you to exchange the plug-in which provides this package. The OSGi specification promotes the usage of package dependencies. If you require this flexibility, prefer the usage of package dependencies. |
For legacy reasons OSGi supports a dynamic import of packages. You should not use this feature, it is a symptom of a non-modular design. |
3.3. Dependencies using versions
A plug-in can define that it depends on a certain version (or a range) of another plug-in. For example, plug-in A can define that it depends on plug-in C in version 2.0. Another plug-in B can define that it depends on version 1.0 of plug-in C.
3.4. Life cycle of plug-ins
The OSGi runtime reads the manifest file of a plug-in during startup. It ensures that all dependencies are present and can also check their dependencies.
If all required dependencies are resolved, the plug-in is in the RESOLVED status otherwise it stays in the INSTALLED status.
In case several plug-ins exist which can satisfy the dependency, the plug-in with the highest valid version is used.
If the versions are the same, the plug-in with the lowest unique identifier (ID) is used. Every plug-in gets this ID assigned by the framework during the installation.
When the plug-in starts, its status is STARTING. If a plug-in sets the lazy flag to true in its manifest, the Equinox runtime moves that plug-in to the ACTIVE state, if one of its classes or interface is used. In this state the plug-in can provide OSGi services in Equinox.
Other OSGi runtimes behave differently, for example Felix always activates all plug-ins. |
This life cycle is depicted in the following graphic.
3.5. Defining provisional API via the x-internal and x-friends
OSGi allows you to define provisional API, i.e. packages which can be accessed by other plug-ins but are not yet defined as API.
This can be done via the x-internal
or the x-friends
flag.
The following screenshot shows how to define a package as provisional API.
Such a setting would result in the package flagged as x-internal.
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Model
Bundle-SymbolicName: com.vogella.tasks.model
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: VOGELLA
Automatic-Module-Name: com.vogella.tasks.model
Bundle-RequiredExecutionEnvironment: JavaSE-21
Export-Package: com.vogella.tasks.model;x-internal:=true
x-friends
also defines provisional API but also allows you to define a set of plug-ins which can access the provisional API without a warning or error message.
x-friends
replaces automatically x-internal
if you use the Eclipse manifest editor and add a plug-in to the Package Visibility section on the Runtime tab.
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Model
Bundle-SymbolicName: com.vogella.tasks.model
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: VOGELLA
Automatic-Module-Name: com.vogella.tasks.model
Bundle-RequiredExecutionEnvironment: JavaSE-21
Export-Package: com.vogella.tasks.model;x-friends:="another.bundle"
You can configure the Java editor to show an error, warning or to ignore usage of provisional API. The default is to display a warning message. You can adjust this in the Eclipse preferences via the preference setting. |
3.6. Eclipse platform API
The Eclipse platform project marks almost all packages either as public API or as provisional API. If the Eclipse platform project releases an API, the platform project plans to keep this API stable for as long as possible.
If API is marked as provisional, the platform team can change this API in the future. If you use such API, you must be prepared that you might have to make some adjustments to your application in a future Eclipse release.
If you use unreleased API, you see a Discouraged access: The …is not API (restriction on required project …) warning in the Java editor.
You can turn off these warnings for your workspace via 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 and afterwards use the same path as for accessing the global settings. You might have to activate the Enable project specific settings checkbox at the top of the Error/Warnings preference page. |
4. OSGi Services
Services provide functionality to other software components. A service interface in OSGi is defined in a plug-in by a standard Java class or an interface. Service implementations for a service interface are published to the OSGi service registry so that other plug-ins can access them.
Multiple plug-ins can provide a service implementation for the service interface. Plug-ins can access the service implementation via the the service interface.
During the declaration of a service it is possible to specify key / values which can be used to configure the service.
A service can be dynamically started and stopped, and plug-ins which use services must be able to handle this dynamic behavior. The plug-ins can register listeners to be informed if a service is started or stopped.
To provide a service a plug-in needs to be in the ACTIVE
life cycle status.
OSGi provides several ways of defining, providing and consuming services. The following description focuses on the usage of declarative services.
4.1. OSGi declarative services (OSGi ds)
The OSGi declarative services (DS) allows you to define and consume service via meta-data. Tooling allows you to generate service metadata based on annotations in your source code, we cover this in the exercises for the Eclipse IDE.
The following is an example of using an annotation to define an OSGi service.
It provides a service implementation for the TaskService
interface.
import org.osgi.service.component.annotations.Component;
// more stuff
@Component
public class TransientTaskServiceImpl implements TaskService {
// as before ...
}
The preferred way of defining OSGi services is using the annotations, as this is the simplest way possible. Alternatively, you can also define and register the service component definition manually via an XML file or use the OSGi API for starting, stopping and tracking services. This is covered later. |
The annotation is used to generate an XML file, which is read once the plug-in gets activated.
OSGi service definitions can be:
-
delayed component - service is not activated until requested, this means class and implementation loading is delayed until this time. This type must define a OSGi service
-
immediate component - service is activated as soon as its dependencies are satisfied, does not need to specify a service
-
factory component - creates and activates a new service an request, service is not re-used if it become unsatisfied or unregistered
The following table gives a brief overview of the OSGi ds terminology, you can skip this table and return to it, in case you want to lookup a definition.
Term | Definition |
---|---|
Service component |
A Java class inside a bundle that is declared via Component Description and managed by a service component runtime. |
Component Description |
The declaration of a Service Component, contained in an XML document or defined via annotations which are used to generate the XML document |
Component Configuration |
A Component Description that is parameterized with component properties. It is used to track the component dependencies and manages the Component Instance. |
Component Instance |
The instance of the component implementation class. It is created when a Component Configuration is activated and discarded if the Component Configuration is deactivated. |
4.2. Accessing the BundleContext
Sometimes you need to access information about the bundle, e.g., the bundle version or the bundle name.
Access to the bundle and its bundleContext is performed via the Bundle
and BundleContext
class.
You can use the FrameworkUtil
class from the OSGi framework to access the BundleContext
for a class.
Bundle bundle = FrameworkUtil.getBundle(this.getClass());
BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
4.3. Immediately activate service
If you want to activate a service immediately after its dependencies are satisfied, the immediate
parameter to the @Component
annotation can be used.
4.4. Setting the start level for declarative services
You need to ensure that the org.apache.felix.scr
plug-in is started before any application plug-in which wants to consume a service.
You can ensure this in your run configuration by setting the auto-start field to true and the start level lower than 4 (4 is the default value) for the org.apache.felix.src
plug-in
The Eclipse RCP framework automatically starts the required plug-ins for using declarative OSGi service. It is not required to manually set a start level in your product configuration file.
4.5. Good practice for defining services
It is good practice to define a service via a plug-in which only contains the interface definition. Another plug-in would provide the implementation for this service. This allows you to change the implementation of the service via a different plug-in.
4.5.1. Life cycle
A plug-in which provides a service must be in its ACTIVE
life cycle status.
Therefore, ensure that the Activate this plug-in when one of its classes is loaded flag is set on the MANIFEST.MF file.
The DS annotation support in sets this flag automatically, if you use the default settings
The above will set the Lazy Activation Policy setting via the Bundle-ActivationPolicy key in the manifest. This is necessary for the Eclipse runtime (Equinox), other OSGi runtimes will activate bundle always if their dependencies are fulfilled. This flag ensures that the bundle is activated (auto-started) as soon as one of its classes is accessed. |
OSGi Services have their own life cycle, the following states are possible:
State | Definition |
---|---|
Enabled/Disabled |
The initial enabled state of a component is specified via Component Description. All components are disabled when the bundle is stopped. It is possible to change the enabled state programmatically at runtime via ComponentContext. |
UNSATISFIED |
The component is not ready to be started. See Satisfied for the necessary criteria. This status can also be reached again if a component is not Satisfied anymore. |
Satisfied |
A component is in a Satisfied state when it is enabled and the required referenced services are available. If the ConfigurationAdmin is used and configuration-policy=required is specified, a configuration object also needs to be available to bring a component to satisfied state. |
REGISTERED |
A component is in REGISTERED state if it Satisfied and not yet requested. Only applies for Delayed Components. |
ACTIVE |
The component was activated due to immediate activation or, in case of a Delayed Component, it was requested. |
If the bundle is started, an OSGi component called SCR checks if the bundle contains component descriptions. It does that by reading the MANIFEST.MF and searching for Service-Components. If it finds one it will start to process the componentDescription and create a component configuration. If the configuration is enabled, it checks for required references and configurations if necessary. If all of these are satisfied, the component can be activated.
An Immediate Component will activate immediately, a Delayed Component moves to the REGISTERED state, awaiting the first request to the provided service. If a Component Configuration contains dynamic references, the references can rebind in ACTIVE state, otherwise it will be re-activated. If a Component Configuration becomes unsatisfied (e.g. a bound service becomes unavailable), the Component Configuration will be deactivated. Note that a Delayed Component will also be deactivated and gets back to REGISTERED state if no other bundle references it anymore.
5. More information on OSGi ds services
5.1. References to other services
OSGi service can define dependencies to other services.
You can use the @Reference
annotation to define (so-called event) methods to bind to these services and to update them.
You can also use it on fields.
Method | Description |
---|---|
bind |
Called to bind a new service to the component. For static references this method is called before the activate method. For dynamic references this method can also be called while the component is active. |
updated |
Called when the configuration properties of a bound service are modified. |
unbind |
Called when the SCR needs to unbind the service. For static references this method is called after the deactivate method. For dynamic references this method can also be called while the component is active. |
The bind event methods are typically prefixed with either bind, set or add. The unbind method should have the corresponding prefixes unbind, unset or remove, so they are automatically picked up by @Reference. T
5.2. Defining the service priority
OSGi allows to define multiple service implementations. To define the importance of them, it is possible to define a ranking for a service.
This is done via a service property, called service.ranking
.
By default, the service ranking is zero, the higher the ranking the more important is the service.
The ranking order is defined as follows:
Sorted on descending ranking order (highest first) If the ranking numbers are equal, sorted on ascending service.id property (oldest first)
You can specify these properties via the @ServiceRanking annotation or the property
value.
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.propertytypes.ServiceRanking;
@Component()
@ServiceRanking(5)
public class TestServiceB implements TestService, TestOtherService {
}
@Component(
property = {
"service.ranking:Integer=7",
"another.customproperty=online"
}
)
public class OnlineDataService implements DataService {
Frameworks like the Eclipse dependency injection framework automatically inject the service with the highest service ranking.
5.3. Configuration
As OSGi service component can be configured via key-value pairs (properties).
These are accessed via a Map<String, Object>
.
These properties can be defined via the following ways:
-
inline
-
Java properties file
-
OSGi Configuration Admin
-
via argument of the ComponentFactory.newInstance method if a factory is used
These configurations options are processed in the above order, so a Java properties file can override inline configuration. The first three options are demonstrated in a later exercise.
5.3.1. Inline
You can add properties to a declarative service component via the @Component annotation property type element. The value of that annotation type element is an array of Strings, which need to be given as key-value pairs in the format <name>(:<type>)?=<value> where the type information is optional and defaults to String.
5.3.2. Java properties file
Another way to configure you OSGi services is to use a Java Properties File that is located inside the bundle.
It can be specified via the @Component annotation properties type element, where the value needs to be an entry path relative to the root of the bundle.
5.3.3. Using the OSGi configuration admin
The configuration admin service allows to configure properties of services. It uses the PID (Persistent IDentity) of the service, which default to the fully qualified class name. You can also configure it via the configurationPid type element of the @Component annotation.
The configurationPolicy
property of the @Component
annotation allows to configure, if a configuration is optional, required for ignored.
-
ConfigurationPolicy.OPTIONAL - Use the corresponding configuration object if present. This is the default value.
-
ConfigurationPolicy.REQUIRE - Configuration object must be present for the service to satisfy its requirements
-
ConfigurationPolicy.IGNORE - Ignore any corresponding configuration object even if it is present. This means that the component properties can not be changed dynamically using the configuration admin.
To be able to handle changes in the configuration, you can use a method annotated with @Modified
.
If you do not do this, the service is stopped and restarted with the new configuration.
6. OSGi Http whiteboard pattern
The OSGi specification allows to register servlets (webcomponets) via the whiteboard pattern. Deployment descriptors or deployment annotations are not supported.
Via Http Whiteboard it is possible to register:
-
Servlets
-
Servlet Filters
-
Resources
-
Servlet Listeners
The following is an example for a servlet registration via the whiteboard pattern.
@Component(service = Servlet.class ,
property= "osgi.http.whiteboard.servlet.pattern=/hello",
scope=ServiceScope.PROTOTYPE)
public class HelloWorldServlet extends javax.servlet.http.HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.getWriter().println("Hello World");
}
}
With the above coding a servlet is registered for the /hello path via the pre-defined osgi.http.whiteboard.servlet.pattern
property.
The first call will call init()
, following calls with call only the servlet via the request and the once the servlet is not used anymore, destroy
is called.
Webcomponents should always be registered with the scope Prototype, this ensures that init()
and destroy()
is never call twice on the same instance.
If more than one Http Whiteboard implementation is available in a runtime, the init() and destroy() calls would be executed multiple times, which violates the Servlet specification. It is therefore recommended to use the PROTOTYPE scope for servlets to ensure that every Http Whiteboard implementation gets its own service instance.
7. Using the OSGi console
7.1. Access to the OSGi console for your Eclipse application
If you specify the -console parameter in your run configuration, you can use a console / terminal to interact with the OSGi console.
The OSGi console is like a command-line shell. In this console you can type a command to perform an OSGi action. This can be useful to analyze problems on the OSGi layer of your application.
Use, for example, the command ss
to get an overview of all bundles, their status and bundle-id.
The following table is a reference of the most important OSGi commands.
Command | Description |
---|---|
help |
Lists the available commands. |
ss |
Lists the installed bundles and their status. |
ss vogella |
Lists bundles and their status that have vogella within their name. |
start <bundle-id> |
Starts the bundle with the <bundle-id> ID. |
stop <bundle-id> |
Stops the bundle with the <bundle-id> ID. |
diag <bundle-id> |
Diagnoses a particular bundle. It lists all missing dependencies. |
install URL |
Installs a bundle from a URL. |
uninstall <bundle-id> |
Uninstalls the bundle with the <bundle-id> ID. |
bundle <bundle-id> |
Shows information about the bundle with the <bundle-id> ID, including the registered and used services. |
headers <bundle-id> |
Shows the MANIFEST.MF information for a bundle. |
services filter |
Shows all available services and their consumers. Filter is an optional LDAP filter, e.g., to see all services which provide a ManagedService implementation use the "services (objectclass=*ManagedService)" command. |
scr:list |
lists all DS components |
scr:info <id> |
dump detailed information for a selected DS component |
Add the |
7.2. Required bundles
The following plug-ins are necessary to use the OSGi console. The easiest way to add them to your Eclipse application is to add them directly via the runtime configuration.
-
org.eclipse.equinox.console
-
org.apache.felix.gogo.command
-
org.apache.felix.gogo.runtime
-
org.apache.felix.gogo.shell
7.3. Accessing the OSGi console via a telnet client
If you want to access your application via a Telnet client, you can add another parameter to the -console
parameter.
This specifies the port to which you can connect via the telnet protocol.
-console 5555
To access such a application, use a telnet client, for example on Linux: telnet localhost 5555
from the command line.
In an OSGi console accessed via telnet, you can use tab completion and a history of the commands similar to the Bash shell under Linux.
The specification of the port must be used, if you want to access the OSGi console via the Console view of the Eclipse IDE.
Use only |
7.4. Access to the Eclipse OSGi console
You can also access the OSGi console of your running Eclipse IDE. In the Console View you find a menu entry with the tooltip Open Console. If you select Host OSGi Console, you will have access to your running OSGi instance.
Please note that interfering with your running Eclipse IDE via the OSGi console, may put the Eclipse IDE into a bad state.
This requires that the plug-in development tooling (PDE) is installed in your Eclipse IDE. If you are able to create plug-ins via the wizard than the PDE tooling is installed. |
8. Exercise overview
OSGi bundles / plug-ins can be used in standalone OSGi runtime or in an Eclipse RCP application. This text focus on the usage of OSGi in an standalone environent. If you want to learn about OSGi in RCP, please see our commercial training at Eclipse RCP training.
8.1. Download the Eclipse IDE
The Eclipse IDE provides multiple downloads which can be used to develop Eclipse based applications. The most commonly used are:
-
Eclipse SDK - minimal IDE which you need for RCP development
-
Eclipse IDE for Eclipse Committers package provides additional functionality, e.g. Git support for the Eclipse IDE
-
Eclipse IDE for RCP and RAP Developers package provides even more functionality than the committers package
Download either one of them. We describe two alternatives, the SDK and the RCP and RAP Developers download.
The advantage of the using the SDK download is that it is the minimal set of plug-ins needed for RCP development.
The screenshots in this guide are based on the SDK download. |
8.1.1. Alternative 1: Download the Eclipse SDK
To Download the latest release of the Eclipse SDK (Software development kit) build from the SDK download page. Click on the link of the latest released version (the release version with the highest number). The download section should look similar to the following screenshot.
The download is a compressed archive of multiple files. This format depends on your platform:
-
Windows uses the zip format
-
Linux uses the tar.gz format.
-
Mac uses the dmg (Disk Image) format
The Eclipse SDK does not include a Java runtime, you need to provide this separately. If you follow Download the RCP package, you do not need an additional Java installation.
8.1.2. Alternative 2: Download the Eclipse RCP (and RAP) package
Eclipse provides pre-packaged packages which can also be used for RCP development.
Open https://www.eclipse.org/downloads/packages/ in your browser and download the Eclipse IDE for RCP and RAP Developers
package
or download the installer and install this package.
The RCP package also includes an Java runtime, you do not need to install one separately.
You can also use the Eclipse installer to installer this package. See Eclipse installer for information about the installer. The installer is useful, if you want to download several versions of Eclipse. It uses a shared installation pool for common plug-ins, which reduces the required space.
9. Exercise: Create a plug-in for a data model
In this exercise you create a plug-in which contains the definition of a data model. You also make this data model available to other plug-ins to provide certain packages as API.
9.1. Create the plug-in for the data model
Naming convention: simple plug-in
A plug-in can be generated by Eclipse via the 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.
|
Create a simple plug-in project via the
menu entry with the following name:com.vogella.tasks.model
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.
9.2. Create the base class
Create the com.vogella.tasks.model
package and the following model class.
package com.vogella.tasks.model;
import java.time.LocalDate;
public class Task {
private final long id;
private String summary = "";
private String description = "";
private boolean done = false;
private LocalDate dueDate = LocalDate.now();
}
You see an error for your final id field. This error is solved in the next section. |
9.3. Generate two constructors
Generate two constructors:
-
a constructor using only the id field
-
a constructor using all fields
You can do this via the Select
menu entry.Ensure that you have created both constructors, because they are required in the following exercises. |
9.4. Generate getter and setter methods
Use the
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 |
At this point the resulting class should look like the following listing.
package com.vogella.tasks.model;
import java.time.LocalDate;
public class Task {
private final long id;
private String summary = "";
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public boolean isDone() {
return done;
}
public void setDone(boolean done) {
this.done = done;
}
public LocalDate getDueDate() {
return dueDate;
}
public void setDueDate(LocalDate dueDate) {
this.dueDate = dueDate;
}
public long getId() {
return id;
}
public Task(long id, String summary, String description, boolean done, LocalDate dueDate) {
this.id = id;
this.summary = summary;
this.description = description;
this.done = done;
this.dueDate = dueDate;
}
public Task(long id) {
this.id = id;
}
private String description = "";
private boolean done = false;
private LocalDate dueDate = LocalDate.now();
}
9.5. Generate the toString() method
Use the toString()
method for the Todo
class based on the id and summary field.
9.6. Generate the hashCode() and equals() methods
Use 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.
9.7. Write a copy() method
Add the following copy()
method to the class.
public Task copy() {
return new Task(
this.id,
this.summary,
this.description,
this.done,
this.dueDate);
}
9.8. Add constants for its fields
To access later the fields on Task
create the following constants in the class.
public static final String FIELD_ID = "id";
public static final String FIELD_SUMMARY = "summary";
public static final String FIELD_DESCRIPTION = "description";
public static final String FIELD_DONE = "done";
public static final String FIELD_DUEDATE = "dueDate";
9.9. Validate
The resulting Task
class should now look similar to the following.
package com.vogella.tasks.model;
import java.time.LocalDate;
import java.util.Objects;
public class Task {
public static final String FIELD_ID = "id";
public static final String FIELD_SUMMARY = "summary";
public static final String FIELD_DESCRIPTION = "description";
public static final String FIELD_DONE = "done";
public static final String FIELD_DUEDATE = "dueDate";
private final long id;
private String summary = "";
private String description = "";
private boolean done = false;
private LocalDate dueDate = LocalDate.now();
public Task(long id, String summary, String description, boolean done, LocalDate dueDate) {
this.id = id;
this.summary = summary;
this.description = description;
this.done = done;
this.dueDate = dueDate;
}
public Task(long id) {
this.id = id;
}
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public boolean isDone() {
return done;
}
public void setDone(boolean done) {
this.done = done;
}
public LocalDate getDueDate() {
return dueDate;
}
public void setDueDate(LocalDate dueDate) {
this.dueDate = dueDate;
}
public long getId() {
return id;
}
@Override
public String toString() {
return "Task [id=" + id + ", summary=" + summary + "]";
}
public Task copy() {
return new Task(this.id, this.summary, this.description, this.done, this.dueDate);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Task other = (Task) obj;
return id == other.id;
}
}
9.10. Define a service interface to access tasks
Create the following TaskService
interface.
package com.vogella.tasks.model;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
public interface TaskService {
/**
* Get all tasks
*/
List<Task> getAll();
/**
* Updates existing task or create new task
*
* @param task
* @return
*/
boolean update(Task task);
/**
* Returns an Optional wrapping a task or wrapping null if not task exists for the given id
*
* @param task
* @return empty optional if not found, otherwise Optional holder the task
*/
Optional<Task> get(long id);
/**
* Deletes the task with the id
* @param id task id to delete
* @return true if task existed and was deleted, false otherwise
*/
boolean delete(long id);
/**
* Allow to specify a consumer which retrieves all tasks
*
* @param tasksConsumer
*/
void consume(Consumer<List<Task>> tasksConsumer);
}
9.11. Define the API of the model plug-in
Export the com.vogella.tasks.model
package to define it as API.
For this, open the MANIFEST.MF
file and select the Runtime tab.
Add com.vogella.tasks.model
to the exported packages.
9.12. Next Steps
In the following exercises, you will implement and use an OSGi service which uses this data model.
10. Exercise: Create a run configuration for your OSGi application
In this exercise you setup an run configuration for your OSGi runtime in the Eclipse IDE.
10.1. Create run configuration
Select
from the top-level menu. Select OSGi Framework and press the new button.On the Bundles tab, deselect all existing selection and add the following framework plug-ins:
-
org.eclipse.osgi
-
org.eclipse.osgi.util
-
org.eclipse.osgi.services
-
org.eclipse.equinox.console
-
org.apache.felix.gogo.shell
-
org.apache.felix.gogo.runtime
-
org.apache.felix.gogo.command
-
org.apache.felix.scr
Also add your plug-in to the runtime.
-
com.vogella.tasks.model
As a result the following bundles should be selected.
10.2. Check arguments
Switch to the Arguments tag.
The generated run configuration uses the -console
parameter by default.
Also it specifies the VM argument, eclipse.ignoreApp and osgi.noShutdown.
10.3. Run your OSGi application
Press the Run
button to start your OSGi runtime.
By default, this starts an OSGi runtime with your modules.
As you specified the `-console ` parameter, you can use OSGi console commands.
Type ss
into the console view to get an overview of the plug-ins and their status.
The model
plug-in should be listed and in RESOLVED
or in the ACTIVE
status, if the Default Auto-Start setting on the Bundles tab is set to true.
In the next exercise you define an OSGi service which uses this plug-in.
10.5. Terminate running OSGi instance
As you specified the osgi.noShutdown=true
flag, the OSGi application will not shutdown.
This is useful if the OSGi instance provides services to the outside.
Use the exit
command or the terminate button to terminate the running instance.
If you have multiple instance running, you can use the drop-down button in Eclipse to switch to them.
11. Exercise: Implementing and providing an OSGi service
In this exercise, you create a plug-in for a service implementation. This implementation provides access to the task data.
This service implementation uses transient data storage, i.e., the data is not persisted between application restarts. A later exercise will persist the data as JSON on the file system. |
11.1. Create a new plug-in for the OSGi service
Create a new simple plug-in project called com.vogella.tasks.services
.
The MacOS operating system treats folders ending with .service special, therefore we use the .services ending. |
11.2. Define the dependencies in the service plug-in
To use classes from other plug-ins in a plug-in, you need to add a dependency to them.
To achieve this, open the MANIFEST.MF file of the com.vogella.tasks.services
plug-in.
Select the Dependencies tab and add the following to the Imported Packages selection:
-
com.vogella.tasks.model
-
org.osgi.service.component.annotations
Ensure you have selected the Imported Packages section.
Otherwise, the |
Select the org.osgi.service.component.annotations
and press Properties….
Mark this package as Optional and ensure you specify the highest available number (currently 1.3.0) as minimun requirement.
The result should look similar to the following screenshot.
The text file should look similar to the following.
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Services
Bundle-SymbolicName: com.vogella.tasks.services
Bundle-Version: 1.0.0.qualifier
Import-Package: com.vogella.tasks.model,
org.osgi.service.component.annotations;version="1.3.0";resolution:=optional
Bundle-Vendor: VOGELLA
Automatic-Module-Name: com.vogella.tasks.services
Bundle-RequiredExecutionEnvironment: JavaSE-21
11.3. Enable annotation processing and plug-in activation
You want to generate OSGi service metadata based on annotations in classes. For this, enable the option under
.11.4. Provide an implementation of the TaskService interface
Create the com.vogella.tasks.services.internal
package in your service plug-in and create the following class.
package com.vogella.tasks.services.internal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.osgi.service.component.annotations.Component;
import com.vogella.tasks.model.Task;
import com.vogella.tasks.model.TaskService;
@Component (1)
public class TransientTaskServiceImpl implements TaskService {
private static AtomicInteger current = new AtomicInteger(1);
private List<Task> tasks;
public TransientTaskServiceImpl() {
tasks = createTestData();
}
@Override
public List<Task> getAll() {
return tasks.stream().map(Task::copy).collect(Collectors.toList());
}
@Override
public void consume(Consumer<List<Task>> taskConsumer) {
// always pass a new copy of the data
taskConsumer.accept(tasks.stream().map(Task::copy).collect(Collectors.toList()));
}
// create or update an existing instance of a task object
@Override
public synchronized boolean update(Task newTask) {
// hold the Optional object as reference to determine, if the object is
// newly created or not
Optional<Task> taskOptional = findById(newTask.getId());
// get the actual object or create a new one
Task task = taskOptional.orElse(new Task(current.getAndIncrement()));
task.setSummary(newTask.getSummary());
task.setDescription(newTask.getDescription());
task.setDone(newTask.isDone());
task.setDueDate(newTask.getDueDate());
if (!taskOptional.isPresent()) {
tasks.add(task);
}
return true;
}
@Override
public Optional<Task> get(long id) {
return findById(id).map(Task::copy);
}
@Override
public boolean delete(long id) {
Optional<Task> deletedTask = findById(id);
deletedTask.ifPresent(t -> tasks.remove(t));
return deletedTask.isPresent();
}
// Example data, change if you like
private List<Task> createTestData() {
List<Task> list = List.of(
create("Application model", "Dynamics"), create("Application model", "Flexible and extensible"),
create("DI", "@Inject as programming mode"), create("OSGi", "Services"),
create("SWT", "Widgets"), create("JFace", "Especially Viewers!"),
create("CSS Styling", "Style your application"),
create("Eclipse services", "Selection, model, Part"),
create("Renderer", "Different UI toolkit"), create("Compatibility Layer", "Run Eclipse 3.x"));
return new ArrayList<>(list);
}
private Task create(String summary, String description) {
return new Task(current.getAndIncrement(), summary, description, false,
LocalDate.now().plusDays(current.longValue()));
}
private Optional<Task> findById(long id) {
return tasks.stream().filter(t -> t.getId() == id).findAny();
}
}
1 | The @Component annotation triggers the generation of OSGi meta-data to the class available as OSGi services for the TaskService interface. |
11.5. Add the provided capability to your manifest
The service implementation should define the information for the runtime, that is provides the service. You can do this via the following entry in the manifest.
Provide-Capability: osgi.service; objectClass=com.vogella.tasks.model.TaskService
The full manifest looks similar to the following listing:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Services
Bundle-SymbolicName: com.vogella.tasks.services
Bundle-Version: 1.0.0.qualifier
Import-Package: com.vogella.tasks.model,
org.osgi.service.component.annotations;version="1.3.0";resolution:=optional
Bundle-Vendor: VOGELLA
Automatic-Module-Name: com.vogella.tasks.services
Bundle-RequiredExecutionEnvironment: JavaSE-21
Provide-Capability: osgi.service; objectClass=com.vogella.tasks.model.TaskService
11.6. Add your new plug-in to the product
Add this new plug-in to your product (via the feature).
11.7. Further steps
By default, the OSGi runtime initializes and provides OSGi services on demand. Hence you need to consume the OSGI service to start it. This is done in the next exercises.
12. Exercise: Consume the OSGi service via an OSGi immediate component
In this exercise you will create another OSGi component which consumes the created service. As this OSGi component is not a service it is called an immediate component and activated as soon as its dependencies are satisfied.
OSGi does not use JSR 330 (with @Inject) for this purpose but uses its custom (compile-time) annotations for this.
Ensure you enabled the DS Annotations setting in the preferences of Eclipse to ensure these annotations are used to generated the necessary configuration files.
12.1. Create a new plug-in
Create another simple plug-in named com.vogella.osgi.taskconsumer
.
12.2. Add manifest dependencies
Add the required package dependencies in the manifest to use the TaskService
interface and the @Component
annotation.
See below solution for the correct entry.
-
com.vogella.tasks.model
-
org.osgi.service.component.annotations, ensure to use as minimum version 1.3.0 and make the resolution optional
A dependency to com.vogella.task.services is NOT necessary, the implementation is provided by OSGi without direct dependency. |
12.3. Add an immediate OSGi component
Use the @Component
annotation to add another OSGi component via a class named TaskConsumer
.
Receive the TaskService
the @Reference
annotation, either on a field or on a method.
Also implement a method annotated with the @Activate
annotation to write to the number of task to the console.
This tasks can be received from the service.
Show Solution
package com.vogella.osgi.taskconsumer;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import com.vogella.tasks.model.TaskService;
@Component
public class TaskConsumer {
@Reference
TaskService taskService;
// The above shows a field reference, you could also use @Reference on a method
// private void bindTaskService(TaskService taskService) {
// this.taskService = taskService;
// System.out.println("Injected " + taskService);
// }
//
// @SuppressWarnings("unused")
// private void unbindTaskService(TaskService taskService) {
// System.out.println("Removed " + taskService);
// taskService = null;
// }
@Activate
public void activate() {
System.out.println("Activate called");
System.out.println("Number of tasks: " + taskService.getAll().size());
}
}
Also add Require-Capability: osgi.service;filter:="(objectClass=com.vogella.tasks.model.TaskService)"
to your MANIFEST.MF, indicating that such a service is required for this plug-in to work.
The resulting MANIFEST should look like the following, Service-Component and Bundle-ActivationPolicy are added by the tooling.
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Taskconsumer
Bundle-SymbolicName: com.vogella.osgi.taskconsumer
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: VOGELLA
Automatic-Module-Name: com.vogella.osgi.taskconsumer
Bundle-RequiredExecutionEnvironment: JavaSE-21
Require-Capability: osgi.service;filter:="(objectClass=com.vogella.tasks.model.TaskService)"
Import-Package: com.vogella.tasks.model,
org.osgi.service.component.annotations;version="1.3.0";resolution:=optional
Service-Component: OSGI-INF/com.vogella.osgi.taskconsumer.TaskConsumer.xml
Bundle-ActivationPolicy: lazy
12.4. Add to your product
Add the new plug-in to your product via your feature.
12.5. Validate
Start again via the product and ensure you see the output of the @Activate
on the console.
12.6. Remove the System.out statement
Remove the System.out statements in TaskConsumer
, these are only useful to demonstrate the functionality but should be removed to avoid unnecessary output on the console in future exercises.
13. Exercise: Validate
Add your com.vogella.osgi.taskconsumer
plug-in to your run configuration.
Start your OSGi runtime and observe the output of the your console.
You should see the output of the OSGi service consumption.
14. Exercise: Creating a feature based product for the runtime configuration
To manage the plug-ins included in the OSGi runtime, you can use a product configuration file. While the product is coming from the Eclipse IDE and RCP development point of view and is not perfect for OSGi development, it can still be handy to have one place to configure your run configuration.
This product can also be used for a Maven Tycho command line build.
In this exercise you create one product based on features.
14.1. Create a feature project
Create a feature project named com.vogella.osgi.feature via
.If you have already an existing launch configuration you can use that to popular the feature.
Open the feature.xml
file and switch to the _Included Plug-ins` tag.
Ensure the following framework plug-ins are included in your feature.xml
file:
-
org.eclipse.osgi
-
org.eclipse.osgi.util
-
org.eclipse.osgi.services
-
org.eclipse.equinox.console
-
org.apache.felix.gogo.shell
-
org.apache.felix.gogo.runtime
-
org.apache.felix.gogo.command
-
org.apache.felix.scr
Also ensure that your plug-in is part of the feature.
-
com.vogella.tasks.model
14.2. Create a project to host the product configuration file
Create a new project named com.vogella.osgi.runtime of type General via the
menu entry.14.3. Create a product configuration file
Right-click on the com.vogella.osgi.runtime project and select
.Create a product configuration file called osgi-runtime.product inside the main folder of the project using basis settings.
Press the Finish button. The file is created and opened in an editor.
On the Overview tab ensure the following settings:
-
ID = com.vogella.osgiruntime
-
Version = 1.0.0.qualifier
-
Uncheck The product includes native launcher artifacts
Leave the product and application empty. Product and Application are used in RCP products, and therefore not needed for a headless OSGi command line application.
Also set that the product is based on features.
14.5. Configuration
Configure the Start Levels:
-
org.apache.felix.scr, StartLevel = 2, Auto-Start = true
Equinox does not automatically activate any bundle. Bundles are only activated if a class is directly requested from it. But the service component runtime is never required directly. So you need to configure that it is activated, otherwise, org.apache.felix.scr will never get activated. |
Configure the following properties:
Properties | Description |
---|---|
eclipse.ignoreApp = true |
Tells Equinox to skip trying to start an Eclipse application. |
osgi.noShutdown = true |
The OSGi framework will not be shut down after the Eclipse application has ended. You can find further information about these properties in the Equinox Framework QuickStart Guide and the Eclipse Platform Help. |
14.6. Adjust launch configuration
The above configuration is used for the exported application. You also have to configure the parameters for the start from the IDE.
Add on the Launching tab:
-
On the Program Arguments: -console
-
On the VM Arguments: -Declipse.ignoreApp=true -Dosgi.noShutdown=true
When adding the parameters in the Launching tab instead of the Configuration tab, the configurations are added to the eclipse.ini in the root folder, not to the config.ini in the configuration folder. When starting the application via the exported jar, the eclipse.ini in the root folder is not inspected. |
14.7. Validate
Click on the Run button to start your OSGi command line application.
If the system shows an validation error, that the application is not maintained, you can ignore that. |
15. Exercise: Use OSGi console
In this exercise you learn how to use the OSGi console to analyze the state of your plug-ins at runtime.
Ensure that the -console
parameter is set in your run configuration as runtime parameter.
To use the console, you need to have the following components included in your runtime:
If you followed this description, this should already be the case. Use |
Start your application. In the Console view of Eclipse, use the following commands:
-
ss
- see short status of your plug-ins, also shows the ids of the plug-ins -
bundle <id-of-the-plug-in>
- get an overview of the bundle -
diag <id-of-the-plug-in>
- get information about the bundle -
scr:list
- show all the OSGi components -
scr:list <id-of-the-plug-in>
- show the OSGi components of that plug-in
16. Exercise: Extend the OSGi console with additional commands
In this exercise you learn how to add additional commands to the OSGi console.
16.1. Create a new plug-in and its dependencies
Create a new plug-in named com.vogella.osgi.console
.
Add the following imported package dependencies to its manifest:
-
org.osgi.service.component.annotations
-
org.eclipse.osgi.framework.console
As always, mark org.osgi.service.component.annotations
as optional.
16.2. Create implementation
To extend the OSGi console, provide new new class AndQuitCommand implements org.eclipse.osgi.framework.console.CommandProvider
as OSGi component via the @Component
annotation.
Add methods prefixed with _ are available as additional commands.
For example, you can add the following method to provide the add
command which take two parameters.
import org.eclipse.osgi.framework.console.CommandInterpreter;
// more code
public void _add(CommandInterpreter ci) {
int a = Integer.parseInt(ci.nextArgument());
int b = Integer.parseInt(ci.nextArgument());
ci.println(a+b);
}
Show Solution
package com.vogella.osgi.console;
import org.eclipse.osgi.framework.console.CommandInterpreter;
import org.eclipse.osgi.framework.console.CommandProvider;
import org.osgi.service.component.annotations.Component;
@Component
public class AndQuitCommand implements CommandProvider {
public AndQuitCommand() {
System.out.println("command");
}
// add prints sum of its two arguments
public void _add(CommandInterpreter ci) {
int a = Integer.parseInt(ci.nextArgument());
int b = Integer.parseInt(ci.nextArgument());
ci.println(a+b);
}
// quit just calls "exit"
public void _quit(CommandInterpreter ci) {
ci.execute("exit");
}
@Override
public String getHelp() {
return "";
}
}
16.3. Add to your product / runtime
Add the new plug-ins to your product (via the feature). Start via the product to ensure that it is added to the run configuration.
17. Exercise: Write tests for OSGi plug-ins
This exercise demonstrates how to create and run unit and integration tests for OSGi services from the Eclipse IDE.
You will write simple tests for the com.vogella.tasks.services
plug-in which does not export any API but contains the implementation classes.
17.1. Update your target platform
Add the following to your target platform from the https://download.eclipse.org/releases/latest update site: The easiest way to do this is to use the Source tab of the target editor which also provides code completion.
-
org.junit.jupiter.api
-
org.junit
-
org.junit.jupiter.engine
-
org.junit.jupiter.params
-
org.junit.jupiter.migrationsupport
-
org.eclipse.jdt.junit5.runtime
17.2. Create fragment project for your test
Create a new fragment project called com.vogella.tasks.services.tests via the
menu entry.Enter com.vogella.tasks.services
as host plug-in.
17.3. Write a JUnit unit test
First you implement a unit test, without considering the OSGi environment.
Right-click on the project and select
.Also add JUnit 5 to your build path.
Implement the following class.
package com.vogella.tasks.services.tests;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import com.vogella.tasks.services.internal.TransientTaskServiceImpl;
class TransientTaskServiceImplTests {
@Test
@DisplayName("TransientTaskServiceImpl can be initialized")
void assertThatTaskServiceCanBeInitialized() {
TransientTaskServiceImpl service = new TransientTaskServiceImpl();
assertNotNull(service);
assertTrue(service.getAll().size()>0);
}
@Test
@DisplayName("TransientTaskServiceImpl provides at least one task ")
void assertThatTaskServiceProvidesData() {
TransientTaskServiceImpl service = new TransientTaskServiceImpl();
assertTrue(service.getAll().size()>0);
}
}
17.4. Validate that the JUnit test executes
Right-click the test and select
. The tests should both execute successfully.17.5. Update your manifest dependencies
To access the OSGi API for service in your test, add the following plug-ins as plug-in dependencies to your test plug-in.
-
org.junit
-
org.junit.jupiter.api
-
org.eclipse.osgi
-
org.apache.felix.scr
OSGi promotes the usage of package dependencies to make the design more flexible. For the test, we use plug-in dependencies in this example to make the setup faster. |
org.junit
is necessary to avoid java.lang.ClassNotFoundException: org.apache.maven.surefire.junitplatform.JUnitPlatformProvider for JUnit 5 tests.
See Bug report
The osgi and felix plug-in are required as runtime to provide the OSGi runtime and the OSGi ds component.
17.6. Write an integration test considering the OSGi service environment
In the above test you didn’t use the OSGi service layer but initialized the service yourself. In the next test, you will test if the OSGi server layer correctly initialized the service. This is especially necessary if the services to test reference other services or OSGi features are used, like the EventAdmin for event processing or the ConfigurationAdmin to configure components at runtime.
Create the following unit test.
package com.vogella.tasks.services.tests;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import org.osgi.util.tracker.ServiceTracker;
import com.vogella.tasks.model.TaskService;
public class TransientTaskServiceIntegrationTest {
@Test
public void assertServiceAccessWithOSGiContextWorks() {
TaskService taskService = getService(TaskService.class);
assertNotNull(taskService, "No TaskService found");
}
static <T> T getService(Class<T> clazz) {
Bundle bundle = FrameworkUtil.getBundle(TransientTaskServiceIntegrationTest.class);
if (bundle != null) {
ServiceTracker<T, T> st =
new ServiceTracker<T, T>(
bundle.getBundleContext(), clazz, null);
st.open();
if (st != null) {
try {
// give the runtime some time to startup
return st.waitForService(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return null;
}
}
17.6.1. Validate in the IDE
Right-click the test and select
.17.7. Add to the Tycho build
Add your test plug-in as module to an existing Tycho build. In case you are using pomless builds, this should allow to execute the tests on the command.
Maven Tycho support for JUnit 5 did show in the past sometimes issues. If you face such an issue, temporary disable the test and report to https://bugs.eclipse.org/bugs/enter_bug.cgi?product=Tycho |
The test plug-in should successfully execute the tests.
18. Using the OSGi service low-level API
While the preferred way of defining and providing OSGi services is based on OSGi ds, you can also use OSGi API for this. This chapter describes this API, in case you need it.
18.1. Registering services via the BundleContext
A bundle can also register itself for the events(ServiceEvents
) of the BundleContext
.
These are, for example, triggered if a new bundle is installed or de-installed or if a new service is registered.
To publish a service in your bundle use:
BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
bundleContext.registerService(IMyService.class.getName(), new MzServiceImpl(), null);
Once the service is no longer used, you must unregister the service with OSGi. OSGi counts the usage of services to enable the dynamic replacement of services. So once the service is no longer used by your implementation, unregister it. This is demonstrated by the following code:
context.ungetService(serviceReference);
In the registerService()
method from the BundleContext
class you can specify arbitrary properties in the dictionary parameter.
You can use the getProperty()
method of the ServiceReference
class from the org.osgi.framework
package, to access a specific property.
18.2. Accessing a service via API
A bundle can acquire a service via the BundleContext
class.
The following code demonstrates that.
BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
ServiceReference<?> serviceReference = bundleContext.getServiceReference(IMyService.class.getName());
IMyService service = (IMyService) bundleContext.getService(serviceReference);
18.3. Using an activator for service registration
Avoid using activators as they can slow down the startup of your application. |
A bundle can define a Bundle-Activator
(Activator) in its declaration.
This class must implement the BundleActivator
interface.
If defined, OSGi injects the BundleContext
into the start()
and stop()
methods of the implementing Activator
class.
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
public class Activator implements BundleActivator {
public void start(BundleContext context) throws Exception {
System.out.println("Starting bundle");
// do something with the context, e.g.
// register services
}
public void stop(BundleContext context) throws Exception {
System.out.println("Stopping bundle");
// do something with the context, e.g.
// unregister service
}
}
20. OSGi services resources
include::../10_Include/sourcejava.adoc[] If you need more assistance we offer https://learn.vogella.com/[Online Training] and https://www.vogella.com/training/[Onsite training] as well as https://www.vogella.com/consulting/[consulting]