You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by ri...@apache.org on 2005/08/16 20:34:41 UTC

svn commit: r233031 [6/21] - in /incubator/oscar/trunk: ./ etc/ lib/ src/ src/org/ src/org/apache/ src/org/apache/osgi/ src/org/apache/osgi/bundle/ src/org/apache/osgi/bundle/bundlerepository/ src/org/apache/osgi/bundle/bundlerepository/kxmlsax/ src/or...

Added: incubator/oscar/trunk/src/org/apache/osgi/framework/Felix.java
URL: http://svn.apache.org/viewcvs/incubator/oscar/trunk/src/org/apache/osgi/framework/Felix.java?rev=233031&view=auto
==============================================================================
--- incubator/oscar/trunk/src/org/apache/osgi/framework/Felix.java (added)
+++ incubator/oscar/trunk/src/org/apache/osgi/framework/Felix.java Tue Aug 16 11:33:34 2005
@@ -0,0 +1,3675 @@
+/*
+ *   Copyright 2005 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.osgi.framework;
+
+import java.io.*;
+import java.net.*;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.util.*;
+
+import org.apache.osgi.framework.cache.*;
+import org.apache.osgi.framework.searchpolicy.*;
+import org.apache.osgi.framework.util.*;
+import org.apache.osgi.framework.util.Util;
+import org.apache.osgi.moduleloader.*;
+import org.apache.osgi.moduleloader.search.ResolveException;
+import org.apache.osgi.moduleloader.search.ResolveListener;
+import org.osgi.framework.*;
+import org.osgi.service.packageadmin.ExportedPackage;
+
+public class Felix
+{
+    // Logging related member variables.
+    private LogWrapper m_logger = new LogWrapper();
+    // Config properties.
+    private PropertyResolver m_config = new ConfigImpl();
+    // Configuration properties passed into constructor.
+    private MutablePropertyResolver m_configProps = null;
+
+    // MODULE MANAGER.
+    private ModuleManager m_mgr = null;
+
+    // Object used as a lock when calculating which bundles
+    // when performing an operation on one or more bundles.
+    private Object[] m_bundleLock = new Object[0];
+
+    // Maps a bundle location to a bundle location;
+    // used to reserve a location when installing a bundle.
+    private Map m_installRequestMap = null;
+    // This lock must be acquired to modify m_installRequestMap;
+    // to help avoid deadlock this lock as priority 1 and should
+    // be acquired before locks with lower priority.
+    private Object[] m_installRequestLock_Priority1 = new Object[0];
+
+    // Maps a bundle location to a bundle.
+    private HashMap m_installedBundleMap = null;
+    // This lock must be acquired to modify m_installedBundleMap;
+    // to help avoid deadlock this lock as priority 2 and should
+    // be acquired before locks with lower priority.
+    private Object[] m_installedBundleLock_Priority2 = new Object[0];
+
+    // An array of uninstalled bundles before a refresh occurs.
+    private BundleImpl[] m_uninstalledBundles = null;
+    // This lock must be acquired to modify m_uninstalledBundles;
+    // to help avoid deadlock this lock as priority 3 and should
+    // be acquired before locks with lower priority.
+    private Object[] m_uninstalledBundlesLock_Priority3 = new Object[0];
+
+    // Status flag for framework.
+    public static final int INITIAL_STATUS  = -1;
+    public static final int RUNNING_STATUS  = 0;
+    public static final int STARTING_STATUS = 1;
+    public static final int STOPPING_STATUS = 2;
+    private int m_frameworkStatus = INITIAL_STATUS;
+
+    // Framework's active start level.
+    private int m_activeStartLevel =
+        FelixConstants.FRAMEWORK_INACTIVE_STARTLEVEL;
+
+    // Local file system cache.
+    private BundleCache m_cache = null;
+
+    // Next available bundle identifier.
+    private long m_nextId = 1L;
+
+    // List of event listeners.
+    private FelixDispatchQueue m_dispatchQueue = null;
+    // Re-usable event dispatchers.
+    private Dispatcher m_frameworkDispatcher = null;
+    private Dispatcher m_bundleDispatcher = null;
+    private Dispatcher m_serviceDispatcher = null;
+
+    // Service registry.
+    private ServiceRegistry m_registry = null;
+
+    // Reusable admin permission object for all instances
+    // of the BundleImpl.
+    private static AdminPermission m_adminPerm = new AdminPermission();
+
+    /**
+     * <p>
+     * This method starts the framework instance; instances of the framework
+     * are dormant until this method is called. The caller may also provide
+     * <tt>MutablePropertyResolver</tt> implementations that the instance will
+     * use to obtain configuration or framework properties. Configuration
+     * properties are used internally by the framework and its extensions to alter
+     * its default behavior. Framework properties are used by bundles
+     * and are accessible from <tt>BundleContext.getProperty()</tt>.
+     * </p>
+     * <p>
+     * Configuration properties are the sole means to configure the framework's
+     * default behavior; the framework does not refer to any system properties for
+     * configuration information. If a <tt>MutablePropertyResolver</tt> is
+     * supplied to this method for configuration properties, then the framework will
+     * consult the <tt>MutablePropertyResolver</tt> instance for any and all
+     * configuration properties. It is possible to specify a <tt>null</tt>
+     * configuration property resolver, in which case the framework will use its
+     * default behavior in all cases. However, if the
+     * <a href="cache/DefaultBundleCache.html"><tt>DefaulBundleCache</tt></a>
+     * is used, then at a minimum a profile name or profile directory must
+     * be specified.
+     * </p>
+     * <p>
+     * The following configuration properties can be specified:
+     * </p>
+     * <ul>
+     *   <li><tt>felix.cache.class</tt> - The class name to be used when
+     *       creating an instance for the bundle cache; this class must
+     *       implement the <tt>BundleCache</tt> interface and have a default
+     *       constructor. By default, the framework will create an instance of
+     *       <tt>DefaultBundleCache</tt> for the bundle cache.
+     *   </li>
+     *   <li><tt>felix.auto.install.&lt;n&gt;</tt> - Space-delimited list of
+     *       bundles to automatically install into start level <tt>n</tt> when
+     *       the framework is started. Append a specific start level to this
+     *       property name to assign the bundles' start level
+     *       (e.g., <tt>felix.auto.install.2</tt>).
+     *   </li>
+     *   <li><tt>felix.auto.start.&lt;n&gt;</tt> - Space-delimited list of
+     *       bundles to automatically install and start into start level
+     *       <tt>n</tt> when the framework is started. Append a
+     *       specific start level to this property name to assign the
+     *       bundles' start level(e.g., <tt>felix.auto.start.2</tt>).
+     *   </li>
+     *   <li><tt>felix.startlevel.framework</tt> - The initial start level
+     *       of the framework once it starts execution; the default
+     *       value is 1.
+     *   </li>
+     *   <li><tt>felix.startlevel.bundle</tt> - The default start level for
+     *       newly installed bundles; the default value is 1.
+     *   </li>
+     *   <li><tt>felix.embedded.execution</tt> - Flag to indicate whether
+     *       the framework is embedded into a host application; the default value is
+     *       "<tt>false</tt>". If this flag is "<tt>true</tt>" then the framework
+     *       will not called <tt>System.exit()</tt> upon termination.
+     *   </li>
+     *   <li><tt>felix.strict.osgi</tt> - Flag to indicate whether the framework is
+     *       running in strict OSGi mode; the default value is "<tt>true</tt>".
+     *       If this flag is "<tt>false</tt>" it enables a non-OSGi-compliant
+     *       feature by persisting <tt>BundleActivator</tt>s that implement
+     *       <tt>Serializable</tt>. This feature is not recommended since
+     *       it is non-compliant.
+     *   </li>
+     * </ul>
+     * <p>
+     * Besides the above framework configuration properties, it is also
+     * possible to specify properties for the bundle cache. The available
+     * bundle cache properties depend on the cache implementation
+     * being used. For the properties of the default bundle cache, refer to the
+     * <a href="cache/DefaultBundleCache.html"><tt>DefaulBundleCache</tt></a>
+     * API documentation.
+     * </p>
+     * <p>
+     * Framework properties are somewhat misnamed, since they are not used by
+     * the framework, but by bundles via <tt>BundleContext.getProperty()</tt>.
+     * Please refer to bundle documentation of your specific bundle for any
+     * available properties.
+     * </p>
+     * <p>
+     * The <a href="Main.html"><tt>Main</tt></a> class implements some
+     * functionality for default property file handling, which makes it
+     * possible to specify configuration properties and framework properties
+     * in files that are automatically loaded when starting the framework. If you
+     * plan to create your own framework instance, you may be
+     * able to take advantage of the features it provides; refer to its
+     * class documentation for more information.
+     * </p>
+     * 
+     * @param configProps An object for obtaining configuration properties,
+     *        may be <tt>null</tt>.
+     * @param frameworkProps An object for obtaining framework properties,
+     *        may be <tt>null</tt>.
+     * @param activatorList A list of System Bundle activators.
+    **/
+    public synchronized void start(
+        MutablePropertyResolver configProps,
+        List activatorList)
+    {
+        if (m_frameworkStatus != INITIAL_STATUS)
+        {
+            throw new IllegalStateException("Invalid framework status: " + m_frameworkStatus);
+        }
+
+        // The framework is now in its startup sequence.
+        m_frameworkStatus = STARTING_STATUS;
+
+        // Initialize member variables.
+        m_mgr = null;
+        m_configProps = (configProps == null)
+            ? new MutablePropertyResolverImpl(new CaseInsensitiveMap()) : configProps;
+        m_activeStartLevel = FelixConstants.FRAMEWORK_INACTIVE_STARTLEVEL;
+        m_installRequestMap = new HashMap();
+        m_installedBundleMap = new HashMap();
+        m_uninstalledBundles = null;
+        m_cache = null;
+        m_nextId = 1L;
+        m_dispatchQueue = null;
+        m_registry = new ServiceRegistry(m_logger);
+
+        // Add a listener to the service registry; this is
+        // used to distribute service registry events to
+        // service listeners.
+        m_registry.addServiceListener(new ServiceListener() {
+            public void serviceChanged(ServiceEvent event)
+            {
+                fireServiceEvent(event);
+            }
+        });
+
+        // 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);
+        }
+        catch (Exception ex)
+        {
+            System.err.println("Error creating bundle cache:");
+            ex.printStackTrace();
+
+            // Only shutdown the JVM if the framework is running stand-alone.
+            String embedded = m_config.get(
+                FelixConstants.EMBEDDED_EXECUTION_PROP);
+            boolean isEmbedded = (embedded == null)
+                ? false : embedded.equals("true");
+            if (!isEmbedded)
+            {
+                System.exit(-1);
+            }
+            else
+            {
+                throw new RuntimeException(ex.toString());
+            }
+        }
+
+        // Create search policy for module loader.
+        R4SearchPolicy searchPolicy = new R4SearchPolicy(m_logger);
+
+        // Add a resolver listener to the search policy
+        // so that we will be notified when modules are resolved
+        // in order to update the bundle state.
+        searchPolicy.addResolverListener(new ResolveListener() {
+            public void moduleResolved(ModuleEvent event)
+            {
+                BundleImpl bundle = null;
+                try
+                {
+                    long id = BundleInfo.getBundleIdFromModuleId(
+                        event.getModule().getId());
+                    if (id >= 0)
+                    {
+                        // Update the bundle's state to resolved when the
+                        // current module is resolved; just ignore resolve
+                        // events for older revisions since this only occurs
+                        // when an update is done on an unresolved bundle
+                        // and there was no refresh performed.
+                        bundle = (BundleImpl) getBundle(id);
+
+                        // Lock the bundle first.
+                        try
+                        {
+                            acquireBundleLock(bundle);
+                            if (bundle.getInfo().getCurrentModule() == event.getModule())
+                            {
+                                bundle.getInfo().setState(Bundle.RESOLVED);
+                            }
+                        }
+                        catch (BundleException ex)
+                        {
+                            // This should not happen, but if it does
+                            // there isn't much we can do.
+                        }
+                        finally
+                        {
+                            releaseBundleLock(bundle);
+                        }
+                    }
+                }
+                catch (NumberFormatException ex)
+                {
+                    // Ignore.
+                }
+            }
+
+            public void moduleUnresolved(ModuleEvent event)
+            {
+                // We can ignore this, because the only time it
+                // should happen is when a refresh occurs. The
+                // refresh operation resets the bundle's state
+                // by calling BundleInfo.reset(), thus it is not
+                // necessary for us to reset the bundle's state
+                // here.
+            }
+        });
+
+        m_mgr = new ModuleManager(searchPolicy, new OSGiURLPolicy(this));
+
+        // Initialize dispatch queue.
+        m_dispatchQueue = new FelixDispatchQueue(m_logger);
+
+        // Initialize framework properties.
+        initializeFrameworkProperties();
+
+        // Before we reload any cached bundles, let's create a system
+        // bundle that is responsible for providing specific container
+        // related services.
+        SystemBundle systembundle = null;
+        try
+        {
+            // Create a simple bundle info for the system bundle.
+            BundleInfo info = new BundleInfo(
+                m_logger, new SystemBundleArchive(), null);
+            systembundle = new SystemBundle(this, info, activatorList);
+            systembundle.getInfo().addModule(
+                m_mgr.addModule(
+                    "0", systembundle.getAttributes(),
+                    systembundle.getResourceSources(),
+                    systembundle.getLibrarySources(),
+                    true)); // HACK ALERT! This flag indicates that we will
+                            // use the parent class loader as a resource source.
+            m_installedBundleMap.put(
+                systembundle.getInfo().getLocation(), systembundle);
+
+            // Manually resolve the System Bundle, which will cause its
+            // state to be set to RESOLVED.
+            try
+            {
+                searchPolicy.resolve(systembundle.getInfo().getCurrentModule());
+            }
+            catch (ResolveException ex)
+            {
+                // This should never happen.
+                throw new BundleException(
+                        "Unresolved package in System Bundle:"
+                        + ex.getPackage());
+            }
+
+            // Start the system bundle; this will set its state
+            // to STARTING, we must set its state to ACTIVE after
+            // all bundles are restarted below according to the spec.
+            systembundle.start();
+        }
+        catch (Exception ex)
+        {
+            m_mgr = null;
+            DispatchQueue.shutdown();
+            m_logger.log(LogWrapper.LOG_ERROR, "Unable to start system bundle.", ex);
+            throw new RuntimeException("Unable to start system bundle.");
+        }
+        
+        // Reload and cached bundles.
+        BundleArchive[] archives = null;
+
+        // First get cached bundle identifiers.
+        try
+        {
+            archives = m_cache.getArchives();
+        }
+        catch (Exception ex)
+        {
+            m_logger.log(
+                LogWrapper.LOG_ERROR,
+                "Unable to list saved bundles: " + ex, ex);
+            archives = null;
+        }
+
+        BundleImpl bundle = null;
+
+        // 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
+            {
+                // 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
+                // with an UNINSTALLED persistent state, then try to remove
+                // it now.
+                if (archives[i].getPersistentState() == Bundle.UNINSTALLED)
+                {
+                    m_cache.remove(archives[i]);
+                }
+                // Otherwise re-install the cached bundle.
+                else
+                {
+                    // Install the cached bundle.
+                    bundle = (BundleImpl) installBundle(
+                        archives[i].getId(), archives[i].getLocation(), null);
+                }
+            }
+            catch (Exception ex)
+            {
+                fireFrameworkEvent(FrameworkEvent.ERROR, bundle, ex);
+                try
+                {
+                    m_logger.log(
+                        LogWrapper.LOG_ERROR,
+                        "Unable to re-install " + archives[i].getLocation(),
+                        ex);
+                }
+                catch (Exception ex2)
+                {
+                    m_logger.log(
+                        LogWrapper.LOG_ERROR,
+                        "Unable to re-install bundle " + archives[i].getId(),
+                        ex);
+                }
+                // TODO: Perhaps we should remove the cached bundle?
+            }
+        }
+
+        // Get the framework's default start level.
+        int startLevel = FelixConstants.FRAMEWORK_DEFAULT_STARTLEVEL;
+        String s = m_config.get(FelixConstants.FRAMEWORK_STARTLEVEL_PROP);
+        if (s != null)
+        {
+            try
+            {
+                startLevel = Integer.parseInt(s);
+            }
+            catch (NumberFormatException ex)
+            {
+                startLevel = FelixConstants.FRAMEWORK_DEFAULT_STARTLEVEL;
+            }
+        }
+
+        // Load bundles from auto-install and auto-start properties;
+        processAutoProperties();
+
+        // This will restart bundles if necessary.
+        setFrameworkStartLevel(startLevel);
+
+        // The framework is now running.
+        m_frameworkStatus = RUNNING_STATUS;
+
+        // Set the system bundle state to ACTIVE.
+        systembundle.getInfo().setState(Bundle.ACTIVE);
+
+        // Fire started event for system bundle.
+        fireBundleEvent(BundleEvent.STARTED, systembundle);
+
+        // Send a framework event to indicate the framework has started.
+        fireFrameworkEvent(FrameworkEvent.STARTED, getBundle(0), null);
+    }
+
+    /**
+     * This method cleanly shuts down the framework, it must be called at the
+     * end of a session in order to shutdown all active bundles.
+    **/
+    public synchronized void shutdown()
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+
+        // Change framework status from running to stopping.
+        // If framework is not running, then just return.
+        if (m_frameworkStatus != RUNNING_STATUS)
+        {
+            return;
+        }
+
+        // The framework is now in its shutdown sequence.
+        m_frameworkStatus = STOPPING_STATUS;
+
+        // Set the start level to zero in order to stop
+        // all bundles in the framework.
+        setFrameworkStartLevel(0);
+
+        // Just like initialize() called the system bundle's start()
+        // method, we must call its stop() method here so that it
+        // can perform any necessary clean up.
+        try
+        {
+            getBundle(0).stop();
+        }
+        catch (Exception ex)
+        {
+            fireFrameworkEvent(FrameworkEvent.ERROR, getBundle(0), ex);
+            m_logger.log(LogWrapper.LOG_ERROR, "Error stopping system bundle.", ex);
+        }
+
+        // Loop through all bundles and update any updated bundles.
+        Bundle[] bundles = getBundles();
+        for (int i = 0; i < bundles.length; i++)
+        {
+            BundleImpl bundle = (BundleImpl) bundles[i];
+            if (bundle.getInfo().isRemovalPending())
+            {
+                try
+                {
+                    purgeBundle(bundle);
+                }
+                catch (Exception ex)
+                {
+                    fireFrameworkEvent(FrameworkEvent.ERROR, bundle, ex);
+                    m_logger.log(LogWrapper.LOG_ERROR, "Unable to purge bundle "
+                        + bundle.getInfo().getLocation(), ex);
+                }
+            }
+        }
+
+        // Remove any uninstalled bundles.
+        for (int i = 0;
+            (m_uninstalledBundles != null) && (i < m_uninstalledBundles.length);
+            i++)
+        {
+            try
+            {
+                garbageCollectBundle(m_uninstalledBundles[i]);
+            }
+            catch (Exception ex)
+            {
+                m_logger.log(
+                    LogWrapper.LOG_ERROR,
+                    "Unable to remove "
+                    + m_uninstalledBundles[i].getInfo().getLocation(), ex);
+            }
+        }
+
+        // Shutdown event dispatching queue.
+        DispatchQueue.shutdown();
+
+        // The framework is no longer in a usable state.
+        m_frameworkStatus = INITIAL_STATUS;
+    }
+
+    public int getStatus()
+    {
+        return m_frameworkStatus;
+    }
+
+    /**
+     * Returns the active start level of the framework; this method
+     * implements functionality for the Start Level service.
+     * @return The active start level of the framework.
+    **/
+    protected int getStartLevel()
+    {
+        return m_activeStartLevel;
+    }
+
+    /**
+     * Implements the functionality of the <tt>setStartLevel()</tt>
+     * method for the StartLevel service, but does not do the security or
+     * parameter check. The security and parameter check are done in the
+     * StartLevel service implementation because this method is called on
+     * a separate thread and the caller's thread would already be gone if
+     * we did the checks in this method.
+     * @param requestedLevel The new start level of the framework.
+    **/
+    protected synchronized void setFrameworkStartLevel(int requestedLevel)
+    {
+        // Determine if we are lowering or raising the
+        // active start level.
+        boolean lowering = (requestedLevel < m_activeStartLevel);
+
+        // Record new start level.
+        m_activeStartLevel = requestedLevel;
+
+        // Get array of all installed bundles.
+        Bundle[] bundles = getBundles();
+
+        // Sort bundle array by start level either ascending or
+        // descending depending on whether the start level is being
+        // lowered or raised.
+        Comparator comparator = null;
+        if (lowering)
+        {
+            // Sort descending to stop highest start level first.
+            comparator = new Comparator() {
+                public int compare(Object o1, Object o2)
+                {
+                    BundleImpl b1 = (BundleImpl) o1;
+                    BundleImpl b2 = (BundleImpl) o2;
+                    if (b1.getInfo().getStartLevel(getInitialBundleStartLevel())
+                        < b2.getInfo().getStartLevel(getInitialBundleStartLevel()))
+                    {
+                        return 1;
+                    }
+                    else if (b1.getInfo().getStartLevel(getInitialBundleStartLevel())
+                        > b2.getInfo().getStartLevel(getInitialBundleStartLevel()))
+                    {
+                        return -1;
+                    }
+                    return 0;
+                }
+            };
+        }
+        else
+        {
+            // Sort ascending to start lowest start level first.
+            comparator = new Comparator() {
+                public int compare(Object o1, Object o2)
+                {
+                    BundleImpl b1 = (BundleImpl) o1;
+                    BundleImpl b2 = (BundleImpl) o2;
+                    if (b1.getInfo().getStartLevel(getInitialBundleStartLevel())
+                        > b2.getInfo().getStartLevel(getInitialBundleStartLevel()))
+                    {
+                        return 1;
+                    }
+                    else if (b1.getInfo().getStartLevel(getInitialBundleStartLevel())
+                        < b2.getInfo().getStartLevel(getInitialBundleStartLevel()))
+                    {
+                        return -1;
+                    }
+                    return 0;
+                }
+            };
+        }
+
+        Arrays.sort(bundles, comparator);
+
+        // Stop or start the bundles according to the start level.
+        for (int i = 0; (bundles != null) && (i < bundles.length); i++)
+        {
+            BundleImpl impl = (BundleImpl) bundles[i];
+
+            // Ignore the system bundle, since its start() and
+            // stop() methods get called explicitly in initialize()
+            // and shutdown(), respectively.
+            if (impl.getInfo().getBundleId() == 0)
+            {
+                continue;
+            }
+
+            // Start the bundle if necessary.
+            if ((impl.getInfo().getPersistentState() == Bundle.ACTIVE) &&
+                (impl.getInfo().getStartLevel(getInitialBundleStartLevel())
+                    <= m_activeStartLevel))
+            {
+                try
+                {
+                    startBundle(impl, false);
+                }
+                catch (Throwable th)
+                {
+                    fireFrameworkEvent(FrameworkEvent.ERROR, impl, th);
+                    m_logger.log(
+                        LogWrapper.LOG_ERROR,
+                        "Error starting " + impl.getInfo().getLocation(), th);
+                }
+            }
+            // Stop the bundle if necessary.
+            else if (impl.getInfo().getStartLevel(getInitialBundleStartLevel())
+                > m_activeStartLevel)
+            {
+                try
+                {
+                    stopBundle(impl, false);
+                }
+                catch (Throwable th)
+                {
+                    fireFrameworkEvent(FrameworkEvent.ERROR, impl, th);
+                    m_logger.log(
+                        LogWrapper.LOG_ERROR,
+                        "Error stopping " + impl.getInfo().getLocation(), th);
+                }
+            }
+        }
+
+        fireFrameworkEvent(FrameworkEvent.STARTLEVEL_CHANGED, getBundle(0), null);
+    }
+
+    /**
+     * Returns the start level into which newly installed bundles will
+     * be placed by default; this method implements functionality for
+     * the Start Level service.
+     * @return The default start level for newly installed bundles.
+    **/
+    protected int getInitialBundleStartLevel()
+    {
+        String s = m_config.get(FelixConstants.BUNDLE_STARTLEVEL_PROP);
+
+        if (s != null)
+        {
+            try
+            {
+                int i = Integer.parseInt(s);
+                return (i > 0) ? i : FelixConstants.BUNDLE_DEFAULT_STARTLEVEL;
+            }
+            catch (NumberFormatException ex)
+            {
+                // Ignore and return the default value.
+            }
+        }
+        return FelixConstants.BUNDLE_DEFAULT_STARTLEVEL;
+    }
+
+    /**
+     * Sets the default start level into which newly installed bundles
+     * will be placed; this method implements functionality for the Start
+     * Level service.
+     * @param startLevel The new default start level for newly installed
+     *        bundles.
+     * @throws java.lang.IllegalArgumentException If the specified start
+     *         level is not greater than zero.
+     * @throws java.security.SecurityException If the caller does not
+     *         have <tt>AdminPermission</tt>.
+    **/
+    protected void setInitialBundleStartLevel(int startLevel)
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+
+        if (startLevel <= 0)
+        {
+            throw new IllegalArgumentException(
+                "Initial start level must be greater than zero.");
+        }
+
+        m_configProps.put(
+            FelixConstants.BUNDLE_STARTLEVEL_PROP, Integer.toString(startLevel));
+    }
+
+    /**
+     * Returns the start level for the specified bundle; this method
+     * implements functionality for the Start Level service.
+     * @param bundle The bundle to examine.
+     * @return The start level of the specified bundle.
+     * @throws java.lang.IllegalArgumentException If the specified
+     *          bundle has been uninstalled.
+    **/
+    protected int getBundleStartLevel(Bundle bundle)
+    {
+        if (bundle.getState() == Bundle.UNINSTALLED)
+        {
+            throw new IllegalArgumentException("Bundle is uninstalled.");
+        }
+
+        return ((BundleImpl) bundle).getInfo().getStartLevel(getInitialBundleStartLevel());
+    }
+
+    /**
+     * Sets the start level of the specified bundle; this method
+     * implements functionality for the Start Level service.
+     * @param bundle The bundle whose start level is to be modified.
+     * @param startLevel The new start level of the specified bundle.
+     * @throws java.lang.IllegalArgumentException If the specified
+     *          bundle is the system bundle or if the bundle has been
+     *          uninstalled.
+     * @throws java.security.SecurityException If the caller does not
+     *          have <tt>AdminPermission</tt>.
+    **/
+    protected void setBundleStartLevel(Bundle bundle, int startLevel)
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+
+        // Cannot change the system bundle.
+        if (bundle.getBundleId() == 0)
+        {
+            throw new IllegalArgumentException(
+                "Cannot change system bundle start level.");
+        }
+
+        // Acquire bundle lock.
+        try
+        {
+            acquireBundleLock((BundleImpl) bundle);
+        }
+        catch (BundleException ex)
+        {
+            m_logger.log(LogWrapper.LOG_ERROR, "Unable to acquire lock to set start level.", ex);
+            return;
+        }
+        
+        Throwable rethrow = null;
+
+        try
+        {
+            if (bundle.getState() == Bundle.UNINSTALLED)
+            {
+                throw new IllegalArgumentException("Bundle is uninstalled.");
+            }
+
+            if (startLevel >= 1)
+            {
+                BundleImpl impl = (BundleImpl) bundle;
+                impl.getInfo().setStartLevel(startLevel);
+    
+                try
+                {
+                    // Start the bundle if necessary.
+                    if ((impl.getInfo().getPersistentState() == Bundle.ACTIVE) &&
+                        (impl.getInfo().getStartLevel(getInitialBundleStartLevel())
+                            <= m_activeStartLevel))
+                    {
+                        startBundle(impl, false);
+                    }
+                    // Stop the bundle if necessary.
+                    else if (impl.getInfo().getStartLevel(getInitialBundleStartLevel())
+                        > m_activeStartLevel)
+                    {
+                        stopBundle(impl, false);
+                    }
+                }
+                catch (Throwable th)
+                {
+                    rethrow = th;
+                    m_logger.log(LogWrapper.LOG_ERROR, "Error starting/stopping bundle.", th);
+                }
+            }
+            else
+            {
+                m_logger.log(LogWrapper.LOG_WARNING, "Bundle start level must be greater than zero.");
+            }
+        }
+        finally
+        {
+            // Always release bundle lock.
+            releaseBundleLock((BundleImpl) bundle);
+        }
+
+        if (rethrow != null)
+        {
+            fireFrameworkEvent(FrameworkEvent.ERROR, bundle, rethrow);
+        }
+    }
+
+    /**
+     * Returns whether a bundle is persistently started; this is an
+     * method implementation for the Start Level service.
+     * @param bundle The bundle to examine.
+     * @return <tt>true</tt> if the bundle is marked as persistently
+     *          started, <tt>false</tt> otherwise.
+     * @throws java.lang.IllegalArgumentException If the specified
+     *          bundle has been uninstalled.
+    **/
+    protected boolean isBundlePersistentlyStarted(Bundle bundle)
+    {
+        if (bundle.getState() == Bundle.UNINSTALLED)
+        {
+            throw new IllegalArgumentException("Bundle is uninstalled.");
+        }
+
+        return (((BundleImpl) bundle).getInfo().getPersistentState() == Bundle.ACTIVE);
+    }
+
+    //
+    // Implementation of Bundle interface methods.
+    //
+
+    /**
+     * Implementation for Bundle.getHeaders().
+    **/
+    protected Dictionary getBundleHeaders(BundleImpl bundle)
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+        return new MapToDictionary(bundle.getInfo().getCurrentHeader());
+    }
+
+    /**
+     * Implementation for Bundle.getLocation().
+    **/
+    protected String getBundleLocation(BundleImpl bundle)
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+        return bundle.getInfo().getLocation();
+    }
+
+    /**
+     * Implementation for Bundle.getResource().
+    **/
+    protected URL getBundleResource(BundleImpl bundle, String name)
+    {
+        if (bundle.getInfo().getState() == Bundle.UNINSTALLED)
+        {
+            throw new IllegalStateException("The bundle is uninstalled.");
+        }
+        else if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+        return bundle.getInfo().getCurrentModule().getClassLoader().getResource(name);
+    }
+
+    protected ServiceReference[] getBundleRegisteredServices(BundleImpl bundle)
+    {
+        if (bundle.getInfo().getState() == Bundle.UNINSTALLED)
+        {
+            throw new IllegalStateException("The bundle is uninstalled.");
+        }
+
+        // Filter list of registered service references.
+        ServiceReference[] refs = m_registry.getRegisteredServices(bundle);
+        List list = new ArrayList();
+        for (int refIdx = 0; (refs != null) && (refIdx < refs.length); refIdx++)
+        {
+            // Check that the current security context has permission
+            // to get at least one of the service interfaces; the
+            // objectClass property of the service stores its service
+            // interfaces.
+            boolean hasPermission = false;
+            if (System.getSecurityManager() != null)
+            {
+                String[] objectClass = (String[])
+                    refs[refIdx].getProperty(Constants.OBJECTCLASS);
+                if (objectClass == null)
+                {
+                    return null;
+                }
+                for (int ifcIdx = 0;
+                    !hasPermission && (ifcIdx < objectClass.length);
+                    ifcIdx++)
+                {
+                    try
+                    {
+                        ServicePermission perm =
+                            new ServicePermission(
+                                objectClass[ifcIdx], ServicePermission.GET);
+                        AccessController.checkPermission(perm);
+                        hasPermission = true;
+                    }
+                    catch (Exception ex)
+                    {
+                    }
+                }
+            }
+            else
+            {
+                hasPermission = true;
+            }
+
+            if (hasPermission)
+            {
+                list.add(refs[refIdx]);
+            }
+        }
+
+        if (list.size() > 0)
+        {
+            return (ServiceReference[])
+                list.toArray(new ServiceReference[list.size()]);
+        }
+
+        return null;
+    }
+
+    protected ServiceReference[] getBundleServicesInUse(Bundle bundle)
+    {
+        // Filter list of "in use" service references.
+        ServiceReference[] refs = m_registry.getServicesInUse(bundle);
+        List list = new ArrayList();
+        for (int refIdx = 0; (refs != null) && (refIdx < refs.length); refIdx++)
+        {
+            // Check that the current security context has permission
+            // to get at least one of the service interfaces; the
+            // objectClass property of the service stores its service
+            // interfaces.
+            boolean hasPermission = false;
+            if (System.getSecurityManager() != null)
+            {
+                String[] objectClass = (String[])
+                    refs[refIdx].getProperty(Constants.OBJECTCLASS);
+                if (objectClass == null)
+                {
+                    return null;
+                }
+                for (int ifcIdx = 0;
+                    !hasPermission && (ifcIdx < objectClass.length);
+                    ifcIdx++)
+                {
+                    try
+                    {
+                        ServicePermission perm =
+                            new ServicePermission(
+                                objectClass[ifcIdx], ServicePermission.GET);
+                        AccessController.checkPermission(perm);
+                        hasPermission = true;
+                    }
+                    catch (Exception ex)
+                    {
+                    }
+                }
+            }
+            else
+            {
+                hasPermission = true;
+            }
+
+            if (hasPermission)
+            {
+                list.add(refs[refIdx]);
+            }
+        }
+
+        if (list.size() > 0)
+        {
+            return (ServiceReference[])
+                list.toArray(new ServiceReference[list.size()]);
+        }
+
+        return null;
+    }
+
+    protected boolean bundleHasPermission(BundleImpl bundle, Object obj)
+    {
+        if (bundle.getInfo().getState() == Bundle.UNINSTALLED)
+        {
+            throw new IllegalStateException("The bundle is uninstalled.");
+        }
+
+// TODO: IMPLEMENT THIS CORRECTLY.
+        return true;
+    }
+
+    /**
+     * Implementation for Bundle.start().
+    **/
+    protected void startBundle(BundleImpl bundle, boolean record)
+        throws BundleException
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+
+        // CONCURRENCY NOTE:
+        // Starting a bundle may actually impact many bundles, since
+        // the bundle being started my need to be resolved, which in
+        // turn may need to resolve other bundles. Despite this fact,
+        // we only acquire the lock for the bundle being started, because
+        // when resolve is called on this bundle, it will eventually
+        // call resolve on the module loader search policy, which does
+        // its own locking on the module manager instance. Since the 
+        // resolve algorithm is locking the module manager instance, it
+        // is not possible for other bundles to be installed or removed,
+        // so we don't have to worry about these possibilities.
+        //
+        // Further, if other bundles are started during this operation,
+        // then either they will resolve first because they got the lock
+        // on the module manager or we will resolve first since we got
+        // the lock on the module manager, so there should be no interference.
+        // If other bundles are stopped or uninstalled, this should pose
+        // no problems, since this does not impact their resolved state.
+        // If a refresh occurs, then the refresh algorithm ulimately has
+        // to acquire the module manager instance lock too before it can
+        // completely purge old modules, so it should also complete either
+        // before or after this bundle is started. At least that's the
+        // theory.
+
+        // Acquire bundle lock.
+        acquireBundleLock(bundle);
+
+        try
+        {
+            _startBundle(bundle, record);
+        }
+        finally
+        {
+            // Release bundle lock.
+            releaseBundleLock(bundle);
+        }
+    }
+
+    private void _startBundle(BundleImpl bundle, boolean record)
+        throws BundleException
+    {
+        // Set and save the bundle's persistent state to active
+        // if we are supposed to record state change.
+        if (record)
+        {
+            bundle.getInfo().setPersistentStateActive();
+        }
+
+        // Try to start the bundle.
+        BundleInfo info = bundle.getInfo();
+
+        // Ignore bundles whose persistent state is not active
+        // or whose start level is greater than the framework's.
+        if ((info.getPersistentState() != Bundle.ACTIVE)
+            || (info.getStartLevel(getInitialBundleStartLevel()) > getStartLevel()))
+        {
+            return;
+        }
+
+        switch (info.getState())
+        {
+            case Bundle.UNINSTALLED:
+                throw new IllegalStateException("Cannot start an uninstalled bundle.");
+            case Bundle.STARTING:
+            case Bundle.STOPPING:
+                throw new BundleException("Starting a bundle that is starting or stopping is currently not supported.");
+            case Bundle.ACTIVE:
+                return;
+            case Bundle.INSTALLED:
+                _resolveBundle(bundle);
+            case Bundle.RESOLVED:
+                info.setState(Bundle.STARTING);
+        }
+
+        try
+        {
+            // Set the bundle's activator.
+            bundle.getInfo().setActivator(createBundleActivator(bundle.getInfo()));
+
+            // Activate the bundle if it has an activator.
+            if (bundle.getInfo().getActivator() != null)
+            {
+                if (info.getContext() == null)
+                {
+                    info.setContext(new BundleContextImpl(this, bundle));
+                }
+
+                if (System.getSecurityManager() != null)
+                {
+//                    m_startStopPrivileged.setAction(StartStopPrivileged.START_ACTION);
+//                    m_startStopPrivileged.setBundle(bundle);
+//                    AccessController.doPrivileged(m_startStopPrivileged);
+                }
+                else
+                {
+                    info.getActivator().start(info.getContext());
+                }
+            }
+
+            info.setState(Bundle.ACTIVE);
+
+            fireBundleEvent(BundleEvent.STARTED, bundle);
+        }
+        catch (Throwable th)
+        {
+            // If there was an error starting the bundle,
+            // then reset its state to RESOLVED.
+            info.setState(Bundle.RESOLVED);
+
+            // Unregister any services offered by this bundle.
+            m_registry.unregisterServices(bundle);
+
+            // Release any services being used by this bundle.
+            m_registry.ungetServices(bundle);
+
+            // Remove any listeners registered by this bundle.
+            removeListeners(bundle);
+
+            // The spec says to expect BundleException or
+            // SecurityException, so rethrow these exceptions.
+            if (th instanceof BundleException)
+            {
+                throw (BundleException) th;
+            }
+            else if (th instanceof SecurityException)
+            {
+                throw (SecurityException) th;
+            }
+            // Convert a privileged action exception to the
+            // nested exception.
+            else if (th instanceof PrivilegedActionException)
+            {
+                th = ((PrivilegedActionException) th).getException();
+            }
+
+            // Rethrow all other exceptions as a BundleException.
+            throw new BundleException("Activator start error.", th);
+        }
+    }
+
+    protected void _resolveBundle(BundleImpl bundle)
+        throws BundleException
+    {
+        // If a security manager is installed, then check for permission
+        // to import the necessary packages.
+        if (System.getSecurityManager() != null)
+        {
+            URL url = null;
+            try
+            {
+                url = new URL(bundle.getInfo().getLocation());
+            }
+            catch (MalformedURLException ex)
+            {
+                throw new BundleException("Cannot resolve, bad URL "
+                    + bundle.getInfo().getLocation());
+            }
+
+//            try
+//            {
+//                AccessController.doPrivileged(new CheckImportsPrivileged(url, bundle));
+//            }
+//            catch (PrivilegedActionException ex)
+//            {
+//                Exception thrown = ((PrivilegedActionException) ex).getException();
+//                if (thrown instanceof AccessControlException)
+//                {
+//                    throw (AccessControlException) thrown;
+//                }
+//                else
+//                {
+//                    throw new BundleException("Problem resolving: " + ex);
+//                }
+//            }
+        }
+
+        // Get the import search policy.
+        R4SearchPolicy search = (R4SearchPolicy) m_mgr.getSearchPolicy();
+
+        Module module = bundle.getInfo().getCurrentModule();
+        try
+        {
+            search.resolve(module);
+        }
+        catch (ResolveException ex)
+        {
+            if (ex.getModule() != null)
+            {
+                throw new BundleException(
+                    "Unresolved package in bundle "
+                    + BundleInfo.getBundleIdFromModuleId(ex.getModule().getId())
+                    + ": " + ex.getPackage());
+            }
+            else
+            {
+                throw new BundleException(ex.getMessage());
+            }
+        }
+
+        bundle.getInfo().setState(Bundle.RESOLVED);
+    }
+
+    protected void updateBundle(BundleImpl bundle, InputStream is)
+        throws BundleException
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+
+        // Acquire bundle lock.
+        acquireBundleLock(bundle);
+
+        try
+        {
+            _updateBundle(bundle, is);
+        }
+        finally
+        {
+            // Release bundle lock.
+            releaseBundleLock(bundle);
+        }
+    }
+
+    protected void _updateBundle(BundleImpl bundle, InputStream is)
+        throws BundleException
+    {
+        // We guarantee to close the input stream, so put it in a
+        // finally clause.
+    
+        try
+        {
+            // Variable to indicate whether bundle is active or not.
+            Exception rethrow = null;
+
+            // Cannot update an uninstalled bundle.
+            BundleInfo info = bundle.getInfo();
+            if (info.getState() == Bundle.UNINSTALLED)
+            {
+                throw new IllegalStateException("The bundle is uninstalled.");
+            }
+
+            // First get the update-URL from our header.
+            String updateLocation = (String)
+                info.getCurrentHeader().get(Constants.BUNDLE_UPDATELOCATION);
+
+            // If no update location specified, use original location.
+            if (updateLocation == null)
+            {
+                updateLocation = info.getLocation();
+            }
+
+            // Stop the bundle, but do not change the persistent state.
+            stopBundle(bundle, false);
+
+            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());
+                // Update the bundle; this operation will increase
+                // the revision count for the bundle.
+                m_cache.update(archive, 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.
+                Module module = createModule(
+                    info.getBundleId(),
+                    archive.getRevisionCount() - 1,
+                    info.getCurrentHeader());
+                // Add module to bundle info.
+                info.addModule(module);
+            }
+            catch (Exception ex)
+            {
+                m_logger.log(LogWrapper.LOG_ERROR, "Unable to update the bundle.", ex);
+                rethrow = ex;
+            }
+
+            info.setState(Bundle.INSTALLED);
+
+            // Mark as needing a refresh.
+            info.setRemovalPending();
+    
+            // Fire updated event if successful.
+            if (rethrow == null)
+            {
+                fireBundleEvent(BundleEvent.UPDATED, bundle);
+            }
+    
+            // Restart bundle, but do not change the persistent state.
+            // This will not start the bundle if it was not previously
+            // active.
+            startBundle(bundle, false);
+    
+            // If update failed, rethrow exception.
+            if (rethrow != null)
+            {
+                throw new BundleException("Update failed.", rethrow);
+            }
+        }
+        finally
+        {
+            try
+            {
+                if (is != null) is.close();
+            }
+            catch (IOException ex)
+            {
+                m_logger.log(LogWrapper.LOG_ERROR, "Unable to close input stream.", ex);
+            }
+        }
+    }
+
+    protected void stopBundle(BundleImpl bundle, boolean record)
+        throws BundleException
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+
+        // Acquire bundle lock.
+        acquireBundleLock(bundle);
+
+        try
+        {
+            _stopBundle(bundle, record);
+        }
+        finally
+        {
+            // Always release bundle lock.
+            releaseBundleLock(bundle);
+        }
+    }
+
+    private void _stopBundle(BundleImpl bundle, boolean record)
+        throws BundleException
+    {
+        Throwable rethrow = null;
+    
+        // Set the bundle's persistent state to inactive if necessary.
+        if (record)
+        {
+            bundle.getInfo().setPersistentStateInactive();
+        }
+
+        BundleInfo info = bundle.getInfo();
+        
+        switch (info.getState())
+        {
+            case Bundle.UNINSTALLED:
+                throw new IllegalStateException("Cannot stop an uninstalled bundle.");
+            case Bundle.STARTING:
+            case Bundle.STOPPING:
+                throw new BundleException("Stopping a bundle that is starting or stopping is currently not supported.");
+            case Bundle.INSTALLED:
+            case Bundle.RESOLVED:
+                return;
+            case Bundle.ACTIVE:
+                // Set bundle state..
+                info.setState(Bundle.STOPPING);
+        }
+            
+        try
+        {
+            if (bundle.getInfo().getActivator() != null)
+            {
+                if (System.getSecurityManager() != null)
+                {
+//                    m_startStopPrivileged.setAction(StartStopPrivileged.STOP_ACTION);
+//                    m_startStopPrivileged.setBundle(bundle);
+//                    AccessController.doPrivileged(m_startStopPrivileged);
+                }
+                else
+                {
+                    info.getActivator().stop(info.getContext());
+                }
+            }
+        
+            // Try to save the activator in the cache.
+            // NOTE: This is non-standard OSGi behavior and only
+            // occurs if strictness is disabled.
+            String strict = m_config.get(FelixConstants.STRICT_OSGI_PROP);
+            boolean isStrict = (strict == null) ? true : strict.equals("true");
+            if (!isStrict)
+            {
+                try
+                {
+                    m_cache.getArchive(info.getBundleId())
+                        .setActivator(info.getActivator());
+                }
+                catch (Exception ex)
+                {
+                    // Problem saving activator, so ignore it.
+                    // TODO: Perhaps we should handle this some other way?
+                }
+            }
+        }
+        catch (Throwable th)
+        {
+            m_logger.log(LogWrapper.LOG_ERROR, "Error stopping bundle.", th);
+            rethrow = th;
+        }
+                  
+        // Unregister any services offered by this bundle.
+        m_registry.unregisterServices(bundle);
+        
+        // Release any services being used by this bundle.
+        m_registry.ungetServices(bundle);
+        
+        // The spec says that we must remove all event
+        // listeners for a bundle when it is stopped.
+        removeListeners(bundle);
+        
+        info.setState(Bundle.RESOLVED);
+        fireBundleEvent(BundleEvent.STOPPED, bundle);
+        
+        // Throw activator error if there was one.
+        if (rethrow != null)
+        {
+            // The spec says to expect BundleException or
+            // SecurityException, so rethrow these exceptions.
+            if (rethrow instanceof BundleException)
+            {
+                throw (BundleException) rethrow;
+            }
+            else if (rethrow instanceof SecurityException)
+            {
+                throw (SecurityException) rethrow;
+            }
+            else if (rethrow instanceof PrivilegedActionException)
+            {
+                rethrow = ((PrivilegedActionException) rethrow).getException();
+            }
+    
+            // Rethrow all other exceptions as a BundleException.
+            throw new BundleException("Activator stop error.", rethrow);
+        }
+    }
+
+    protected void uninstallBundle(BundleImpl bundle) throws BundleException
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+
+        // Acquire bundle lock.
+        acquireBundleLock(bundle);
+
+        try
+        {
+            _uninstallBundle(bundle);
+        }
+        finally
+        {
+            // Always release bundle lock.
+            releaseBundleLock(bundle);
+        }
+    }
+
+    private void _uninstallBundle(BundleImpl bundle) throws BundleException
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+
+        BundleException rethrow = null;
+
+        BundleInfo info = bundle.getInfo();
+        if (info.getState() == Bundle.UNINSTALLED)
+        {
+            throw new IllegalStateException("The bundle is uninstalled.");
+        }
+
+        // The spec says that uninstall should always succeed, so
+        // catch an exception here if stop() doesn't succeed and
+        // rethrow it at the end.
+        try
+        {
+            stopBundle(bundle, true);
+        }
+        catch (BundleException ex)
+        {
+            rethrow = ex;
+        }
+
+        // Remove the bundle from the installed map.
+        BundleImpl target = null;
+        synchronized (m_installedBundleLock_Priority2)
+        {
+            target = (BundleImpl) m_installedBundleMap.remove(info.getLocation());
+        }
+
+        // Finally, put the uninstalled bundle into the
+        // uninstalled list for subsequent refreshing.
+        if (target != null)
+        {
+            // Set the bundle's persistent state to uninstalled.
+            target.getInfo().setPersistentStateUninstalled();
+
+            // Mark bundle for removal.
+            target.getInfo().setRemovalPending();
+
+            // Put bundle in uninstalled bundle array.
+            rememberUninstalledBundle(bundle);
+        }
+        else
+        {
+            m_logger.log(
+                LogWrapper.LOG_ERROR, "Unable to remove bundle from installed map!");
+        }
+
+        // Set state to uninstalled.
+        info.setState(Bundle.UNINSTALLED);
+
+        // Fire bundle event.
+        fireBundleEvent(BundleEvent.UNINSTALLED, bundle);
+
+        if (rethrow != null)
+        {
+            throw rethrow;
+        }
+    }
+
+    //
+    // Implementation of BundleContext interface methods.
+    //
+
+    /**
+     * Implementation for BundleContext.getProperty(). Returns
+     * environment property associated with the framework.
+     *
+     * @param key The name of the property to retrieve.
+     * @return The value of the specified property or null.
+    **/
+    protected String getProperty(String key)
+    {
+        // First, check the config properties.
+        String val = (String) m_configProps.get(key);
+        // If not found, then try the system properties.
+        return (val == null) ? System.getProperty(key) : val;
+    }
+
+    protected Bundle installBundle(String location, InputStream is)
+        throws BundleException
+    {
+        return installBundle(-1, location, is);
+    }
+
+    private Bundle installBundle(long id, String location, InputStream is)
+        throws BundleException
+    {
+        if (System.getSecurityManager() != null)
+        {
+            AccessController.checkPermission(m_adminPerm);
+        }
+    
+        BundleImpl bundle = null;
+
+        // Acquire an install lock.
+        acquireInstallLock(location);
+
+        try
+        {
+            // Check to see if the framework is still running;
+            if ((getStatus() == Felix.STOPPING_STATUS) ||
+                (getStatus() == Felix.INITIAL_STATUS))
+            {
+                throw new BundleException("The framework has been shutdown.");
+            }
+
+            // If bundle location is already installed, then
+            // return it as required by the OSGi specification.
+            bundle = (BundleImpl) getBundle(location);
+            if (bundle != null)
+            {
+                return bundle;
+            }
+
+            // Determine if this is a new or existing bundle.
+            boolean isNew = (id < 0);
+
+            // If the bundle is new we must cache its JAR file.
+            if (isNew)
+            {
+                // First generate an identifier for it.
+                id = getNextId();
+
+                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);
+                }
+                catch (Exception ex)
+                {
+                    throw new BundleException(
+                        "Unable to cache bundle: " + location, ex);
+                }
+                finally
+                {
+                    try
+                    {
+                        if (is != null) is.close();
+                    }
+                    catch (IOException ex)
+                    {
+                        m_logger.log(
+                            LogWrapper.LOG_ERROR,
+                            "Unable to close input stream.", ex);
+                    }
+                }
+            }
+            else
+            {
+                // If the bundle we are installing is not new,
+                // then try to purge old revisions before installing
+                // it; this is done just in case a "refresh"
+                // didn't occur last session...this would only be
+                // due to an error or system crash.
+                try
+                {
+                    if (m_cache.getArchive(id).getRevisionCount() > 1)
+                    {
+                        m_cache.purge(m_cache.getArchive(id));
+                    }
+                }
+                catch (Exception ex)
+                {
+                    ex.printStackTrace();
+                    m_logger.log(
+                        LogWrapper.LOG_ERROR,
+                        "Could not purge bundle.", ex);
+                }
+            }
+
+            try
+            {
+                BundleArchive archive = m_cache.getArchive(id);
+                bundle = new BundleImpl(this, createBundleInfo(archive));
+            }
+            catch (Exception ex)
+            {
+                // If the bundle is new, then remove it from the cache.
+                // TODO: Perhaps it should be removed if it is not new too.
+                if (isNew)
+                {
+                    try
+                    {
+                        m_cache.remove(m_cache.getArchive(id));
+                    }
+                    catch (Exception ex1)
+                    {
+                        m_logger.log(
+                            LogWrapper.LOG_ERROR,
+                            "Could not remove from cache.", ex1);
+                    }
+                }
+                throw new BundleException("Could not create bundle object.", ex);
+            }
+
+            // If the bundle is new, then set its start level; existing
+            // bundles already have their start level set.
+            if (isNew)
+            {
+                // This will persistently set the bundle's start level.
+                bundle.getInfo().setStartLevel(getInitialBundleStartLevel());
+            }
+
+            synchronized (m_installedBundleLock_Priority2)
+            {
+                m_installedBundleMap.put(location, bundle);
+            }
+        }
+        finally
+        {
+            // Always release install lock.
+            releaseInstallLock(location);
+
+            // Always try to close the input stream.
+            try
+            {
+                if (is != null) is.close();
+            }
+            catch (IOException ex)
+            {
+                m_logger.log(
+                    LogWrapper.LOG_ERROR,
+                    "Unable to close input stream.", ex);
+                // Not much else we can do.
+            }
+        }
+    
+        // Fire bundle event.
+        fireBundleEvent(BundleEvent.INSTALLED, bundle);
+    
+        // Return new bundle.
+        return bundle;
+    }
+
+    /**
+     * Retrieves a bundle from its location.
+     *
+     * @param location The location of the bundle to retrieve.
+     * @return The bundle associated with the location or null if there
+     *         is no bundle associated with the location.
+    **/
+    protected Bundle getBundle(String location)
+    {
+        synchronized (m_installedBundleLock_Priority2)
+        {
+            return (Bundle) m_installedBundleMap.get(location);
+        }
+    }
+
+    /**
+     * Implementation for BundleContext.getBundle(). Retrieves a
+     * bundle from its identifier.
+     *
+     * @param id The identifier of the bundle to retrieve.
+     * @return The bundle associated with the identifier or null if there
+     *         is no bundle associated with the identifier.
+    **/
+    protected Bundle getBundle(long id)
+    {
+        synchronized (m_installedBundleLock_Priority2)
+        {
+            BundleImpl bundle = null;
+
+            for (Iterator i = m_installedBundleMap.values().iterator(); i.hasNext(); )
+            {
+                bundle = (BundleImpl) i.next();
+                if (bundle.getInfo().getBundleId() == id)
+                {
+                    return bundle;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    // Private member for method below.
+    private Comparator m_comparator = null;
+
+    /**
+     * Implementation for BundleContext.getBundles(). Retrieves
+     * all installed bundles.
+     *
+     * @return An array containing all installed bundles or null if
+     *         there are no installed bundles.
+    **/
+    protected Bundle[] getBundles()
+    {
+        if (m_comparator == null)
+        {
+            m_comparator = new Comparator() {
+                public int compare(Object o1, Object o2)
+                {
+                    Bundle b1 = (Bundle) o1;
+                    Bundle b2 = (Bundle) o2;
+                    if (b1.getBundleId() > b2.getBundleId())
+                        return 1;
+                    else if (b1.getBundleId() < b2.getBundleId())
+                        return -1;
+                    return 0;
+                }
+            };
+        }
+
+        Bundle[] bundles = null;
+
+        synchronized (m_installedBundleLock_Priority2)
+        {
+            if (m_installedBundleMap.size() == 0)
+            {
+                return null;
+            }
+
+            bundles = new Bundle[m_installedBundleMap.size()];
+            int counter = 0;
+            for (Iterator i = m_installedBundleMap.values().iterator(); i.hasNext(); )
+            {
+                bundles[counter++] = (Bundle) i.next();
+            }
+        }
+
+        Arrays.sort(bundles, m_comparator);
+
+        return bundles;
+    }
+
+    protected void addBundleListener(Bundle bundle, BundleListener l)
+    {
+        // The spec says do nothing if the listener is
+        // already registered.
+        BundleListenerWrapper old = (BundleListenerWrapper)
+            m_dispatchQueue.getListener(BundleListener.class, l);
+        if (old == null)
+        {
+            l = new BundleListenerWrapper(bundle, l);
+            m_dispatchQueue.addListener(BundleListener.class, l);
+        }
+    }
+
+    protected void removeBundleListener(BundleListener l)
+    {
+        m_dispatchQueue.removeListener(BundleListener.class, l);
+    }
+
+    /**
+     * Implementation for BundleContext.addServiceListener().
+     * Adds service listener to the listener list so that is
+     * can listen for <code>ServiceEvent</code>s.
+     *
+     * @param bundle The bundle that registered the listener.
+     * @param l The service listener to add to the listener list.
+     * @param f The filter for the listener; may be null.
+    **/
+    protected void addServiceListener(Bundle bundle, ServiceListener l, String f)
+        throws InvalidSyntaxException
+    {
+        // The spec says if the listener is already registered,
+        // then replace filter.
+        ServiceListenerWrapper old = (ServiceListenerWrapper)
+            m_dispatchQueue.getListener(ServiceListener.class, l);
+        if (old != null)
+        {
+            old.setFilter((f == null) ? null : new FilterImpl(m_logger, f));
+        }
+        else
+        {
+            l = new ServiceListenerWrapper(
+                bundle, l, (f == null) ? null : new FilterImpl(m_logger, f));
+            m_dispatchQueue.addListener(ServiceListener.class, l);
+        }
+    }
+
+    /**
+     * Implementation for BundleContext.removeServiceListener().
+     * Removes service listeners from the listener list.
+     *
+     * @param l The service listener to remove from the listener list.
+    **/
+    protected void removeServiceListener(ServiceListener l)
+    {
+        m_dispatchQueue.removeListener(ServiceListener.class, l);
+    }
+
+    protected void addFrameworkListener(Bundle bundle, FrameworkListener l)
+    {
+        // The spec says do nothing if the listener is
+        // already registered.
+        FrameworkListenerWrapper old = (FrameworkListenerWrapper)
+            m_dispatchQueue.getListener(FrameworkListener.class, l);
+        if (old == null)
+        {
+            l = new FrameworkListenerWrapper(bundle, l);
+            m_dispatchQueue.addListener(FrameworkListener.class, l);
+        }
+    }
+
+    protected void removeFrameworkListener(FrameworkListener l)
+    {
+        m_dispatchQueue.removeListener(FrameworkListener.class, l);
+    }
+
+    /**
+     * Remove all of the specified bundle's event listeners from
+     * the framework.
+     * @param bundle The bundle whose listeners are to be removed.
+    **/
+    private void removeListeners(Bundle bundle)
+    {
+        if (bundle == null)
+        {
+            return;
+        }
+
+        // Remove all listeners associated with the supplied bundle;
+        // it is only possible to know the bundle associated with a
+        // listener if the listener was wrapper by a ListenerWrapper,
+        // so look for those.
+        Object[] listeners = m_dispatchQueue.getListeners();
+        for (int i = listeners.length - 2; i >= 0; i -= 2)
+        {
+            // Check for listener wrappers and then compare the bundle.
+            if (listeners[i + 1] instanceof ListenerWrapper)
+            {
+                ListenerWrapper lw = (ListenerWrapper) listeners[i + 1];
+                if ((lw.getBundle() != null) && (lw.getBundle().equals(bundle)))
+                {
+                    m_dispatchQueue.removeListener(
+                        (Class) listeners[i], (EventListener) listeners[i+1]);
+                }
+            }
+        }
+    }
+
+    /**
+     * Implementation for BundleContext.registerService(). Registers
+     * a service for the specified bundle bundle.
+     *
+     * @param classNames A string array containing the names of the classes
+     *                under which the new service is available.
+     * @param svcObj The service object or <code>ServiceFactory</code>.
+     * @param dict A dictionary of properties that further describe the
+     *             service or null.
+     * @return A <code>ServiceRegistration</code> object or null.
+    **/
+    protected ServiceRegistration registerService(
+        BundleImpl bundle, String[] classNames, Object svcObj, Dictionary dict)
+    {
+        if (classNames == null)
+        {
+            throw new NullPointerException("Service class names cannot be null.");
+        }
+        else if (svcObj == null)
+        {
+            throw new IllegalArgumentException("Service object cannot be null.");
+        }
+
+        // Check for permission to register all passed in interface names.
+        if (System.getSecurityManager() != null)
+        {
+            for (int i = 0; i < classNames.length; i++)
+            {
+                ServicePermission perm = new ServicePermission(
+                    classNames[i], ServicePermission.REGISTER);
+                AccessController.checkPermission(perm);
+            }
+        }
+
+        // Acquire bundle lock.
+        try
+        {
+            acquireBundleLock(bundle);
+        }
+        catch (BundleException ex)
+        {
+            // This would probably only happen when the bundle is uninstalled.
+            throw new IllegalStateException(
+                "Can only register services while bundle is active or activating.");
+        }
+
+        ServiceRegistration reg = null;
+
+        try
+        {
+            BundleInfo info = bundle.getInfo();
+
+            // Can only register services if starting or active.
+            if ((info.getState() & (Bundle.STARTING | Bundle.ACTIVE)) == 0)
+            {
+                throw new IllegalStateException(
+                    "Can only register services while bundle is active or activating.");
+            }
+
+            // Check to make sure that the service object is
+            // an instance of all service classes; ignore if
+            // service object is a service factory.
+            if (!(svcObj instanceof ServiceFactory))
+            {
+                for (int i = 0; i < classNames.length; i++)
+                {
+                    Class clazz = loadClassUsingClass(svcObj.getClass(), classNames[i]);
+                    if (clazz == null)
+                    {
+                        throw new IllegalArgumentException(
+                            "Cannot cast service: " + classNames[i]);
+                    }
+                    else if (!clazz.isAssignableFrom(svcObj.getClass()))
+                    {
+                        throw new IllegalArgumentException(
+                            "Service object is not an instance of \""
+                            + classNames[i] + "\".");
+                    }
+                }
+            }
+
+            reg = m_registry.registerService(bundle, classNames, svcObj, dict);
+        }
+        finally
+        {
+            // Always release bundle lock.
+            releaseBundleLock(bundle);
+        }
+        
+        // NOTE: The service registered event is fired from the service
+        // registry to the framework, where it is then redistributed to
+        // interested service event listeners.
+
+        return reg;
+    }
+
+    /**
+     * <p>
+     * This is a simple utility class that attempts to load the named
+     * class using the class loader of the supplied class or
+     * the class loader of one of its super classes or their implemented
+     * interfaces. This is necessary during service registration to test
+     * whether a given service object implements its declared service
+     * interfaces.
+     * </p>
+     * <p>
+     * To perform this test, the framework must try to load
+     * the classes associated with the declared service interfaces, so
+     * it must choose a class loader. The class loader of the registering
+     * bundle cannot be used, since this disallows third parties to
+     * register service on behalf of another bundle. Consequently, the
+     * class loader of the service object must be used. However, this is
+     * also not sufficient since the class loader of the service object
+     * may not have direct access to the class in question.
+     * </p>
+     * <p>
+     * The service object's class loader may not have direct access to
+     * its service interface if it extends a super class from another
+     * bundle which implements the service interface from an imported
+     * bundle or if it implements an extension of the service interface
+     * from another bundle which imports the base interface from another
+     * bundle. In these cases, the service object's class loader only has
+     * access to the super class's class or the extended service interface,
+     * respectively, but not to the actual service interface.
+     * </p>
+     * <p>
+     * Thus, it is necessary to not only try to load the service interface
+     * class from the service object's class loader, but from the class
+     * loaders of any interfaces it implements and the class loaders of
+     * all super classes.
+     * </p>
+     * @param svcObj the class that is the root of the search.
+     * @param name the name of the class to load.
+     * @return the loaded class or <tt>null</tt> if it could not be
+     *         loaded.
+    **/
+    private static Class loadClassUsingClass(Class clazz, String name)
+    {
+        while (clazz != null)
+        {
+            // Get the class loader of the current class object.
+            ClassLoader loader = clazz.getClassLoader();
+            // A null class loader represents the system class loader.
+            loader = (loader == null) ? ClassLoader.getSystemClassLoader() : loader;
+            try
+            {
+                return loader.loadClass(name);
+            }
+            catch (ClassNotFoundException ex)
+            {
+                // Ignore and try interface class loaders.
+            }
+
+            // Try to see if we can load the class from
+            // one of the class's implemented interface
+            // class loaders.
+            Class[] ifcs = clazz.getInterfaces();
+            for (int i = 0; i < ifcs.length; i++)
+            {
+                clazz = loadClassUsingClass(ifcs[i], name);
+                if (clazz != null)
+                {
+                    return clazz;
+                }
+            }
+
+            // Try to see if we can load the class from
+            // the super class class loader.
+            clazz = clazz.getSuperclass();
+        }
+
+        return null;
+    }
+
+    protected ServiceReference[] getServiceReferences(
+        BundleImpl bundle, String className, String expr)
+        throws InvalidSyntaxException
+    {
+        // Define filter if expression is not null.
+        Filter filter = null;
+        if (expr != null)
+        {
+            filter = new FilterImpl(m_logger, expr);
+        }
+
+        // Ask the service registry for all matching service references.
+        List refList = m_registry.getServiceReferences(className, filter);
+
+        // The returned reference list must be filtered for two cases:
+        // 1) The requesting bundle may not be wired to the same class
+        //    as the providing bundle (i.e, different versions), so filter
+        //    any services for which the requesting bundle might get a
+        //    class cast exception.
+        // 2) Security is enabled and the requesting bundle does not have
+        //    permission access the service.
+        for (int refIdx = 0; (refList != null) && (refIdx < refList.size()); refIdx++)
+        {
+            // Get the current service reference.
+            ServiceReference ref = (ServiceReference) refList.get(refIdx);
+
+            // Get the service's objectClass property.
+            String[] objectClass = (String[]) ref.getProperty(FelixConstants.OBJECTCLASS);
+
+            // Boolean flag.
+            boolean allow = false;
+
+            // Filter the service reference if the requesting bundle
+            // does not have permission.
+            if (System.getSecurityManager() != null)
+            {
+                for (int classIdx = 0;
+                    !allow && (classIdx < objectClass.length);
+                    classIdx++)
+                {
+                    try
+                    {
+                        ServicePermission perm = new ServicePermission(
+                            objectClass[classIdx], ServicePermission.GET);
+                        AccessController.checkPermission(perm);
+                        // The bundle only needs permission for one
+                        // of the service interfaces, so break out
+                        // of the loop when permission is granted.
+                        allow = true;
+                    }
+                    catch (Exception ex)
+                    {
+                        // We do not throw this exception since the bundle
+                        // is not supposed to know about the service at all
+                        // if it does not have permission.
+                        m_logger.log(LogWrapper.LOG_ERROR, ex.getMessage());
+                    }
+                }
+                
+                if (!allow)
+                {
+                    refList.remove(refIdx);
+                    refIdx--;
+                    continue;
+                }
+            }
+
+            // Now check for castability.
+            if (!isServiceAssignable(bundle, ref))
+            {
+                refList.remove(refIdx);
+                refIdx--;
+            }
+        }
+
+        if (refList.size() > 0)
+        {
+            return (ServiceReference[]) refList.toArray(new ServiceReference[refList.size()]);
+        }
+
+        return null;
+    }
+
+    /**
+     * This method determines if the requesting bundle is able to cast
+     * the specified service reference based on class visibility rules
+     * of the underlying modules.
+     * @param requester The bundle requesting the service.
+     * @param ref The service in question.
+     * @return <tt>true</tt> if the requesting bundle is able to case
+     *         the service object to a known type.
+    **/
+    protected boolean isServiceAssignable(BundleImpl requester, ServiceReference ref)
+    {
+        // Boolean flag.
+        boolean allow = true;
+        // Get the service's objectClass property.
+        String[] objectClass = (String[]) ref.getProperty(FelixConstants.OBJECTCLASS);
+
+        // The the service reference is not assignable when the requesting
+        // bundle is wired to a different version of the service object.
+        // NOTE: We are pessimistic here, if any class in the service's
+        // objectClass is not usable by the requesting bundle, then we
+        // disallow the service reference.
+        for (int classIdx = 0; (allow) && (classIdx < objectClass.length); classIdx++)
+        {
+            if (!ref.isAssignableTo(requester, objectClass[classIdx]))
+            {
+                allow = false;
+            }
+        }
+        return allow;
+    }
+
+    protected Object getService(Bundle bundle, ServiceReference ref)
+    {
+        // Check that the bundle has permission to get at least
+        // one of the service interfaces; the objectClass property
+        // of the service stores its service interfaces.
+        String[] objectClass = (String[])
+            ref.getProperty(Constants.OBJECTCLASS);
+        if (objectClass == null)
+        {
+            return null;
+        }
+
+        boolean hasPermission = false;
+        if (System.getSecurityManager() != null)
+        {
+            for (int i = 0;
+                !hasPermission && (i < objectClass.length);
+                i++)
+            {
+                try
+                {
+                    ServicePermission perm =
+                        new ServicePermission(
+                            objectClass[i], ServicePermission.GET);
+                    AccessController.checkPermission(perm);
+                    hasPermission = true;
+                }
+                catch (Exception ex)
+                {
+                }
+            }
+        }
+        else
+        {
+            hasPermission = true;
+        }
+
+        // If the bundle does not permission to access the service,
+        // then return null.
+        if (!hasPermission)
+        {
+            return null;
+        }
+
+        return m_registry.getService(bundle, ref);
+    }
+
+    protected boolean ungetService(Bundle bundle, ServiceReference ref)
+    {
+        return m_registry.ungetService(bundle, ref);
+    }
+
+    protected File getDataFile(BundleImpl bundle, String s)
+    {
+        // The spec says to throw an error if the bundle
+        // is stopped, which I assume means not active,
+        // starting, or stopping.
+        if ((bundle.getInfo().getState() != Bundle.ACTIVE) &&
+            (bundle.getInfo().getState() != Bundle.STARTING) &&
+            (bundle.getInfo().getState() != Bundle.STOPPING))
+        {
+            throw new IllegalStateException("Only active bundles can create files.");
+        }
+        try
+        {
+            return m_cache.getArchive(
+                bundle.getInfo().getBundleId()).getDataFile(s);
+        }
+        catch (Exception ex)
+        {
+            m_logger.log(LogWrapper.LOG_ERROR, ex.getMessage());
+            return null;
+        }
+    }
+
+    //
+    // PackageAdmin related methods.
+    //
+
+    /**
+     * Returns the exported package associated with the specified
+     * package name. This is used by the PackageAdmin service
+     * implementation.
+     *
+     * @param name The name of the exported package to find.
+     * @return The exported package or null if no matching package was found.
+    **/
+    protected ExportedPackage getExportedPackage(String pkgName)
+    {
+        // First, find the bundle exporting the package.
+        BundleImpl bundle = null;
+        R4SearchPolicy search = (R4SearchPolicy) m_mgr.getSearchPolicy();
+        Module[] exporters = search.getInUseExporters(new R4Package(pkgName, null, null));
+        if (exporters != null)
+        {
+            // Since OSGi R4 there may be more than one exporting, so just
+            // take the first one.
+            bundle = (BundleImpl) getBundle(
+                BundleInfo.getBundleIdFromModuleId(exporters[0].getId()));
+        }
+
+        // If we have found the exporting bundle, then return the
+        // exported package interface instance.
+        if (bundle != null)
+        {
+            // We need to find the version of the exported package, but this
+            // is tricky since there may be multiple versions of the package
+            // offered by a given bundle, since multiple revisions of the
+            // bundle JAR file may exist if the bundle was updated without
+            // refreshing the framework. In this case, each revision of the
+            // bundle JAR file is represented as a module in the BundleInfo
+            // module array, which is ordered from oldest to newest. We assume
+            // that the first module found to be exporting the package is the
+            // provider of the package, which makes sense since it must have
+            // been resolved first.
+            Module[] modules = bundle.getInfo().getModules();
+            for (int modIdx = 0; modIdx < modules.length; modIdx++)
+            {

[... 1258 lines stripped ...]