Hi,

PFA a patch for following File Install bundle issues;
https://issues.apache.org/jira/browse/FELIX-937
https://issues.apache.org/jira/browse/FELIX-938
https://issues.apache.org/jira/browse/FELIX-939

Your comments are welcome.

Thanks,
Sahoo
Index: src/main/java/org/apache/felix/fileinstall/DirectoryWatcher.java
===================================================================
--- src/main/java/org/apache/felix/fileinstall/DirectoryWatcher.java	(revision 744132)
+++ src/main/java/org/apache/felix/fileinstall/DirectoryWatcher.java	(working copy)
@@ -26,6 +26,8 @@
  */
 import java.io.*;
 import java.util.*;
+import java.net.URISyntaxException;
+import java.net.URI;
 
 import org.osgi.framework.*;
 import org.osgi.service.cm.*;
@@ -46,6 +48,7 @@
     boolean startBundles = true; // by default, we start bundles.
     BundleContext context;
     boolean reported;
+    Map/* <String, Jar> */ currentManagedBundles = new HashMap();
 
     public DirectoryWatcher(Dictionary properties, BundleContext context)
     {
@@ -79,17 +82,16 @@
         log(DIR + "            " + watchedDirectory.getAbsolutePath(), null);
         log(DEBUG + "          " + debug, null);
         log(START_NEW_BUNDLES + "          " + startBundles, null);
-        Map currentManagedBundles = new HashMap(); // location -> Long(time)
+        initializeCurrentManagedBundles();
         Map currentManagedConfigs = new HashMap(); // location -> Long(time)
-
         while (!interrupted())
         {
             try
             {
-                Set/* <String> */ installed = new HashSet();
+                Map/* <String, Jar> */ installed = new HashMap();
                 Set/* <String> */ configs = new HashSet();
                 traverse(installed, configs, watchedDirectory);
-                doInstalled(currentManagedBundles, installed);
+                doInstalled(installed);
                 doConfigs(currentManagedConfigs, configs);
                 Thread.sleep(poll);
             }
@@ -276,137 +278,40 @@
     }
 
     /**
-     * Install bundles that were discovered and uninstall bundles that are gone
-     * from the current state.
+     * Install bundles that were discovered, uninstall bundles that are gone
+     * from the current state and update the ones that have been changed.
+     * Keep {...@link #currentManagedBundles} up-to-date.
      *
-     * @param current
-     *            A map location -> path that holds the current state
      * @param discovered
-     *            A set of paths that represent the just found bundles
+     *            A map of path to {...@link Jar} that holds the discovered state
      */
-    void doInstalled(Map current, Set discovered)
+    void doInstalled(Map discovered)
     {
-        boolean refresh = false;
-        Bundle bundles[] = context.getBundles();
-        for (int i = 0; i < bundles.length; i++)
-        {
-            Bundle bundle = bundles[i];
-            String location = bundle.getLocation();
-            if (discovered.contains(location))
-            {
-                // We have a bundle that is already installed
-                // so we know it
-                discovered.remove(location);
+        // Find out all the new, deleted and common bundles.
+        // new = discovered - current,
+        Set newBundles = new HashSet(discovered.values());
+        newBundles.removeAll(currentManagedBundles.values());
 
-                File file = new File(location);
+        // deleted = current - discovered
+        Set deletedBundles = new HashSet(currentManagedBundles.values());
+        deletedBundles.removeAll(discovered.values());
 
-                // Modified date does not work on the Nokia
-                // for some reason, so we take size into account
-                // as well.
-                long newSize = file.length();
-                Long oldSizeObj = (Long) current.get(location);
-                long oldSize = oldSizeObj == null ? 0 : oldSizeObj.longValue();
+        // existing = intersection of current & discovered
+        Set existingBundles = new HashSet(discovered.values());
+        existingBundles.retainAll(currentManagedBundles.values());
 
-                if (file.lastModified() > bundle.getLastModified() + 4000 && oldSize != newSize)
-                {
-                    try
-                    {
-                        // We treat this as an update, it is modified,,
-                        // different size, and it is present in the dir
-                        // as well as in the list of bundles.
-                        current.put(location, new Long(newSize));
-                        InputStream in = new FileInputStream(file);
-                        bundle.update(in);
-                        refresh = true;
-                        in.close();
-                        log("Updated " + location, null);
-                    }
-                    catch (Exception e)
-                    {
-                        log("Failed to update bundle ", e);
-                    }
-                }
-
-                // Fragments can not be started. All other
-                // bundles are started only if user has asked us to
-                // start bundles. No need to check status of bundles
-                // before starting, because OSGi treats this
-                // as a noop when the bundle is already started
-                if (startBundles && !isFragment(bundle))
-                {
-                    try
-                    {
-                        bundle.start();
-                    }
-                    catch (Exception e)
-                    {
-                        log("Fail to start bundle " + location, e);
-                    }
-                }
-            }
-            else
-            {
-                // Hmm. We found a bundle that looks like it came from our
-                // watched directory but we did not find it this round.
-                // Just remove it.
-                if (bundle.getLocation().startsWith(watchedDirectory.getAbsolutePath()))
-                {
-                    try
-                    {
-                        bundle.uninstall();
-                        refresh = true;
-                        log("Uninstalled " + location, null);
-                    }
-                    catch (Exception e)
-                    {
-                        log("failed to uninstall bundle: ", e);
-                    }
-                }
-            }
-        }
-
-        List starters = new ArrayList();
-        for (Iterator it = discovered.iterator(); it.hasNext();)
-        {
-            try
-            {
-                String path = (String) it.next();
-                File file = new File(path);
-                InputStream in = new FileInputStream(file);
-                Bundle bundle = context.installBundle(path, in);
-                in.close();
-                if (startBundles)
-                {
-                    // We do not start this bundle yet. We wait after
-                    // refresh because this will minimize the disruption
-                    // as well as temporary unresolved errors.
-                    starters.add(bundle);
-                }
-                log("Installed " + file.getAbsolutePath(), null);
-            }
-            catch (Exception e)
-            {
-                log("failed to install/start bundle: ", e);
-            }
-        }
-
+        // We do the operations in the following order:
+        // uninstall, update, install, refresh & start.
+        uninstall(deletedBundles);
+        boolean refresh = updateIfNeeded(existingBundles);
+        Collection starters = install(newBundles);
+        if (!startBundles) starters = Collections.emptyList();
         if (refresh || starters.size() != 0)
         {
             refresh();
             for (Iterator b = starters.iterator(); b.hasNext();)
             {
-                Bundle bundle = (Bundle) b.next();
-                if (!isFragment(bundle))
-                {
-                    try
-                    {
-                        bundle.start();
-                    }
-                    catch (BundleException e)
-                    {
-                        log("Error while starting a newly installed bundle", e);
-                    }
-                }
+                start((Bundle) b.next());
             }
         }
     }
@@ -461,17 +366,17 @@
     }
 
     /**
-     * Traverse the directory and fill the map with the found jars and
-     * configurations keyed by the abs file path.
+     * Traverse the directory and fill the set with the found jars and
+     * configurations.
      *
      * @param jars
-     *            Returns the abspath -> file for found jars
+     *            Returns path -> {...@link Jar} map for found jars
      * @param configs
      *            Returns the abspath -> file for found configurations
      * @param jardir
      *            The directory to traverse
      */
