All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.osgi.container.ModuleContainer Maven / Gradle / Ivy

There is a newer version: 1.9.22.1
Show newest version
/*******************************************************************************
 * Copyright (c) 2012, 2021 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.osgi.container;

import java.io.Closeable;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.eclipse.osgi.container.Module.StartOptions;
import org.eclipse.osgi.container.Module.State;
import org.eclipse.osgi.container.Module.StopOptions;
import org.eclipse.osgi.container.ModuleContainer.ResolutionLock.Permits;
import org.eclipse.osgi.container.ModuleContainerAdaptor.ContainerEvent;
import org.eclipse.osgi.container.ModuleContainerAdaptor.ModuleEvent;
import org.eclipse.osgi.container.ModuleDatabase.Sort;
import org.eclipse.osgi.container.ModuleRequirement.DynamicModuleRequirement;
import org.eclipse.osgi.framework.eventmgr.CopyOnWriteIdentityMap;
import org.eclipse.osgi.framework.eventmgr.EventDispatcher;
import org.eclipse.osgi.framework.eventmgr.EventManager;
import org.eclipse.osgi.framework.eventmgr.ListenerQueue;
import org.eclipse.osgi.framework.util.SecureAction;
import org.eclipse.osgi.framework.util.ThreadInfoReport;
import org.eclipse.osgi.internal.container.InternalUtils;
import org.eclipse.osgi.internal.container.LockSet;
import org.eclipse.osgi.internal.container.NamespaceList;
import org.eclipse.osgi.internal.debug.Debug;
import org.eclipse.osgi.internal.framework.EquinoxConfiguration;
import org.eclipse.osgi.internal.messages.Msg;
import org.eclipse.osgi.report.resolution.ResolutionReport;
import org.eclipse.osgi.report.resolution.ResolutionReport.Entry;
import org.eclipse.osgi.service.debug.DebugOptions;
import org.eclipse.osgi.service.debug.DebugOptionsListener;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.AdminPermission;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.Version;
import org.osgi.framework.namespace.HostNamespace;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.framework.namespace.PackageNamespace;
import org.osgi.framework.startlevel.FrameworkStartLevel;
import org.osgi.framework.wiring.BundleCapability;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.FrameworkWiring;
import org.osgi.resource.Namespace;
import org.osgi.resource.Requirement;
import org.osgi.resource.Resource;
import org.osgi.resource.Wire;
import org.osgi.service.resolver.ResolutionException;

/**
 * A container for installing, updating, uninstalling and resolve modules.
 * @since 3.10
 */
public final class ModuleContainer implements DebugOptionsListener {
	private final static SecureAction secureAction = AccessController.doPrivileged(SecureAction.createSecureAction());

	/**
	 * Used by install operations to establish a write lock on an install location
	 */
	private final LockSet locationLocks = new LockSet<>();

	/**
	 * Used by install and update operations to establish a write lock for a name
	 */
	private final LockSet nameLocks = new LockSet<>();

	/**
	 * An implementation of FrameworkWiring for this container
	 */
	private final ContainerWiring frameworkWiring;

	/**
	 * An implementation of FrameworkStartLevel for this container
	 */
	private final ContainerStartLevel frameworkStartLevel;

	/**
	 * The module database for this container.
	 */
	final ModuleDatabase moduleDatabase;

	/**
	 * The module adaptor for this container.
	 */
	final ModuleContainerAdaptor adaptor;

	/**
	 * The module resolver which implements the ResolverContext and handles calling the
	 * resolver service.
	 */
	private final ModuleResolver moduleResolver;

	/**
	 * Holds the system module while it is being refreshed
	 */
	private final AtomicReference refreshingSystemModule = new AtomicReference<>();

	private final long moduleLockTimeout;

	private final boolean autoStartOnResolve;

	final boolean restrictParallelStart;

	boolean DEBUG_MONITOR_LAZY = false;
	boolean DEBUG_BUNDLE_START_TIME = false;

	/**
	 * Constructs a new container with the specified adaptor, module database.
	 * @param adaptor the adaptor for the container
	 * @param moduledataBase the module database
	 */
	public ModuleContainer(ModuleContainerAdaptor adaptor, ModuleDatabase moduledataBase) {
		this.adaptor = adaptor;
		this.moduleResolver = new ModuleResolver(adaptor);
		this.moduleDatabase = moduledataBase;
		this.frameworkWiring = new ContainerWiring();
		this.frameworkStartLevel = new ContainerStartLevel();
		long tempModuleLockTimeout = 30;
		String moduleLockTimeoutProp = adaptor.getProperty(EquinoxConfiguration.PROP_MODULE_LOCK_TIMEOUT);
		if (moduleLockTimeoutProp != null) {
			try {
				tempModuleLockTimeout = Long.parseLong(moduleLockTimeoutProp);
				// don't do anything less than one second
				if (tempModuleLockTimeout < 1) {
					tempModuleLockTimeout = 1;
				}
			} catch (NumberFormatException e) {
				// will default to 30
			}
		}
		this.moduleLockTimeout = tempModuleLockTimeout;
		DebugOptions debugOptions = adaptor.getDebugOptions();
		if (debugOptions != null) {
			this.DEBUG_MONITOR_LAZY = debugOptions.getBooleanOption(Debug.OPTION_MONITOR_LAZY, false);
		}

		String autoStartOnResolveProp = adaptor.getProperty(EquinoxConfiguration.PROP_MODULE_AUTO_START_ON_RESOLVE);
		if (autoStartOnResolveProp == null) {
			autoStartOnResolveProp = Boolean.toString(true);
		}
		this.autoStartOnResolve = Boolean.parseBoolean(autoStartOnResolveProp);
		this.restrictParallelStart = Boolean.parseBoolean(adaptor.getProperty(EquinoxConfiguration.PROP_EQUINOX_START_LEVEL_RESTRICT_PARALLEL));
	}

	/**
	 * Returns the adaptor for this container
	 * @return the adaptor for this container
	 */
	public ModuleContainerAdaptor getAdaptor() {
		return adaptor;
	}

	/**
	 * Returns the list of currently installed modules sorted by module id.
	 * @return the list of currently installed modules sorted by module id.
	 */
	public List getModules() {
		return moduleDatabase.getModules();
	}

	/**
	 * Returns the module installed with the specified id, or null if no
	 * such module is installed.
	 * @param id the id of the module
	 * @return the module with the specified id, or null of no such module is installed.
	 */
	public Module getModule(long id) {
		return moduleDatabase.getModule(id);
	}

	/**
	 * Returns the module installed with the specified location, or null if no
	 * such module is installed.
	 * @param location the location of the module
	 * @return the module with the specified location, or null of no such module is installed.
	 */
	public Module getModule(String location) {
		return moduleDatabase.getModule(location);
	}

	/**
	 * Creates a synthetic requirement that is not associated with any module revision.
	 * This is useful for calling {@link FrameworkWiring#findProviders(Requirement)}.
	 * @param namespace the requirement namespace
	 * @param directives the requirement directives
	 * @param attributes the requirement attributes
	 * @return a synthetic requirement
	 */
	public static Requirement createRequirement(String namespace, Map directives, Map attributes) {
		return new ModuleRequirement(namespace, directives, attributes, null);
	}

