added support for adding dynamic aspects for services, plus a test that validates the basic behaviour

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@885969 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/DependencyActivatorBase.java b/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/DependencyActivatorBase.java
index 2f24f8e..782c8ad 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/DependencyActivatorBase.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/DependencyActivatorBase.java
@@ -18,6 +18,7 @@
  */
 package org.apache.felix.dependencymanager;
 
+import java.util.Dictionary;
 import java.util.List;
 
 import org.apache.felix.dependencymanager.dependencies.BundleDependency;
@@ -141,6 +142,10 @@
         return m_manager.createBundleDependency();
     }
 
+    public Service createAspectService(Class serviceInterface, String serviceFilter, Object aspectImplementation, Dictionary properties) {
+        return m_manager.createAspectService(serviceInterface, serviceFilter, aspectImplementation, properties);
+    }
+
     /**
      * Cleans up all services and their dependencies.
      * 
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/DependencyManager.java b/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/DependencyManager.java
index 26a9066..3ebfe5e 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/DependencyManager.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/DependencyManager.java
@@ -20,12 +20,14 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Dictionary;
 import java.util.List;
 
 import org.apache.felix.dependencymanager.dependencies.BundleDependency;
 import org.apache.felix.dependencymanager.dependencies.ConfigurationDependency;
 import org.apache.felix.dependencymanager.dependencies.ServiceDependency;
 import org.apache.felix.dependencymanager.dependencies.TemporalServiceDependency;
+import org.apache.felix.dependencymanager.impl.AspectImpl;
 import org.apache.felix.dependencymanager.impl.Logger;
 import org.apache.felix.dependencymanager.impl.ServiceImpl;
 import org.osgi.framework.BundleContext;
@@ -108,7 +110,17 @@
     public BundleDependency createBundleDependency() {
         return new BundleDependency(m_context, m_logger);
     }
-    
+
+    public Service createAspectService(Class serviceInterface, String serviceFilter, Object aspectImplementation, Dictionary properties) {
+        return createService()
+            .setImplementation(new AspectImpl(serviceInterface, serviceFilter, aspectImplementation, properties))
+            .add(createServiceDependency()
+                .setService(serviceInterface, serviceFilter)
+                .setAutoConfig(false)
+                .setCallbacks("added", "removed")
+            );
+    }
+
     /**
      * Returns a list of services.
      * 
@@ -117,5 +129,4 @@
     public List getServices() {
         return Collections.unmodifiableList(m_services);
     }
-
 }
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/Service.java b/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/Service.java
index dddc800..cdcea6f 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/Service.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/Service.java
@@ -36,6 +36,7 @@
      * @return this service
      */
     public Service add(Dependency dependency);
