Control OSGi DS Component Instances (2024 Edition)
There are use cases where a more fine grained control is needed for component instance creation. I spent some time in investigating how this is done with OSGi Declarative Services in detail. It turned out that it is easier as it seems, mainly because of missing or misleading tutorials. In 2017 I wrote the blog posts Control OSGi DS Component Instances and Control OSGi DS Component Instances via Configuration Admin as part of my OSGi Declarative Service blog post series, where I covered this topic. This blog post is the update of those blog posts, to cover the current state of the OSGi specification and the available tooling. I dediced to combine the two blog posts into one, to find the related information in one place.
For the start you need to know that by default there is only one component configuration created and activated in the OSGi runtime at the same time. This means that every bundle is sharing the same component instance. So you have a singleton instance for every service.
Note:
Singleton instance in terms of “one single instance” not “Singleton Pattern”!
If you think about multi-threading or context dependent services, you may need multiple instances of a service. In an OSGi environment there are basically the following categories:
- one service instance per runtime
- one service instance per bundle
- one service instance per component/requestor
- one service instance per request
Additonally there are scenarios where you need something like a session. But a session is nothing natural in the OSGi world. At least not as natural as it is in the context of a web application. Anyhow, we add the following category:
- one service instance per session/transation
Note:
Instance creation control can only be done for service components. So ensure to specify the service annotation type element in @Component
if the implementation does not implement an interface.
To control the instance creation you can use the following mechanisms:
- Specify the Service Scope by using the
scope
annotation type element of@Component
- Create a Factory Component by using the
factory
annotation type element of@Component
- Create a Component Configuration by using the
ConfigurationAdmin
Note:
The servicefactory
annotation type element of @Component
was deprecated with DS 1.3 in favour of the scope
annotation type element.
This tutorial will therefore not cover the servicefactory
anymore.
Preparation
For some hands on this topic, we first create some bundles to play with.
Note:
I don’t want to explain every step for creating services in detail in this blog post. Especially because it depends on the tooling and the project setup, e.g. PDE vs. Bndtools vs. Maven with Bndtools.
If you don’t know how to perform the necessary steps, please have a look at my Getting Started with OSGi Declarative Services blog post.
- Create an API bundle
org.fipro.oneshot.api
with a service interfaceOneShot
``` java package org.fipro.oneshot;
public interface OneShot { void shoot(String target); }
2. Create a provider bundle `org.fipro.oneshot.provider` with a service implementation `Hitman`
``` java
package org.fipro.oneshot.provider;
import java.util.concurrent.atomic.AtomicInteger;
import org.fipro.oneshot.OneShot;
import org.osgi.service.component.annotations.Component;
@Component
public class Hitman implements OneShot {
private static AtomicInteger instanceCounter = new AtomicInteger();
private final int instanceNo;
public Hitman() {
instanceNo = instanceCounter.incrementAndGet();
}
@Override
public void shoot(String target) {
System.out.println("BAM! I am hitman #" + instanceNo + ". And I killed " + target);
}
}
This implementation will count the number of instances in a static field and remembers it in a member variable, so we can identify the created instance when the service is called.
- Create a command bundle
org.fipro.oneshot.command
with a console command to call the service ``` java package org.fipro.oneshot.command;
import org.fipro.oneshot.OneShot; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference;
@Component( property= { “osgi.command.scope=fipro”, “osgi.command.function=kill” }, service=KillCommand.class) public class KillCommand {
@Reference
private OneShot killer;
public void kill(String target) {
this.killer.shoot(target);
} } ``` 4. Create a command bundle `org.fipro.oneshot.assassinate` with two different console commands that call the service ``` java package org.fipro.oneshot.assassinate;
import org.fipro.oneshot.OneShot; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference;
@Component( property= { “osgi.command.scope=fipro”, “osgi.command.function=assassinate” }, service=AssassinateCommand.class ) public class AssassinateCommand {
@Reference
private OneShot hitman;
public void assassinate(String target) {
hitman.shoot(target);
} } ``` ``` java package org.fipro.oneshot.assassinate;
import org.fipro.oneshot.OneShot; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference;
@Component( property= { “osgi.command.scope=fipro”, “osgi.command.function=eliminate” }, service=EliminateCommand.class, reference = @Reference( name=”hitman”, service=OneShot.class) ) public class EliminateCommand {
@Activate
private ComponentContext context;
public void eliminate(String target) {
OneShot hitman = (OneShot) this.context.locateService("hitman");
hitman.shoot(target);
} } ```
The EliminateCommand
uses the Lookup Strategy to lazily activate the referenced component. For this it needs the ComponentContext
which is injected via field injection. The injection of the ComponentContext
activation object is supported since DS 1.4. Additionally it needs the information about the references to lookup. This can be done either by using a ServiceReference
parameter in ComponentContext#locateService()
or by configuring the lookup strategy references via the @Component
annotation, which is shown in the snippet above.
The usage of the Lookup Strategy is probably quite useless in this example, but I wanted to show how it works and that it works fine.
Scope
For Delayed Components it is possible to specify a service scope, which is described in OSGi Compendium - Declarative Services Specification - Synopsis. On the service consumer side, it is possible to configure the reference scope, which is desribed in OSGi Compendium - Declarative Services Specification - Reference Scope.
The following chapters will explain how to handle component instances by using scopes.
One instance per runtime
There is not much to say about this. This is the default behavior if you do not specify something else. There is only one component configuration created and activated. Therefore only one component instance is created and shared between all bundles.
A singleton instance can be explicitly configured on the component by using the scope
annotation type element with value ServiceScope.SINGLETON
:
@Component(scope=ServiceScope.SINGLETON)
public class Hitman implements OneShot {
Note:
For Immediate Components and Factory Components it is not allowed to use other values for servicefactory
or scope
!
If you launch an OSGi application with the necessary bundles (org.apache.felix.scr
, org.apache.felix.gogo.*
, org.fipro.oneshot.*
) and call the commands kill, assassinate and eliminate one after the other, you should get an output similar to this (on a Felix console):
g! kill Dirk
BAM! I am hitman #1. And I killed Dirk
g! assassinate Dirk
BAM! I am hitman #1. And I killed Dirk
g! eliminate Dirk
BAM! I am hitman #1. And I killed Dirk
Every command has a reference to the same Hitman
instance, as can be seen by the instance counter in the output.
One instance per bundle
There are use cases where it is useful to have one component configuration created and activated per bundle. For example if the component configuration contains special bundle related configurations.
A bundle scope service can be configured on the component by using the scope
annotation type element with value ServiceScope.BUNDLE
:
@Component(scope=ServiceScope.BUNDLE)
public class Hitman implements OneShot {
When launching the OSGi application and calling the commands kill, assassinate and eliminate one after the other, you should get an output similar to this (on a Felix console):
g! kill Dirk
BAM! I am hitman #1. And I killed Dirk
g! assassinate Dirk
BAM! I am hitman #2. And I killed Dirk
g! eliminate Dirk
BAM! I am hitman #2. And I killed Dirk
g! kill Dirk
BAM! I am hitman #1. And I killed Dirk
You can see that the kill command has a reference to the Hitman
instance #1, while the assassinate and the eliminate command both have a reference to the Hitman
instance #2, as both reside in the same bundle.
One instance per requestor
There are some use cases where every consumer needs its own instance of a service.
For this scenario the ServiceScope.PROTOTYPE
can be used.
@Component(scope=ServiceScope.PROTOTYPE)
public class Hitman implements OneShot {
Setting the scope of the service component to ServiceScope.PROTOTYPE
does not mean that every consumer gets a distinct service instance automatically. By default the result will be the same as with using the BUNDLE scope. So if you start the application with the updated Hitman
service, you will get the same result as before.
The reason for this is the reference scope. It is configured on the consumer side via @Reference
and specifies how the service reference should be resolved. There are three possible values:
ReferenceScope.BUNDLE
All component instances in a bundle will use the same service object. (default)ReferenceScope.PROTOTYPE
Every component instance in a bundle may use a distinct service object.ReferenceScope.PROTOTYPE_REQUIRED
Every component instance in a bundle must use a distinct service object.
As the default of the reference scope is ReferenceScope.BUNDLE
, we see the same behavior for service scope ServiceScope.PROTOTYPE
as we saw for service scope ServiceScope.BUNDLE
. That means the consumer components need to be modified to achieve that every command instance gets its own service instance.
@Component(
property= {
"osgi.command.scope=fipro",
"osgi.command.function=assassinate"
},
service=AssassinateCommand.class )
public class AssassinateCommand {
@Reference(scope=ReferenceScope.PROTOTYPE_REQUIRED)
private OneShot hitman;
public void assassinate(String target) {
hitman.shoot(target);
}
}
@Component(
property= {
"osgi.command.scope=fipro",
"osgi.command.function=eliminate"
},
service=EliminateCommand.class,
reference=@Reference(
name="hitman",
service=OneShot.class,
scope=ReferenceScope.PROTOTYPE_REQUIRED)
)
public class EliminateCommand {
@Activate
private ComponentContext context;
public void eliminate(String target) {
OneShot hitman = (OneShot) this.context.locateService("hitman");
hitman.shoot(target);
}
}
Note:
In the example I have chosen to use the reference scope ReferenceScope.PROTOTYPE_REQUIRED
. In the given scenario also ReferenceScope.PROTOTYPE
would be sufficient, as the concrete service implementation uses the ServiceScope.PROTOTYPE
service scope. But IMHO it is better to specify directly which reference scope to use, instead of having a weak rule.
When launching the OSGi application and calling the commands one after the other, you should get an output similar to this (on a Felix console):
g! kill Dirk
BAM! I am hitman #1. And I killed Dirk
g! assassinate Dirk
BAM! I am hitman #2. And I killed Dirk
g! eliminate Dirk
BAM! I am hitman #3. And I killed Dirk
g! kill Dirk
BAM! I am hitman #1. And I killed Dirk
g! eliminate Dirk
BAM! I am hitman #3. And I killed Dirk
You can see that every command instance gets its own service instance. For the kill
command it was not necessary to specify the reference scope because the bundle only has one consumer.
Note:
The above output comes from an execution with the PDE project layout. If you run the example using Bndtools (with or without Maven) the eliminate command will create a new instance everytime.
The reason is how the frameworks deal with service instance disposal. This is explained in more detail in Component Instance Cleanup.
One instance per request
In some use cases it is required to have a distinct service instance per request. This is for example needed for web requests, where it is required that services are created and destroyed in every request, or for multi-threading where services can be executed in parallel (hopefully without side-effects).
Like before the PROTOTYPE
scope helps in solving that requirement, in combination with the ComponentServiceObjects
interface. The implementation of the ComponentServiceObjects
is a factory that allows to create and destroy service instances on demand.
The following example shows the usage. Create it in the org.fipro.oneshot.command
bundle.
package org.fipro.oneshot.command;
import org.fipro.oneshot.OneShot;
import org.osgi.service.component.ComponentServiceObjects;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceScope;
@Component(
property= {
"osgi.command.scope=fipro",
"osgi.command.function=terminate"
},
service=TerminateCommand.class )
public class TerminateCommand {
// get a factory for creating prototype scoped service instances
@Reference(scope=ReferenceScope.PROTOTYPE_REQUIRED)
private ComponentServiceObjects<OneShot> oneShotFactory;
public void terminate(String target) {
// create a new service instance OneShot
OneShot oneShot = oneShotFactory.getService();
try {
oneShot.shoot(target);
}
finally {
// destroy the service instance
oneShotFactory.ungetService(oneShot);
}
}
}
Note:
There is no special modification needed in the component configuration of the provider. It simply needs to be configured with a ServiceScope.PROTOTYPE
as shown before. The consumer needs to decide what instance should be referenced, the same as every service in the bundle, a new one for every component or a new one for each request.
Executing the terminate command multiple times will show that for each call a new Hitman
instance is created. Mixing it with the previous commands will show that the other services keep a fixed instance, while terminate constantly will create and use a new instance per execution.
g! kill Dirk
BAM! I am hitman #1. And I killed Dirk
g! terminate Dirk
BAM! I am hitman #2. And I killed Dirk
g! terminate Dirk
BAM! I am hitman #3. And I killed Dirk
g! terminate Dirk
BAM! I am hitman #4. And I killed Dirk
g! kill Dirk
BAM! I am hitman #1. And I killed Dirk
Factory Component
Using the scope
in the @Component
and the @Reference
annotation already helps with the instance management of Delayed Components.
But there are use cases where it is not sufficient. In such cases you can use a Factory Component to create a service instance per consumer or per request or even for a execution session.
The Factory Component is the third type of components specified by the OSGi Compendium Specification, next to the Immediate Component and the Delayed Component.
It therefore has its own lifecycle, which can be seen in the following diagram.
When the component configuration of a Factory Component is satisfied, a ComponentFactory
is registered. This can be used to activate a new component instance, which is destroyed once it is disposed or the component configuration is not satisfied anymore.
While this looks quite complicated on first sight, it is a lot easier when using DS annotations. You only need to specify the factory
annotation type element on @Component
. The following snippet shows this for a new OneShot
implementation.
For the exercise add it to the org.fipro.oneshot.provider
bundle.
package org.fipro.oneshot.provider;
import java.util.concurrent.atomic.AtomicInteger;
import org.fipro.oneshot.OneShot;
import org.osgi.service.component.annotations.Component;
@Component(factory="fipro.oneshot.factory")
public class Shooter implements OneShot {
private static AtomicInteger instanceCounter = new AtomicInteger();
private final int instanceNo;
public Shooter() {
instanceNo = instanceCounter.incrementAndGet();
}
@Override public void shoot(String target) {
System.out.println("PEW PEW! I am shooter #" + instanceNo + ". And I hit " + target);
}
}
As explained above, the SCR will register a ComponentFactory
that can be used to create and activate new component configurations on demand. On the consumer side this means it is not possible to get a Shooter
service instance via @Reference
, as it is not registered as a Delayed Component. You need to reference a ComponentFactory
instance by specifying the correct target property. The target property needs to be specified for the key component.factory
and the value of the factory
annotation type element on the @Component
annotation of the Factory Component.
The following snippet shows the consumer of a Factory Component. Create it in the org.fipro.oneshot.command
bundle.
package org.fipro.oneshot.command;
import org.fipro.oneshot.OneShot;
import org.osgi.service.component.ComponentFactory;
import org.osgi.service.component.ComponentInstance;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@Component(
property= {
"osgi.command.scope=fipro",
"osgi.command.function=shoot"
},
service=ShootCommand.class )
public class ShootCommand {
@Reference(target = "(component.factory=fipro.oneshot.factory)")
private ComponentFactory<OneShot> factory;
public void shoot(String target) {
// create a new service instance
ComponentInstance<OneShot> instance = this.factory.newInstance(null);
OneShot shooter = instance.getInstance();
try {
shooter.shoot(target);
}
finally {
// destroy the service instance
instance.dispose();
}
}
}
Comparing the Factory Component with the PROTOTYPE
scoped service, the following differences can be seen:
- A
PROTOTYPE
scoped service is a Delayed Component, while the Factory Component is a different component type with its own lifecycle. - A Factory Component can only be consumed by getting the
ComponentFactory
injected, while aPROTOTYPE
scoped service can be created and consumed in different ways. - A component configuration needs to be provided when creating the component instance via
ComponentFactory
. APROTOTYPE
scoped service can simply use the configuration mechanisms provided in combination with the Configuration Admin.
Compared to creating the service instance by using the constructor, the nice thing on using a Factory Component or a PROTOTPYE
scoped service is that the configured service references are resolved by the SCR. You could verify this for example by adding a reference to the StringInverter
service from my previous blog post.
Note:
To create an instance per requestor by using a Factory Component, you would simply create the instance in the @Activate
method, and dispose it on @Deactivate
.
Properties
Thinking about the configuration of component instances and the usage of a Factory Component, we need to distinguish different aspects.
- Specify the
properties
annotation type element of the@Component
annotation
The properties specified in in theproperties
are added to every createdShooter
instance. They are not the properties of the Factory Component.
To test this, update theShooter
implementation:- Specify a property
shooter.name
via the@Component
annotation - Get the component properties injected
- Print the value of the
shooter.name
property to the console ```java package org.fipro.oneshot.provider;
import java.util.Map; import java.util.concurrent.atomic.AtomicInteger;
import org.fipro.oneshot.OneShot; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component;
@Component( factory=”fipro.oneshot.factory”, property = “shooter.name=John Rambo”) public class Shooter implements OneShot {
private static AtomicInteger instanceCounter = new AtomicInteger(); private final int instanceNo; @Activate private Map<String, Object> properties; public Shooter() { instanceNo = instanceCounter.incrementAndGet(); } @Override public void shoot(String target) { System.out.println("PEW PEW! I am shooter #" + instanceNo + ". And I hit " + target); System.out.println("My name is " + properties.get("shooter.name")); } } ``` Start the application and call `shoot Dirk` two times, you will see the following output: ``` g! shoot Dirk PEW PEW! I am shooter #1. And I hit Dirk My name is John Rambo g! shoot Dirk PEW PEW! I am shooter #2. And I hit Dirk My name is John Rambo ```
- Specify a property
ComponentFactory#newInstance(Dictionary)
The parameter inComponentFactory#newInstance(Dictionary)
can be aDictionary
with additional properties provided to the created component configuration. They will be added to the createdShooter
instance, overriding values from the@Component
annotation.
To test this update theShootCommand
:- Create a
HashTable
- Set a value for the
shooter.name
property - Pass the
HashTable
to thenewInstance()
callHashtable<String, Object> properties = new Hashtable<>(); properties.put("shooter.name", "Hitman Agent 47"); ComponentInstance<OneShot> instance = this.factory.newInstance(properties);
Start the application and call
shoot Dirk
two times, you will see the following output:g! shoot Dirk PEW PEW! I am shooter #1. And I hit Dirk My name is Hitman Agent 47 g! shoot Dirk PEW PEW! I am shooter #2. And I hit Dirk My name is Hitman Agent 47
- Create a
-
Specify the
factoryProperty
annotation type element of the@Component
annotation
This has actually no impact on the createdShooter
component instance. These are the properties of the Factory Component, which can be used for example in the Target Property of the factory component reference. This makes it in dynamic configurable environments very helpful, if a Factory Component needs to be resolved based on a configuration, and you for example specify the Target Property dynamically as described in Configuring OSGi Declarative Services.To demonstrate this:
- Add a
factoryPropery
with nameorganization
to theShooter
implementation:@Component( factory="fipro.oneshot.factory", factoryProperty = "organization=army", property = "shooter.name=John Rambo") public class Shooter implements OneShot {
- Create a copy of the
Shooter
class, name itSniper
and modify the values of thefactoryProperty
and theproperty
:package org.fipro.oneshot.provider; import java.util.Map; import org.fipro.oneshot.OneShot; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @Component( factory="fipro.oneshot.factory", factoryProperty = "organization=marine", property = "shooter.name=Bob Lee Swagger") public class Sniper implements OneShot { @Activate private Map<String, Object> properties; @Override public void shoot(String target) { System.out.println("I hit " + target + " and you will never see me!"); System.out.println("My name is " + properties.get("shooter.name")); } }
- Update the
ShootCommand
and extend the LDAP expression of the Target Property for theComponentFactory
@Reference(target = "(&(component.factory=fipro.oneshot.factory)(organization=marine))") private ComponentFactory<OneShot> factory;
- Add a
Start the application now, and you will see the output of Sniper
, but of course the name of the passed properties.
If you remove the shooter.name
property in the ShootCommand
, you will also see the shooter.name
configured in the Sniper
component.
To make it a bit more fun, let’s add some runtime dynamics and add a command that can be used to change the Target Property. The details about this are explained in Configuring OSGi Declarative Services - Target Property
package org.fipro.oneshot.command;
import java.io.IOException;
import java.util.Dictionary;
import java.util.Hashtable;
import org.apache.felix.service.command.Descriptor;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@Component(
property= {
"osgi.command.scope:String=fipro",
"osgi.command.function:String=change"
},
service=ChangeShooterCommand.class
)
public class ChangeShooterCommand {
@Reference
ConfigurationAdmin admin;
@Descriptor("change the organization that will send the shooter")
public void change(
@Descriptor("the name of the organization, can be 'army' or 'marine'") String org) throws IOException {
Configuration config =
this.admin.getConfiguration("org.fipro.oneshot.command.ShootCommand");
Dictionary<String, Object> props = null;
if (config != null && config.getProperties() != null) {
props = config.getProperties();
} else {
props = new Hashtable<>();
}
// change the organization
StringBuilder filter =
new StringBuilder("(&(component.factory=fipro.oneshot.factory)(organization=" + org + "))");
props.put("factory.target", filter.toString());
config.update(props);
}
}
If you now start the application and use the shoot
and change
commands multiple times, the output looks similar to the following example:
g! shoot Dirk
I hit Dirk and you will never see me!
My name is Bob Lee Swagger
g! change army
g! shoot Dirk
PEW PEW! I am shooter #1. And I hit Dirk
My name is John Rambo
g! change marine
g! shoot Dirk
I hit Dirk and you will never see me!
My name is Bob Lee Swagger
Note:
If you pass any other value than army or marine to the change
command, the shoot
command will not be available, because the Target Property of the ComponentFactory
reference can’t be satisfied.
Configuration Admin
As explained initially, there are situations where a service instance is needed for some kind of session. A session is mainly used to associate a set of states to someone (e.g. a user) over time. An alternative view on a session can be in a more technical way, e.g. an execution session like some sort of transaction where the processing is based on a configuration. Configurations for OSGi services are managed by the Configuration Admin. Having these things in mind and searching the web and digging through the OSGi Compendium Specification, I came across the Managed Service Factory and this blog post by Neil Bartlett (already quiet some years old).
To summarize the information in short, the idea is to create a new service instance per Component Configuration. So for every session a new Component Configuration needs to be created, which leads to the creation of a new Component Instance. Typically some unique identifier like the session ID needs to be added to the component properties, so it is possible to use filters based on that.
The Managed Service Factory description in the specification is hard to understand (at least for me), the tutorials that exist mainly focus on the usage without Declarative Services by implementing the corresponding interfaces, and the blog post by Neil unfortunately only covers half of the topic. In the OSGi Compendium Specification - Configuration Admin Service Specification there is also the following statement: “Extenders like Declarative Services use Configurations but bypass the general Managed Service or Managed Service Factory method.” That basically means that with Declarative Services you are not implementing a Managed Service Factory directly, but you are using the same principles.
Therefore I will try to explain how to create service instances for different configurations with a small example using the ConfigurationAdmin
.
Note:
The Factory Component is similar, but from a technical point of view, you create a new component instance by using the ComponentFactory#newInstance()
API. So the service consumer references the ComponentFactory
and creates the instance on demand. Using the ConfigurationAdmin
you create a configuration and you are independent of the service that uses it.
Note:
An introduction to the usage of the ConfigurationAdmin
can be found in the Configuring OSGi Declarative Services blog post.
For the example we again create a service that should be created on demand and a command that consumes the created service. Additionally we create a command to create a configuration.
Service Implementation
Create a new implementation of the OneShot
service interface and put it in the org.fipro.oneshot.provider
bundle.
- Use the classname
Borg
-
Specify a configuration PID so it is not necessary to use the fully qualified class name later.
Remember: the configuration PID defaults to the configured name, which defaults to the fully qualified class name of the component class. - Set the configuration policy REQUIRE, so the component will only be satisfied and therefore activated once a matching configuration object is set by the Configuration Admin.
- Create the Component Property Type
BorgConfig
for type safe access to the Configuration Properties. - Get the configuration injected when the component is activated.
- Add the modified lifecycle event method to be able to change the configuration at runtime.
- Add an instance counter to verify the created instances.
package org.fipro.oneshot.provider;
import java.util.concurrent.atomic.AtomicInteger;
import org.fipro.oneshot.OneShot;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Modified;
@Component(
configurationPid="Borg",
configurationPolicy=ConfigurationPolicy.REQUIRE)
public class Borg implements OneShot {
@interface BorgConfig {
String name() default "";
}
@Activate
BorgConfig config;
private static AtomicInteger instanceCounter = new AtomicInteger();
private final int instanceNo;
public Borg() {
instanceNo = instanceCounter.incrementAndGet();
}
@Modified
void modified(BorgConfig config) {
this.config = config;
}
@Override
public void shoot(String target) {
System.out.println("Borg " + config.name() + " #" + instanceNo + " of "+ instanceCounter.get()
+ " took orders and executed " + target);
}
}
Configuration Creation
The next step is to create a configuration. For this we need to have a look at the ConfigurationAdmin
API.
In my Configuring OSGi Declarative Services blog post I only talked about ConfigurationAdmin#getConfiguration(String, String)
.
This is used to get or create the configuration of a singleton service.
For the configuration policy REQUIRE this means that a single Managed Service is created once the Configuration
object is used by a requesting bundle.
In such a case the Configuration Properties will contain the property service.pid
with the value of the configuration PID.
To create and handle multiple service instances via Component Configuration, a different API needs to be used.
For creating new Configuration
objects (not get or create) there is ConfigurationAdmin#createFactoryConfiguration(String, String)
.
This way a Managed Service Factory will be registered by the requesting bundle, which allows to create multiple Component Instances with different configurations.
In this case the Configuration Properties will contain the property service.factoryPid
with the value of the configuration PID and the service.pid
with a unique value.
As it is not possible to mix Managed Services and Managed Service Factories with the same PID, another method needs to be used to access existing configurations.
For this ConfigurationAdmin#listConfigurations(String)
can be used. The parameter can be a filter and the result will be an array of Configuration
objects that match the filter.
The filter needs to be an LDAP filter that can test any Configuration Properties, including service.pid
and service.factoryPid
.
The following snippet will only return existing Configuration
objects for the Borg
service when it was created via Managed Service Factory.
this.configAdmin.listConfigurations("(service.factoryPid=Borg)")
The parameters of ConfigurationAdmin#getConfiguration(String, String)
and ConfigurationAdmin#createFactoryConfiguration(String, String)
are actually the same. The first parameter is the PID that needs to match the configuration PID of the component, the second is the location binding. It is best practice to use “?” as value for the location parameter.
Create the following console command in the org.fipro.oneshot.command
bundle:
package org.fipro.oneshot.command;
import java.io.IOException;
import java.util.Dictionary;
import java.util.Hashtable;
import org.apache.felix.service.command.CommandProcessor;
import org.apache.felix.service.command.Descriptor;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@Component(
property= {
CommandProcessor.COMMAND_SCOPE + "=fipro",
CommandProcessor.COMMAND_FUNCTION + "=assimilate"},
service=AssimilateCommand.class
)
public class AssimilateCommand {
@Reference
ConfigurationAdmin configAdmin;
@Descriptor("assimilates the given soldier to the Borg")
public void assimilate(
@Descriptor("the name of the soldier to assimilate") String soldier) {
assimilate(soldier, null);
}
@Descriptor("assimilates the given soldier to the Borg with the given name")
public void assimilate(
@Descriptor("the name of the soldier to assimilate") String soldier,
@Descriptor("the new name for the assimilated Borg") String newName) {
try {
// filter to find the Borg created by the
// Managed Service Factory with the given name
String filter = "(&(name=" + soldier + ")" + "(service.factoryPid=Borg))";
Configuration[] configurations = this.configAdmin.listConfigurations(filter);
if (configurations == null || configurations.length == 0) {
//create a new configuration
Configuration config = this.configAdmin.createFactoryConfiguration("Borg", "?");
Hashtable<String, Object> map = new Hashtable<>();
if (newName == null) {
map.put("name", soldier);
System.out.println("Assimilated " + soldier);
} else {
map.put("name", newName);
System.out.println("Assimilated " + soldier + " and named it " + newName);
}
config.update(map);
} else if (newName != null) {
// update the existing configuration
Configuration config = configurations[0];
// it is guaranteed by listConfigurations() that only
// Configuration objects are returned with non-null properties
Dictionary<String, Object> map = config.getProperties();
map.put("name", newName);
config.update(map);
System.out.println(soldier + " already assimilated and renamed to " + newName);
}
} catch (IOException | InvalidSyntaxException e1) {
e1.printStackTrace();
}
}
}
In the above snippet name is used as the unique identifier for a created Component Instance.
Let’s explain what happens in the above snippet:
- Check if there is already a
Configuration
object in the database for the given name and factory.
This is done by usingConfigurationAdmin#listConfigurations(String)
with an LDAP filter for the name and the Managed Service Factory withservice.factoryPid=Borg
. The Managed Service Factory PID is the value of the configuration PID used for theBorg
service component. - If there is no configuration available for a
Borg
with the given name, a newConfiguration
object is created viaConfigurationAdmin#createFactoryConfiguration(String, String)
. - If there is a configuration available and a
newName
is passed, update theConfiguration
viaConfiguration#update(Dictionary)
Note:
To verify the Configuration Properties you could implement an activate method in the Borg
implementation to show them on the console like in the following snippet:
@Activate
void activate(Map<String, Object> properties) {
properties.forEach((k, v) -> {
System.out.println(k+"="+v);
});
System.out.println();
}
Once a service instance is activated it should output all Configuration Properties, including the service.pid
and service.factoryPid
for the instance.
Service Consumer
Finally we create the following execute command in the org.fipro.oneshot.command
bundle to verify the instance creation:
package org.fipro.oneshot.command;
import java.util.List;
import java.util.ListIterator;
import org.fipro.oneshot.OneShot;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@Component(
property= {
"osgi.command.scope=fipro",
"osgi.command.function=execute"
},
service=ExecuteCommand.class )
public class ExecuteCommand {
@Reference(target="(service.factoryPid=Borg)")
private volatile List<OneShot> borgs;
public void execute(String target) {
for (ListIterator<OneShot> it = borgs.listIterator(borgs.size()); it.hasPrevious(); ) {
it.previous().shoot(target);
}
}
}
For simplicity we have a dynamic reference to all available OneShot
service instances that have the service.factoryPid=Borg
.
As a short reminder on the field injection in OSGi DS:
If the type is a Collection
the cardinality is 0..n, and marking it volatile
specifies it to be a dynamic reluctant reference.
Starting the application and executing some assimilate and execute commands will show something similar to the following on the console:
g! assimilate Lars
Assimilated Lars
g! assimilate Simon
Assimilated Simon
g! execute Dirk
Borg Lars #1 of 2 took orders and executed Dirk
Borg Simon #2 of 2 took orders and executed Dirk
g! assimilate Lars Locutus
Lars already assimilated and renamed to Locutus
g! execute Dirk
Borg Locutus #1 of 2 took orders and executed Dirk
Borg Simon #2 of 2 took orders and executed Dirk
The first two assimilate calls create new Borg
service instances. This is verified by the execute command. The following assimilate call renames an existing Borg
, so no new service instance is created.
Component Instance Cleanup
When talking about component instances, you also need to talk about the lifecycle methods. In some situations you might expect that a component instance is deactivated, because it is not used anymore, but it actually is not.
To experiment with that you can modify for example Hitman
and KillCommand
and add methods for @Activate
and @Deactivate
.
@Activate
void activate() {
System.out.println(getClass().getSimpleName() + " activated");
}
@Deactivate
void deactivate() {
System.out.println(getClass().getSimpleName() + " deactivated");
}
If you start the OSGi application and call the kill
command, you will notice that:
- in the PDE variant the
Hitman
and theKillCommand
component get activated, but they are not deactivated. - in the Bndtools variant the
Hitman
and theKillCommand
component get activated, and theKillCommand
is deactivated directly after the execution. TheHitman
component is not deactivated, even though it is inServiceScope.PROTOTYPE
. The reason is that theKillCommand
does not specify theReferenceScope.PROTOTYPE_REQUIRED
, and therefore it is consumed inReferenceScope.BUNDLE
. And as the bundle as component instance consumer is still active, theHitman
component is not automatically deactivated.
Note:
For testing you can change the reference scope of the OneShot
reference in the KillCommand
to ReferenceScope.PROTOTYPE_REQUIRED
.
If you execute the kill
command now, the Hitman
instance will also be deactivated.
If you try to execute the kill
command a second time, you might notice an error. At least if you are using Felix SCR 2.2.10 or 2.2.12.
You will get a ServiceRegistrationImpl: Error ungetting service. (java.lang.IllegalArgumentException)
when running on Felix as OSGi implementation. With Equinox as OSGi implementation the exception is not directly shown, but consecutive calls
of the kill
command will fail with NullPointerException: Cannot invoke "Object.getClass()" because "target" is null
. There is already a ticket for this issue in the Felix bug tracker.
With Felix SCR 2.2.6 you will not see the above error it works as intented.
The reason for the different behavior in the Equinox PDE project layout and Bndtools is the following.
For Delayed Components the OSGi Compendium Specification says:
If the service registered by a component configuration becomes unused because there are no more bundles using it, then SCR should deactivate that component configuration.
Should is a quite weak statement, so it is easy to have a different understanding of this part of the specification. Apache Felix SCR is taking that statement very serious and deactivates and destroys the component once the last consumer that references the component instance is done with it. Equinox DS on the other hand kept the instance. And even though Equinox DS was replaced with Felix SCR in Eclipse, the default behavior of keeping component instances was kept for the PDE project setup.
This behavior can be configured via the system property ds.delayed.keepInstances
as described in Felix SCR Configuration.
To keep component instances and not dispose them once they are no longer used (like the Equinox DS / Eclipse default behavior):
-Dds.delayed.keepInstances=true
To destroy component instances once they are no longer used (the Felix SCR default behavior):
-Dds.delayed.keepInstances=false
To get some more insight on this you might also want to look at the ticket in the Felix bug tracker where this was discussed.
Add the JVM arguments for the runtime you are experimenting with and check the results.
Conclusion
In this blog post I explained how to control the creation of service instances in DS. For this I used:
- Service & Reference Scopes
- Factory Components
- Configuration Admin
Using the right method for your use case, it is possible to deal with service instances as required. Especially the usage of a service per session typically creates some headaches. But with this blog post, you should be able to handle this in the future.
Of course you can find the sources of this tutorial in my GitHub account:
- OSGi DS Getting Started (PDE)
This repository contains the sources in PDE project layout. - OSGi DS Getting Started (Bndtools)
This repository contains the sources in Bndtools project layout using a Bndtools workspace. - OSGi DS Gettings Started (Bnd with Maven)
This repository contains the sources in a Maven project layout that uses the bnd Maven plugins.