blob: ce85e5074b7490b7ac1ece8e1845a192e5474594 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2017 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.container;
import java.net.URL;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.osgi.container.ModuleRevisionBuilder.GenericInfo;
import org.eclipse.osgi.internal.container.AtomicLazyInitializer;
import org.eclipse.osgi.internal.container.InternalUtils;
import org.osgi.framework.AdminPermission;
import org.osgi.framework.Bundle;
import org.osgi.framework.namespace.HostNamespace;
import org.osgi.framework.namespace.PackageNamespace;
import org.osgi.framework.wiring.*;
import org.osgi.resource.*;
/**
* An implementation of {@link BundleWiring}.
* @since 3.10
*/
public final class ModuleWiring implements BundleWiring {
class LoaderInitializer implements Callable<ModuleLoader> {
@Override
public ModuleLoader call() throws Exception {
if (!isValid) {
return null;
}
return getRevision().getRevisions().getContainer().adaptor.createModuleLoader(ModuleWiring.this);
}
}
private static final RuntimePermission GET_CLASSLOADER_PERM = new RuntimePermission("getClassLoader"); //$NON-NLS-1$
private static final String DYNAMICALLY_ADDED_IMPORT_DIRECTIVE = "x.dynamically.added"; //$NON-NLS-1$
private final ModuleRevision revision;
private volatile List<ModuleCapability> capabilities;
private volatile List<ModuleRequirement> requirements;
private final Collection<String> substitutedPkgNames;
private final AtomicLazyInitializer<ModuleLoader> loader = new AtomicLazyInitializer<>();
private final LoaderInitializer loaderInitializer = new LoaderInitializer();
private volatile List<ModuleWire> providedWires;
private volatile List<ModuleWire> requiredWires;
volatile boolean isValid = true;
private final AtomicReference<Set<String>> dynamicMissRef = new AtomicReference<>();
ModuleWiring(ModuleRevision revision, List<ModuleCapability> capabilities, List<ModuleRequirement> requirements, List<ModuleWire> providedWires, List<ModuleWire> requiredWires, Collection<String> substitutedPkgNames) {
super();
this.revision = revision;
this.capabilities = capabilities;
this.requirements = requirements;
this.providedWires = providedWires;
this.requiredWires = requiredWires;
this.substitutedPkgNames = substitutedPkgNames.isEmpty() ? Collections.<String> emptyList() : substitutedPkgNames;
}
@Override
public Bundle getBundle() {
return revision.getBundle();
}
@Override
public boolean isCurrent() {
return isValid && revision.isCurrent();
}
@Override
public boolean isInUse() {
return isCurrent() || !providedWires.isEmpty() || isFragmentInUse();
}
private boolean isFragmentInUse() {
// A fragment is considered in use if it has any required host wires
if ((BundleRevision.TYPE_FRAGMENT & revision.getTypes()) != 0) {
List<ModuleWire> hostWires = getRequiredModuleWires(HostNamespace.HOST_NAMESPACE);
// hostWires may be null if the fragment wiring is no longer valid
return hostWires == null ? false : !hostWires.isEmpty();
}
return false;
}
/**
* Returns the same result as {@link #getCapabilities(String)} except
* uses type ModuleCapability.
* @param namespace the namespace
* @return the capabilities
* @see #getCapabilities(String)
*/
public List<ModuleCapability> getModuleCapabilities(String namespace) {
return getModuleCapabilities(namespace, capabilities);
}
private List<ModuleCapability> getModuleCapabilities(String namespace, List<ModuleCapability> allCapabilities) {
if (!isValid)
return null;
if (namespace == null)
return new ArrayList<>(allCapabilities);
List<ModuleCapability> result = new ArrayList<>();
for (ModuleCapability capability : allCapabilities) {
if (namespace.equals(capability.getNamespace())) {
result.add(capability);
}
}
return result;
}
/**
* Returns the same result as {@link #getRequirements(String)} except
* uses type ModuleRequirement.
* @param namespace the namespace
* @return the requirements
* @see #getRequirements(String)
*/
public List<ModuleRequirement> getModuleRequirements(String namespace) {
return getModuleRequirements(namespace, requirements);
}
List<ModuleRequirement> getPersistentRequirements() {
List<ModuleRequirement> persistentRequriements = getModuleRequirements(null);
if (persistentRequriements == null) {
return null;
}
for (Iterator<ModuleRequirement> iRequirements = persistentRequriements.iterator(); iRequirements.hasNext();) {
ModuleRequirement requirement = iRequirements.next();
if (PackageNamespace.PACKAGE_NAMESPACE.equals(requirement.getNamespace())) {
if ("true".equals(requirement.getDirectives().get(DYNAMICALLY_ADDED_IMPORT_DIRECTIVE))) { //$NON-NLS-1$
iRequirements.remove();
}
}
}
return persistentRequriements;
}
private List<ModuleRequirement> getModuleRequirements(String namespace, List<ModuleRequirement> allRequirements) {
if (!isValid)
return null;
if (namespace == null)
return new ArrayList<>(allRequirements);
List<ModuleRequirement> result = new ArrayList<>();
for (ModuleRequirement requirement : allRequirements) {
if (namespace.equals(requirement.getNamespace())) {
result.add(requirement);
}
}
return result;
}
@Override
public List<BundleCapability> getCapabilities(String namespace) {
return InternalUtils.asListBundleCapability(getModuleCapabilities(namespace));
}
@Override
public List<BundleRequirement> getRequirements(String namespace) {
return InternalUtils.asListBundleRequirement(getModuleRequirements(namespace));
}
/**
* Returns the same result as {@link #getProvidedWires(String)} except
* uses type ModuleWire.
* @param namespace the namespace
* @return the wires
* @see #getProvidedWires(String)
*/
public List<ModuleWire> getProvidedModuleWires(String namespace) {
return getWires(namespace, providedWires);
}
List<ModuleWire> getPersistentProvidedWires() {
return getPersistentWires(providedWires);
}
/**
* Returns the same result as {@link #getRequiredWires(String)} except
* uses type ModuleWire.
* @param namespace the namespace
* @return the wires
* @see #getRequiredWires(String)
*/
public List<ModuleWire> getRequiredModuleWires(String namespace) {
return getWires(namespace, requiredWires);
}
List<ModuleWire> getPersistentRequiredWires() {
return getPersistentWires(requiredWires);
}
private List<ModuleWire> getPersistentWires(List<ModuleWire> allWires) {
List<ModuleWire> persistentWires = getWires(null, allWires);
if (persistentWires == null) {
return null;
}
for (Iterator<ModuleWire> iWires = persistentWires.iterator(); iWires.hasNext();) {
ModuleWire wire = iWires.next();
if (PackageNamespace.PACKAGE_NAMESPACE.equals(wire.getRequirement().getNamespace())) {
if ("true".equals(wire.getRequirement().getDirectives().get(DYNAMICALLY_ADDED_IMPORT_DIRECTIVE))) { //$NON-NLS-1$
iWires.remove();
}
}
}
return persistentWires;
}
@Override
public List<BundleWire> getProvidedWires(String namespace) {
return InternalUtils.asListBundleWire(getWires(namespace, providedWires));
}
@Override
public List<BundleWire> getRequiredWires(String namespace) {
return InternalUtils.asListBundleWire(getWires(namespace, requiredWires));
}
private List<ModuleWire> getWires(String namespace, List<ModuleWire> allWires) {
if (!isValid)
return null;
if (namespace == null)
return new ArrayList<>(allWires);
List<ModuleWire> result = new ArrayList<>();
for (ModuleWire moduleWire : allWires) {
if (namespace.equals(moduleWire.getCapability().getNamespace())) {
result.add(moduleWire);
}
}
return result;
}
@Override
public ModuleRevision getRevision() {
return revision;
}
@Override
public ClassLoader getClassLoader() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(GET_CLASSLOADER_PERM);
}
if (!isValid) {
return null;
}
ModuleLoader current = getModuleLoader();
if (current == null) {
// must not be valid
return null;
}
return current.getClassLoader();
}
/**
* Returns the module loader for this wiring. If the module
* loader does not exist yet then one will be created
* @return the module loader for this wiring.
*/
public ModuleLoader getModuleLoader() {
return loader.getInitialized(loaderInitializer);
}
void loadFragments(Collection<ModuleRevision> fragments) {
ModuleLoader current = loader.get();
if (current != null) {
current.loadFragments(fragments);
}
}
@Override
public List<URL> findEntries(String path, String filePattern, int options) {
if (!hasResourcePermission())
return Collections.emptyList();
if (!isValid) {
return null;
}
ModuleLoader current = getModuleLoader();
if (current == null) {
// must not be valid
return null;
}
return current.findEntries(path, filePattern, options);
}
@Override
public Collection<String> listResources(String path, String filePattern, int options) {
if (!hasResourcePermission())
return Collections.emptyList();
if (!isValid) {
return null;
}
ModuleLoader current = getModuleLoader();
if (current == null) {
// must not be valid
return null;
}
return current.listResources(path, filePattern, options);
}
@Override
public List<Capability> getResourceCapabilities(String namespace) {
return InternalUtils.asListCapability(getCapabilities(namespace));
}
@Override
public List<Requirement> getResourceRequirements(String namespace) {
return InternalUtils.asListRequirement(getRequirements(namespace));
}
@Override
public List<Wire> getProvidedResourceWires(String namespace) {
return InternalUtils.asListWire(getWires(namespace, providedWires));
}
@Override
public List<Wire> getRequiredResourceWires(String namespace) {
return InternalUtils.asListWire(getWires(namespace, requiredWires));
}
@Override
public ModuleRevision getResource() {
return revision;
}
void setProvidedWires(List<ModuleWire> providedWires) {
this.providedWires = providedWires;
}
void setRequiredWires(List<ModuleWire> requiredWires) {
this.requiredWires = requiredWires;
}
void setCapabilities(List<ModuleCapability> capabilities) {
this.capabilities = capabilities;
}
void unload() {
// When unloading a wiring we need to release the loader.
// This is so that the loaders are not pinned when stopping the framework.
// Then the framework can be relaunched, at which point new loaders will
// get created.
invalidate0(true);
}
void invalidate() {
invalidate0(false);
}
private void invalidate0(boolean releaseLoader) {
// set the isValid to false first
isValid = false;
ModuleLoader current = releaseLoader ? loader.getAndClear() : loader.get();
revision.getRevisions().getContainer().getAdaptor().invalidateWiring(this, current);
}
void validate() {
this.isValid = true;
}
boolean isSubtituted(ModuleCapability capability) {
if (!PackageNamespace.PACKAGE_NAMESPACE.equals(capability.getNamespace())) {
return false;
}
return substitutedPkgNames.contains(capability.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE));
}
/**
* Returns true if the specified package name has been substituted in this wiring
* @param packageName the package name to check
* @return true if the specified package name has been substituted in this wiring
*/
public boolean isSubstitutedPackage(String packageName) {
return substitutedPkgNames.contains(packageName);
}
/**
* Returns an unmodifiable collection of package names for
* package capabilities that have been substituted.
* @return the substituted package names
*/
public Collection<String> getSubstitutedNames() {
return Collections.unmodifiableCollection(substitutedPkgNames);
}
private boolean hasResourcePermission() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
try {
sm.checkPermission(new AdminPermission(getBundle(), AdminPermission.RESOURCE));
} catch (SecurityException e) {
return false;
}
}
return true;
}
/**
* Adds the {@link ModuleRevisionBuilder#getRequirements() requirements} from
* the specified builder to this wiring. The new requirements must be in the
* {@link PackageNamespace}. These requirements are transient
* and will not exist when loading up persistent wirings.
* @param builder the builder that defines the new dynamic imports.
*/
public void addDynamicImports(ModuleRevisionBuilder builder) {
List<GenericInfo> newImports = builder.getRequirements();
List<ModuleRequirement> newRequirements = new ArrayList<>();
for (GenericInfo info : newImports) {
if (!PackageNamespace.PACKAGE_NAMESPACE.equals(info.getNamespace())) {
throw new IllegalArgumentException("Invalid namespace for package imports: " + info.getNamespace()); //$NON-NLS-1$
}
Map<String, Object> attributes = new HashMap<>(info.getAttributes());
Map<String, String> directives = new HashMap<>(info.getDirectives());
directives.put(DYNAMICALLY_ADDED_IMPORT_DIRECTIVE, "true"); //$NON-NLS-1$
directives.put(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE, PackageNamespace.RESOLUTION_DYNAMIC);
newRequirements.add(new ModuleRequirement(info.getNamespace(), directives, attributes, revision));
}
ModuleDatabase moduleDatabase = revision.getRevisions().getContainer().moduleDatabase;
moduleDatabase.writeLock();
try {
List<ModuleRequirement> updatedRequirements = new ArrayList<>(requirements);
updatedRequirements.addAll(newRequirements);
requirements = updatedRequirements;
} finally {
moduleDatabase.writeUnlock();
}
}
void addDynamicPackageMiss(String packageName) {
Set<String> misses = dynamicMissRef.get();
if (misses == null) {
dynamicMissRef.compareAndSet(null, Collections.synchronizedSet(new HashSet<String>()));
misses = dynamicMissRef.get();
}
misses.add(packageName);
}
boolean isDynamicPackageMiss(String packageName) {
Set<String> misses = dynamicMissRef.get();
return misses != null && misses.contains(packageName);
}
void removeDynamicPackageMisses(Collection<String> packageNames) {
Set<String> misses = dynamicMissRef.get();
if (misses != null) {
misses.removeAll(packageNames);
}
}
@Override
public String toString() {
return revision.toString();
}
Collection<Wire> getSubstitutionWires() {
if (substitutedPkgNames.isEmpty()) {
return Collections.emptyList();
}
// Could cache this, but seems unnecessary since it will only be used by the resolver
Collection<Wire> substitutionWires = new ArrayList<>(substitutedPkgNames.size());
List<ModuleWire> current = requiredWires;
for (ModuleWire wire : current) {
Capability cap = wire.getCapability();
if (PackageNamespace.PACKAGE_NAMESPACE.equals(cap.getNamespace())) {
if (substitutedPkgNames.contains(cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE))) {
substitutionWires.add(wire);
}
}
}
return substitutionWires;
}
}