You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by pd...@apache.org on 2016/04/24 00:56:55 UTC

svn commit: r1740677 - in /felix/trunk/dependencymanager: org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/ org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/

Author: pderop
Date: Sat Apr 23 22:56:55 2016
New Revision: 1740677

URL: http://svn.apache.org/viewvc?rev=1740677&view=rev
Log:
FELIX-5237: added support for configuration types which are either annotations or java8 interface with default methods.
(slightly modified the patch proposed by Guillaume in order to check if the method is part of an annotation type or if
the method is a java8 default method. If the method is part of a simple interface, then the behavior is the same as before).

Added:
    felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX5238_TypeSafeConfigWithDefaultMethodTest.java
Modified:
    felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/Configurable.java

Added: felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX5238_TypeSafeConfigWithDefaultMethodTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX5238_TypeSafeConfigWithDefaultMethodTest.java?rev=1740677&view=auto
==============================================================================
--- felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX5238_TypeSafeConfigWithDefaultMethodTest.java (added)
+++ felix/trunk/dependencymanager/org.apache.felix.dependencymanager.itest/src/org/apache/felix/dm/itest/api/FELIX5238_TypeSafeConfigWithDefaultMethodTest.java Sat Apr 23 22:56:55 2016
@@ -0,0 +1,155 @@
+/*
+ * 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.itest.api;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.itest.util.Ensure;
+import org.apache.felix.dm.itest.util.TestBase;
+import org.junit.Assert;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationException;
+
+
+/**
+ * Tests for type-safe configuration using either an annotation or an interface having
+ * some default methods.
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class FELIX5238_TypeSafeConfigWithDefaultMethodTest extends TestBase {
+    final static String PID = "ConfigurationDependencyTest.pid";
+    
+    /**
+     * Tests that we can provision a type-safe config annotation to a component.
+     */
+    public void testComponentWithRequiredConfigurationWithTypeSafeConfigAnnotation() {
+        DependencyManager m = getDM();
+        // helper class that ensures certain steps get executed in sequence
+        Ensure e = new Ensure();
+        // create a service provider and consumer
+        ConfigurationConsumer1 consumer = new ConfigurationConsumer1(e);
+        Component s1 = m.createComponent()
+        		.setImplementation(consumer).add(m.createConfigurationDependency().setCallback("updated", MyConfigAnnot.class).setPid(PID).setPropagate(true));
+        Component s2 = m.createComponent()
+        		.setImplementation(new ConfigurationCreator(e, PID, 1)).add(m.createServiceDependency().setService(ConfigurationAdmin.class).setRequired(true));
+        m.add(s1);
+        m.add(s2);
+        e.waitForStep(1, 5000); // s2 called in init
+        e.waitForStep(3, 5000); // s1 called in updated(), then in init()
+        m.remove(s2);           // remove conf
+        e.waitForStep(5, 5000); // s1 called in updated(null), s1 called in destroy()
+        m.remove(s1);
+    }
+        
+    /**
+     * Tests that we can provision a type-safe config annotation to a component.
+     */
+    public void testComponentWithRequiredConfigurationWithTypeSafeConfigInterfaceWithDefaultMethod() {
+        DependencyManager m = getDM();
+        // helper class that ensures certain steps get executed in sequence
+        Ensure e = new Ensure();
+        // create a service provider and consumer
+        ConfigurationConsumer2 consumer = new ConfigurationConsumer2(e);
+        Component s1 = m.createComponent()
+        		.setImplementation(consumer).add(m.createConfigurationDependency().setCallback("updated", MyConfigInterface.class).setPid(PID).setPropagate(true));
+        Component s2 = m.createComponent()
+        		.setImplementation(new ConfigurationCreator(e, PID, 1)).add(m.createServiceDependency().setService(ConfigurationAdmin.class).setRequired(true));
+        m.add(s1);
+        m.add(s2);
+        e.waitForStep(1, 5000); // s2 called in init
+        e.waitForStep(3, 5000); // s1 called in updated(), then in init()
+        m.remove(s2);           // remove conf
+        e.waitForStep(5, 5000); // s1 called in updated(null), s1 called in destroy()
+        m.remove(s1);
+    }
+        
+    public static @interface MyConfigAnnot {
+        String getTestkey();
+        String getTestkey2() default "123";
+    }
+    
+    public static interface MyConfigInterface {
+    	String getTestkey();
+    	default String getTestkey2() { return "123"; }
+    }
+
+    static class ConfigurationConsumerBase {
+        protected final Ensure m_ensure;
+
+        public ConfigurationConsumerBase(Ensure e) {
+            m_ensure = e;
+        }
+
+        // called after configuration has been injected.
+        public void init() {
+            m_ensure.step(3); 
+        }
+        
+        public void destroy() {
+            m_ensure.step(); 
+        }        
+    }
+
+    static class ConfigurationConsumer1 extends ConfigurationConsumerBase {
+        public ConfigurationConsumer1(Ensure e) {
+            super(e);
+        }
+
+        // configuration updates is always the first invoked callback (before init).
+        public void updated(Component component, MyConfigAnnot cfg) throws ConfigurationException {
+        	if (cfg != null) {
+        		Assert.assertNotNull(component);
+        		Assert.assertNotNull(cfg);
+        		if (!"testvalue".equals(cfg.getTestkey())) {
+        			Assert.fail("Could not find the configured property.");
+        		}
+        		if (!"123".equals(cfg.getTestkey2())) {
+        			Assert.fail("Could not find the configured property.");
+        		}
+        		m_ensure.step(2);
+        	} else {
+        		m_ensure.step();
+        	}
+        }
+    }
+    
+    static class ConfigurationConsumer2 extends ConfigurationConsumerBase {
+        public ConfigurationConsumer2(Ensure e) {
+            super(e);
+        }
+
+        // configuration updates is always the first invoked callback (before init).
+        public void updated(Component component, MyConfigInterface cfg) throws ConfigurationException {
+        	if (cfg != null) {
+        		Assert.assertNotNull(component);
+        		Assert.assertNotNull(cfg);
+        		if (!"testvalue".equals(cfg.getTestkey())) {
+        			Assert.fail("Could not find the configured property.");
+        		}
+        		if (!"123".equals(cfg.getTestkey2())) {
+        			Assert.fail("Could not find the configured property.");
+        		}
+        		m_ensure.step(2);
+        	} else {
+        		m_ensure.step();
+        	}
+        }
+    }
+}

