Fixed FELIX-3204

* Added the support of temporal.from, temporal.filters (with fallback to requires.from and requires.filters) to the temporal dependency handler
* Added an id attribute
* Updated the XSD file


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1204903 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/TemporalHandler.java b/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/TemporalHandler.java
index 6e05562..4dd9a33 100644
--- a/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/TemporalHandler.java
+++ b/ipojo/handler/temporal/src/main/java/org/apache/felix/ipojo/handler/temporal/TemporalHandler.java
@@ -21,6 +21,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Dictionary;
+import java.util.Hashtable;
 import java.util.List;
 
 import org.apache.felix.ipojo.ConfigurationException;
@@ -114,6 +115,20 @@
         if (deps == null || deps.length == 0) {
         	deps = meta.getElements("temporal", NAMESPACE);
         }
+        
+        // Get instance filters.
+        Dictionary filtersConfiguration = getRequiresFilters(dictionary.get("temporal.filters"));
+        if(filtersConfiguration == null || filtersConfiguration.isEmpty()) {
+        	// Fall back on the Requires handler configuration, if any
+        	filtersConfiguration = getRequiresFilters(dictionary.get("requires.filters"));
+        }
+        // Get from filters if any.
+        Dictionary fromConfiguration = getRequiresFilters(dictionary.get("temporal.from"));
+        if(fromConfiguration == null || fromConfiguration.isEmpty()) {
+        	// Fall back on the Requires handler configuration, if any
+        	fromConfiguration = getRequiresFilters(dictionary.get("requires.from"));
+        }
+
 
         for (int i = 0; i < deps.length; i++) {
             if (!deps[i].containsAttribute("field") || m_dependencies.contains(deps[i].getAttribute("field"))) {
@@ -122,23 +137,17 @@
             }
             String field = deps[i].getAttribute("field");
 
+            String id = field;
+            if (deps[i].containsAttribute("id")) {
+                id = deps[i].getAttribute("id");
+            }
+
             FieldMetadata fieldmeta = manipulation.getField(field);
             if (fieldmeta == null) {
                 error("The field " + field + " does not exist in the class " + getInstanceManager().getClassName());
                 return;
             }
 
-            String fil = deps[i].getAttribute("filter");
-            Filter filter = null;
-            if (fil != null) {
-                try {
-                    filter = getInstanceManager().getContext().createFilter(fil);
-                } catch (InvalidSyntaxException e) {
-                    error("Cannot create the field from " + fil + ": " + e.getMessage());
-                    return;
-                }
-            }
-
             boolean agg = false;
             boolean collection = false;
             String spec = fieldmeta.getFieldType();
@@ -155,8 +164,42 @@
                 }
             }
 
+            // Determine the filter
+            String fil = deps[i].getAttribute("filter");
+            // Override the filter if filter configuration if available in the instance configuration
+            if (filtersConfiguration != null && id != null && filtersConfiguration.get(id) != null) {
+                fil = (String) filtersConfiguration.get(id);
+            }
+
+            // Check the from attribute
+            String from = deps[i].getAttribute("from");
+            if (fromConfiguration != null && id != null && fromConfiguration.get(id) != null) {
+                from = (String) fromConfiguration.get(id);
+            }
+
+            if (from != null) {
+                String fromFilter = "(|(instance.name=" + from + ")(service.pid=" + from + "))";
+                if (agg) {
+                    warn("The 'from' attribute is incompatible with aggregate requirements: only one provider will " +
+                            "match : " + fromFilter);
+                }
+                if (fil != null) {
+                    fil = "(&" + fromFilter + fil + ")"; // Append the two filters
+                } else {
+                    fil = fromFilter;
+                }
+            }
+
+            Filter filter = null;
+            if (fil != null) {
+                try {
+                    filter = getInstanceManager().getContext().createFilter(fil);
+                } catch (InvalidSyntaxException e) {
+                    throw new ConfigurationException("A requirement filter is invalid : " + filter + " - " + e.getMessage());
+                }
+            }
+
             String prox = deps[i].getAttribute("proxy");
-            //boolean proxy = prox != null && prox.equals("true");
             // Use proxy by default except for array:
             boolean proxy = prox == null  || prox.equals("true");
 
