felix-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rickh...@apache.org
Subject svn commit: r380806 [1/2] - in /incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix: framework/ framework/cache/ framework/searchpolicy/ framework/util/ shell/impl/
Date Fri, 24 Feb 2006 20:09:30 GMT
Author: rickhall
Date: Fri Feb 24 12:09:28 2006
New Revision: 380806

URL: http://svn.apache.org/viewcvs?rev=380806&view=rev
Log:
Modified the Felix bundle cache to support installing bundles by referenced
JAR files as well as exploded directories. These changes percolated into
various other areaas of the impl which are also changed here.

Added:
    incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/BundleRevision.java   (with props)
    incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DefaultBundleArchive.java   (with props)
    incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java   (with props)
    incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/JarRevision.java   (with props)
    incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/SystemBundleArchive.java
      - copied, changed from r377666, incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/SystemBundleArchive.java
Removed:
    incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/SystemBundleArchive.java
    incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java
    incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java
Modified:
    incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/BundleInfo.java
    incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/Felix.java
    incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/SystemBundle.java
    incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DefaultBundleCache.java
    incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/searchpolicy/R4Library.java
    incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/util/FelixConstants.java
    incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/shell/impl/InstallCommandImpl.java

Modified: incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/BundleInfo.java
URL: http://svn.apache.org/viewcvs/incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/BundleInfo.java?rev=380806&r1=380805&r2=380806&view=diff
==============================================================================
--- incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/BundleInfo.java (original)
+++ incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/BundleInfo.java Fri Feb 24 12:09:28 2006
@@ -18,14 +18,14 @@
 
 import java.util.Map;
 
-import org.apache.felix.framework.cache.BundleArchive;
+import org.apache.felix.framework.cache.DefaultBundleArchive;
 import org.apache.felix.moduleloader.IModule;
 import org.osgi.framework.*;
 
 class BundleInfo
 {
     private Logger m_logger = null;
-    private BundleArchive m_archive = null;
+    private DefaultBundleArchive m_archive = null;
     private IModule[] m_modules = null;
     private int m_state = 0;
     private long m_modified = 0;
@@ -39,7 +39,7 @@
     private int m_lockCount = 0;
     private Thread m_lockThread = null;
 
-    protected BundleInfo(Logger logger, BundleArchive archive, IModule module)
+    protected BundleInfo(Logger logger, DefaultBundleArchive archive, IModule module)
         throws Exception
     {
         m_logger = logger;
@@ -56,7 +56,7 @@
      *  Returns the bundle archive associated with this bundle.
      * @return the bundle archive associated with this bundle.
     **/
-    public BundleArchive getArchive()
+    public DefaultBundleArchive getArchive()
     {
         return m_archive;
     }
@@ -121,7 +121,18 @@
 
     public long getBundleId()
     {
-        return m_archive.getId();
+        try
+        {
+            return m_archive.getId();
+        }
+        catch (Exception ex)
+        {
+            m_logger.log(
+                Logger.LOG_ERROR,
+                "Error getting the identifier from bundle archive.",
+                ex);
+            return -1;
+        }
     }
     
     public String getLocation()
@@ -134,7 +145,7 @@
         {
             m_logger.log(
                 Logger.LOG_ERROR,
-                "Error reading location from bundle archive.",
+                "Error getting location from bundle archive.",
                 ex);
             return null;
         }
@@ -177,7 +188,7 @@
         {
             // Return the header for the most recent bundle revision only,
             // since we shouldn't ever need access to older revisions.
-            return m_archive.getManifestHeader(m_archive.getRevisionCount() - 1);
+            return m_archive.getRevision(m_archive.getRevisionCount() - 1).getManifestHeader();
         }
         catch (Exception ex)
         {

Modified: incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/Felix.java
URL: http://svn.apache.org/viewcvs/incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/Felix.java?rev=380806&r1=380805&r2=380806&view=diff
==============================================================================
--- incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/Felix.java (original)
+++ incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/Felix.java Fri Feb 24 12:09:28 2006
@@ -80,7 +80,7 @@
         FelixConstants.FRAMEWORK_INACTIVE_STARTLEVEL;
 
     // Local file system cache.
-    private BundleCache m_cache = null;
+    private DefaultBundleCache m_cache = null;
 
     // Next available bundle identifier.
     private long m_nextId = 1L;
@@ -240,19 +240,9 @@
             }
         });
 
-        // Create default storage system from the specified cache class
-        // or use the default cache if no custom cache was specified.
-        String className = m_config.get(FelixConstants.CACHE_CLASS_PROP);
-        if (className == null)
-        {
-            className = DefaultBundleCache.class.getName();
-        }
-
         try
         {
-            Class clazz = Class.forName(className);
-            m_cache = (BundleCache) clazz.newInstance();
-            m_cache.initialize(m_config, m_logger);
+            m_cache = new DefaultBundleCache(m_config, m_logger);
         }
         catch (Exception ex)
         {
@@ -389,7 +379,7 @@
         }
         
         // Reload and cached bundles.
-        BundleArchive[] archives = null;
+        DefaultBundleArchive[] archives = null;
 
         // First get cached bundle identifiers.
         try
@@ -409,13 +399,13 @@
         // Now install all cached bundles.
         for (int i = 0; (archives != null) && (i < archives.length); i++)
         {
-            // Make sure our id generator is not going to overlap.
-            // TODO: This is not correct since it may lead to re-used
-            // ids, which is not okay according to OSGi.
-            m_nextId = Math.max(m_nextId, archives[i].getId() + 1);
-
             try
             {
+                // Make sure our id generator is not going to overlap.
+                // TODO: This is not correct since it may lead to re-used
+                // ids, which is not okay according to OSGi.
+                m_nextId = Math.max(m_nextId, archives[i].getId() + 1);
+
                 // It is possible that a bundle in the cache was previously
                 // uninstalled, but not completely deleted (perhaps because
                 // of a crash or a locked file), so if we see an archive
@@ -447,7 +437,7 @@
                 {
                     m_logger.log(
                         Logger.LOG_ERROR,
-                        "Unable to re-install bundle " + archives[i].getId(),
+                        "Unable to re-install cached bundle.",
                         ex);
                 }
                 // TODO: Perhaps we should remove the cached bundle?
@@ -1086,7 +1076,7 @@
             }
         }
         // Strip leading '/' if present.
-        if (path.charAt(0) == '/')
+        if ((path.length() > 0) && (path.charAt(0) == '/'))
         {
             path = path.substring(1);
         }
