blob: c8f1919b8587a73091fa37edab715dcebc2c3e9a [file] [log] [blame]
/*******************************************************************************
* 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<String> locationLocks = new LockSet<>();
/**
* Used by install and update operations to establish a write lock for a name
*/
private final LockSet<String> 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<SystemModule> 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<Module> 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<String, String> directives, Map<String, ?> 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.
* <p>
* 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<Module> 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<ModuleCapability> 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<Module> 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<ModuleCapability> 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<Module> triggers, boolean triggersMandatory) {
return resolve(triggers, triggersMandatory, false);
}
private ResolutionReport resolve(Collection<Module> triggers, boolean triggersMandatory, boolean restartTriggers) {
if (isRefreshingSystemModule()) {
return new ModuleResolutionReport(null, Collections.<Resource, List<Entry>> 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.<Resource, List<Entry>> emptyMap(), new ResolutionException(be));
}
}
throw e;
}
} while (report == null);
} catch (ResolutionLockException e) {
return new ModuleResolutionReport(null, Collections.<Resource, List<Entry>> emptyMap(), new ResolutionException("Timeout acquiring lock for resolution", e, Collections.<Requirement> emptyList())); //$NON-NLS-1$
}
return report;
}
private ResolutionReport resolveAndApply(Collection<Module> triggers, boolean triggersMandatory, boolean restartTriggers, ResolutionLock.Permits resolutionPermits) {
if (triggers == null)
triggers = new ArrayList<>(0);
Collection<ModuleRevision> triggerRevisions = new ArrayList<>(triggers.size());
Collection<ModuleRevision> unresolved = new ArrayList<>();
Map<ModuleRevision, ModuleWiring> 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<Module> 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<Resource, List<Wire>> resolutionResult = report.getResolutionResult();
Map<ModuleRevision, ModuleWiring> deltaWiring = resolutionResult == null ? Collections.<ModuleRevision, ModuleWiring> emptyMap() : moduleResolver.generateDelta(resolutionResult, wiringClone);
if (deltaWiring.isEmpty())
return report; // nothing to do
Collection<Module> 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<ModuleRevision, ModuleWiring> deltaWiring;
Collection<Module> modulesResolved;
long timestamp;
try (Permits resolutionPermits = _resolutionLock.acquire(ResolutionLock.MAX_RESOLUTION_PERMITS)) {
do {
result = null;
Map<ModuleRevision, ModuleWiring> wiringClone = null;
List<DynamicModuleRequirement> dynamicReqs = null;
Collection<ModuleRevision> 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<Module> 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<Resource, List<Wire>> resolutionResult = report.getResolutionResult();
deltaWiring = resolutionResult == null ? Collections.<ModuleRevision, ModuleWiring> 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<Entry> 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.<Module> emptyList(), timestamp, false, resolutionPermits));
} catch (ResolutionLockException e) {
return null;
}
return result;
}
private ModuleWire findExistingDynamicWire(ModuleWiring wiring, String dynamicPkgName) {
if (wiring == null) {
return null;
}
List<ModuleWire> 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<ModuleRevision, ModuleWiring> deltaWiring, Collection<Module> modulesResolved, Collection<Module> triggers, long timestamp, boolean restartTriggers, ResolutionLock.Permits resolutionPermits) {
List<Module> 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<ModuleWiring, Collection<ModuleRevision>> hostsWithDynamicFrags = new HashMap<>(0);
moduleDatabase.writeLock();
try {
if (timestamp != moduleDatabase.getRevisionsTimestamp())
return false; // need to try again
Map<ModuleRevision, ModuleWiring> wiringCopy = moduleDatabase.getWiringsCopy();
for (Map.Entry<ModuleRevision, ModuleWiring> 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<ModuleRevision> 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<ModuleWiring, Collection<ModuleRevision>> 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<Module> triggerSet = restartTriggers ? new HashSet<>(triggers) : Collections.<Module> 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<DynamicModuleRequirement> 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<DynamicModuleRequirement> 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<Module> unresolve(Collection<Module> initial) {
Collection<Module> refreshTriggers = null;
while (refreshTriggers == null) {
refreshTriggers = unresolve0(initial);
}
return refreshTriggers;
}
private Collection<Module> unresolve0(Collection<Module> initial) {
Map<ModuleRevision, ModuleWiring> wiringCopy;
List<Module> refreshTriggers;
Collection<ModuleRevision> toRemoveRevisions;
Collection<ModuleWiring> toRemoveWirings;
Map<ModuleWiring, Collection<ModuleWire>> 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<Module> 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<ModuleWire> removedWires = removedWiring.getRequiredModuleWires(null);
for (ModuleWire wire : removedWires) {
Collection<ModuleWire> 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<Module> modulesLocked = new ArrayList<>(refreshTriggers.size());
Collection<Module> 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<Module> 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<Module> 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<ModuleWiring, Collection<ModuleWire>> entry : toRemoveWireLists.entrySet()) {
NamespaceList.Builder<ModuleWire> 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<Module> initial) {
if (initial == null) {
return;
}
Long zero = Long.valueOf(0);
for (Iterator<Module> 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<ModuleWire> 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<Module> initial) {
initial = initial == null ? null : new ArrayList<>(initial);
Collection<Module> 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<Module> getDependencyClosure(Collection<Module> 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<ModuleRevision> 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<Module> 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<ModuleRevision, ModuleWiring> 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<Module> 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<ModuleRevision, ModuleWiring> 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<Module> 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<ModuleRevision, ModuleWiring> wirings = moduleDatabase.getWiringsCopy();
for (ModuleWiring wiring : wirings.values()) {
wiring.unload();
}
} finally {
moduleDatabase.readUnlock();
}
}
Set<Module> getRefreshClosure(Collection<Module> initial, Map<ModuleRevision, ModuleWiring> wiringCopy) {
Set<Module> refreshClosure = new HashSet<>();
if (initial == null) {
initial = new HashSet<>();
Collection<ModuleRevision> 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<ModuleRevision, ModuleWiring> wiringCopy, Set<Module> refreshClosure) {
if (refreshClosure.contains(module))
return;
refreshClosure.add(module);
List<ModuleRevision> revisions = module.getRevisions().getModuleRevisions();
for (ModuleRevision revision : revisions) {
ModuleWiring wiring = wiringCopy.get(revision);
if (wiring == null)
continue;
List<ModuleWire> 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<ModuleWire> hosts = wiring.getRequiredModuleWires(HostNamespace.HOST_NAMESPACE);
for (ModuleWire hostWire : hosts) {
addDependents(hostWire.getProvider().getRevisions().getModule(), wiringCopy, refreshClosure);
}
}
}
}
static Collection<ModuleRevision> getDependencyClosure(ModuleRevision initial, Map<ModuleRevision, ModuleWiring> wiringCopy) {
Set<ModuleRevision> dependencyClosure = new HashSet<>();
addDependents(initial, wiringCopy, dependencyClosure);
return dependencyClosure;
}
private static void addDependents(ModuleRevision revision, Map<ModuleRevision, ModuleWiring> wiringCopy, Set<ModuleRevision> dependencyClosure) {
if (dependencyClosure.contains(revision))
return;
dependencyClosure.add(revision);
ModuleWiring wiring = wiringCopy.get(revision);
if (wiring == null)
return;
List<ModuleWire> 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<ModuleWire> 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<String, String> directives = Collections.<String, String> singletonMap(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter);
return new ModuleRequirement(IdentityNamespace.IDENTITY_NAMESPACE, directives, Collections.<String, Object> emptyMap(), null);
}
class ContainerWiring implements FrameworkWiring, EventDispatcher<ContainerWiring, FrameworkListener[], Collection<Module>> {
private final Object monitor = new Object();
private EventManager refreshThread = null;
@Override
public Bundle getBundle() {
return getSystemBundle();
}
@Override
public void refreshBundles(Collection<Bundle> bundles, FrameworkListener... listeners) {
checkAdminPermission(getBundle(), AdminPermission.RESOLVE);
Collection<Module> modules = getModules(bundles);
// queue to refresh in the background
// notice that we only do one refresh operation at a time
CopyOnWriteIdentityMap<ContainerWiring, FrameworkListener[]> dispatchListeners = new CopyOnWriteIdentityMap<>();
dispatchListeners.put(this, listeners);
ListenerQueue<ContainerWiring, FrameworkListener[], Collection<Module>> queue = new ListenerQueue<>(getManager());
queue.queueListeners(dispatchListeners.entrySet(), this);
// dispatch the refresh job
queue.dispatchEventAsynchronous(0, modules);
}
@Override
public boolean resolveBundles(Collection<Bundle> bundles) {
checkAdminPermission(getBundle(), AdminPermission.RESOLVE);
Collection<Module> 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<Bundle> getRemovalPendingBundles() {
moduleDatabase.readLock();
try {
Collection<Bundle> removalPendingBundles = new HashSet<>();
Collection<ModuleRevision> removalPending = moduleDatabase.getRemovalPending();
for (ModuleRevision moduleRevision : removalPending) {
removalPendingBundles.add(moduleRevision.getBundle());
}
return removalPendingBundles;
} finally {
moduleDatabase.readUnlock();
}
}
@Override
public Collection<Bundle> getDependencyClosure(Collection<Bundle> bundles) {
Collection<Module> modules = getModules(bundles);
moduleDatabase.readLock();
try {
Collection<Module> closure = getRefreshClosure(modules, moduleDatabase.getWiringsCopy());
Collection<Bundle> result = new ArrayList<>(closure.size());
for (Module module : closure) {
result.add(module.getBundle());
}
return result;
} finally {
moduleDatabase.readUnlock();
}
}
@Override
public Collection<BundleCapability> findProviders(Requirement requirement) {
return InternalUtils.asListBundleCapability(moduleDatabase.findCapabilities(requirement));
}
private Collection<Module> getModules(final Collection<Bundle> bundles) {
if (bundles == null)
return null;
return AccessController.doPrivileged(new PrivilegedAction<Collection<Module>>() {
@Override
public Collection<Module> run() {
Collection<Module> 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<Module> 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<Module, FrameworkListener[], Integer> {
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<Module, FrameworkListener[]> dispatchListeners = new CopyOnWriteIdentityMap<>();
dispatchListeners.put(module, new FrameworkListener[0]);
ListenerQueue<Module, FrameworkListener[], Integer> 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<Module, FrameworkListener[]> dispatchListeners = new CopyOnWriteIdentityMap<>();
dispatchListeners.put(moduleDatabase.getModule(0), listeners);
ListenerQueue<Module, FrameworkListener[], Integer> 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<Module> sorted = null;
long currentTimestamp = Long.MIN_VALUE;
if (newStartLevel > currentSL) {
List<Module> lazyStart = null;
List<Module> lazyStartParallel = null;
List<Module> eagerStart = null;
List<Module> 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<Module> lazyStart, List<Module> lazyStartParallel, List<Module> eagerStart, List<Module> 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<Module> sortedModules, List<Module> lazyStart, List<Module> lazyStartParallel, List<Module> eagerStart, List<Module> 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<Module> candidatesToStart, boolean inParallel) {
if (candidatesToStart.isEmpty()) {
return;
}
final List<Module> 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<Module> sortedModules) {
ListIterator<Module> 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();
}
}
}