blob: 4c11d7e0c627d14b5a27aee727edf5b034492fde [file] [log] [blame]
/*******************************************************************************
* Copyright (c) Intel Corporation
* Copyright (c) 2017
*
* 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.felix.fileinstall.plugins.resolver.impl;
import java.io.IOException;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.felix.fileinstall.plugins.resolver.ResolveRequest;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.namespace.IdentityNamespace;
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.Wiring;
import org.osgi.service.log.LogService;
import org.osgi.service.repository.ContentNamespace;
import org.osgi.service.repository.Repository;
import org.osgi.service.resolver.HostedCapability;
import org.osgi.service.resolver.ResolveContext;
import aQute.bnd.deployer.repository.FixedIndexedRepo;
import aQute.bnd.service.url.URLConnector;
class PluginResolveContext extends ResolveContext {
private static final String INITIAL_RESOURCE_CAPABILITY_NAMESPACE = "__initial";
private static final String MIME_BUNDLE = "application/vnd.osgi.bundle";
private final BundleContext bundleContext;
// The repositories that will be queries for providers
private final Map<URI, Repository> repositories = new HashMap<>();
// A cache of resource->location (URL), generated during resolve and queried
// after resolve in order to fetch the resource.
private final Map<Resource, String> resourceLocationMap = new IdentityHashMap<>();
// A cache of resources to the repositories which own them; used from
// insertHostedCapability method.
private final Map<Resource, Repository> resourceRepositoryMap = new IdentityHashMap<>();
private final ResourceImpl initialResource;
private final LogService log;
PluginResolveContext(BundleContext bundleContext, ResolveRequest request, LogService log) throws IOException {
this.bundleContext = bundleContext;
this.log = log;
this.initialResource = new ResourceImpl();
for (Requirement requirement : request.getRequirements()) {
this.initialResource.addRequirement(requirement);
}
this.initialResource.addCapability(createIdentityCap(this.initialResource, request.getName()));
this.initialResource.addCapability(createInitialMarkerCapability(this.initialResource));
BasicRegistry registry = new BasicRegistry().put(URLConnector.class, new JarURLConnector());
for (URI indexUri : request.getIndexes()) {
// URI cachedIndexUri = getCacheIndexURI(indexUri);
Map<String, String> repoProps = new HashMap<>();
repoProps.put("locations", indexUri.toString());
FixedIndexedRepo repo = new FixedIndexedRepo();
repo.setRegistry(registry);
repo.setProperties(repoProps);
this.repositories.put(indexUri, repo);
}
}
@Override
public Collection<Resource> getMandatoryResources() {
return Collections.<Resource>singleton(this.initialResource);
}
@Override
public List<Capability> findProviders(Requirement requirement) {
List<Capability> resultCaps = new LinkedList<>();
// Find from installed bundles
Bundle[] bundles = this.bundleContext.getBundles();
for (Bundle bundle : bundles) {
if (bundle.getState() == Bundle.UNINSTALLED) {
continue; // Skip UNINSTALLED bundles
}
BundleRevision revision = bundle.adapt(BundleRevision.class);
List<Capability> bundleCaps = revision.getCapabilities(requirement.getNamespace());
if (bundleCaps != null) {
for (Capability bundleCap : bundleCaps) {
if (match(requirement, bundleCap, this.log)) {
resultCaps.add(bundleCap);
}
}
}
}
// Find from repositories
for (Entry<URI, Repository> repoEntry : this.repositories.entrySet()) {
Repository repository = repoEntry.getValue();
Map<Requirement, Collection<Capability>> providers = repository
.findProviders(Collections.singleton(requirement));
if (providers != null) {
Collection<Capability> repoCaps = providers.get(requirement);
if (repoCaps != null) {
resultCaps.addAll(repoCaps);
for (Capability repoCap : repoCaps) {
// Get the list of physical URIs for this resource.
Resource resource = repoCap.getResource();
// Keep track of which repositories own which resources.
this.resourceRepositoryMap.put(resource, repository);
// Resolve the Resource's URI relative to the Repository
// Index URI and save for later.
URI repoIndexUri = repoEntry.getKey();
URI resolvedUri = resolveResourceLocation(resource, repoIndexUri);
if (resolvedUri != null) {
// Cache the resolved URI into the resource URI map,
// which will be used after resolve.
this.resourceLocationMap.put(resource, resolvedUri.toString());
}
}
}
}
}
return resultCaps;
}
static boolean match(Requirement requirement, Capability capability) {
return match(requirement, capability, null);
}
static boolean match(Requirement requirement, Capability capability, LogService log) {
// Namespace MUST match
if (!requirement.getNamespace().equals(capability.getNamespace())) {
return false;
}
// If capability effective!=resolve then it matches only requirements
// with same effective
String capabilityEffective = capability.getDirectives().get(Namespace.CAPABILITY_EFFECTIVE_DIRECTIVE);
if (capabilityEffective != null) {
String requirementEffective = requirement.getDirectives().get(Namespace.REQUIREMENT_EFFECTIVE_DIRECTIVE);
if (!capabilityEffective.equals(Namespace.EFFECTIVE_RESOLVE)
&& !capabilityEffective.equals(requirementEffective)) {
return false;
}
}
String filterStr = requirement.getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE);
if (filterStr == null) {
return true; // no filter, the requirement always matches
}
try {
Filter filter = FrameworkUtil.createFilter(filterStr);
return filter.matches(capability.getAttributes());
} catch (InvalidSyntaxException e) {
if (log != null) {
Resource resource = requirement.getResource();
String id = resource != null ? getIdentity(resource) : "<unknown>";
log.log(LogService.LOG_ERROR,
String.format("Invalid filter syntax in requirement from resource %s: %s", id, filterStr), e);
}
return false;
}
}
/**
* Get the repository index for the specified resource, where 0 indicates an
* existing OSGi bundle in the framework and -1 indicates not found. This
* method is used by
* {@link #insertHostedCapability(List, HostedCapability)}.
*/
private int findResourceRepositoryIndex(Resource resource) {
if (resource instanceof BundleRevision) {
return 0;
}
int index = 1;
Repository repo = this.resourceRepositoryMap.get(resource);
if (repo == null) {
return -1;
}
for (Repository match : this.repositories.values()) {
if (repo == match) {
return index;
}
index++;
}
// Still not found
return -1;
}
@Override
public int insertHostedCapability(List<Capability> capabilities, HostedCapability hc) {
int hostIndex = findResourceRepositoryIndex(hc.getResource());
if (hostIndex == -1) {
throw new IllegalArgumentException(
"Hosted capability has host resource not found in any known repository.");
}
for (int pos = 0; pos < capabilities.size(); pos++) {
int resourceIndex = findResourceRepositoryIndex(capabilities.get(pos).getResource());
if (resourceIndex > hostIndex) {
capabilities.add(pos, hc);
return pos;
}
}
// The list passed by (some versions of) Felix does not support the
// single-arg add() method... this throws UnsupportedOperationException.
// So we have to call the two-arg add() with an explicit index.
int lastPos = capabilities.size();
capabilities.add(lastPos, hc);
return lastPos;
}
@Override
public boolean isEffective(Requirement requirement) {
return true;
}
@Override
public Map<Resource, Wiring> getWirings() {
Map<Resource, Wiring> wiringMap = new HashMap<>();
Bundle[] bundles = this.bundleContext.getBundles();
for (Bundle bundle : bundles) {
// BundleRevision extends Resource
BundleRevision revision = bundle.adapt(BundleRevision.class);
// BundleWiring extends Wiring
BundleWiring wiring = revision.getWiring();
if (wiring != null) {
wiringMap.put(revision, wiring);
}
}
return wiringMap;
}
boolean isInitialResource(Resource resource) {
List<Capability> markerCaps = resource.getCapabilities(INITIAL_RESOURCE_CAPABILITY_NAMESPACE);
return markerCaps != null && !markerCaps.isEmpty();
}
String getLocation(Resource resource) {
String location;
if (resource instanceof BundleRevision) {
location = ((BundleRevision) resource).getBundle().getLocation();
} else {
location = this.resourceLocationMap.get(resource);
}
return location;
}
private static CapabilityImpl createInitialMarkerCapability(Resource resource) {
return new CapabilityImpl(INITIAL_RESOURCE_CAPABILITY_NAMESPACE, Collections.<String,String>emptyMap(), Collections.<String,Object>emptyMap(),
resource);
}
private static CapabilityImpl createIdentityCap(Resource resource, String identity) {
Map<String, Object> idCapAttrs = new HashMap<>();
idCapAttrs.put(IdentityNamespace.IDENTITY_NAMESPACE, identity);
CapabilityImpl idCap = new CapabilityImpl(IdentityNamespace.IDENTITY_NAMESPACE, Collections.<String,String>emptyMap(),
idCapAttrs, resource);
return idCap;
}
private static URI resolveResourceLocation(Resource resource, URI indexUri) {
List<Capability> contentCaps = resource.getCapabilities(ContentNamespace.CONTENT_NAMESPACE);
if (contentCaps != null) {
for (Capability contentCap : contentCaps) {
// Ensure this content entry has the correct MIME type for a
// bundle
if (MIME_BUNDLE.equals(contentCap.getAttributes().get(ContentNamespace.CAPABILITY_MIME_ATTRIBUTE))) {
// Get the URI attribute in the index, which is either an
// asbolute URI, or a relative URI to be resolved against
// the index URI.
URI rawUri = null;
Object uriObj = contentCap.getAttributes().get(ContentNamespace.CAPABILITY_URL_ATTRIBUTE);
if (uriObj instanceof URI) {
rawUri = (URI) uriObj;
} else if (uriObj instanceof String) {
rawUri = URI.create((String) uriObj);
}
if (rawUri != null) {
URI resolvedUri = URIUtils.resolve(indexUri, rawUri);
return resolvedUri;
}
}
}
}
// No content capability was found in the appropriate form
return null;
}
private static String getIdentity(Resource resource) {
List<Capability> caps = resource.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE);
if (caps == null || caps.isEmpty()) {
return "<unknown>";
}
Object idObj = caps.get(0).getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE);
if (!(idObj instanceof String)) {
return "<unknown>";
}
return (String) idObj;
}
}