	/**
	 * Installs a new module using the specified location.  The specified
	 * builder is used to create a new {@link ModuleRevision revision}
	 * which will become the {@link Module#getCurrentRevision() current}
	 * revision of the new module.
	 * 

* If a module already exists with the specified location then the * existing module is returned and the builder is not used. * @param origin the module performing the install, may be {@code null}. * @param location The location identifier of the module to install. * @param builder the builder used to create the revision to install. * @param revisionInfo the revision info for the new revision, may be {@code null}. * @return a new module or a existing module if one exists at the * specified location. * @throws BundleException if some error occurs installing the module */ public Module install(Module origin, String location, ModuleRevisionBuilder builder, Object revisionInfo) throws BundleException { long id = builder.getId(); ModuleRevisionBuilder adaptBuilder = getAdaptor().adaptModuleRevisionBuilder(ModuleEvent.INSTALLED, origin, builder, revisionInfo); if (adaptBuilder != null) { // be sure to restore the id from the original builder adaptBuilder.setInternalId(id); builder = adaptBuilder; } String name = builder.getSymbolicName(); boolean locationLocked = false; boolean nameLocked = false; try { // Attempt to lock the location and name try { locationLocked = locationLocks.tryLock(location, 5, TimeUnit.SECONDS); nameLocked = name != null && nameLocks.tryLock(name, 5, TimeUnit.SECONDS); if (!locationLocked) { throw new BundleException("Failed to obtain location lock for installation: " + location, BundleException.STATECHANGE_ERROR, new ThreadInfoReport(locationLocks.getLockInfo(location))); //$NON-NLS-1$ } if (name != null && !nameLocked) { throw new BundleException("Failed to obtain symbolic name lock for installation: " + name, BundleException.STATECHANGE_ERROR, new ThreadInfoReport(nameLocks.getLockInfo(name))); //$NON-NLS-1$ } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new BundleException("Failed to obtain id locks for installation.", BundleException.STATECHANGE_ERROR, e); //$NON-NLS-1$ } Module existingLocation = null; Collection collisionCandidates = Collections.emptyList(); moduleDatabase.readLock(); try { existingLocation = moduleDatabase.getModule(location); if (existingLocation == null) { // Collect existing current revisions with the same name and version as the revision we want to install // This is to perform the collision check below List sameIdentity = moduleDatabase.findCapabilities(getIdentityRequirement(name, builder.getVersion())); if (!sameIdentity.isEmpty()) { collisionCandidates = new ArrayList<>(1); for (ModuleCapability identity : sameIdentity) { ModuleRevision equinoxRevision = identity.getRevision(); if (!equinoxRevision.isCurrent()) continue; // only pay attention to current revisions // need to prevent duplicates here; this is in case a revisions object contains multiple revision objects. if (!collisionCandidates.contains(equinoxRevision.getRevisions().getModule())) collisionCandidates.add(equinoxRevision.getRevisions().getModule()); } } } } finally { moduleDatabase.readUnlock(); } // Check that the existing location is visible from the origin module if (existingLocation != null) { if (origin != null) { Bundle bundle = origin.getBundle(); BundleContext context = bundle == null ? null : bundle.getBundleContext(); if (context != null && context.getBundle(existingLocation.getId()) == null) { Bundle b = existingLocation.getBundle(); throw new BundleException(NLS.bind(Msg.ModuleContainer_NameCollisionWithLocation, new Object[] {b.getSymbolicName(), b.getVersion(), location}), BundleException.REJECTED_BY_HOOK); } } return existingLocation; } // Check that the bundle does not collide with other bundles with the same name and version // This is from the perspective of the origin bundle if (origin != null && !collisionCandidates.isEmpty()) { adaptor.getModuleCollisionHook().filterCollisions(ModuleCollisionHook.INSTALLING, origin, collisionCandidates); } if (!collisionCandidates.isEmpty()) { throw new BundleException(NLS.bind(Msg.ModuleContainer_NameCollision, name, builder.getVersion()), BundleException.DUPLICATE_BUNDLE_ERROR); } Module result = moduleDatabase.install(location, builder, revisionInfo); adaptor.publishModuleEvent(ModuleEvent.INSTALLED, result, origin); return result; } finally { if (locationLocked) locationLocks.unlock(location); if (nameLocked) nameLocks.unlock(name); } } /** * Updates the specified module with a new revision. The specified * builder is used to create a new {@link ModuleRevision revision} * which will become the {@link Module#getCurrentRevision() current} * revision of the new module. * @param module the module to update * @param builder the builder used to create the revision for the update. * @param revisionInfo the revision info for the new revision, may be {@code null}. * @throws BundleException if some error occurs updating the module */ public void update(Module module, ModuleRevisionBuilder builder, Object revisionInfo) throws BundleException { long id = builder.getId(); ModuleRevisionBuilder adaptBuilder = getAdaptor().adaptModuleRevisionBuilder(ModuleEvent.UPDATED, module, builder, revisionInfo); if (adaptBuilder != null) { // be sure to restore the id from the original builder adaptBuilder.setInternalId(id); builder = adaptBuilder; } checkAdminPermission(module.getBundle(), AdminPermission.LIFECYCLE); String name = builder.getSymbolicName(); boolean nameLocked = false; try { // Attempt to lock the name try { if (name != null && !(nameLocked = nameLocks.tryLock(name, 5, TimeUnit.SECONDS))) { throw new BundleException("Failed to obtain id locks for installation: " + name, //$NON-NLS-1$ BundleException.STATECHANGE_ERROR, new ThreadInfoReport(nameLocks.getLockInfo(name))); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new BundleException("Failed to obtain id locks for installation.", BundleException.STATECHANGE_ERROR, e); //$NON-NLS-1$ } Collection collisionCandidates = Collections.emptyList(); moduleDatabase.readLock(); try { // Collect existing bundles with the same name and version as the bundle we want to install // This is to perform the collision check below List sameIdentity = moduleDatabase.findCapabilities(getIdentityRequirement(name, builder.getVersion())); if (!sameIdentity.isEmpty()) { collisionCandidates = new ArrayList<>(1); for (ModuleCapability identity : sameIdentity) { ModuleRevision equinoxRevision = identity.getRevision(); if (!equinoxRevision.isCurrent()) continue; Module m = equinoxRevision.getRevisions().getModule(); if (m.equals(module)) continue; // don't worry about the updating modules revisions // need to prevent duplicates here; this is in case a revisions object contains multiple revision objects. if (!collisionCandidates.contains(m)) collisionCandidates.add(m); } } } finally { moduleDatabase.readUnlock(); } // Check that the module does not collide with other modules with the same name and version // This is from the perspective of the module being updated if (module != null && !collisionCandidates.isEmpty()) { adaptor.getModuleCollisionHook().filterCollisions(ModuleCollisionHook.UPDATING, module, collisionCandidates); } if (!collisionCandidates.isEmpty()) { throw new BundleException(NLS.bind(Msg.ModuleContainer_NameCollision, name, builder.getVersion()), BundleException.DUPLICATE_BUNDLE_ERROR); } module.lockStateChange(ModuleEvent.UPDATED); State previousState; try { module.checkValid(); previousState = module.getState(); if (Module.ACTIVE_SET.contains(previousState)) { // throwing an exception from stop terminates update module.stop(StopOptions.TRANSIENT); } if (Module.RESOLVED_SET.contains(previousState)) { // set the state to installed and publish unresolved event module.setState(State.INSTALLED); adaptor.publishModuleEvent(ModuleEvent.UNRESOLVED, module, module); } moduleDatabase.update(module, builder, revisionInfo); } finally { module.unlockStateChange(ModuleEvent.UPDATED); } // only publish updated event on success adaptor.publishModuleEvent(ModuleEvent.UPDATED, module, module); if (Module.ACTIVE_SET.contains(previousState)) { try { // restart the module if necessary module.start(StartOptions.TRANSIENT_RESUME); } catch (BundleException e) { getAdaptor().publishContainerEvent(ContainerEvent.ERROR, module, e); } } } finally { if (nameLocked) nameLocks.unlock(name); } } /** * Uninstalls the specified module. * @param module the module to uninstall * @throws BundleException if some error occurs uninstalling the module */ public void uninstall(Module module) throws BundleException { checkAdminPermission(module.getBundle(), AdminPermission.LIFECYCLE); module.lockStateChange(ModuleEvent.UNINSTALLED); State previousState; try { module.checkValid(); previousState = module.getState(); if (Module.ACTIVE_SET.contains(module.getState())) { try { module.stop(StopOptions.TRANSIENT); } catch (BundleException e) { adaptor.publishContainerEvent(ContainerEvent.ERROR, module, e); } } if (Module.RESOLVED_SET.contains(previousState)) { // set the state to installed and publish unresolved event module.setState(State.INSTALLED); adaptor.publishModuleEvent(ModuleEvent.UNRESOLVED, module, module); } moduleDatabase.uninstall(module); module.setState(State.UNINSTALLED); } finally { module.unlockStateChange(ModuleEvent.UNINSTALLED); } adaptor.publishModuleEvent(ModuleEvent.UNINSTALLED, module, module); } ModuleWiring getWiring(ModuleRevision revision) { return moduleDatabase.getWiring(revision); } /** * Returns the {@link FrameworkWiring} for this container * @return the framework wiring for this container. */ public FrameworkWiring getFrameworkWiring() { return frameworkWiring; } /** * Returns the {@link FrameworkStartLevel} for this container * @return the framework start level for this container */ public FrameworkStartLevel getFrameworkStartLevel() { return frameworkStartLevel; } /** * Attempts to resolve the current revisions of the specified modules. * @param triggers the modules to resolve or {@code null} to resolve all unresolved * current revisions. * @param triggersMandatory true if the triggers must be resolved. This will result in * a {@link ResolutionException} if set to true and one of the triggers could not be resolved. * @see FrameworkWiring#resolveBundles(Collection) * @return A resolution report for the resolve operation */ public ResolutionReport resolve(Collection triggers, boolean triggersMandatory) { return resolve(triggers, triggersMandatory, false); } private ResolutionReport resolve(Collection triggers, boolean triggersMandatory, boolean restartTriggers) { if (isRefreshingSystemModule()) { return new ModuleResolutionReport(null, Collections.emptyMap(), new ResolutionException("Unable to resolve while shutting down the framework.")); //$NON-NLS-1$ } ResolutionReport report = null; try (ResolutionLock.Permits resolutionPermits = _resolutionLock.acquire(1)) { do { try { report = resolveAndApply(triggers, triggersMandatory, restartTriggers, resolutionPermits); } catch (RuntimeException e) { if (e.getCause() instanceof BundleException) { BundleException be = (BundleException) e.getCause(); if (be.getType() == BundleException.REJECTED_BY_HOOK || be.getType() == BundleException.STATECHANGE_ERROR) { return new ModuleResolutionReport(null, Collections.emptyMap(), new ResolutionException(be)); } } throw e; } } while (report == null); } catch (ResolutionLockException e) { return new ModuleResolutionReport(null, Collections.emptyMap(), new ResolutionException("Timeout acquiring lock for resolution", e, Collections.emptyList())); //$NON-NLS-1$ } return report; } private ResolutionReport resolveAndApply(Collection triggers, boolean triggersMandatory, boolean restartTriggers, ResolutionLock.Permits resolutionPermits) { if (triggers == null) { triggers = new ArrayList<>(0); } if (triggers.isEmpty()) { // if there are no triggers then they cannot be mandatory triggersMandatory = false; } Collection triggerRevisions = new ArrayList<>(triggers.size()); Collection unresolved = new ArrayList<>(); Map wiringClone; long timestamp; moduleDatabase.readLock(); try { timestamp = moduleDatabase.getRevisionsTimestamp(); wiringClone = moduleDatabase.getWiringsClone(); for (Module module : triggers) { if (!State.UNINSTALLED.equals(module.getState())) { ModuleRevision current = module.getCurrentRevision(); if (current != null) triggerRevisions.add(current); } } Collection allModules = moduleDatabase.getModules(); for (Module module : allModules) { ModuleRevision revision = module.getCurrentRevision(); if (revision != null && !wiringClone.containsKey(revision)) unresolved.add(revision); } } finally { moduleDatabase.readUnlock(); } ModuleResolutionReport report = moduleResolver.resolveDelta(triggerRevisions, triggersMandatory, unresolved, wiringClone, moduleDatabase); Map> resolutionResult = report.getResolutionResult(); Map deltaWiring = resolutionResult == null ? Collections.emptyMap() : moduleResolver.generateDelta(resolutionResult, wiringClone); if (deltaWiring.isEmpty()) return report; // nothing to do Collection modulesResolved = new ArrayList<>(); for (ModuleRevision deltaRevision : deltaWiring.keySet()) { if (!wiringClone.containsKey(deltaRevision)) modulesResolved.add(deltaRevision.getRevisions().getModule()); } return applyDelta(deltaWiring, modulesResolved, triggers, timestamp, restartTriggers, resolutionPermits) ? report : null; } /** * Attempts to resolve the specified dynamic package name request for the specified revision. * @param dynamicPkgName the package name to attempt a dynamic resolution for * @param revision the module revision the dynamic resolution request is for * @return the new resolution wire establishing a dynamic package resolution or null if * a dynamic wire could not be established. */ public ModuleWire resolveDynamic(String dynamicPkgName, ModuleRevision revision) { ModuleWire result; Map deltaWiring; Collection modulesResolved; long timestamp; try (Permits resolutionPermits = _resolutionLock.acquire(ResolutionLock.MAX_RESOLUTION_PERMITS)) { do { result = null; Map wiringClone = null; List dynamicReqs = null; Collection unresolved = new ArrayList<>(); moduleDatabase.readLock(); try { ModuleWiring wiring = revision.getWiring(); if (wiring == null) { // not resolved!! return null; } if (wiring.isDynamicPackageMiss(dynamicPkgName)) { // cached a miss for this package return null; } // need to check that another thread has not done the work already result = findExistingDynamicWire(revision.getWiring(), dynamicPkgName); if (result != null) { return result; } dynamicReqs = getDynamicRequirements(dynamicPkgName, revision); if (dynamicReqs.isEmpty()) { // save the miss for the package name wiring.addDynamicPackageMiss(dynamicPkgName); return null; } timestamp = moduleDatabase.getRevisionsTimestamp(); wiringClone = moduleDatabase.getWiringsClone(); Collection allModules = moduleDatabase.getModules(); for (Module module : allModules) { ModuleRevision current = module.getCurrentRevision(); if (current != null && !wiringClone.containsKey(current)) unresolved.add(current); } } finally { moduleDatabase.readUnlock(); } deltaWiring = null; boolean foundCandidates = false; for (DynamicModuleRequirement dynamicReq : dynamicReqs) { ModuleResolutionReport report = moduleResolver.resolveDynamicDelta(dynamicReq, unresolved, wiringClone, moduleDatabase); Map> resolutionResult = report.getResolutionResult(); deltaWiring = resolutionResult == null ? Collections.emptyMap() : moduleResolver.generateDelta(resolutionResult, wiringClone); if (deltaWiring.get(revision) != null) { break; } // Did not establish a valid wire. // Check to see if any candidates were available. // this is used for caching purposes below List revisionEntries = report.getEntries().get(revision); if (revisionEntries == null || revisionEntries.isEmpty()) { foundCandidates = true; } else { // must make sure there is no MISSING_CAPABILITY type entry boolean isMissingCapability = false; for (Entry entry : revisionEntries) { isMissingCapability |= Entry.Type.MISSING_CAPABILITY.equals(entry.getType()); } foundCandidates |= !isMissingCapability; } } if (deltaWiring == null || deltaWiring.get(revision) == null) { if (!foundCandidates) { ModuleWiring wiring = revision.getWiring(); if (wiring != null) { // save the miss for the package name wiring.addDynamicPackageMiss(dynamicPkgName); } } return null; // nothing to do } modulesResolved = new ArrayList<>(); for (ModuleRevision deltaRevision : deltaWiring.keySet()) { if (!wiringClone.containsKey(deltaRevision)) modulesResolved.add(deltaRevision.getRevisions().getModule()); } // Save the result ModuleWiring wiring = deltaWiring.get(revision); result = findExistingDynamicWire(wiring, dynamicPkgName); } while (!applyDelta(deltaWiring, modulesResolved, Collections.emptyList(), timestamp, false, resolutionPermits)); } catch (ResolutionLockException e) { return null; } return result; } private ModuleWire findExistingDynamicWire(ModuleWiring wiring, String dynamicPkgName) { if (wiring == null) { return null; } List wires = wiring.getRequiredModuleWires(PackageNamespace.PACKAGE_NAMESPACE); // No null check; we are holding the database lock here. // Work backwards to find the first wire with the dynamic requirement that matches package name for (int i = wires.size() - 1; i >= 0; i--) { ModuleWire wire = wires.get(i); if (dynamicPkgName.equals(wire.getCapability().getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE))) { return wire; } if (!PackageNamespace.RESOLUTION_DYNAMIC.equals(wire.getRequirement().getDirectives().get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE))) { return null; } } return null; } // The resolution algorithm uses optimistic locking approach; // This involves taking a snapshot of the state and performing an // operation on the snapshot while holding no locks and then // obtaining the write lock to apply the results. If we // detect the state has changed since the snapshot taken then // the process is started over. If we allow too many threads // to try to do this at the same time it causes thrashing // between taking the snapshot and successfully applying the // results. Instead of resorting to single threaded operations // we choose to limit the number of concurrent resolves final ResolutionLock _resolutionLock = new ResolutionLock(); final ReentrantLock _bundleStateLock = new ReentrantLock(); static class ResolutionLockException extends Exception { private static final long serialVersionUID = 1L; public ResolutionLockException() { super(); } public ResolutionLockException(Throwable cause) { super(cause); } } /** * A resolution hook allows for a max of 10 threads to do resolution operations * at the same time. The implementation uses a semaphore to grant the max number * of permits (threads) but a reentrant read lock is also used to detect reentry. * If a thread reenters then no extra permits are required by the thread. * This lock returns a Permits object that implements closeable for use in * try->with. If permits is closed multiple times then the additional close * operations are a no-op. */ static class ResolutionLock { final static int MAX_RESOLUTION_PERMITS = 10; final Semaphore permitPool = new Semaphore(MAX_RESOLUTION_PERMITS); final ReentrantReadWriteLock reenterLock = new ReentrantReadWriteLock(); class Permits implements Closeable { private final int aquiredPermits; private final AtomicBoolean closed = new AtomicBoolean(); Permits(int requestedPermits) throws ResolutionLockException { if (reenterLock.getReadHoldCount() > 0) { // thread is already holding permits; don't request more requestedPermits = 0; } this.aquiredPermits = requestedPermits; boolean previousInterruption = Thread.interrupted(); try { if (!permitPool.tryAcquire(requestedPermits, 30, TimeUnit.SECONDS)) { throw new ResolutionLockException(); } } catch (InterruptedException e) { throw new ResolutionLockException(e); } finally { if (previousInterruption) { Thread.currentThread().interrupt(); } } // mark this thread as holding permits reenterLock.readLock().lock(); } @Override public void close() { if (closed.compareAndSet(false, true)) { permitPool.release(aquiredPermits); reenterLock.readLock().unlock(); } } } Permits acquire(int requestedPermits) throws ResolutionLockException { return new Permits(requestedPermits); } } private boolean applyDelta(Map deltaWiring, Collection modulesResolved, Collection triggers, long timestamp, boolean restartTriggers, ResolutionLock.Permits resolutionPermits) { List modulesLocked = new ArrayList<>(modulesResolved.size()); // now attempt to apply the delta try { // Acquire the necessary RESOLVED state change lock. // Note this is done while holding a global lock to avoid multiple threads trying to compete over // locking multiple modules; otherwise out of order locks between modules can happen // NOTE this MUST be done outside of holding the moduleDatabase lock also to avoid // introducing out of order locks between the bundle state change lock and the moduleDatabase // lock. _bundleStateLock.lock(); try { for (Module module : modulesResolved) { try { // avoid grabbing the lock if the timestamp has changed if (timestamp != moduleDatabase.getRevisionsTimestamp()) { return false; // need to try again } module.lockStateChange(ModuleEvent.RESOLVED); modulesLocked.add(module); } catch (BundleException e) { // before throwing an exception here, see if the timestamp has changed if (timestamp != moduleDatabase.getRevisionsTimestamp()) { return false; // need to try again } // TODO throw some appropriate exception throw new IllegalStateException(Msg.ModuleContainer_StateLockError, e); } } } finally { _bundleStateLock.unlock(); } Map> hostsWithDynamicFrags = new HashMap<>(0); moduleDatabase.writeLock(); try { if (timestamp != moduleDatabase.getRevisionsTimestamp()) return false; // need to try again Map wiringCopy = moduleDatabase.getWiringsCopy(); for (Map.Entry deltaEntry : deltaWiring.entrySet()) { ModuleWiring current = wiringCopy.get(deltaEntry.getKey()); if (current != null) { // need to update the provided capabilities, provided and required wires for currently resolved current.setCapabilities(deltaEntry.getValue().getCapabilities()); current.setProvidedWires(deltaEntry.getValue().getProvidedWires()); current.setRequirements(deltaEntry.getValue().getRequirements()); current.setRequiredWires(deltaEntry.getValue().getRequiredWires()); deltaEntry.setValue(current); // set the real wiring into the delta } else { ModuleRevision revision = deltaEntry.getValue().getRevision(); modulesResolved.add(revision.getRevisions().getModule()); if ((revision.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0) { for (ModuleWire hostWire : deltaEntry.getValue().getRequiredModuleWires(HostNamespace.HOST_NAMESPACE)) { // check to see if the host revision has a wiring ModuleWiring hostWiring = hostWire.getProvider().getWiring(); if (hostWiring != null) { Collection dynamicFragments = hostsWithDynamicFrags.get(hostWiring); if (dynamicFragments == null) { dynamicFragments = new ArrayList<>(); hostsWithDynamicFrags.put(hostWiring, dynamicFragments); } dynamicFragments.add(hostWire.getRequirer()); } } } } } moduleDatabase.mergeWiring(deltaWiring); moduleDatabase.sortModules(modulesLocked, Sort.BY_DEPENDENCY, Sort.BY_START_LEVEL); } finally { moduleDatabase.writeUnlock(); } // set the modules state to resolved for (Module module : modulesLocked) { module.setState(State.RESOLVED); } // attach fragments to already resolved hosts that have // dynamically attached fragments for (Map.Entry> dynamicFragments : hostsWithDynamicFrags.entrySet()) { dynamicFragments.getKey().loadFragments(dynamicFragments.getValue()); } } finally { for (Module module : modulesLocked) { module.unlockStateChange(ModuleEvent.RESOLVED); } } // release resolution permits before firing events resolutionPermits.close(); for (Module module : modulesLocked) { adaptor.publishModuleEvent(ModuleEvent.RESOLVED, module, module); } // If there are any triggers re-start them now if requested Set triggerSet = restartTriggers ? new HashSet<>(triggers) : Collections.emptySet(); if (restartTriggers) { for (Module module : triggers) { if (module.getId() != 0 && Module.RESOLVED_SET.contains(module.getState())) { start(module, StartOptions.TRANSIENT_RESUME); } } } if (autoStartOnResolve) { // This is questionable behavior according to the spec but this was the way equinox previously behaved // Need to auto-start any persistently started bundles that got resolved for (Module module : modulesLocked) { // Note that we check inStart here. There is still a timing issue that is impossible to avoid. // Another thread could attempt to start the module but we could check inStart() before that thread // increments inStart. One thread will win the race to grab the module STARTED lock. That thread // will end up actually starting the module and the other thread will block. If a timeout occurs // the blocking thread will get an exception. if (!module.inStart() && module.getId() != 0 && !triggerSet.contains(module)) { start(module, StartOptions.TRANSIENT_IF_AUTO_START, StartOptions.TRANSIENT_RESUME); } } } return true; } private void start(Module module, StartOptions... options) { try { secureAction.start(module, options); } catch (BundleException e) { if (e.getType() == BundleException.STATECHANGE_ERROR) { if (Module.ACTIVE_SET.contains(module.getState())) { // There is still a timing issue here; // but at least try to detect that another thread is starting the module return; } } adaptor.publishContainerEvent(ContainerEvent.ERROR, module, e); } catch (IllegalStateException e) { // been uninstalled return; } } private List getDynamicRequirements(String dynamicPkgName, ModuleRevision revision) { // TODO Will likely need to optimize this if ((revision.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0) { // only do this for hosts return Collections.emptyList(); } ModuleWiring wiring = revision.getWiring(); if (wiring == null) { // not resolved! return Collections.emptyList(); } List result = new ArrayList<>(1); // check the dynamic import packages DynamicModuleRequirement dynamicRequirement; for (ModuleRequirement requirement : wiring.getModuleRequirements(PackageNamespace.PACKAGE_NAMESPACE)) { dynamicRequirement = requirement.getDynamicPackageRequirement(revision, dynamicPkgName); if (dynamicRequirement != null) { result.add(dynamicRequirement); } } if (!result.isEmpty()) { // must check that the wiring does not export the package for (ModuleCapability capability : wiring.getModuleCapabilities(PackageNamespace.PACKAGE_NAMESPACE)) { if (dynamicPkgName.equals(capability.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE))) { // the package is exported, must not allow dynamic import return Collections.emptyList(); } } } return result; } private Collection unresolve(Collection initial) { Collection refreshTriggers = null; while (refreshTriggers == null) { refreshTriggers = unresolve0(initial); } return refreshTriggers; } private Collection unresolve0(Collection initial) { Map wiringCopy; List refreshTriggers; Collection toRemoveRevisions; Collection toRemoveWirings; Map> toRemoveWireLists; long timestamp; moduleDatabase.readLock(); try { checkSystemExtensionRefresh(initial); timestamp = moduleDatabase.getRevisionsTimestamp(); wiringCopy = moduleDatabase.getWiringsCopy(); refreshTriggers = new ArrayList<>(getRefreshClosure(initial, wiringCopy)); toRemoveRevisions = new ArrayList<>(); toRemoveWirings = new ArrayList<>(); toRemoveWireLists = new HashMap<>(); for (Iterator iTriggers = refreshTriggers.iterator(); iTriggers.hasNext();) { Module module = iTriggers.next(); boolean first = true; for (ModuleRevision revision : module.getRevisions().getModuleRevisions()) { ModuleWiring removedWiring = wiringCopy.remove(revision); if (removedWiring != null) { toRemoveWirings.add(removedWiring); List removedWires = removedWiring.getRequiredModuleWires(null); for (ModuleWire wire : removedWires) { Collection providerWires = toRemoveWireLists.get(wire.getProviderWiring()); if (providerWires == null) { providerWires = new ArrayList<>(); toRemoveWireLists.put(wire.getProviderWiring(), providerWires); } providerWires.add(wire); } } if (!first || revision.getRevisions().isUninstalled()) { toRemoveRevisions.add(revision); } first = false; } if (module.getState().equals(State.UNINSTALLED)) { iTriggers.remove(); } } moduleDatabase.sortModules(refreshTriggers, Sort.BY_START_LEVEL, Sort.BY_DEPENDENCY); } finally { moduleDatabase.readUnlock(); } Module systemModule = moduleDatabase.getModule(0); if (refreshTriggers.contains(systemModule) && Module.ACTIVE_SET.contains(systemModule.getState())) { refreshSystemModule(); return Collections.emptyList(); } Collection modulesLocked = new ArrayList<>(refreshTriggers.size()); Collection modulesUnresolved = new ArrayList<>(); try { // Acquire the module state change locks. // Note this is done while holding a global lock to avoid multiple threads trying to compete over // locking multiple modules; otherwise out of order locks between modules can happen // NOTE this MUST be done outside of holding the moduleDatabase lock also to avoid // introducing out of order locks between the bundle state change lock and the moduleDatabase // lock. _bundleStateLock.lock(); try { // go in reverse order for (ListIterator iTriggers = refreshTriggers.listIterator(refreshTriggers.size()); iTriggers.hasPrevious();) { Module refreshModule = iTriggers.previous(); refreshModule.lockStateChange(ModuleEvent.UNRESOLVED); modulesLocked.add(refreshModule); } } catch (BundleException e) { // TODO throw some appropriate exception throw new IllegalStateException(Msg.ModuleContainer_StateLockError, e); } finally { _bundleStateLock.unlock(); } // Must not hold the module database lock while stopping bundles // Stop any active bundles and remove non-active modules from the refreshTriggers for (ListIterator iTriggers = refreshTriggers.listIterator(refreshTriggers.size()); iTriggers.hasPrevious();) { Module refreshModule = iTriggers.previous(); State previousState = refreshModule.getState(); if (Module.ACTIVE_SET.contains(previousState)) { try { refreshModule.stop(StopOptions.TRANSIENT); } catch (BundleException e) { adaptor.publishContainerEvent(ContainerEvent.ERROR, refreshModule, e); } } else { iTriggers.remove(); } } // do a sanity check on states of the modules, they must be INSTALLED, RESOLVED or UNINSTALLED for (Module module : modulesLocked) { if (Module.ACTIVE_SET.contains(module.getState())) { throw new IllegalStateException("Module is in the wrong state: " + module + ": " + module.getState()); //$NON-NLS-1$ //$NON-NLS-2$ } } // finally apply the unresolve to the database moduleDatabase.writeLock(); try { if (timestamp != moduleDatabase.getRevisionsTimestamp()) return null; // need to try again // remove any wires from unresolved wirings that got removed for (Map.Entry> entry : toRemoveWireLists.entrySet()) { NamespaceList.Builder provided = entry.getKey().getProvidedWires().createBuilder(); provided.removeAll(entry.getValue()); entry.getKey().setProvidedWires(provided.build()); for (ModuleWire removedWire : entry.getValue()) { // invalidate the wire removedWire.invalidate(); } } // remove any revisions that got removed as part of the refresh for (ModuleRevision removed : toRemoveRevisions) { removed.getRevisions().removeRevision(removed); moduleDatabase.removeCapabilities(removed); } // invalidate any removed wiring objects for (ModuleWiring moduleWiring : toRemoveWirings) { moduleWiring.invalidate(); } moduleDatabase.setWiring(wiringCopy); // check for any removal pendings moduleDatabase.cleanupRemovalPending(); } finally { moduleDatabase.writeUnlock(); } // set the state of modules to unresolved for (Module module : modulesLocked) { if (State.RESOLVED.equals(module.getState())) { module.setState(State.INSTALLED); modulesUnresolved.add(module); } } } finally { for (Module module : modulesLocked) { module.unlockStateChange(ModuleEvent.UNRESOLVED); } } // publish unresolved events after giving up all locks for (Module module : modulesUnresolved) { adaptor.publishModuleEvent(ModuleEvent.UNRESOLVED, module, module); } return refreshTriggers; } private void checkSystemExtensionRefresh(Collection initial) { if (initial == null) { return; } Long zero = Long.valueOf(0); for (Iterator iModules = initial.iterator(); iModules.hasNext();) { Module m = iModules.next(); if (m.getId().equals(zero)) { // never allow system bundle to be unresolved directly if the system module is active if (Module.ACTIVE_SET.contains(m.getState())) { iModules.remove(); } } else { if (Module.RESOLVED_SET.contains(m.getState())) { // check if current revision is an extension of the system module ModuleRevision current = m.getCurrentRevision(); if ((current.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0) { ModuleWiring wiring = current.getWiring(); if (wiring != null) { List hostWires = wiring.getRequiredModuleWires(HostNamespace.HOST_NAMESPACE); for (ModuleWire hostWire : hostWires) { if (hostWire.getProvider().getRevisions().getModule().getId().equals(zero)) { // The current revision is the extension to allow it to refresh // this would just shutdown the framework for no reason iModules.remove(); } } } } } } } } /** * Refreshes the specified collection of modules. * @param initial the modules to refresh or {@code null} to refresh the * removal pending. * @return a resolution report for the resolve operation that may have * occurred during the refresh operation. * @see FrameworkWiring#refreshBundles(Collection, FrameworkListener...) */ public ResolutionReport refresh(Collection initial) { initial = initial == null ? null : new ArrayList<>(initial); Collection refreshTriggers = unresolve(initial); if (!isRefreshingSystemModule()) { return resolve(refreshTriggers, false, true); } return new ModuleResolutionReport(null, null, null); } /** * Returns the dependency closure of for the specified modules. * @param initial The initial modules for which to generate the dependency closure * @return A collection containing a snapshot of the dependency closure of the specified * modules, or an empty collection if there were no specified modules. */ public Collection getDependencyClosure(Collection initial) { moduleDatabase.readLock(); try { return getRefreshClosure(initial, moduleDatabase.getWiringsCopy()); } finally { moduleDatabase.readUnlock(); } } /** * Returns the revisions that have {@link ModuleWiring#isCurrent() non-current}, {@link ModuleWiring#isInUse() in use} module wirings. * @return A collection containing a snapshot of the revisions which have non-current, in use ModuleWirings, * or an empty collection if there are no such revisions. */ public Collection getRemovalPending() { return moduleDatabase.getRemovalPending(); } /** * Return the active start level value of this container. * * If the container is in the process of changing the start level this * method must return the active start level if this differs from the * requested start level. * * @return The active start level value of the Framework. */ public int getStartLevel() { return frameworkStartLevel.getStartLevel(); } void setStartLevel(Module module, int startlevel) { frameworkStartLevel.setStartLevel(module, startlevel); } long getModuleLockTimeout() { return this.moduleLockTimeout; } void open() { loadModules(); frameworkStartLevel.open(); frameworkWiring.open(); refreshingSystemModule.set(null); } void close() { frameworkStartLevel.close(); frameworkWiring.close(); unloadModules(); } private void loadModules() { List modules = null; moduleDatabase.readLock(); try { modules = getModules(); for (Module module : modules) { try { module.lockStateChange(ModuleEvent.RESOLVED); ModuleWiring wiring = moduleDatabase.getWiring(module.getCurrentRevision()); if (wiring != null) { module.setState(State.RESOLVED); } else { module.setState(State.INSTALLED); } } catch (BundleException e) { throw new IllegalStateException("Unable to lock module state.", e); //$NON-NLS-1$ } } Map wirings = moduleDatabase.getWiringsCopy(); for (ModuleWiring wiring : wirings.values()) { wiring.validate(); } } finally { if (modules != null) { for (Module module : modules) { try { module.unlockStateChange(ModuleEvent.RESOLVED); } catch (IllegalMonitorStateException e) { // ignore } } } moduleDatabase.readUnlock(); } } private void unloadModules() { List modules = null; moduleDatabase.readLock(); try { modules = getModules(); for (Module module : modules) { if (module.getId() != 0) { try { module.lockStateChange(ModuleEvent.UNINSTALLED); } catch (BundleException e) { throw new IllegalStateException("Unable to lock module state.", e); //$NON-NLS-1$ } module.setState(State.UNINSTALLED); } } Map wirings = moduleDatabase.getWiringsCopy(); for (ModuleWiring wiring : wirings.values()) { wiring.unload(); } } finally { if (modules != null) { for (Module module : modules) { if (module.getId() != 0) { try { module.unlockStateChange(ModuleEvent.UNINSTALLED); } catch (IllegalMonitorStateException e) { // ignore } } } } moduleDatabase.readUnlock(); } } /** * Sets all the module states uninstalled except for the system module. * @throws BundleException */ public void setInitialModuleStates() throws BundleException { moduleDatabase.readLock(); try { List modules = getModules(); for (Module module : modules) { if (module.getId() == 0) { module.lockStateChange(ModuleEvent.UNINSTALLED); try { module.setState(State.INSTALLED); } finally { module.unlockStateChange(ModuleEvent.UNINSTALLED); } } else { module.lockStateChange(ModuleEvent.UNINSTALLED); try { module.setState(State.UNINSTALLED); } finally { module.unlockStateChange(ModuleEvent.UNINSTALLED); } } } Map wirings = moduleDatabase.getWiringsCopy(); for (ModuleWiring wiring : wirings.values()) { wiring.unload(); } } finally { moduleDatabase.readUnlock(); } } Set getRefreshClosure(Collection initial, Map wiringCopy) { Set refreshClosure = new HashSet<>(); if (initial == null) { initial = new HashSet<>(); Collection removalPending = moduleDatabase.getRemovalPending(); for (ModuleRevision revision : removalPending) { initial.add(revision.getRevisions().getModule()); } } for (Module module : initial) addDependents(module, wiringCopy, refreshClosure); return refreshClosure; } private static void addDependents(Module module, Map wiringCopy, Set refreshClosure) { if (refreshClosure.contains(module)) return; refreshClosure.add(module); List revisions = module.getRevisions().getModuleRevisions(); for (ModuleRevision revision : revisions) { ModuleWiring wiring = wiringCopy.get(revision); if (wiring == null) continue; List provided = wiring.getProvidedModuleWires(null); // No null checks; we are holding the read lock here. // Add all requirers of the provided wires for (ModuleWire providedWire : provided) { addDependents(providedWire.getRequirer().getRevisions().getModule(), wiringCopy, refreshClosure); } // add all hosts of a fragment if (revision.getTypes() == BundleRevision.TYPE_FRAGMENT) { List hosts = wiring.getRequiredModuleWires(HostNamespace.HOST_NAMESPACE); for (ModuleWire hostWire : hosts) { addDependents(hostWire.getProvider().getRevisions().getModule(), wiringCopy, refreshClosure); } } } } static Collection getDependencyClosure(ModuleRevision initial, Map wiringCopy) { Set dependencyClosure = new HashSet<>(); addDependents(initial, wiringCopy, dependencyClosure); return dependencyClosure; } private static void addDependents(ModuleRevision revision, Map wiringCopy, Set dependencyClosure) { if (dependencyClosure.contains(revision)) return; dependencyClosure.add(revision); ModuleWiring wiring = wiringCopy.get(revision); if (wiring == null) return; List provided = wiring.getProvidedModuleWires(null); // No null checks; we are holding the read lock here. // Add all requirers of the provided wires for (ModuleWire providedWire : provided) { addDependents(providedWire.getRequirer(), wiringCopy, dependencyClosure); } // add all hosts of a fragment if (revision.getTypes() == BundleRevision.TYPE_FRAGMENT) { List hosts = wiring.getRequiredModuleWires(HostNamespace.HOST_NAMESPACE); for (ModuleWire hostWire : hosts) { addDependents(hostWire.getProvider(), wiringCopy, dependencyClosure); } } } Bundle getSystemBundle() { Module systemModule = moduleDatabase.getModule(0); return systemModule == null ? null : systemModule.getBundle(); } void checkAdminPermission(Bundle bundle, String action) { if (bundle == null) return; SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(new AdminPermission(bundle, action)); } void refreshSystemModule() { final SystemModule systemModule = (SystemModule) moduleDatabase.getModule(0); if (systemModule == refreshingSystemModule.getAndSet(systemModule)) { return; } getAdaptor().refreshedSystemModule(); Thread t = new Thread(new Runnable() { @Override public void run() { try { systemModule.lockStateChange(ModuleEvent.UNRESOLVED); try { systemModule.stop(); } finally { systemModule.unlockStateChange(ModuleEvent.UNRESOLVED); } } catch (BundleException e) { e.printStackTrace(); } } }); t.start(); } boolean isRefreshingSystemModule() { return refreshingSystemModule.get() != null; } static Requirement getIdentityRequirement(String name, Version version) { version = version == null ? Version.emptyVersion : version; String filter = "(&(" + IdentityNamespace.IDENTITY_NAMESPACE + "=" + name + ")(" + IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE + "=" + version.toString() + "))"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$//$NON-NLS-4$//$NON-NLS-5$ Map directives = Collections.singletonMap(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter); return new ModuleRequirement(IdentityNamespace.IDENTITY_NAMESPACE, directives, Collections.emptyMap(), null); } class ContainerWiring implements FrameworkWiring, EventDispatcher> { private final Object monitor = new Object(); private EventManager refreshThread = null; @Override public Bundle getBundle() { return getSystemBundle(); } @Override public void refreshBundles(Collection bundles, FrameworkListener... listeners) { checkAdminPermission(getBundle(), AdminPermission.RESOLVE); Collection modules = getModules(bundles); // queue to refresh in the background // notice that we only do one refresh operation at a time CopyOnWriteIdentityMap dispatchListeners = new CopyOnWriteIdentityMap<>(); dispatchListeners.put(this, listeners); ListenerQueue> queue = new ListenerQueue<>(getManager()); queue.queueListeners(dispatchListeners.entrySet(), this); // dispatch the refresh job queue.dispatchEventAsynchronous(0, modules); } @Override public boolean resolveBundles(Collection bundles) { checkAdminPermission(getBundle(), AdminPermission.RESOLVE); Collection modules = getModules(bundles); resolve(modules, false); if (modules == null) { modules = ModuleContainer.this.getModules(); } for (Module module : modules) { if (getWiring(module.getCurrentRevision()) == null) return false; } return true; } @Override public Collection getRemovalPendingBundles() { moduleDatabase.readLock(); try { Collection removalPendingBundles = new HashSet<>(); Collection removalPending = moduleDatabase.getRemovalPending(); for (ModuleRevision moduleRevision : removalPending) { removalPendingBundles.add(moduleRevision.getBundle()); } return removalPendingBundles; } finally { moduleDatabase.readUnlock(); } } @Override public Collection getDependencyClosure(Collection bundles) { Collection modules = getModules(bundles); moduleDatabase.readLock(); try { Collection closure = getRefreshClosure(modules, moduleDatabase.getWiringsCopy()); Collection result = new ArrayList<>(closure.size()); for (Module module : closure) { result.add(module.getBundle()); } return result; } finally { moduleDatabase.readUnlock(); } } @Override public Collection findProviders(Requirement requirement) { return InternalUtils.asList(moduleDatabase.findCapabilities(requirement)); } private Collection getModules(final Collection bundles) { if (bundles == null) return null; return AccessController.doPrivileged(new PrivilegedAction>() { @Override public Collection run() { Collection result = new ArrayList<>(bundles.size()); for (Bundle bundle : bundles) { Module module = bundle.adapt(Module.class); if (module == null) throw new IllegalStateException("Could not adapt a bundle to a module. " + bundle); //$NON-NLS-1$ result.add(module); } return result; } }); } @Override public void dispatchEvent(ContainerWiring eventListener, FrameworkListener[] frameworkListeners, int eventAction, Collection eventObject) { try { refresh(eventObject); } finally { adaptor.publishContainerEvent(ContainerEvent.REFRESH, moduleDatabase.getModule(0), null, frameworkListeners); } } private EventManager getManager() { synchronized (monitor) { if (refreshThread == null) { refreshThread = new EventManager("Refresh Thread: " + adaptor.toString()); //$NON-NLS-1$ } return refreshThread; } } // because of bug 378491 we have to synchronize access to the manager // so we can close and re-open ourselves void close() { synchronized (monitor) { // force a manager to be created if it did not exist EventManager manager = getManager(); // this prevents any operations until open is called manager.close(); } } void open() { synchronized (monitor) { if (refreshThread != null) { // Make sure it is closed just incase refreshThread.close(); // a new one will be constructed on demand refreshThread = null; } } } } @Override public void optionsChanged(DebugOptions options) { moduleResolver.setDebugOptions(); frameworkStartLevel.setDebugOptions(); if (options != null) { this.DEBUG_MONITOR_LAZY = options.getBooleanOption(Debug.OPTION_MONITOR_LAZY, false); this.DEBUG_BUNDLE_START_TIME = options.getBooleanOption(Debug.OPTION_DEBUG_BUNDLE_START_TIME, false); } } class ContainerStartLevel implements FrameworkStartLevel, EventDispatcher { static final int USE_BEGINNING_START_LEVEL = Integer.MIN_VALUE; private static final int FRAMEWORK_STARTLEVEL = 1; private static final int MODULE_STARTLEVEL = 2; private final AtomicInteger activeStartLevel = new AtomicInteger(0); private final Object eventManagerLock = new Object(); private EventManager startLevelThread = null; private final Object frameworkStartLevelLock = new Object(); boolean debugStartLevel = false; { setDebugOptions(); } void setDebugOptions() { DebugOptions options = getAdaptor().getDebugOptions(); debugStartLevel = options == null ? false : options.getBooleanOption(Debug.OPTION_DEBUG_STARTLEVEL, false); } @Override public Bundle getBundle() { return getSystemBundle(); } @Override public int getStartLevel() { return activeStartLevel.get(); } void setStartLevel(Module module, int startlevel) { checkAdminPermission(module.getBundle(), AdminPermission.EXECUTE); if (module.getId() == 0) { throw new IllegalArgumentException(Msg.ModuleContainer_SystemStartLevelError); } if (startlevel < 1) { throw new IllegalArgumentException(Msg.ModuleContainer_NegativeStartLevelError + startlevel); } int currentLevel = module.getStartLevel(); if (currentLevel == startlevel) { return; // do nothing } moduleDatabase.setStartLevel(module, startlevel); // only queue the start level if // 1) the current level is less than the new startlevel, may need to stop or // 2) the module is marked for persistent activation, may need to start if (currentLevel < startlevel || module.isPersistentlyStarted()) { // queue start level operation in the background // notice that we only do one start level operation at a time CopyOnWriteIdentityMap dispatchListeners = new CopyOnWriteIdentityMap<>(); dispatchListeners.put(module, new FrameworkListener[0]); ListenerQueue queue = new ListenerQueue<>(getManager()); queue.queueListeners(dispatchListeners.entrySet(), this); // dispatch the start level job queue.dispatchEventAsynchronous(MODULE_STARTLEVEL, startlevel); } } @Override public void setStartLevel(int startlevel, FrameworkListener... listeners) { checkAdminPermission(getBundle(), AdminPermission.STARTLEVEL); if (startlevel < 1) { throw new IllegalArgumentException(Msg.ModuleContainer_NegativeStartLevelError + startlevel); } if (activeStartLevel.get() == 0) { throw new IllegalStateException(Msg.ModuleContainer_SystemNotActiveError); } if (debugStartLevel) { Debug.println("StartLevel: setStartLevel: " + startlevel); //$NON-NLS-1$ } // queue start level operation in the background // notice that we only do one start level operation at a time CopyOnWriteIdentityMap dispatchListeners = new CopyOnWriteIdentityMap<>(); dispatchListeners.put(moduleDatabase.getModule(0), listeners); ListenerQueue queue = new ListenerQueue<>(getManager()); queue.queueListeners(dispatchListeners.entrySet(), this); // dispatch the start level job queue.dispatchEventAsynchronous(FRAMEWORK_STARTLEVEL, startlevel); } @Override public int getInitialBundleStartLevel() { return moduleDatabase.getInitialModuleStartLevel(); } @Override public void setInitialBundleStartLevel(int startlevel) { checkAdminPermission(getBundle(), AdminPermission.STARTLEVEL); if (startlevel < 1) { throw new IllegalArgumentException(Msg.ModuleContainer_NegativeStartLevelError + startlevel); } moduleDatabase.setInitialModuleStartLevel(startlevel); } @Override public void dispatchEvent(Module module, FrameworkListener[] listeners, int eventAction, Integer startlevel) { switch (eventAction) { case FRAMEWORK_STARTLEVEL : doContainerStartLevel(module, startlevel, listeners); break; case MODULE_STARTLEVEL : if (debugStartLevel) { Debug.println("StartLevel: changing bundle startlevel; " + toString(module) + "; newSL=" + startlevel + "; activeSL=" + getStartLevel()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } try { if (getStartLevel() < startlevel) { if (Module.ACTIVE_SET.contains(module.getState())) { if (debugStartLevel) { Debug.println("StartLevel: stopping bundle; " + toString(module) + "; with startLevel=" + startlevel); //$NON-NLS-1$ //$NON-NLS-2$ } // Note that we don't need to hold the state change lock // here when checking the active status because no other // thread will successfully be able to start this bundle // since the start-level is no longer met. module.stop(StopOptions.TRANSIENT); } } else { if (debugStartLevel) { Debug.println("StartLevel: resuming bundle; " + toString(module) + "; with startLevel=" + startlevel); //$NON-NLS-1$ //$NON-NLS-2$ } module.start(StartOptions.TRANSIENT_IF_AUTO_START, StartOptions.TRANSIENT_RESUME); } } catch (BundleException e) { adaptor.publishContainerEvent(ContainerEvent.ERROR, module, e); } break; default : break; } } void doContainerStartLevel(Module module, int newStartLevel, FrameworkListener... listeners) { synchronized (frameworkStartLevelLock) { if (newStartLevel == USE_BEGINNING_START_LEVEL) { String beginningSL = adaptor.getProperty(Constants.FRAMEWORK_BEGINNING_STARTLEVEL); newStartLevel = beginningSL == null ? 1 : Integer.parseInt(beginningSL); } try { int currentSL = getStartLevel(); if (currentSL == 0) { // check for an active framework; this is only valid when the system bundle is starting Module systemModule = moduleDatabase.getModule(0); if (systemModule != null && !State.STARTING.equals(systemModule.getState())) { return; } } // Note that we must get a new list of modules each time; // this is because additional modules could have been installed from the previous start-level // but only do this if the module database has changed!! List sorted = null; long currentTimestamp = Long.MIN_VALUE; if (newStartLevel > currentSL) { List lazyStart = null; List lazyStartParallel = null; List eagerStart = null; List eagerStartParallel = null; for (int i = currentSL; i < newStartLevel; i++) { int toStartLevel = i + 1; activeStartLevel.set(toStartLevel); if (debugStartLevel) { Debug.println("StartLevel: incremented active start level to; " + toStartLevel); //$NON-NLS-1$ } if (sorted == null || currentTimestamp != moduleDatabase.getTimestamp()) { moduleDatabase.readLock(); try { sorted = moduleDatabase.getSortedModules(Sort.BY_START_LEVEL); lazyStart = new ArrayList<>(sorted.size()); lazyStartParallel = new ArrayList<>(sorted.size()); eagerStart = new ArrayList<>(sorted.size()); eagerStartParallel = new ArrayList<>(sorted.size()); separateModulesByActivationPolicy(sorted, lazyStart, lazyStartParallel, eagerStart, eagerStartParallel); currentTimestamp = moduleDatabase.getTimestamp(); } finally { moduleDatabase.readUnlock(); } } incStartLevel(toStartLevel, lazyStart, lazyStartParallel, eagerStart, eagerStartParallel); } } else { for (int i = currentSL; i > newStartLevel; i--) { int toStartLevel = i - 1; activeStartLevel.set(toStartLevel); if (debugStartLevel) { Debug.println("StartLevel: decremented active start level to " + toStartLevel); //$NON-NLS-1$ } if (sorted == null || currentTimestamp != moduleDatabase.getTimestamp()) { moduleDatabase.readLock(); try { sorted = moduleDatabase.getSortedModules(Sort.BY_START_LEVEL, Sort.BY_DEPENDENCY); currentTimestamp = moduleDatabase.getTimestamp(); } finally { moduleDatabase.readUnlock(); } } decStartLevel(toStartLevel, sorted); } } if (currentSL > 0 && newStartLevel > 0) { // Only fire the start level event if we are not in the middle // of launching or shutting down the framework adaptor.publishContainerEvent(ContainerEvent.START_LEVEL, module, null, listeners); } } catch (Error | RuntimeException e) { adaptor.publishContainerEvent(ContainerEvent.ERROR, module, e, listeners); throw e; } } } private void incStartLevel(int toStartLevel, List lazyStart, List lazyStartParallel, List eagerStart, List eagerStartParallel) { // start lazy activated first // start parallel bundles first incStartLevel(toStartLevel, lazyStartParallel, true); incStartLevel(toStartLevel, lazyStart, false); incStartLevel(toStartLevel, eagerStartParallel, true); incStartLevel(toStartLevel, eagerStart, false); } private void separateModulesByActivationPolicy(List sortedModules, List lazyStart, List lazyStartParallel, List eagerStart, List eagerStartParallel) { for (Module module : sortedModules) { if (!restrictParallelStart || module.isParallelActivated()) { if (module.isLazyActivate()) { lazyStartParallel.add(module); } else { eagerStartParallel.add(module); } } else { if (module.isLazyActivate()) { lazyStart.add(module); } else { eagerStart.add(module); } } } } private void incStartLevel(final int toStartLevel, List candidatesToStart, boolean inParallel) { if (candidatesToStart.isEmpty()) { return; } final List toStart = new ArrayList<>(); for (final Module module : candidatesToStart) { if (isRefreshingSystemModule()) { return; } try { int moduleStartLevel = module.getStartLevel(); if (moduleStartLevel < toStartLevel) { // skip modules who should have already been started continue; } else if (moduleStartLevel == toStartLevel) { toStart.add(module); } else { break; } } catch (IllegalStateException e) { // been uninstalled continue; } } if (toStart.isEmpty()) { return; } final Executor executor = inParallel ? adaptor.getStartLevelExecutor() : new Executor() { @Override public void execute(Runnable command) { command.run(); } }; final CountDownLatch done = new CountDownLatch(toStart.size()); for (final Module module : toStart) { executor.execute(new Runnable() { @Override public void run() { try { if (debugStartLevel) { Debug.println("StartLevel: resuming bundle; " + ContainerStartLevel.this.toString(module) + "; with startLevel=" + toStartLevel); //$NON-NLS-1$ //$NON-NLS-2$ } module.start(StartOptions.TRANSIENT_IF_AUTO_START, StartOptions.TRANSIENT_RESUME); } catch (BundleException e) { adaptor.publishContainerEvent(ContainerEvent.ERROR, module, e); } catch (IllegalStateException e) { // been uninstalled } finally { done.countDown(); } } }); } try { done.await(); } catch (InterruptedException e) { adaptor.publishContainerEvent(ContainerEvent.ERROR, moduleDatabase.getModule(0), e); } } private void decStartLevel(int toStartLevel, List sortedModules) { ListIterator iModules = sortedModules.listIterator(sortedModules.size()); while (iModules.hasPrevious()) { Module module = iModules.previous(); try { int moduleStartLevel = module.getStartLevel(); if (moduleStartLevel > toStartLevel + 1) { // skip modules who should have already been stopped continue; } else if (moduleStartLevel <= toStartLevel) { // stopped all modules we are going to for this start level break; } try { if (Module.ACTIVE_SET.contains(module.getState())) { if (debugStartLevel) { Debug.println("StartLevel: stopping bundle; " + toString(module) + "; with startLevel=" + moduleStartLevel); //$NON-NLS-1$ //$NON-NLS-2$ } // Note that we don't need to hold the state change lock // here when checking the active status because no other // thread will successfully be able to start this bundle // since the start-level is no longer met. module.stop(StopOptions.TRANSIENT); } } catch (BundleException e) { adaptor.publishContainerEvent(ContainerEvent.ERROR, module, e); } } catch (IllegalStateException e) { // been uninstalled continue; } } } private EventManager getManager() { synchronized (eventManagerLock) { if (startLevelThread == null) { startLevelThread = new EventManager("Start Level: " + adaptor.toString()); //$NON-NLS-1$ } return startLevelThread; } } // because of bug 378491 we have to synchronize access to the manager // so we can close and re-open ourselves void close() { synchronized (eventManagerLock) { // force a manager to be created if it did not exist EventManager manager = getManager(); // this prevents any operations until open is called manager.close(); } } void open() { synchronized (eventManagerLock) { if (startLevelThread != null) { // Make sure it is closed just incase startLevelThread.close(); // a new one will be constructed on demand startLevelThread = null; } } } String toString(Module m) { Bundle b = m.getBundle(); return b != null ? b.toString() : m.toString(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy