| /* |
| * 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.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import org.apache.felix.framework.searchpolicy.ResolveException; |
| import org.apache.felix.framework.searchpolicy.Resolver; |
| import org.apache.felix.framework.util.Util; |
| import org.apache.felix.framework.util.VersionRange; |
| import org.apache.felix.framework.util.manifestparser.R4Attribute; |
| import org.apache.felix.framework.util.manifestparser.R4Directive; |
| import org.apache.felix.framework.util.manifestparser.Requirement; |
| import org.apache.felix.moduleloader.ICapability; |
| import org.apache.felix.moduleloader.IModule; |
| import org.apache.felix.moduleloader.IRequirement; |
| import org.apache.felix.moduleloader.IWire; |
| import org.osgi.framework.BundlePermission; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.PackagePermission; |
| import org.osgi.framework.Version; |
| |
| public class FelixResolverState implements Resolver.ResolverState |
| { |
| private final Logger m_logger; |
| // List of all modules. |
| private final List m_moduleList = new ArrayList(); |
| // Map of fragment symbolic names to list of fragment modules sorted by version. |
| private final Map m_fragmentMap = new HashMap(); |
| // Maps a package name to a list of exporting capabilities. |
| private final Map m_unresolvedPkgIndex = new HashMap(); |
| // Maps a package name to a list of exporting capabilities. |
| private final Map m_resolvedPkgIndex = new HashMap(); |
| // Maps a module to a list of capabilities. |
| private final Map m_resolvedCapMap = new HashMap(); |
| |
| public FelixResolverState(Logger logger) |
| { |
| m_logger = logger; |
| } |
| |
| public synchronized void addModule(IModule module) |
| { |
| if (Util.isFragment(module)) |
| { |
| addFragment(module); |
| } |
| else |
| { |
| addHost(module); |
| } |
| |
| //System.out.println("UNRESOLVED PACKAGES:"); |
| //dumpPackageIndex(m_unresolvedPkgIndex); |
| //System.out.println("RESOLVED PACKAGES:"); |
| //dumpPackageIndex(m_resolvedPkgIndex); |
| } |
| |
| public synchronized void removeModule(IModule module) |
| { |
| if (Util.isFragment(module)) |
| { |
| removeFragment(module); |
| } |
| else |
| { |
| removeHost(module); |
| } |
| } |
| |
| private void addFragment(IModule fragment) |
| { |
| // TODO: FRAGMENT - This should check to make sure that the host allows fragments. |
| IModule bestFragment = indexFragment(m_fragmentMap, fragment); |
| |
| // If the newly added fragment is the highest version for |
| // its given symbolic name, then try to merge it to any |
| // matching unresolved hosts and remove the previous highest |
| // version of the fragment. |
| if (bestFragment == fragment) |
| { |
| |
| // If we have any matching hosts, then merge the new fragment while |
| // removing any older version of the new fragment. Also remove host's |
| // existing capabilities from the package index and reindex its new |
| // ones after attaching the fragment. |
| List matchingHosts = getMatchingHosts(fragment); |
| for (int hostIdx = 0; hostIdx < matchingHosts.size(); hostIdx++) |
| { |
| IModule host = ((ICapability) matchingHosts.get(hostIdx)).getModule(); |
| |
| // Get the fragments currently attached to the host so we |
| // can remove the older version of the current fragment, if any. |
| IModule[] fragments = ((ModuleImpl) host).getFragments(); |
| List fragmentList = new ArrayList(); |
| for (int fragIdx = 0; |
| (fragments != null) && (fragIdx < fragments.length); |
| fragIdx++) |
| { |
| if (!fragments[fragIdx].getSymbolicName().equals( |
| bestFragment.getSymbolicName())) |
| { |
| fragmentList.add(fragments[fragIdx]); |
| } |
| } |
| |
| // Now add the new fragment in bundle ID order. |
| int index = -1; |
| for (int listIdx = 0; |
| (index < 0) && (listIdx < fragmentList.size()); |
| listIdx++) |
| { |
| IModule f = (IModule) fragmentList.get(listIdx); |
| if (bestFragment.getBundle().getBundleId() |
| < f.getBundle().getBundleId()) |
| { |
| index = listIdx; |
| } |
| } |
| fragmentList.add( |
| (index < 0) ? fragmentList.size() : index, bestFragment); |
| |
| // Remove host's existing exported packages from index. |
| ICapability[] caps = host.getCapabilities(); |
| for (int i = 0; (caps != null) && (i < caps.length); i++) |
| { |
| if (caps[i].getNamespace().equals(ICapability.PACKAGE_NAMESPACE)) |
| { |
| // Get package name. |
| String pkgName = (String) |
| caps[i].getProperties().get(ICapability.PACKAGE_PROPERTY); |
| // Remove from "unresolved" package map. |
| List capList = (List) m_unresolvedPkgIndex.get(pkgName); |
| if (capList != null) |
| { |
| capList.remove(caps[i]); |
| } |
| } |
| } |
| |
| // Check if fragment conflicts with existing metadata. |
| checkForConflicts(host, fragmentList); |
| |
| // Attach the fragments to the host. |
| fragments = (fragmentList.size() == 0) |
| ? null |
| : (IModule[]) fragmentList.toArray(new IModule[fragmentList.size()]); |
| try |
| { |
| ((ModuleImpl) host).attachFragments(fragments); |
| } |
| catch (Exception ex) |
| { |
| // Try to clean up by removing all fragments. |
| try |
| { |
| ((ModuleImpl) host).attachFragments(null); |
| } |
| catch (Exception ex2) |
| { |
| } |
| m_logger.log(Logger.LOG_ERROR, |
| "Serious error attaching fragments.", ex); |
| } |
| |
| // Reindex the host's exported packages. |
| caps = host.getCapabilities(); |
| for (int i = 0; (caps != null) && (i < caps.length); i++) |
| { |
| if (caps[i].getNamespace().equals(ICapability.PACKAGE_NAMESPACE)) |
| { |
| indexPackageCapability(m_unresolvedPkgIndex, caps[i]); |
| } |
| } |
| } |
| } |
| } |
| |
| private void removeFragment(IModule fragment) |
| { |
| // Get fragment list, which may be null for system bundle fragments. |
| List fragList = (List) m_fragmentMap.get(fragment.getSymbolicName()); |
| if (fragList != null) |
| { |
| // Remove from fragment map. |
| fragList.remove(fragment); |
| if (fragList.size() == 0) |
| { |
| m_fragmentMap.remove(fragment.getSymbolicName()); |
| } |
| |
| // If we have any matching hosts, then remove fragment while |
| // removing any older version of the new fragment. Also remove host's |
| // existing capabilities from the package index and reindex its new |
| // ones after attaching the fragment. |
| List matchingHosts = getMatchingHosts(fragment); |
| for (int hostIdx = 0; hostIdx < matchingHosts.size(); hostIdx++) |
| { |
| IModule host = ((ICapability) matchingHosts.get(hostIdx)).getModule(); |
| |
| // Check to see if the removed fragment was actually merged with |
| // the host, since it might not be if it wasn't the highest version. |
| // If it was, recalculate the fragments for the host. |
| IModule[] fragments = ((ModuleImpl) host).getFragments(); |
| for (int fragIdx = 0; |
| (fragments != null) && (fragIdx < fragments.length); |
| fragIdx++) |
| { |
| if (!fragments[fragIdx].equals(fragment)) |
| { |
| List fragmentList = getMatchingFragments(host); |
| |
| // Remove host's existing exported packages from index. |
| ICapability[] caps = host.getCapabilities(); |
| for (int i = 0; (caps != null) && (i < caps.length); i++) |
| { |
| if (caps[i].getNamespace().equals(ICapability.PACKAGE_NAMESPACE)) |
| { |
| // Get package name. |
| String pkgName = (String) |
| caps[i].getProperties().get(ICapability.PACKAGE_PROPERTY); |
| // Remove from "unresolved" package map. |
| List capList = (List) m_unresolvedPkgIndex.get(pkgName); |
| if (capList != null) |
| { |
| capList.remove(caps[i]); |
| } |
| } |
| } |
| |
| // Check if fragment conflicts with existing metadata. |
| checkForConflicts(host, fragmentList); |
| |
| // Attach the fragments to the host. |
| fragments = (fragmentList.size() == 0) |
| ? null |
| : (IModule[]) fragmentList.toArray(new IModule[fragmentList.size()]); |
| try |
| { |
| ((ModuleImpl) host).attachFragments(fragments); |
| } |
| catch (Exception ex) |
| { |
| // Try to clean up by removing all fragments. |
| try |
| { |
| ((ModuleImpl) host).attachFragments(null); |
| } |
| catch (Exception ex2) |
| { |
| } |
| m_logger.log(Logger.LOG_ERROR, |
| "Serious error attaching fragments.", ex); |
| } |
| |
| // Reindex the host's exported packages. |
| caps = host.getCapabilities(); |
| for (int i = 0; (caps != null) && (i < caps.length); i++) |
| { |
| if (caps[i].getNamespace().equals(ICapability.PACKAGE_NAMESPACE)) |
| { |
| indexPackageCapability(m_unresolvedPkgIndex, caps[i]); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| public void unmergeFragment(IModule module) |
| { |
| if (!Util.isFragment(module)) |
| { |
| return; |
| } |
| |
| // Get fragment list, which may be null for system bundle fragments. |
| List fragList = (List) m_fragmentMap.get(module.getSymbolicName()); |
| if (fragList != null) |
| { |
| // Remove from fragment map. |
| fragList.remove(module); |
| if (fragList.size() == 0) |
| { |
| m_fragmentMap.remove(module.getSymbolicName()); |
| } |
| |
| // If we have any matching hosts, then remove fragment while |
| // removing any older version of the new fragment. Also remove host's |
| // existing capabilities from the package index and reindex its new |
| // ones after attaching the fragment. |
| List matchingHosts = getMatchingHosts(module); |
| for (int hostIdx = 0; hostIdx < matchingHosts.size(); hostIdx++) |
| { |
| IModule host = ((ICapability) matchingHosts.get(hostIdx)).getModule(); |
| // Find any unresolved hosts into which the fragment is merged |
| // and unmerge it. |
| IModule[] fragments = ((ModuleImpl) host).getFragments(); |
| for (int fragIdx = 0; |
| !host.isResolved() && (fragments != null) && (fragIdx < fragments.length); |
| fragIdx++) |
| { |
| if (!fragments[fragIdx].equals(module)) |
| { |
| List fragmentList = getMatchingFragments(host); |
| |
| // Remove host's existing exported packages from index. |
| ICapability[] caps = host.getCapabilities(); |
| for (int i = 0; (caps != null) && (i < caps.length); i++) |
| { |
| if (caps[i].getNamespace().equals(ICapability.PACKAGE_NAMESPACE)) |
| { |
| // Get package name. |
| String pkgName = (String) |
| caps[i].getProperties().get(ICapability.PACKAGE_PROPERTY); |
| // Remove from "unresolved" package map. |
| List capList = (List) m_unresolvedPkgIndex.get(pkgName); |
| if (capList != null) |
| { |
| capList.remove(caps[i]); |
| } |
| } |
| } |
| |
| // Check if fragment conflicts with existing metadata. |
| checkForConflicts(host, fragmentList); |
| |
| // Attach the fragments to the host. |
| fragments = (fragmentList.size() == 0) |
| ? null |
| : (IModule[]) fragmentList.toArray(new IModule[fragmentList.size()]); |
| try |
| { |
| ((ModuleImpl) host).attachFragments(fragments); |
| } |
| catch (Exception ex) |
| { |
| // Try to clean up by removing all fragments. |
| try |
| { |
| ((ModuleImpl) host).attachFragments(null); |
| } |
| catch (Exception ex2) |
| { |
| } |
| m_logger.log(Logger.LOG_ERROR, |
| "Serious error attaching fragments.", ex); |
| } |
| |
| // Reindex the host's exported packages. |
| caps = host.getCapabilities(); |
| for (int i = 0; (caps != null) && (i < caps.length); i++) |
| { |
| if (caps[i].getNamespace().equals(ICapability.PACKAGE_NAMESPACE)) |
| { |
| indexPackageCapability(m_unresolvedPkgIndex, caps[i]); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private List getMatchingHosts(IModule fragment) |
| { |
| // Find the fragment's host requirement. |
| IRequirement hostReq = getFragmentHostRequirement(fragment); |
| |
| // Create a list of all matching hosts for this fragment. |
| List matchingHosts = new ArrayList(); |
| SecurityManager sm = System.getSecurityManager(); |
| if (sm != null) |
| { |
| if (!((BundleProtectionDomain) fragment.getSecurityContext()).impliesDirect(new BundlePermission( |
| fragment.getSymbolicName(), BundlePermission.FRAGMENT))) |
| { |
| return matchingHosts; |
| } |
| } |
| for (int hostIdx = 0; (hostReq != null) && (hostIdx < m_moduleList.size()); hostIdx++) |
| { |
| IModule host = (IModule) m_moduleList.get(hostIdx); |
| // Only look at unresolved hosts, since we don't support |
| // dynamic attachment of fragments. |
| if (host.isResolved() |
| || ((BundleImpl) host.getBundle()).isStale() |
| || ((BundleImpl) host.getBundle()).isRemovalPending()) |
| { |
| continue; |
| } |
| |
| // Find the host capability for the current host. |
| ICapability hostCap = Util.getSatisfyingCapability(host, hostReq); |
| |
| // If there is no host capability in the current module, |
| // then just ignore it. |
| if (hostCap == null) |
| { |
| continue; |
| } |
| |
| if (sm != null) |
| { |
| if (!((BundleProtectionDomain) host.getSecurityContext()).impliesDirect(new BundlePermission(host.getSymbolicName(), |
| BundlePermission.HOST))) |
| { |
| continue; |
| } |
| } |
| |
| matchingHosts.add(hostCap); |
| } |
| |
| return matchingHosts; |
| } |
| |
| private void checkForConflicts(IModule host, List fragmentList) |
| { |
| if ((fragmentList == null) || (fragmentList.size() == 0)) |
| { |
| return; |
| } |
| |
| // Verify the fragments do not have conflicting imports. |
| // For now, just check for duplicate imports, but in the |
| // future we might want to make this more fine grained. |
| // First get the host's imported packages. |
| final int MODULE_IDX = 0, REQ_IDX = 1; |
| Map ipMerged = new HashMap(); |
| Map rbMerged = new HashMap(); |
| IRequirement[] reqs = host.getRequirements(); |
| for (int reqIdx = 0; (reqs != null) && (reqIdx < reqs.length); reqIdx++) |
| { |
| if (reqs[reqIdx].getNamespace().equals(ICapability.PACKAGE_NAMESPACE)) |
| { |
| ipMerged.put( |
| ((Requirement) reqs[reqIdx]).getTargetName(), |
| new Object[] { host, reqs[reqIdx] }); |
| } |
| else if (reqs[reqIdx].getNamespace().equals(ICapability.MODULE_NAMESPACE)) |
| { |
| rbMerged.put( |
| ((Requirement) reqs[reqIdx]).getTargetName(), |
| new Object[] { host, reqs[reqIdx] }); |
| } |
| } |
| // Loop through each fragment verifying it does not conflict. |
| // Add its package and bundle dependencies if they do not |
| // conflict or remove the fragment if it does conflict. |
| for (Iterator it = fragmentList.iterator(); it.hasNext(); ) |
| { |
| IModule fragment = (IModule) it.next(); |
| reqs = fragment.getRequirements(); |
| Map ipFragment = new HashMap(); |
| Map rbFragment = new HashMap(); |
| for (int reqIdx = 0; |
| (reqs != null) && (reqIdx < reqs.length); |
| reqIdx++) |
| { |
| if (reqs[reqIdx].getNamespace().equals(ICapability.PACKAGE_NAMESPACE) |
| || reqs[reqIdx].getNamespace().equals(ICapability.MODULE_NAMESPACE)) |
| { |
| String targetName = ((Requirement) reqs[reqIdx]).getTargetName(); |
| Map mergedReqMap = |
| (reqs[reqIdx].getNamespace().equals(ICapability.PACKAGE_NAMESPACE)) |
| ? ipMerged : rbMerged; |
| Map fragmentReqMap = |
| (reqs[reqIdx].getNamespace().equals(ICapability.PACKAGE_NAMESPACE)) |
| ? ipFragment : rbFragment; |
| Object[] existing = (Object[]) mergedReqMap.get(targetName); |
| if (existing == null) |
| { |
| fragmentReqMap.put(targetName, new Object[] { fragment, reqs[reqIdx] }); |
| } |
| else if (isRequirementConflicting( |
| (Requirement) existing[REQ_IDX], (Requirement) reqs[reqIdx])) |
| { |
| ipFragment.clear(); |
| rbFragment.clear(); |
| it.remove(); |
| m_logger.log( |
| Logger.LOG_DEBUG, |
| "Excluding fragment " + fragment.getSymbolicName() |
| + " from " + host.getSymbolicName() |
| + " due to conflict with " |
| + (reqs[reqIdx].getNamespace().equals(ICapability.PACKAGE_NAMESPACE) |
| ? "imported package " : "required bundle ") |
| + targetName + " from " |
| + ((IModule) existing[MODULE_IDX]).getSymbolicName()); |
| // No need to finish processing current fragment. |
| break; |
| } |
| else |
| { |
| // If there is an overlapping requirement for the existing |
| // target, then try to calculate the intersecting requirement |
| // and set the existing requirement to that instead. This |
| // makes it so version ranges do not have to be exact, just |
| // overlapping. |
| Requirement intersection = calculateVersionIntersection( |
| (Requirement) existing[REQ_IDX], (Requirement) reqs[reqIdx]); |
| if (intersection != existing[REQ_IDX]) |
| { |
| existing[REQ_IDX] = intersection; |
| } |
| } |
| } |
| } |
| |
| // Merge non-conflicting requirements into overall set |
| // of requirements and continue checking for conflicts |
| // with the next fragment. |
| for (Iterator it2 = ipFragment.entrySet().iterator(); it2.hasNext(); ) |
| { |
| Map.Entry entry = (Map.Entry) it2.next(); |
| ipMerged.put(entry.getKey(), entry.getValue()); |
| } |
| for (Iterator it2 = rbFragment.entrySet().iterator(); it2.hasNext(); ) |
| { |
| Map.Entry entry = (Map.Entry) it2.next(); |
| rbMerged.put(entry.getKey(), entry.getValue()); |
| } |
| } |
| } |
| |
| private boolean isRequirementConflicting( |
| Requirement existing, Requirement additional) |
| { |
| // If the namespace is not the same, then they do NOT conflict. |
| if (!existing.getNamespace().equals(additional.getNamespace())) |
| { |
| return false; |
| } |
| // If the target name is not the same, then they do NOT conflict. |
| if (!existing.getTargetName().equals(additional.getTargetName())) |
| { |
| return false; |
| } |
| // If the existing version range floor is greater than the additional |
| // version range's floor, then they are inconflict since we cannot |
| // widen the constraint. |
| if (!existing.getTargetVersionRange().intersects( |
| additional.getTargetVersionRange())) |
| { |
| return true; |
| } |
| // If optionality is not the same, then they conflict, unless |
| // the existing requirement is not optional, then it doesn't matter |
| // what subsequent requirements are since non-optional is stronger |
| // than optional. |
| if (existing.isOptional() && !additional.isOptional()) |
| { |
| return true; |
| } |
| // Verify directives are the same. |
| final R4Directive[] exDirs = (existing.getDirectives() == null) |
| ? new R4Directive[0] : existing.getDirectives(); |
| final R4Directive[] addDirs = (additional.getDirectives() == null) |
| ? new R4Directive[0] : additional.getDirectives(); |
| // Put attributes in a map, since ordering is arbitrary. |
| final Map exDirMap = new HashMap(); |
| for (int i = 0; i < exDirs.length; i++) |
| { |
| exDirMap.put(exDirs[i].getName(), exDirs[i]); |
| } |
| // If attribute values do not match, then they conflict. |
| for (int i = 0; i < addDirs.length; i++) |
| { |
| // Ignore resolution directive, since we've already tested it above. |
| if (!addDirs[i].getName().equals(Constants.RESOLUTION_DIRECTIVE)) |
| { |
| final R4Directive exDir = (R4Directive) exDirMap.get(addDirs[i].getName()); |
| if ((exDir == null) || |
| !exDir.getValue().equals(addDirs[i].getValue())) |
| { |
| return true; |
| } |
| } |
| } |
| // Verify attributes are the same. |
| final R4Attribute[] exAttrs = (existing.getAttributes() == null) |
| ? new R4Attribute[0] : existing.getAttributes(); |
| final R4Attribute[] addAttrs = (additional.getAttributes() == null) |
| ? new R4Attribute[0] : additional.getAttributes(); |
| // Put attributes in a map, since ordering is arbitrary. |
| final Map exAttrMap = new HashMap(); |
| for (int i = 0; i < exAttrs.length; i++) |
| { |
| exAttrMap.put(exAttrs[i].getName(), exAttrs[i]); |
| } |
| // If attribute values do not match, then they conflict. |
| for (int i = 0; i < addAttrs.length; i++) |
| { |
| // Ignore version property, since we've already tested it above. |
| if (!(additional.getNamespace().equals(ICapability.PACKAGE_NAMESPACE) |
| && addAttrs[i].getName().equals(ICapability.VERSION_PROPERTY)) |
| && !(additional.getNamespace().equals(ICapability.MODULE_NAMESPACE) |
| && addAttrs[i].getName().equals(Constants.BUNDLE_VERSION_ATTRIBUTE))) |
| { |
| final R4Attribute exAttr = (R4Attribute) exAttrMap.get(addAttrs[i].getName()); |
| if ((exAttr == null) || |
| !exAttr.getValue().equals(addAttrs[i].getValue()) || |
| (exAttr.isMandatory() != addAttrs[i].isMandatory())) |
| { |
| return true; |
| } |
| } |
| } |
| // They do no conflict. |
| return false; |
| } |
| |
| static Requirement calculateVersionIntersection( |
| Requirement existing, Requirement additional) |
| { |
| Requirement intersection = existing; |
| int existVersionIdx = -1, addVersionIdx = -1; |
| |
| // Find the existing version attribute. |
| for (int i = 0; (existVersionIdx < 0) && (i < existing.getAttributes().length); i++) |
| { |
| if ((existing.getNamespace().equals(ICapability.PACKAGE_NAMESPACE) |
| && existing.getAttributes()[i].getName().equals(ICapability.VERSION_PROPERTY)) |
| || (existing.getNamespace().equals(ICapability.MODULE_NAMESPACE) |
| && existing.getAttributes()[i].getName().equals(Constants.BUNDLE_VERSION_ATTRIBUTE))) |
| { |
| existVersionIdx = i; |
| } |
| } |
| |
| // Find the additional version attribute. |
| for (int i = 0; (addVersionIdx < 0) && (i < additional.getAttributes().length); i++) |
| { |
| if ((additional.getNamespace().equals(ICapability.PACKAGE_NAMESPACE) |
| && additional.getAttributes()[i].getName().equals(ICapability.VERSION_PROPERTY)) |
| || (additional.getNamespace().equals(ICapability.MODULE_NAMESPACE) |
| && additional.getAttributes()[i].getName().equals(Constants.BUNDLE_VERSION_ATTRIBUTE))) |
| { |
| addVersionIdx = i; |
| } |
| } |
| |
| // Use the additional requirement's version range if it |
| // has one and the existing requirement does not. |
| if ((existVersionIdx == -1) && (addVersionIdx != -1)) |
| { |
| intersection = additional; |
| } |
| // If both requirements have version ranges, then create |
| // a new requirement with an intersecting version range. |
| else if ((existVersionIdx != -1) && (addVersionIdx != -1)) |
| { |
| VersionRange vr = ((VersionRange) existing.getAttributes()[existVersionIdx].getValue()) |
| .intersection((VersionRange) additional.getAttributes()[addVersionIdx].getValue()); |
| R4Attribute[] attrs = existing.getAttributes(); |
| R4Attribute[] newAttrs = new R4Attribute[attrs.length]; |
| System.arraycopy(attrs, 0, newAttrs, 0, attrs.length); |
| newAttrs[existVersionIdx] = new R4Attribute( |
| attrs[existVersionIdx].getName(), vr, false); |
| intersection = new Requirement( |
| existing.getNamespace(), |
| existing.getDirectives(), |
| newAttrs); |
| } |
| |
| return intersection; |
| } |
| |
| private void addHost(IModule host) |
| { |
| // When a module is added, we first need to pre-merge any potential fragments |
| // into the host and then second create an aggregated list of unresolved |
| // capabilities to simplify later processing when resolving bundles. |
| m_moduleList.add(host); |
| |
| // |
| // First, merge applicable fragments. |
| // |
| |
| List fragmentList = getMatchingFragments(host); |
| |
| // Attach any fragments we found for this host. |
| if (fragmentList.size() > 0) |
| { |
| // Check if fragment conflicts with existing metadata. |
| checkForConflicts(host, fragmentList); |
| |
| // Attach the fragments to the host. |
| IModule[] fragments = |
| (IModule[]) fragmentList.toArray(new IModule[fragmentList.size()]); |
| try |
| { |
| ((ModuleImpl) host).attachFragments(fragments); |
| } |
| catch (Exception ex) |
| { |
| // Try to clean up by removing all fragments. |
| try |
| { |
| ((ModuleImpl) host).attachFragments(null); |
| } |
| catch (Exception ex2) |
| { |
| } |
| m_logger.log(Logger.LOG_ERROR, |
| "Serious error attaching fragments.", ex); |
| } |
| } |
| |
| // |
| // Second, index module's capabilities. |
| // |
| |
| ICapability[] caps = host.getCapabilities(); |
| |
| // Add exports to unresolved package map. |
| for (int i = 0; (caps != null) && (i < caps.length); i++) |
| { |
| if (caps[i].getNamespace().equals(ICapability.PACKAGE_NAMESPACE)) |
| { |
| indexPackageCapability(m_unresolvedPkgIndex, caps[i]); |
| } |
| } |
| } |
| |
| private void removeHost(IModule host) |
| { |
| // We need remove the host's exports from the "resolved" and |
| // "unresolved" package maps, remove its dependencies on fragments |
| // and exporters, and remove it from the module list. |
| m_moduleList.remove(host); |
| |
| // Remove exports from package maps. |
| ICapability[] caps = host.getCapabilities(); |
| for (int i = 0; (caps != null) && (i < caps.length); i++) |
| { |
| if (caps[i].getNamespace().equals(ICapability.PACKAGE_NAMESPACE)) |
| { |
| // Get package name. |
| String pkgName = (String) |
| caps[i].getProperties().get(ICapability.PACKAGE_PROPERTY); |
| // Remove from "unresolved" package map. |
| List capList = (List) m_unresolvedPkgIndex.get(pkgName); |
| if (capList != null) |
| { |
| capList.remove(caps[i]); |
| } |
| |
| // Remove from "resolved" package map. |
| capList = (List) m_resolvedPkgIndex.get(pkgName); |
| if (capList != null) |
| { |
| capList.remove(caps[i]); |
| } |
| } |
| } |
| |
| // Remove the module from the "resolved" map. |
| m_resolvedCapMap.remove(host); |
| |
| // Set fragments to null, which will remove the module from all |
| // of its dependent fragment modules. |
| try |
| { |
| ((ModuleImpl) host).attachFragments(null); |
| } |
| catch (Exception ex) |
| { |
| m_logger.log(Logger.LOG_ERROR, "Error detaching fragments.", ex); |
| } |
| // Set wires to null, which will remove the module from all |
| // of its dependent modules. |
| ((ModuleImpl) host).setWires(null); |
| } |
| |
| private List getMatchingFragments(IModule host) |
| { |
| // Find the host capability for the current host. |
| ICapability[] caps = Util.getCapabilityByNamespace(host, ICapability.HOST_NAMESPACE); |
| ICapability hostCap = (caps.length == 0) ? null : caps[0]; |
| |
| // If we have a host capability, then loop through all fragments trying to |
| // find ones that match. |
| List fragmentList = new ArrayList(); |
| SecurityManager sm = System.getSecurityManager(); |
| if (sm != null) |
| { |
| if (!((BundleProtectionDomain) host.getSecurityContext()).impliesDirect(new BundlePermission(host.getSymbolicName(), BundlePermission.HOST))) |
| { |
| return fragmentList; |
| } |
| } |
| for (Iterator it = m_fragmentMap.entrySet().iterator(); (hostCap != null) && it.hasNext(); ) |
| { |
| Map.Entry entry = (Map.Entry) it.next(); |
| List fragments = (List) entry.getValue(); |
| IModule fragment = null; |
| for (int i = 0; (fragment == null) && (i < fragments.size()); i++) |
| { |
| IModule f = (IModule) fragments.get(i); |
| if (!((BundleImpl) f.getBundle()).isStale() |
| && !((BundleImpl) f.getBundle()).isRemovalPending()) |
| { |
| fragment = f; |
| } |
| } |
| |
| if (fragment == null) |
| { |
| continue; |
| } |
| |
| if (sm != null) |
| { |
| if (!((BundleProtectionDomain) fragment.getSecurityContext()).impliesDirect(new BundlePermission(fragment.getSymbolicName(), BundlePermission.FRAGMENT))) |
| { |
| continue; |
| } |
| } |
| IRequirement hostReq = getFragmentHostRequirement(fragment); |
| |
| // If we have a host requirement, then loop through each host and |
| // see if it matches the host requirement. |
| if ((hostReq != null) && hostReq.isSatisfied(hostCap)) |
| { |
| // Now add the new fragment in bundle ID order. |
| int index = -1; |
| for (int listIdx = 0; |
| (index < 0) && (listIdx < fragmentList.size()); |
| listIdx++) |
| { |
| IModule existing = (IModule) fragmentList.get(listIdx); |
| if (fragment.getBundle().getBundleId() |
| < existing.getBundle().getBundleId()) |
| { |
| index = listIdx; |
| } |
| } |
| fragmentList.add( |
| (index < 0) ? fragmentList.size() : index, fragment); |
| } |
| } |
| |
| return fragmentList; |
| } |
| |
| public synchronized IModule findHost(IModule rootModule) throws ResolveException |
| { |
| IModule newRootModule = rootModule; |
| if (Util.isFragment(rootModule)) |
| { |
| List matchingHosts = getMatchingHosts(rootModule); |
| IModule currentBestHost = null; |
| for (int hostIdx = 0; hostIdx < matchingHosts.size(); hostIdx++) |
| { |
| IModule host = ((ICapability) matchingHosts.get(hostIdx)).getModule(); |
| if (currentBestHost == null) |
| { |
| currentBestHost = host; |
| } |
| else if (currentBestHost.getVersion().compareTo(host.getVersion()) < 0) |
| { |
| currentBestHost = host; |
| } |
| } |
| newRootModule = currentBestHost; |
| |
| if (newRootModule == null) |
| { |
| throw new ResolveException( |
| "Unable to find host.", rootModule, getFragmentHostRequirement(rootModule)); |
| } |
| } |
| |
| return newRootModule; |
| } |
| |
| private IRequirement getFragmentHostRequirement(IModule fragment) |
| { |
| // Find the fragment's host requirement. |
| IRequirement[] reqs = fragment.getRequirements(); |
| IRequirement hostReq = null; |
| for (int reqIdx = 0; (hostReq == null) && (reqIdx < reqs.length); reqIdx++) |
| { |
| if (reqs[reqIdx].getNamespace().equals(ICapability.HOST_NAMESPACE)) |
| { |
| hostReq = reqs[reqIdx]; |
| } |
| } |
| return hostReq; |
| } |
| |
| /** |
| * This method is used for installing system bundle extensions. It actually |
| * refreshes the system bundle module's capabilities in the resolver state |
| * to capture additional capabilities. |
| * @param module The module being refresh, which should always be the system bundle. |
| **/ |
| synchronized void refreshSystemBundleModule(IModule module) |
| { |
| // The system bundle module should always be resolved, so we only need |
| // to update the resolved capability map. |
| ICapability[] caps = module.getCapabilities(); |
| for (int i = 0; (caps != null) && (i < caps.length); i++) |
| { |
| List resolvedCaps = (List) m_resolvedCapMap.get(module); |
| if (resolvedCaps == null) |
| { |
| m_resolvedCapMap.put(module, resolvedCaps = new ArrayList()); |
| } |
| if (!resolvedCaps.contains(caps[i])) |
| { |
| resolvedCaps.add(caps[i]); |
| } |
| |
| // If the capability is a package, then add the exporter module |
| // of the wire to the "resolved" package index and remove it |
| // from the "unresolved" package index. |
| if (caps[i].getNamespace().equals(ICapability.PACKAGE_NAMESPACE)) |
| { |
| // Add to "resolved" package index. |
| indexPackageCapability(m_resolvedPkgIndex, caps[i]); |
| } |
| } |
| } |
| |
| private void dumpPackageIndex(Map pkgIndex) |
| { |
| for (Iterator i = pkgIndex.entrySet().iterator(); i.hasNext(); ) |
| { |
| Map.Entry entry = (Map.Entry) i.next(); |
| List capList = (List) entry.getValue(); |
| if (capList.size() > 0) |
| { |
| if (!((capList.size() == 1) && ((ICapability) capList.get(0)).getModule().getId().equals("0"))) |
| { |
| System.out.println(" " + entry.getKey()); |
| for (int j = 0; j < capList.size(); j++) |
| { |
| System.out.println(" " + ((ICapability) capList.get(j)).getModule()); |
| } |
| } |
| } |
| } |
| } |
| |
| public synchronized IModule[] getModules() |
| { |
| return (IModule[]) m_moduleList.toArray(new IModule[m_moduleList.size()]); |
| } |
| |
| public synchronized void moduleResolved(IModule module) |
| { |
| if (module.isResolved()) |
| { |
| // At this point, we need to remove all of the resolved module's |
| // capabilities from the "unresolved" package map and put them in |
| // in the "resolved" package map, with the exception of any |
| // package exports that are also imported. In that case we need |
| // to make sure that the import actually points to the resolved |
| // module and not another module. If it points to another module |
| // then the capability should be ignored, since the framework |
| // decided to honor the import and discard the export. |
| ICapability[] caps = module.getCapabilities(); |
| |
| // First remove all existing capabilities from the "unresolved" map. |
| for (int capIdx = 0; (caps != null) && (capIdx < caps.length); capIdx++) |
| { |
| if (caps[capIdx].getNamespace().equals(ICapability.PACKAGE_NAMESPACE)) |
| { |
| // Get package name. |
| String pkgName = (String) |
| caps[capIdx].getProperties().get(ICapability.PACKAGE_PROPERTY); |
| // Remove the module's capability for the package. |
| List capList = (List) m_unresolvedPkgIndex.get(pkgName); |
| capList.remove(caps[capIdx]); |
| } |
| } |
| |
| // Next create a copy of the module's capabilities so we can |
| // null out any capabilities that should be ignored. |
| ICapability[] capsCopy = (caps == null) ? null : new ICapability[caps.length]; |
| if (capsCopy != null) |
| { |
| System.arraycopy(caps, 0, capsCopy, 0, caps.length); |
| } |
| // Loop through the module's capabilities to determine which ones |
| // can be ignored by seeing which ones satifies the wire requirements. |
| // TODO: RB - Bug here because a requirement for a package need not overlap the |
| // capability for that package and this assumes it does. This might |
| // require us to introduce the notion of a substitutable capability. |
| IWire[] wires = module.getWires(); |
| for (int capIdx = 0; (capsCopy != null) && (capIdx < capsCopy.length); capIdx++) |
| { |
| // Loop through all wires to see if the current capability |
| // satisfies any of the wire requirements. |
| for (int wireIdx = 0; (wires != null) && (wireIdx < wires.length); wireIdx++) |
| { |
| // If one of the module's capabilities satifies the requirement |
| // for an existing wire, this means the capability was |
| // substituted with another provider by the resolver and |
| // the module's capability was not used. Therefore, we should |
| // null it here so it doesn't get added the list of resolved |
| // capabilities for this module. |
| if (wires[wireIdx].getRequirement().isSatisfied(capsCopy[capIdx])) |
| { |
| capsCopy[capIdx] = null; |
| break; |
| } |
| } |
| } |
| |
| // Now loop through all capabilities and add them to the "resolved" |
| // capability and package index maps, ignoring any that were nulled out. |
| for (int capIdx = 0; (capsCopy != null) && (capIdx < capsCopy.length); capIdx++) |
| { |
| if (capsCopy[capIdx] != null) |
| { |
| List resolvedCaps = (List) m_resolvedCapMap.get(module); |
| if (resolvedCaps == null) |
| { |
| m_resolvedCapMap.put(module, resolvedCaps = new ArrayList()); |
| } |
| if (!resolvedCaps.contains(capsCopy[capIdx])) |
| { |
| resolvedCaps.add(capsCopy[capIdx]); |
| } |
| |
| // If the capability is a package, then add the exporter module |
| // of the wire to the "resolved" package index and remove it |
| // from the "unresolved" package index. |
| if (capsCopy[capIdx].getNamespace().equals(ICapability.PACKAGE_NAMESPACE)) |
| { |
| // Add to "resolved" package index. |
| indexPackageCapability(m_resolvedPkgIndex, capsCopy[capIdx]); |
| } |
| } |
| } |
| } |
| |
| //System.out.println("UNRESOLVED PACKAGES:"); |
| //dumpPackageIndex(m_unresolvedPkgIndex); |
| //System.out.println("RESOLVED PACKAGES:"); |
| //dumpPackageIndex(m_resolvedPkgIndex); |
| } |
| |
| public synchronized List getResolvedCandidates(IRequirement req, IModule reqModule) |
| { |
| // Synchronized on the module manager to make sure that no |
| // modules are added, removed, or resolved. |
| List candidates = new ArrayList(); |
| if (req.getNamespace().equals(ICapability.PACKAGE_NAMESPACE) |
| && (((Requirement) req).getTargetName() != null)) |
| { |
| String pkgName = ((Requirement) req).getTargetName(); |
| List capList = (List) m_resolvedPkgIndex.get(pkgName); |
| |
| for (int capIdx = 0; (capList != null) && (capIdx < capList.size()); capIdx++) |
| { |
| ICapability cap = (ICapability) capList.get(capIdx); |
| if (req.isSatisfied(cap)) |
| { |
| if (System.getSecurityManager() != null) |
| { |
| if (reqModule != ((ICapability) capList.get(capIdx)).getModule()) |
| { |
| if ((!((BundleProtectionDomain)((ICapability) |
| capList.get(capIdx)).getModule().getSecurityContext()).impliesDirect( |
| new PackagePermission(((Requirement) req).getTargetName(), PackagePermission.EXPORTONLY))) || |
| !((reqModule == null) || |
| ((BundleProtectionDomain) reqModule.getSecurityContext()).impliesDirect( |
| new PackagePermission(((Requirement) req).getTargetName(), ((ICapability) |
| capList.get(capIdx)).getModule().getBundle(),PackagePermission.IMPORT)) |
| )) |
| { |
| continue; |
| } |
| } |
| } |
| candidates.add(cap); |
| } |
| } |
| } |
| else |
| { |
| Iterator i = m_resolvedCapMap.entrySet().iterator(); |
| while (i.hasNext()) |
| { |
| Map.Entry entry = (Map.Entry) i.next(); |
| IModule module = (IModule) entry.getKey(); |
| List caps = (List) entry.getValue(); |
| for (int capIdx = 0; (caps != null) && (capIdx < caps.size()); capIdx++) |
| { |
| ICapability cap = (ICapability) caps.get(capIdx); |
| if (req.isSatisfied(cap)) |
| { |
| if (System.getSecurityManager() != null) |
| { |
| if (req.getNamespace().equals(ICapability.PACKAGE_NAMESPACE) && ( |
| !((BundleProtectionDomain) cap.getModule().getSecurityContext()).impliesDirect( |
| new PackagePermission((String) cap.getProperties().get(ICapability.PACKAGE_PROPERTY), PackagePermission.EXPORTONLY)) || |
| !((reqModule == null) || |
| ((BundleProtectionDomain) reqModule.getSecurityContext()).impliesDirect( |
| new PackagePermission((String) cap.getProperties().get(ICapability.PACKAGE_PROPERTY), cap.getModule().getBundle(),PackagePermission.IMPORT)) |
| ))) |
| { |
| if (reqModule != cap.getModule()) |
| { |
| continue; |
| } |
| } |
| if (req.getNamespace().equals(ICapability.MODULE_NAMESPACE) && ( |
| !((BundleProtectionDomain) cap.getModule().getSecurityContext()).impliesDirect( |
| new BundlePermission(cap.getModule().getSymbolicName(), BundlePermission.PROVIDE)) || |
| !((reqModule == null) || |
| ((BundleProtectionDomain) reqModule.getSecurityContext()).impliesDirect( |
| new BundlePermission(reqModule.getSymbolicName(), BundlePermission.REQUIRE)) |
| ))) |
| { |
| continue; |
| } |
| } |
| candidates.add(cap); |
| } |
| } |
| } |
| } |
| Collections.sort(candidates); |
| return candidates; |
| } |
| |
| public synchronized List getUnresolvedCandidates(IRequirement req, IModule reqModule) |
| { |
| // Get all matching unresolved capabilities. |
| List candidates = new ArrayList(); |
| if (req.getNamespace().equals(ICapability.PACKAGE_NAMESPACE) && |
| (((Requirement) req).getTargetName() != null)) |
| { |
| List capList = (List) m_unresolvedPkgIndex.get(((Requirement) req).getTargetName()); |
| for (int capIdx = 0; (capList != null) && (capIdx < capList.size()); capIdx++) |
| { |
| // If compatible and it is not currently resolved, then add |
| // the unresolved candidate to the list. |
| if (req.isSatisfied((ICapability) capList.get(capIdx))) |
| { |
| if (System.getSecurityManager() != null) |
| { |
| if (reqModule != ((ICapability) capList.get(capIdx)).getModule()) |
| { |
| if (!((BundleProtectionDomain)((ICapability) |
| capList.get(capIdx)).getModule().getSecurityContext()).impliesDirect( |
| new PackagePermission(((Requirement) req).getTargetName(), PackagePermission.EXPORTONLY)) || |
| !((reqModule == null) || |
| ((BundleProtectionDomain) reqModule.getSecurityContext()).impliesDirect( |
| new PackagePermission(((Requirement) req).getTargetName(), ((ICapability) |
| capList.get(capIdx)).getModule().getBundle(),PackagePermission.IMPORT)) |
| )) |
| { |
| continue; |
| } |
| } |
| } |
| candidates.add(capList.get(capIdx)); |
| } |
| } |
| } |
| else |
| { |
| IModule[] modules = getModules(); |
| for (int modIdx = 0; (modules != null) && (modIdx < modules.length); modIdx++) |
| { |
| // Get the module's export package for the target package. |
| ICapability cap = Util.getSatisfyingCapability(modules[modIdx], req); |
| // If compatible and it is not currently resolved, then add |
| // the unresolved candidate to the list. |
| if ((cap != null) && !modules[modIdx].isResolved()) |
| { |
| if (System.getSecurityManager() != null) |
| { |
| if (req.getNamespace().equals(ICapability.PACKAGE_NAMESPACE) && ( |
| !((BundleProtectionDomain) cap.getModule().getSecurityContext()).impliesDirect( |
| new PackagePermission((String) cap.getProperties().get(ICapability.PACKAGE_PROPERTY), PackagePermission.EXPORTONLY)) || |
| !((reqModule == null) || |
| ((BundleProtectionDomain) reqModule.getSecurityContext()).impliesDirect( |
| new PackagePermission((String) cap.getProperties().get(ICapability.PACKAGE_PROPERTY), cap.getModule().getBundle(),PackagePermission.IMPORT)) |
| ))) |
| { |
| if (reqModule != cap.getModule()) |
| { |
| continue; |
| } |
| } |
| if (req.getNamespace().equals(ICapability.MODULE_NAMESPACE) && ( |
| !((BundleProtectionDomain) cap.getModule().getSecurityContext()).impliesDirect( |
| new BundlePermission(cap.getModule().getSymbolicName(), BundlePermission.PROVIDE)) || |
| !((reqModule == null) || |
| ((BundleProtectionDomain) reqModule.getSecurityContext()).impliesDirect( |
| new BundlePermission(reqModule.getSymbolicName(), BundlePermission.REQUIRE)) |
| ))) |
| { |
| continue; |
| } |
| } |
| candidates.add(cap); |
| } |
| } |
| } |
| |
| // Create list of compatible providers. |
| Collections.sort(candidates); |
| return candidates; |
| } |
| |
| // |
| // Utility methods. |
| // |
| |
| private void indexPackageCapability(Map map, ICapability capability) |
| { |
| if (capability.getNamespace().equals(ICapability.PACKAGE_NAMESPACE)) |
| { |
| String pkgName = (String) |
| capability.getProperties().get(ICapability.PACKAGE_PROPERTY); |
| List capList = (List) map.get(pkgName); |
| |
| // We want to add the capability into the list of exporters |
| // in sorted order (descending version and ascending bundle |
| // identifier). Insert using a simple binary search algorithm. |
| if (capList == null) |
| { |
| capList = new ArrayList(); |
| capList.add(capability); |
| } |
| else |
| { |
| Version version = (Version) |
| capability.getProperties().get(ICapability.VERSION_PROPERTY); |
| Version middleVersion = null; |
| int top = 0, bottom = capList.size() - 1, middle = 0; |
| while (top <= bottom) |
| { |
| middle = (bottom - top) / 2 + top; |
| middleVersion = (Version) |
| ((ICapability) capList.get(middle)) |
| .getProperties().get(ICapability.VERSION_PROPERTY); |
| // Sort in reverse version order. |
| int cmp = middleVersion.compareTo(version); |
| if (cmp < 0) |
| { |
| bottom = middle - 1; |
| } |
| else if (cmp == 0) |
| { |
| // Sort further by ascending bundle ID. |
| long middleId = ((ICapability) capList.get(middle)) |
| .getModule().getBundle().getBundleId(); |
| long exportId = capability.getModule().getBundle().getBundleId(); |
| if (middleId < exportId) |
| { |
| top = middle + 1; |
| } |
| else |
| { |
| bottom = middle - 1; |
| } |
| } |
| else |
| { |
| top = middle + 1; |
| } |
| } |
| |
| // Ignore duplicates. |
| if ((top >= capList.size()) || (capList.get(top) != capability)) |
| { |
| capList.add(top, capability); |
| } |
| } |
| |
| map.put(pkgName, capList); |
| } |
| } |
| |
| private IModule indexFragment(Map map, IModule module) |
| { |
| List modules = (List) map.get(module.getSymbolicName()); |
| |
| // We want to add the fragment into the list of matching |
| // fragments in sorted order (descending version and |
| // ascending bundle identifier). Insert using a simple |
| // binary search algorithm. |
| if (modules == null) |
| { |
| modules = new ArrayList(); |
| modules.add(module); |
| } |
| else |
| { |
| Version version = module.getVersion(); |
| Version middleVersion = null; |
| int top = 0, bottom = modules.size() - 1, middle = 0; |
| while (top <= bottom) |
| { |
| middle = (bottom - top) / 2 + top; |
| middleVersion = ((IModule) modules.get(middle)).getVersion(); |
| // Sort in reverse version order. |
| int cmp = middleVersion.compareTo(version); |
| if (cmp < 0) |
| { |
| bottom = middle - 1; |
| } |
| else if (cmp == 0) |
| { |
| // Sort further by ascending bundle ID. |
| long middleId = ((IModule) modules.get(middle)).getBundle().getBundleId(); |
| long exportId = module.getBundle().getBundleId(); |
| if (middleId < exportId) |
| { |
| top = middle + 1; |
| } |
| else |
| { |
| bottom = middle - 1; |
| } |
| } |
| else |
| { |
| top = middle + 1; |
| } |
| } |
| |
| // Ignore duplicates. |
| if ((top >= modules.size()) || (modules.get(top) != module)) |
| { |
| modules.add(top, module); |
| } |
| } |
| |
| map.put(module.getSymbolicName(), modules); |
| |
| return (IModule) modules.get(0); |
| } |
| } |