The OSGi Service Platform Service Compendium specification defines a
number of additional services that may be supported by OSGi
implementations. Spring Dynamic Modules supports an additional
"compendium" namespace that provides integration with some of these services.
By convention, the prefix osgix
is used for this
namespace:
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/osgi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgix="http://www.springframework.org/schema/osgi-compendium" xmlns:beans="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd http://www.springframework.org/schema/osgi-compendium http://www.springframework.org/schema/osgi-compendium/spring-osgi-compendium.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- use the OSGi namespace elements directly --> <service id="simpleServiceOsgi" ref="simpleService" interface="org.xyz.MyService" /> <!-- qualify compendium namespace elements --> <osgix:cm-properties id="cm" persistent-id="com.xyz.myapp"/> </beans:beans>
Compendium namespace declaration (bound to | |
Schema location (namespace URI) | |
XML schema to use for the compendium namespace |
At present this namespace provides support for the Configuration Admin service. Support for other compendium services may be added in future releases.
One of the most important compendium services, is the Configuration Admin which, as a name implies, provides configuration to interested bundles through the OSGi service registry. Spring DM provides dedicated support for Configuration Admin (CM), allowing consumption and injection of the configuration data in a declarative way.
In its simplest form, the CM can be seen as a configuration source, namely a Dictionary
whose
keys are always String
s. Spring DM can expose entries in the CM as a Properties
object,
through the cm-properties
element. A minimal declaration looks as follows:
<osgix:cm-properties id="ds.cfg" persistent-id="data.source.office.1"/>
The configuration above, exposes the properties available in the CM under data.source.office.1 entry as a bean named ds.cfg.
Note | |
---|---|
The |
Those familiar with Spring's
util namespace will
find <osgi:cm-properties/>
element similar to <util:properties/>
.
It is possible to specify a default set of property values to be used in the event that the configuration dictionary does not contain
an entry for a given key. The declaration is similar to the props
element inside the Spring beans namespace:
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/osgi-compendium" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:osgix="http://www.springframework.org/schema/osgi-compendium" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/osgi-compendium http://www.springframework.org/schema/osgi-compendium/spring-osgi-compendium.xsd"> <osgix:cm-properties id="cfg.with.defaults" persistent-id="data.source.office.2"> <beans:prop key="host">localhost</beans:prop> <beans:prop key="port">3306</beans:prop> </osgix:cm-properties> </beans:beans>
By default, the properties found in the Configuration Admin entry will override the local properties. Thus, for the previous example, if the
data.source.office.2
configuration contains a host entry, its value will override the locally defined
localhost
. For cases where this behaviour is undesired, the attribute local-override
(default false
) allows one to revert the merging algorithm, forcing the local properties to override the entries in the CM.
Since cm-properties
exposes the CM entries as Properties
, it can be used with Spring's
PropertyPlaceholderConfigurer
and PropertyOverrideConfigurer
to externalize and customize environment-specific properties:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgix="http://www.springframework.org/schema/osgi-compendium" xmlns:ctx="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/osgi-compendium http://www.springframework.org/schema/osgi-compendium/spring-osgi-compendium.xsd"> <!-- Configuration Admin entry --> <osgix:cm-properties id="cmProps" persistent-id="com.xyz.myapp"> <prop key="host">localhost</prop> </osgix:cm-properties> <!-- placeholder configurer --> <ctx:property-placeholder properties-ref="cmProps" /> <bean id="dataSource" ...> <property name="host" value="${host}"/> <property name="timeout" value="${timeout}"/> </bean> </beans>
An important aspect of cm-properties
is does not reflect
any that any subsequent changes made to the entry it represents, made through the Configuration Admin API.
That is, once resolved, the cm-properties
content remains the same, regardless of any updates
made the to CM entry it represents.
Based on a configuration admin entry, Spring DM can autowire by name, the properties of a given bean. To use this feature, define
a nested managed-properties
inside the bean definition:
<bean id="managedComponent" class="MessageTank"> <osgix:managed-properties persistent-id="com.xyz.messageservice"/> </bean>
For each key in the dictionary stored by Configuration Admin under the given persistent id,
if the bean type has a property with a matching name (following JavaBeans conventions),
then that component property will be dependency injected with the value stored in Configuration Admin under the key.
If the definition of SomeClass
from the example above is as follows:
public class MessageTank { private int amount; public int getAmount() { return this.amount; } public void setAmount(int amount) { this.amount = amount; } }
and the configuration dictionary stored under the pid com.xyz.messageservice
contains an entry
amount=200
, then the setAmount
method will be invoked on the bean
instance during configuration, passing in the value 200
.
If a property value is defined both in the configuration dictionary stored in the Configuration Admin service and in a property element declaration nested in the component element, then the value from Configuration Admin takes precedence:
<bean id="managedComponent" class="MessageTank"> <osgix:managed-properties persistent-id="com.xyz.messageservice"/> <property name="amount" value="100"/> <property name="threshold" value="500"/> </bean>
Property values specified via property elements can therefore be treated as default values to be used if none is available through Configuration Admin.
Warning | |
---|---|
Do not share the same persistent-id (PID) between multiple bundles or definitions, as only one
of them will receive notifications. managed-properties relies on org.osgi.service.cm.ManagedService
contract which mandates that each ManagedService instance must be identified with its own unique PID.
Please see the Configuration Admin spec, specifically section 104.3 and 104.5 |
A powerful feature of Configuration Admin is the ability to update (or delete) entries at runtime. That is, the configuration data
stored in Configuration Admin may be updated after the bean has been created. By default, any post-creation updates will be ignored. However,
one can configure managed-properties
element to receive configuration updates through the update-strategy
attribute, which can have a value of either bean-managed
or container-managed
.
bean-managed
strategy will pass all the updates made to the configuration to a callback present on the bean, specified
through the update-method
attribute (which becomes required). The update method must have one of the following signatures:
public void anyMethodName(Map properties) public void anyMethodName(Map<String,?> properties); // for Java 5
In contrast, the container-managed
update strategy will re-inject bean properties by name based on the new properties
received in the update. For container-managed
updates, the component class must provide setter methods for the component properties
that it wishes to have updated. Consider the following class definitions:
public class ContainerManagedBean { // will be reinjected (since it has a setter) private Integer integer; // will not be reinjected (no setter present) private Long waitTime; public void setInteger(Integer integer) { this.integer = integer; } } public class SelfManagedBean { // update callback public void updateCallback(Map properties) { System.out.println("Received properties " + properties); System.out.println("Props can be used as a Dictionary " + (Dictionary) properties); // do more work ... } }
and configuraton:
<bean id="containerManaged" class="ContainerManagedBean"> <osgix:managed-properties persistent-id="labX" update-strategy="container-managed"/> <property name="integer" value="23"/> </bean> <bean id="beanManaged" class="SelfManagedBean"> <osgix:managed-properties persistent-id="labY" update-strategy="bean-managed" update-method="updateCallback"/> </bean>
Any updates made to the CM entry labX
will be automatically reinjected on existing instances of
containerManaged
bean while the labY
updates will be passed to updateCallback
method.
The update options are summarized in the table below:
Table 10.1. Managed Properties Update Options
update-strategy | update-method | Behaviour |
---|---|---|
container-managed | ignored | Reinjects the bean properties, using the properties present in the update. The re-injection will be applied while locking (through
a synchronized instruction) the bean instance. If the locking or re-injection strategy is not suitable, consider using
the bean-managed approach. |
bean-managed | required | Invokes the update-method callback on the bean instance, passing the updated configuration (as a
Map object that can be safely cast to a Dictionary if needed). No locking is
performed. |
The Configuration Admin service supports a notion of a managed service factory(see section 104.6 in the Compendium Specification).
A managed service factory is identified by a factory pid which allows multiple Configuration
objects
to be associated with the factory. Configuration
objects associated with the factory can be added or removed at any point.
The main intent of a factory is to create an OSGi service for each configuration: adding a new Configuration
entry results
in a new OSGi service being registered, removing a Configuration
, unregisters the service.
Spring DM provides support for the managed service factory concept through the managed-service-factory
element. Once
defined, the configuration associated with the factory pid will automatically create (or remove) bean instances which will be registered (or unregistered)
in the OSGi space based on a template bean definition and the CM configuration.
This might sound more complicated then it actually is, so let's look at a simplistic example:
<osgix:managed-service-factory id="simple-msf" factory-pid="com.xyz.messageservice" auto-export="all-classes"> <bean class="com.xyz.MessageTank"/> </osgix:managed-service-factory>
factory persistent id (pid) | |
Shortcut flag used to determine under what interfaces the OSGi service is published (more info below) | |
bean definition template. For each detected configuration, a new service will be created using the bean definition template. |
In its simplest form, the managed-service-factory
requires the factory pid, a bean definition
used as a template and some information on how possible bean instances are published as services. Basically, the definition above instructs Spring DM to
to monitor the given factory pid (through a dedicated ManagedServiceFactory
implementation (see the Compendium Spec for
more info)) and for every Configuration
object associated with the factory pid, to create a new, anonymous instance of
the nested bean declared and export that instance as an OSGi service. The lifecycle of these beans instances is tied to the lifecycle of the
associated Configuration
objects. If a new configuration is added, a new bean is created and exported.
If a configuration object is deleted or disassociated from the factory pid then the corresponding bean instance is destroyed.
In many regards, managed-service-factory
acts as a specialized service exporter, similar to the
service
element but supporting the concept of
managed properties. In fact, many of
service
's attributes that indicate how a bean is exported, are found in managed-service-factory
(as you saw in the
previous example with auto-export
) as are the managed-properties
attributes.
The list of attributes can be found below:
Table 10.2. Managed Service Factory Options
Name | Values | Description | |||
---|---|---|---|---|---|
interface | fully qualified class name (such as java.lang.Thread ) | the fully qualified name of the class under which the object will be exported | |||
context-class-loader | unmanaged | service-provider | Defines how the context class loader will be managed when an operation is invoked on the
exported service. The default value is unmanaged which means that no management of
the context class loader is attempted. A value of service-provider guarantees that
the context class loader will have visibility of all the resources on the class path of
bundle exporting the service. | ||
auto-export | disabled (default) | interfaces | class-hierarchy | all-classes | Enables Spring to automatically manage the set of service interfaces advertised for the
service. By default this facility is disabled . A value of interfaces advertises all
of the Java interfaces supported by the exported service. A value of class-hierarchy
advertises all the Java classes in the hierarchy of the exported service. A value of
all-classes advertises all Java interfaces and classes. |
update-strategy | none (default) | bean-managed | container-managed | Defines the update strategy for configuration modifications made after the associated beans have been created. |
Similar to the service
element, a list of interfaces or/and registration listeners can be declared to be notified when a
service is being registered/unregistered. For more information on the semantics, please see Section 7.1.1, “Controlling The Set Of Advertised Service Interfaces For
An Exported Service” and
Section 7.1.7, “Service Registration And Unregistration Lifecycle” chapters.
Now that the managed-service-factory
options have been explained, let's look at a more complex configuration:
<bean id="queueTracker" class="org.xyz.queue.QueueTracker"/> <osgix:managed-service-factory id="data-msf" factory-pid="org.xyz.labX" update-strategy="bean-managed" update-method="refresh"> <osgix:interfaces> <value>java.util.Collection</value> <value>java.util.Queue</value> </osgix:interfaces> <osgix:registration-listener ref="queueTracker" registration-method="track" unregistration-method="untrack"/> <bean class="com.xyz.ResizableQueue"> <property name="size" value="100"/> <property name="concurrency" value="10"/> <property name="fair" value="false"/> </bean> </osgix:managed-service-factory>
| |
how should Spring DM behave when a | |
the method to invoke when for | |
the interfaces under which the nested beans are published as OSGi services | |
listener notified when a service (based on the CM | |
custom (optional) service registration method | |
custom (optional) service unregistration method | |
bean definition template |
The example above, creates a imaginary ResizeableQueue
instance for each Configuration
entry
present under the org.xyz.labX
factory pid. Each instance has default values assigned to size
, concurrency
and fair
parameters. However, just like managed-properties
, during the bean creation, the values received from the
Configuration Admin will be injected by name, possibly overriding existing settings. Once created and configured, each nested, anonymous bean instance
is registered as an OSGi service under the java.util.Collection
and java.util.Queue
interfaces. The OSGi service lifecycle is monitored by a registration listener, namely the bean queueTracker
.
Finally, due to the specified update-strategy
, any updates executed to each CM configuration will cause the
refresh
callback to be invoked on the associated bean instance.
The simplest way to work directly with the configuration data stored under a given persistent id or factory persistent id,
is to register a service that implements either the ManagedService
or ManagedServiceFactory
interface and specify the pid that you are interested in as a service property (for more information, see the Configuration Admin chapter
in the OSGi compendium spec). For example:
<osgi:service interface="org.osgi.service.cm.ManagedService" ref="myManagedService"> <osgi:service-properties> <entry key="service.pid" value="my.managed.service.pid"/> </osgi:service-properties> </osgi:service> <bean id="myManagedService" class="com.xyz.MyManagedService"/>