Renamed packages to the new package structure and update source code to match.
git-svn-id: https://svn.apache.org/repos/asf/incubator/felix/trunk@233548 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/org/apache/felix/framework/Felix.java b/src/org/apache/felix/framework/Felix.java
new file mode 100644
index 0000000..94001ab
--- /dev/null
+++ b/src/org/apache/felix/framework/Felix.java
@@ -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.felix.framework;
+
+import java.io.*;
+import java.net.*;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.util.*;
+
+import org.apache.felix.framework.cache.*;
+import org.apache.felix.framework.searchpolicy.*;
+import org.apache.felix.framework.util.*;
+import org.apache.felix.framework.util.Util;
+import org.apache.felix.moduleloader.*;
+import org.apache.felix.moduleloader.search.ResolveException;
+import org.apache.felix.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++)
+ {
+ R4Package pkg = R4SearchPolicy.getExportPackage(modules[modIdx], pkgName);
+ if (pkg != null)
+ {
+ return new ExportedPackageImpl(this, bundle, pkgName, pkg.getVersionLow());
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns an array of all actively exported packages from the specified
+ * bundle or if the specified bundle is <tt>null</tt> an array
+ * containing all actively exported packages by all bundles.
+ *
+ * @param b The bundle whose exported packages are to be retrieved
+ * or <tt>null</tt> if the exported packages of all bundles are
+ * to be retrieved.
+ * @return An array of exported packages.
+ **/
+ protected ExportedPackage[] getExportedPackages(Bundle b)
+ {
+ List list = new ArrayList();
+
+ // If a bundle is specified, then return its
+ // exported packages.
+ if (b != null)
+ {
+ BundleImpl bundle = (BundleImpl) b;
+ getExportedPackages(bundle, list);
+ }
+ // Otherwise return all exported packages.
+ else
+ {
+ // To create a list of all exported packages, we must look
+ // in the installed and uninstalled sets of bundles. To
+ // ensure a somewhat consistent view, we will gather all
+ // of this information from within the installed bundle
+ // lock.
+ synchronized (m_installedBundleLock_Priority2)
+ {
+ // First get exported packages from uninstalled bundles.
+ synchronized (m_uninstalledBundlesLock_Priority3)
+ {
+ for (int bundleIdx = 0;
+ (m_uninstalledBundles != null) && (bundleIdx < m_uninstalledBundles.length);
+ bundleIdx++)
+ {
+ BundleImpl bundle = m_uninstalledBundles[bundleIdx];
+ getExportedPackages(bundle, list);
+ }
+ }
+
+ // Now get exported packages from installed bundles.
+ Bundle[] bundles = getBundles();
+ for (int bundleIdx = 0; bundleIdx < bundles.length; bundleIdx++)
+ {
+ BundleImpl bundle = (BundleImpl) bundles[bundleIdx];
+ getExportedPackages(bundle, list);
+ }
+ }
+ }
+
+ return (ExportedPackage[]) list.toArray(new ExportedPackage[list.size()]);
+ }
+
+ /**
+ * Adds any current active exported packages from the specified bundle
+ * to the passed in list.
+ * @param bundle The bundle from which to retrieve exported packages.
+ * @param list The list to which the exported packages are added
+ **/
+ private void getExportedPackages(BundleImpl bundle, List list)
+ {
+ R4SearchPolicy policy = (R4SearchPolicy) m_mgr.getSearchPolicy();
+
+ // Since a bundle may have many modules associated with it,
+ // one for each revision in the cache, search each module
+ // for each revision to get all exports.
+ Module[] modules = bundle.getInfo().getModules();
+ for (int modIdx = 0; modIdx < modules.length; modIdx++)
+ {
+ R4Package[] exports = R4SearchPolicy.getExportsAttr(modules[modIdx]);
+ if (exports.length > 0)
+ {
+ for (int expIdx = 0; expIdx < exports.length; expIdx++)
+ {
+ // See if the target bundle's module is one of the
+ // "in use" exporters of the package.
+ Module[] inUseModules = policy.getInUseExporters(exports[expIdx]);
+ if (R4SearchPolicy.isModuleInArray(inUseModules, modules[modIdx]))
+ {
+ list.add(new ExportedPackageImpl(
+ this, bundle, exports[expIdx].getId(), exports[expIdx].getVersionLow()));
+ }
+ }
+ }
+ }
+ }
+
+ protected Bundle[] getImportingBundles(ExportedPackage ep)
+ {
+ // Get exporting bundle; we need to use this internal
+ // method because the spec says ep.getExportingBundle()
+ // should return null if the package is stale.
+ BundleImpl exporter = (BundleImpl)
+ ((ExportedPackageImpl) ep).getExportingBundleInternal();
+ BundleInfo exporterInfo = exporter.getInfo();
+
+ // Create list for storing importing bundles.
+ List list = new ArrayList();
+ Bundle[] bundles = getBundles();
+
+ // Check all bundles to see who imports the package.
+ for (int bundleIdx = 0; bundleIdx < bundles.length; bundleIdx++)
+ {
+ BundleImpl importer = (BundleImpl) bundles[bundleIdx];
+
+ // Ignore the bundle if it imports from itself.
+ if (exporter != importer)
+ {
+ // Check the import wires of all modules for all bundles.
+ Module[] modules = importer.getInfo().getModules();
+ for (int modIdx = 0; modIdx < modules.length; modIdx++)
+ {
+ R4Wire wire = R4SearchPolicy.getWire(modules[modIdx], ep.getName());
+
+ // If the resolving module is associated with the
+ // exporting bundle, then add current bundle to
+ // import list.
+ if ((wire != null) && exporterInfo.hasModule(wire.m_module))
+ {
+ // Add the bundle to the list of importers.
+ list.add(bundles[bundleIdx]);
+ break;
+ }
+ }
+ }
+ }
+
+ // Return the results.
+ if (list.size() > 0)
+ {
+ return (Bundle[]) list.toArray(new Bundle[list.size()]);
+ }
+
+ return null;
+ }
+
+ protected void refreshPackages(Bundle[] targets)
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.checkPermission(m_adminPerm);
+ }
+
+ // Acquire locks for all impacted bundles.
+ BundleImpl[] bundles = acquireBundleRefreshLocks(targets);
+
+ // Remove any targeted bundles from the uninstalled bundles
+ // array, since they will be removed from the system after
+ // the refresh.
+ for (int i = 0; (bundles != null) && (i < bundles.length); i++)
+ {
+ forgetUninstalledBundle(bundles[i]);
+ }
+
+ try
+ {
+ // If there are targets, then refresh each one.
+ if (bundles != null)
+ {
+ // At this point the map contains every bundle that has been
+ // updated and/or removed as well as all bundles that import
+ // packages from these bundles.
+
+ // Create refresh helpers for each bundle.
+ RefreshHelper[] helpers = new RefreshHelper[bundles.length];
+ for (int i = 0; i < bundles.length; i++)
+ {
+ helpers[i] = new RefreshHelper(bundles[i]);
+ }
+
+ // Stop, purge or remove, and reinitialize all bundles first.
+ for (int i = 0; i < helpers.length; i++)
+ {
+ helpers[i].stop();
+ helpers[i].purgeOrRemove();
+ helpers[i].reinitialize();
+ }
+
+ // Then restart all bundles that were previously running.
+ for (int i = 0; i < helpers.length; i++)
+ {
+ helpers[i].restart();
+ }
+ }
+ }
+ finally
+ {
+ // Always release all bundle locks.
+ releaseBundleLocks(bundles);
+ }
+
+ fireFrameworkEvent(FrameworkEvent.PACKAGES_REFRESHED, getBundle(0), null);
+ }
+
+ private void populateImportGraph(BundleImpl target, Map map)
+ {
+ // Get the exported packages for the specified bundle.
+ ExportedPackage[] pkgs = getExportedPackages(target);
+
+ for (int pkgIdx = 0; (pkgs != null) && (pkgIdx < pkgs.length); pkgIdx++)
+ {
+ // Get all imports of this package.
+ Bundle[] importers = getImportingBundles(pkgs[pkgIdx]);
+
+ for (int impIdx = 0;
+ (importers != null) && (impIdx < importers.length);
+ impIdx++)
+ {
+ // Add each importing bundle to map.
+ map.put(importers[impIdx], importers[impIdx]);
+ // Now recurse into each bundle to get its importers.
+ populateImportGraph(
+ (BundleImpl) importers[impIdx], map);
+ }
+ }
+ }
+
+ //
+ // Miscellaneous private methods.
+ //
+
+ private BundleInfo createBundleInfo(BundleArchive archive)
+ throws Exception
+ {
+ // Get the bundle manifest.
+ Map headerMap = null;
+ try
+ {
+ // Although there should only ever be one revision at this
+ // point, get the header for the current revision to be safe.
+ headerMap = archive.getManifestHeader(archive.getRevisionCount() - 1);
+ }
+ catch (Exception ex)
+ {
+ throw new BundleException("Unable to read JAR manifest.", ex);
+ }
+
+ // We can't do anything without the manifest header.
+ if (headerMap == null)
+ {
+ throw new BundleException("Unable to read JAR manifest header.");
+ }
+
+ // Create the module for the bundle; although there should only
+ // ever be one revision at this point, create the module for
+ // the current revision to be safe.
+ Module module = createModule(
+ archive.getId(), archive.getRevisionCount() - 1, headerMap);
+
+ // Finally, create an return the bundle info.
+ return new BundleInfo(m_logger, archive, module);
+ }
+
+ /**
+ * Creates a module for a given bundle by reading the bundle's
+ * manifest meta-data and converting it to work with the underlying
+ * import/export search policy of the module loader.
+ * @param id The identifier of the bundle for which the module should
+ * be created.
+ * @param headers The headers map associated with the bundle.
+ * @return The initialized and/or newly created module.
+ **/
+ private Module createModule(long id, int revision, Map headerMap)
+ throws Exception
+ {
+ // Get the manifest version.
+ String version = (String) headerMap.get(FelixConstants.BUNDLE_MANIFESTVERSION);
+ version = (version == null) ? "1" : version;
+ if (!version.equals("1") && !version.equals("2"))
+ {
+ throw new BundleException("Unknown 'Bundle-ManifestVersion' value: " + version);
+ }
+
+ // Create the resource sources for the bundle. The resource sources
+ // are comprised of the bundle's class path values (as JarResourceSources).
+ ResourceSource[] resSources = null;
+ try
+ {
+ // Get bundle class path for the specified revision from cache.
+ String[] classPath = m_cache.getArchive(id).getClassPath(revision);
+
+ // Create resource sources for everything.
+ resSources = new ResourceSource[classPath.length];
+ for (int i = 0; i < classPath.length; i++)
+ {
+ resSources[i] = new JarResourceSource(classPath[i]);
+ }
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ throw new BundleException("Error in class path: " + ex);
+ }
+
+ // Get import packages from bundle manifest.
+ R4Package[] imports = R4Package.parseImportOrExportHeader(
+ (String) headerMap.get(Constants.IMPORT_PACKAGE));
+
+
+ // Check to make sure that R3 bundles have only specified
+ // the 'specification-version' attribute and no directives.
+ if (version.equals("1"))
+ {
+ for (int i = 0; (imports != null) && (i < imports.length); i++)
+ {
+ if (imports[i].getDirectives().length != 0)
+ {
+ throw new BundleException("R3 imports cannot contain directives.");
+ }
+ // NOTE: This is checking for "version" rather than "specification-version"
+ // because the package class normalizes to "version" to avoid having
+ // future special cases. This could be changed if more strict behavior
+ // is required.
+ if ((imports[i].getVersionHigh() != null) ||
+ (imports[i].getAttributes().length > 1) ||
+ ((imports[i].getAttributes().length == 1) &&
+ (!imports[i].getAttributes()[0].getName().equals(FelixConstants.VERSION_ATTRIBUTE))))
+ {
+ throw new BundleException(
+ "Import does not conform to R3 syntax: " + imports[i]);
+ }
+ }
+ }
+
+ // Get export packages from bundle manifest.
+ R4Package[] exports = R4Package.parseImportOrExportHeader(
+ (String) headerMap.get(Constants.EXPORT_PACKAGE));
+
+ // Check to make sure that R3 bundles have only specified
+ // the 'specification-version' attribute and no directives.
+ // In addition, all R3 exports imply imports, so add a
+ // corresponding import for each export.
+ if (version.equals("1"))
+ {
+ for (int i = 0; (exports != null) && (i < exports.length); i++)
+ {
+ if (exports[i].getDirectives().length != 0)
+ {
+ throw new BundleException("R3 exports cannot contain directives.");
+ }
+ // NOTE: This is checking for "version" rather than "specification-version"
+ // because the package class normalizes to "version" to avoid having
+ // future special cases. This could be changed if more strict behavior
+ // is required.
+ if ((exports[i].getAttributes().length > 1) ||
+ ((exports[i].getAttributes().length == 1) &&
+ (!exports[i].getAttributes()[0].getName().equals(FelixConstants.VERSION_ATTRIBUTE))))
+ {
+ throw new BundleException(
+ "Export does not conform to R3 syntax: " + imports[i]);
+ }
+ }
+
+ R4Package[] newImports = new R4Package[imports.length + exports.length];
+ System.arraycopy(imports, 0, newImports, 0, imports.length);
+ System.arraycopy(exports, 0, newImports, imports.length, exports.length);
+ imports = newImports;
+ }
+
+ // For R3 bundles, add a "uses" directive onto each export
+ // that references every other import (which will include
+ // exports, since export implies import); this is
+ // necessary since R3 bundles assumed a single class space,
+ // but R4 allows for multiple class spaces.
+ if (version.equals("1"))
+ {
+ String usesValue = "";
+ for (int i = 0; (imports != null) && (i < imports.length); i++)
+ {
+ usesValue = usesValue
+ + ((usesValue.length() > 0) ? "," : "")
+ + imports[i].getId();
+ }
+ R4Directive uses = new R4Directive(
+ FelixConstants.USES_DIRECTIVE, usesValue);
+ for (int i = 0; (exports != null) && (i < exports.length); i++)
+ {
+ exports[i] = new R4Package(
+ exports[i].getId(),
+ new R4Directive[] { uses },
+ exports[i].getAttributes());
+ }
+ }
+
+// TODO: CHECK FOR DUPLICATE IMPORTS/EXPORTS HERE.
+
+ // Get dynamic import packages from bundle manifest.
+ R4Package[] dynamics = R4Package.parseImportOrExportHeader(
+ (String) headerMap.get(Constants.DYNAMICIMPORT_PACKAGE));
+
+ // Check to make sure that R3 bundles have no attributes or
+ // directives.
+ if (version.equals("1"))
+ {
+ for (int i = 0; (dynamics != null) && (i < dynamics.length); i++)
+ {
+ if (dynamics[i].getDirectives().length != 0)
+ {
+ throw new BundleException("R3 dynamic imports cannot contain directives.");
+ }
+ if (dynamics[i].getAttributes().length != 0)
+ {
+ throw new BundleException("R3 dynamic imports cannot contain attributes.");
+ }
+ }
+ }
+
+ Object[][] attributes = {
+ new Object[] { R4SearchPolicy.EXPORTS_ATTR, exports },
+ new Object[] { R4SearchPolicy.IMPORTS_ATTR, imports },
+ new Object[] { R4SearchPolicy.DYNAMICIMPORTS_ATTR, dynamics }
+ };
+
+ // Get native library entry names for module library sources.
+ LibraryInfo[] libraries =
+ Util.parseLibraryStrings(
+ Util.parseDelimitedString(
+ (String) headerMap.get(Constants.BUNDLE_NATIVECODE), ","));
+ LibrarySource[] libSources = {
+ new OSGiLibrarySource(
+ m_logger, m_cache, id, revision,
+ getProperty(Constants.FRAMEWORK_OS_NAME),
+ getProperty(Constants.FRAMEWORK_PROCESSOR),
+ libraries)
+ };
+
+ Module module =
+ m_mgr.addModule(
+ Long.toString(id) + "." + Integer.toString(revision),
+ attributes, resSources, libSources);
+
+ return module;
+ }
+
+ private BundleActivator createBundleActivator(BundleInfo info)
+ throws Exception
+ {
+ // CONCURRENCY NOTE:
+ // This method is called indirectly from startBundle() (via _startBundle()),
+ // which has the exclusion lock, so there is no need to do any locking here.
+
+ BundleActivator activator = null;
+
+ String strict = m_config.get(FelixConstants.STRICT_OSGI_PROP);
+ boolean isStrict = (strict == null) ? true : strict.equals("true");
+ if (!isStrict)
+ {
+ try
+ {
+ activator =
+ m_cache.getArchive(info.getBundleId())
+ .getActivator(info.getCurrentModule().getClassLoader());
+ }
+ catch (Exception ex)
+ {
+ activator = null;
+ }
+ }
+
+ // If there was no cached activator, then get the activator
+ // class from the bundle manifest.
+ if (activator == null)
+ {
+ // Get the associated bundle archive.
+ BundleArchive ba = m_cache.getArchive(info.getBundleId());
+ // Get the manifest from the current revision; revision is
+ // base zero so subtract one from the count to get the
+ // current revision.
+ Map headerMap = ba.getManifestHeader(ba.getRevisionCount() - 1);
+ // Get the activator class attribute.
+ String className = (String) headerMap.get(Constants.BUNDLE_ACTIVATOR);
+ // Try to instantiate activator class if present.
+ if (className != null)
+ {
+ className = className.trim();
+ Class clazz = info.getCurrentModule().getClassLoader().loadClass(className);
+ if (clazz == null)
+ {
+ throw new BundleException("Not found: "
+ + className);
+ }
+ activator = (BundleActivator) clazz.newInstance();
+ }
+ }
+
+ return activator;
+ }
+
+ private void purgeBundle(BundleImpl bundle) throws Exception
+ {
+ // Acquire bundle lock.
+ acquireBundleLock(bundle);
+
+ try
+ {
+ BundleInfo info = bundle.getInfo();
+
+ // In case of a refresh, then we want to physically
+ // remove the bundle's modules from the module manager.
+ // This is necessary for two reasons: 1) because
+ // under Windows we won't be able to delete the bundle
+ // because files might be left open in the resource
+ // sources of its modules and 2) we want to make sure
+ // that no references to old modules exist since they
+ // will all be stale after the refresh. The only other
+ // way to do this is to remove the bundle, but that
+ // would be incorrect, because this is a refresh operation
+ // and should not trigger bundle REMOVE events.
+ Module[] modules = info.getModules();
+ for (int i = 0; i < modules.length; i++)
+ {
+ m_mgr.removeModule(modules[i]);
+ }
+
+ // Purge all bundle revisions, but the current one.
+ m_cache.purge(m_cache.getArchive(info.getBundleId()));
+ }
+ finally
+ {
+ // Always release the bundle lock.
+ releaseBundleLock(bundle);
+ }
+ }
+
+ private void garbageCollectBundle(BundleImpl bundle) throws Exception
+ {
+ // CONCURRENCY NOTE: There is no reason to lock this bundle,
+ // because this method is only called during shutdown or a
+ // refresh operation and these are already guarded by locks.
+
+ // Remove the bundle's associated modules from
+ // the module manager.
+ Module[] modules = bundle.getInfo().getModules();
+ for (int i = 0; i < modules.length; i++)
+ {
+ m_mgr.removeModule(modules[i]);
+ }
+
+ // Remove the bundle from the cache.
+ m_cache.remove(m_cache.getArchive(bundle.getInfo().getBundleId()));
+ }
+
+ //
+ // Event-related methods.
+ //
+
+ /**
+ * Fires bundle events.
+ **/
+ private void fireFrameworkEvent(
+ int type, Bundle bundle, Throwable throwable)
+ {
+ if (m_frameworkDispatcher == null)
+ {
+ m_frameworkDispatcher = new Dispatcher() {
+ public void dispatch(EventListener l, EventObject eventObj)
+ {
+ ((FrameworkListener) l)
+ .frameworkEvent((FrameworkEvent) eventObj);
+ }
+ };
+ }
+ FrameworkEvent event = new FrameworkEvent(type, bundle, throwable);
+ m_dispatchQueue.dispatch(
+ m_frameworkDispatcher, FrameworkListener.class, event);
+ }
+
+ /**
+ * Fires bundle events.
+ *
+ * @param type The type of bundle event to fire.
+ * @param bundle The bundle associated with the event.
+ **/
+ private void fireBundleEvent(int type, Bundle bundle)
+ {
+ if (m_bundleDispatcher == null)
+ {
+ m_bundleDispatcher = new Dispatcher() {
+ public void dispatch(EventListener l, EventObject eventObj)
+ {
+ ((BundleListener) l)
+ .bundleChanged((BundleEvent) eventObj);
+ }
+ };
+ }
+ BundleEvent event = null;
+ event = new BundleEvent(type, bundle);
+ m_dispatchQueue.dispatch(m_bundleDispatcher,
+ BundleListener.class, event);
+ }
+
+ /**
+ * Fires service events.
+ *
+ * @param type The type of service event to fire.
+ * @param ref The service reference associated with the event.
+ **/
+ private void fireServiceEvent(ServiceEvent event)
+ {
+ if (m_serviceDispatcher == null)
+ {
+ m_serviceDispatcher = new Dispatcher() {
+ public void dispatch(EventListener l, EventObject eventObj)
+ {
+// TODO: Filter service events based on service permissions.
+ if (l instanceof ListenerWrapper)
+ {
+ BundleImpl bundle = (BundleImpl) ((ServiceListenerWrapper) l).getBundle();
+ if (isServiceAssignable(bundle, ((ServiceEvent) eventObj).getServiceReference()))
+ {
+ ((ServiceListener) l)
+ .serviceChanged((ServiceEvent) eventObj);
+ }
+ }
+ else
+ {
+ ((ServiceListener) l)
+ .serviceChanged((ServiceEvent) eventObj);
+ }
+ }
+ };
+ }
+ m_dispatchQueue.dispatch(m_serviceDispatcher,
+ ServiceListener.class, event);
+ }
+
+ //
+ // Property related methods.
+ //
+
+ private void initializeFrameworkProperties()
+ {
+ // Standard OSGi properties.
+ m_configProps.put(
+ FelixConstants.FRAMEWORK_VERSION,
+ FelixConstants.FRAMEWORK_VERSION_VALUE);
+ m_configProps.put(
+ FelixConstants.FRAMEWORK_VENDOR,
+ FelixConstants.FRAMEWORK_VENDOR_VALUE);
+ m_configProps.put(
+ FelixConstants.FRAMEWORK_LANGUAGE,
+ System.getProperty("user.language"));
+ m_configProps.put(
+ FelixConstants.FRAMEWORK_OS_VERSION,
+ System.getProperty("os.version"));
+
+ String s = null;
+ s = OSGiLibrarySource.normalizePropertyValue(
+ FelixConstants.FRAMEWORK_OS_NAME,
+ System.getProperty("os.name"));
+ m_configProps.put(FelixConstants.FRAMEWORK_OS_NAME, s);
+ s = OSGiLibrarySource.normalizePropertyValue(
+ FelixConstants.FRAMEWORK_PROCESSOR,
+ System.getProperty("os.arch"));
+ m_configProps.put(FelixConstants.FRAMEWORK_PROCESSOR, s);
+
+ // The framework version property.
+ m_configProps.put(
+ FelixConstants.FELIX_VERSION_PROPERTY,
+ FelixConstants.FELIX_VERSION_VALUE);
+ }
+
+ private void processAutoProperties()
+ {
+ // The auto-install property specifies a space-delimited list of
+ // bundle URLs to be automatically installed into each new profile;
+ // the start level to which the bundles are assigned is specified by
+ // appending a ".n" to the auto-install property name, where "n" is
+ // the desired start level for the list of bundles.
+ String[] keys = m_config.getKeys();
+ for (int i = 0; (keys != null) && (i < keys.length); i++)
+ {
+ if (keys[i].startsWith(FelixConstants.AUTO_INSTALL_PROP))
+ {
+ int startLevel = 1;
+ try
+ {
+ startLevel = Integer.parseInt(keys[i].substring(keys[i].lastIndexOf('.') + 1));
+ }
+ catch (NumberFormatException ex)
+ {
+ m_logger.log(LogWrapper.LOG_ERROR, "Invalid property: " + keys[i]);
+ }
+ StringTokenizer st = new StringTokenizer(m_config.get(keys[i]), "\" ",true);
+ if (st.countTokens() > 0)
+ {
+ String location = null;
+ do
+ {
+ location = nextLocation(st);
+ if (location != null)
+ {
+ try
+ {
+ BundleImpl b = (BundleImpl) installBundle(location, null);
+ b.getInfo().setStartLevel(startLevel);
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR, "Auto-properties install.", ex);
+ }
+ }
+ }
+ while (location != null);
+ }
+ }
+ }
+
+ // The auto-start property specifies a space-delimited list of
+ // bundle URLs to be automatically installed and started into each
+ // new profile; the start level to which the bundles are assigned
+ // is specified by appending a ".n" to the auto-start property name,
+ // where "n" is the desired start level for the list of bundles.
+ // The following code starts bundles in two passes, first it installs
+ // them, then it starts them.
+ for (int i = 0; (keys != null) && (i < keys.length); i++)
+ {
+ if (keys[i].startsWith(FelixConstants.AUTO_START_PROP))
+ {
+ int startLevel = 1;
+ try
+ {
+ startLevel = Integer.parseInt(keys[i].substring(keys[i].lastIndexOf('.') + 1));
+ }
+ catch (NumberFormatException ex)
+ {
+ m_logger.log(LogWrapper.LOG_ERROR, "Invalid property: " + keys[i]);
+ }
+ StringTokenizer st = new StringTokenizer(m_config.get(keys[i]), "\" ",true);
+ if (st.countTokens() > 0)
+ {
+ String location = null;
+ do
+ {
+ location = nextLocation(st);
+ if (location != null)
+ {
+ try
+ {
+ BundleImpl b = (BundleImpl) installBundle(location, null);
+ b.getInfo().setStartLevel(startLevel);
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(LogWrapper.LOG_ERROR, "Auto-properties install.", ex);
+ }
+ }
+ }
+ while (location != null);
+ }
+ }
+ }
+
+ // Now loop through and start the installed bundles.
+ for (int i = 0; (keys != null) && (i < keys.length); i++)
+ {
+ if (keys[i].startsWith(FelixConstants.AUTO_START_PROP))
+ {
+ StringTokenizer st = new StringTokenizer(m_config.get(keys[i]), "\" ",true);
+ if (st.countTokens() > 0)
+ {
+ String location = null;
+ do
+ {
+ location = nextLocation(st);
+ if (location != null)
+ {
+ // Installing twice just returns the same bundle.
+ try
+ {
+ BundleImpl bundle = (BundleImpl) installBundle(location, null);
+ if (bundle != null)
+ {
+ startBundle(bundle, true);
+ }
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR, "Auto-properties start.", ex);
+ }
+ }
+ }
+ while (location != null);
+ }
+ }
+ }
+ }
+
+ private String nextLocation(StringTokenizer st)
+ {
+ String retVal = null;
+
+ if (st.countTokens() > 0)
+ {
+ String tokenList = "\" ";
+ StringBuffer tokBuf = new StringBuffer(10);
+ String tok = null;
+ boolean inQuote = false;
+ boolean tokStarted = false;
+ boolean exit = false;
+ while ((st.hasMoreTokens()) && (!exit))
+ {
+ tok = st.nextToken(tokenList);
+ if (tok.equals("\""))
+ {
+ inQuote = ! inQuote;
+ if (inQuote)
+ {
+ tokenList = "\"";
+ }
+ else
+ {
+ tokenList = "\" ";
+ }
+
+ }
+ else if (tok.equals(" "))
+ {
+ if (tokStarted)
+ {
+ retVal = tokBuf.toString();
+ tokStarted=false;
+ tokBuf = new StringBuffer(10);
+ exit = true;
+ }
+ }
+ else
+ {
+ tokStarted = true;
+ tokBuf.append(tok.trim());
+ }
+ }
+
+ // Handle case where end of token stream and
+ // still got data
+ if ((!exit) && (tokStarted))
+ {
+ retVal = tokBuf.toString();
+ }
+ }
+
+ return retVal;
+ }
+
+ //
+ // Private utility methods.
+ //
+
+ /**
+ * Generated the next valid bundle identifier.
+ **/
+ private synchronized long getNextId()
+ {
+ return m_nextId++;
+ }
+
+ //
+ // Configuration methods and inner classes.
+ //
+
+ public PropertyResolver getConfig()
+ {
+ return m_config;
+ }
+
+ private class ConfigImpl implements PropertyResolver
+ {
+ public String get(String key)
+ {
+ return (m_configProps == null) ? null : m_configProps.get(key);
+ }
+
+ public String[] getKeys()
+ {
+ return m_configProps.getKeys();
+ }
+ }
+
+ //
+ // Logging methods and inner classes.
+ //
+
+ public LogWrapper getLogger()
+ {
+ return m_logger;
+ }
+
+ /**
+ * Simple class that is used in <tt>refreshPackages()</tt> to embody
+ * the refresh logic in order to keep the code clean. This class is
+ * not static because it needs access to framework event firing methods.
+ **/
+ private class RefreshHelper
+ {
+ private BundleImpl m_bundle = null;
+
+ public RefreshHelper(Bundle bundle)
+ {
+ m_bundle = (BundleImpl) bundle;
+ }
+
+ public void stop()
+ {
+ if (m_bundle.getInfo().getState() == Bundle.ACTIVE)
+ {
+ try
+ {
+ stopBundle(m_bundle, false);
+ }
+ catch (BundleException ex)
+ {
+ fireFrameworkEvent(FrameworkEvent.ERROR, m_bundle, ex);
+ }
+ }
+ }
+
+ public void purgeOrRemove()
+ {
+ try
+ {
+ BundleInfo info = m_bundle.getInfo();
+
+ // Remove or purge the bundle depending on its
+ // current state.
+ if (info.getState() == Bundle.UNINSTALLED)
+ {
+ // This physically removes the bundle from memory
+ // as well as the bundle cache.
+ garbageCollectBundle(m_bundle);
+ m_bundle = null;
+ }
+ else
+ {
+ // This physically removes all old revisions of the
+ // bundle from memory and only maintains the newest
+ // version in the bundle cache.
+ purgeBundle(m_bundle);
+ }
+ }
+ catch (Exception ex)
+ {
+ fireFrameworkEvent(FrameworkEvent.ERROR, m_bundle, ex);
+ }
+ }
+
+ public void reinitialize()
+ {
+ if (m_bundle != null)
+ {
+ try
+ {
+ BundleInfo info = m_bundle.getInfo();
+ BundleInfo newInfo = createBundleInfo(info.getArchive());
+ newInfo.syncLock(info);
+ m_bundle.setInfo(newInfo);
+ }
+ catch (Exception ex)
+ {
+ fireFrameworkEvent(FrameworkEvent.ERROR, m_bundle, ex);
+ }
+ }
+ }
+
+ public void restart()
+ {
+ if (m_bundle != null)
+ {
+ try
+ {
+ startBundle(m_bundle, false);
+ }
+ catch (BundleException ex)
+ {
+ fireFrameworkEvent(FrameworkEvent.ERROR, m_bundle, ex);
+ }
+ }
+ }
+ }
+
+ //
+ // Locking related methods.
+ //
+
+ private void rememberUninstalledBundle(BundleImpl bundle)
+ {
+ synchronized (m_uninstalledBundlesLock_Priority3)
+ {
+ // Verify that the bundle is not already in the array.
+ for (int i = 0;
+ (m_uninstalledBundles != null) && (i < m_uninstalledBundles.length);
+ i++)
+ {
+ if (m_uninstalledBundles[i] == bundle)
+ {
+ return;
+ }
+ }
+
+ if (m_uninstalledBundles != null)
+ {
+ BundleImpl[] newBundles =
+ new BundleImpl[m_uninstalledBundles.length + 1];
+ System.arraycopy(m_uninstalledBundles, 0,
+ newBundles, 0, m_uninstalledBundles.length);
+ newBundles[m_uninstalledBundles.length] = bundle;
+ m_uninstalledBundles = newBundles;
+ }
+ else
+ {
+ m_uninstalledBundles = new BundleImpl[] { bundle };
+ }
+ }
+ }
+
+ private void forgetUninstalledBundle(BundleImpl bundle)
+ {
+ synchronized (m_uninstalledBundlesLock_Priority3)
+ {
+ if (m_uninstalledBundles == null)
+ {
+ return;
+ }
+
+ int idx = -1;
+ for (int i = 0; i < m_uninstalledBundles.length; i++)
+ {
+ if (m_uninstalledBundles[i] == bundle)
+ {
+ idx = i;
+ break;
+ }
+ }
+
+ if (idx >= 0)
+ {
+ // If this is the only bundle, then point to empty list.
+ if ((m_uninstalledBundles.length - 1) == 0)
+ {
+ m_uninstalledBundles = new BundleImpl[0];
+ }
+ // Otherwise, we need to do some array copying.
+ else
+ {
+ BundleImpl[] newBundles =
+ new BundleImpl[m_uninstalledBundles.length - 1];
+ System.arraycopy(m_uninstalledBundles, 0, newBundles, 0, idx);
+ if (idx < newBundles.length)
+ {
+ System.arraycopy(
+ m_uninstalledBundles, idx + 1,
+ newBundles, idx, newBundles.length - idx);
+ }
+ m_uninstalledBundles = newBundles;
+ }
+ }
+ }
+ }
+
+ protected void acquireInstallLock(String location)
+ throws BundleException
+ {
+ synchronized (m_installRequestLock_Priority1)
+ {
+ while (m_installRequestMap.get(location) != null)
+ {
+ try
+ {
+ m_installRequestLock_Priority1.wait();
+ }
+ catch (InterruptedException ex)
+ {
+ throw new BundleException("Unable to install, thread interrupted.");
+ }
+ }
+
+ m_installRequestMap.put(location, location);
+ }
+ }
+
+ protected void releaseInstallLock(String location)
+ {
+ synchronized (m_installRequestLock_Priority1)
+ {
+ m_installRequestMap.remove(location);
+ m_installRequestLock_Priority1.notifyAll();
+ }
+ }
+
+ protected void acquireBundleLock(BundleImpl bundle)
+ throws BundleException
+ {
+ synchronized (m_bundleLock)
+ {
+ while (!bundle.getInfo().isLockable())
+ {
+ try
+ {
+ m_bundleLock.wait();
+ }
+ catch (InterruptedException ex)
+ {
+ // Ignore and just keep waiting.
+ }
+ }
+ bundle.getInfo().lock();
+ }
+ }
+
+ protected void releaseBundleLock(BundleImpl bundle)
+ {
+ synchronized (m_bundleLock)
+ {
+ bundle.getInfo().unlock();
+ m_bundleLock.notifyAll();
+ }
+ }
+
+ protected BundleImpl[] acquireBundleRefreshLocks(Bundle[] targets)
+ {
+ // Hold bundles to be locked.
+ BundleImpl[] bundles = null;
+
+ synchronized (m_bundleLock)
+ {
+ boolean success = false;
+ while (!success)
+ {
+ // If targets is null, then refresh all pending bundles.
+ Bundle[] newTargets = targets;
+ if (newTargets == null)
+ {
+ List list = new ArrayList();
+
+ // First add all uninstalled bundles.
+ synchronized (m_uninstalledBundlesLock_Priority3)
+ {
+ for (int i = 0;
+ (m_uninstalledBundles != null) && (i < m_uninstalledBundles.length);
+ i++)
+ {
+ list.add(m_uninstalledBundles[i]);
+ }
+ }
+
+ // Then add all updated bundles.
+ synchronized (m_installedBundleLock_Priority2)
+ {
+ Iterator iter = m_installedBundleMap.values().iterator();
+ while (iter.hasNext())
+ {
+ BundleImpl bundle = (BundleImpl) iter.next();
+ if (bundle.getInfo().isRemovalPending())
+ {
+ list.add(bundle);
+ }
+ }
+ }
+
+ // Create an array.
+ if (list.size() > 0)
+ {
+ newTargets = (Bundle[]) list.toArray(new Bundle[list.size()]);
+ }
+ }
+
+ // If there are targets, then find all dependencies
+ // for each one.
+ if (newTargets != null)
+ {
+ // Create map of bundles that import the packages
+ // from the target bundles.
+ Map map = new HashMap();
+ for (int targetIdx = 0; targetIdx < newTargets.length; targetIdx++)
+ {
+ // Add the current target bundle to the map of
+ // bundles to be refreshed.
+ BundleImpl target = (BundleImpl) newTargets[targetIdx];
+ map.put(target, target);
+ // Add all importing bundles to map.
+ populateImportGraph(target, map);
+ }
+
+ bundles = (BundleImpl[]) map.values().toArray(new BundleImpl[map.size()]);
+ }
+
+ // Check if all corresponding bundles can be locked
+ boolean lockable = true;
+ if (bundles != null)
+ {
+ for (int i = 0; lockable && (i < bundles.length); i++)
+ {
+ lockable = bundles[i].getInfo().isLockable();
+ }
+
+ // If we can lock all bundles, then lock them.
+ if (lockable)
+ {
+ for (int i = 0; i < bundles.length; i++)
+ {
+ bundles[i].getInfo().lock();
+ }
+ success = true;
+ }
+ // Otherwise, wait and try again.
+ else
+ {
+ try
+ {
+ m_bundleLock.wait();
+ }
+ catch (InterruptedException ex)
+ {
+ // Ignore and just keep waiting.
+ }
+ }
+ }
+ else
+ {
+ // If there were no bundles to lock, then we can just
+ // exit the lock loop.
+ success = true;
+ }
+ }
+ }
+
+ return bundles;
+ }
+
+ protected void releaseBundleLocks(BundleImpl[] bundles)
+ {
+ // Always unlock any locked bundles.
+ synchronized (m_bundleLock)
+ {
+ for (int i = 0; (bundles != null) && (i < bundles.length); i++)
+ {
+ bundles[i].getInfo().unlock();
+ }
+ m_bundleLock.notifyAll();
+ }
+ }
+}