| /* |
| * 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.felix.framework; |
| |
| 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.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.NoSuchElementException; |
| import java.util.Set; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.LinkedBlockingQueue; |
| import java.util.concurrent.ThreadFactory; |
| import java.util.concurrent.ThreadPoolExecutor; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| import org.apache.felix.framework.capabilityset.CapabilitySet; |
| import org.apache.felix.framework.capabilityset.SimpleFilter; |
| import org.apache.felix.framework.resolver.CandidateComparator; |
| import org.apache.felix.framework.resolver.ResolveException; |
| import org.apache.felix.framework.util.FelixConstants; |
| import org.apache.felix.framework.util.ShrinkableCollection; |
| import org.apache.felix.framework.util.Util; |
| import org.apache.felix.framework.util.manifestparser.NativeLibrary; |
| import org.apache.felix.framework.wiring.BundleRequirementImpl; |
| import org.apache.felix.framework.wiring.BundleWireImpl; |
| import org.apache.felix.resolver.ResolverImpl; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleEvent; |
| import org.osgi.framework.BundleException; |
| import org.osgi.framework.BundlePermission; |
| import org.osgi.framework.CapabilityPermission; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.PackagePermission; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.framework.hooks.resolver.ResolverHook; |
| import org.osgi.framework.hooks.resolver.ResolverHookFactory; |
| import org.osgi.framework.wiring.BundleCapability; |
| import org.osgi.framework.wiring.BundleRequirement; |
| import org.osgi.framework.wiring.BundleRevision; |
| import org.osgi.framework.wiring.BundleWire; |
| import org.osgi.framework.wiring.BundleWiring; |
| import org.osgi.resource.Capability; |
| 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.ResolutionException; |
| import org.osgi.service.resolver.Resolver; |
| |
| class StatefulResolver |
| { |
| private final Logger m_logger; |
| private final Felix m_felix; |
| private final ServiceRegistry m_registry; |
| private final Executor m_executor; |
| private final ResolverImpl m_resolver; |
| private boolean m_isResolving = false; |
| |
| // Set of all revisions. |
| private final Set<BundleRevision> m_revisions; |
| // Set of all fragments. |
| private final Set<BundleRevision> m_fragments; |
| // Capability sets. |
| private final Map<String, CapabilitySet> m_capSets; |
| // Maps singleton symbolic names to list of bundle revisions sorted by version. |
| private final Map<String, List<BundleRevision>> m_singletons; |
| // Selected singleton bundle revisions. |
| private final Set<BundleRevision> m_selectedSingletons; |
| |
| StatefulResolver(Felix felix, ServiceRegistry registry) |
| { |
| m_felix = felix; |
| m_registry = registry; |
| m_logger = m_felix.getLogger(); |
| m_executor = getExecutor(); |
| m_resolver = new ResolverImpl(m_logger, m_executor); |
| |
| m_revisions = new HashSet<BundleRevision>(); |
| m_fragments = new HashSet<BundleRevision>(); |
| m_capSets = new HashMap<String, CapabilitySet>(); |
| m_singletons = new HashMap<String, List<BundleRevision>>(); |
| m_selectedSingletons = new HashSet<BundleRevision>(); |
| |
| List<String> indices = new ArrayList<String>(); |
| indices.add(BundleRevision.BUNDLE_NAMESPACE); |
| m_capSets.put(BundleRevision.BUNDLE_NAMESPACE, new CapabilitySet(indices, true)); |
| |
| indices = new ArrayList<String>(); |
| indices.add(BundleRevision.PACKAGE_NAMESPACE); |
| m_capSets.put(BundleRevision.PACKAGE_NAMESPACE, new CapabilitySet(indices, true)); |
| |
| indices = new ArrayList<String>(); |
| indices.add(BundleRevision.HOST_NAMESPACE); |
| m_capSets.put(BundleRevision.HOST_NAMESPACE, new CapabilitySet(indices, true)); |
| } |
| |
| private Executor getExecutor() |
| { |
| String str = m_felix.getProperty(FelixConstants.RESOLVER_PARALLELISM); |
| int parallelism = Runtime.getRuntime().availableProcessors(); |
| if (str != null) |
| { |
| try |
| { |
| parallelism = Integer.parseInt(str); |
| } |
| catch (NumberFormatException e) |
| { |
| // Ignore |
| } |
| } |
| if (parallelism <= 1) |
| { |
| return new Executor() |
| { |
| public void execute(Runnable command) |
| { |
| command.run(); |
| } |
| }; |
| } |
| else |
| { |
| ThreadPoolExecutor executor = new ThreadPoolExecutor( |
| parallelism, parallelism, |
| 60, TimeUnit.SECONDS, |
| new LinkedBlockingQueue<Runnable>(), |
| new ThreadFactory() |
| { |
| final AtomicInteger counter = new AtomicInteger(); |
| @Override |
| public Thread newThread(Runnable r) |
| { |
| Thread thread = new Thread(r, "FelixResolver-" + counter.incrementAndGet()); |
| thread.setDaemon(true); |
| return thread; |
| } |
| }); |
| executor.allowCoreThreadTimeOut(true); |
| return executor; |
| } |
| } |
| |
| void start() |
| { |
| m_registry.registerService(m_felix, |
| new String[] { Resolver.class.getName() }, |
| new ResolverImpl(m_logger, 1), |
| null); |
| } |
| |
| synchronized void addRevision(BundleRevision br) |
| { |
| // Always attempt to remove the revision, since |
| // this method can be used for re-indexing a revision |
| // after it has been resolved. |
| removeRevision(br); |
| |
| m_revisions.add(br); |
| |
| // Add singletons to the singleton map. |
| boolean isSingleton = Util.isSingleton(br); |
| if (isSingleton) |
| { |
| // Index the new singleton. |
| addToSingletonMap(m_singletons, br); |
| } |
| |
| // We always need to index non-singleton bundle capabilities, but |
| // singleton bundles only need to be index if they are resolved. |
| // Unresolved singleton capabilities are only indexed before a |
| // resolve operation when singleton selection is performed. |
| if (!isSingleton || (br.getWiring() != null)) |
| { |
| if (Util.isFragment(br)) |
| { |
| m_fragments.add(br); |
| } |
| indexCapabilities(br); |
| } |
| } |
| |
| synchronized void removeRevision(BundleRevision br) |
| { |
| if (m_revisions.remove(br)) |
| { |
| m_fragments.remove(br); |
| deindexCapabilities(br); |
| |
| // If this module is a singleton, then remove it from the |
| // singleton map. |
| List<BundleRevision> revisions = m_singletons.get(br.getSymbolicName()); |
| if (revisions != null) |
| { |
| revisions.remove(br); |
| if (revisions.isEmpty()) |
| { |
| m_singletons.remove(br.getSymbolicName()); |
| } |
| } |
| } |
| } |
| |
| boolean isEffective(Requirement req) |
| { |
| String effective = req.getDirectives().get(Constants.EFFECTIVE_DIRECTIVE); |
| return ((effective == null) || effective.equals(Constants.EFFECTIVE_RESOLVE)); |
| } |
| |
| synchronized List<BundleCapability> findProviders( |
| BundleRequirement req, boolean obeyMandatory) |
| { |
| ResolverHookRecord record = new ResolverHookRecord( |
| Collections.<ServiceReference<ResolverHookFactory>, ResolverHook>emptyMap(), null); |
| return findProvidersInternal(record, req, obeyMandatory, true); |
| } |
| |
| synchronized List<BundleCapability> findProvidersInternal( |
| final ResolverHookRecord record, |
| final Requirement req, |
| final boolean obeyMandatory, |
| final boolean invokeHooksAndSecurity) |
| { |
| List<BundleCapability> result = new ArrayList<BundleCapability>(); |
| |
| CapabilitySet capSet = m_capSets.get(req.getNamespace()); |
| if (capSet != null) |
| { |
| // Get the requirement's filter; if this is our own impl we |
| // have a shortcut to get the already parsed filter, otherwise |
| // we must parse it from the directive. |
| SimpleFilter sf; |
| if (req instanceof BundleRequirementImpl) |
| { |
| sf = ((BundleRequirementImpl) req).getFilter(); |
| } |
| else |
| { |
| String filter = req.getDirectives().get(Constants.FILTER_DIRECTIVE); |
| if (filter == null) |
| { |
| sf = new SimpleFilter(null, null, SimpleFilter.MATCH_ALL); |
| } |
| else |
| { |
| sf = SimpleFilter.parse(filter); |
| } |
| } |
| |
| // Find the matching candidates. |
| Set<Capability> matches = capSet.match(sf, obeyMandatory); |
| // Filter matching candidates. |
| for (Capability cap : matches) |
| { |
| if (!(cap instanceof BundleCapability)) |
| continue; |
| |
| BundleCapability bcap = (BundleCapability) cap; |
| |
| // Filter according to security. |
| if (invokeHooksAndSecurity && filteredBySecurity((BundleRequirement)req, bcap)) |
| { |
| continue; |
| } |
| // Filter already resolved hosts, since we don't support |
| // dynamic attachment of fragments. |
| if (req.getNamespace().equals(BundleRevision.HOST_NAMESPACE) |
| && (bcap.getRevision().getWiring() != null)) |
| { |
| continue; |
| } |
| |
| result.add(bcap); |
| } |
| } |
| |
| if ( invokeHooksAndSecurity ) |
| { |
| // If we have resolver hooks, then we may need to filter our results |
| // based on a whitelist and/or fine-grained candidate filtering. |
| if (!result.isEmpty() && !record.getResolverHookRefs().isEmpty()) |
| { |
| // It we have a whitelist, then first filter out candidates |
| // from disallowed revisions. |
| if (record.getBundleRevisionWhitelist() != null) |
| { |
| for (Iterator<BundleCapability> it = result.iterator(); it.hasNext(); ) |
| { |
| if (!record.getBundleRevisionWhitelist().contains(it.next().getRevision())) |
| { |
| it.remove(); |
| } |
| } |
| } |
| |
| // Now give the hooks a chance to do fine-grained filtering. |
| ShrinkableCollection<BundleCapability> shrinkable = |
| new ShrinkableCollection<BundleCapability>(result); |
| for (ResolverHook hook : record.getResolverHooks()) |
| { |
| try |
| { |
| Felix.m_secureAction |
| .invokeResolverHookMatches(hook, (BundleRequirement)req, shrinkable); |
| } |
| catch (Throwable th) |
| { |
| m_logger.log(Logger.LOG_WARNING, "Resolver hook exception.", th); |
| } |
| } |
| } |
| } |
| |
| Collections.sort(result, new CandidateComparator()); |
| |
| return result; |
| } |
| |
| private boolean filteredBySecurity(BundleRequirement req, BundleCapability cap) |
| { |
| if (System.getSecurityManager() != null) |
| { |
| BundleRevisionImpl reqRevision = (BundleRevisionImpl) req.getRevision(); |
| |
| if (req.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE)) |
| { |
| if (!((BundleProtectionDomain) ((BundleRevisionImpl) cap.getRevision()).getProtectionDomain()).impliesDirect( |
| new PackagePermission((String) cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE), |
| PackagePermission.EXPORTONLY)) || |
| !((reqRevision == null) || |
| ((BundleProtectionDomain) reqRevision.getProtectionDomain()).impliesDirect( |
| new PackagePermission((String) cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE), |
| cap.getRevision().getBundle(),PackagePermission.IMPORT)) |
| )) |
| { |
| if (reqRevision != cap.getRevision()) |
| { |
| return true; |
| } |
| } |
| } |
| else if (req.getNamespace().equals(BundleRevision.BUNDLE_NAMESPACE)) |
| { if (!((BundleProtectionDomain) ((BundleRevisionImpl) cap.getRevision()).getProtectionDomain()).impliesDirect( |
| new BundlePermission(cap.getRevision().getSymbolicName(), BundlePermission.PROVIDE)) || |
| !((reqRevision == null) || |
| ((BundleProtectionDomain) reqRevision.getProtectionDomain()).impliesDirect( |
| new BundlePermission(cap.getRevision().getSymbolicName(), BundlePermission.REQUIRE)) |
| )) |
| { |
| return true; |
| } |
| } |
| else if (req.getNamespace().equals(BundleRevision.HOST_NAMESPACE)) |
| { |
| if (!((BundleProtectionDomain) reqRevision.getProtectionDomain()) |
| .impliesDirect(new BundlePermission( |
| cap.getRevision().getSymbolicName(), |
| BundlePermission.FRAGMENT)) |
| || !((BundleProtectionDomain) ((BundleRevisionImpl) cap.getRevision()).getProtectionDomain()) |
| .impliesDirect(new BundlePermission( |
| cap.getRevision().getSymbolicName(), |
| BundlePermission.HOST))) |
| { |
| return true; |
| } |
| } |
| else if (!req.getNamespace().equals("osgi.ee")) |
| { |
| if (!((BundleProtectionDomain) ((BundleRevisionImpl) cap.getRevision()).getProtectionDomain()).impliesDirect( |
| new CapabilityPermission(req.getNamespace(), CapabilityPermission.PROVIDE)) |
| || |
| !((reqRevision == null) || ((BundleProtectionDomain) reqRevision.getProtectionDomain()).impliesDirect( |
| new CapabilityPermission(req.getNamespace(), cap.getAttributes(), cap.getRevision().getBundle(), CapabilityPermission.REQUIRE)))) |
| { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| void resolve( |
| Set<BundleRevision> mandatory, |
| Set<BundleRevision> optional) |
| throws ResolutionException, BundleException |
| { |
| // Acquire global lock. |
| boolean locked = m_felix.acquireGlobalLock(); |
| if (!locked) |
| { |
| throw new ResolveException( |
| "Unable to acquire global lock for resolve.", null, null); |
| } |
| |
| // Make sure we are not already resolving, which can be |
| // the case if a resolver hook does something bad. |
| if (m_isResolving) |
| { |
| m_felix.releaseGlobalLock(); |
| throw new IllegalStateException("Nested resolve operations not allowed."); |
| } |
| m_isResolving = true; |
| |
| Map<Resource, List<Wire>> wireMap = null; |
| try |
| { |
| // Make our own copy of revisions. |
| mandatory = (mandatory.isEmpty()) |
| ? mandatory : new HashSet<BundleRevision>(mandatory); |
| optional = (optional.isEmpty()) |
| ? optional : new HashSet<BundleRevision>(optional); |
| |
| // Prepare resolver hooks, if any. |
| ResolverHookRecord record = prepareResolverHooks(mandatory, optional); |
| |
| // Select any singletons in the resolver state. |
| selectSingletons(record); |
| |
| // Extensions are resolved differently. |
| for (Iterator<BundleRevision> it = mandatory.iterator(); it.hasNext(); ) |
| { |
| BundleRevision br = it.next(); |
| BundleImpl bundle = (BundleImpl) br.getBundle(); |
| if (bundle.isExtension()) |
| { |
| it.remove(); |
| } |
| else if (Util.isSingleton(br) && !isSelectedSingleton(br)) |
| { |
| throw new ResolveException("Singleton conflict.", br, null); |
| } |
| } |
| for (Iterator<BundleRevision> it = optional.iterator(); it.hasNext(); ) |
| { |
| BundleRevision br = it.next(); |
| BundleImpl bundle = (BundleImpl) br.getBundle(); |
| if (bundle.isExtension()) |
| { |
| it.remove(); |
| } |
| else if (Util.isSingleton(br) && !isSelectedSingleton(br)) |
| { |
| it.remove(); |
| } |
| } |
| |
| // Catch any resolve exception to rethrow later because |
| // we may need to call end() on resolver hooks. |
| ResolutionException rethrow = null; |
| try |
| { |
| // Resolve the revision. |
| wireMap = m_resolver.resolve( |
| new ResolveContextImpl( |
| this, |
| getWirings(), |
| record, |
| mandatory, |
| optional, |
| getFragments())); |
| } |
| catch (ResolutionException ex) |
| { |
| rethrow = ex; |
| } |
| |
| // Release resolver hooks, if any. |
| releaseResolverHooks(record); |
| |
| // If the resolve failed, rethrow the exception. |
| if (rethrow != null) |
| { |
| throw rethrow; |
| } |
| |
| // Otherwise, mark all revisions as resolved. |
| markResolvedRevisions(wireMap); |
| } |
| finally |
| { |
| // Clear resolving flag. |
| m_isResolving = false; |
| // Always release the global lock. |
| m_felix.releaseGlobalLock(); |
| } |
| |
| fireResolvedEvents(wireMap); |
| } |
| |
| BundleRevision resolve(BundleRevision revision, String pkgName) |
| throws ResolutionException, BundleException |
| { |
| BundleRevision provider = null; |
| |
| // We cannot dynamically import if the revision is not already resolved |
| // or if it is not allowed, so check that first. Note: We check if the |
| // dynamic import is allowed without holding any locks, but this is |
| // okay since the resolver will double check later after we have |
| // acquired the global lock below. |
| if ((revision.getWiring() != null) && isAllowedDynamicImport(revision, pkgName)) |
| { |
| // Acquire global lock. |
| boolean locked = m_felix.acquireGlobalLock(); |
| if (!locked) |
| { |
| throw new ResolveException( |
| "Unable to acquire global lock for resolve.", revision, null); |
| } |
| |
| // Make sure we are not already resolving, which can be |
| // the case if a resolver hook does something bad. |
| if (m_isResolving) |
| { |
| m_felix.releaseGlobalLock(); |
| throw new IllegalStateException("Nested resolve operations not allowed."); |
| } |
| m_isResolving = true; |
| |
| Map<Resource, List<Wire>> wireMap = null; |
| try |
| { |
| // Double check to make sure that someone hasn't beaten us to |
| // dynamically importing the package, which can happen if two |
| // threads are racing to do so. If we have an existing wire, |
| // then just return it instead. |
| provider = ((BundleWiringImpl) revision.getWiring()) |
| .getImportedPackageSource(pkgName); |
| if (provider == null) |
| { |
| // Prepare resolver hooks, if any. |
| ResolverHookRecord record = |
| prepareResolverHooks( |
| Collections.singleton(revision), Collections.EMPTY_SET); |
| |
| // Select any singletons in the resolver state. |
| selectSingletons(record); |
| |
| // Catch any resolve exception to rethrow later because |
| // we may need to call end() on resolver hooks. |
| ResolutionException rethrow = null; |
| try |
| { |
| List<BundleRequirement> dynamics = |
| Util.getDynamicRequirements(revision.getWiring().getRequirements(null)); |
| |
| // Loop through the importer's dynamic requirements to determine if |
| // there is a matching one for the package from which we want to |
| // load a class. |
| Map<String, Object> attrs = Collections.singletonMap( |
| BundleRevision.PACKAGE_NAMESPACE, (Object) pkgName); |
| BundleRequirementImpl req = new BundleRequirementImpl( |
| revision, |
| BundleRevision.PACKAGE_NAMESPACE, |
| Collections.EMPTY_MAP, |
| attrs); |
| final List<BundleCapability> candidates = findProvidersInternal(record, req, false, true); |
| |
| // Try to find a dynamic requirement that matches the capabilities. |
| final BundleRequirementImpl dynReq = findDynamicRequirement(dynamics, candidates); |
| |
| // If we found a matching dynamic requirement, then filter out |
| // any candidates that do not match it. |
| if (dynReq != null) |
| { |
| for (Iterator<BundleCapability> itCand = candidates.iterator(); |
| itCand.hasNext(); ) |
| { |
| Capability cap = itCand.next(); |
| if (!CapabilitySet.matches( |
| cap, dynReq.getFilter())) |
| { |
| itCand.remove(); |
| } |
| } |
| } |
| else |
| { |
| candidates.clear(); |
| } |
| |
| Map<Resource, Wiring> wirings = getWirings(); |
| |
| wireMap = dynReq != null && wirings.containsKey(revision) ? m_resolver.resolveDynamic( |
| new ResolveContextImpl( |
| this, |
| wirings, |
| record, |
| Collections.<BundleRevision>emptyList(), |
| Collections.<BundleRevision>emptyList(), |
| getFragments()) |
| { |
| @Override |
| public List<Capability> findProviders(Requirement br) |
| { |
| return (List) (br == dynReq ? candidates : super.findProviders(br)); |
| } |
| }, |
| revision.getWiring(), dynReq) : Collections.<Resource, List<Wire>>emptyMap(); |
| } |
| catch (ResolutionException ex) |
| { |
| rethrow = ex; |
| } |
| |
| // Release resolver hooks, if any. |
| releaseResolverHooks(record); |
| |
| // If the resolve failed, rethrow the exception. |
| if (rethrow != null) |
| { |
| throw rethrow; |
| } |
| |
| if ((wireMap != null) && wireMap.containsKey(revision)) |
| { |
| List<Wire> dynamicWires = wireMap.remove(revision); |
| Wire dynamicWire = dynamicWires.get(0); |
| |
| // Mark all revisions as resolved. |
| markResolvedRevisions(wireMap); |
| |
| // Dynamically add new wire to importing revision. |
| if (dynamicWire != null) |
| { |
| // TODO is a rw already a BundleWire? |
| // TODO can we optimize this? |
| if (dynamicWire.getRequirer() instanceof BundleRevision && |
| dynamicWire.getRequirement() instanceof BundleRequirement && |
| dynamicWire.getProvider() instanceof BundleRevision && |
| dynamicWire.getCapability() instanceof BundleCapability) |
| { |
| BundleRevision dwRequirer = (BundleRevision) dynamicWire.getRequirer(); |
| BundleRequirement dwRequirement = (BundleRequirement) dynamicWire.getRequirement(); |
| BundleRevision dwProvider = (BundleRevision) dynamicWire.getProvider(); |
| BundleCapability dwCapability = (BundleCapability) dynamicWire.getCapability(); |
| |
| BundleWire bw = new BundleWireImpl( |
| dwRequirer, |
| dwRequirement, |
| dwProvider, |
| dwCapability); |
| |
| m_felix.getDependencies().addDependent(bw); |
| |
| ((BundleWiringImpl) revision.getWiring()).addDynamicWire(bw); |
| |
| m_felix.getLogger().log( |
| Logger.LOG_DEBUG, |
| "DYNAMIC WIRE: " + dynamicWire); |
| |
| provider = ((BundleWiringImpl) revision.getWiring()) |
| .getImportedPackageSource(pkgName); |
| } |
| } |
| } |
| } |
| } |
| finally |
| { |
| // Clear resolving flag. |
| m_isResolving = false; |
| // Always release the global lock. |
| m_felix.releaseGlobalLock(); |
| } |
| |
| fireResolvedEvents(wireMap); |
| } |
| |
| return provider; |
| } |
| |
| private BundleRequirementImpl findDynamicRequirement(List<BundleRequirement> dynamics, List<BundleCapability> candidates) |
| { |
| for (int dynIdx = 0; (candidates.size() > 0) && (dynIdx < dynamics.size()); dynIdx++) |
| { |
| for (Iterator<BundleCapability> itCand = candidates.iterator(); itCand.hasNext(); ) |
| { |
| Capability cap = itCand.next(); |
| if (CapabilitySet.matches( |
| cap, |
| ((BundleRequirementImpl) dynamics.get(dynIdx)).getFilter())) |
| { |
| return (BundleRequirementImpl) dynamics.get(dynIdx); |
| } |
| } |
| } |
| return null; |
| } |
| |
| private ResolverHookRecord prepareResolverHooks( |
| Set<BundleRevision> mandatory, Set<BundleRevision> optional) |
| throws BundleException, ResolutionException |
| { |
| // This map maps the hook factory service to the actual hook objects. It |
| // needs to be a map that preserves insertion order to ensure that we call |
| // hooks in the correct order. |
| // The hooks are added in the order that m_felix.getHooks() returns them which |
| // is also the order in which they should be called. |
| Map<ServiceReference<ResolverHookFactory>, ResolverHook> hookMap = |
| new LinkedHashMap<ServiceReference<ResolverHookFactory>, ResolverHook>(); |
| |
| // Get resolver hook factories. |
| Set<ServiceReference<ResolverHookFactory>> hookRefs = |
| m_felix.getHookRegistry().getHooks(ResolverHookFactory.class); |
| Collection<BundleRevision> whitelist; |
| |
| if (!hookRefs.isEmpty()) |
| { |
| // Create triggers list. |
| Set<BundleRevision> triggers; |
| if (!mandatory.isEmpty() && !optional.isEmpty()) |
| { |
| triggers = new HashSet<BundleRevision>(mandatory); |
| triggers.addAll(optional); |
| } |
| else |
| { |
| triggers = (mandatory.isEmpty()) |
| ? optional : mandatory; |
| } |
| triggers = Collections.unmodifiableSet(triggers); |
| |
| BundleException rethrow = null; |
| |
| // Create resolver hook objects by calling begin() on factory. |
| for (ServiceReference<ResolverHookFactory> ref : hookRefs) |
| { |
| try |
| { |
| ResolverHookFactory rhf = m_felix.getService(m_felix, ref, false); |
| if (rhf != null) |
| { |
| ResolverHook hook = |
| Felix.m_secureAction |
| .invokeResolverHookFactory(rhf, triggers); |
| if (hook != null) |
| { |
| hookMap.put(ref, hook); |
| } |
| } |
| } |
| catch (Throwable ex) |
| { |
| rethrow = new BundleException( |
| "Resolver hook exception: " + ex.getMessage(), |
| BundleException.REJECTED_BY_HOOK, |
| ex); |
| // Resolver hook spec: if there is an exception during the resolve operation; abort. |
| // So we break here to make sure that no further resolver hooks are created. |
| break; |
| } |
| } |
| |
| if (rethrow != null) |
| { |
| for (ResolverHook hook : hookMap.values()) |
| { |
| try |
| { |
| Felix.m_secureAction.invokeResolverHookEnd(hook); |
| } |
| catch (Exception ex) |
| { |
| rethrow = new BundleException( |
| "Resolver hook exception: " + ex.getMessage(), |
| BundleException.REJECTED_BY_HOOK, |
| ex); |
| } |
| } |
| |
| throw rethrow; |
| } |
| |
| // Ask hooks to indicate which revisions should not be resolved. |
| whitelist = new ShrinkableCollection<BundleRevision>(getUnresolvedRevisions()); |
| int originalSize = whitelist.size(); |
| for (ResolverHook hook : hookMap.values()) |
| { |
| try |
| { |
| Felix.m_secureAction |
| .invokeResolverHookResolvable(hook, whitelist); |
| } |
| catch (Throwable ex) |
| { |
| rethrow = new BundleException( |
| "Resolver hook exception: " + ex.getMessage(), |
| BundleException.REJECTED_BY_HOOK, |
| ex); |
| // Resolver hook spec: if there is an exception during the resolve operation; abort. |
| // So we break here to make sure that no further resolver operations are executed. |
| break; |
| } |
| } |
| |
| if (rethrow != null) |
| { |
| for (ResolverHook hook : hookMap.values()) |
| { |
| try |
| { |
| Felix.m_secureAction.invokeResolverHookEnd(hook); |
| } |
| catch (Exception ex) |
| { |
| rethrow = new BundleException( |
| "Resolver hook exception: " + ex.getMessage(), |
| BundleException.REJECTED_BY_HOOK, |
| ex); |
| } |
| } |
| |
| throw rethrow; |
| } |
| |
| // If nothing was removed, then just null the whitelist |
| // as an optimization. |
| if (whitelist.size() == originalSize) |
| { |
| whitelist = null; |
| } |
| |
| // Check to make sure the target revisions are allowed to resolve. |
| if (whitelist != null) |
| { |
| // We only need to check this for the non-dynamic import |
| // case. The dynamic import case will only have one resolved |
| // trigger revision in the mandatory set, so ignore that case. |
| if (mandatory.isEmpty() |
| || !optional.isEmpty() |
| || (mandatory.iterator().next().getWiring() == null)) |
| { |
| mandatory.retainAll(whitelist); |
| optional.retainAll(whitelist); |
| if (mandatory.isEmpty() && optional.isEmpty()) |
| { |
| throw new ResolveException( |
| "Resolver hook prevented resolution.", null, null); |
| } |
| } |
| } |
| } |
| else |
| { |
| whitelist = null; |
| } |
| |
| return new ResolverHookRecord(hookMap, whitelist); |
| } |
| |
| private void releaseResolverHooks(ResolverHookRecord record) |
| throws BundleException |
| { |
| // If we have resolver hooks, we must call end() on them. |
| if (!record.getResolverHookRefs().isEmpty()) |
| { |
| // Verify that all resolver hook service references are still valid |
| // Call end() on resolver hooks. |
| for (ResolverHook hook : record.getResolverHooks()) |
| { |
| try |
| { |
| Felix.m_secureAction.invokeResolverHookEnd(hook); |
| } |
| catch (Throwable th) |
| { |
| m_logger.log( |
| Logger.LOG_WARNING, "Resolver hook exception.", th); |
| } |
| } |
| // Verify that all hook service references are still valid |
| // and unget all resolver hook factories. |
| boolean invalid = false; |
| for (ServiceReference<ResolverHookFactory> ref : record.getResolverHookRefs()) |
| { |
| if (ref.getBundle() == null) |
| { |
| invalid = true; |
| } |
| m_felix.ungetService(m_felix, ref, null); |
| } |
| if (invalid) |
| { |
| throw new BundleException( |
| "Resolver hook service unregistered during resolve.", |
| BundleException.REJECTED_BY_HOOK); |
| } |
| } |
| } |
| |
| // This method duplicates a lot of logic from: |
| // ResolverImpl.getDynamicImportCandidates() |
| // This is only a rough check since it doesn't include resolver hooks. |
| boolean isAllowedDynamicImport(BundleRevision revision, String pkgName) |
| { |
| // Unresolved revisions cannot dynamically import, nor can the default |
| // package be dynamically imported. |
| if ((revision.getWiring() == null) || pkgName.length() == 0) |
| { |
| return false; |
| } |
| |
| // If the revision doesn't have dynamic imports, then just return |
| // immediately. |
| List<BundleRequirement> dynamics = |
| Util.getDynamicRequirements(revision.getWiring().getRequirements(null)); |
| if ((dynamics == null) || dynamics.isEmpty()) |
| { |
| return false; |
| } |
| |
| // If the revision exports this package, then we cannot |
| // attempt to dynamically import it. |
| for (BundleCapability cap : revision.getWiring().getCapabilities(null)) |
| { |
| if (cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE) |
| && cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE).equals(pkgName)) |
| { |
| return false; |
| } |
| } |
| |
| // If this revision already imports or requires this package, then |
| // we cannot dynamically import it. |
| if (((BundleWiringImpl) revision.getWiring()).hasPackageSource(pkgName)) |
| { |
| return false; |
| } |
| |
| // Loop through the importer's dynamic requirements to determine if |
| // there is a matching one for the package from which we want to |
| // load a class. |
| Map<String, Object> attrs = Collections.singletonMap( |
| BundleRevision.PACKAGE_NAMESPACE, (Object) pkgName); |
| BundleRequirementImpl req = new BundleRequirementImpl( |
| revision, |
| BundleRevision.PACKAGE_NAMESPACE, |
| Collections.EMPTY_MAP, |
| attrs); |
| List<BundleCapability> candidates = findProviders(req, false); |
| |
| // Try to find a dynamic requirement that matches the capabilities. |
| BundleRequirementImpl dynReq = null; |
| for (int dynIdx = 0; |
| (candidates.size() > 0) && (dynReq == null) && (dynIdx < dynamics.size()); |
| dynIdx++) |
| { |
| for (Iterator<BundleCapability> itCand = candidates.iterator(); |
| (dynReq == null) && itCand.hasNext(); ) |
| { |
| Capability cap = itCand.next(); |
| if (CapabilitySet.matches( |
| cap, |
| ((BundleRequirementImpl) dynamics.get(dynIdx)).getFilter())) |
| { |
| dynReq = (BundleRequirementImpl) dynamics.get(dynIdx); |
| } |
| } |
| } |
| |
| // If we found a matching dynamic requirement, then filter out |
| // any candidates that do not match it. |
| if (dynReq != null) |
| { |
| for (Iterator<BundleCapability> itCand = candidates.iterator(); |
| itCand.hasNext(); ) |
| { |
| Capability cap = itCand.next(); |
| if (!CapabilitySet.matches( |
| cap, dynReq.getFilter())) |
| { |
| itCand.remove(); |
| } |
| } |
| } |
| else |
| { |
| candidates.clear(); |
| } |
| |
| return !candidates.isEmpty(); |
| } |
| |
| private void markResolvedRevisions(Map<Resource, List<Wire>> wireMap) |
| throws ResolveException |
| { |
| boolean debugLog = m_felix.getLogger().getLogLevel() >= Logger.LOG_DEBUG; |
| |
| // DO THIS IN THREE PASSES: |
| // 1. Aggregate fragments per host. |
| // 2. Attach wires and fragments to hosts. |
| // -> If fragments fail to attach, then undo. |
| // 3. Mark hosts and fragments as resolved. |
| |
| // First pass. |
| if (wireMap != null) |
| { |
| // First pass: Loop through the wire map to find the host wires |
| // for any fragments and map a host to all of its fragments. |
| Map<Resource, List<BundleRevision>> hosts = |
| new HashMap<Resource, List<BundleRevision>>(); |
| for (Entry<Resource, List<Wire>> entry : wireMap.entrySet()) |
| { |
| Resource revision = entry.getKey(); |
| List<Wire> wires = entry.getValue(); |
| |
| if (Util.isFragment(revision)) |
| { |
| for (Wire w : wires) |
| { |
| List<BundleRevision> fragments = hosts.get(w.getProvider()); |
| if (fragments == null) |
| { |
| fragments = new ArrayList<BundleRevision>(); |
| hosts.put(w.getProvider(), fragments); |
| } |
| |
| if (w.getRequirer() instanceof BundleRevision) |
| fragments.add((BundleRevision) w.getRequirer()); |
| } |
| } |
| } |
| |
| // Second pass: Loop through the wire map to do three things: |
| // 1) convert resolver wires to bundle wires 2) create wiring |
| // objects for revisions and 3) record dependencies among |
| // revisions. We don't actually set the wirings here because |
| // that indicates that a revision is resolved and we don't want |
| // to mark anything as resolved unless we succussfully create |
| // all wirings. |
| Map<BundleRevision, BundleWiringImpl> wirings = |
| new HashMap<BundleRevision, BundleWiringImpl>(wireMap.size()); |
| for (Entry<Resource, List<Wire>> entry : wireMap.entrySet()) |
| { |
| Resource resource = entry.getKey(); |
| if (!(resource instanceof BundleRevision)) |
| continue; |
| |
| BundleRevision revision = (BundleRevision) resource; |
| List<Wire> resolverWires = entry.getValue(); |
| |
| List<BundleWire> bundleWires = |
| new ArrayList<BundleWire>(resolverWires.size()); |
| |
| // Need to special case fragments since they may already have |
| // wires if they are already attached to another host; if that |
| // is the case, then we want to merge the old host wires with |
| // the new ones. |
| if ((revision.getWiring() != null) && Util.isFragment(revision)) |
| { |
| // Fragments only have host wires, so just add them all. |
| bundleWires.addAll(revision.getWiring().getRequiredWires(null)); |
| } |
| |
| // Loop through resolver wires to calculate the package |
| // space implied by the wires as well as to record the |
| // dependencies. |
| Map<String, BundleRevision> importedPkgs = |
| new HashMap<String, BundleRevision>(); |
| Map<String, List<BundleRevision>> requiredPkgs = |
| new HashMap<String, List<BundleRevision>>(); |
| for (Wire rw : resolverWires) |
| { |
| // TODO is a rw already a BundleWire? |
| // TODO can we optimize this? |
| if (!(rw.getRequirer() instanceof BundleRevision)) |
| continue; |
| BundleRevision requirer = (BundleRevision) rw.getRequirer(); |
| if (!(rw.getRequirement() instanceof BundleRequirement)) |
| continue; |
| BundleRequirement requirement = (BundleRequirement) rw.getRequirement(); |
| if (!(rw.getProvider() instanceof BundleRevision)) |
| continue; |
| BundleRevision provider = (BundleRevision) rw.getProvider(); |
| if (!(rw.getCapability() instanceof BundleCapability)) |
| continue; |
| BundleCapability capability = (BundleCapability) rw.getCapability(); |
| |
| BundleWire bw = new BundleWireImpl( |
| requirer, |
| requirement, |
| provider, |
| capability); |
| bundleWires.add(bw); |
| |
| if (Util.isFragment(revision)) |
| { |
| if (debugLog) |
| { |
| m_felix.getLogger().log( |
| Logger.LOG_DEBUG, |
| "FRAGMENT WIRE: " + rw.toString()); |
| } |
| } |
| else |
| { |
| if (debugLog) |
| { |
| m_felix.getLogger().log(Logger.LOG_DEBUG, "WIRE: " + rw.toString()); |
| } |
| |
| if (capability.getNamespace() |
| .equals(BundleRevision.PACKAGE_NAMESPACE)) |
| { |
| importedPkgs.put( |
| (String) capability.getAttributes() |
| .get(BundleRevision.PACKAGE_NAMESPACE), |
| provider); |
| } |
| else if (capability.getNamespace() |
| .equals(BundleRevision.BUNDLE_NAMESPACE)) |
| { |
| Set<String> pkgs = calculateExportedAndReexportedPackages( |
| provider, |
| wireMap, |
| new HashSet<String>(), |
| new HashSet<BundleRevision>()); |
| for (String pkg : pkgs) |
| { |
| List<BundleRevision> revs = requiredPkgs.get(pkg); |
| if (revs == null) |
| { |
| revs = new ArrayList<BundleRevision>(); |
| requiredPkgs.put(pkg, revs); |
| } |
| revs.add(provider); |
| } |
| } |
| } |
| } |
| |
| List<BundleRevision> fragments = hosts.get(revision); |
| try |
| { |
| wirings.put( |
| revision, |
| new BundleWiringImpl( |
| m_felix.getLogger(), |
| m_felix.getConfig(), |
| this, |
| (BundleRevisionImpl) revision, |
| fragments, |
| bundleWires, |
| importedPkgs, |
| requiredPkgs)); |
| } |
| catch (Exception ex) |
| { |
| // This is a fatal error, so undo everything and |
| // throw an exception. |
| for (Entry<BundleRevision, BundleWiringImpl> wiringEntry |
| : wirings.entrySet()) |
| { |
| // Dispose of wiring. |
| try |
| { |
| wiringEntry.getValue().dispose(); |
| } |
| catch (Exception ex2) |
| { |
| // We are in big trouble. |
| RuntimeException rte = new RuntimeException( |
| "Unable to clean up resolver failure.", ex2); |
| m_felix.getLogger().log( |
| Logger.LOG_ERROR, |
| rte.getMessage(), ex2); |
| throw rte; |
| } |
| } |
| |
| ResolveException re = new ResolveException( |
| "Unable to resolve " + revision, |
| revision, null); |
| re.initCause(ex); |
| m_felix.getLogger().log( |
| Logger.LOG_ERROR, |
| re.getMessage(), ex); |
| throw re; |
| } |
| } |
| |
| // Third pass: Loop through the wire map to mark revision as resolved |
| // and update the resolver state. |
| for (Entry<BundleRevision, BundleWiringImpl> entry : wirings.entrySet()) |
| { |
| BundleRevisionImpl revision = (BundleRevisionImpl) entry.getKey(); |
| |
| // Mark revision as resolved. |
| BundleWiring wiring = entry.getValue(); |
| revision.resolve(entry.getValue()); |
| |
| // Record dependencies. |
| for (BundleWire bw : wiring.getRequiredWires(null)) |
| { |
| m_felix.getDependencies().addDependent(bw); |
| } |
| |
| // Reindex the revision's capabilities since its resolved |
| // capabilities could be different than its declared ones |
| // (e.g., due to substitutable exports). |
| addRevision(revision); |
| |
| // Update the state of the revision's bundle to resolved as well. |
| markBundleResolved(revision); |
| } |
| } |
| } |
| |
| private void markBundleResolved(BundleRevision revision) |
| { |
| // Update the bundle's state to resolved when the |
| // current revision is resolved; just ignore resolve |
| // events for older revisions since this only occurs |
| // when an update is done on an unresolved bundle |
| // and there was no refresh performed. |
| BundleImpl bundle = (BundleImpl) revision.getBundle(); |
| |
| // Lock the bundle first. |
| try |
| { |
| // Acquire bundle lock. |
| try |
| { |
| m_felix.acquireBundleLock( |
| bundle, Bundle.INSTALLED | Bundle.RESOLVED | Bundle.ACTIVE); |
| } |
| catch (IllegalStateException ex) |
| { |
| // There is nothing we can do. |
| } |
| if (bundle.adapt(BundleRevision.class) == revision) |
| { |
| if (bundle.getState() != Bundle.INSTALLED) |
| { |
| m_felix.getLogger().log(bundle, |
| Logger.LOG_WARNING, |
| "Received a resolve event for a bundle that has already been resolved."); |
| } |
| else |
| { |
| m_felix.setBundleStateAndNotify(bundle, Bundle.RESOLVED); |
| } |
| } |
| } |
| finally |
| { |
| m_felix.releaseBundleLock(bundle); |
| } |
| } |
| |
| private void fireResolvedEvents(Map<Resource, List<Wire>> wireMap) |
| { |
| if (wireMap != null) |
| { |
| Iterator<Entry<Resource, List<Wire>>> iter = |
| wireMap.entrySet().iterator(); |
| // Iterate over the map to fire necessary RESOLVED events. |
| while (iter.hasNext()) |
| { |
| Entry<Resource, List<Wire>> entry = iter.next(); |
| Resource resource = entry.getKey(); |
| if (!(resource instanceof BundleRevision)) |
| continue; |
| |
| BundleRevision revision = (BundleRevision) resource; |
| |
| // Fire RESOLVED events for all fragments. |
| List<BundleRevision> fragments = |
| Util.getFragments(revision.getWiring()); |
| for (int i = 0; i < fragments.size(); i++) |
| { |
| m_felix.fireBundleEvent( |
| BundleEvent.RESOLVED, fragments.get(i).getBundle()); |
| } |
| m_felix.fireBundleEvent(BundleEvent.RESOLVED, revision.getBundle()); |
| } |
| } |
| } |
| |
| private static Set<String> calculateExportedAndReexportedPackages( |
| BundleRevision br, |
| Map<Resource, List<Wire>> wireMap, |
| Set<String> pkgs, |
| Set<BundleRevision> cycles) |
| { |
| if (!cycles.contains(br)) |
| { |
| cycles.add(br); |
| |
| // Add all exported packages. |
| for (BundleCapability cap : br.getDeclaredCapabilities(null)) |
| { |
| if (cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE)) |
| { |
| pkgs.add((String) |
| cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE)); |
| } |
| } |
| |
| // Now check to see if any required bundles are required with reexport |
| // visibility, since we need to include those packages too. |
| if (br.getWiring() == null) |
| { |
| for (Wire rw : wireMap.get(br)) |
| { |
| if (rw.getCapability().getNamespace().equals( |
| BundleRevision.BUNDLE_NAMESPACE)) |
| { |
| String dir = rw.getRequirement() |
| .getDirectives().get(Constants.VISIBILITY_DIRECTIVE); |
| if ((dir != null) && (dir.equals(Constants.VISIBILITY_REEXPORT))) |
| { |
| calculateExportedAndReexportedPackages( |
| // TODO need to fix the cast |
| (BundleRevision) rw.getProvider(), |
| wireMap, |
| pkgs, |
| cycles); |
| } |
| } |
| } |
| } |
| else |
| { |
| for (BundleWire bw : br.getWiring().getRequiredWires(null)) |
| { |
| if (bw.getCapability().getNamespace().equals( |
| BundleRevision.BUNDLE_NAMESPACE)) |
| { |
| String dir = bw.getRequirement() |
| .getDirectives().get(Constants.VISIBILITY_DIRECTIVE); |
| if ((dir != null) && (dir.equals(Constants.VISIBILITY_REEXPORT))) |
| { |
| calculateExportedAndReexportedPackages( |
| bw.getProviderWiring().getRevision(), |
| wireMap, |
| pkgs, |
| cycles); |
| } |
| } |
| } |
| } |
| } |
| |
| return pkgs; |
| } |
| |
| private synchronized void indexCapabilities(BundleRevision br) |
| { |
| List<BundleCapability> caps = |
| (Util.isFragment(br) || (br.getWiring() == null)) |
| ? br.getDeclaredCapabilities(null) |
| : br.getWiring().getCapabilities(null); |
| if (caps != null) |
| { |
| for (BundleCapability cap : caps) |
| { |
| // If the capability is from a different revision, then |
| // don't index it since it is a capability from a fragment. |
| // In that case, the fragment capability is still indexed. |
| // It will be the resolver's responsibility to find all |
| // attached hosts for fragments. |
| if (cap.getRevision() == br) |
| { |
| CapabilitySet capSet = m_capSets.get(cap.getNamespace()); |
| if (capSet == null) |
| { |
| capSet = new CapabilitySet(null, true); |
| m_capSets.put(cap.getNamespace(), capSet); |
| } |
| capSet.addCapability(cap); |
| } |
| } |
| } |
| } |
| |
| private synchronized void deindexCapabilities(BundleRevision br) |
| { |
| // We only need be concerned with declared capabilities here, |
| // because resolved capabilities will be a subset, since fragment |
| // capabilities are not considered to be part of the host. |
| List<BundleCapability> caps = br.getDeclaredCapabilities(null); |
| if (caps != null) |
| { |
| for (BundleCapability cap : caps) |
| { |
| CapabilitySet capSet = m_capSets.get(cap.getNamespace()); |
| if (capSet != null) |
| { |
| capSet.removeCapability(cap); |
| } |
| } |
| } |
| } |
| |
| private synchronized boolean isSelectedSingleton(BundleRevision br) |
| { |
| return m_selectedSingletons.contains(br); |
| } |
| |
| private synchronized void selectSingletons(ResolverHookRecord record) |
| throws BundleException |
| { |
| // First deindex any unresolved singletons to make sure |
| // there aren't any available from previous resolves. |
| // Also remove them from the fragment list, for the same |
| // reason. |
| m_selectedSingletons.clear(); |
| for (Entry<String, List<BundleRevision>> entry : m_singletons.entrySet()) |
| { |
| for (BundleRevision singleton : entry.getValue()) |
| { |
| if (singleton.getWiring() == null) |
| { |
| deindexCapabilities(singleton); |
| m_fragments.remove(singleton); |
| } |
| } |
| } |
| |
| // If no resolver hooks, then use default singleton selection |
| // algorithm, otherwise defer to the resolver hooks. |
| if (record.getResolverHookRefs().isEmpty()) |
| { |
| selectDefaultSingletons(record); |
| } |
| else |
| { |
| selectSingletonsUsingHooks(record); |
| } |
| } |
| |
| /* |
| * Selects the singleton with the highest version from groupings |
| * based on the symbolic name. No selection is made if the group |
| * already has a resolved singleton. |
| */ |
| private void selectDefaultSingletons(ResolverHookRecord record) |
| { |
| // Now select the singletons available for this resolve operation. |
| for (Entry<String, List<BundleRevision>> entry : m_singletons.entrySet()) |
| { |
| selectSingleton(record, entry.getValue()); |
| } |
| } |
| |
| /* |
| * Groups singletons based on resolver hook filtering and then selects |
| * the singleton from each group with the highest version that is in |
| * the resolver hook whitelist. No selection is made if a group already |
| * has a resolved singleton in it. |
| */ |
| private void selectSingletonsUsingHooks(ResolverHookRecord record) |
| throws BundleException |
| { |
| // Convert singleton bundle revision map into a map using |
| // bundle capabilities instead, since this is what the resolver |
| // hooks require. |
| Map<BundleCapability, Collection<BundleCapability>> allCollisions |
| = new HashMap<BundleCapability, Collection<BundleCapability>>(); |
| for (Entry<String, List<BundleRevision>> entry : m_singletons.entrySet()) |
| { |
| Collection<BundleCapability> bundleCaps = |
| new ArrayList<BundleCapability>(); |
| for (BundleRevision br : entry.getValue()) |
| { |
| List<BundleCapability> caps = |
| br.getDeclaredCapabilities(BundleRevision.BUNDLE_NAMESPACE); |
| if (!caps.isEmpty()) |
| { |
| bundleCaps.add(caps.get(0)); |
| } |
| } |
| |
| for (BundleCapability bc : bundleCaps) |
| { |
| Collection<BundleCapability> capCopy = |
| new ShrinkableCollection<BundleCapability>( |
| new ArrayList<BundleCapability>(bundleCaps)); |
| capCopy.remove(bc); |
| allCollisions.put(bc, capCopy); |
| } |
| } |
| |
| // Invoke hooks to allow them to filter singleton collisions. |
| for (ResolverHook hook : record.getResolverHooks()) |
| { |
| for (Entry<BundleCapability, Collection<BundleCapability>> entry |
| : allCollisions.entrySet()) |
| { |
| try |
| { |
| Felix.m_secureAction |
| .invokeResolverHookSingleton(hook, entry.getKey(), entry.getValue()); |
| } |
| catch (Throwable ex) |
| { |
| throw new BundleException( |
| "Resolver hook exception: " + ex.getMessage(), |
| BundleException.REJECTED_BY_HOOK, |
| ex); |
| } |
| } |
| } |
| |
| // Create groups according to how the resolver hooks filtered the |
| // collisions. |
| List<List<BundleRevision>> groups = new ArrayList<List<BundleRevision>>(); |
| while (!allCollisions.isEmpty()) |
| { |
| BundleCapability target = allCollisions.entrySet().iterator().next().getKey(); |
| groups.add(groupSingletons(allCollisions, target, new ArrayList<BundleRevision>())); |
| } |
| |
| // Now select the singletons available for this resolve operation. |
| for (List<BundleRevision> group : groups) |
| { |
| selectSingleton(record, group); |
| } |
| } |
| |
| private List<BundleRevision> groupSingletons( |
| Map<BundleCapability, Collection<BundleCapability>> allCollisions, |
| BundleCapability target, List<BundleRevision> group) |
| { |
| if (!group.contains(target.getRevision())) |
| { |
| // Add the target since it is implicitly part of the group. |
| group.add(target.getRevision()); |
| |
| // Recursively add the revisions of any singleton's in the |
| // target's collisions. |
| Collection<BundleCapability> collisions = allCollisions.remove(target); |
| for (BundleCapability collision : collisions) |
| { |
| groupSingletons(allCollisions, collision, group); |
| } |
| |
| // Need to check the values of other collisions for this target |
| // and add those to the target's group too, since collisions are |
| // treated as two-way relationships. Repeat until there are no |
| // collision groups left that contain the target capability. |
| boolean repeat; |
| do |
| { |
| repeat = false; |
| for (Entry<BundleCapability, Collection<BundleCapability>> entry: |
| allCollisions.entrySet()) |
| { |
| if (entry.getValue().contains(target)) |
| { |
| repeat = true; |
| groupSingletons(allCollisions, entry.getKey(), group); |
| break; |
| } |
| } |
| } |
| while (repeat); |
| } |
| return group; |
| } |
| |
| /* |
| * Selects the highest bundle revision from the group that is |
| * in the resolver hook whitelist (if there are hooks). No |
| * selection is made if there is an already resolved singleton |
| * in the group, since it is already indexed. |
| */ |
| private void selectSingleton(ResolverHookRecord record, List<BundleRevision> singletons) |
| { |
| BundleRevision selected = null; |
| for (BundleRevision singleton : singletons) |
| { |
| // If a singleton is already resolved, |
| // then there is nothing to do. |
| if (singleton.getWiring() != null) |
| { |
| selected = null; |
| break; |
| } |
| // If this singleton is not in the whitelist, then it cannot |
| // be selected. If it is, in can only be selected if it has |
| // a higher version than the currently selected singleton, if |
| // there is one. |
| if (((record.getBundleRevisionWhitelist() == null) || record.getBundleRevisionWhitelist().contains(singleton)) |
| && ((selected == null) |
| || (selected.getVersion().compareTo(singleton.getVersion()) > 0))) |
| { |
| selected = singleton; |
| } |
| } |
| if (selected != null) |
| { |
| // Record the selected singleton. |
| m_selectedSingletons.add(selected); |
| // Index its capabilities. |
| indexCapabilities(selected); |
| // If the selected singleton is a fragment, then |
| // add it to the list of fragments. |
| if (Util.isFragment(selected)) |
| { |
| m_fragments.add(selected); |
| } |
| } |
| } |
| |
| private synchronized Set<BundleRevision> getFragments() |
| { |
| Set<BundleRevision> fragments = new HashSet(m_fragments); |
| // Filter out any fragments that are not the current revision. |
| for (Iterator<BundleRevision> it = fragments.iterator(); it.hasNext(); ) |
| { |
| BundleRevision fragment = it.next(); |
| BundleRevision currentFragmentRevision = |
| fragment.getBundle().adapt(BundleRevision.class); |
| if (fragment != currentFragmentRevision) |
| { |
| it.remove(); |
| } |
| } |
| return fragments; |
| } |
| |
| private synchronized Set<BundleRevision> getUnresolvedRevisions() |
| { |
| Set<BundleRevision> unresolved = new HashSet<BundleRevision>(); |
| for (BundleRevision revision : m_revisions) |
| { |
| if (revision.getWiring() == null) |
| { |
| unresolved.add(revision); |
| } |
| } |
| return unresolved; |
| } |
| |
| private synchronized Map<Resource, Wiring> getWirings() |
| { |
| Map<Resource, Wiring> wirings = new HashMap<Resource, Wiring>(); |
| |
| for (BundleRevision revision : m_revisions) |
| { |
| if (revision.getWiring() != null) |
| { |
| wirings.put(revision, revision.getWiring()); |
| } |
| } |
| return wirings; |
| } |
| |
| private static void addToSingletonMap( |
| Map<String, List<BundleRevision>> singletons, BundleRevision br) |
| { |
| List<BundleRevision> revisions = singletons.get(br.getSymbolicName()); |
| if (revisions == null) |
| { |
| revisions = new ArrayList<BundleRevision>(); |
| } |
| revisions.add(br); |
| singletons.put(br.getSymbolicName(), revisions); |
| } |
| |
| static class ResolverHookRecord |
| { |
| final Map<ServiceReference<ResolverHookFactory>, ResolverHook> m_resolveHookMap; |
| final Collection<BundleRevision> m_brWhitelist; |
| |
| /** The map passed in must be of an ordered type, so that the iteration order over the values |
| * is predictable. |
| */ |
| ResolverHookRecord(Map<ServiceReference<ResolverHookFactory>, ResolverHook> resolveHookMap, |
| Collection<BundleRevision> brWhiteList) |
| { |
| m_resolveHookMap = resolveHookMap; |
| m_brWhitelist = brWhiteList; |
| } |
| |
| Collection<BundleRevision> getBundleRevisionWhitelist() |
| { |
| return m_brWhitelist; |
| } |
| |
| Set<ServiceReference<ResolverHookFactory>> getResolverHookRefs() |
| { |
| return m_resolveHookMap.keySet(); |
| } |
| |
| // This slightly over the top implementation to obtain the hooks is to ensure that at the point that |
| // the actual hook is obtained, the service is still registered. There are CT tests that unregister |
| // the hook service while iterating over the hooks and expect that the unregistered hook is not called |
| // in that case. |
| Iterable<ResolverHook> getResolverHooks() |
| { |
| return new Iterable<ResolverHook>() |
| { |
| @Override |
| public Iterator<ResolverHook> iterator() |
| { |
| return new Iterator<ResolverHook>() |
| { |
| private final Iterator<Map.Entry<ServiceReference<ResolverHookFactory>, ResolverHook>> it = |
| m_resolveHookMap.entrySet().iterator(); |
| private Entry<ServiceReference<ResolverHookFactory>, ResolverHook> next = null; |
| |
| @Override |
| public boolean hasNext() |
| { |
| if (next == null) |
| findNext(); |
| |
| return next != null; |
| } |
| |
| @Override |
| public ResolverHook next() |
| { |
| if (next == null) |
| findNext(); |
| |
| if (next == null) |
| throw new NoSuchElementException(); |
| |
| ResolverHook hook = next.getValue(); |
| next = null; |
| return hook; |
| } |
| |
| // Find the next hook on the iterator, but only if the service is still registered. |
| // If the service has since been unregistered, skip the hook. |
| private void findNext() |
| { |
| while (it.hasNext()) |
| { |
| next = it.next(); |
| if (next.getKey().getBundle() != null) |
| return; |
| else |
| next = null; |
| } |
| } |
| |
| @Override |
| public void remove() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| } |
| }; |
| } |
| } |
| } |