blob: 28eaef05aafcfc18c80e2350eaae31ba88476156 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011 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 - Initial API and implementation
******************************************************************************/
package org.eclipse.equinox.internal.resolver;
import java.util.*;
import java.util.Map.Entry;
import org.eclipse.equinox.internal.resolver.FilterParser.FilterComponent;
import org.eclipse.osgi.service.resolver.*;
import org.eclipse.osgi.service.resolver.extras.*;
import org.osgi.framework.*;
import org.osgi.framework.hooks.resolver.ResolverHook;
import org.osgi.framework.hooks.resolver.ResolverHookFactory;
import org.osgi.framework.resource.*;
import org.osgi.framework.wiring.*;
import org.osgi.service.resolver.Environment;
import org.osgi.service.resolver.ResolutionException;
public class EquinoxResolverHook implements ResolverHookFactory, ResolverHook {
private final State state;
private final Environment environment;
private final Map<Resource, Map<String, List<Wire>>> wiring;
private final Map<Resource, BundleDescription> resourceToDescription = new HashMap<Resource, BundleDescription>();
public EquinoxResolverHook(State state, Environment environment) {
this.state = state;
this.environment = environment;
Map<Resource, List<Wire>> envWiring = environment.getWiring();
this.wiring = new HashMap<Resource, Map<String, List<Wire>>>();
if (envWiring != null) {
for (Map.Entry<Resource, List<Wire>> wires : envWiring.entrySet()) {
Map<String, List<Wire>> mappedWires = new HashMap<String, List<Wire>>();
this.wiring.put(wires.getKey(), mappedWires);
for (Wire wire : wires.getValue()) {
String namespace = wire.getRequirement().getNamespace();
List<Wire> namespaceWires = mappedWires.get(namespace);
if (namespaceWires == null) {
namespaceWires = new ArrayList<Wire>();
mappedWires.put(namespace, namespaceWires);
}
namespaceWires.add(wire);
}
}
}
}
public Map<Resource, List<Wire>> resolve(Collection<? extends Resource> mandatoryResources, Collection<? extends Resource> optionalResources) throws ResolutionException {
if (mandatoryResources == null)
mandatoryResources = Collections.emptyList();
if (optionalResources == null)
optionalResources = Collections.emptyList();
if (mandatoryResources.isEmpty() && optionalResources.isEmpty())
return Collections.emptyMap();
// Populate the Equinox State with the initial set of resolved resources
for (Resource resource : wiring.keySet()) {
state.addBundle(createDescription(resource));
}
// resolve the existing resources according to the wiring
if (!wiring.isEmpty())
state.resolve();
// all descriptions added up to this point must be resolved.
for (BundleDescription description : resourceToDescription.values()) {
if (!description.isResolved())
throw new ResolutionException("Could not resolve the resource: " + description.getUserObject()); //$NON-NLS-1$
}
// Populate the Equinox State with the initial set of mandatory/optional resources
for (Resource resource : mandatoryResources)
state.addBundle(createDescription(resource));
for (Resource resource : optionalResources)
state.addBundle(createDescription(resource));
Map<Resource, List<Wire>> result = new HashMap<Resource, List<Wire>>();
state.resolve();
List<BundleDescription> unresolvedMandatory = new ArrayList<BundleDescription>(mandatoryResources.size());
for (Resource resource : mandatoryResources) {
BundleDescription description = resourceToDescription.get(resource);
if (!description.isResolved())
unresolvedMandatory.add(description);
else
addWires(result, description);
}
if (!unresolvedMandatory.isEmpty()) {
// TODO need to make a better error message here
throw new ResolutionException("Could not resolve mandatory resources: " + unresolvedMandatory); //$NON-NLS-1$
}
for (Resource resource : optionalResources) {
BundleDescription description = resourceToDescription.get(resource);
if (description.isResolved())
addWires(result, description);
}
return result;
}
private void addWires(Map<Resource, List<Wire>> result, BundleDescription resolved) {
Resource resource = (Resource) resolved.getUserObject();
if (result.get(resource) != null || wiring.get(resource) != null)
return; // already processed or previously resolved
BundleWiring bundleWiring = resolved.getWiring();
List<BundleWire> bundleWires = bundleWiring.getRequiredWires(null);
List<Wire> wires = new ArrayList<Wire>(bundleWires.size());
result.put(resource, wires);
for (BundleWire bundleWire : bundleWires) {
final Resource requirer = (Resource) ((BundleDescription) bundleWire.getRequirer()).getUserObject();
final Resource provider = (Resource) ((BundleDescription) bundleWire.getProvider()).getUserObject();
final Requirement requirement = (Requirement) ((SpecificationReference) bundleWire.getRequirement()).getSpecification().getUserObject();
final Capability capability;
BundleDescription capabilityDescription;
if (BundleRevision.HOST_NAMESPACE.equals(requirement.getNamespace()) || BundleRevision.BUNDLE_NAMESPACE.equals(requirement.getNamespace())) {
List<Capability> capabilities = provider.getCapabilities(requirement.getNamespace());
capability = capabilities.get(0);
capabilityDescription = (BundleDescription) bundleWire.getProvider();
} else {
BaseDescription baseDescription = ((DescriptionReference) bundleWire.getCapability()).getDescription();
capabilityDescription = baseDescription.getSupplier();
capability = (Capability) baseDescription.getUserObject();
}
addWires(result, capabilityDescription);
wires.add(new Wire() {
public Resource getRequirer() {
return requirer;
}
public Requirement getRequirement() {
return requirement;
}
public Resource getProvider() {
return provider;
}
public Capability getCapability() {
return capability;
}
public String toString() {
return "[" + requirer + ':' + requirement.getResource() + ':' + requirement + "] -> [" + provider + ':' + capability.getResource() + ':' + capability + ']'; //$NON-NLS-1$ //$NON-NLS-2$
}
});
}
}
Capability getOriginalCapability(Capability capability) {
if (BundleRevision.HOST_NAMESPACE.equals(capability.getNamespace()) || BundleRevision.BUNDLE_NAMESPACE.equals(capability.getNamespace())) {
Resource provider = (Resource) ((BundleDescription) capability.getResource()).getUserObject();
List<Capability> capabilities = provider.getCapabilities(capability.getNamespace());
return capabilities.get(0);
}
return (Capability) ((DescriptionReference) capability).getDescription().getUserObject();
}
private BundleDescription createDescription(Resource resource) {
BundleDescription description = resourceToDescription.get(resource);
if (description != null)
return description;
Collection<Capability> idList = resource.getCapabilities(ResourceConstants.IDENTITY_NAMESPACE);
if (idList.size() != 1)
throw new IllegalArgumentException("Bogus osgi.identity: " + idList); //$NON-NLS-1$
Capability id = idList.iterator().next();
Map<String, Object> idAttrs = new HashMap<String, Object>(id.getAttributes());
String symbolicName = (String) idAttrs.remove(ResourceConstants.IDENTITY_NAMESPACE);
Version version = (Version) idAttrs.remove(ResourceConstants.IDENTITY_VERSION_ATTRIBUTE);
String symbolicNameSpecification = symbolicName + toString(idAttrs, "=", true) + toString(id.getDirectives(), ":=", true); //$NON-NLS-1$ //$NON-NLS-2$
List<ExportPackageDescription> exportPackages = new ArrayList<ExportPackageDescription>();
List<GenericDescription> provideCapabilities = new ArrayList<GenericDescription>();
List<ImportPackageSpecification> importPackages = new ArrayList<ImportPackageSpecification>();
List<GenericSpecification> requireCapabilities = new ArrayList<GenericSpecification>();
List<HostSpecification> fragmentHost = new ArrayList<HostSpecification>(0);
List<BundleSpecification> requireBundles = new ArrayList<BundleSpecification>();
Collection<Capability> capabilities = resource.getCapabilities(null);
Capability osgiIdentity = null;
for (Capability capability : capabilities) {
String namespace = capability.getNamespace();
if (ResourceConstants.IDENTITY_NAMESPACE.equals(namespace)) {
osgiIdentity = capability;
continue;
} else if (namespace == null || BundleRevision.HOST_NAMESPACE.equals(namespace) || BundleRevision.BUNDLE_NAMESPACE.equals(namespace)) {
continue;
} else if (BundleRevision.PACKAGE_NAMESPACE.equals(namespace)) {
exportPackages.addAll(creatExportPackage(capability));
} else {
provideCapabilities.addAll(createProvideCapability(capability));
}
}
Collection<Requirement> requirements = resource.getRequirements(null);
for (Requirement requirement : requirements) {
String namespace = requirement.getNamespace();
if (namespace == null || !environment.isEffective(requirement)) {
continue;
} else if (BundleRevision.BUNDLE_NAMESPACE.equals(namespace)) {
requireBundles.addAll(createRequireBundle(requirement));
} else if (BundleRevision.HOST_NAMESPACE.equals(namespace)) {
fragmentHost.addAll(createFragmentHost(requirement));
} else if (BundleRevision.PACKAGE_NAMESPACE.equals(namespace)) {
importPackages.addAll(createImportPackage(requirement));
} else {
requireCapabilities.addAll(createRequireCapability(requirement));
}
}
BundleDescription result = state.getFactory().createBundleDescription(state.getHighestBundleId() + 1, symbolicNameSpecification, version, null, requireBundles.toArray(new BundleSpecification[requireBundles.size()]), fragmentHost.size() == 0 ? null : fragmentHost.get(0), importPackages.toArray(new ImportPackageSpecification[importPackages.size()]), exportPackages.toArray(new ExportPackageDescription[exportPackages.size()]), null, null, requireCapabilities.toArray(new GenericSpecification[requireCapabilities.size()]), provideCapabilities.toArray(new GenericDescription[provideCapabilities.size()]), null);
resourceToDescription.put(resource, result);
result.setUserObject(resource);
GenericDescription[] genericDescs = result.getGenericCapabilities();
for (GenericDescription genericDesc : genericDescs) {
if (ResourceConstants.IDENTITY_NAMESPACE.equals(genericDesc.getType()))
genericDesc.setUserObject(osgiIdentity);
}
return result;
}
private List<ExportPackageDescription> creatExportPackage(Capability capability) {
Map<String, Object> attributes = new HashMap<String, Object>(capability.getAttributes());
Map<String, String> directives = capability.getDirectives();
String packageName = (String) attributes.remove(BundleRevision.PACKAGE_NAMESPACE);
// remove invalid attributes
attributes.remove(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE);
attributes.remove(Constants.BUNDLE_VERSION_ATTRIBUTE);
String declaration = packageName + toString(attributes, "=", true) + toString(directives, ":=", true); //$NON-NLS-1$//$NON-NLS-2$
List<ExportPackageDescription> result = state.getFactory().createExportPackageDescriptions(declaration);
for (ExportPackageDescription export : result) {
export.setUserObject(capability);
}
return result;
}
private List<GenericDescription> createProvideCapability(Capability capability) {
Map<String, Object> attributes = capability.getAttributes();
Map<String, String> directives = capability.getDirectives();
if ("osgi.ee".equals(capability.getNamespace())) { //$NON-NLS-1$
attributes = new HashMap<String, Object>(attributes);
attributes.remove("x-equinox-ee"); //$NON-NLS-1$
}
if (directives.get(Constants.EFFECTIVE_DIRECTIVE) != null) {
// always remove the effective directive
directives = new HashMap<String, String>(directives);
directives.remove(Constants.EFFECTIVE_DIRECTIVE);
}
String declaration = capability.getNamespace() + toString(attributes, "=", false) + toString(directives, ":=", true); //$NON-NLS-1$//$NON-NLS-2$
List<GenericDescription> result = state.getFactory().createGenericDescriptions(declaration);
for (GenericDescription genericDescription : result) {
genericDescription.setUserObject(capability);
}
return result;
}
private List<BundleSpecification> createRequireBundle(Requirement requirement) {
String declaration = createOSGiRequirement(requirement, BundleRevision.BUNDLE_NAMESPACE, Constants.BUNDLE_VERSION_ATTRIBUTE);
List<BundleSpecification> result = state.getFactory().createBundleSpecifications(declaration);
for (BundleSpecification bundleSpecification : result) {
bundleSpecification.setUserObject(requirement);
}
return result;
}
private List<HostSpecification> createFragmentHost(Requirement requirement) {
String declaration = createOSGiRequirement(requirement, BundleRevision.HOST_NAMESPACE, Constants.BUNDLE_VERSION_ATTRIBUTE);
List<HostSpecification> result = state.getFactory().createHostSpecifications(declaration);
for (HostSpecification hostSpecification : result) {
hostSpecification.setUserObject(requirement);
}
return result;
}
private List<ImportPackageSpecification> createImportPackage(Requirement requirement) {
String declaration = createOSGiRequirement(requirement, BundleRevision.PACKAGE_NAMESPACE, Constants.VERSION_ATTRIBUTE, Constants.BUNDLE_VERSION_ATTRIBUTE);
List<ImportPackageSpecification> result = state.getFactory().createImportPackageSpecifications(declaration);
for (ImportPackageSpecification importPackageSpecification : result) {
importPackageSpecification.setUserObject(requirement);
}
return result;
}
private List<GenericSpecification> createRequireCapability(Requirement requirement) {
Map<String, String> directives = requirement.getDirectives();
if (directives.get(Constants.EFFECTIVE_DIRECTIVE) != null) {
// always remove the effective directive; all requirements are effective at this point
directives = new HashMap<String, String>(directives);
directives.remove(Constants.EFFECTIVE_DIRECTIVE);
}
String declaration = requirement.getNamespace() + toString(requirement.getAttributes(), "=", false) + toString(requirement.getDirectives(), ":=", true); //$NON-NLS-1$ //$NON-NLS-2$
List<GenericSpecification> result = state.getFactory().createGenericSpecifications(declaration);
for (GenericSpecification genericSpecification : result) {
genericSpecification.setUserObject(requirement);
}
return result;
}
private String createOSGiRequirement(Requirement requirement, String namespace, String... versions) {
Map<String, String> directives = new HashMap<String, String>(requirement.getDirectives());
String filter = directives.remove(ResourceConstants.REQUIREMENT_FILTER_DIRECTIVE);
if (filter == null)
throw new IllegalArgumentException("No filter directive found:" + requirement); //$NON-NLS-1$
FilterParser parser = new FilterParser(filter);
FilterComponent component = null;
try {
component = parser.parse();
} catch (InvalidSyntaxException e) {
throw new IllegalArgumentException("Invalid filter directive", e); //$NON-NLS-1$
}
Map<String, String> matchingAttributes = component.getStandardOSGiAttributes(versions);
String name = matchingAttributes.remove(namespace);
if (name == null)
throw new IllegalArgumentException("Invalid requirement: " + requirement); //$NON-NLS-1$
return name + toString(matchingAttributes, "=", true) + toString(directives, ":=", true); //$NON-NLS-1$ //$NON-NLS-2$
}
public ResolverHook begin(Collection<BundleRevision> triggers) {
return this;
}
public void filterResolvable(Collection<BundleRevision> candidates) {
// nothing
}
public void filterSingletonCollisions(BundleCapability singleton, Collection<BundleCapability> collisionCandidates) {
// clear all collisions; environment is expected to handle singletons
collisionCandidates.clear();
return;
}
public void filterMatches(BundleRequirement requirement, Collection<BundleCapability> candidates) {
// this is quick and dirty way to do this; we know the resources are BundleDescriptions
BundleDescription description = (BundleDescription) requirement.getResource();
Resource originalResource = (Resource) description.getUserObject();
Map<String, List<Wire>> resolvedWires = wiring.get(originalResource);
if (resolvedWires != null) {
filterAccordingToWiring(requirement, originalResource, description, resolvedWires, candidates);
} else {
filterAccordingToEnvironment(requirement, candidates);
}
}
private void filterAccordingToEnvironment(BundleRequirement requirement, Collection<BundleCapability> candidates) {
Requirement originalRequirement = (Requirement) ((SpecificationReference) requirement).getSpecification().getUserObject();
Collection<Capability> envCapabilities = environment.findProviders(originalRequirement);
// first find new resources that should be added to the state
boolean added = false;
for (Capability capability : envCapabilities) {
Resource originalResource = capability.getResource();
BundleDescription existing = resourceToDescription.get(originalResource);
if (existing == null) {
BundleDescription toAdd = createDescription(originalResource);
state.addBundle(toAdd);
added = true;
}
}
if (added)
return; // do nothing resolver needs to recall us
// Now filter out the candidates from the resolver according to the environment
for (Iterator<BundleCapability> iCandidates = candidates.iterator(); iCandidates.hasNext();) {
BaseDescription description = ((DescriptionReference) iCandidates.next()).getDescription();
Capability originalCapability;
if (BundleRevision.HOST_NAMESPACE.equals(requirement.getNamespace()) || BundleRevision.BUNDLE_NAMESPACE.equals(requirement.getNamespace())) {
Resource provider = (Resource) description.getUserObject();
List<Capability> hostCapabilities = provider.getCapabilities(requirement.getNamespace());
originalCapability = hostCapabilities.get(0);
} else {
originalCapability = (Capability) description.getUserObject();
}
if (!envCapabilities.contains(originalCapability))
iCandidates.remove();
}
if (candidates.size() > 1) {
if (candidates instanceof Sortable<?>) {
final List<Capability> sorted = new ArrayList<Capability>(envCapabilities);
((Sortable<BundleCapability>) candidates).sort(new Comparator<BundleCapability>() {
public int compare(BundleCapability o1, BundleCapability o2) {
Capability orig1 = getOriginalCapability(o1);
Capability orig2 = getOriginalCapability(o2);
int o1Index = sorted.indexOf(orig1);
int o2Index = sorted.indexOf(orig2);
return o1Index - o2Index;
}
});
}
}
}
private Resource getHostResource(Resource originalResource, Map<String, List<Wire>> resolvedWires) {
if (resolvedWires == null)
return null;
List<Wire> hostWires = resolvedWires.get(BundleRevision.HOST_NAMESPACE);
if (hostWires == null)
return null;
return hostWires.size() > 0 ? hostWires.get(0).getCapability().getResource() : null;
}
@SuppressWarnings("unchecked")
private void filterAccordingToWiring(Requirement requirement, Resource originalResource, BundleDescription description, Map<String, List<Wire>> resolvedWires, Collection<BundleCapability> candidates) {
if (description.getHost() != null && !BundleRevision.HOST_NAMESPACE.equals(requirement.getNamespace())) {
originalResource = getHostResource(originalResource, resolvedWires);
resolvedWires = originalResource == null ? Collections.EMPTY_MAP : wiring.get(originalResource);
}
List<Wire> namespaceWires = resolvedWires.get(requirement.getNamespace());
if (namespaceWires == null) {
candidates.clear();
} else {
for (Iterator<BundleCapability> iCandidates = candidates.iterator(); iCandidates.hasNext();) {
Capability candidate = iCandidates.next();
Resource origProvider = (Resource) ((BaseDescription) candidate.getResource()).getUserObject();
Collection<Capability> origCapabilities = origProvider.getCapabilities(candidate.getNamespace());
boolean found = false;
wireLoop: for (Wire wire : namespaceWires) {
for (Capability capability : origCapabilities) {
if (wire.getCapability().equals(capability)) {
found = true;
break wireLoop;
}
}
}
if (!found)
iCandidates.remove();
}
}
}
public void end() {
// nothing
}
static <V> String toString(Map<String, V> map, String assignment, boolean stringsOnly) {
if (map.isEmpty())
return ""; //$NON-NLS-1$
Set<Entry<String, V>> set = map.entrySet();
StringBuffer sb = new StringBuffer();
for (Entry<String, V> entry : set) {
sb.append("; "); //$NON-NLS-1$
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof List) {
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) value;
if (list.size() == 0)
continue;
Object component = list.get(0);
String className = component.getClass().getName();
String type = className.substring(className.lastIndexOf('.') + 1);
sb.append(key).append(':').append("List<").append(type).append(">").append(assignment).append('"'); //$NON-NLS-1$ //$NON-NLS-2$
for (Object object : list)
sb.append(object).append(',');
sb.setLength(sb.length() - 1);
sb.append('"');
} else {
String type = ""; //$NON-NLS-1$
if (!(value instanceof String) && !stringsOnly) {
String className = value.getClass().getName();
type = ":" + className.substring(className.lastIndexOf('.') + 1); //$NON-NLS-1$
}
sb.append(key).append(type).append(assignment).append('"').append(value).append('"');
}
}
return sb.toString();
}
}