FELIX-4050: Avoid messing up component state calculation when an init method adds
available dependencies using the API and also returns a Map for configuring
other named annotated dependencies.


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1486131 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/dependencymanager/runtime/src/main/java/org/apache/felix/dm/runtime/AbstractBuilder.java b/dependencymanager/runtime/src/main/java/org/apache/felix/dm/runtime/AbstractBuilder.java
index c8e23b6..9fbb616 100644
--- a/dependencymanager/runtime/src/main/java/org/apache/felix/dm/runtime/AbstractBuilder.java
+++ b/dependencymanager/runtime/src/main/java/org/apache/felix/dm/runtime/AbstractBuilder.java
@@ -115,7 +115,7 @@
             String name = dependency.getString(Params.name, null);
             if (name == null) {
                 DependencyBuilder depBuilder = new DependencyBuilder(dependency);
-                Log.instance().info("ServiceLifecycleHandler.init: adding dependency %s into service %s",
+                Log.instance().info("adding dependency %s into service %s",
                                    dependency, srvMeta);
                 Dependency d = depBuilder.build(b, dm, false);
                 s.add(d);
diff --git a/dependencymanager/runtime/src/main/java/org/apache/felix/dm/runtime/ServiceLifecycleHandler.java b/dependencymanager/runtime/src/main/java/org/apache/felix/dm/runtime/ServiceLifecycleHandler.java
index 90ff707..a41358e 100644
--- a/dependencymanager/runtime/src/main/java/org/apache/felix/dm/runtime/ServiceLifecycleHandler.java
+++ b/dependencymanager/runtime/src/main/java/org/apache/felix/dm/runtime/ServiceLifecycleHandler.java
@@ -90,15 +90,15 @@
  */
 public class ServiceLifecycleHandler
 {
-    private String m_init;
-    private String m_start;
-    private String m_stop;
-    private String m_destroy;
-    private MetaData m_srvMeta;
-    private List<MetaData> m_depsMeta;
-    private List<Dependency> m_namedDeps = new ArrayList<Dependency>();
-    private Bundle m_bundle;
-    private ToggleServiceDependency m_toggle;
+    private final String m_init;
+    private final String m_start;
+    private final String m_stop;
+    private final String m_destroy;
+    private final MetaData m_srvMeta;
+    private final List<MetaData> m_depsMeta;
+    private final List<Dependency> m_namedDeps = new ArrayList<Dependency>();
+    private final Bundle m_bundle;
+    private volatile ToggleServiceDependency m_toggle;
     private final static Object SYNC = new Object();
 
     /**
@@ -128,14 +128,17 @@
      * the actual Service' init method, to see if a dependency customization map is returned.
      * We also check if a Lifecycle Controller is used. In this case, we add a hidden custom dependency,
      * allowing to take control of when the component is actually started/stopped.
-     * @param service The Annotated Service
+     * We also handle an edge case described in FELIX-4050, where component state calculation 
+     * may mess up if some dependencies are added using the API from the init method.
+     * 
+     * @param c The Annotated Component
      */
-    @SuppressWarnings("unchecked")
-    public void init(Component service)
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public void init(Component c)
         throws Exception
     {
-        Object serviceInstance = service.getService();
-        DependencyManager dm = service.getDependencyManager();
+        Object serviceInstance = c.getService();
+        DependencyManager dm = c.getDependencyManager();
 
         // Check if a lifecycle controller is defined for this service. If true, then 
         // We'll use the ToggleServiceDependency in order to manually activate/deactivate 
@@ -154,7 +157,7 @@
             m_toggle = new ToggleServiceDependency();
             AtomicBoolean startFlag = new AtomicBoolean(false);
             // Add the toggle to the service (we'll remove it from our destroy emthod).
-            service.add(m_toggle);
+            c.add(m_toggle);
             // Inject the runnable that will start our service, when invoked.
             setField(serviceInstance, starter, Runnable.class, new ComponentStarter(componentName, m_toggle, startFlag));
             if (stopper != null) {
@@ -163,15 +166,27 @@
             }
         }
 
-        // Invoke all composites' init methods, and for each one, check if a dependency
+        // Before invoking an optional init method, we have to handle an edge case (FELIX-4050), where
+        // init may add dependencies using the API and also return a map for configuring some
+        // named dependencies. We have to add a hidden toggle dependency in the component, which we'll 
+        // active *after* the init method is called, and possibly *after* named dependencies are configured.
+        
+        ToggleServiceDependency initToggle = null;
+        if (m_init != null) 
+        {
+            initToggle = new ToggleServiceDependency();
+            c.add(initToggle); 
+        }
+        
+        // Invoke component and all composites init methods, and for each one, check if a dependency
         // customization map is returned by the method. This map will be used to configure 
         // some dependency filters (or required flag).
 
         Map<String, String> customization = new HashMap<String, String>();
-        Object[] composites = service.getCompositionInstances();
+        Object[] composites = c.getCompositionInstances();
         for (Object composite: composites)
         {
-            Object o = invokeMethod(composite, m_init, dm, service);
+            Object o = invokeMethod(composite, m_init, dm, c);
             if (o != null && Map.class.isAssignableFrom(o.getClass()))
             {
                 customization.putAll((Map) o);
@@ -180,7 +195,9 @@
 
         Log.instance().debug("ServiceLifecycleHandler.init: invoked init method from service %s " +
                              ", returned map: %s", serviceInstance, customization);
-
+        
+        // Apply name dependency filters possibly returned by the init() method.
+        
         for (MetaData dependency: m_depsMeta)
         {
             // Check if this dependency has a name, and if we find the name from the 
@@ -214,10 +231,19 @@
         }
         
         // Add all extra dependencies in one shot, in order to calculate state changes for all dependencies at a time.
-        if (m_namedDeps.size() > 0) {
+        if (m_namedDeps.size() > 0) 
+        {
             Log.instance().info("ServiceLifecycleHandler.init: adding extra/named dependencies %s",
                                 m_namedDeps);
-            service.add(m_namedDeps);
+            c.add(m_namedDeps);
+        }
+        
+        // init method fully handled, and all possible named dependencies have been configured. Now, activate the 
+        // hidden toggle, and then remove it from the component, because we don't need it anymore.
+        if (initToggle != null) 
+        {
+            initToggle.setAvailable(true);
+            c.remove(initToggle);
         }
     }
 
@@ -228,7 +254,7 @@
      * some additional properties which must be appended to existing service properties.
      * Such extra properties takes precedence over existing service properties.
      */
-    @SuppressWarnings("unchecked")
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     public void start(Component service)
         throws IllegalArgumentException, IllegalAccessException, InvocationTargetException
     {