FELIX-5177:
- added a configType attribute in FactoryConfigurationAdapterService annotation.
- when using a config type with ConfigurationDependency, then assume that pid is set to the fqdn of the provided config type, in case
no pid has already been set using setPid method.
- code cleanup.
- adapted samples to use configuration type.
- added javadocs.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1730934 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/dependencymanager/org.apache.felix.dependencymanager.annotation/src/org/apache/felix/dm/annotation/api/ConfigurationDependency.java b/dependencymanager/org.apache.felix.dependencymanager.annotation/src/org/apache/felix/dm/annotation/api/ConfigurationDependency.java
index 6ef1f60..d747c59 100644
--- a/dependencymanager/org.apache.felix.dependencymanager.annotation/src/org/apache/felix/dm/annotation/api/ConfigurationDependency.java
+++ b/dependencymanager/org.apache.felix.dependencymanager.annotation/src/org/apache/felix/dm/annotation/api/ConfigurationDependency.java
@@ -22,6 +22,9 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Map;
/**
@@ -29,11 +32,21 @@
* is always required, and allows you to depend on the availability of a valid configuration
* for your component. This dependency requires the OSGi Configuration Admin Service.
*
+ * The annotation can be applied on a callback method which accepts the following parameters:
+ *
+ * <p><ul>
+ * <li>callback(Dictionary)
+ * <li>callback(Component, Dictionary)
+ * <li>callback(Configuration interface) // type safe configuration
+ * <li>callback(Component, Configuration interface) // type safe configuration
+ * </ul>
+ *
* <h3>Usage Examples</h3>
*
* <p> In the following example, the "Printer" component depends on a configuration
* whose PID name is "sample.PrinterConfiguration". This service will initialize
* its ip/port number from the provided configuration.
+ *
* <p> First, we define the configuration metadata, using standard bndtools metatatype annotations
* (see http://www.aqute.biz/Bnd/MetaType):
*
@@ -63,10 +76,8 @@
*
* @Component
* public class Printer {
- * @ConfigurationDependency(pidClass = PrinterConfiguration.class) // Will use pid "sample.PrinterConfiguration"
- * void updated(Dictionary props) {
- * // load configuration from the provided dictionary, or throw an exception of any configuration error.
- * PrinterConfig cnf = Configurable.createConfigurable(PrinterConfig.class, props);
+ * @ConfigurationDependency // Will use the fqdn of the PrinterConfiguration interface as the pid.
+ * void updated(PrinterConfiguration cnf) {
* String ip = cnf.ipAddress();
* int port = cnf.portNumber();
* ...
@@ -75,6 +86,59 @@
* </pre>
* </blockquote>
*
+ * In the above example, the updated callback accepts a type-safe configuration type (and its fqdn is used as the pid).
+ * <p> Configuration type is a new feature that allows you to specify an interface that is implemented
+ * by DM and such interface is then injected to your callback instead of the actual Dictionary.
+ * Using such configuration interface provides a way for creating type-safe configurations from a actual {@link Dictionary} that is
+ * normally injected by Dependency Manager.
+ * The callback accepts in argument an interface that you have to provide, and DM will inject a proxy that converts
+ * method calls from your configuration-type to lookups in the actual map or dictionary. The results of these lookups are then
+ * converted to the expected return type of the invoked configuration method.<br>
+ * As proxies are injected, no implementations of the desired configuration-type are necessary!
+ * </p>
+ * <p>
+ * The lookups performed are based on the name of the method called on the configuration type. The method names are
+ * "mangled" to the following form: <tt>[lower case letter] [any valid character]*</tt>. Method names starting with
+ * <tt>get</tt> or <tt>is</tt> (JavaBean convention) are stripped from these prefixes. For example: given a dictionary
+ * with the key <tt>"foo"</tt> can be accessed from a configuration-type using the following method names:
+ * <tt>foo()</tt>, <tt>getFoo()</tt> and <tt>isFoo()</tt>.
+ * </p>
+ * <p>
+ * The return values supported are: primitive types (or their object wrappers), strings, enums, arrays of
+ * primitives/strings, {@link Collection} types, {@link Map} types, {@link Class}es and interfaces. When an interface is
+ * returned, it is treated equally to a configuration type, that is, it is returned as a proxy.
+ * </p>
+ * <p>
+ * Arrays can be represented either as comma-separated values, optionally enclosed in square brackets. For example:
+ * <tt>[ a, b, c ]</tt> and <tt>a, b,c</tt> are both considered an array of length 3 with the values "a", "b" and "c".
+ * Alternatively, you can append the array index to the key in the dictionary to obtain the same: a dictionary with
+ * "arr.0" => "a", "arr.1" => "b", "arr.2" => "c" would result in the same array as the earlier examples.
+ * </p>
+ * <p>
+ * Maps can be represented as single string values similarly as arrays, each value consisting of both the key and value
+ * separated by a dot. Optionally, the value can be enclosed in curly brackets. Similar to array, you can use the same
+ * dot notation using the keys. For example, a dictionary with
+ *
+ * <pre>{@code "map" => "{key1.value1, key2.value2}"}</pre>
+ *
+ * and a dictionary with <p>
+ *
+ * <pre>{@code "map.key1" => "value1", "map2.key2" => "value2"}</pre>
+ *
+ * result in the same map being returned.
+ * Instead of a map, you could also define an interface with the methods <tt>getKey1()</tt> and <tt>getKey2</tt> and use
+ * that interface as return type instead of a {@link Map}.
+ * </p>
+ * <p>
+ * In case a lookup does not yield a value from the underlying map or dictionary, the following rules are applied:
+ * <ol>
+ * <li>primitive types yield their default value, as defined by the Java Specification;
+ * <li>string, {@link Class}es and enum values yield <code>null</code>;
+ * <li>for arrays, collections and maps, an empty array/collection/map is returned;
+ * <li>for other interface types that are treated as configuration type a null-object is returned.
+ * </ol>
+ * </p>
+ *
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
@Retention(RetentionPolicy.CLASS)
@@ -92,9 +156,10 @@
* You can use this method when you use an interface annotated with standard bndtols metatype annotations.
* (see http://www.aqute.biz/Bnd/MetaType).
* @return the pid class
+ * @deprecated just define an updated callback which accepts as argument a configuration type.
*/
Class<?> pidClass() default Object.class;
-
+
/**
* Returns true if the configuration properties must be published along with the service.
* Any additional service properties specified directly are merged with these.
diff --git a/dependencymanager/org.apache.felix.dependencymanager.annotation/src/org/apache/felix/dm/annotation/api/FactoryConfigurationAdapterService.java b/dependencymanager/org.apache.felix.dependencymanager.annotation/src/org/apache/felix/dm/annotation/api/FactoryConfigurationAdapterService.java
index 245a12c..a95689e 100644
--- a/dependencymanager/org.apache.felix.dependencymanager.annotation/src/org/apache/felix/dm/annotation/api/FactoryConfigurationAdapterService.java
+++ b/dependencymanager/org.apache.felix.dependencymanager.annotation/src/org/apache/felix/dm/annotation/api/FactoryConfigurationAdapterService.java
@@ -31,6 +31,13 @@
* Depending on the <code>propagate</code> parameter, every public factory configuration properties
* (which don't start with ".") will be propagated along with the adapter service properties.
*
+ * <p> If you specify a configuration type, then the fqdn of the configuration interface is used as the factory pid,
+ * else you can specify the factory pid explicitly using the factoryPid attribute.
+ * If no configuration type is used and no factoryPid attribute is specified, then the factory pid will be set to the fqdn of
+ * the class on which this annotation is applied.
+ *
+ * <p> (see javadoc from {@link ConfigurationDependency} for more informations about configuration types).
+ *
* <h3>Usage Examples</h3>
* Here, a "Dictionary" service instance is created for each existing "sample.DictionaryConfiguration" factory pids.
*
@@ -55,19 +62,16 @@
* </pre>
* </blockquote>
*
- * And here is the Dictionary service:
- *
+ * And here is the factory pid adapter service, which is instantiated for each instance of the "sample.DictionaryConfiguration" factory pid:
+ *
* <blockquote>
* <pre>
* import java.util.List;
* import aQute.bnd.annotation.metatype.Configurable;
*
- * @FactoryConfigurationAdapterService(factoryPidClass=DictionaryConfiguration.class)
+ * @FactoryConfigurationAdapterService(configType=DictionaryConfiguration.class)
* public class DictionaryImpl implements DictionaryService {
- * protected void updated(Dictionary<String, ?> props) {
- * // load configuration from the provided dictionary, or throw an exception of any configuration error.
- * DictionaryConfiguration cnf = Configurable.createConfigurable(DictionaryConfiguration.class, props);
- *
+ * protected void updated(DictionaryConfiguration config) {
* m_lang = config.lang();
* m_words.clear();
* for (String word : conf.words()) {
@@ -79,6 +83,7 @@
* </pre>
* </blockquote>
*
+ *
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
@Retention(RetentionPolicy.CLASS)
@@ -100,6 +105,15 @@
Property[] properties() default {};
/**
+ * Returns the type safe configuration class which will be injected in the updated callback.
+ * By default, the factory pid is assumed to match the fqdn of the configuration type.
+ * see javadoc from {@link ConfigurationDependency} for more informations about configuration types.
+ * @return the configuration type to pass in the "updated" callback argument.
+ * @see ConfigurationDependency
+ */
+ Class<?> configType() default Object.class;
+
+ /**
* Returns the factory pid whose configurations will instantiate the annotated service class. (By default, the pid is the
* service class name).
* @return the factory pid
@@ -111,6 +125,8 @@
* You can use this method when you use an interface annoted with standard bndtols metatype annotations.
* (see http://www.aqute.biz/Bnd/MetaType).
* @return the factory pid class
+ * @deprecated use {@link #configType()} and accept a configuration type parameter from your updated callback. The pid
+ * is then assumed to match the fqdn of the configuration type.
*/
Class<?> factoryPidClass() default Object.class;
diff --git a/dependencymanager/org.apache.felix.dependencymanager.annotation/src/org/apache/felix/dm/annotation/plugin/bnd/AnnotationCollector.java b/dependencymanager/org.apache.felix.dependencymanager.annotation/src/org/apache/felix/dm/annotation/plugin/bnd/AnnotationCollector.java
index 5a9497f..978571c 100644
--- a/dependencymanager/org.apache.felix.dependencymanager.annotation/src/org/apache/felix/dm/annotation/plugin/bnd/AnnotationCollector.java
+++ b/dependencymanager/org.apache.felix.dependencymanager.annotation/src/org/apache/felix/dm/annotation/plugin/bnd/AnnotationCollector.java
@@ -640,7 +640,7 @@
if (! Dictionary.class.getName().equals(confProxyType))
{
// It's a conf proxy type.
- writer.put(EntryParam.confProxyType, confProxyType);
+ writer.put(EntryParam.configType, confProxyType);
}
else
{
@@ -655,7 +655,7 @@
if (! Dictionary.class.getName().equals(confProxyType))
{
// It's a conf proxy type.
- writer.put(EntryParam.confProxyType, confProxyType);
+ writer.put(EntryParam.configType, confProxyType);
}
else
{
@@ -922,12 +922,19 @@
// factory pid attribute (can be specified using the factoryPid attribute, or using the factoryPidClass attribute)
String factoryPidClass = parseClassAttrValue(annotation.get(EntryParam.factoryPidClass.toString()));
- // If a factory pid class is specified, consider it as a possible candidate for a configuration proxy.
- if (factoryPidClass != null) {
- writer.put(EntryParam.confProxyType, factoryPidClass);
+ // Test if a type safe configuration type is provided.
+ String configType = parseClassAttrValue(annotation.get(EntryParam.configType.toString()));
+
+ if (configType != null) {
+ writer.put(EntryParam.configType, configType);
}
- String factoryPid = factoryPidClass != null ? factoryPidClass : get(annotation, EntryParam.factoryPid.toString(), m_className);
+ String factoryPid = null;
+
+ factoryPid = get(annotation, EntryParam.factoryPid.toString(), factoryPidClass);
+ if (factoryPid == null) {
+ factoryPid = configType != null ? configType : m_className;
+ }
writer.put(EntryParam.factoryPid, factoryPid);
diff --git a/dependencymanager/org.apache.felix.dependencymanager.annotation/src/org/apache/felix/dm/annotation/plugin/bnd/EntryParam.java b/dependencymanager/org.apache.felix.dependencymanager.annotation/src/org/apache/felix/dm/annotation/plugin/bnd/EntryParam.java
index bff3b7a..2835f70 100644
--- a/dependencymanager/org.apache.felix.dependencymanager.annotation/src/org/apache/felix/dm/annotation/plugin/bnd/EntryParam.java
+++ b/dependencymanager/org.apache.felix.dependencymanager.annotation/src/org/apache/felix/dm/annotation/plugin/bnd/EntryParam.java
@@ -44,7 +44,7 @@
autoConfig,
pid,
pidClass,
- confProxyType, // inject a proxy configuration type
+ configType, // inject a proxy configuration type
factoryPid,
factoryPidClass,
propagate,
diff --git a/dependencymanager/org.apache.felix.dependencymanager.runtime/src/org/apache/felix/dm/runtime/DependencyBuilder.java b/dependencymanager/org.apache.felix.dependencymanager.runtime/src/org/apache/felix/dm/runtime/DependencyBuilder.java
index 7171103..d724df2 100644
--- a/dependencymanager/org.apache.felix.dependencymanager.runtime/src/org/apache/felix/dm/runtime/DependencyBuilder.java
+++ b/dependencymanager/org.apache.felix.dependencymanager.runtime/src/org/apache/felix/dm/runtime/DependencyBuilder.java
@@ -139,7 +139,7 @@
private Dependency createConfigurationDependency(Bundle b, DependencyManager dm) throws Exception
{
- String confProxyType = m_metaData.getString(Params.confProxyType, null);
+ String confProxyType = m_metaData.getString(Params.configType, null);
String pid = m_metaData.getString(Params.pid);
boolean propagate = "true".equals(m_metaData.getString(Params.propagate, "false"));
String callback = m_metaData.getString(Params.updated, "updated");
diff --git a/dependencymanager/org.apache.felix.dependencymanager.runtime/src/org/apache/felix/dm/runtime/FactoryConfigurationAdapterServiceBuilder.java b/dependencymanager/org.apache.felix.dependencymanager.runtime/src/org/apache/felix/dm/runtime/FactoryConfigurationAdapterServiceBuilder.java
index 9f13923..b74108c 100644
--- a/dependencymanager/org.apache.felix.dependencymanager.runtime/src/org/apache/felix/dm/runtime/FactoryConfigurationAdapterServiceBuilder.java
+++ b/dependencymanager/org.apache.felix.dependencymanager.runtime/src/org/apache/felix/dm/runtime/FactoryConfigurationAdapterServiceBuilder.java
@@ -51,7 +51,7 @@
String[] provides = srvMeta.getStrings(Params.provides, null);
Dictionary<String, Object> properties = srvMeta.getDictionary(Params.properties, null);
boolean propagate = "true".equals(srvMeta.getString(Params.propagate, "false"));
- String configProxyClassName = srvMeta.getString(Params.confProxyType, null);
+ String configProxyClassName = srvMeta.getString(Params.configType, null);
Component c = null;
if (configProxyClassName != null)
diff --git a/dependencymanager/org.apache.felix.dependencymanager.runtime/src/org/apache/felix/dm/runtime/Params.java b/dependencymanager/org.apache.felix.dependencymanager.runtime/src/org/apache/felix/dm/runtime/Params.java
index ee2dd86..44868c8 100644
--- a/dependencymanager/org.apache.felix.dependencymanager.runtime/src/org/apache/felix/dm/runtime/Params.java
+++ b/dependencymanager/org.apache.felix.dependencymanager.runtime/src/org/apache/felix/dm/runtime/Params.java
@@ -65,5 +65,5 @@
componentField,
registered,
unregistered,
- confProxyType
+ configType
}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.samples/hello.api.bnd b/dependencymanager/org.apache.felix.dependencymanager.samples/hello.api.bnd
index 79a44d6..7c5262f 100644
--- a/dependencymanager/org.apache.felix.dependencymanager.samples/hello.api.bnd
+++ b/dependencymanager/org.apache.felix.dependencymanager.samples/hello.api.bnd
@@ -14,8 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-Private-Package: \
- org.apache.felix.dependencymanager.samples.hello.api
Bundle-Activator: org.apache.felix.dependencymanager.samples.hello.api.Activator
Bundle-Description: Dependency Manager hello example with API
-Bundle-Name: Dependency Manager Hello Example
\ No newline at end of file
+Bundle-Name: Dependency Manager Hello Example
+Private-Package: org.apache.felix.dependencymanager.samples.hello.api
\ No newline at end of file
diff --git a/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/conf/Configurator.java b/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/conf/Configurator.java
index afc1809..727f284 100644
--- a/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/conf/Configurator.java
+++ b/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/conf/Configurator.java
@@ -21,6 +21,7 @@
import java.io.IOException;
import java.util.Hashtable;
+import org.apache.felix.dependencymanager.samples.hello.api.ServiceConsumerConf;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.log.LogService;
@@ -41,13 +42,13 @@
System.out.println("Configuring sample components ... please consult log messages to see example output, like this:");
System.out.println("\"log warn\"");
// Provide configuration to the hello.ServiceConsumer component
- m_serviceConsumerConf = m_ca.getConfiguration("org.apache.felix.dependencymanager.samples.hello.api.ServiceConsumer", null);
+ m_serviceConsumerConf = m_ca.getConfiguration("org.apache.felix.dependencymanager.samples.hello.api.ServiceConsumerConf", null);
Hashtable<String, String> props = new Hashtable<>();
props.put("key", "value");
m_serviceConsumerConf.update(props);
// Provide configuration to the hello.annot.ServiceConsumer component
- m_serviceConsumerAnnotConf = m_ca.getConfiguration("org.apache.felix.dependencymanager.samples.hello.annot.ServiceConsumer", null);
+ m_serviceConsumerAnnotConf = m_ca.getConfiguration("org.apache.felix.dependencymanager.samples.hello.annot.ServiceConsumerConf", null);
props = new Hashtable<>();
props.put("key", "value");
m_serviceConsumerAnnotConf.update(props);
diff --git a/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/hello/annot/ServiceConsumer.java b/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/hello/annot/ServiceConsumer.java
index 4093371..7d3e5bf 100644
--- a/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/hello/annot/ServiceConsumer.java
+++ b/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/hello/annot/ServiceConsumer.java
@@ -39,15 +39,16 @@
@ServiceDependency
volatile LogService log;
- Dictionary<?, ?> conf;
+ ServiceConsumerConf conf;
- @ConfigurationDependency
- protected void update(Dictionary<?, ?> conf) {
+ @ConfigurationDependency
+ protected void update(ServiceConsumerConf conf) { // type safe config
this.conf = conf;
}
@Start
public void start() {
+ log.log(LogService.LOG_WARNING, "ServiceConsumer.start: configured key=" + conf.getKey());
log.log(LogService.LOG_WARNING, "ServiceConsumer.start: calling service.hello() ...");
this.service.hello();
}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/hello/annot/ServiceConsumerConf.java b/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/hello/annot/ServiceConsumerConf.java
new file mode 100644
index 0000000..a26e5cc
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/hello/annot/ServiceConsumerConf.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dependencymanager.samples.hello.annot;
+
+/**
+ * This is our type-safe configuration for the ServiceConsumer component.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public interface ServiceConsumerConf {
+ String getKey();
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/hello/api/Activator.java b/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/hello/api/Activator.java
index 8dbec58..f688d94 100644
--- a/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/hello/api/Activator.java
+++ b/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/hello/api/Activator.java
@@ -37,8 +37,7 @@
dm.add(createComponent()
.setImplementation(ServiceConsumer.class)
.add(createServiceDependency().setService(LogService.class).setRequired(true))
- .add(createConfigurationDependency()
- .setPid(ServiceConsumer.class.getName()).setCallback("updated"))
+ .add(createConfigurationDependency().setCallback("updated", ServiceConsumerConf.class))
.add(createServiceDependency().setService(ServiceProvider.class).setRequired(true)));
}
}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/hello/api/ServiceConsumer.java b/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/hello/api/ServiceConsumer.java
index 058a5db..a5097b7 100644
--- a/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/hello/api/ServiceConsumer.java
+++ b/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/hello/api/ServiceConsumer.java
@@ -30,13 +30,14 @@
public class ServiceConsumer {
volatile ServiceProvider service;
volatile LogService log;
- Dictionary<?, ?> conf;
-
- protected void update(Dictionary<?, ?> conf) {
+ ServiceConsumerConf conf;
+
+ protected void updated(ServiceConsumerConf conf) {
this.conf = conf;
}
public void start() {
+ log.log(LogService.LOG_WARNING, "ServiceConsumer.start: configured key = " + conf.getKey());
log.log(LogService.LOG_WARNING, "ServiceConsumer.start: calling service.hello()");
this.service.hello();
}
diff --git a/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/hello/api/ServiceConsumerConf.java b/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/hello/api/ServiceConsumerConf.java
new file mode 100644
index 0000000..a464ff7
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager.samples/src/org/apache/felix/dependencymanager/samples/hello/api/ServiceConsumerConf.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dependencymanager.samples.hello.api;
+
+/**
+ * This is our type-safe configuration for the ServiceConsumer component.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public interface ServiceConsumerConf {
+ String getKey();
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ConfigurationDependency.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ConfigurationDependency.java
index a4a97fc..d34015d 100644
--- a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ConfigurationDependency.java
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ConfigurationDependency.java
@@ -18,6 +18,10 @@
*/
package org.apache.felix.dm;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Map;
+
import aQute.bnd.annotation.ProviderType;
/**
@@ -48,8 +52,61 @@
* The callback invoked when a configuration dependency is updated can supports the following signatures:<p>
* <ul><li> updated(Dictionary)
* <li> updated(Component, Dictionary)
+ * <li> updated(Configuration interface)
+ * <li> updated(Component, Configuration interface)
* </ul>
*
+ * <p> Configuration interface is a new feature that allows you to specify an interface that is implemented
+ * by DM and such interface is then injected to your callback instead of the actual Dictionary.
+ * Using such configuration interface provides a way for creating type-safe configurations from a actual {@link Dictionary} that is
+ * normally injected by Dependency Manager.
+ * The callback accepts in argument an interface that you have to provide, and DM will inject a proxy that converts
+ * method calls from your configuration-type to lookups in the actual map or dictionary. The results of these lookups are then
+ * converted to the expected return type of the invoked configuration method.<br>
+ * As proxies are injected, no implementations of the desired configuration-type are necessary!
+ * </p>
+ * <p>
+ * The lookups performed are based on the name of the method called on the configuration type. The method names are
+ * "mangled" to the following form: <tt>[lower case letter] [any valid character]*</tt>. Method names starting with
+ * <tt>get</tt> or <tt>is</tt> (JavaBean convention) are stripped from these prefixes. For example: given a dictionary
+ * with the key <tt>"foo"</tt> can be accessed from a configuration-type using the following method names:
+ * <tt>foo()</tt>, <tt>getFoo()</tt> and <tt>isFoo()</tt>.
+ * </p>
+ * <p>
+ * The return values supported are: primitive types (or their object wrappers), strings, enums, arrays of
+ * primitives/strings, {@link Collection} types, {@link Map} types, {@link Class}es and interfaces. When an interface is
+ * returned, it is treated equally to a configuration type, that is, it is returned as a proxy.
+ * </p>
+ * <p>
+ * Arrays can be represented either as comma-separated values, optionally enclosed in square brackets. For example:
+ * <tt>[ a, b, c ]</tt> and <tt>a, b,c</tt> are both considered an array of length 3 with the values "a", "b" and "c".
+ * Alternatively, you can append the array index to the key in the dictionary to obtain the same: a dictionary with
+ * "arr.0" => "a", "arr.1" => "b", "arr.2" => "c" would result in the same array as the earlier examples.
+ * </p>
+ * <p>
+ * Maps can be represented as single string values similarly as arrays, each value consisting of both the key and value
+ * separated by a dot. Optionally, the value can be enclosed in curly brackets. Similar to array, you can use the same
+ * dot notation using the keys. For example, a dictionary with
+ *
+ * <pre>{@code "map" => "{key1.value1, key2.value2}"}</pre>
+ *
+ * and a dictionary with <p>
+ *
+ * <pre>{@code "map.key1" => "value1", "map2.key2" => "value2"}</pre>
+ *
+ * result in the same map being returned.
+ * Instead of a map, you could also define an interface with the methods <tt>getKey1()</tt> and <tt>getKey2</tt> and use
+ * that interface as return type instead of a {@link Map}.
+ * </p>
+ * <p>
+ * In case a lookup does not yield a value from the underlying map or dictionary, the following rules are applied:
+ * <ol>
+ * <li>primitive types yield their default value, as defined by the Java Specification;
+ * <li>string, {@link Class}es and enum values yield <code>null</code>;
+ * <li>for arrays, collections and maps, an empty array/collection/map is returned;
+ * <li>for other interface types that are treated as configuration type a null-object is returned.
+ * </ol>
+ * </p>
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
@ProviderType
@@ -98,6 +155,7 @@
* is available. The contract for this method is identical to that of
* <code>ManagedService.updated(Dictionary) throws ConfigurationException</code> with the difference that
* instead of a Dictionary it accepts an interface of the given configuration type.<br>
+ * By default, the pid is assumed to match the fqdn of the configuration type.
*
* <p>The callback is invoked on the instantiated component.
*
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/DependencyManager.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/DependencyManager.java
index 8a21037..87ea8df 100644
--- a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/DependencyManager.java
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/DependencyManager.java
@@ -467,8 +467,10 @@
* <li> updated(Component, Dictionary)
* </ul>
* @param propagate true if public factory configuration should be propagated to the adapter service properties
- * @param configType the configuration type to use instead of a dictionary or map.
+ * @param configType the configuration type to use instead of a dictionary. See the javadoc from {@link ConfigurationDependency} for
+ * more informations about type-safe configuration.
* @return a service that acts as a factory for generating the managed service factory configuration adapter
+ * @see ConfigurationDependency
*/
public Component createFactoryConfigurationAdapterService(String factoryPid, String update, boolean propagate, Class<?> configType) {
return new FactoryConfigurationAdapterImpl(this, factoryPid, update, propagate, null, configType);
@@ -491,7 +493,8 @@
* </ul>
* @param propagate true if public factory configuration should be propagated to the adapter service properties
* @param callbackInstance the object on which the updated callback will be invoked.
- * @param configType the configuration type to use instead of a dictionary or map.
+ * @param configType the configuration type to use instead of a dictionary. See the javadoc from {@link ConfigurationDependency} for
+ * more informations about type-safe configuration.
* @return a service that acts as a factory for generating the managed service factory configuration adapter
*/
public Component createFactoryConfigurationAdapterService(String factoryPid, String update, boolean propagate, Object callbackInstance, Class<?> configType) {
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/ComponentContext.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/ComponentContext.java
index b3aa039..82b9a0e 100644
--- a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/ComponentContext.java
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/ComponentContext.java
@@ -18,6 +18,7 @@
*/
package org.apache.felix.dm.context;
+import java.util.Dictionary;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -156,4 +157,15 @@
* @return all the available dependency services for a given dependency
*/
public Set<Event> getDependencyEvents(DependencyContext dc);
+
+ /**
+ * Creates a configuration for a given type backed by a given dictionary.
+ * This method can be used by any custom Dependency Manager dependency that
+ * needs to expose some configuration through a dynamic proxy interface.
+ *
+ * @param type the configuration class, cannot be <code>null</code>;
+ * @param config the configuration to wrap, cannot be <code>null</code>.
+ * @return an instance of the given type that wraps the given configuration.
+ */
+ public <T> T createConfigurationProxy(Class<T> type, Dictionary<?, ?> config);
}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentImpl.java
index 0a7a49f..13ff38e 100644
--- a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentImpl.java
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentImpl.java
@@ -341,6 +341,11 @@
}
@Override
+ public <T> T createConfigurationProxy(Class<T> type, Dictionary<?, ?> config) {
+ return Configurable.create(type, config);
+ }
+
+ @Override
public Executor getExecutor() {
return m_executor;
}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java
index 1122d44..5725161 100644
--- a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java
@@ -21,6 +21,7 @@
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Dictionary;
+import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
@@ -126,8 +127,10 @@
* Sets a type-safe callback method invoked on the instantiated component.
*/
public ConfigurationDependency setCallback(String callback, Class<?> configType) {
+ Objects.nonNull(configType);
setCallback(callback);
m_configType = configType;
+ m_pid = (m_pid == null) ? configType.getName() : m_pid;
return this;
}
@@ -136,8 +139,10 @@
* The component is not yet instantiated at the time the callback is invoked.
*/
public ConfigurationDependency setCallback(Object instance, String callback, Class<?> configType) {
+ Objects.nonNull(configType);
setCallback(instance, callback);
m_configType = configType;
+ m_pid = (m_pid == null) ? configType.getName() : m_pid;
return this;
}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/FilterComponent.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/FilterComponent.java
index f7ebb98..5c27564 100644
--- a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/FilterComponent.java
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/FilterComponent.java
@@ -67,6 +67,11 @@
}
@Override
+ public <T> T createConfigurationProxy(Class<T> type, Dictionary<?, ?> config) {
+ return m_component.createConfigurationProxy(type, config);
+ }
+
+ @Override
public Executor getExecutor() {
return m_component.getExecutor();
}