+    public Service add(List dependencies);
     
     /**
      * Removes a dependency from this service.
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/impl/AspectImpl.java b/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/impl/AspectImpl.java
new file mode 100644
index 0000000..e5a969e
--- /dev/null
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/impl/AspectImpl.java
@@ -0,0 +1,72 @@
+package org.apache.felix.dependencymanager.impl;
+
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.apache.felix.dependencymanager.Service;
+import org.osgi.framework.ServiceReference;
+
+public class AspectImpl {
+	private volatile DependencyManager m_manager;
+	private volatile Service m_service;
+	private final Class m_serviceInterface;
+	private final String m_serviceFilter;
+	private final Object m_aspectImplementation;
+	private final Map m_services = new HashMap();
+    private final Dictionary m_aspectProperties;
+	
+	public AspectImpl(Class serviceInterface, String serviceFilter, Object aspectImplementation, Dictionary properties) {
+		m_serviceInterface = serviceInterface;
+		m_serviceFilter = serviceFilter;
+		m_aspectImplementation = aspectImplementation;
+		m_aspectProperties = properties;
+	}
+
+	public void added(ServiceReference ref, Object service) {
+		Properties props = new Properties();
+		String[] keys = ref.getPropertyKeys();
+		for (int i = 0; i < keys.length; i++) {
+		    props.put(keys[i], ref.getProperty(keys[i]));
+		}
+		Enumeration e = m_aspectProperties.keys();
+        while (e.hasMoreElements()) {
+            Object key = e.nextElement();
+            props.put(key, m_aspectProperties.get(key));
+        }
+
+		Service newService = m_manager.createService()
+			.setInterface(m_serviceInterface.getName(), props)
+			.setImplementation(m_aspectImplementation)
+			.add(m_service.getDependencies())
+			.add(m_manager.createServiceDependency()
+				.setService(m_serviceInterface, ref)
+				.setRequired(true)
+				);
+		m_services.put(ref, newService);
+		m_manager.add(newService);
+	}
+
+	public void removed(ServiceReference ref, Object service) {
+		Service newService = (Service) m_services.remove(ref);
+		if (newService == null) {
+			System.out.println("Service should not be null here, dumping stack.");
+			Thread.dumpStack();
+		}
+		else {
+			m_manager.remove(newService);
+		}
+	}
+	
+    public void stop() { 
+        Iterator i = m_services.values().iterator();
+        while (i.hasNext()) {
+            m_manager.remove((Service) i.next());
+        }
+        m_services.clear();
+    }
+}
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/impl/ServiceImpl.java b/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/impl/ServiceImpl.java
index 17c5100..f11f1d3 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/impl/ServiceImpl.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dependencymanager/impl/ServiceImpl.java
@@ -904,23 +904,13 @@
                 }
                 catch (Exception e) {
                     m_logger.log(Logger.LOG_ERROR, "Could not obtain instances from the composition manager.", e);
-                    instances = new Object[] { m_serviceInstance };
+                    instances = m_serviceInstance == null ? new Object[] {} : new Object[] { m_serviceInstance };
                 }
             }
         }
         else {
-            instances = new Object[] { m_serviceInstance };
+            instances = m_serviceInstance == null ? new Object[] {} : new Object[] { m_serviceInstance };
         }
-        // TODO remove this test code; there are definitely cases where some instances in this array can be
-        // null, but it's not always harmful (in fact it's not possible to determine that here), this also happens
-        // when you start tracking required dependencies... it's probably safe not to include these null's in the
-        // array in the first place
-//        for (int i = 0; i < instances.length; i++) {
-//            if (instances[i] == null) {
-//                System.out.println("GetCompositionInstances had a null instance at index " + i + " dumping stack:");
-//                Thread.dumpStack();
-//            }
-//        }
         return instances;
     }
 
diff --git a/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/AspectTest.java b/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/AspectTest.java
new file mode 100644
index 0000000..b810919
--- /dev/null
+++ b/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/AspectTest.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dependencymanager.test;
+
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.provision;
+
+import java.util.Properties;
+
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.apache.felix.dependencymanager.Service;
+import org.apache.felix.dependencymanager.impl.Logger;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.Configuration;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+
+@RunWith(JUnit4TestRunner.class)
+public class AspectTest {
+    @Configuration
+    public static Option[] configuration() {
+        return options(
+            provision(
+                mavenBundle().groupId("org.osgi").artifactId("org.osgi.compendium").version("4.2.0"),
+                mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.dependencymanager").versionAsInProject()
+            )
+        );
+    }    
+
+    @Test
+    public void testServiceRegistrationAndConsumption(BundleContext context) {
+        DependencyManager m = new DependencyManager(context, new Logger(context));
+        // helper class that ensures certain steps get executed in sequence
+        Ensure e = new Ensure();
+        // create a service provider and consumer
+        Service sp = m.createService().setImplementation(new ServiceProvider(e)).setInterface(ServiceInterface.class.getName(), null);
+        Service sc = m.createService().setImplementation(new ServiceConsumer(e)).add(m.createServiceDependency().setService(ServiceInterface.class).setRequired(true));
+        Service sa = m.createAspectService(ServiceInterface.class, "(|(!(" + Constants.SERVICE_RANKING + "=*))(" + Constants.SERVICE_RANKING + "<=0))", new ServiceAspect(e), new Properties() {{ put(Constants.SERVICE_RANKING, Integer.valueOf(1)); }} );
+        m.add(sc);
+        m.add(sp);
+        e.waitForStep(3, 2000);
+        m.add(sa);
+        e.waitForStep(4, 2000);
+        e.step(5);
+        e.waitForStep(8, 2000);
+        m.remove(sa);
+        e.waitForStep(9, 2000);
+        e.step(10);
+        e.waitForStep(11, 2000);
+        m.remove(sp);
+        m.remove(sc);
+    }
+    
+    static interface ServiceInterface {
+        public void invoke(Runnable run);
+    }
+
+    static class ServiceProvider implements ServiceInterface {
+        private final Ensure m_ensure;
+        public ServiceProvider(Ensure e) {
+            m_ensure = e;
+        }
+        public void invoke(Runnable run) {
+            run.run();
+        }
+    }
+    
+    static class ServiceAspect implements ServiceInterface {
+        private final Ensure m_ensure;
+        private volatile ServiceInterface m_originalService;
+        
+        public ServiceAspect(Ensure e) {
+            m_ensure = e;
+        }
+        public void start() {
+            m_ensure.step(4);
+        }
+        public void invoke(Runnable run) {
+            m_ensure.step(6);
+            m_originalService.invoke(run);
+        }
+        
+        public void stop() {
+            m_ensure.step(9);
+        }
+    }
+
+    static class ServiceConsumer implements Runnable {
+        private volatile ServiceInterface m_service;
+        private final Ensure m_ensure;
+
+        public ServiceConsumer(Ensure e) {
+            m_ensure = e;
+        }
+        
+        public void init() {
+            Thread t = new Thread(this);
+            t.start();
+        }
+        
+        public void run() {
+            m_ensure.step(1);
+            m_service.invoke(Ensure.createRunnableStep(m_ensure, 2));
+            m_ensure.step(3);
+            m_ensure.waitForStep(5, 2000);
+            m_service.invoke(Ensure.createRunnableStep(m_ensure, 7));
+            m_ensure.step(8);
+            m_ensure.waitForStep(10, 2000);
+            m_service.invoke(Ensure.createRunnableStep(m_ensure, 11));
+        }
+    }
+}
diff --git a/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/Ensure.java b/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/Ensure.java
index bdac0bd..d908036 100644
--- a/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/Ensure.java
+++ b/dependencymanager/test/src/test/java/org/apache/felix/dependencymanager/test/Ensure.java
@@ -79,4 +79,8 @@
             System.out.println("[Ensure " + INSTANCE + "] arrived at step " + nr);
         }
     }
+    
+    public static Runnable createRunnableStep(final Ensure ensure, final int nr) {
+        return new Runnable() { public void run() { ensure.step(nr); }};
+    }
 }