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.<n></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.<n></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 ...]