Move singleton selection back into the resolver state, in preparation
for implementing resolver hook singleton selection. (FELIX-2986)
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1165690 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/StatefulResolver.java b/framework/src/main/java/org/apache/felix/framework/StatefulResolver.java
index 9c8daa1..deb49e6 100644
--- a/framework/src/main/java/org/apache/felix/framework/StatefulResolver.java
+++ b/framework/src/main/java/org/apache/felix/framework/StatefulResolver.java
@@ -50,6 +50,7 @@
import org.osgi.framework.Constants;
import org.osgi.framework.PackagePermission;
import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
import org.osgi.framework.hooks.resolver.ResolverHook;
import org.osgi.framework.hooks.resolver.ResolverHookFactory;
import org.osgi.framework.wiring.BundleCapability;
@@ -126,19 +127,29 @@
// Extensions are resolved differently.
for (Iterator<BundleRevision> it = mandatoryRevisions.iterator(); it.hasNext(); )
{
- BundleImpl bundle = (BundleImpl) it.next().getBundle();
+ BundleRevision br = it.next();
+ BundleImpl bundle = (BundleImpl) br.getBundle();
if (bundle.isExtension())
{
it.remove();
}
+ else if (Util.isSingleton(br) && !m_resolverState.isSelectedSingleton(br))
+ {
+ throw new ResolveException("Singleton conflict.", br, null);
+ }
}
for (Iterator<BundleRevision> it = optionalRevisions.iterator(); it.hasNext(); )
{
- BundleImpl bundle = (BundleImpl) it.next().getBundle();
+ BundleRevision br = it.next();
+ BundleImpl bundle = (BundleImpl) br.getBundle();
if (bundle.isExtension())
{
it.remove();
}
+ else if (Util.isSingleton(br) && !m_resolverState.isSelectedSingleton(br))
+ {
+ it.remove();
+ }
}
// Get resolver hook factories.
@@ -918,6 +929,10 @@
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;
+ // Maps singleton symbolic names to selected bundle revision.
+ private final Map<String, BundleRevision> m_selectedSingleton;
// Execution environment.
private final String m_fwkExecEnvStr;
// Parsed framework environments
@@ -938,6 +953,8 @@
m_revisions = new HashSet<BundleRevision>();
m_fragments = new HashSet<BundleRevision>();
m_capSets = new HashMap<String, CapabilitySet>();
+ m_singletons = new HashMap<String, List<BundleRevision>>();
+ m_selectedSingleton = new HashMap<String, BundleRevision>();
m_fwkExecEnvStr = (fwkExecEnvStr != null) ? fwkExecEnvStr.trim() : null;
m_fwkExecEnvSet = parseExecutionEnvironments(fwkExecEnvStr);
@@ -975,10 +992,52 @@
// after it has been resolved.
removeRevision(br);
- // Add the revision and index its declared or resolved
- // capabilities depending on whether it is resolved or
- // not.
- m_revisions.add(br);
+ if (Util.isSingleton(br))
+ {
+ // Index the new singleton.
+ indexSingleton(m_singletons, br);
+ // Get the currently selected singleton.
+ BundleRevision selected = m_selectedSingleton.get(br.getSymbolicName());
+ // Get the highest singleton version.
+ BundleRevision highest = m_singletons.get(br.getSymbolicName()).get(0);
+ // Select the highest version if not already selected or resolved.
+ if ((selected == null)
+ || ((selected.getWiring() == null) && (selected != highest)))
+ {
+ m_selectedSingleton.put(br.getSymbolicName(), highest);
+ if (selected != null)
+ {
+ deindexCapabilities(selected);
+ m_revisions.remove(selected);
+ if (Util.isFragment(selected))
+ {
+ m_fragments.remove(selected);
+ }
+ }
+ br = highest;
+ }
+ else if (selected != null)
+ {
+ // Since the newly added singleton was not selected, null
+ // it out so that it is ignored.
+ br = null;
+ }
+ }
+
+ // Add the revision and index its capabilities.
+ if (br != null)
+ {
+ m_revisions.add(br);
+ if (Util.isFragment(br))
+ {
+ m_fragments.add(br);
+ }
+ indexCapabilities(br);
+ }
+ }
+
+ private synchronized void indexCapabilities(BundleRevision br)
+ {
List<BundleCapability> caps = (br.getWiring() == null)
? br.getDeclaredCapabilities(null)
: br.getWiring().getCapabilities(null);
@@ -989,6 +1048,8 @@
// 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());
@@ -1001,10 +1062,24 @@
}
}
}
+ }
- if (Util.isFragment(br))
+ 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)
{
- m_fragments.add(br);
+ for (BundleCapability cap : caps)
+ {
+ CapabilitySet capSet = m_capSets.get(cap.getNamespace());
+ if (capSet != null)
+ {
+ capSet.removeCapability(cap);
+ }
+ }
}
}
@@ -1012,25 +1087,45 @@
{
if (m_revisions.remove(br))
{
- // We only need be concerned with declared capabilities here,
- // because resolved capabilities will be a subset.
- 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);
- }
- }
- }
+ deindexCapabilities(br);
if (Util.isFragment(br))
{
m_fragments.remove(br);
}
+
+ // If this module is a singleton, then remove it from the
+ // singleton map and potentially select a new singleton.
+ List<BundleRevision> revisions = m_singletons.get(br.getSymbolicName());
+ if (revisions != null)
+ {
+ BundleRevision selected = m_selectedSingleton.get(br.getSymbolicName());
+ revisions.remove(br);
+ if (revisions.isEmpty())
+ {
+ m_singletons.remove(br.getSymbolicName());
+ }
+
+ // If this was the selected singleton, then we have to
+ // select another.
+ if (selected == br)
+ {
+ if (!revisions.isEmpty())
+ {
+ selected = revisions.get(0);
+ m_selectedSingleton.put(br.getSymbolicName(), selected);
+ if (Util.isFragment(selected))
+ {
+ m_fragments.add(selected);
+ }
+ indexCapabilities(selected);
+ }
+ else
+ {
+ m_selectedSingleton.remove(br.getSymbolicName());
+ }
+ }
+ }
}
}
@@ -1039,6 +1134,11 @@
return new HashSet(m_fragments);
}
+ synchronized boolean isSelectedSingleton(BundleRevision br)
+ {
+ return (m_selectedSingleton.get(br.getSymbolicName()) == br);
+ }
+
//
// ResolverState methods.
//
@@ -1267,4 +1367,62 @@
}
return newSet;
}
+
+ private static void indexSingleton(
+ Map<String, List<BundleRevision>> singletons, BundleRevision br)
+ {
+ List<BundleRevision> revisions = singletons.get(br.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 (revisions == null)
+ {
+ revisions = new ArrayList<BundleRevision>();
+ revisions.add(br);
+ }
+ else
+ {
+ Version version = br.getVersion();
+ int top = 0, bottom = revisions.size() - 1;
+ while (top <= bottom)
+ {
+ int middle = (bottom - top) / 2 + top;
+ Version middleVersion = revisions.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 = revisions.get(middle).getBundle().getBundleId();
+ long exportId = br.getBundle().getBundleId();
+ if (middleId < exportId)
+ {
+ top = middle + 1;
+ }
+ else
+ {
+ bottom = middle - 1;
+ }
+ }
+ else
+ {
+ top = middle + 1;
+ }
+ }
+
+ // Ignore duplicates.
+ if ((top >= revisions.size()) || (revisions.get(top) != br))
+ {
+ revisions.add(top, br);
+ }
+ }
+
+ singletons.put(br.getSymbolicName(), revisions);
+ }
}
\ No newline at end of file
diff --git a/framework/src/main/java/org/apache/felix/framework/resolver/Candidates.java b/framework/src/main/java/org/apache/felix/framework/resolver/Candidates.java
index 4e4ccda..f26181b 100644
--- a/framework/src/main/java/org/apache/felix/framework/resolver/Candidates.java
+++ b/framework/src/main/java/org/apache/felix/framework/resolver/Candidates.java
@@ -612,7 +612,7 @@
* @throws ResolveException if the removal of any unselected fragments result
* in the root module being unable to resolve.
**/
- public void prepare(List<BundleRevision> existingSingletons)
+ public void prepare()
{
boolean init = false;
@@ -622,51 +622,6 @@
init = true;
}
- final Map<String, BundleRevision> singletons = new HashMap<String, BundleRevision>();
-
- for (Iterator<BundleRevision> it = m_involvedRevisions.iterator(); it.hasNext(); )
- {
- BundleRevision br = it.next();
- if (isSingleton(br))
- {
- if (!init)
- {
- populateDependents();
- init = true;
- }
-
- // See if there is an existing singleton for the
- // revision's symbolic name.
- BundleRevision singleton = singletons.get(br.getSymbolicName());
- // If there is no existing singleton or this revision is
- // a resolved singleton or this revision has a higher version
- // and the existing singleton is not resolved, then select
- // this revision as the singleton.
- if ((singleton == null)
- || (br.getWiring() != null)
- || ((br.getVersion().compareTo(singleton.getVersion()) > 0)
- && (singleton.getWiring() == null)))
- {
- singletons.put(br.getSymbolicName(), br);
- // Remove the singleton revision from the candidates
- // if it wasn't selected.
- if (singleton != null)
- {
- removeRevision(
- singleton,
- new ResolveException(
- "Conflict with another singleton.", singleton, null));
- }
- }
- else
- {
- removeRevision(br,
- new ResolveException(
- "Conflict with another singleton.", br, null));
- }
- }
- }
-
// If the root is a singleton, then prefer it over any other singleton.
// TODO: OSGi R4.3/SINGLETON - How do we prefer the root as a singleton?
/*
@@ -690,22 +645,6 @@
}
}
*/
-
- // Make sure selected singletons do not conflict with existing
- // singletons passed into this method.
- for (int i = 0; (existingSingletons != null) && (i < existingSingletons.size()); i++)
- {
- BundleRevision existing = existingSingletons.get(i);
- BundleRevision singleton = singletons.get(existing.getSymbolicName());
- if ((singleton != null) && (singleton != existing))
- {
- singletons.remove(singleton.getSymbolicName());
- removeRevision(singleton,
- new ResolveException(
- "Conflict with another singleton.", singleton, null));
- }
- }
-
// This method performs the following steps:
// 1. Select the fragments to attach to a given host.
// 2. Wrap hosts and attach fragments.
diff --git a/framework/src/main/java/org/apache/felix/framework/resolver/ResolverImpl.java b/framework/src/main/java/org/apache/felix/framework/resolver/ResolverImpl.java
index 7d561da..2588613 100644
--- a/framework/src/main/java/org/apache/felix/framework/resolver/ResolverImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/resolver/ResolverImpl.java
@@ -117,7 +117,7 @@
}
// Merge any fragments into hosts.
- allCandidates.prepare(getResolvedSingletons(state));
+ allCandidates.prepare();
// Create a combined list of populated revisions; for
// optional revisions. We do not need to consider ondemand
@@ -303,7 +303,7 @@
}
// Merge any fragments into hosts.
- allCandidates.prepare(getResolvedSingletons(state));
+ allCandidates.prepare();
// Record the initial candidate permutation.
m_usesPermutations.add(allCandidates);
@@ -393,25 +393,6 @@
return null;
}
- private static List<BundleRevision> getResolvedSingletons(ResolverState state)
- {
- BundleRequirementImpl req = new BundleRequirementImpl(
- null,
- BundleCapabilityImpl.SINGLETON_NAMESPACE,
- Collections.EMPTY_MAP,
- Collections.EMPTY_MAP);
- SortedSet<BundleCapability> caps = state.getCandidates(req, true);
- List<BundleRevision> singletons = new ArrayList();
- for (BundleCapability cap : caps)
- {
- if (cap.getRevision().getWiring() != null)
- {
- singletons.add(cap.getRevision());
- }
- }
- return singletons;
- }
-
private static Candidates getDynamicImportCandidates(
ResolverState state, BundleRevision revision, String pkgName)
{
diff --git a/framework/src/main/java/org/apache/felix/framework/util/Util.java b/framework/src/main/java/org/apache/felix/framework/util/Util.java
index f9a0719..41e4305 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/Util.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/Util.java
@@ -26,6 +26,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Properties;
import org.apache.felix.framework.Logger;
import org.apache.felix.framework.capabilityset.CapabilitySet;
@@ -610,6 +611,35 @@
}
/**
+ * Returns true if the specified bundle revision is a singleton
+ * (i.e., directive singleton:=true in Bundle-SymbolicName).
+ *
+ * @param revision the revision to check for singleton status.
+ * @return true if the revision is a singleton, false otherwise.
+ **/
+ public static boolean isSingleton(BundleRevision revision)
+ {
+ final List<BundleCapability> caps = revision.getDeclaredCapabilities(null);
+ for (BundleCapability cap : caps)
+ {
+ // Find the bundle capability and check its directives.
+ if (cap.getNamespace().equals(BundleRevision.BUNDLE_NAMESPACE))
+ {
+ for (Entry<String, String> entry : cap.getDirectives().entrySet())
+ {
+ if (entry.getKey().equalsIgnoreCase(Constants.SINGLETON_DIRECTIVE))
+ {
+ return Boolean.valueOf((String) entry.getValue());
+ }
+ }
+ // Can only have one bundle capability, so break.
+ break;
+ }
+ }
+ return false;
+ }
+
+ /**
* Checks if the provided module definition declares a fragment host.
*
* @param module the module to check
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 e0639cb..4ebd09f 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
@@ -131,26 +131,6 @@
hostAttrs));
}
}
-
- // Add a singleton capability if the bundle is a singleton.
- // This is sort of a hack, but we need this for the resolver
- // to be able to resolve singletons. It is not possible to
- // attach this information to the bundle or host capabilities
- // because fragments don't have those capabilities, but fragments
- // can be singletons too.
-// TODO: OSGi R4.4 - Eventually we will have an identity capability from OBR
-// that we can use instead of this custom singleton capability.
- if (isSingleton(bundleCap))
- {
- Map<String, Object> singletonAttrs =
- new HashMap<String, Object>(bundleCap.getAttributes());
- Object value = singletonAttrs.remove(BundleRevision.BUNDLE_NAMESPACE);
- singletonAttrs.put(BundleCapabilityImpl.SINGLETON_NAMESPACE, value);
- capList.add(new BundleCapabilityImpl(
- owner, BundleCapabilityImpl.SINGLETON_NAMESPACE,
- Collections.EMPTY_MAP,
- singletonAttrs));
- }
}
// Verify that bundle symbolic name is specified.
@@ -288,19 +268,6 @@
m_isExtension = checkExtensionBundle(headerMap);
}
- private static boolean isSingleton(BundleCapabilityImpl cap)
- {
- if (cap.getNamespace().equals(BundleRevision.BUNDLE_NAMESPACE))
- {
- String value = cap.getDirectives().get(Constants.SINGLETON_DIRECTIVE);
- if ((value != null) && Boolean.valueOf(value))
- {
- return true;
- }
- }
- return false;
- }
-
private static List<ParsedHeaderClause> normalizeImportClauses(
Logger logger, List<ParsedHeaderClause> clauses, String mv)
throws BundleException
diff --git a/framework/src/main/java/org/apache/felix/framework/wiring/BundleCapabilityImpl.java b/framework/src/main/java/org/apache/felix/framework/wiring/BundleCapabilityImpl.java
index 4114ab0..ba36535 100644
--- a/framework/src/main/java/org/apache/felix/framework/wiring/BundleCapabilityImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/wiring/BundleCapabilityImpl.java
@@ -34,8 +34,6 @@
public class BundleCapabilityImpl implements BundleCapability
{
- public static final String SINGLETON_NAMESPACE = "singleton";
-
public static final String VERSION_ATTR = "version";
private final BundleRevision m_revision;