/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.aries.subsystem.core.internal;
import java.io.IOException;
import java.net.URISyntaxException;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.aries.subsystem.core.archive.ProvisionPolicyDirective;
import org.apache.aries.subsystem.core.archive.SubsystemContentHeader;
import org.apache.aries.subsystem.core.archive.SubsystemManifest;
import org.apache.aries.subsystem.core.archive.SubsystemTypeHeader;
import org.apache.aries.subsystem.core.internal.BundleResourceInstaller.BundleConstituent;
import org.apache.aries.subsystem.core.internal.DependencyCalculator.MissingCapability;
import org.apache.aries.subsystem.core.internal.StartAction.Restriction;
import org.apache.aries.subsystem.core.repository.Repository;
import org.eclipse.equinox.region.Region;
import org.osgi.framework.BundleException;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.namespace.ExecutionEnvironmentNamespace;
import org.osgi.framework.namespace.HostNamespace;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.framework.namespace.NativeNamespace;
import org.osgi.framework.namespace.PackageNamespace;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.BundleWiring;
import org.osgi.resource.Capability;
import org.osgi.resource.Namespace;
import org.osgi.resource.Requirement;
import org.osgi.resource.Resource;
import org.osgi.resource.Wire;
import org.osgi.resource.Wiring;
import org.osgi.service.resolver.HostedCapability;
import org.osgi.service.subsystem.Subsystem.State;
public class ResolveContext extends org.osgi.service.resolver.ResolveContext {
private final Repository contentRepository;
private final Repository localRepository;
private final Repository preferredProviderRepository;
private final Repository repositoryServiceRepository;
private final SubsystemResource resource;
private final Repository systemRepository;
private final Map<Resource, Wiring> wirings = computeWirings();
public ResolveContext(SubsystemResource resource) {
this.resource = resource;
contentRepository = new ContentRepository(resource.getInstallableContent(), resource.getSharedContent());
localRepository = resource.getLocalRepository();
preferredProviderRepository = new PreferredProviderRepository(resource);
repositoryServiceRepository = new RepositoryServiceRepository();
systemRepository = Activator.getInstance().getSystemRepository();
}
private void installDependenciesOfRequirerIfNecessary(Requirement requirement) {
if (requirement == null) {
return;
}
Resource requirer = requirement.getResource();
if (resource.equals(requirer)) {
return;
}
Collection<BasicSubsystem> subsystems;
if (requirer instanceof BasicSubsystem) {
BasicSubsystem subsystem = (BasicSubsystem)requirer;
subsystems = Collections.singletonList(subsystem);
}
else if (requirer instanceof BundleRevision) {
BundleRevision revision = (BundleRevision)requirer;
BundleConstituent constituent = new BundleConstituent(null, revision);
subsystems = Activator.getInstance().getSubsystems().getSubsystemsByConstituent(constituent);
}
else {
return;
}
for (BasicSubsystem subsystem : subsystems) {
if (Utils.isProvisionDependenciesInstall(subsystem)
|| !State.INSTALLING.equals(subsystem.getState())) {
continue;
}
AccessController.doPrivileged(new StartAction(subsystem, subsystem, subsystem, Restriction.INSTALL_ONLY));
}
}
private boolean isResolved(Resource resource) {
return wirings.containsKey(resource);
}
private boolean isProcessableAsFragment(Requirement requirement) {
Resource resource = requirement.getResource();
String namespace = requirement.getNamespace();
return Utils.isFragment(resource)
&& !(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE.equals(namespace)
|| HostNamespace.HOST_NAMESPACE.equals(namespace));
}
private void processAsFragment(Requirement requirement, List<Capability> capabilities) {
String namespace = requirement.getNamespace();
Resource fragment = requirement.getResource();
Wiring fragmentWiring = wirings.get(fragment);
List<Wire> fragmentWires = fragmentWiring.getRequiredResourceWires(HostNamespace.HOST_NAMESPACE);
for (Wire fragmentWire : fragmentWires) {
Resource host = fragmentWire.getProvider();
Wiring hostWiring = wirings.get(host);
List<Wire> hostWires = hostWiring.getRequiredResourceWires(namespace);
processWires(hostWires, requirement, capabilities);
}
}
private void processWires(Collection<Wire> wires, Requirement requirement, List<Capability> capabilities) {
for (Wire wire : wires) {
processWire(wire, requirement, capabilities);
}
}
private void processWire(Wire wire, Requirement requirement, List<Capability> capabilities) {
if (requirement.equals(wire.getRequirement())) {
capabilities.add(wire.getCapability());
}
}
private void processCapability(Capability capability, Requirement requirement, List<Capability> capabilities) {
if (ResourceHelper.matches(requirement, capability)) {
capabilities.add(capability);
}
}
private void processResourceCapabilities(Collection<Capability> resourceCapabilities, Requirement requirement, List<Capability> capabilities) {
for (Capability resourceCapability : resourceCapabilities) {
processCapability(resourceCapability, requirement, capabilities);
}
}
private void processAsBundle(Requirement requirement, List<Capability> capabilities) {
String namespace = requirement.getNamespace();
Resource bundle = requirement.getResource();
Wiring wiring = wirings.get(bundle);
List<Wire> wires = wiring.getRequiredResourceWires(namespace);
processWires(wires, requirement, capabilities);
}
private void processAsSubstitutableExport(boolean isFragment, Requirement requirement, List<Capability> capabilities) {
String namespace = requirement.getNamespace();
if (!PackageNamespace.PACKAGE_NAMESPACE.equals(namespace)) {
return;
}
Resource resource = requirement.getResource();
Wiring wiring = wirings.get(resource);
if (isFragment) {
List<Wire> fragmentWires = wiring.getRequiredResourceWires(HostNamespace.HOST_NAMESPACE);
for (Wire fragmentWire : fragmentWires) {
Resource host = fragmentWire.getProvider();
processResourceCapabilities(
wirings.get(host).getResourceCapabilities(namespace),
requirement,
capabilities);
}
}
else {
List<Capability> resourceCapabilities = wiring.getResourceCapabilities(namespace);
processResourceCapabilities(resourceCapabilities, requirement, capabilities);
}
}
private void processAlreadyResolvedResource(Resource resource, Requirement requirement, List<Capability> capabilities) {
boolean isFragment = isProcessableAsFragment(requirement);
if (isFragment) {
processAsFragment(requirement, capabilities);
}
else {
processAsBundle(requirement, capabilities);
}
if (capabilities.isEmpty() && Utils.isMandatory(requirement)) {
processAsSubstitutableExport(isFragment, requirement, capabilities);
if (capabilities.isEmpty()) {
// ARIES-1538. Do not fail subsystem resolution if an already
// resolved resource has a missing dependency.
capabilities.add(new MissingCapability(requirement));
}
}
}
private void processNewlyResolvedResource(Resource resource, Requirement requirement, List<Capability> capabilities) {
try {
// Only check the system repository for osgi.ee and osgi.native
if (ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE.equals(requirement.getNamespace())
|| NativeNamespace.NATIVE_NAMESPACE.equals(requirement.getNamespace())) {
addDependenciesFromSystemRepository(requirement, capabilities);
} else {
addDependenciesFromContentRepository(requirement, capabilities);
addDependenciesFromPreferredProviderRepository(requirement, capabilities);
addDependenciesFromSystemRepository(requirement, capabilities);
addDependenciesFromLocalRepository(requirement, capabilities);
if (capabilities.isEmpty()) {
addDependenciesFromRepositoryServiceRepositories(requirement, capabilities);
}
}
if (capabilities.isEmpty()) {
// Is the requirement optional?
String resolution = requirement.getDirectives().get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE);
if (Namespace.RESOLUTION_OPTIONAL.equals(resolution)) {
// Yes, it's optional. Add a missing capability to ensure
// it gets added to the sharing policy per the specification.
capabilities.add(new MissingCapability(requirement));
}
// Is the requirement resource already resolved? See ARIES-1538.
else if (isResolved(requirement.getResource())) {
// Yes, the resource has already been resolved. Do not fail
// the subsystem resolution due to a missing dependency.
capabilities.add(new MissingCapability(requirement));
}
}
}
catch (Throwable t) {
Utils.handleTrowable(t);
}
}
@Override
public List<Capability> findProviders(Requirement requirement) {
ArrayList<Capability> capabilities = new ArrayList<Capability>();
Resource resource = requirement.getResource();
if (isResolved(resource)
&& Utils.isEffectiveResolve(requirement)) {
processAlreadyResolvedResource(resource, requirement, capabilities);
}
else {
installDependenciesOfRequirerIfNecessary(requirement);
processNewlyResolvedResource(resource, requirement, capabilities);
}
capabilities.trimToSize();
return capabilities;
}
@Override
public int insertHostedCapability(List<Capability> capabilities, HostedCapability hostedCapability) {
// Must specify the location where the capability is to be added. From the ResoveContext javadoc:
// "This method must insert the specified HostedCapability in a place that makes the list maintain
// the preference order."
// The Felix implementation provides a list that requires the index to be specified in the add() call,
// otherwise it will throw an exception.
int sz = capabilities.size();
capabilities.add(sz, hostedCapability);
return sz;
}
@Override
public boolean isEffective(Requirement requirement) {
return true;
}
@Override
public Collection<Resource> getMandatoryResources() {
return resource.getMandatoryResources();
}
@Override
public Collection<Resource> getOptionalResources() {
return resource.getOptionalResources();
}
@Override
public Map<Resource, Wiring> getWirings() {
return Collections.emptyMap();
}
private boolean addDependencies(Repository repository, Requirement requirement, List<Capability> capabilities, boolean validate) throws BundleException, IOException, InvalidSyntaxException, URISyntaxException {
if (repository == null)
return false;
Map<Requirement, Collection<Capability>> m = repository.findProviders(Collections.singleton(requirement));
if (m.containsKey(requirement)) {
Collection<Capability> cc = m.get(requirement);
addValidCapabilities(cc, capabilities, requirement, validate);
}
return !capabilities.isEmpty();
}
private boolean addDependenciesFromContentRepository(Requirement requirement, List<Capability> capabilities) throws BundleException, IOException, InvalidSyntaxException, URISyntaxException {
return addDependencies(contentRepository, requirement, capabilities, false);
}
private boolean addDependenciesFromLocalRepository(Requirement requirement, List<Capability> capabilities) throws BundleException, IOException, InvalidSyntaxException, URISyntaxException {
return addDependencies(localRepository, requirement, capabilities, true);
}
private boolean addDependenciesFromPreferredProviderRepository(Requirement requirement, List<Capability> capabilities) throws BundleException, IOException, InvalidSyntaxException, URISyntaxException {
return addDependencies(preferredProviderRepository, requirement, capabilities, true);
}
private boolean addDependenciesFromRepositoryServiceRepositories(Requirement requirement, List<Capability> capabilities) throws BundleException, IOException, InvalidSyntaxException, URISyntaxException {
return addDependencies(repositoryServiceRepository, requirement, capabilities, true);
}
private boolean addDependenciesFromSystemRepository(Requirement requirement, List<Capability> capabilities) throws BundleException, IOException, InvalidSyntaxException, URISyntaxException {
boolean result = addDependencies(systemRepository, requirement, capabilities, true);
return result;
}
private void addValidCapabilities(Collection<Capability> from, Collection<Capability> to, Requirement requirement, boolean validate) throws BundleException, IOException, InvalidSyntaxException, URISyntaxException {
for (Capability c : from) {
if (!validate || isValid(c, requirement)) {
// either validation is not requested or the capability is valid.
to.add(c);
}
}
}
private void addWiring(Resource resource, Map<Resource, Wiring> wirings) {
if (resource instanceof BundleConstituent) {
BundleConstituent bc = (BundleConstituent)resource;
BundleWiring wiring = bc.getWiring();
if (wiring != null) {
wirings.put(bc.getBundle().adapt(BundleRevision.class), wiring);
}
}
else if (resource instanceof BundleRevision) {
BundleRevision br = (BundleRevision)resource;
BundleWiring wiring = br.getWiring();
if (wiring != null) {
wirings.put(br, wiring);
}
}
}
private Map<Resource, Wiring> computeWirings() {
Map<Resource, Wiring> wirings = new HashMap<Resource, Wiring>();
for (BasicSubsystem subsystem : Activator.getInstance().getSubsystems().getSubsystems()) { // NEED
for (Resource constituent : subsystem.getConstituents()) {
addWiring(constituent, wirings);
}
}
return Collections.unmodifiableMap(wirings);
}
private boolean isContent(Resource resource) {
return this.resource.isContent(resource);
}
private boolean isInstallable(Resource resource) {
return !isShared(resource);
}
private boolean isShared(Resource resource) {
return Utils.isSharedResource(resource);
}
private boolean isValid(Capability capability, Requirement requirement) throws BundleException, IOException, InvalidSyntaxException, URISyntaxException {
if (IdentityNamespace.IDENTITY_NAMESPACE.equals(capability.getNamespace()))
return true;
Resource provider = capability.getResource();
Resource requirer = requirement.getResource();
SubsystemManifest manifest = resource.getSubsystemManifest();
SubsystemContentHeader header = manifest.getSubsystemContentHeader();
if (header.contains(provider) && header.contains(requirer)) {
// Shortcut. If both the provider and requirer are content then they
// are in the same region and the capability will be visible.
return true;
}
Region from = findRegionForCapabilityValidation(provider);
Region to = findRegionForCapabilityValidation(requirer);
return new SharingPolicyValidator(from, to).isValid(capability);
}
private boolean isAcceptDependencies() {
SubsystemManifest manifest = resource.getSubsystemManifest();
SubsystemTypeHeader header = manifest.getSubsystemTypeHeader();
ProvisionPolicyDirective directive = header.getProvisionPolicyDirective();
return directive.isAcceptDependencies();
}
private Region findRegionForCapabilityValidation(Resource resource) throws BundleException, IOException, InvalidSyntaxException, URISyntaxException {
if (isInstallable(resource)) {
// This is an installable resource so we need to figure out where it
// will be installed.
if (isContent(resource) // If the resource is content of this subsystem, it will be installed here.
// Or if this subsystem accepts dependencies, the resource will be installed here.
|| isAcceptDependencies()) {
if (this.resource.isComposite()) {
// Composites define their own sharing policy with which
// their regions are already configured by the time we get
// here. We ensure capabilities are visible to this region.
return this.resource.getRegion();
}
// For applications and features, we must ensure capabilities
// are visible to their scoped parent. Features import
// everything. Applications have their sharing policies
// computed, so if capabilities are visible to the parent, we
// know we can make them visible to the application.
BasicSubsystem parent = this.resource.getParents().iterator().next();
// If the parent accepts dependencies, the resource will
// be installed there and all capabilities will be visible.
if (parent.getSubsystemManifest().getSubsystemTypeHeader().getProvisionPolicyDirective().isAcceptDependencies()) {
return parent.getRegion();
}
// Otherwise, the "parent" is defined as the first scoped
// ancestor whose sharing policy has already been set. This
// covers the case of multiple subsystems from the same archive
// being installed whose regions will form a tree of depth N.
parent = Utils.findFirstScopedAncestorWithSharingPolicy(this.resource);
return parent.getRegion();
}
return Utils.findFirstSubsystemAcceptingDependenciesStartingFrom(this.resource.getParents().iterator().next()).getRegion();
}
else {
// This is an already installed resource from the system repository.
if (Utils.isBundle(resource)) {
if (isContent(resource)
&& this.resource.getSubsystemManifest().getSubsystemTypeHeader().getAriesProvisionDependenciesDirective().isResolve()) {
// If we get here with a subsystem that is
// apache-aries-provision-dependencies:=resolve, it means
// that a restart has occurred with the subsystem in the
// INSTALLING state. Its content has already been installed.
// However, because the sharing policy has not yet been set,
// we must treat it similarly to the installable content case
// above.
return Utils.findFirstScopedAncestorWithSharingPolicy(this.resource).getRegion();
}
BundleRevision revision = resource instanceof BundleRevision ? (BundleRevision)resource : ((BundleRevisionResource)resource).getRevision();
// If it's a bundle, use region digraph to get the region in order
// to account for bundles in isolated regions outside of the
// subsystems API.
return Activator.getInstance().getRegionDigraph().getRegion(revision.getBundle());
}
else {
if (this.resource.getSubsystemManifest().getSubsystemTypeHeader().getAriesProvisionDependenciesDirective().isResolve()) {
return Utils.findFirstScopedAncestorWithSharingPolicy(this.resource).getRegion();
}
// If it's anything else, get the region from one of the
// subsystems referencing it.
return Activator.getInstance().getSubsystems().getSubsystemsReferencing(resource).iterator().next().getRegion();
}
}
}
}