-    void traverse(Set jars, Set configs, File jardir)
+    void traverse(Map/* <String, Jar> */ jars, Set configs, File jardir)
     {
         String list[] = jardir.list();
         for (int i = 0; i < list.length; i++)
@@ -479,7 +384,8 @@
             File file = new File(jardir, list[i]);
             if (list[i].endsWith(".jar"))
             {
-                jars.add(file.getAbsolutePath());
+                Jar jar = new Jar(file);
+                jars.put(jar.getPath(), jar);
             }
             else if (list[i].endsWith(".cfg"))
             {
@@ -570,4 +476,179 @@
             // Ignore
         }
     }
-}
+
+    /**
+     * This method goes through all the currently installed bundles
+     * and returns information about those bundles whose location
+     * refers to a file in our {...@link #watchedDirectory}.
+     */
+    private void initializeCurrentManagedBundles()
+    {
+        Bundle[] bundles = this.context.getBundles();
+        String watchedDirPath = watchedDirectory.toURI().normalize().getPath();
+        for (int i = 0; i < bundles.length; ++i)
+        {
+            try
+            {
+                String location =  new URI(bundles[i].getLocation()).normalize().getPath();
+                final int index = location.lastIndexOf('/');
+                if (index != -1 && location.substring(0, index + 1).equals(watchedDirPath))
+                {
+                    // This bundle's location matches our watched dir path
+                    Jar jar = new Jar(bundles[i]);
+                    currentManagedBundles.put(jar.getPath(), jar);
+                }
+            }
+            catch (URISyntaxException e)
+            {
+                // Ignore and continue.
+                // This can never happen for bundles that have been installed
+                // by FileInstall, as we always use proper filepath as location.
+            }
+        }
+    }
+
+    /**
+     * @param jars Collection of {...@link Jar} to be installed
+     * @return List of Bundles just installed
+     */
+    private Collection/* <Bundle> */ install(Collection jars)
+    {
+        List bundles = new ArrayList();
+        for (Iterator iter = jars.iterator(); iter.hasNext();)
+        {
+            Bundle bundle = install((Jar) iter.next());
+            if (bundle != null)
+            {
+                bundles.add(bundle);
+            }
+        }
+        return bundles;
+    }
+
+    /**
+     * @param jars Collection of {...@link Jar} to be uninstalled
+     */
+    private void uninstall(Collection jars)
+    {
+        for (Iterator iter = jars.iterator(); iter.hasNext();)
+        {
+            final Jar jar = (Jar) iter.next();
+            uninstall(jar);
+        }
+    }
+
+    /**
+     * Update the bundles if the underlying file has changed.
+     * This method reads the information about jars to be updated,
+     * compares them with information available in {...@link #currentManagedBundles}.
+     * If the file is newer, it update the bundle.
+     *
+     * @param jars    Collection of {...@link Jar}s representing state of files.
+     * @return boolean if any bundle got updated, else false
+     */
+    private boolean updateIfNeeded(Collection jars)
+    {
+        boolean refresh = false;
+        for (Iterator iter = jars.iterator(); iter.hasNext();)
+        {
+            Jar e = (Jar) iter.next();
+            Jar c = (Jar) currentManagedBundles.get(e.getPath());
+            if (e.isNewer(c))
+            {
+                refresh |= update(c);
+            }
+        }
+        return refresh;
+    }
+
+    private Bundle install(Jar jar)
+    {
+        Bundle bundle = null;
+        try
+        {
+            String path = jar.getPath();
+            File file = new File(path);
+            InputStream in = new FileInputStream(file);
+            bundle = context.installBundle(path, in);
+            in.close();
+            currentManagedBundles.put(path, new Jar(bundle));
+            log("Installed " + file.getAbsolutePath(), null);
+        }
+        catch (Exception e)
+        {
+            log("failed to install bundle: ", e);
+        }
+        return bundle;
+    }
+
+    private void uninstall(Jar jar)
+    {
+        try
+        {
+            Jar old = (Jar) currentManagedBundles.remove(jar.getPath());
+            Bundle bundle = context.getBundle(old.getBundleId());
+            bundle.uninstall();
+            log("Uninstalled " + jar.getPath(), null);
+        }
+        catch (Exception e)
+        {
+            log("failed to uninstall bundle: ", e);
+        }
+    }
+
+    private boolean update(Jar jar)
+    {
+        boolean refresh = false;
+        InputStream in = null;
+        try
+        {
+            File file = new File(jar.getPath());
+            in = new FileInputStream(file);
+            Bundle bundle = context.getBundle(jar.getBundleId());
+            bundle.update(in);
+            refresh = true;
+            jar.setLastModified(bundle.getLastModified());
+            jar.setLength(file.length());
+            log("Updated " + jar.getPath(), null);
+        }
+        catch (Exception e)
+        {
+            log("Failed to update bundle ", e);
+        }
+        finally
+        {
+            if (in != null)
+            {
+                try
+                {
+                    in.close();
+                }
+                catch (IOException e)
+                {
+                }
+            }
+        }
+        return refresh;
+    }
+
+    private void start(Bundle bundle)
+    {
+        // 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))
+        {
+            try
+            {
+                bundle.start();
+            }
+            catch (BundleException e)
+            {
+                log("Error while starting a newly installed bundle", e);
+            }
+        }
+    }
+
+}
\ No newline at end of file
Index: src/main/java/org/apache/felix/fileinstall/Jar.java
===================================================================
--- src/main/java/org/apache/felix/fileinstall/Jar.java	(revision 0)
+++ src/main/java/org/apache/felix/fileinstall/Jar.java	(revision 0)
@@ -0,0 +1,116 @@
+/*
+ * 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.fileinstall;
+
+import org.osgi.framework.Bundle;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * This class is used to cache vital information of a jar file
+ * that is used during later processing. It also overrides hashCode and
+ * equals methods so that it can be used in various Set operations.
+ * It uses file's path as the primary key. Before
+ *
+ * @author [email protected]
+ */
+class Jar
+{
+    private String path;
+    private long length = -1;
+    private long lastModified = -1;
+    private long bundleId = -1;
+
+    Jar(File file)
+    {
+        // Convert to a URI because the location of a bundle
+        // is typically a URI. At least, that's the case for
+        // autostart bundles.
+        // Normalisation is needed to ensure that we don't treat (e.g.)
+        // /tmp/foo and /tmp//foo differently.
+        path = file.toURI().normalize().getPath();
+        lastModified = file.lastModified();
+        length = file.length();
+    }
+
+    Jar(Bundle b) throws URISyntaxException
+    {
+        // Normalisation is needed to ensure that we don't treat (e.g.)
+        // /tmp/foo and /tmp//foo differently.
+        URI uri = new URI(b.getLocation()).normalize();
+        path = uri.getPath();
+        lastModified = b.getLastModified();
+        bundleId = b.getBundleId();
+    }
+
+    public String getPath()
+    {
+        return path;
+    }
+
+    public long getLastModified()
+    {
+        return lastModified;
+    }
+
+    public void setLastModified(long lastModified)
+    {
+        this.lastModified = lastModified;
+    }
+
+    public long getLength()
+    {
+        return length;
+    }
+
+    public void setLength(long length)
+    {
+        this.length = length;
+    }
+
+    public long getBundleId()
+    {
+        return bundleId;
+    }
+
+    public boolean isNewer(Jar other)
+    {
+        return (getLastModified() > other.getLastModified());
+    }
+
+    // Override hashCode and equals as this object is used in Set
+    public int hashCode()
+    {
+        return path.hashCode();
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (obj instanceof Jar)
+        {
+            return this.path.equals(((Jar) obj).path);
+        }
+        return false;
+    }
+}

Reply via email to