@@ -1534,34 +1524,11 @@
 
             try
             {
-                // Get the URL input stream if necessary.
-                if (is == null)
-                {
-                    // Do it the manual way to have a chance to 
-                    // set request properties such as proxy auth.
-                    URL url = new URL(updateLocation);
-                    URLConnection conn = url.openConnection(); 
-
-                    // Support for http proxy authentication.
-                    String auth = System.getProperty("http.proxyAuth");
-                    if ((auth != null) && (auth.length() > 0))
-                    {
-                        if ("http".equals(url.getProtocol()) ||
-                            "https".equals(url.getProtocol()))
-                        {
-                            String base64 = Util.base64Encode(auth);
-                            conn.setRequestProperty(
-                                "Proxy-Authorization", "Basic " + base64);
-                        }
-                    }
-                    is = conn.getInputStream();
-                }
-
                 // Get the bundle's archive.
-                BundleArchive archive = m_cache.getArchive(info.getBundleId());
+                DefaultBundleArchive archive = m_cache.getArchive(info.getBundleId());
                 // Update the bundle; this operation will increase
                 // the revision count for the bundle.
-                m_cache.update(archive, is);
+                archive.revise(updateLocation, is);
                 // Create a module for the new revision; the revision is
                 // base zero, so subtract one from the revision count to
                 // get the revision of the new update.
@@ -1594,7 +1561,7 @@
             // This will not start the bundle if it was not previously
             // active.
             startBundle(bundle, false);
-    
+
             // If update failed, rethrow exception.
             if (rethrow != null)
             {
@@ -1860,7 +1827,7 @@
         {
             AccessController.checkPermission(m_adminPerm);
         }
-    
+
         BundleImpl bundle = null;
 
         // Acquire an install lock.
@@ -1894,30 +1861,8 @@
 
                 try
                 {
-                    // Get the URL input stream if necessary.
-                    if (is == null)
-                    {
-                        // Do it the manual way to have a chance to 
-                        // set request properties such as proxy auth.
-                        URL url = new URL(location);
-                        URLConnection conn = url.openConnection(); 
-
-                        // Support for http proxy authentication.
-                        String auth = System.getProperty("http.proxyAuth");
-                        if ((auth != null) && (auth.length() > 0))
-                        {
-                            if ("http".equals(url.getProtocol()) ||
-                                "https".equals(url.getProtocol()))
-                            {
-                                String base64 = Util.base64Encode(auth);
-                                conn.setRequestProperty(
-                                    "Proxy-Authorization", "Basic " + base64);
-                            }
-                        }
-                        is = conn.getInputStream();
-                    }
                     // Add the bundle to the cache.
-                    m_cache.create(id, location, is);
+                    m_cache.create(id, location);
                 }
                 catch (Exception ex)
                 {
@@ -1949,7 +1894,7 @@
                 {
                     if (m_cache.getArchive(id).getRevisionCount() > 1)
                     {
-                        m_cache.purge(m_cache.getArchive(id));
+                        m_cache.getArchive(id).purge();
                     }
                 }
                 catch (Exception ex)
@@ -1962,7 +1907,7 @@
 
             try
             {
-                BundleArchive archive = m_cache.getArchive(id);
+                DefaultBundleArchive archive = m_cache.getArchive(id);
                 bundle = new BundleImpl(this, createBundleInfo(archive));
             }
             catch (Exception ex)
@@ -2884,7 +2829,7 @@
     // Miscellaneous private methods.
     //
 
-    private BundleInfo createBundleInfo(BundleArchive archive)
+    private BundleInfo createBundleInfo(DefaultBundleArchive archive)
         throws Exception
     {
         // Get the bundle manifest.
@@ -2893,7 +2838,7 @@
         {
             // Although there should only ever be one revision at this
             // point, get the header for the current revision to be safe.
-            headerMap = archive.getManifestHeader(archive.getRevisionCount() - 1);
+            headerMap = archive.getRevision(archive.getRevisionCount() - 1).getManifestHeader();
         }
         catch (Exception ex)
         {
@@ -3142,8 +3087,8 @@
         // Create the content loader associated with the module archive.
         IContentLoader contentLoader = new ContentLoaderImpl(
                 m_logger,
-                m_cache.getArchive(id).getContent(revision),
-                m_cache.getArchive(id).getContentPath(revision));
+                m_cache.getArchive(id).getRevision(revision).getContent(),
+                m_cache.getArchive(id).getRevision(revision).getContentPath());
         // Set the content loader's search policy.
         contentLoader.setSearchPolicy(
                 new R4SearchPolicy(m_policyCore, module));
@@ -3190,11 +3135,11 @@
         if (activator == null)
         {
             // Get the associated bundle archive.
-            BundleArchive ba = m_cache.getArchive(info.getBundleId());
+            DefaultBundleArchive ba = m_cache.getArchive(info.getBundleId());
             // Get the manifest from the current revision; revision is
             // base zero so subtract one from the count to get the
             // current revision.
-            Map headerMap = ba.getManifestHeader(ba.getRevisionCount() - 1);
+            Map headerMap = ba.getRevision(ba.getRevisionCount() - 1).getManifestHeader();
             // Get the activator class attribute.
             String className = (String) headerMap.get(Constants.BUNDLE_ACTIVATOR);
             // Try to instantiate activator class if present.
@@ -3241,7 +3186,7 @@
             }
 
             // Purge all bundle revisions, but the current one.
-            m_cache.purge(m_cache.getArchive(info.getBundleId()));
+            m_cache.getArchive(info.getBundleId()).purge();
         }
         finally
         {

Modified: incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/SystemBundle.java
URL: http://svn.apache.org/viewcvs/incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/SystemBundle.java?rev=380806&r1=380805&r2=380806&view=diff
==============================================================================
--- incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/SystemBundle.java (original)
+++ incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/SystemBundle.java Fri Feb 24 12:09:28 2006
@@ -21,6 +21,7 @@
 import java.security.PrivilegedAction;
 import java.util.*;
 
+import org.apache.felix.framework.cache.SystemBundleArchive;
 import org.apache.felix.framework.searchpolicy.*;
 import org.apache.felix.framework.util.FelixConstants;
 import org.apache.felix.framework.util.StringMap;

Added: incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/BundleRevision.java
URL: http://svn.apache.org/viewcvs/incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/BundleRevision.java?rev=380806&view=auto
==============================================================================
--- incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/BundleRevision.java (added)
+++ incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/BundleRevision.java Fri Feb 24 12:09:28 2006
@@ -0,0 +1,166 @@
+/*
+ *   Copyright 2006 The Apache Software Foundation
+ *
+ *   Licensed 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.framework.cache;
+
+import java.io.File;
+import java.util.Map;
+
+import org.apache.felix.framework.Logger;
+import org.apache.felix.moduleloader.IContent;
+
+/**
+ * <p>
+ * This class implements an abstract revision of a bundle archive. A revision
+ * is an abstraction of a bundle's actual content and is associated with a
+ * parent bundle archive. A bundle archive may have multiple revisions assocaited
+ * with it at one time, since updating a bundle results in a new version of the
+ * bundle's content until the bundle is refreshed. Upon a refresh, then old
+ * revisions are then purged. This abstract class is the base class for all
+ * concrete types of revisions, such as ones for a JAR file or directories. All
+ * revisions are assigned a root directory into which all of their state should
+ * be stored, if necessary. Clean up of this directory is the responsibility
+ * of the parent bundle archive and not of the revision itself.
+ * </p>
+ * @see org.apache.felix.framework.cache.BundleCache
+ * @see org.apache.felix.framework.cache.BundleArchive
+**/
+public abstract class BundleRevision
+{
+    private Logger m_logger;
+    private File m_revisionRootDir = null;
+    private String m_location = null;
+
+    /**
+     * <p>
+     * This constructor is only used by the system bundle archive.
+     * </p>
+    **/
+    BundleRevision()
+    {
+    }
+
+    /**
+     * <p>
+     * This class is abstract and cannot be created. It represents a revision
+     * of a bundle, i.e., its content. A revision is associated with a particular
+     * location string, which is typically in URL format. Subclasses of this
+     * class provide particular functionality, such as a revision in the form
+     * of a JAR file or a directory. Each revision subclass is expected to use
+     * the root directory associated with the abstract revision instance to
+     * store any state; this will ensure that resources used by the revision are
+     * properly freed when the revision is no longer needed.
+     * </p>
+     * @param logger a logger for use by the revision.
+     * @param revisionRootDir the root directory to be used by the revision
+     *        subclass for storing any state.
+     * @param location the location string associated with the revision.
+     * @throws Exception if any errors occur.
+    **/
+    public BundleRevision(Logger logger, File revisionRootDir, String location)
+        throws Exception
+    {
+        m_logger = logger;
+        m_revisionRootDir = revisionRootDir;
+        m_location = location;
+    }
+
+
+    /**
+     * <p>
+     * Returns the logger for this revision.
+     * <p>
+     * @return the logger instance for this revision.
+    **/
+    public Logger getLogger()
+    {
+        return m_logger;
+    }
+
+    /**
+     * <p>
+     * Returns the root directory for this revision.
+     * </p>
+     * @return the root directory for this revision.
+    **/
+    public File getRevisionRootDir()
+    {
+        return m_revisionRootDir;
+    }
+
+    /**
+     * <p>
+     * Returns the location string this revision.
+     * </p>
+     * @return the location string for this revision.
+    **/
+    public String getLocation()
+    {
+        return m_location;
+    }
+
+    /**
+     * <p>
+     * Returns the main attributes of the JAR file manifest header of the
+     * revision. The returned map is case insensitive.
+     * </p>
+     * @return the case-insensitive JAR file manifest header of the revision.
+     * @throws java.lang.Exception if any error occurs.
+    **/
+    public abstract Map getManifestHeader() throws Exception;
+
+    /**
+     * <p>
+     * Returns a content object that is associated with the revision.
+     * </p>
+     * @return a content object that is associated with the revision.
+     * @throws java.lang.Exception if any error occurs.
+    **/
+    public abstract IContent getContent() throws Exception;
+
+    /**
+     * <p>
+     * Returns an array of content objects that are associated with the
+     * specified revision's bundle class path.
+     * </p>
+     * @return an array of content objects for the revision's bundle class path.
+     * @throws java.lang.Exception if any error occurs.
+    **/
+    public abstract IContent[] getContentPath() throws Exception;
+
+    /**
+     * <p>
+     * Returns the absolute file path for the specified native library of the
+     * revision.
+     * </p>
+     * @param libName the name of the library.
+     * @return a <tt>String</tt> that contains the absolute path name to
+     *         the requested native library of the revision.
+     * @throws java.lang.Exception if any error occurs.
+    **/
+    public abstract String findLibrary(String libName) throws Exception;
+
+    /**
+     * <p>
+     * This method is called when the revision is no longer needed. The directory
+     * associated with the revision will automatically be removed for each
+     * revision, so this method only needs to be concerned with other issues,
+     * such as open files.
+     * </p>
+     * @throws Exception if any error occurs.
+    **/
+    public abstract void dispose() throws Exception;
+}
\ No newline at end of file

Propchange: incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/BundleRevision.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DefaultBundleArchive.java
URL: http://svn.apache.org/viewcvs/incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DefaultBundleArchive.java?rev=380806&view=auto
==============================================================================
--- incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DefaultBundleArchive.java (added)
+++ incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DefaultBundleArchive.java Fri Feb 24 12:09:28 2006
@@ -0,0 +1,961 @@
+/*
+ *   Copyright 2006 The Apache Software Foundation
+ *
+ *   Licensed 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.framework.cache;
+
+import java.io.*;
+
+import org.apache.felix.framework.Logger;
+import org.apache.felix.framework.util.ObjectInputStreamX;
+import org.apache.felix.moduleloader.IModule;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+
+/**
+ * <p>
+ * This class is a logical abstraction for a bundle archive. This class,
+ * combined with <tt>BundleCache</tt> and concrete <tt>BundleRevision</tt>
+ * subclasses, implement the bundle cache for Felix. The bundle archive
+ * abstracts the actual bundle content into revisions and the revisions
+ * provide access to the actual bundle content. When a bundle is
+ * installed it has one revision associated with its content. Updating a
+ * bundle adds another revision for the updated content. Any number of
+ * revisions can be associated with a bundle archive. When the bundle
+ * (or framework) is refreshed, then all old revisions are purged and only
+ * the most recent revision is maintained.
+ * </p>
+ * <p>
+ * The content associated with a revision can come in many forms, such as
+ * a standard JAR file or an exploded bundle directory. The bundle archive
+ * is responsible for creating all revision instances during invocations
+ * of the <tt>revise()</tt> method call. Internally, it determines the
+ * concrete type of revision type by examining the location string as an
+ * URL. Currently, it supports standard JAR files, referenced JAR files,
+ * and referenced directories. Examples of each type of URL are, respectively:
+ * </p>
+ * <ul>
+ *   <li><tt>http://www.foo.com/bundle.jar</tt></li>
+ *   <li><tt>reference:file:/foo/bundle.jar</tt></li>
+ *   <li><tt>reference:file:/foo/bundle/</tt></li>
+ * </ul>
+ * <p>
+ * The "<tt>reference:</tt>" notation signifies that the resource should be
+ * used "in place", meaning that they will not be copied. For referenced JAR
+ * files, some resources may still be copied, such as embedded JAR files or
+ * native libraries, but for referenced exploded bundle directories, nothing
+ * will be copied. Currently, reference URLs can only refer to "file:" targets.
+ * </p>
+ * @see org.apache.felix.framework.cache.BundleCache
+ * @see org.apache.felix.framework.cache.BundleRevision
+**/
+public class DefaultBundleArchive
+{
+    public static final transient String FILE_PROTOCOL = "file:";
+    public static final transient String REFERENCE_PROTOCOL = "reference:";
+    public static final transient String INPUTSTREAM_PROTOCOL = "inputstream:";
+
+    private static final transient String BUNDLE_ID_FILE = "bundle.id";
+    private static final transient String BUNDLE_LOCATION_FILE = "bundle.location";
+    private static final transient String CURRENT_LOCATION_FILE = "current.location";
+    private static final transient String BUNDLE_STATE_FILE = "bundle.state";
+    private static final transient String BUNDLE_START_LEVEL_FILE = "bundle.startlevel";
+    private static final transient String REFRESH_COUNTER_FILE = "refresh.counter";
+    private static final transient String BUNDLE_ACTIVATOR_FILE = "bundle.activator";
+    private static final transient String REVISION_DIRECTORY = "version";
+    private static final transient String DATA_DIRECTORY = "data";
+    private static final transient String ACTIVE_STATE = "active";
+    private static final transient String INSTALLED_STATE = "installed";
+    private static final transient String UNINSTALLED_STATE = "uninstalled";
+
+    private Logger m_logger = null;
+    private long m_id = -1;
+    private File m_archiveRootDir = null;
+    private String m_originalLocation = null;
+    private String m_currentLocation = null;
+    private int m_persistentState = -1;
+    private int m_startLevel = -1;
+    private BundleRevision[] m_revisions = null;
+
+    private long m_refreshCount = -1;
+
+    /**
+     * <p>
+     * This constructor is only used by the system bundle archive implementation
+     * because it is special an is not really an archive.
+     * </p>
+    **/
+    DefaultBundleArchive()
+    {
+    }
+
+    /**
+     * <p>
+     * This constructor is used for creating new archives when a bundle is
+     * installed into the framework. Each archive receives a logger, a root
+     * directory, its associated bundle identifier, and the associated bundle
+     * location string. The root directory is where any required state can be
+     * stored.
+     * </p>
+     * @param logger the logger to be used by the archive.
+     * @param archiveRootDir the archive root directory for storing state.
+     * @param id the bundle identifier associated with the archive.
+     * @param location the bundle location string associated with the archive.
+     * @throws Exception if any error occurs.
+    **/
+    public DefaultBundleArchive(
+        Logger logger, File archiveRootDir, long id, String location)    
+        throws Exception
+    {
+        m_logger = logger;
+        m_archiveRootDir = archiveRootDir;
+        m_id = id;
+        if (m_id <= 0)
+        {
+            throw new IllegalArgumentException(
+                "Bundle ID cannot be less than or equal to zero.");
+        }
+        m_originalLocation = location;
+
+        // Save state.
+        initialize();
+
+        // Add a revision for the content.
+        revise(getCurrentLocation(), null);
+    }
+
+    /**
+     * <p>
+     * This constructor is called when an archive for a bundle is being
+     * reconstructed when the framework is restarted. Each archive receives
+     * a logger, a root directory, and its associated bundle identifier.
+     * The root directory is where any required state can be stored.
+     * </p>
+     * @param logger the logger to be used by the archive.
+     * @param archiveRootDir the archive root directory for storing state.
+     * @param id the bundle identifier associated with the archive.
+     * @throws Exception if any error occurs.
+    **/
+    public DefaultBundleArchive(Logger logger, File archiveRootDir)    
+        throws Exception
+    {
+        m_logger = logger;
+        m_archiveRootDir = archiveRootDir;
+
+        // Add a revision for the content.
+        revise(getCurrentLocation(), null);
+    }
+
+    /**
+     * <p>
+     * Returns the bundle identifier associated with this archive.
+     * </p>
+     * @return the bundle identifier associated with this archive.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized long getId() throws Exception
+    {
+        if (m_id > 0)
+        {
+            return m_id;
+        }
+
+        // Read bundle location.
+        InputStream is = null;
+        BufferedReader br = null;
+        try
+        {
+            is = DefaultBundleCache.getSecureAction()
+                .getFileInputStream(new File(m_archiveRootDir, BUNDLE_ID_FILE));
+            br = new BufferedReader(new InputStreamReader(is));
+            m_id = Long.parseLong(br.readLine());
+        }
+        catch (FileNotFoundException ex)
+        {
+            // HACK: Get the bundle identifier from the archive root directory
+            // name, which is of the form "bundle<id>" where <id> is the bundle
+            // identifier numbers. This is a hack to deal with old archives that
+            // did not save their bundle identifier, but instead had it passed
+            // into them. Eventually, this can be removed.
+            m_id = Long.parseLong(
+                m_archiveRootDir.getName().substring(
+                    DefaultBundleCache.BUNDLE_DIR_PREFIX.length()));
+        }
+        finally
+        {
+            if (br != null) br.close();
+            if (is != null) is.close();
+        }
+
+        return m_id;
+    }
+
+    /**
+     * <p>
+     * Returns the location string associated with this archive.
+     * </p>
+     * @return the location string associated with this archive.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized String getLocation() throws Exception
+    {
+        if (m_originalLocation != null)
+        {
+            return m_originalLocation;
+        }
+
+        // Read bundle location.
+        InputStream is = null;
+        BufferedReader br = null;
+        try
+        {
+            is = DefaultBundleCache.getSecureAction()
+                .getFileInputStream(new File(m_archiveRootDir, BUNDLE_LOCATION_FILE));
+            br = new BufferedReader(new InputStreamReader(is));
+            m_originalLocation = br.readLine();
+            return m_originalLocation;
+        }
+        finally
+        {
+            if (br != null) br.close();
+            if (is != null) is.close();
+        }
+    }
+
+    /**
+     * <p>
+     * Returns the persistent state of this archive. The value returned is
+     * one of the following: <tt>Bundle.INSTALLED</tt>, <tt>Bundle.ACTIVE</tt>,
+     * or <tt>Bundle.UNINSTALLED</tt>.
+     * </p>
+     * @return the persistent state of this archive.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized int getPersistentState() throws Exception
+    {
+        if (m_persistentState >= 0)
+        {
+            return m_persistentState;
+        }
+
+        // Get bundle state file.
+        File stateFile = new File(m_archiveRootDir, BUNDLE_STATE_FILE);
+
+        // If the state file doesn't exist, then
+        // assume the bundle was installed.
+        if (!DefaultBundleCache.getSecureAction().fileExists(stateFile))
+        {
+            return Bundle.INSTALLED;
+        }
+
+        // Read the bundle state.
+        InputStream is = null;
+        BufferedReader br = null;
+        try
+        {
+            is = DefaultBundleCache.getSecureAction()
+                .getFileInputStream(stateFile);
+            br = new BufferedReader(new InputStreamReader(is));
+            String s = br.readLine();
+            if (s.equals(ACTIVE_STATE))
+            {
+                m_persistentState = Bundle.ACTIVE;
+            }
+            else if (s.equals(UNINSTALLED_STATE))
+            {
+                m_persistentState = Bundle.UNINSTALLED;
+            }
+            else
+            {
+                m_persistentState = Bundle.INSTALLED;
+            }
+            return m_persistentState;
+        }
+        finally
+        {
+            if (br != null) br.close();
+            if (is != null) is.close();
+        }
+    }
+
+    /**
+     * <p>
+     * Sets the persistent state of this archive. The value is
+     * one of the following: <tt>Bundle.INSTALLED</tt>, <tt>Bundle.ACTIVE</tt>,
+     * or <tt>Bundle.UNINSTALLED</tt>.
+     * </p>
+     * @param state the persistent state value to set for this archive.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized void setPersistentState(int state) throws Exception
+    {
+        // Write the bundle state.
+        OutputStream os = null;
+        BufferedWriter bw= null;
+        try
+        {
+            os = DefaultBundleCache.getSecureAction()
+                .getFileOutputStream(new File(m_archiveRootDir, BUNDLE_STATE_FILE));
+            bw = new BufferedWriter(new OutputStreamWriter(os));
+            String s = null;
+            switch (state)
+            {
+                case Bundle.ACTIVE:
+                    s = ACTIVE_STATE;
+                    break;
+                case Bundle.UNINSTALLED:
+                    s = UNINSTALLED_STATE;
+                    break;
+                default:
+                    s = INSTALLED_STATE;
+                    break;
+            }
+            bw.write(s, 0, s.length());
+            m_persistentState = state;
+        }
+        catch (IOException ex)
+        {
+            m_logger.log(
+                Logger.LOG_ERROR,
+                getClass().getName() + ": Unable to record state - " + ex);
+            throw ex;
+        }
+        finally
+        {
+            if (bw != null) bw.close();
+            if (os != null) os.close();
+        }
+    }
+
+    /**
+     * <p>
+     * Returns the start level of this archive.
+     * </p>
+     * @return the start level of this archive.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized int getStartLevel() throws Exception
+    {
+        if (m_startLevel >= 0)
+        {
+            return m_startLevel;
+        }
+
+        // Get bundle start level file.
+        File levelFile = new File(m_archiveRootDir, BUNDLE_START_LEVEL_FILE);
+
+        // If the start level file doesn't exist, then
+        // return an error.
+        if (!DefaultBundleCache.getSecureAction().fileExists(levelFile))
+        {
+            return -1;
+        }
+
+        // Read the bundle start level.
+        InputStream is = null;
+        BufferedReader br= null;
+        try
+        {
+            is = DefaultBundleCache.getSecureAction()
+                .getFileInputStream(levelFile);
+            br = new BufferedReader(new InputStreamReader(is));
+            m_startLevel = Integer.parseInt(br.readLine());
+            return m_startLevel;
+        }
+        finally
+        {
+            if (br != null) br.close();
+            if (is != null) is.close();
+        }
+    }
+
+    /**
+     * <p>
+     * Sets the the start level of this archive this archive.
+     * </p>
+     * @param level the start level to set for this archive.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized void setStartLevel(int level) throws Exception
+    {
+        // Write the bundle start level.
+        OutputStream os = null;
+        BufferedWriter bw = null;
+        try
+        {
+            os = DefaultBundleCache.getSecureAction()
+                .getFileOutputStream(new File(m_archiveRootDir, BUNDLE_START_LEVEL_FILE));
+            bw = new BufferedWriter(new OutputStreamWriter(os));
+            String s = Integer.toString(level);
+            bw.write(s, 0, s.length());
+            m_startLevel = level;
+        }
+        catch (IOException ex)
+        {
+            m_logger.log(
+                Logger.LOG_ERROR,
+                getClass().getName() + ": Unable to record start level - " + ex);
+            throw ex;
+        }
+        finally
+        {
+            if (bw != null) bw.close();
+            if (os != null) os.close();
+        }
+    }
+
+    /**
+     * <p>
+     * Returns a <tt>File</tt> object corresponding to the data file
+     * of the relative path of the specified string.
+     * </p>
+     * @return a <tt>File</tt> object corresponding to the specified file name.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized File getDataFile(String fileName) throws Exception
+    {
+        // Do some sanity checking.
+        if ((fileName.length() > 0) && (fileName.charAt(0) == File.separatorChar))
+            throw new IllegalArgumentException("The data file path must be relative, not absolute.");
+        else if (fileName.indexOf("..") >= 0)
+            throw new IllegalArgumentException("The data file path cannot contain a reference to the \"..\" directory.");
+
+        // Get bundle data directory.
+        File dataDir = new File(m_archiveRootDir, DATA_DIRECTORY);
+        // Create the data directory if necessary.
+        if (DefaultBundleCache.getSecureAction().fileExists(dataDir))
+        {
+            if (!DefaultBundleCache.getSecureAction().mkdir(dataDir))
+            {
+                throw new IOException("Unable to create bundle data directory.");
+            }
+        }
+
+        // Return the data file.
+        return new File(dataDir, fileName);
+    }
+
+    /**
+     * <p>
+     * Returns the serialized activator for this archive. This is an
+     * extension to the OSGi specification.
+     * </p>
+     * @return the serialized activator for this archive.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized BundleActivator getActivator(IModule module)
+        throws Exception
+    {
+        // Get bundle activator file.
+        File activatorFile = new File(m_archiveRootDir, BUNDLE_ACTIVATOR_FILE);
+        // If the activator file doesn't exist, then
+        // assume there isn't one.
+        if (!DefaultBundleCache.getSecureAction().fileExists(activatorFile))
+        {
+            return null;
+        }
+
+        // Deserialize the activator object.
+        InputStream is = null;
+        ObjectInputStreamX ois = null;
+        try
+        {
+            is = DefaultBundleCache.getSecureAction()
+                .getFileInputStream(activatorFile);
+            ois = new ObjectInputStreamX(is, module);
+            Object o = ois.readObject();
+            return (BundleActivator) o;
+        }
+        catch (Exception ex)
+        {
+            m_logger.log(
+                Logger.LOG_ERROR,
+                getClass().getName() + ": Trying to deserialize - " + ex);
+        }
+        finally
+        {
+            if (ois != null) ois.close();
+            if (is != null) is.close();
+        }
+
+        return null;
+    }
+
+    /**
+     * <p>
+     * Serializes the activator for this archive.
+     * </p>
+     * @param obj the activator to serialize.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized void setActivator(Object obj) throws Exception
+    {
+        if (!(obj instanceof Serializable))
+        {
+            return;
+        }
+
+        // Serialize the activator object.
+        OutputStream os = null;
+        ObjectOutputStream oos = null;
+        try
+        {
+            os = DefaultBundleCache.getSecureAction()
+                .getFileOutputStream(new File(m_archiveRootDir, BUNDLE_ACTIVATOR_FILE));
+            oos = new ObjectOutputStream(os);
+            oos.writeObject(obj);
+        }
+        catch (IOException ex)
+        {
+            m_logger.log(
+                Logger.LOG_ERROR,
+                getClass().getName() + ": Unable to serialize activator - " + ex);
+            throw ex;
+        }
+        finally
+        {
+            if (oos != null) oos.close();
+            if (os != null) os.close();
+        }
+    }
+
+    /**
+     * <p>
+     * Returns the number of revisions available for this archive.
+     * </p>
+     * @return tthe number of revisions available for this archive.
+    **/
+    public synchronized int getRevisionCount()
+    {
+        return (m_revisions == null) ? 0 : m_revisions.length;
+    }
+
+    /**
+     * <p>
+     * Returns the revision object for the specified revision.
+     * </p>
+     * @return the revision object for the specified revision.
+    **/
+    public synchronized BundleRevision getRevision(int i)
+    {
+        if ((i >= 0) && (i < getRevisionCount()))
+        {
+            return m_revisions[i];
+        }
+        return null;
+    }
+
+    /**
+     * <p>
+     * This method adds a revision to the archive. The revision is created
+     * based on the specified location and/or input stream.
+     * </p>
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized void revise(String location, InputStream is)
+        throws Exception
+    {
+        // If we have an input stream, then we have to use it
+        // no matter what the update location is, so just ignore
+        // the update location and set the location to be input
+        // stream.
+        if (is != null)
+        {
+            location = "inputstream:";
+        }
+        BundleRevision revision = createRevisionFromLocation(location, is);
+        if (revision == null)
+        {
+            throw new Exception("Unable to revise archive.");
+        }
+
+        // Set the current revision location to match.
+        setCurrentLocation(location);
+
+        // Add new revision to revision array.
+        if (m_revisions == null)
+        {
+            m_revisions = new BundleRevision[] { revision };
+        }
+        else
+        {
+            BundleRevision[] tmp = new BundleRevision[m_revisions.length + 1];
+            System.arraycopy(m_revisions, 0, tmp, 0, m_revisions.length);
+            tmp[m_revisions.length] = revision;
+            m_revisions = tmp;
+        }
+    }
+
+    /**
+     * <p>
+     * This method removes all old revisions associated with the archive
+     * and keeps only the current revision.
+     * </p>
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized void purge() throws Exception
+    {
+        // Get the current refresh count.
+        long refreshCount = getRefreshCount();
+        // Get the current revision count.
+        int count = getRevisionCount();
+
+        // Dispose and delete all but the current revision.
+        File revisionDir = null;
+        for (int i = 0; i < count - 1; i++)
+        {
+            m_revisions[i].dispose();
+            revisionDir = new File(m_archiveRootDir, REVISION_DIRECTORY + refreshCount + "." + i);
+            if (DefaultBundleCache.getSecureAction().fileExists(revisionDir))
+            {
+                DefaultBundleCache.deleteDirectoryTree(revisionDir);
+            }
+        }
+
+        // We still need to dispose the current revision, but we
+        // don't want to delete it, because we want to rename it
+        // to the new refresh level.
+        m_revisions[count - 1].dispose();
+
+        // Increment the refresh count.
+        setRefreshCount(refreshCount + 1);
+
+        // Rename the current revision directory to be the zero revision
+        // of the new refresh level.
+        File currentDir = new File(m_archiveRootDir, REVISION_DIRECTORY + (refreshCount + 1) + ".0");
+        revisionDir = new File(m_archiveRootDir, REVISION_DIRECTORY + refreshCount + "." + (count - 1));
+        DefaultBundleCache.getSecureAction().renameFile(revisionDir, currentDir);
+
+        // Null the revision array since they are all invalid now.
+        m_revisions = null;
+        // Finally, recreate the revision for the current location.
+        BundleRevision revision = createRevisionFromLocation(getCurrentLocation(), null);
+        // Create new revision array.
+        m_revisions = new BundleRevision[] { revision };
+    }
+
+    /**
+     * <p>
+     * This method disposes removes the bundle archive directory.
+     * </p>
+     * @throws Exception if any error occurs.
+    **/
+    /* package */ void dispose() throws Exception
+    {
+        if (!DefaultBundleCache.deleteDirectoryTree(m_archiveRootDir))
+        {
+            m_logger.log(
+                Logger.LOG_ERROR,
+                getClass().getName()
+                    + ": Unable to delete archive directory - "
+                    + m_archiveRootDir);
+        }
+    }
+
+    /**
+     * <p>
+     * Initializes the bundle archive object by creating the archive
+     * root directory and saving the initial state.
+     * </p>
+     * @throws Exception if any error occurs.
+    **/
+    private void initialize() throws Exception
+    {
+        OutputStream os = null;
+        BufferedWriter bw = null;
+
+        try
+        {
+            // If the archive directory exists, then we don't
+            // need to initialize since it has already been done.
+            if (DefaultBundleCache.getSecureAction().fileExists(m_archiveRootDir))
+            {
+                return;
+            }
+
+            // Create archive directory, if it does not exist.
+            if (!DefaultBundleCache.getSecureAction().mkdir(m_archiveRootDir))
+            {
+                m_logger.log(
+                    Logger.LOG_ERROR,
+                    getClass().getName() + ": Unable to create archive directory.");
+                throw new IOException("Unable to create archive directory.");
+            }
+
+            // Save id.
+            os = DefaultBundleCache.getSecureAction()
+                .getFileOutputStream(new File(m_archiveRootDir, BUNDLE_ID_FILE));
+            bw = new BufferedWriter(new OutputStreamWriter(os));
+            bw.write(Long.toString(m_id), 0, Long.toString(m_id).length());
+            bw.close();
+            os.close();
+
+            // Save location string.
+            os = DefaultBundleCache.getSecureAction()
+                .getFileOutputStream(new File(m_archiveRootDir, BUNDLE_LOCATION_FILE));
+            bw = new BufferedWriter(new OutputStreamWriter(os));
+            bw.write(m_originalLocation, 0, m_originalLocation.length());
+        }
+        finally
+        {
+            if (bw != null) bw.close();
+            if (os != null) os.close();
+        }
+    }
+
+    /**
+     * <p>
+     * Returns the current location associated with the bundle archive,
+     * which is the last location from which the bundle was updated. It is
+     * necessary to keep track of this so it is possible to determine what
+     * kind of revision needs to be created when recreating revisions when
+     * the framework restarts.
+     * @return the last update location.
+     * @throws Exception if any error occurs.
+    **/
+    private String getCurrentLocation() throws Exception
+    {
+        if (m_currentLocation != null)
+        {
+            return m_currentLocation;
+        }
+
+        // Read current location.
+        InputStream is = null;
+        BufferedReader br = null;
+        try
+        {
+            is = DefaultBundleCache.getSecureAction()
+                .getFileInputStream(new File(m_archiveRootDir, CURRENT_LOCATION_FILE));
+            br = new BufferedReader(new InputStreamReader(is));
+            m_currentLocation = br.readLine();
+            return m_currentLocation;
+        }
+        catch (FileNotFoundException ex)
+        {
+            return getLocation();
+        }
+        finally
+        {
+            if (br != null) br.close();
+            if (is != null) is.close();
+        }
+    }
+
+    /**
+     * <p>
+     * Set the current location associated with the bundle archive,
+     * which is the last location from which the bundle was updated. It is
+     * necessary to keep track of this so it is possible to determine what
+     * kind of revision needs to be created when recreating revisions when
+     * the framework restarts.
+     * @throws Exception if any error occurs.
+    **/
+    private void setCurrentLocation(String location) throws Exception
+    {
+        // Save current location.
+        OutputStream os = null;
+        BufferedWriter bw = null;
+        try
+        {
+            os = DefaultBundleCache.getSecureAction()
+                .getFileOutputStream(new File(m_archiveRootDir, CURRENT_LOCATION_FILE));
+            bw = new BufferedWriter(new OutputStreamWriter(os));
+            bw.write(location, 0, location.length());
+            m_currentLocation = location;
+        }
+        finally
+        {
+            if (bw != null) bw.close();
+            if (os != null) os.close();
+        }
+    }
+
+    /**
+     * <p>
+     * Creates a revision based on the location string and/or input stream.
+     * </p>
+     * @return the location string associated with this archive.
+    **/
+    private BundleRevision createRevisionFromLocation(String location, InputStream is)
+        throws Exception
+    {
+        // The revision directory is name using the refresh count and
+        // the revision count. The revision count is obvious, but the
+        // refresh count is less obvious. This is necessary due to how
+        // native libraries are handled in Java; needless to say, every
+        // time a bundle is refreshed we must change the name of its
+        // native libraries so that we can reload them. Thus, we use the
+        // refresh counter as a way to change the name of the revision
+        // directory to give native libraries new absolute names.
+        File revisionRootDir = new File(m_archiveRootDir,
+            REVISION_DIRECTORY + getRefreshCount() + "." + getRevisionCount());
+
+        try
+        {
+            // Check if the location string represents a reference URL.
+            if ((location != null) && location.startsWith(REFERENCE_PROTOCOL))
+            {
+                // Reference URLs only support the file protocol.
+                location = location.substring(REFERENCE_PROTOCOL.length());
+                if (!location.startsWith(FILE_PROTOCOL))
+                {
+                    throw new IOException("Reference URLs can only be files: " + location);
+                }
+
+                // Make sure the referenced file exists.
+                File file = new File(location.substring(FILE_PROTOCOL.length()));
+                if (!DefaultBundleCache.getSecureAction().fileExists(file))
+                {
+                    throw new IOException("Referenced file does not exist: " + file);
+                }
+
+                // If the referenced file is a directory, then create a directory
+                // revision; otherwise, create a JAR revision with the reference
+                // flag set to true.
+                if (DefaultBundleCache.getSecureAction().isFileDirectory(file))
+                {
+                    return new DirectoryRevision(m_logger, revisionRootDir, location);
+                }
+                else
+                {
+                    return new JarRevision(m_logger, revisionRootDir, location, true);
+                }
+            }
+            else if (location.startsWith(INPUTSTREAM_PROTOCOL))
+            {
+                // Assume all input streams point to JAR files.
+                return new JarRevision(m_logger, revisionRootDir, location, false, is);
+            }
+            else
+            {
+                // Anything else is assumed to be a URL to a JAR file.
+                return new JarRevision(m_logger, revisionRootDir, location, false);
+            }
+        }
+        catch (Exception ex)
+        {
+            if (DefaultBundleCache.getSecureAction().fileExists(revisionRootDir))
+            {
+                if (!DefaultBundleCache.deleteDirectoryTree(revisionRootDir))
+                {
+                    m_logger.log(
+                        Logger.LOG_ERROR,
+                        getClass().getName()
+                            + ": Unable to delete revision directory - "
+                            + revisionRootDir);
+                }
+            }
+            throw ex;
+        }
+    }
+
+    /**
+     * This utility method is used to retrieve the current refresh
+     * counter value for the bundle. This value is used when generating
+     * the bundle revision directory name where native libraries are extracted.
+     * This is necessary because Sun's JVM requires a one-to-one mapping
+     * between native libraries and class loaders where the native library
+     * is uniquely identified by its absolute path in the file system. This
+     * constraint creates a problem when a bundle is refreshed, because it
+     * gets a new class loader. Using the refresh counter to generate the name
+     * of the bundle revision directory resolves this problem because each time
+     * bundle is refresh, the native library will have a unique name.
+     * As a result of the unique name, the JVM will then reload the
+     * native library without a problem.
+    **/
+    private long getRefreshCount() throws Exception
+    {
+        // If we have already read the refresh counter file,
+        // then just return the result.
+        if (m_refreshCount >= 0)
+        {
+            return m_refreshCount;
+        }
+
+        // Get refresh counter file.
+        File counterFile = new File(m_archiveRootDir, REFRESH_COUNTER_FILE);
+
+        // If the refresh counter file doesn't exist, then
+        // assume the counter is at zero.
+        if (!DefaultBundleCache.getSecureAction().fileExists(counterFile))
+        {
+            return 0;
+        }
+
+        // Read the bundle refresh counter.
+        InputStream is = null;
+        BufferedReader br = null;
+        try
+        {
+            is = DefaultBundleCache.getSecureAction()
+                .getFileInputStream(counterFile);
+            br = new BufferedReader(new InputStreamReader(is));
+            long counter = Long.parseLong(br.readLine());
+            return counter;
+        }
+        finally
+        {
+            if (br != null) br.close();
+            if (is != null) is.close();
+        }
+    }
+
+    /**
+     * This utility method is used to retrieve the current refresh
+     * counter value for the bundle. This value is used when generating
+     * the bundle revision directory name where native libraries are extracted.
+     * This is necessary because Sun's JVM requires a one-to-one mapping
+     * between native libraries and class loaders where the native library
+     * is uniquely identified by its absolute path in the file system. This
+     * constraint creates a problem when a bundle is refreshed, because it
+     * gets a new class loader. Using the refresh counter to generate the name
+     * of the bundle revision directory resolves this problem because each time
+     * bundle is refresh, the native library will have a unique name.
+     * As a result of the unique name, the JVM will then reload the
+     * native library without a problem.
+    **/
+    private void setRefreshCount(long counter)
+        throws Exception
+    {
+        // Get refresh counter file.
+        File counterFile = new File(m_archiveRootDir, REFRESH_COUNTER_FILE);
+
+        // Write the refresh counter.
+        OutputStream os = null;
+        BufferedWriter bw = null;
+        try
+        {
+            os = DefaultBundleCache.getSecureAction()
+                .getFileOutputStream(counterFile);
+            bw = new BufferedWriter(new OutputStreamWriter(os));
+            String s = Long.toString(counter);
+            bw.write(s, 0, s.length());
+            m_refreshCount = counter;
+        }
+        catch (IOException ex)
+        {
+            m_logger.log(
+                Logger.LOG_ERROR,
+                getClass().getName() + ": Unable to write refresh counter: " + ex);
+            throw ex;
+        }
+        finally
+        {
+            if (bw != null) bw.close();
+            if (os != null) os.close();
+        }
+    }
+}
\ No newline at end of file

Propchange: incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DefaultBundleArchive.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DefaultBundleCache.java
URL: http://svn.apache.org/viewcvs/incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DefaultBundleCache.java?rev=380806&r1=380805&r2=380806&view=diff
==============================================================================
--- incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DefaultBundleCache.java (original)
+++ incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DefaultBundleCache.java Fri Feb 24 12:09:28 2006
@@ -1,5 +1,5 @@
 /*
- *   Copyright 2005 The Apache Software Foundation
+ *   Copyright 2006 The Apache Software Foundation
  *
  *   Licensed under the Apache License, Version 2.0 (the "License");
  *   you may not use this file except in compliance with the License.
@@ -17,16 +17,20 @@
 package org.apache.felix.framework.cache;
 
 import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
 
 import org.apache.felix.framework.Logger;
 import org.apache.felix.framework.util.PropertyResolver;
+import org.apache.felix.framework.util.SecureAction;
 
 /**
  * <p>
- * This class, combined with <tt>DefaultBundleArchive</tt>, implements the
- * default file system-based bundle cache for Felix. It is possible to
- * configure the default behavior of this class by passing properties into
- * Felix constructor. The configuration properties for this class are:
+ * This class, combined with <tt>BundleArchive</tt>, and concrete
+ * <tt>BundleRevision</tt> subclasses, implement the Felix bundle cache.
+ * It is possible to configure the default behavior of this class by
+ * passing properties into Felix' constructor. The configuration properties
+ * for this class are:
  * </p>
  * <ul>
  *   <li><tt>felix.cache.bufsize</tt> - Sets the buffer size to be used by
@@ -65,7 +69,7 @@
  * </p>
  * @see org.apache.felix.framework.util.DefaultBundleArchive
 **/
-public class DefaultBundleCache implements BundleCache
+public class DefaultBundleCache
 {
     public static final String CACHE_BUFSIZE_PROP = "felix.cache.bufsize";
     public static final String CACHE_DIR_PROP = "felix.cache.dir";
@@ -79,19 +83,169 @@
     private PropertyResolver m_cfg = null;
     private Logger m_logger = null;
     private File m_profileDir = null;
-    private BundleArchive[] m_archives = null;
+    private DefaultBundleArchive[] m_archives = null;
 
-    public DefaultBundleCache()
-    {
-    }
+    private static SecureAction m_secureAction = new SecureAction();
 
-    public void initialize(PropertyResolver cfg, Logger logger) throws Exception
+    public DefaultBundleCache(PropertyResolver cfg, Logger logger)
+        throws Exception
     {
-        // Save Properties reference.
         m_cfg = cfg;
-        // Save LogService reference.
         m_logger = logger;
+        initialize();
+    }
+
+    /* package */ static SecureAction getSecureAction()
+    {
+        return m_secureAction;
+    }
+
+    public synchronized DefaultBundleArchive[] getArchives()
+        throws Exception
+    {
+        return m_archives;
+    }
+
+    public synchronized DefaultBundleArchive getArchive(long id)
+        throws Exception
+    {
+        for (int i = 0; i < m_archives.length; i++)
+        {
+            if (m_archives[i].getId() == id)
+            {
+                return m_archives[i];
+            }
+        }
+        return null;
+    }
+
+    public synchronized int getArchiveIndex(DefaultBundleArchive ba)
+    {
+        for (int i = 0; i < m_archives.length; i++)
+        {
+            if (m_archives[i] == ba)
+            {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    public synchronized DefaultBundleArchive create(long id, String location)
+        throws Exception
+    {
+        // Construct archive root directory.
+        File archiveRootDir =
+            new File(m_profileDir, BUNDLE_DIR_PREFIX + Long.toString(id));
+
+        try
+        {
+            // Create the archive and add it to the list of archives.
+            DefaultBundleArchive ba = new DefaultBundleArchive(m_logger, archiveRootDir, id, location);
+            DefaultBundleArchive[] tmp = new DefaultBundleArchive[m_archives.length + 1];
+            System.arraycopy(m_archives, 0, tmp, 0, m_archives.length);
+            tmp[m_archives.length] = ba;
+            m_archives = tmp;
+            return ba;
+        }
+        catch (Exception ex)
+        {
+            if (m_secureAction.fileExists(archiveRootDir))
+            {
+                if (!DefaultBundleCache.deleteDirectoryTree(archiveRootDir))
+                {
+                    m_logger.log(
+                        Logger.LOG_ERROR,
+                        getClass().getName()
+                            + ": Unable to delete the archive directory - "
+                            + archiveRootDir);
+                }
+            }
+            throw ex;
+        }
+    }
+
+    public synchronized void remove(DefaultBundleArchive ba)
+        throws Exception
+    {
+        // Remove the archive.
+        ba.dispose();
+        // Remove the archive from the cache.
+        int idx = getArchiveIndex(ba);
+        if (idx >= 0)
+        {
+            DefaultBundleArchive[] tmp =
+                new DefaultBundleArchive[m_archives.length - 1];
+            System.arraycopy(m_archives, 0, tmp, 0, idx);
+            if (idx < tmp.length)
+            {
+                System.arraycopy(m_archives, idx + 1, tmp, idx,
+                    tmp.length - idx);
+            }
+            m_archives = tmp;
+        }
+    }
+
+    //
+    // Static file-related utility methods.
+    //
+
+    /**
+     * This method copies an input stream to the specified file.
+     * <p>
+     * Security: This method must be called from within a <tt>doPrivileged()</tt>
+     * block since it accesses the disk.
+     * @param is the input stream to copy.
+     * @param outputFile the file to which the input stream should be copied.
+    **/
+    protected static void copyStreamToFile(InputStream is, File outputFile)
+        throws IOException
+    {
+        OutputStream os = null;
+
+        try
+        {
+            os = getSecureAction().getFileOutputStream(outputFile);
+            os = new BufferedOutputStream(os, BUFSIZE);
+            byte[] b = new byte[BUFSIZE];
+            int len = 0;
+            while ((len = is.read(b)) != -1)
+            {
+                os.write(b, 0, len);
+            }
+        }
+        finally
+        {
+            if (is != null) is.close();
+            if (os != null) os.close();
+        }
+    }
+
+    protected static boolean deleteDirectoryTree(File target)
+    {
+        if (!getSecureAction().fileExists(target))
+        {
+            return true;
+        }
+
+        if (getSecureAction().isFileDirectory(target))
+        {
+            File[] files = getSecureAction().listDirectory(target);
+            for (int i = 0; i < files.length; i++)
+            {
+                deleteDirectoryTree(files[i]);
+            }
+        }
+
+        return getSecureAction().deleteFile(target);
+    }
+
+    //
+    // Private methods.
+    //
 
+    private void initialize() throws Exception
+    {
         // Get buffer size value.
         try
         {
@@ -148,141 +302,44 @@
             m_profileDir = new File(cacheDirStr, profileName);
         }
 
-        // Create profile directory.
-        if (!m_profileDir.exists())
+        // Create profile directory, if it does not exist.
+        if (!getSecureAction().fileExists(m_profileDir))
         {
-            if (!m_profileDir.mkdirs())
+            if (!getSecureAction().mkdirs(m_profileDir))
             {
                 m_logger.log(
                     Logger.LOG_ERROR,
-                    "Unable to create directory: " + m_profileDir);
+                    getClass().getName() + ": Unable to create directory: "
+                        + m_profileDir);
                 throw new RuntimeException("Unable to create profile directory.");
             }
         }
 
         // Create the existing bundle archives in the profile directory,
         // if any exist.
-        File[] children = m_profileDir.listFiles();
-        int count = 0;
-        for (int i = 0; (children != null) && (i < children.length); i++)
-        {
-            // Count the legitimate bundle directories.
-            if (children[i].getName().startsWith(BUNDLE_DIR_PREFIX))
-            {
-                count++;
-            }
-        }
-        m_archives = new BundleArchive[count];
-        count = 0;
+        List archiveList = new ArrayList();
+        File[] children = getSecureAction().listDirectory(m_profileDir);
         for (int i = 0; (children != null) && (i < children.length); i++)
         {
             // Ignore directories that aren't bundle directories.
             if (children[i].getName().startsWith(BUNDLE_DIR_PREFIX))
             {
-                String id = children[i].getName().substring(BUNDLE_DIR_PREFIX.length());
-                m_archives[count] = new DefaultBundleArchive(
-                    m_logger, children[i], Long.parseLong(id));
-                count++;
-            }
-        }
-    }
-
-    public BundleArchive[] getArchives()
-        throws Exception
-    {
-        return m_archives;
-    }
-
-    public BundleArchive getArchive(long id)
-        throws Exception
-    {
-        for (int i = 0; i < m_archives.length; i++)
-        {
-            if (m_archives[i].getId() == id)
-            {
-                return m_archives[i];
-            }
-        }
-        return null;
-    }
-
-    public int getArchiveIndex(BundleArchive ba)
-    {
-        for (int i = 0; i < m_archives.length; i++)
-        {
-            if (m_archives[i] == ba)
-            {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    public BundleArchive create(long id, String location, InputStream is)
-        throws Exception
-    {
-        // Define new bundle's directory.
-        File bundleDir = new File(m_profileDir, "bundle" + id);
-
-        try
-        {
-            // Buffer the input stream.
-            is = new BufferedInputStream(is, DefaultBundleCache.BUFSIZE);
-            // Create an archive instance for the new bundle.
-            BundleArchive ba = new DefaultBundleArchive(
-                m_logger, bundleDir, id, location, is);
-            // Add the archive instance to the list of bundle archives.
-            BundleArchive[] bas = new BundleArchive[m_archives.length + 1];
-            System.arraycopy(m_archives, 0, bas, 0, m_archives.length);
-            bas[m_archives.length] = ba;
-            m_archives = bas;
-            return ba;
-        }
-        finally
-        {
-            if (is != null) is.close();
-        }
-    }
-
-    public void update(BundleArchive ba, InputStream is)
-        throws Exception
-    {
-        try
-        {
-            // Buffer the input stream.
-            is = new BufferedInputStream(is, DefaultBundleCache.BUFSIZE);
-            // Do the update.
-            ((DefaultBundleArchive) ba).update(is);
-        }
-        finally
-        {
-            if (is != null) is.close();
-        }
-    }
-
-    public void purge(BundleArchive ba)
-        throws Exception
-    {
-        ((DefaultBundleArchive) ba).purge();
-    }
-
-    public void remove(BundleArchive ba)
-        throws Exception
-    {
-        // Remove the archive itself.
-        ((DefaultBundleArchive) ba).remove();
-        // Remove the archive from the cache.
-        int idx = getArchiveIndex(ba);
-        if (idx >= 0)
-        {
-            BundleArchive[] bas = new BundleArchive[m_archives.length - 1];
-            System.arraycopy(m_archives, 0, bas, 0, idx);
-            if (idx < bas.length)
-            {
-                System.arraycopy(m_archives, idx + 1, bas, idx,
-                    bas.length - idx);
+                // Recreate the bundle archive.
+                try
+                {
+                    archiveList.add(
+                        new DefaultBundleArchive(m_logger, children[i]));
+                }
+                catch (Exception ex)
+                {
+                    // Log and ignore.
+                    m_logger.log(Logger.LOG_ERROR,
+                        getClass().getName() + ": Error creating archive.", ex);
+                }
             }
-            m_archives = bas;
         }
+        
+        m_archives = (DefaultBundleArchive[])
+            archiveList.toArray(new DefaultBundleArchive[archiveList.size()]);
     }
-}
+}
\ No newline at end of file

Added: incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java
URL: http://svn.apache.org/viewcvs/incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java?rev=380806&view=auto
==============================================================================
--- incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java (added)
+++ incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java Fri Feb 24 12:09:28 2006
@@ -0,0 +1,157 @@
+/*
+ *   Copyright 2006 The Apache Software Foundation
+ *
+ *   Licensed 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.framework.cache;
+
+import java.io.*;
+import java.util.Map;
+import java.util.jar.Manifest;
+
+import org.apache.felix.framework.Logger;
+import org.apache.felix.framework.util.*;
+import org.apache.felix.moduleloader.*;
+
+/**
+ * <p>
+ * This class implements a bundle archive revision for exploded bundle
+ * JAR files. It uses the specified location directory "in-place" to
+ * execute the bundle and does not copy the bundle content at all.
+ * </p>
+**/
+class DirectoryRevision extends BundleRevision
+{
+    private File m_refDir = null;
+    private Map m_header = null;
+
+    public DirectoryRevision(
+        Logger logger, File revisionRootDir, String location)    
+        throws Exception
+    {
+        super(logger, revisionRootDir, location);
+        m_refDir = new File(location.substring(
+            location.indexOf(DefaultBundleArchive.FILE_PROTOCOL)
+                + DefaultBundleArchive.FILE_PROTOCOL.length()));
+    }
+
+    public synchronized Map getManifestHeader()
+        throws Exception
+    {
+        if (m_header != null)
+        {
+            return m_header;
+        }
+
+        // Read the header file from the reference directory.
+        InputStream is = null;
+
+        try
+        {
+            // Open manifest file.
+            is = DefaultBundleCache.getSecureAction()
+                .getFileInputStream(new File(m_refDir, "META-INF/MANIFEST.MF"));
+            // Error if no jar file.
+            if (is == null)
+            {
+                throw new IOException("No manifest file found.");
+            }
+
+            // Get manifest.
+            Manifest mf = new Manifest(is);
+            // Create a case insensitive map of manifest attributes.
+            m_header = new StringMap(mf.getMainAttributes(), false);
+            return m_header;
+        }
+        finally
+        {
+            if (is != null) is.close();
+        }
+    }
+
+    public IContent getContent() throws Exception
+    {
+        return new DirectoryContent(m_refDir);
+    }
+
+    public synchronized IContent[] getContentPath() throws Exception
+    {
+        // Creating the content path entails examining the bundle's
+        // class path to determine whether the bundle JAR file itself
+        // is on the bundle's class path and then creating content
+        // objects for everything on the class path.
+    
+        // Get the bundle's manifest header.
+        Map map = getManifestHeader();
+    
+        // Find class path meta-data.
+        String classPath = (map == null)
+            ? null : (String) map.get(FelixConstants.BUNDLE_CLASSPATH);
+    
+        // Parse the class path into strings.
+        String[] classPathStrings = Util.parseDelimitedString(
+            classPath, FelixConstants.CLASS_PATH_SEPARATOR);
+    
+        if (classPathStrings == null)
+        {
+            classPathStrings = new String[0];
+        }
+    
+        // Create the bundles class path.
+        IContent self = new DirectoryContent(m_refDir);
+        IContent[] contentPath = new IContent[classPathStrings.length];
+        for (int i = 0; i < classPathStrings.length; i++)
+        {
+            if (classPathStrings[i].equals(FelixConstants.CLASS_PATH_DOT))
+            {
+                contentPath[i] = self;
+            }
+            else
+            {
+                // Determine if the class path entry is a file or directory.
+                File file = new File(m_refDir, classPathStrings[i]);
+                if (DefaultBundleCache.getSecureAction().isFileDirectory(file))
+                {
+                    contentPath[i] = new DirectoryContent(file);
+                }
+                else
+                {
+                    contentPath[i] = new JarContent(file);
+                }
+            }
+        }
+    
+        // If there is nothing on the class path, then include
+        // "." by default, as per the spec.
+        if (contentPath.length == 0)
+        {
+            contentPath = new IContent[] { self };
+        }
+    
+        return contentPath;
+    }
+
+// TODO: This will need to consider security.
+    public String findLibrary(String libName) throws Exception
+    {
+        return new File(m_refDir, libName).toString();
+    }
+
+    public void dispose() throws Exception
+    {
+        // Nothing to dispose of, since we don't maintain any state outside
+        // of the revision directory, which will be automatically deleted
+        // by the parent bundle archive.
+    }
+}
\ No newline at end of file

Propchange: incubator/felix/trunk/org.apache.felix.framework/src/main/java/org/apache/felix/framework/cache/DirectoryRevision.java
------------------------------------------------------------------------------
    svn:eol-style = native



Mime
View raw message