[FELIX-3346] File Install doesn't scan when the framework start level is lower than felix.fileinstall.start.level setting
Patch provided by Bert Jacobs

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1301366 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/DirectoryWatcher.java b/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/DirectoryWatcher.java
index fbc9e26..e50ce59 100644
--- a/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/DirectoryWatcher.java
+++ b/fileinstall/src/main/java/org/apache/felix/fileinstall/internal/DirectoryWatcher.java
@@ -54,6 +54,7 @@
 import org.osgi.framework.Constants;
 import org.osgi.framework.Version;
 import org.osgi.service.packageadmin.PackageAdmin;
+import org.osgi.service.startlevel.StartLevel;
 
 /**
  * -DirectoryWatcher-
@@ -92,6 +93,7 @@
     public final static String NO_INITIAL_DELAY = "felix.fileinstall.noInitialDelay";
     public final static String DISABLE_CONFIG_SAVE = "felix.fileinstall.disableConfigSave";
     public final static String START_LEVEL = "felix.fileinstall.start.level";
+    public final static String ACTIVE_LEVEL = "felix.fileinstall.active.level";
 
     static final SecureRandom random = new SecureRandom();
 
@@ -110,6 +112,7 @@
     String originatingFileName;
     boolean noInitialDelay;
     int startLevel;
+    int activeLevel;
 
     // Map of all installed artifacts
     Map/* <File, Artifact> */ currentManagedArtifacts = new HashMap/* <File, Artifact> */();
@@ -119,6 +122,9 @@
 
     // Represents files that could not be processed because of a missing artifact listener
     Set/* <File> */ processingFailures = new HashSet/* <File> */();
+    
+    // Represents installed artifacts which need to be started later because they failed to start
+    Set/* <Artifact> */ delayedStart = new HashSet/* <Artifact> */();
 
     // Represents artifacts that could not be installed
     Map/* <File, Artifact> */ installationFailures = new HashMap/* <File, Artifact> */();
@@ -141,6 +147,7 @@
         filter = (String) properties.get(FILTER);
         noInitialDelay = getBoolean(properties, NO_INITIAL_DELAY, false);
         startLevel = getInt(properties, START_LEVEL, 0);    // by default, do not touch start level
+        activeLevel = getInt(properties, ACTIVE_LEVEL, 0);    // by default, always scan
         this.context.addBundleListener(this);
 
         FilenameFilter flt;
@@ -259,8 +266,8 @@
         {
             try
             {
-                // Waiting for start level
-                if (FileInstall.getStartLevel().getStartLevel() >= startLevel)
+                // Don't access the disk when the framework is still in a startup phase.
+                if (FileInstall.getStartLevel().getStartLevel() >= activeLevel)
                 {
                     Set/*<File>*/ files = scanner.scan(false);
                     // Check that there is a result.  If not, this means that the directory can not be listed,
@@ -306,10 +313,9 @@
                 Artifact artifact = (Artifact) entry.getValue();
                 if (artifact.getBundleId() == bundleEvent.getBundle().getBundleId())
                 {
-                    log(Logger.LOG_DEBUG,
-                        "Bundle " + bundleEvent.getBundle().getBundleId()
+                    log(Logger.LOG_DEBUG, "Bundle " + bundleEvent.getBundle().getBundleId()
                             + " has been uninstalled", null);
-                    currentManagedArtifacts.remove(entry.getKey());
+                    it.remove();
                     break;
                 }
             }
@@ -459,12 +465,15 @@
             refresh();
         }
 
-        if (startBundles || startLevel != 0)
+        if (startBundles)
         {
             // Try to start all the bundles that are not persistently stopped
-            processAllBundles();
-            // Try to start newly installed bundles
-            process(installedBundles);
+            startAllBundles();
+            
+            delayedStart.addAll(installedBundles);
+            delayedStart.removeAll(uninstalledBundles);
+            // Try to start newly installed bundles, or bundles which we missed on a previous round
+            startBundles(delayedStart);
         }
     }
 
@@ -989,6 +998,13 @@
         is.reset();
         Bundle b = context.installBundle(bundleLocation, is);
         Util.storeChecksum(b, checksum, context);
+        
+        // Set default start level at install time, the user can override it if he wants
+        if (startLevel != 0)
+        {
+            FileInstall.getStartLevel().setBundleStartLevel(b, startLevel);
+        }
+        
         return b;
     }
 
@@ -1132,7 +1148,13 @@
         }
     }
 
-    private void processAllBundles()
+    /**
+     * Tries to start all the bundles which somehow got stopped transiently.
+     * The File Install component will only retry the start When {@link #USE_START_TRANSIENT}
+     * is set to true or when a bundle is persistently started. Persistently stopped bundles
+     * are ignored.
+     */
+    private void startAllBundles()
     {
         List bundles = new ArrayList();
         for (Iterator it = currentManagedArtifacts.values().iterator(); it.hasNext();)
@@ -1152,30 +1174,38 @@
                 }
             }
         }
-        process(bundles);
+        startBundles(bundles);
     }
 
-    private void process(Collection/* <Bundle> */ bundles)
+     /**
+      * Starts a bundle and removes it from the Collection when successfully started.
+      * @param bundles
+      */
+    private void startBundles(Collection/* <Bundle> */ bundles)
     {
         for (Iterator b = bundles.iterator(); b.hasNext(); )
         {
-            process((Bundle) b.next());
+            if (startBundle((Bundle) b.next()))
+            {
+                b.remove();
+            }
         }
     }
 
-    private void process(Bundle bundle)
+     /**
+      * Start a bundle, if the framework's startlevel allows it.
+      * @param bundle the bundle to start.
+      * @return whether the bundle was started.
+      */
+    private boolean startBundle(Bundle bundle)
     {
-        // Change startLevel for bundle
-        if (startLevel != 0)
-        {
-            FileInstall.getStartLevel().setBundleStartLevel(bundle, startLevel);
-        }
-
-        // Fragments can not be started.
-        // No need to check status of bundles
-        // before starting, because OSGi treats this
-        // as a noop when the bundle is already started
-        if (!isFragment(bundle) && startBundles)
+        StartLevel startLevelSvc = FileInstall.getStartLevel();
+        // Fragments can never be started.
+        // Bundles can only be started transient when the start level of the framework is high
+        // enough. Persistent (i.e. non-transient) starts will simply make the framework start the
+        // bundle when the start level is high enough.
+        if (!isFragment(bundle) && startBundles
+                && startLevelSvc.getStartLevel() >= startLevelSvc.getBundleStartLevel(bundle))
         {
             try
             {
@@ -1183,6 +1213,7 @@
                 options |= useStartActivationPolicy ? Bundle.START_ACTIVATION_POLICY : 0;
                 bundle.start(options);
                 log(Logger.LOG_INFO, "Started bundle: " + bundle.getLocation(), null);
+                return true;
             }
             catch (BundleException e)
             {
@@ -1190,5 +1221,6 @@
                 log(Logger.LOG_WARNING, "Error while starting bundle: " + bundle.getLocation(), e);
             }
         }
+        return false;
     }
 }