I am not really sure what you try to achieve but maybe I can shed some light how this is supposed to work. If this does not work it might help to create a real albeit small project on Github that demonstrates your requirements.
# Declarative Services
In Declarative Services there are a rather large number of different mechanisms at work. I just list them here in increasing complexity and then will describe them then one by one:
* Trees & Forest
* PID
* Component service properties
* Default configuration
* Singleton configuration
* Factory configuration
* Mandatory configuration
* PIDs are Public API
* Management Agent
* Metatypes
* Component Factory
* Targets, or how to Wire Services
## Trees & Forest
The OSGi specifications are very cohesive and decoupled. Though this provides numerous benefits it clearly makes it hard to see how the different parts interact for newcomers. More important, how should you actually use them together?
The best advice is to start very simple and use defaults. This basically means the skeleton of your component is:
package com.acme;
@Component
public class Foo implements Bar {
@Reference
EventAdmin eventAdmin;
@Reference
final List<Foo> foo = new CopyOnWriteArrayList<>();
@interface Config {
int port();
}
@Activate void activate( Config config ) {
}
@Deactivate void close() {
}
}
This gives you a static component that can be be fully configured through the `com.acme.Foo` PID with Configuration Admin whatever _Management Agent_ you pick.
service.pid : com.acme.Foo
port : 8080
If you use bndtools, use the osgi.enroute.configurer.simple.provider bundle. This allows you to specify the configuration data in JSON in a bundle in the `configuration/configuration.json` resource. The best practice is to create one ‘application’ project that only holds configuration data and no code. This application project pulls together the components that are used in a specific application and configures them.
If you run into a problem with this basic model (though realize that 95% of the components can get away with these defaults) that does not seem to have a simple solution then first try to see if you redefine the problem to fit the simple model. (Yes, numerous times people define the problem in terms of a specific solution and not of actual requirements. Taking a step back and trying to see what is really required can make a surprising difference.)
If that does not work, ask on a mailing list. Declarative Services are so widely used that is extremely unlikely there is no solution to your actual requirements.
## The PID
The core idea is the PID: an identifier that identifies the configuration for the a component _type_. For example, the following component has a PID of `com.acme.FooImpl`:
package com.acme;
@Component
public class FooImpl { }
## Default Configuration
Let’s forget Configuration Admin for a moment. If you run this component without any configuration then the component will be started since by default it can live without configuration.
## Component Service Properties
If you want to create a service property to be registered (and provided to the `activate` method) then you can provide such a property in the @Component annotation:
@Component(
property=“foo=bar”
)
public class Foo {
@Activate void activate( Map<String,Object> map) {
assert “bar”.equals(map.get(“foo”));
}
}
## Configuration Properties Suck
Properties suck but are often a necessary evil. They do not refactor easily, are error prone in their spelling, require messy conversions to their desired Java types, require casting, and in general look awful in your code with their yelling upper case (assuming you turn the keys into constants). How nice would it be if you could just use type safe constructs?
Well, you can. In the previous examples we used a `Map<String,Object>` to get the service properties in the `activate` method. However, we can also replace this map with any annotation type:
@interface Config {
String foo() default “bar”;
}
@Activate void activate( Foo.Config config ) {
assert “bar”equals( config.foo() );
}
## Factory Configuration
In most cases it makes sense to be able to create multiple instances of a component. For example, you are polling a host for some information in your application. It is common that you want the final application to be able to poll different hosts and not just one. Even if you want it to be just one, it is bad practice to choose a singleton configuration if it is theoretically possible that one day you need two or more.
This is where Configuration Admin comes in. Let’s ignore how we manage Configuration Admin but assume that there are two factory configurations for the ‘com.acme.Foo’ Factory PID:
service.pid : com.acme.Foo-126121312-12112
service.factoryPid : com.acme.Foo
foo : one
service.pid : com.acme.Foo-456781232-83622
service.factoryPid : com.acme.Foo
foo : two
With this configuration the `com.acme.Foo` component will be instantiated twice. One component will get foo=one and the other will get foo=two
@Activate void activate( Foo.Config config) {
String foo = config.foo();
assert “one”.equals(foo) || “two”.equals(foo);
}
## Singleton Configuration
There are cases where a singleton configuration is necessary. If it is logically impossible to have more than one instance then a singleton configuration should be used. However, these cases are more rare than most developers realize. For example, the Apache Felix jetty configuration is a singleton configuration but should likely have been a factory configuration because it is possible to have multiple web servers on different ports. This would have simplified the configuration and made it more flexible.
Assume that there is a singleton Configuration for the ‘com.acme.Foo’ PID:
service.pid : com.acme.Foo
foo : singleton
If we now run our system, the activate method gets called with `foo=singleton`
@Activate void activate( Foo.Config config) {
assert “singleton”.equals( config.foo() );
}
## Mandatory Configuration
Maybe you’ve noticed, but factories now have a rather strange life cycle. If there is no configuration for the given PID, then the component gets instantiated once with the service properties defined in the @Component annotation. If you then create a factory configuration for that PID, the default component is deleted and the factory configuration is used to instantiate a new component. The consequence of this is that you cannot have zero components, there will always be at least one. Though a default instance is sometimes handy, in general it does not make sense. In that case you can make the configuration mandatory: only if the configuration is set (either singleton or factory) will the component be instantiated.
@Component(
configurationPolicy = ConfigurationPolicy.MANDATORY
)
With a mandatory configuration the component is only instantiated when there is a Configuration for the corresponding PID. The following table shows how the configuration interacts with this policy:
Mandatory Optional Ignore
no config no instance default default
singleton config 1 instance 1 instance default
one factory config 1 instance 1 instance default
two factory config 2 instances 2 instances default
PIDs are public API since external parties will be aware of them when they configure your component. For this reason it is sometimes better to create a name that can be kept constant over time even if implementations change.
@Component( name = “com.acme.foo” )
public class FooImpl { }
## Management Agent
The previous section should make it clear how the instances of a component can be controlled through the configurations associated with the component’s PID. Components follow Configuration Admin and, except for some default service properties, should never ever try to configure themselves. Things are a lot simpler if components just do what they are being told by Configuration Admin!
However, that leaves the question: Who sets the Configurations for a given PID? This role is in general fulfilled by the _management agent_. This is not magic entity in OSGi, it is just one or more bundles that perform the role of managing the framework. One of it is roles is to configure the components. This is not a single solution. On the contrary, there are lots of viable solutions. The following lists describes a list of popular management agents:
* FileInstall – Watches a directory and synchronizes any configuration in that directory to Configuration Admin. File Install allows you to manage singleton and factory configurations just by adding/removing/changing a file in a directory. In a cluster, a directory can be shared (even over for example Dropbox). Very convenient.
* Web Console – Apache Felix Web Console is a Web based tool that allows you manage configurations. The Web Console is excellent during debugging and small systems because it is very interactive.
* Configurer – The OSGi enRoute configurer reads JSON resources from active bundles and installs them as configurations. The Configurer is very suitable for production since it delivers configuration as a bundle, just as code bundles.
* Deployment Admin – Provides a means to deliver configuration through Deployment Packages and a Resource Processor. I think Apache Ace uses this.
Of course there is always the option to write your own management agent. The Configuration Admin API is quite flexible though be aware of some of the quirks (locations!). However, postponing the decision to handle your own configuration until there is no alternative and you’ve also become quite experienced is the recommended (in)action.
## Metatype
Using the annotation types significantly cleaned up our code. However, with File Install, Configurer, and Web Console we’re still having to use properties. Since properties are evil, we’d like to have a more elegant solution. This elegant solution is called _metatype_. It is an OSGi service that enables components to describe their configuration types. User agents can then use a metatype to automatically create a form for that configuration. Apache Felix Web Console fully supports metatypes in its web based user interface.
So how do we link our annotation type to a metatype? Though the metatypes are defined in XML there are fortunately (build) annotations for it.
@ObjectClassDefinition
@interface Config {
@AttributeDefinition
String foo();
}
@Designate( ocd = Config.class, factory=true )
@Component
public class Foo {}
The @ObjectClassDefinition and @AttributeDefinition can describe the ‘properties’ and provide additional information to control the user interface. The @Designate annotation links a configuration type (or as we call it: an Object Class Definition or ocd) to a component’s PID. This will create the proper XML for the Metatype service to link it all together.
## Targets, or how to Wire Services
Services are linked to each other by their object classes. I.e. one component requires an instance of the Foo service type. You create that dependency with the @Reference annotation. An @Component annotated type can then register this service. However, sometimes you cannot avoid but make the _wiring_ of the services more complicated. Sometimes you really need to ensure that a component Bar is really wired to a specific instance of Foo.
This can be done with the `target` field in the @Reference annotation.
@Reference( target=“(foo=bar)” )
Foo foo;
A component Foo can then register its service with a number of properties under Configuration Admin control.
@Component public class FooImpl implements Foo {
}
Another component can depend on this property.
@Component public class Bar {
@Reference( target=“(foo=bar)” )
Foo foo;
}
We can now create a configuration for FooImpl
service.pid : com.acme.FooImpl-131231412-15322
service.factoryPid : com.acme.FooImpl
foo : ‘bar’
This will create an instance of the Foo service with a service property ‘foo=bar’. The Bar service has a dependency on this service so it will be wired to it.
Obviously this is not symmetric. We need to know the name of the service property ahead of time in the Bar service. It is therefore possible to set the target field of the @Reference annotation with configuration admin:
service.pid : com.acme.Bar
target.foo : ‘(foo=bar)’
## Component Factory
This part barely made it in the spec and in general it is not advised to use it except when you really know what you’re doing.