Author: pderop
Date: Thu Feb  4 22:55:19 2016
New Revision: 1728564

URL: http://svn.apache.org/viewvc?rev=1728564&view=rev
Log:
FELIX-5177: Applied the patch from Jan Willem, and slightly modified it in 
order to check when callbackInstance is null.
(when callbackInstance is null, the callback must be invoked on the 
instantiated component).

Also, added more signatures for the new callback methods.
Fixed the ConfigurationDependencyTest, in order to ensure that updated() is 
invoked before init() in the ConfigurationConsumerWithTypeSafeConfiguration 
class.

Added:
    
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/Configurable.java
    
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/org/apache/felix/dm/impl/
    
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/org/apache/felix/dm/impl/ConfigurableTest.java
    
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/org/apache/felix/dm/impl/ConfigurationDependencyImplTest.java
Modified:
    
felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ConfigurationDependencyTest.java
    
felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceRaceTest.java
    
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ConfigurationDependency.java
    
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java

Modified: 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ConfigurationDependencyTest.java
URL: 
http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ConfigurationDependencyTest.java?rev=1728564&r1=1728563&r2=1728564&view=diff
==============================================================================
--- 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ConfigurationDependencyTest.java
 (original)
+++ 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ConfigurationDependencyTest.java
 Thu Feb  4 22:55:19 2016
@@ -42,6 +42,26 @@ import org.osgi.service.cm.ManagedServic
 public class ConfigurationDependencyTest extends TestBase {
     final static String PID = "ConfigurationDependencyTest.pid";
     
+    /**
+     * Tests that we can provision a type-safe configuration to a component.
+     */
+    public void 
testComponentWithRequiredConfigurationWithTypeSafeConfiguration() {
+        DependencyManager m = getDM();
+        // helper class that ensures certain steps get executed in sequence
+        Ensure e = new Ensure();
+        // create a service provider and consumer
+        Component s1 = m.createComponent().setImplementation(new 
ConfigurationConsumerWithTypeSafeConfiguration(e)).add(m.createConfigurationDependency().setCallback(null,
 "updated", MyConfig.class).setPid(PID).setPropagate(true));
+        Component s2 = m.createComponent().setImplementation(new 
ConfigurationCreator(e, 
PID)).add(m.createServiceDependency().setService(ConfigurationAdmin.class).setRequired(true));
+        m.add(s1);
+        m.add(s2);
+        e.waitForStep(3, 5000); // component called in updated(), then in 
init()
+        m.remove(s1);
+        m.remove(s2);
+        // ensure we executed all steps inside the component instance
+        e.waitForStep(3, 5000); // conf creator is called in destroy and is 
about to delete the conf
+        e.waitForStep(4, 5000); // type safe conf consumer is destroyed
+    }
+    
     public void 
testComponentWithRequiredConfigurationAndServicePropertyPropagation() {
         DependencyManager m = getDM();
         // helper class that ensures certain steps get executed in sequence
@@ -53,7 +73,7 @@ public class ConfigurationDependencyTest
         m.add(s1);
         m.add(s2);
         m.add(s3);
-        e.waitForStep(4, 50000000);
+        e.waitForStep(4, 5000);
         m.remove(s1);
         m.remove(s2);
         m.remove(s3);
@@ -342,4 +362,35 @@ public class ConfigurationDependencyTest
             m_runnable.run();
         }
     }
+    
+    static interface MyConfig {
+        String getTestkey();
+    }
+
+    static class ConfigurationConsumerWithTypeSafeConfiguration {
+        private final Ensure m_ensure;
+
+        public ConfigurationConsumerWithTypeSafeConfiguration(Ensure e) {
+            m_ensure = e;
+        }
+
+        // configuration updates is always the first invoked callback (before 
init).
+        public void updated(Component component, MyConfig cfg) throws 
ConfigurationException {
+            Assert.assertNotNull(component);
+            Assert.assertNotNull(cfg);
+            m_ensure.step(2);
+            if (!"testvalue".equals(cfg.getTestkey())) {
+                Assert.fail("Could not find the configured property.");
+            }
+        }
+
+        // called after configuration has been injected.
+        public void init() {
+            m_ensure.step(3); 
+        }
+        
+        public void destroy() {
+            m_ensure.step(4); 
+        }        
+    }
 }

Modified: 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceRaceTest.java
URL: 
http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceRaceTest.java?rev=1728564&r1=1728563&r2=1728564&view=diff
==============================================================================
--- 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceRaceTest.java
 (original)
+++ 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ServiceRaceTest.java
 Thu Feb  4 22:55:19 2016
