| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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.karaf.features.internal.region; |
| |
| import java.net.MalformedURLException; |
| 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.Map; |
| import java.util.Set; |
| |
| import org.apache.karaf.features.FeaturesService; |
| import org.apache.karaf.features.internal.download.Downloader; |
| import org.apache.karaf.features.internal.repository.BaseRepository; |
| import org.apache.karaf.features.internal.resolver.CapabilityImpl; |
| import org.apache.karaf.features.internal.resolver.RequirementImpl; |
| import org.apache.karaf.features.internal.resolver.ResolverUtil; |
| import org.apache.karaf.features.internal.resolver.ResourceImpl; |
| import org.eclipse.equinox.region.Region; |
| import org.eclipse.equinox.region.RegionDigraph; |
| import org.eclipse.equinox.region.RegionFilter; |
| import org.osgi.framework.BundleException; |
| import org.osgi.framework.namespace.PackageNamespace; |
| import org.osgi.framework.wiring.BundleRevision; |
| import org.osgi.namespace.service.ServiceNamespace; |
| import org.osgi.resource.Capability; |
| import org.osgi.resource.Requirement; |
| import org.osgi.resource.Resource; |
| import org.osgi.resource.Wiring; |
| import org.osgi.service.repository.Repository; |
| import org.osgi.service.resolver.HostedCapability; |
| import org.osgi.service.resolver.ResolveContext; |
| |
| import static org.apache.karaf.features.internal.resolver.ResourceUtils.addIdentityRequirement; |
| import static org.apache.karaf.features.internal.resolver.ResourceUtils.getUri; |
| import static org.eclipse.equinox.region.RegionFilter.VISIBLE_BUNDLE_NAMESPACE; |
| import static org.osgi.framework.Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE; |
| import static org.osgi.framework.Constants.BUNDLE_VERSION_ATTRIBUTE; |
| import static org.osgi.framework.Constants.RESOLUTION_DIRECTIVE; |
| import static org.osgi.framework.Constants.RESOLUTION_OPTIONAL; |
| import static org.osgi.framework.namespace.IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE; |
| import static org.osgi.framework.namespace.IdentityNamespace.IDENTITY_NAMESPACE; |
| import static org.osgi.resource.Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE; |
| |
| public class SubsystemResolveContext extends ResolveContext { |
| |
| private final Subsystem root; |
| private final Map<String, Region> regions; |
| private final Set<Resource> mandatory = new HashSet<>(); |
| private final CandidateComparator candidateComparator = new CandidateComparator(mandatory); |
| |
| private final Map<Resource, Subsystem> resToSub = new HashMap<Resource, Subsystem>(); |
| private final Repository repository; |
| private final Repository globalRepository; |
| private final Downloader downloader; |
| private final String serviceRequirements; |
| |
| public SubsystemResolveContext(Subsystem root, RegionDigraph digraph, Repository globalRepository, Downloader downloader, String serviceRequirements) throws BundleException { |
| this.root = root; |
| this.globalRepository = globalRepository != null ? new SubsystemRepository(globalRepository) : null; |
| this.downloader = downloader; |
| this.serviceRequirements = serviceRequirements; |
| |
| prepare(root); |
| repository = new BaseRepository(resToSub.keySet()); |
| |
| regions = new HashMap<>(); |
| for (Region region : digraph) { |
| regions.put(region.getName(), region); |
| } |
| // Add a heuristic to sort capabilities : |
| // if a capability comes from a resource which needs to be installed, |
| // prefer that one over any capabilities from other resources |
| findMandatory(); |
| } |
| |
| public Repository getRepository() { |
| return repository; |
| } |
| |
| public Repository getGlobalRepository() { |
| return globalRepository; |
| } |
| |
| void findMandatory() { |
| mandatory.add(root); |
| int nbMandatory; |
| // Iterate while we find more mandatory resources |
| do { |
| nbMandatory = mandatory.size(); |
| for (Resource res : new ArrayList<>(mandatory)) { |
| // Check mandatory requirements of mandatory resources |
| for (Requirement req : res.getRequirements(null)) { |
| if (isOptional(req) || isDynamic(req)) { |
| continue; |
| } |
| List<Capability> caps = findProviders(req); |
| // If there's a single provider for any kind of mandatory requirement, |
| // this means the resource is also mandatory |
| if (caps.size() == 1) { |
| mandatory.add(caps.get(0).getResource()); |
| } else { |
| // In case there are multiple providers |
| // check if there is a single provider which has |
| // a mandatory identity requirement on a mandatory |
| // resource, in which case we also assume this one |
| // is mandatory |
| Set<Resource> mand = new HashSet<>(); |
| for (Capability cap : caps) { |
| Resource r = cap.getResource(); |
| if (mandatory.contains(r)) { |
| mand.add(r); |
| } else { |
| for (Requirement req2 : r.getRequirements(null)) { |
| if (!IDENTITY_NAMESPACE.equals(req2.getNamespace()) || isOptional(req2) || isDynamic(req2)) { |
| continue; |
| } |
| List<Capability> caps2 = findProviders(req2); |
| if (caps2.size() == 1) { |
| Resource r2 = caps2.get(0).getResource(); |
| if (mandatory.contains(r2)) { |
| mand.add(r); |
| } |
| } |
| } |
| } |
| } |
| if (mand.size() == 1) { |
| mandatory.add(mand.iterator().next()); |
| } else { |
| mand.clear(); |
| } |
| } |
| } |
| } |
| } while (mandatory.size() != nbMandatory); |
| } |
| |
| static boolean isOptional(Requirement req) { |
| String resolution = req.getDirectives().get(REQUIREMENT_RESOLUTION_DIRECTIVE); |
| return RESOLUTION_OPTIONAL.equals(resolution); |
| } |
| |
| static boolean isDynamic(Requirement req) { |
| String resolution = req.getDirectives().get(REQUIREMENT_RESOLUTION_DIRECTIVE); |
| return PackageNamespace.RESOLUTION_DYNAMIC.equals(resolution); |
| } |
| |
| void prepare(Subsystem subsystem) { |
| resToSub.put(subsystem, subsystem); |
| for (Resource res : subsystem.getInstallable()) { |
| resToSub.put(res, subsystem); |
| } |
| for (Subsystem child : subsystem.getChildren()) { |
| prepare(child); |
| } |
| } |
| |
| @Override |
| public Collection<Resource> getMandatoryResources() { |
| return Collections.<Resource>singleton(root); |
| } |
| |
| @Override |
| public List<Capability> findProviders(Requirement requirement) { |
| List<Capability> caps = new ArrayList<>(); |
| Region requirerRegion = getRegion(requirement.getResource()); |
| if (requirerRegion != null) { |
| Map<Requirement, Collection<Capability>> resMap = |
| repository.findProviders(Collections.singleton(requirement)); |
| Collection<Capability> res = resMap != null ? resMap.get(requirement) : null; |
| if (res != null && !res.isEmpty()) { |
| caps.addAll(res); |
| } else if (globalRepository != null) { |
| // Only bring in external resources for non optional requirements |
| if (!RESOLUTION_OPTIONAL.equals(requirement.getDirectives().get(RESOLUTION_DIRECTIVE))) { |
| resMap = globalRepository.findProviders(Collections.singleton(requirement)); |
| res = resMap != null ? resMap.get(requirement) : null; |
| if (res != null && !res.isEmpty()) { |
| caps.addAll(res); |
| } |
| } |
| } |
| |
| // Use the digraph to prune non visible capabilities |
| Visitor visitor = new Visitor(caps); |
| requirerRegion.visitSubgraph(visitor); |
| Collection<Capability> allowed = visitor.getAllowed(); |
| caps.retainAll(allowed); |
| // Handle cases where the same bundle is requested from both |
| // a subsystem and one of its ascendant. In such cases, we |
| // need to remove the one from the child if it can view |
| // the parent one |
| if (caps.size() > 1) { |
| Set<Resource> providers = new HashSet<>(); |
| for (Capability cap : caps) { |
| Resource resource = cap.getResource(); |
| String id = ResolverUtil.getSymbolicName(resource) + "|" + ResolverUtil.getVersion(resource); |
| if (!providers.contains(resource)) { |
| Set<Resource> newRes = new HashSet<>(); |
| String r1 = getRegion(resource).getName(); |
| boolean superceded = false; |
| for (Resource r : providers) { |
| String id2 = ResolverUtil.getSymbolicName(r) + "|" + ResolverUtil.getVersion(r); |
| if (id.equals(id2)) { |
| String r2 = getRegion(r).getName(); |
| if (r1.equals(r2)) { |
| if (r instanceof BundleRevision) { |
| newRes.add(r); |
| superceded = true; |
| } else if (resource instanceof BundleRevision) { |
| newRes.add(resource); |
| } else { |
| throw new InternalError(); |
| } |
| } else if (r1.startsWith(r2)) { |
| newRes.add(r); |
| superceded = true; |
| } else if (r2.startsWith(r1)) { |
| newRes.add(resource); |
| } else { |
| newRes.add(r); |
| } |
| } else { |
| newRes.add(r); |
| } |
| } |
| if (!superceded) { |
| newRes.add(resource); |
| } |
| providers = newRes; |
| } |
| } |
| for (Iterator<Capability> it = caps.iterator(); it.hasNext();) { |
| Capability cap = it.next(); |
| if (!providers.contains(cap.getResource())) { |
| it.remove(); |
| } |
| } |
| } |
| // Sort caps |
| Collections.sort(caps, candidateComparator); |
| } |
| return caps; |
| } |
| |
| private Subsystem getSubsystem(Resource resource) { |
| return resToSub.get(resource); |
| } |
| |
| private Region getRegion(Resource resource) { |
| return regions.get(getSubsystem(resource).getName()); |
| } |
| |
| @Override |
| public int insertHostedCapability(List<Capability> capabilities, HostedCapability hostedCapability) { |
| int idx = Collections.binarySearch(capabilities, hostedCapability, candidateComparator); |
| if (idx < 0) { |
| idx = Math.abs(idx + 1); |
| } |
| capabilities.add(idx, hostedCapability); |
| return idx; |
| } |
| |
| @Override |
| public boolean isEffective(Requirement requirement) { |
| boolean isServiceReq = ServiceNamespace.SERVICE_NAMESPACE.equals(requirement.getNamespace()); |
| return !(isServiceReq && FeaturesService.SERVICE_REQUIREMENTS_DISABLE.equals(serviceRequirements)); |
| } |
| |
| @Override |
| public Map<Resource, Wiring> getWirings() { |
| return Collections.emptyMap(); |
| } |
| |
| class Visitor extends AbstractRegionDigraphVisitor<Capability> { |
| |
| Visitor(Collection<Capability> candidates) { |
| super(candidates); |
| } |
| |
| @Override |
| protected boolean contains(Region region, Capability candidate) { |
| return region.equals(getRegion(candidate.getResource())); |
| } |
| |
| @Override |
| protected boolean isAllowed(Capability candidate, RegionFilter filter) { |
| if (filter.isAllowed(candidate.getNamespace(), candidate.getAttributes())) { |
| return true; |
| } |
| Resource resource = candidate.getResource(); |
| List<Capability> identities = resource.getCapabilities(IDENTITY_NAMESPACE); |
| if (identities != null && !identities.isEmpty()) { |
| Capability identity = identities.iterator().next(); |
| Map<String, Object> attrs = new HashMap<String, Object>(); |
| attrs.put(BUNDLE_SYMBOLICNAME_ATTRIBUTE, identity.getAttributes().get(IDENTITY_NAMESPACE)); |
| attrs.put(BUNDLE_VERSION_ATTRIBUTE, identity.getAttributes().get(CAPABILITY_VERSION_ATTRIBUTE)); |
| return filter.isAllowed(VISIBLE_BUNDLE_NAMESPACE, attrs); |
| } |
| return false; |
| } |
| |
| } |
| |
| class SubsystemRepository implements Repository { |
| |
| private final Repository repository; |
| private final Map<Subsystem, Map<Capability, Capability>> mapping = new HashMap<Subsystem, Map<Capability, Capability>>(); |
| |
| public SubsystemRepository(Repository repository) { |
| this.repository = repository; |
| } |
| |
| @Override |
| public Map<Requirement, Collection<Capability>> findProviders(Collection<? extends Requirement> requirements) { |
| Map<Requirement, Collection<Capability>> base = repository.findProviders(requirements); |
| Map<Requirement, Collection<Capability>> result = new HashMap<Requirement, Collection<Capability>>(); |
| for (Map.Entry<Requirement, Collection<Capability>> entry : base.entrySet()) { |
| List<Capability> caps = new ArrayList<Capability>(); |
| Subsystem ss = getSubsystem(entry.getKey().getResource()); |
| while (!ss.isAcceptDependencies()) { |
| ss = ss.getParent(); |
| } |
| Map<Capability, Capability> map = mapping.get(ss); |
| if (map == null) { |
| map = new HashMap<Capability, Capability>(); |
| mapping.put(ss, map); |
| } |
| for (Capability cap : entry.getValue()) { |
| Capability wrapped = map.get(cap); |
| if (wrapped == null) { |
| wrap(map, ss, cap.getResource()); |
| wrapped = map.get(cap); |
| } |
| caps.add(wrapped); |
| } |
| result.put(entry.getKey(), caps); |
| } |
| return result; |
| } |
| |
| private void wrap(Map<Capability, Capability> map, Subsystem subsystem, Resource resource) { |
| ResourceImpl wrapped = new ResourceImpl(); |
| for (Capability cap : resource.getCapabilities(null)) { |
| CapabilityImpl wCap = new CapabilityImpl(wrapped, cap.getNamespace(), cap.getDirectives(), cap.getAttributes()); |
| map.put(cap, wCap); |
| wrapped.addCapability(wCap); |
| } |
| for (Requirement req : resource.getRequirements(null)) { |
| RequirementImpl wReq = new RequirementImpl(wrapped, req.getNamespace(), req.getDirectives(), req.getAttributes()); |
| wrapped.addRequirement(wReq); |
| } |
| addIdentityRequirement(wrapped, subsystem, false); |
| resToSub.put(wrapped, subsystem); |
| try { |
| downloader.download(getUri(wrapped), null); |
| } catch (MalformedURLException e) { |
| throw new IllegalStateException("Unable to download resource: " + getUri(wrapped)); |
| } |
| } |
| } |
| |
| } |