Modified fragment support to allow exact matches in overlapping
requirements. (FELIX-29)
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@771771 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/FelixResolverState.java b/framework/src/main/java/org/apache/felix/framework/FelixResolverState.java
index af88da5..bdbff4a 100644
--- a/framework/src/main/java/org/apache/felix/framework/FelixResolverState.java
+++ b/framework/src/main/java/org/apache/felix/framework/FelixResolverState.java
@@ -28,12 +28,13 @@
import org.apache.felix.framework.searchpolicy.Resolver;
import org.apache.felix.framework.searchpolicy.PackageSource;
import org.apache.felix.framework.util.Util;
+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.Constants;
import org.osgi.framework.PackagePermission;
import org.osgi.framework.Version;
@@ -95,7 +96,7 @@
// Fragments are grouped by symbolic name and descending version.
// Attach the first matching fragment from each group if possible,
// since only one version of a given fragment may attach to a host.
- List list = new ArrayList();
+ List fragmentList = new ArrayList();
for (Iterator it = m_fragmentMap.entrySet().iterator(); it.hasNext(); )
{
Map.Entry entry = (Map.Entry) it.next();
@@ -113,105 +114,31 @@
// Fragments are attached in bundle ID order.
int index = -1;
for (int listIdx = 0;
- (index < 0) && (listIdx < list.size());
+ (index < 0) && (listIdx < fragmentList.size());
listIdx++)
{
if (fragments[fragIdx].getBundle().getBundleId()
- < ((IModule) list.get(listIdx)).getBundle().getBundleId())
+ < ((IModule) fragmentList.get(listIdx)).getBundle().getBundleId())
{
index = listIdx;
}
}
- list.add((index < 0) ? list.size() : index, fragments[fragIdx]);
+ fragmentList.add(
+ (index < 0) ? fragmentList.size() : index,
+ fragments[fragIdx]);
break done;
}
}
}
}
- if (list.size() > 0)
+ if (fragmentList.size() > 0)
{
- // 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.
- 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(), host);
- }
- else if (reqs[reqIdx].getNamespace().equals(ICapability.MODULE_NAMESPACE))
- {
- rbMerged.put(((Requirement) reqs[reqIdx]).getTargetName(), host);
- }
- }
- // Loop through all fragments verifying they do no conflict,
- // adding those packages that do not conflict and removing
- // the fragment if it does conflict.
- for (Iterator it = list.iterator(); it.hasNext(); )
- {
- IModule fragment = (IModule) it.next();
- reqs = fragment.getRequirements();
- List ipFragment = new ArrayList();
- List rbFragment = new ArrayList();
- boolean conflicting = false;
- for (int reqIdx = 0;
- !conflicting && (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;
- List fragmentReqList = (reqs[reqIdx].getNamespace().equals(ICapability.PACKAGE_NAMESPACE))
- ? ipFragment : rbFragment;
- if (mergedReqMap.get(targetName) == null)
- {
- fragmentReqList.add(targetName);
- }
- else
- {
- conflicting = true;
- }
- if (conflicting)
- {
- 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) mergedReqMap.get(targetName)).getSymbolicName());
- }
- }
- }
-
- // Merge non-conflicting requirements into overall set
- // of requirements and continue checking for conflicts
- // with the next fragment.
- for (int pkgIdx = 0; pkgIdx < ipFragment.size(); pkgIdx++)
- {
- ipMerged.put(ipFragment.get(pkgIdx), fragment);
- }
- for (int pkgIdx = 0; pkgIdx < rbFragment.size(); pkgIdx++)
- {
- rbMerged.put(rbFragment.get(pkgIdx), fragment);
- }
- }
+ // Check if fragment conflicts with existing metadata.
+ checkForConflicts(host, fragmentList);
// Attach the fragments to the host.
- IModule[] fragments = (IModule[]) list.toArray(new IModule[list.size()]);
+ IModule[] fragments = (IModule[]) fragmentList.toArray(new IModule[fragmentList.size()]);
((ModuleImpl) host).attachFragments(fragments);
for (int fragIdx = 0; fragIdx < fragments.length; fragIdx++)
@@ -242,6 +169,175 @@
return newRootModule;
}
+ private void checkForConflicts(IModule host, List fragmentList)
+ {
+ // 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 all fragments verifying they do no conflict,
+ // adding those packages that do not conflict and removing
+ // the fragment if it does conflict.
+ for (Iterator it = fragmentList.iterator(); it.hasNext(); )
+ {
+ IModule fragment = (IModule) it.next();
+ reqs = fragment.getRequirements();
+ List ipFragment = new ArrayList();
+ List rbFragment = new ArrayList();
+ boolean conflicting = false;
+ for (int reqIdx = 0;
+ !conflicting && (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;
+ List fragmentReqList = (reqs[reqIdx].getNamespace().equals(ICapability.PACKAGE_NAMESPACE))
+ ? ipFragment : rbFragment;
+ Object[] existing = (Object[]) mergedReqMap.get(targetName);
+ if (existing == null)
+ {
+ fragmentReqList.add(targetName);
+ }
+ else if (isRequirementConflicting(
+ (Requirement) existing[REQ_IDX], (Requirement) reqs[reqIdx]))
+ {
+ conflicting = true;
+ }
+ if (conflicting)
+ {
+ 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());
+ }
+ }
+ }
+
+ // Merge non-conflicting requirements into overall set
+ // of requirements and continue checking for conflicts
+ // with the next fragment.
+ for (int pkgIdx = 0; pkgIdx < ipFragment.size(); pkgIdx++)
+ {
+ ipMerged.put(ipFragment.get(pkgIdx), fragment);
+ }
+ for (int pkgIdx = 0; pkgIdx < rbFragment.size(); pkgIdx++)
+ {
+ rbMerged.put(rbFragment.get(pkgIdx), fragment);
+ }
+ }
+ }
+
+ 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 target version range is not the same, then they conflict.
+ if (!existing.getTargetVersionRange().equals(additional.getTargetVersionRange()))
+ {
+ return true;
+ }
+ // If optionality is not the same, then they conflict.
+ 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();
+ // If different number of directives, then they conflict.
+ if (exDirs.length != addDirs.length)
+ {
+ return true;
+ }
+ // Put directives 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 directive values do not match, then they conflict.
+ for (int i = 0; i < addDirs.length; i++)
+ {
+ 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();
+ // If different number of attributes, then they conflict.
+ if (exAttrs.length != addAttrs.length)
+ {
+ return true;
+ }
+ // 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++)
+ {
+ 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;
+ }
+
private void addFragment(IModule module)
{
indexFragment(m_fragmentMap, module);
@@ -431,21 +527,6 @@
return (IModule[]) m_moduleList.toArray(new IModule[m_moduleList.size()]);
}
-// TODO: FRAGMENT - Not very efficient.
- private static Version getBundleVersion(IModule module)
- {
- ICapability[] caps = module.getCapabilities();
- for (int capIdx = 0; (caps != null) && (capIdx < caps.length); capIdx++)
- {
- if (caps[capIdx].getNamespace().equals(ICapability.MODULE_NAMESPACE))
- {
- return (Version)
- caps[capIdx].getProperties().get(Constants.BUNDLE_VERSION_ATTRIBUTE);
- }
- }
- return Version.emptyVersion;
- }
-
public synchronized void moduleResolved(IModule module)
{
if (module.isResolved())
@@ -741,13 +822,13 @@
}
else
{
- Version version = getBundleVersion(module);
+ Version version = module.getVersion();
Version middleVersion = null;
int top = 0, bottom = modules.length - 1, middle = 0;
while (top <= bottom)
{
middle = (bottom - top) / 2 + top;
- middleVersion = getBundleVersion(modules[middle]);
+ middleVersion = modules[middle].getVersion();
// Sort in reverse version order.
int cmp = middleVersion.compareTo(version);
if (cmp < 0)
diff --git a/framework/src/main/java/org/apache/felix/framework/util/manifestparser/ManifestParser.java b/framework/src/main/java/org/apache/felix/framework/util/manifestparser/ManifestParser.java
index b7a336e..94146c8 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/manifestparser/ManifestParser.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/manifestparser/ManifestParser.java
@@ -95,16 +95,15 @@
m_bundleSymbolicName = (String)
moduleCap.getProperties().get(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE);
- // Add the module capability to all capabilities.
- // TODO: FRAGMENT - Fragment bundles cannot be required, so we
- // should not add this capability, but for now we are using
- // it to get the symbolic name.
- capList.add(moduleCap);
- // Add a host capability if the bundle is not a fragment. A host
- // capability is the same as a module capability, but with a
- // different capability namespace.
+ // Add a module capability and a host capability to all
+ // non-fragment bundles. A host capability is the same
+ // as a module capability, but with a different capability
+ // namespace. Module capabilities resolve required-bundle
+ // dependencies, while host capabilities resolve fragment-host
+ // dependencies.
if (headerMap.get(Constants.FRAGMENT_HOST) == null)
{
+ capList.add(moduleCap);
capList.add(new Capability(
ICapability.HOST_NAMESPACE, null,
((Capability) moduleCap).getAttributes()));
diff --git a/framework/src/main/java/org/apache/felix/framework/util/manifestparser/Requirement.java b/framework/src/main/java/org/apache/felix/framework/util/manifestparser/Requirement.java
index c8b97e6..f70f3ca 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/manifestparser/Requirement.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/manifestparser/Requirement.java
@@ -376,4 +376,4 @@
{
return getNamespace() + "; " + getFilter().toString();
}
-}
+}
\ No newline at end of file