@@ -47,7 +47,7 @@ import org.osgi.service.cm.Configuration
 @SuppressWarnings({"unchecked", "rawtypes"})
 public class ServiceRaceTest extends TestBase {
     volatile ConfigurationAdmin m_cm;
-    final static int STEP_WAIT = 5000;
+    final static int STEP_WAIT = 15000;
     final static int DEPENDENCIES = 10;
     final static int LOOPS = 3000;
     final Ensure m_done = new Ensure(true);

Modified: 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ConfigurationDependency.java
URL: 
http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ConfigurationDependency.java?rev=1728564&r1=1728563&r2=1728564&view=diff
==============================================================================
--- 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ConfigurationDependency.java
 (original)
+++ 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ConfigurationDependency.java
 Thu Feb  4 22:55:19 2016
@@ -59,8 +59,8 @@ public interface ConfigurationDependency
      * is available. The contract for this method is identical to that of
      * <code>ManagedService.updated(Dictionary) throws 
ConfigurationException</code>.
      * By default, if this method is not called, the callback name is 
"updated".
-     * The callback is always invoked with an already instantiated component 
(the component implementation class(es) are
-     * always instantiated before the updated callback is invoked).
+     * 
+     * <p> The callback is invoked on the instantiated component.
      * 
      * @param callback the name of the callback method
      */
@@ -70,10 +70,11 @@ public interface ConfigurationDependency
      * Sets the name of the callback method that should be invoked when a 
configuration
      * is available. The contract for this method is identical to that of
      * <code>ManagedService.updated(Dictionary) throws 
ConfigurationException</code>.
-     * The callback is called with a component that is not yet instantiated. 
This allows factory objects to get
-     * injected with a configuration before its <code>create</code> method is 
called.
      * 
-     * @param instance the instance to call the callbacks on
+     * <p> the callback is invoked on the callback instance, and the component 
is not 
+     * yet instantiated at the time the callback is invoked.
+     * 
+     * @param instance the object to invoke the callback on
      * @param callback the name of the callback method
      */
     ConfigurationDependency setCallback(Object instance, String callback);
@@ -82,18 +83,61 @@ public interface ConfigurationDependency
      * Sets the name of the callback method that should be invoked when a 
configuration
      * is available. The contract for this method is identical to that of
      * <code>ManagedService.updated(Dictionary) throws 
ConfigurationException</code>.
-     * The component instance is instantiated before the callback is invoked 
only the the <code>needsInstance</code> parameter is set to true.
      * 
-     * @param instance the instance to call the callback on
+     * <p> the callback is invoked on the callback instance, and if 
<code>needsInstance</code> is true, 
+     * the component is instantiated at the time the callback is invoked 
+     * 
+     * @param instance the object to invoke the callback on.
      * @param callback the name of the callback method
-     * @param needsInstance true if the component implementation class(es) 
must be created before the
-     *        callback instance is invoked, else false.
+     * @param needsInstance true if the component must be instantiated before 
the callback is invoked on the callback instance.
      */
     ConfigurationDependency setCallback(Object instance, String callback, 
boolean needsInstance);
-    
+
+    /**
+     * Sets the name of the callback method that should be invoked when a 
configuration
+     * 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>
+     * 
+     * <p>The callback is invoked on the instantiated component.
+     * 
+     * @param callback the name of the callback method
+     * @param configType the configuration type that the callback method 
accepts.
+     */
+    ConfigurationDependency setCallback(String callback, Class<?> configType);
+
+    /**
+     * Sets the name of the callback method that should be invoked when a 
configuration
+     * 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>
+     * 
+     * <p> The callback is invoked on the callback instance, and at this point 
the component is not yet instantiated.
+     * 
+     * @param instance the object to invoke the callback on.
+     * @param callback the name of the callback method
+     * @param configType the configuration type that the callback method 
accepts.
+     */
+    ConfigurationDependency setCallback(Object instance, String callback, 
Class<?> configType);
+
+    /**
+     * Sets the name of the callback method that should be invoked when a 
configuration
+     * 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>
+     * 
+     * <p> the callback is invoked on the callback instance, and if 
<code>needsInstance</code> is true, 
+     * the component is instantiated at the time the callback is invoked 
+     * 
+     * @param instance the object to invoke the callback on.
+     * @param callback the name of the callback method
+     * @param configType the configuration type that the callback method 
accepts.
+     * @param needsInstance true if the component must be instantiated before 
the callback is invoked on the callback instance.
+     */
+    ConfigurationDependency setCallback(Object instance, String callback, 
Class<?> configType, boolean needsInstance);
+
     /**
-     * Sets the <code>service.pid</code> of the configuration you are depending
-     * on.
+     * Sets the <code>service.pid</code> of the configuration you are 
depending on.
      */
        ConfigurationDependency setPid(String pid);
 

Added: 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/Configurable.java
URL: 
http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/Configurable.java?rev=1728564&view=auto
==============================================================================
--- 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/Configurable.java
 (added)
+++ 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/Configurable.java
 Thu Feb  4 22:55:19 2016
@@ -0,0 +1,450 @@
+/*
+ * 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.dm.impl;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+/**
+ * Provides a way for creating type-safe configurations from a {@link Map} or 
{@link Dictionary}.
+ * <p>
+ * This class takes a map or dictionary along with a class, the 
configuration-type, and returns a proxy that converts
+ * method calls from the configuration-type to lookups in the 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 returned, 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" =&gt; "a", "arr.1" =&gt; "b", "arr.2" =&gt; "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 <tt>"map" => 
"{key1.value1, key2.value2}"</tt> and a
+ * dictionary with <tt>"map.key1" => "value1", "map2.key2" => "value2"</tt> 
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>
+ */
+public final class Configurable {
+
+    static class ConfigHandler implements InvocationHandler {
+        private final ClassLoader m_cl;
+        private final Map<?, ?> m_config;
+
+        public ConfigHandler(ClassLoader cl, Map<?, ?> config) {
+            m_cl = cl;
+            m_config = config;
+        }
+
+        @Override
+        public Object invoke(Object proxy, Method method, Object[] args) 
throws Throwable {
+            String name = getPropertyName(method.getName());
+
+            Object result = convert(method.getGenericReturnType(), name, 
m_config.get(name), false /* useImplicitDefault */);
+            if (result == null) {
+                Object defaultValue = getDefaultValue(method, name);
+                if (defaultValue != null) {
+                    return defaultValue;
+                }
+            }
+
+            return result;
+        }
+
+        private Object convert(ParameterizedType type, String key, Object 
value) throws Exception {
+            Class<?> resultType = (Class<?>) type.getRawType();
+            if (Class.class.isAssignableFrom(resultType)) {
+                if (value == null) {
+                    return null;
+                }
+                return m_cl.loadClass(value.toString());
+            }
+            else if (Collection.class.isAssignableFrom(resultType)) {
+                Collection<?> input = toCollection(key, value);
+
+                if (resultType == Collection.class || resultType == 
List.class) {
+                    resultType = ArrayList.class;
+                }
+                else if (resultType == Set.class || resultType == 
SortedSet.class) {
+                    resultType = TreeSet.class;
+                }
+                else if (resultType == Queue.class) {
+                    resultType = LinkedList.class;
+                }
+                else if (resultType.isInterface()) {
+                    throw new RuntimeException("Unknown collection interface: 
" + resultType);
+                }
+
+                Collection<Object> result = (Collection<Object>) 
resultType.newInstance();
+                if (input != null) {
+                    Type componentType = type.getActualTypeArguments()[0];
+                    for (Object i : input) {
+                        result.add(convert(componentType, key, i, false /* 
useImplicitDefault */));
+                    }
+                }
+                return result;
+            }
+            else if (Map.class.isAssignableFrom(resultType)) {
+                Map<?, ?> input = toMap(key, value);
+
+                if (resultType == SortedMap.class) {
+                    resultType = TreeMap.class;
+                }
+                else if (resultType == Map.class) {
+                    resultType = LinkedHashMap.class;
+                }
+                else if (resultType.isInterface()) {
+                    throw new RuntimeException("Unknown map interface: " + 
resultType);
+                }
+
+                Map<Object, Object> result = (Map<Object, Object>) 
resultType.newInstance();
+                Type keyType = type.getActualTypeArguments()[0];
+                Type valueType = type.getActualTypeArguments()[1];
+
+                for (Map.Entry<?, ?> entry : input.entrySet()) {
+                    result.put(convert(keyType, key, entry.getKey(), false /* 
useImplicitDefault */), convert(valueType, key, entry.getValue(), false /* 
useImplicitDefault */));
+                }
+                return result;
+            }
+
+            throw new RuntimeException("Unhandled type: " + type);
+        }
+
+        private Object convert(Type type, String key, Object value, boolean 
useImplicitDefault) throws Exception {
+            if (type instanceof ParameterizedType) {
+                return convert((ParameterizedType) type, key, value);
+            }
+            if (type instanceof GenericArrayType) {
+                return convertArray(((GenericArrayType) 
type).getGenericComponentType(), key, value);
+            }
+            Class<?> resultType = (Class<?>) type;
+            if (resultType.isArray()) {
+                return convertArray(resultType.getComponentType(), key, value);
+            }
+            if (resultType.isInstance(value)) {
+                return value;
+            }
+
+            if (Boolean.class.equals(resultType) || 
Boolean.TYPE.equals(resultType)) {
+                if (value == null) {
+                    return useImplicitDefault && resultType.isPrimitive() ? 
DEFAULT_BOOLEAN : null;
+                }
+                return Boolean.valueOf(value.toString());
+            }
+            else if (Byte.class.equals(resultType) || 
Byte.TYPE.equals(resultType)) {
+                if (value == null) {
+                    return useImplicitDefault && resultType.isPrimitive() ? 
DEFAULT_BYTE : null;
+                }
+                if (value instanceof Number) {
+                    return ((Number) value).byteValue();
+                }
+                return Byte.valueOf(value.toString());
+            }
+            else if (Short.class.equals(resultType) || 
Short.TYPE.equals(resultType)) {
+                if (value == null) {
+                    return useImplicitDefault && resultType.isPrimitive() ? 
DEFAULT_SHORT : null;
+                }
+                if (value instanceof Number) {
+                    return ((Number) value).shortValue();
+                }
+                return Short.valueOf(value.toString());
+            }
+            else if (Integer.class.equals(resultType) || 
Integer.TYPE.equals(resultType)) {
+                if (value == null) {
+                    return useImplicitDefault && resultType.isPrimitive() ? 
DEFAULT_INT : null;
+                }
+                if (value instanceof Number) {
+                    return ((Number) value).intValue();
+                }
+                return Integer.valueOf(value.toString());
+            }
+            else if (Long.class.equals(resultType) || 
Long.TYPE.equals(resultType)) {
+                if (value == null) {
+                    return useImplicitDefault && resultType.isPrimitive() ? 
DEFAULT_LONG : null;
+                }
+                if (value instanceof Number) {
+                    return ((Number) value).longValue();
+                }
+                return Long.valueOf(value.toString());
+            }
+            else if (Float.class.equals(resultType) || 
Float.TYPE.equals(resultType)) {
+                if (value == null) {
+                    return useImplicitDefault && resultType.isPrimitive() ? 
DEFAULT_FLOAT : null;
+                }
+                if (value instanceof Number) {
+                    return ((Number) value).floatValue();
+                }
+                return Float.valueOf(value.toString());
+            }
+            else if (Double.class.equals(resultType) || 
Double.TYPE.equals(resultType)) {
+                if (value == null) {
+                    return useImplicitDefault && resultType.isPrimitive() ? 
DEFAULT_DOUBLE : null;
+                }
+                if (value instanceof Number) {
+                    return ((Number) value).doubleValue();
+                }
+                return Double.valueOf(value.toString());
+            }
+            else if (Number.class.equals(resultType)) {
+                if (value == null) {
+                    return null;
+                }
+                String numStr = value.toString();
+                if (numStr.indexOf('.') > 0) {
+                    return Double.valueOf(numStr);
+                }
+                return Long.valueOf(numStr);
+            }
+            else if (String.class.isAssignableFrom(resultType)) {
+                return value == null ? null : value.toString();
+            }
+            else if (Enum.class.isAssignableFrom(resultType)) {
+                if (value == null) {
+                    return null;
+                }
+                Class<Enum> enumType = (Class<Enum>) resultType;
+                return Enum.valueOf(enumType, value.toString().toUpperCase());
+            }
+            else if (resultType.isInterface()) {
+                Map<?, ?> map = toMap(key, value);
+                return create(resultType, map);
+            }
+
+            throw new RuntimeException("Unhandled type: " + type);
+        }
+
+        private Object convertArray(Type type, String key, Object value) 
throws Exception {
+            if (value instanceof String) {
+                String str = (String) value;
+                if (type == Byte.class || type == byte.class) {
+                    return str.getBytes("UTF-8");
+                }
+                if (type == Character.class || type == char.class) {
+                    return str.toCharArray();
+                }
+            }
+
+            Collection<?> input = toCollection(key, value);
+            if (input == null) {
+                return null;
+            }
+
+            Class<?> componentClass = getRawClass(type);
+            Object array = Array.newInstance(componentClass, input.size());
+
+            int i = 0;
+            for (Object next : input) {
+                Array.set(array, i++, convert(type, key, next, false /* 
useImplicitDefault */));
+            }
+            return array;
+        }
+
+        private Object getDefaultValue(Method method, String key) throws 
Exception {
+            return convert(method.getGenericReturnType(), key, null, true /* 
useImplicitDefault */);
+        }
+
+        private Class<?> getRawClass(Type type) {
+            if (type instanceof ParameterizedType) {
+                return (Class<?>) ((ParameterizedType) type).getRawType();
+            }
+            if (type instanceof Class) {
+                return (Class<?>) type;
+            }
+            throw new RuntimeException("Unhandled type: " + type);
+        }
+
+        private Collection<?> toCollection(String prefix, Object value) {
+            if (value instanceof Collection) {
+                return (Collection<?>) value;
+            }
+
+            if (value == null) {
+                List<Object> result = new ArrayList<>();
+
+                String needle = prefix.concat(".");
+                for (Map.Entry<?, ?> entry : m_config.entrySet()) {
+                    String key = entry.getKey().toString();
+                    if (!key.startsWith(needle)) {
+                        continue;
+                    }
+
+                    int idx = 0;
+                    try {
+                        idx = Integer.parseInt(key.substring(needle.length()));
+                    }
+                    catch (NumberFormatException e) {
+                        // Ignore
+                    }
+
+                    result.add(Math.min(result.size(), idx), entry.getValue());
+                }
+
+                return result;
+            }
+
+            if (value.getClass().isArray()) {
+                if (value.getClass().getComponentType().isPrimitive()) {
+                    int length = Array.getLength(value);
+                    List<Object> result = new ArrayList<Object>(length);
+                    for (int i = 0; i < length; i++) {
+                        result.add(Array.get(value, i));
+                    }
+                    return result;
+                }
+                return Arrays.asList((Object[]) value);
+            }
+
+            if (value instanceof String) {
+                String str = (String) value;
+                if (str.startsWith("[") && str.endsWith("]")) {
+                    str = str.substring(1, str.length() - 1);
+                }
+                return Arrays.asList(str.split("\\s*,\\s*"));
+            }
+
+            return Arrays.asList(value);
+        }
+
+        private Map<?, ?> toMap(String prefix, Object value) {
+            if (value instanceof Map) {
+                return (Map<?, ?>) value;
+            }
+
+            Map<String, Object> result = new HashMap<>();
+            if (value == null) {
+                String needle = prefix.concat(".");
+                for (Map.Entry<?, ?> entry : m_config.entrySet()) {
+                    String key = entry.getKey().toString();
+                    if (key.startsWith(needle)) {
+                        result.put(key.substring(needle.length()), 
entry.getValue());
+                    }
+                }
+            }
+            else if (value instanceof String) {
+                String str = (String) value;
+                if (str.startsWith("{") && str.endsWith("}")) {
+                    str = str.substring(1, str.length() - 1);
+                }
+                for (String entry : str.split("\\s*,\\s*")) {
+                    String[] pair = entry.split("\\s*\\.\\s*", 2);
+                    result.put(pair[0], pair[1]);
+                }
+            }
+
+            return result;
+        }
+
+        private String getPropertyName(String id) {
+            StringBuilder sb = new StringBuilder(id);
+            if (id.startsWith("get")) {
+                sb.delete(0, 3);
+            }
+            else if (id.startsWith("is")) {
+                sb.delete(0, 2);
+            }
+            char c = sb.charAt(0);
+            if (Character.isUpperCase(c)) {
+                sb.setCharAt(0, Character.toLowerCase(c));
+            }
+            return sb.toString();
+        }
+    }
+
+    private static final Boolean DEFAULT_BOOLEAN = Boolean.FALSE;
+    private static final Byte DEFAULT_BYTE = new Byte((byte) 0);
+    private static final Short DEFAULT_SHORT = new Short((short) 0);
+    private static final Integer DEFAULT_INT = new Integer(0);
+    private static final Long DEFAULT_LONG = new Long(0);
+    private static final Float DEFAULT_FLOAT = new Float(0.0f);
+    private static final Double DEFAULT_DOUBLE = new Double(0.0);
+
+    /**
+     * Creates a configuration for a given type backed by a given dictionary.
+     * 
+     * @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 static <T> T create(Class<T> type, Dictionary<?, ?> config) {
+        Map<Object, Object> map = new HashMap<Object, Object>();
+        for (Enumeration<?> e = config.keys(); e.hasMoreElements();) {
+            Object key = e.nextElement();
+            map.put(key, config.get(key));
+        }
+        return create(type, map);
+    }
+
+    /**
+     * Creates a configuration for a given type backed by a given map.
+     * 
+     * @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 static <T> T create(Class<T> type, Map<?, ?> config) {
+        ClassLoader cl = type.getClassLoader();
+        Object result = Proxy.newProxyInstance(cl, new Class<?>[] { type }, 
new ConfigHandler(cl, config));
+        return type.cast(result);
+    }
+}

Modified: 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java
URL: 
http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java?rev=1728564&r1=1728563&r2=1728564&view=diff
==============================================================================
--- 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java
 (original)
+++ 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java
 Thu Feb  4 22:55:19 2016
@@ -49,9 +49,11 @@ import org.osgi.service.cm.ManagedServic
  * @author <a href="mailto:[email protected]";>Felix Project Team</a>
  */
 public class ConfigurationDependencyImpl extends 
AbstractDependency<ConfigurationDependency> implements ConfigurationDependency, 
ManagedService {
+    // Our fields are not volatile because they are "safely published" using 
the DM thread model (based on a Concurrent queue).
     private Dictionary<String, Object> m_settings;
        private String m_pid;
        private ServiceRegistration m_registration;
+       private Class<?> m_configType;
     private MetaTypeProviderImpl m_metaType;
        private final AtomicBoolean m_updateInvokedCache = new AtomicBoolean();
        private final Logger m_logger;
@@ -77,6 +79,7 @@ public class ConfigurationDependencyImpl
            m_logger = prototype.m_logger;
         m_metaType = prototype.m_metaType != null ? new 
MetaTypeProviderImpl(prototype.m_metaType, this, null) : null;
         m_needsInstance = prototype.needsInstance();
+        m_configType = prototype.m_configType;
        }
        
     @Override
@@ -89,21 +92,64 @@ public class ConfigurationDependencyImpl
            return new ConfigurationDependencyImpl(this);
        }
 
+       /**
+        * Sets a callback method invoked on the instantiated component.
+        */
     public ConfigurationDependencyImpl setCallback(String callback) {
         super.setCallbacks(callback, null);
         return this;
     }
     
+    /**
+     * Sets a callback method on an external callback instance object.
+     * The component is not yet instantiated at the time the callback is 
invoked.
+     * We check if callback instance is null, in this case, the callback will 
be invoked on the instantiated component.
+     */
     public ConfigurationDependencyImpl setCallback(Object instance, String 
callback) {
-       return setCallback(instance, callback, false);
+        boolean needsInstantiatedComponent = (instance == null);
+       return setCallback(instance, callback, needsInstantiatedComponent);
     }
 
+    /**
+     * Sets a callback method on an external callback instance object.
+     * If needsInstance == true, the component is instantiated at the time the 
callback is invoked.
+     * We check if callback instance is null, in this case, the callback will 
be invoked on the instantiated component.
+     */
     public ConfigurationDependencyImpl setCallback(Object instance, String 
callback, boolean needsInstance) {
         super.setCallbacks(instance, callback, null);
         m_needsInstance = needsInstance;
         return this;
     }
+        
+    /**
+     * Sets a type-safe callback method invoked on the instantiated component.
+     */
+    public ConfigurationDependency setCallback(String callback, Class<?> 
configType) {
+        setCallback(callback);
+        m_configType = configType;
+        return this;
+    }
 
+    /**
+     * Sets a type-safe callback method on an external callback instance 
object.
+     * The component is not yet instantiated at the time the callback is 
invoked.
+     */
+    public ConfigurationDependency setCallback(Object instance, String 
callback, Class<?> configType) {
+        setCallback(instance, callback);
+        m_configType = configType;
+        return this;
+    }
+    
+    /**
+     * Sets a type-safe callback method on an external callback instance 
object.
+     * If needsInstance == true, the component is instantiated at the time the 
callback is invoked.
+     */
+    public ConfigurationDependencyImpl setCallback(Object instance, String 
callback, Class<?> configType, boolean needsInstance) {
+        setCallback(instance, callback, needsInstance);
+        m_configType = configType;
+        return this;
+    }
+    
     @Override
     public boolean needsInstance() {
         return m_needsInstance;
@@ -295,48 +341,54 @@ public class ConfigurationDependencyImpl
         }
     }
     
-    private void invokeUpdated(Dictionary<?,?> settings) throws 
ConfigurationException {
-       if (m_updateInvokedCache.compareAndSet(false, true)) {
-                       Object[] instances = super.getInstances(); // either 
the callback instance or the component instances
-                       if (instances != null) {
-                               for (int i = 0; i < instances.length; i++) {
-                                       try {
-                                               
InvocationUtil.invokeCallbackMethod(instances[i],
-                                                               m_add, new 
Class[][] {
-                                                                               
{ Dictionary.class },
-                                                                               
{ Component.class, Dictionary.class },
-                                                                               
{} },
-                                                               new Object[][] 
{ 
-                                                           { settings }, 
-                                                           { m_component, 
settings },
-                                                           {} });
-                                       }
-
-                                       catch (InvocationTargetException e) {
-                                               // The component has thrown an 
exception during it's
-                                               // callback invocation.
-                                               if (e.getTargetException() 
instanceof ConfigurationException) {
-                                                       // the callback threw 
an OSGi
-                                                       // 
ConfigurationException: just re-throw it.
-                                                       throw 
(ConfigurationException) e
-                                                                       
.getTargetException();
-                                               } else {
-                                                       // wrap the callback 
exception into a
-                                                       // 
ConfigurationException.
-                                                       throw new 
ConfigurationException(null,
-                                                                       
"Configuration update failed",
-                                                                       
e.getTargetException());
-                                               }
-                                       } catch (NoSuchMethodException e) {
-                                               // if the method does not 
exist, ignore it
-                                       } catch (Throwable t) {
-                                               // wrap any other exception as 
a ConfigurationException.
-                                               throw new 
ConfigurationException(null,
-                                                               "Configuration 
update failed", t);
-                                       }
-                               }
-                       }
-       }
+    private void invokeUpdated(Dictionary<?, ?> settings) throws 
ConfigurationException {
+        if (m_updateInvokedCache.compareAndSet(false, true)) {
+            Object[] instances = super.getInstances(); // either the callback 
instance or the component instances
+            if (instances == null) {
+                return;
+            }
+
+            Class<?>[][] sigs = new Class[][] { { Dictionary.class }, { 
Component.class, Dictionary.class }, {} };
+            Object[][] args = new Object[][] { { settings }, { m_component, 
settings }, {} };
+
+            if (m_configType != null) {
+                Object configurable;
+                try {
+                    configurable = Configurable.create(m_configType, settings);
+
+                    sigs = new Class[][] { { Dictionary.class }, { 
Component.class, Dictionary.class }, { Component.class, m_configType }, { 
m_configType }, {} };
+                    args = new Object[][] { { settings }, { m_component, 
settings }, { m_component, configurable }, { configurable }, {} };
+                }
+                catch (Exception e) {
+                    // This is not something we can recover from, use the 
defaults above...
+                    m_component.getLogger().warn("Failed to create 
configurable for method %s and configuration type %s!", e, m_add, m_configType);
+                }
+            }
+
+            for (int i = 0; i < instances.length; i++) {
+                try {
+                    InvocationUtil.invokeCallbackMethod(instances[i], m_add, 
sigs, args);
+                }
+                catch (InvocationTargetException e) {
+                    // The component has thrown an exception during it's 
callback invocation.
+                    if (e.getTargetException() instanceof 
ConfigurationException) {
+                        // the callback threw an OSGi ConfigurationException: 
just re-throw it.
+                        throw (ConfigurationException) e.getTargetException();
+                    }
+                    else {
+                        // wrap the callback exception into a 
ConfigurationException.
+                        throw new ConfigurationException(null, "Configuration 
update failed", e.getTargetException());
+                    }
+                }
+                catch (NoSuchMethodException e) {
+                    // if the method does not exist, ignore it
+                }
+                catch (Throwable t) {
+                    // wrap any other exception as a ConfigurationException.
+                    throw new ConfigurationException(null, "Configuration 
update failed", t);
+                }
+            }
+        }
     }
     
     private synchronized void createMetaTypeImpl() {

Added: 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/org/apache/felix/dm/impl/ConfigurableTest.java
URL: 
http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/org/apache/felix/dm/impl/ConfigurableTest.java?rev=1728564&view=auto
==============================================================================
--- 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/org/apache/felix/dm/impl/ConfigurableTest.java
 (added)
+++ 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/org/apache/felix/dm/impl/ConfigurableTest.java
 Thu Feb  4 22:55:19 2016
@@ -0,0 +1,259 @@
+/*
+ * 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.dm.impl;
+
+import static org.apache.felix.dm.impl.Configurable.*;
+import static org.junit.Assert.*;
+
+import org.apache.felix.dm.impl.Configurable;
+import org.junit.Test;
+
+import java.util.*;
+
+/**
+ * Test cases for {@link Configurable}.
+ */
+public class ConfigurableTest {
+    private static final double ERROR_MARGIN = 0.0001;
+
+    static enum EnumValue {
+        ONE, TWO, THREE;
+    }
+
+    static interface IfaceA {
+        int getInt();
+
+        double getDouble();
+        
+        Byte getByte();
+
+        String getString();
+
+        int[] getArray();
+        
+        boolean isBoolean();
+
+        List<String> getList();
+
+        SortedMap<String, Object> getMap();
+
+        EnumValue getEnum();
+        
+        Map<String, List<Number>> getMapStringList();
+
+        List<String>[] getArrayOfList();
+    }
+
+    static interface IfaceB {
+        Stack<Integer> getA();
+
+        Set<Integer> getB();
+
+        Queue<String> getC();
+    }
+
+    static interface IfaceC {
+        IfaceA getIfaceA();
+        
+        Class<?> getType();
+    }
+
+    @Test
+    public void testHandleImplicitDefaultValues() {
+        IfaceA ifaceA = create(IfaceA.class, createMap());
+        assertNotNull(ifaceA);
+
+        assertEquals(0, ifaceA.getInt());
+        assertEquals(0.0, ifaceA.getDouble(), ERROR_MARGIN);
+        assertEquals(null, ifaceA.getString());
+        assertArrayEquals(new int[0], ifaceA.getArray());
+        assertEquals(Collections.emptyList(), ifaceA.getList());
+        assertEquals(Collections.emptyMap(), ifaceA.getMap());
+        assertEquals(null, ifaceA.getEnum());
+
+        IfaceB ifaceB = create(IfaceB.class, createMap());
+        assertNotNull(ifaceB);
+        
+        assertEquals(new Stack<>(), ifaceB.getA());
+        assertEquals(Collections.emptySet(), ifaceB.getB());
+        assertEquals(new LinkedList<>(), ifaceB.getC());
+
+        IfaceC ifaceC = create(IfaceC.class, createMap());
+        assertNotNull(ifaceC);
+
+        assertNotNull(ifaceC.getIfaceA());
+        assertNull(ifaceC.getType());
+    }
+
+    @Test
+    public void testHandleInt() {
+        IfaceA cfg = create(IfaceA.class, createMap("int", 41));
+        assertNotNull(cfg);
+
+        assertEquals(41, cfg.getInt());
+    }
+
+    @Test
+    public void testHandleDouble() {
+        IfaceA cfg = create(IfaceA.class, createMap("double", 3.141));
+        assertNotNull(cfg);
+
+        assertEquals(3.141, cfg.getDouble(), ERROR_MARGIN);
+    }
+
+    @Test
+    public void testHandleString() {
+        IfaceA cfg = create(IfaceA.class, createMap("string", "hello"));
+        assertNotNull(cfg);
+
+        assertEquals("hello", cfg.getString());
+    }
+
+    @Test
+    public void testHandleEnum() {
+        IfaceA cfg = create(IfaceA.class, createMap("enum", "two"));
+        assertNotNull(cfg);
+
+        assertEquals(EnumValue.TWO, cfg.getEnum());
+    }
+
+    @Test
+    public void testHandleMapFromString() {
+        IfaceA cfg = create(IfaceA.class, createMap("map.a", "1", "map.b", 
"2", "map.c", "hello world", "map.d", "[4, 5, 6]"));
+        assertNotNull(cfg);
+
+        Map<String, Object> map = cfg.getMap();
+        assertEquals("1", map.get("a"));
+        assertEquals("2", map.get("b"));
+        assertEquals("hello world", map.get("c"));
+        assertEquals("[4, 5, 6]", map.get("d"));
+        
+        cfg = create(IfaceA.class, createMap("map", "{a.1, b.2, c.3}"));
+        assertNotNull(cfg);
+
+        map = cfg.getMap();
+        assertEquals("1", map.get("a"));
+        assertEquals("2", map.get("b"));
+        assertEquals("3", map.get("c"));
+    }
+
+    @Test
+    public void testHandleObjectFromString() {
+        IfaceC cfg = create(IfaceC.class, createMap(
+            "ifaceA.array", "[4, 5, 6]", 
+            "ifaceA.arrayOfList.0", "[foo, bar]",
+            "ifaceA.arrayOfList.1", "[qux]",
+            "ifaceA.byte", "127",
+            "ifaceA.double", "3.141",
+            "ifaceA.enum", "THREE", 
+            "ifaceA.int", "123",
+            "ifaceA.list", "[qux, quu]",
+            "ifaceA.map.c", "d",
+            "ifaceA.map.a", "b",
+            "ifaceA.mapStringList.x", "[1, 2, 3]",
+            "ifaceA.mapStringList.y", "[4, 5, 6]",
+            "ifaceA.mapStringList.z", "[7, 8, 9]",
+            "ifaceA.string", "hello world", 
+            "ifaceA.boolean", "true",
+            "type", "java.lang.String"));
+        assertNotNull(cfg);
+        
+        SortedMap<String, Object> sortedMap = new TreeMap<>();
+        sortedMap.put("a", "b");
+        sortedMap.put("c", "d");
+        
+        Map<String, List<Number>> mapStringList = new HashMap<>();
+        mapStringList.put("x", Arrays.<Number> asList(1L, 2L, 3L));
+        mapStringList.put("y", Arrays.<Number> asList(4L, 5L, 6L));
+        mapStringList.put("z", Arrays.<Number> asList(7L, 8L, 9L));
+
+        assertEquals(String.class, cfg.getType());
+
+        IfaceA ifaceA = cfg.getIfaceA();
+        assertNotNull(ifaceA);
+
+        assertArrayEquals(new int[] { 4, 5, 6 }, ifaceA.getArray());
+        assertArrayEquals(new List[] { Arrays.asList("foo", "bar"), 
Arrays.asList("qux") }, ifaceA.getArrayOfList());
+        assertEquals(Byte.valueOf("127"), ifaceA.getByte());
+        assertEquals(3.141, ifaceA.getDouble(), ERROR_MARGIN);
+        assertEquals(EnumValue.THREE, ifaceA.getEnum());
+        assertEquals(123, ifaceA.getInt());
+        assertEquals(Arrays.asList("qux", "quu"), ifaceA.getList());
+        assertEquals(sortedMap, ifaceA.getMap());
+        assertEquals(mapStringList, ifaceA.getMapStringList());
+        assertEquals("hello world", ifaceA.getString());
+        assertEquals(true, ifaceA.isBoolean());
+    }
+
+    @Test
+    public void testHandleArrayDirect() {
+        IfaceA cfg = create(IfaceA.class, createMap("array", new int[] { 2, 3, 
4, 5 }));
+        assertNotNull(cfg);
+
+        int[] vals = cfg.getArray();
+        assertEquals(2, vals[0]);
+        assertEquals(3, vals[1]);
+        assertEquals(4, vals[2]);
+        assertEquals(5, vals[3]);
+    }
+
+    @Test
+    public void testHandleArrayFromString() {
+        IfaceA cfg = create(IfaceA.class, createMap("array", "[2,3,4,5]"));
+        assertNotNull(cfg);
+
+        int[] vals = cfg.getArray();
+        assertEquals(2, vals[0]);
+        assertEquals(3, vals[1]);
+        assertEquals(4, vals[2]);
+        assertEquals(5, vals[3]);
+    }
+
+    @Test
+    public void testHandleListDirect() {
+        IfaceA cfg = create(IfaceA.class, createMap("list", Arrays.asList("2", 
"3", "4", "5")));
+        assertNotNull(cfg);
+
+        List<String> list = cfg.getList();
+        assertEquals("2", list.get(0));
+        assertEquals("3", list.get(1));
+        assertEquals("4", list.get(2));
+        assertEquals("5", list.get(3));
+    }
+
+    @Test
+    public void testHandleListFromString() {
+        IfaceA cfg = create(IfaceA.class, createMap("list", "[2,3,4,5]"));
+        assertNotNull(cfg);
+
+        List<String> list = cfg.getList();
+        assertEquals("2", list.get(0));
+        assertEquals("3", list.get(1));
+        assertEquals("4", list.get(2));
+        assertEquals("5", list.get(3));
+    }
+
+    private static Map<?, ?> createMap(Object... vals) {
+        Map<String, Object> result = new HashMap<>();
+        for (int i = 0; i < vals.length; i += 2) {
+            result.put(vals[i].toString(), vals[i + 1]);
+        }
+        return result;
+    }
+}

Added: 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/org/apache/felix/dm/impl/ConfigurationDependencyImplTest.java
URL: 
http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/org/apache/felix/dm/impl/ConfigurationDependencyImplTest.java?rev=1728564&view=auto
==============================================================================
--- 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/org/apache/felix/dm/impl/ConfigurationDependencyImplTest.java
 (added)
+++ 
felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/org/apache/felix/dm/impl/ConfigurationDependencyImplTest.java
 Thu Feb  4 22:55:19 2016
@@ -0,0 +1,198 @@
+/*
+ * 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.dm.impl;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.felix.dm.Logger;
+import org.apache.felix.dm.context.ComponentContext;
+import org.junit.Test;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+
+/**
+ * @author <a href="mailto:[email protected]";>Felix Project Team</a>
+ */
+public class ConfigurationDependencyImplTest {
+    public static interface MyMap {
+        String getFoo();
+
+        int getQux();
+
+        String[] getQuu();
+    }
+
+    public static interface MyConfiguration {
+        boolean isTrue();
+
+        int getValue();
+
+        long getLongValue();
+
+        double getPi();
+
+        String[] getArgArray();
+
+        List<String> getArgList();
+
+        MyMap getMap();
+
+        String getMessage();
+    }
+
+    static class PlainService {
+        final CountDownLatch m_latch = new CountDownLatch(1);
+
+        public void updated(Dictionary config) throws ConfigurationException {
+            if (config != null) {
+                m_latch.countDown();
+            }
+            assertConfiguration(config);
+        }
+
+        private void assertConfiguration(Dictionary cfg) {
+            assertEquals("isTrue", "true", cfg.get("true"));
+            assertEquals("getValue", "42", cfg.get("value"));
+            assertEquals("getLongValue", "1234567890", cfg.get("longValue"));
+            assertEquals("getPi", "3.141", cfg.get("pi"));
+            assertEquals("getArgArray", "[a, b, c]", cfg.get("argArray"));
+            assertEquals("getArgList", "[d, e, f]", cfg.get("argList"));
+            assertEquals("getMap.foo", "bar", cfg.get("map.foo"));
+            assertEquals("getMap.qux", "123", cfg.get("map.qux"));
+            assertEquals("getMap.quu", "[x, y, z]", cfg.get("map.quu"));
+            assertEquals("getMessage", "hello world!", cfg.get("message"));
+        }
+    }
+
+    static class AManagedService extends PlainService implements 
ManagedService {
+        @Override
+        public void updated(Dictionary config) throws ConfigurationException {
+            super.updated(config);
+        }
+    }
+
+    static class FancyService {
+        final CountDownLatch m_latch = new CountDownLatch(1);
+
+        public void updated(MyConfiguration config) throws 
ConfigurationException {
+            if (config != null) {
+                m_latch.countDown();
+            }
+            assertConfiguration(config);
+        }
+
+        private void assertConfiguration(MyConfiguration cfg) {
+            assertEquals("isTrue", true, cfg.isTrue());
+            assertEquals("getValue", 42, cfg.getValue());
+            assertEquals("getLongValue", 1234567890L, cfg.getLongValue());
+            assertEquals("getPi", 3.141, cfg.getPi(), 0.001);
+            assertArrayEquals("getArgArray", new String[] { "a", "b", "c" }, 
cfg.getArgArray());
+            assertEquals("getArgList", Arrays.asList("d", "e", "f"), 
cfg.getArgList());
+            assertEquals("getMessage", "hello world!", cfg.getMessage());
+
+            MyMap map = cfg.getMap();
+            assertEquals("getMap.getFoo", "bar", map.getFoo());
+            assertEquals("getMap.getQux", 123, map.getQux());
+            assertArrayEquals("getMap.getQuu", new String[] { "x", "y", "z" }, 
map.getQuu());
+        }
+    }
+
+    @Test
+    public void testInvokeManagedServiceUpdatedMethodOk() throws Exception {
+        AManagedService service = new AManagedService();
+
+        ConfigurationDependencyImpl cdi = 
createConfigurationDependency(service);
+        cdi.updated(createDictionary());
+
+        assertTrue(service.m_latch.await(1, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testInvokePlainUpdatedMethodOk() throws Exception {
+        PlainService service = new PlainService();
+
+        ConfigurationDependencyImpl cdi = 
createConfigurationDependency(service);
+        cdi.updated(createDictionary());
+
+        assertTrue(service.m_latch.await(1, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testInvokeFancyUpdatedMethodOk() throws Exception {
+        FancyService service = new FancyService();
+
+        ConfigurationDependencyImpl cdi = 
createConfigurationDependency(service);
+        cdi.setCallback(service, "updated", MyConfiguration.class);
+        cdi.updated(createDictionary());
+
+        assertTrue(service.m_latch.await(1, TimeUnit.SECONDS));
+    }
+
+    @Test
+    public void testDoNotInvokeFancyUpdatedMethodWithWrongSignatureOk() throws 
Exception {
+        FancyService service = new FancyService();
+
+        ConfigurationDependencyImpl cdi = 
createConfigurationDependency(service);
+        cdi.setCallback(service, "updated", Dictionary.class);
+        cdi.updated(createDictionary());
+
+        assertFalse(service.m_latch.await(1, TimeUnit.SECONDS));
+    }
+
+    private Dictionary createDictionary() {
+        Dictionary<String, Object> result = new Hashtable<>();
+        result.put("true", "true");
+        result.put("value", "42");
+        result.put("longValue", "1234567890");
+        result.put("pi", "3.141");
+        result.put("argArray", "[a, b, c]");
+        result.put("argList", "[d, e, f]");
+        result.put("map.foo", "bar");
+        result.put("map.qux", "123");
+        result.put("map.quu", "[x, y, z]");
+        result.put("message", "hello world!");
+        return result;
+    }
+
+    private ConfigurationDependencyImpl createConfigurationDependency(Object 
service) {
+        Logger mockLogger = mock(Logger.class);
+
+        ComponentContext component = mock(ComponentContext.class);
+        
when(component.getExecutor()).thenReturn(Executors.newSingleThreadExecutor());
+        when(component.getLogger()).thenReturn(mockLogger);
+
+        ConfigurationDependencyImpl result = new ConfigurationDependencyImpl();
+        result.setCallback(service, "updated").setPid("does.not.matter");
+        result.setComponentContext(component);
+        return result;
+    }
+}


Reply via email to