@@ -216,6 +259,41 @@
             getInstanceManager().register(fieldmeta, dep);
         }
     }
+    
+    /**
+	 * Gets the requires filter configuration from the given object.
+	 * The given object must come from the instance configuration.
+	 * This method was made to fix FELIX-2688. It supports filter configuration using
+	 * an array:
+	 * <code>{"myFirstDep", "(property1=value1)", "mySecondDep", "(property2=value2)"});</code>
+	 * 
+	 * Copied from DependencyHandler#getRequiresFilters(Object)
+	 * 
+	 * @param requiresFiltersValue the value contained in the instance
+	 * configuration.
+	 * @return the dictionary. If the object in already a dictionary, just returns it,
+	 * if it's an array, builds the dictionary.
+	 * @throws ConfigurationException the dictionary cannot be built
+	 */
+	private Dictionary getRequiresFilters(Object requiresFiltersValue)
+			throws ConfigurationException {
+		if (requiresFiltersValue != null
+				&& requiresFiltersValue.getClass().isArray()) {
+			String[] filtersArray = (String[]) requiresFiltersValue;
+			if (filtersArray.length % 2 != 0) {
+				throw new ConfigurationException(
+						"A requirement filter is invalid : "
+								+ requiresFiltersValue);
+			}
+			Dictionary requiresFilters = new Hashtable();
+			for (int i = 0; i < filtersArray.length; i += 2) {
+				requiresFilters.put(filtersArray[i], filtersArray[i + 1]);
+			}
+			return requiresFilters;
+		}
+
+		return (Dictionary) requiresFiltersValue;
+	}
 
     /**
      * Nothing to do.
diff --git a/ipojo/handler/temporal/src/main/resources/temporal.xsd b/ipojo/handler/temporal/src/main/resources/temporal.xsd
index 33bd3bd..48dbc7f 100644
--- a/ipojo/handler/temporal/src/main/resources/temporal.xsd
+++ b/ipojo/handler/temporal/src/main/resources/temporal.xsd
@@ -33,6 +33,10 @@
         	<xs:annotation>
         		<xs:documentation>The implementation field supporting the dependency.</xs:documentation>
         	</xs:annotation></xs:attribute>
+        <xs:attribute name="id" type="xs:string" use="optional">
+        	<xs:annotation>
+        		<xs:documentation>The dependency id</xs:documentation>
+        	</xs:annotation></xs:attribute>
 		<xs:attribute name="filter" type="xs:string" use="optional">
 			<xs:annotation>
 				<xs:documentation>Filter use to discover matching filter.</xs:documentation></xs:annotation></xs:attribute>
diff --git a/ipojo/manipulator/src/main/resources/xsd/temporal.xsd b/ipojo/manipulator/src/main/resources/xsd/temporal.xsd
index 3f2d47c..4ba6292 100644
--- a/ipojo/manipulator/src/main/resources/xsd/temporal.xsd
+++ b/ipojo/manipulator/src/main/resources/xsd/temporal.xsd
@@ -31,6 +31,10 @@
         	<xs:annotation>
         		<xs:documentation>The implementation field supporting the dependency.</xs:documentation>
         	</xs:annotation></xs:attribute>
+        <xs:attribute name="id" type="xs:string" use="optional">
+        	<xs:annotation>
+        		<xs:documentation>The dependency id</xs:documentation>
+        	</xs:annotation></xs:attribute>
 		<xs:attribute name="filter" type="xs:string" use="optional">
 			<xs:annotation>
 				<xs:documentation>Filter use to discover matching filter.</xs:documentation></xs:annotation></xs:attribute>
diff --git a/ipojo/tests/handler/temporal/src/main/java/org/apache/felix/ipojo/test/scenarios/temporal/FilterTest.java b/ipojo/tests/handler/temporal/src/main/java/org/apache/felix/ipojo/test/scenarios/temporal/FilterTest.java
new file mode 100644
index 0000000..c8a0cd7
--- /dev/null
+++ b/ipojo/tests/handler/temporal/src/main/java/org/apache/felix/ipojo/test/scenarios/temporal/FilterTest.java
@@ -0,0 +1,256 @@
+/*
+ * 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.ipojo.test.scenarios.temporal;
+
+import org.apache.felix.ipojo.ComponentInstance;
+import org.apache.felix.ipojo.junit4osgi.OSGiTestCase;
+import org.apache.felix.ipojo.test.scenarios.temporal.service.CheckService;
+import org.apache.felix.ipojo.test.scenarios.temporal.service.FooService;
+import org.apache.felix.ipojo.test.scenarios.util.Utils;
+import org.osgi.framework.ServiceReference;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+/**
+ * Checks <tt>requires.filter</tt>, <tt>temporal.filter</tt>, <tt>temporal.from</tt> and
+ * <tt>requires.from</tt> attributes.
+ */
+public class FilterTest extends OSGiTestCase {
+
+    /**
+     * Checks <tt>temporal.filter</tt> with dependency id.
+     * The filter is made to get only one provider.
+     */
+    public void testWithTemporalFilters() {
+        String prov = "provider";
+        ComponentInstance provider1 = Utils.getComponentInstanceByName(context, "TEMPORAL-FooProvider", prov);
+        String prov2 = "provider2";
+        ComponentInstance provider2 = Utils.getComponentInstanceByName(context, "TEMPORAL-FooProvider", prov2);
+
+        Dictionary configuration = new Hashtable();
+        String un = "under-1";
+        configuration.put("instance.name", un);
+        Dictionary filter = new Hashtable();
+        filter.put("foo", "(instance.name=provider2)");
+        configuration.put("temporal.filters", filter);
+        ComponentInstance under = Utils.getComponentInstance(context, "TEMPORAL-MultipleCheckServiceProvider", configuration);
+
+        ServiceReference ref_fs = Utils.getServiceReferenceByName(context, FooService.class.getName(), prov);
+        assertNotNull("Check foo availability", ref_fs);
+
+        ServiceReference ref_cs = Utils.getServiceReferenceByName(context, CheckService.class.getName(), un);
+        assertNotNull("Check cs availability", ref_cs);
+
+        CheckService cs = (CheckService) context.getService(ref_cs);
+        assertTrue("Check invocation", cs.check());
+
+        // Stop the provider.
+        provider2.stop();
+        ref_cs = Utils.getServiceReferenceByName(context, CheckService.class.getName(), un);
+        assertNotNull("Check cs availability - 2", ref_cs);
+        long begin = System.currentTimeMillis();
+        DelayedProvider dp2 = new DelayedProvider(provider2, 100);
+        dp2.start();
+        cs = (CheckService) context.getService(ref_cs);
+        assertTrue("Check invocation - 2", cs.check());
+        long end = System.currentTimeMillis();
+        System.out.println("delay = " + (end - begin));
+        assertTrue("Assert min delay", (end - begin) >= 100);
+        assertTrue("Assert max delay", (end - begin) <= 1000);
+        dp2.stop();
+
+        ref_cs = Utils.getServiceReferenceByName(context, CheckService.class.getName(), un);
+        assertNotNull("Check cs availability - 3", ref_cs);
+        cs = (CheckService) context.getService(ref_cs);
+        assertTrue("Check invocation - 3", cs.check());
+
+        provider1.stop();
+        provider2.stop();
+        provider1.dispose();
+        provider2.dispose();
+        under.stop();
+        under.dispose();
+    }
+
+    /**
+     * Checks <tt>requires.filter</tt> with dependency id.
+     * The filter is made to get only one provider.
+     */
+    public void testWithRequireFilters() {
+        String prov = "provider";
+        ComponentInstance provider1 = Utils.getComponentInstanceByName(context, "TEMPORAL-FooProvider", prov);
+        String prov2 = "provider2";
+        ComponentInstance provider2 = Utils.getComponentInstanceByName(context, "TEMPORAL-FooProvider", prov2);
+
+        Dictionary configuration = new Hashtable();
+        String un = "under-1";
+        configuration.put("instance.name", un);
+        Dictionary filter = new Hashtable();
+        filter.put("foo", "(instance.name=provider2)");
+        configuration.put("requires.filters", filter);
+        ComponentInstance under = Utils.getComponentInstance(context, "TEMPORAL-MultipleCheckServiceProvider", configuration);
+
+        ServiceReference ref_fs = Utils.getServiceReferenceByName(context, FooService.class.getName(), prov);
+        assertNotNull("Check foo availability", ref_fs);
+
+        ServiceReference ref_cs = Utils.getServiceReferenceByName(context, CheckService.class.getName(), un);
+        assertNotNull("Check cs availability", ref_cs);
+
+        CheckService cs = (CheckService) context.getService(ref_cs);
+        assertTrue("Check invocation", cs.check());
+
+        // Stop the provider.
+        provider2.stop();
+        ref_cs = Utils.getServiceReferenceByName(context, CheckService.class.getName(), un);
+        assertNotNull("Check cs availability - 2", ref_cs);
+        long begin = System.currentTimeMillis();
+        DelayedProvider dp2 = new DelayedProvider(provider2, 100);
+        dp2.start();
+        cs = (CheckService) context.getService(ref_cs);
+        assertTrue("Check invocation - 2", cs.check());
+        long end = System.currentTimeMillis();
+        System.out.println("delay = " + (end - begin));
+        assertTrue("Assert min delay", (end - begin) >= 100);
+        assertTrue("Assert max delay", (end - begin) <= 1000);
+        dp2.stop();
+
+        ref_cs = Utils.getServiceReferenceByName(context, CheckService.class.getName(), un);
+        assertNotNull("Check cs availability - 3", ref_cs);
+        cs = (CheckService) context.getService(ref_cs);
+        assertTrue("Check invocation - 3", cs.check());
+
+        provider1.stop();
+        provider2.stop();
+        provider1.dispose();
+        provider2.dispose();
+        under.stop();
+        under.dispose();
+    }
+
+    /**
+     * Checks <tt>temporal.from</tt> with dependency id.
+     * The filter is made to get only one specific provider.
+     */
+    public void testWithTemporalFrom() {
+        String prov = "provider";
+        ComponentInstance provider1 = Utils.getComponentInstanceByName(context, "TEMPORAL-FooProvider", prov);
+        String prov2 = "provider2";
+        ComponentInstance provider2 = Utils.getComponentInstanceByName(context, "TEMPORAL-FooProvider", prov2);
+
+        Dictionary configuration = new Hashtable();
+        String un = "under-1";
+        configuration.put("instance.name", un);
+        Dictionary filter = new Hashtable();
+        filter.put("foo", "provider2");
+        configuration.put("temporal.from", filter);
+        ComponentInstance under = Utils.getComponentInstance(context, "TEMPORAL-MultipleCheckServiceProvider", configuration);
+
+        ServiceReference ref_fs = Utils.getServiceReferenceByName(context, FooService.class.getName(), prov);
+        assertNotNull("Check foo availability", ref_fs);
+
+        ServiceReference ref_cs = Utils.getServiceReferenceByName(context, CheckService.class.getName(), un);
+        assertNotNull("Check cs availability", ref_cs);
+
+        CheckService cs = (CheckService) context.getService(ref_cs);
+        assertTrue("Check invocation", cs.check());
+
+        // Stop the provider.
+        provider2.stop();
+        ref_cs = Utils.getServiceReferenceByName(context, CheckService.class.getName(), un);
+        assertNotNull("Check cs availability - 2", ref_cs);
+        long begin = System.currentTimeMillis();
+        DelayedProvider dp2 = new DelayedProvider(provider2, 100);
+        dp2.start();
+        cs = (CheckService) context.getService(ref_cs);
+        assertTrue("Check invocation - 2", cs.check());
+        long end = System.currentTimeMillis();
+        System.out.println("delay = " + (end - begin));
+        assertTrue("Assert min delay", (end - begin) >= 100);
+        assertTrue("Assert max delay", (end - begin) <= 1000);
+        dp2.stop();
+
+        ref_cs = Utils.getServiceReferenceByName(context, CheckService.class.getName(), un);
+        assertNotNull("Check cs availability - 3", ref_cs);
+        cs = (CheckService) context.getService(ref_cs);
+        assertTrue("Check invocation - 3", cs.check());
+
+        provider1.stop();
+        provider2.stop();
+        provider1.dispose();
+        provider2.dispose();
+        under.stop();
+        under.dispose();
+    }
+
+    /**
+     * Checks <tt>requires.from</tt> with dependency id.
+     * The filter is made to get only one specific provider.
+     */
+    public void testWithRequiresFrom() {
+        String prov = "provider";
+        ComponentInstance provider1 = Utils.getComponentInstanceByName(context, "TEMPORAL-FooProvider", prov);
+        String prov2 = "provider2";
+        ComponentInstance provider2 = Utils.getComponentInstanceByName(context, "TEMPORAL-FooProvider", prov2);
+
+        Dictionary configuration = new Hashtable();
+        String un = "under-1";
+        configuration.put("instance.name", un);
+        Dictionary filter = new Hashtable();
+        filter.put("foo", "provider2");
+        configuration.put("requires.from", filter);
+        ComponentInstance under = Utils.getComponentInstance(context, "TEMPORAL-MultipleCheckServiceProvider", configuration);
+
+        ServiceReference ref_fs = Utils.getServiceReferenceByName(context, FooService.class.getName(), prov);
+        assertNotNull("Check foo availability", ref_fs);
+
+        ServiceReference ref_cs = Utils.getServiceReferenceByName(context, CheckService.class.getName(), un);
+        assertNotNull("Check cs availability", ref_cs);
+
+        CheckService cs = (CheckService) context.getService(ref_cs);
+        assertTrue("Check invocation", cs.check());
+
+        // Stop the provider.
+        provider2.stop();
+        ref_cs = Utils.getServiceReferenceByName(context, CheckService.class.getName(), un);
+        assertNotNull("Check cs availability - 2", ref_cs);
+        long begin = System.currentTimeMillis();
+        DelayedProvider dp2 = new DelayedProvider(provider2, 100);
+        dp2.start();
+        cs = (CheckService) context.getService(ref_cs);
+        assertTrue("Check invocation - 2", cs.check());
+        long end = System.currentTimeMillis();
+        System.out.println("delay = " + (end - begin));
+        assertTrue("Assert min delay", (end - begin) >= 100);
+        assertTrue("Assert max delay", (end - begin) <= 1000);
+        dp2.stop();
+
+        ref_cs = Utils.getServiceReferenceByName(context, CheckService.class.getName(), un);
+        assertNotNull("Check cs availability - 3", ref_cs);
+        cs = (CheckService) context.getService(ref_cs);
+        assertTrue("Check invocation - 3", cs.check());
+
+        provider1.stop();
+        provider2.stop();
+        provider1.dispose();
+        provider2.dispose();
+        under.stop();
+        under.dispose();
+    }
+}
diff --git a/ipojo/tests/handler/temporal/src/main/java/org/apache/felix/ipojo/test/scenarios/temporal/TemporalTestSuite.java b/ipojo/tests/handler/temporal/src/main/java/org/apache/felix/ipojo/test/scenarios/temporal/TemporalTestSuite.java
index 180a39c..ebc01b6 100644
--- a/ipojo/tests/handler/temporal/src/main/java/org/apache/felix/ipojo/test/scenarios/temporal/TemporalTestSuite.java
+++ b/ipojo/tests/handler/temporal/src/main/java/org/apache/felix/ipojo/test/scenarios/temporal/TemporalTestSuite.java
@@ -35,6 +35,7 @@
         ots.addTestSuite(NullTest.class);

         ots.addTestSuite(EmptyTest.class);

         ots.addTestSuite(TemporalTest.class);

+        ots.addTestSuite(FilterTest.class);

         return ots;

     }

 

diff --git a/ipojo/tests/handler/temporal/src/main/resources/metadata.xml b/ipojo/tests/handler/temporal/src/main/resources/metadata.xml
index 0ce312d..42262d8 100644
--- a/ipojo/tests/handler/temporal/src/main/resources/metadata.xml
+++ b/ipojo/tests/handler/temporal/src/main/resources/metadata.xml
@@ -42,7 +42,7 @@
 	</component>

 

 	<component classname="org.apache.felix.ipojo.test.scenarios.component.MultipleCheckServiceProvider" name="TEMPORAL-MultipleCheckServiceProvider">

-		<temp:requires field="fs"/>

+		<temp:requires field="fs" id="foo"/>

 		<provides/>

 	</component>