Modified: 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=1740677&r1=1740676&r2=1740677&view=diff
==============================================================================
--- felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/Configurable.java (original)
+++ felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/Configurable.java Sat Apr 23 22:56:55 2016
@@ -18,7 +18,9 @@
  */
 package org.apache.felix.dm.impl;
 
+import java.lang.invoke.MethodHandles;
 import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
 import java.lang.reflect.GenericArrayType;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
@@ -88,7 +90,7 @@ import java.util.TreeSet;
  */
 public final class Configurable {
 
-    static class ConfigHandler implements InvocationHandler {
+	static class ConfigHandler implements InvocationHandler {
         private final ClassLoader m_cl;
         private final Map<?, ?> m_config;
 
@@ -103,7 +105,7 @@ public final class Configurable {
 
             Object result = convert(method.getGenericReturnType(), name, m_config.get(name), false /* useImplicitDefault */);
             if (result == null) {
-                Object defaultValue = getDefaultValue(method, name);
+                Object defaultValue = getDefaultValue(proxy, args, method, name);
                 if (defaultValue != null) {
                     return defaultValue;
                 }
@@ -302,8 +304,26 @@ public final class Configurable {
             return array;
         }
 
-        private Object getDefaultValue(Method method, String key) throws Exception {
-            return convert(method.getGenericReturnType(), key, null, true /* useImplicitDefault */);
+        private Object getDefaultValue(Object proxy, Object[] args, Method method, String key) throws Throwable {
+        	Object def = null;
+        	// Handle cases where the method is part of an annotation or is a java8 default method.
+        	Class<?> methodClass = method.getDeclaringClass();
+        	if (methodClass.isAnnotation()) {
+        		// the config type is an annotation: simply invoke the default value
+        		def = method.getDefaultValue();
+        	} else if (method.isDefault()) {
+        		// The config type is a java8 interface with a default method, invoke it.
+        		// But it's challenging to invoke a default method from a dynamic proxy ... we have to use the MethodHandles.
+        		// see https://zeroturnaround.com/rebellabs/recognize-and-conquer-java-proxies-default-methods-and-method-handles
+        		
+                Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
+                constructor.setAccessible(true);
+                def = constructor.newInstance(methodClass, MethodHandles.Lookup.PRIVATE)
+                		.unreflectSpecial(method, methodClass)
+                		.bindTo(proxy)
+                		.invokeWithArguments(args);
+        	}
+            return convert(method.getGenericReturnType(), key, def, true /* useImplicitDefault */);
         }
 
         private Class<?> getRawClass(Type type) {