Move singleton handling into the resolver. (FELIX-2859)
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1078140 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/Felix.java b/framework/src/main/java/org/apache/felix/framework/Felix.java
index 542a0fa..6d832dc 100644
--- a/framework/src/main/java/org/apache/felix/framework/Felix.java
+++ b/framework/src/main/java/org/apache/felix/framework/Felix.java
@@ -3126,7 +3126,7 @@
List<Attribute> attrs = new ArrayList<Attribute>(1);
attrs.add(new Attribute(Capability.PACKAGE_ATTR, pkgName, false));
Requirement req = new RequirementImpl(null, Capability.PACKAGE_NAMESPACE, dirs, attrs);
- Set<Capability> exports = m_resolver.getCandidates(null, req, false);
+ Set<Capability> exports = m_resolver.getCandidates(req, false);
// We only want resolved capabilities.
for (Iterator<Capability> it = exports.iterator(); it.hasNext(); )
@@ -3273,7 +3273,7 @@
attrs.add(new Attribute(Capability.PACKAGE_ATTR, pkgName, false));
Requirement req =
new RequirementImpl(null, Capability.PACKAGE_NAMESPACE, dirs, attrs);
- Set<Capability> exports = m_resolver.getCandidates(null, req, false);
+ Set<Capability> exports = m_resolver.getCandidates(req, false);
// We only want resolved capabilities.
for (Iterator<Capability> it = exports.iterator(); it.hasNext(); )
{
@@ -3986,10 +3986,9 @@
m_resolverState.removeModule(m);
}
- Set<Capability> getCandidates(
- Module reqModule, Requirement req, boolean obeyMandatory)
+ Set<Capability> getCandidates(Requirement req, boolean obeyMandatory)
{
- return m_resolverState.getCandidates(reqModule, req, obeyMandatory);
+ return m_resolverState.getCandidates(req, obeyMandatory);
}
void resolve(Module rootModule) throws ResolveException
@@ -4160,7 +4159,7 @@
attrs.add(new Attribute(Capability.PACKAGE_ATTR, pkgName, false));
Requirement req = new RequirementImpl(
module, Capability.PACKAGE_NAMESPACE, dirs, attrs);
- Set<Capability> candidates = m_resolverState.getCandidates(module, req, false);
+ Set<Capability> candidates = m_resolverState.getCandidates(req, false);
return !candidates.isEmpty();
}
diff --git a/framework/src/main/java/org/apache/felix/framework/ModuleImpl.java b/framework/src/main/java/org/apache/felix/framework/ModuleImpl.java
index d94b8ed..6b727f0 100644
--- a/framework/src/main/java/org/apache/felix/framework/ModuleImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/ModuleImpl.java
@@ -2297,7 +2297,7 @@
attrs.add(new Attribute(Capability.PACKAGE_ATTR, pkgName, false));
Requirement req = new RequirementImpl(
module, Capability.PACKAGE_NAMESPACE, dirs, attrs);
- Set<Capability> exporters = resolver.getCandidates(module, req, false);
+ Set<Capability> exporters = resolver.getCandidates(req, false);
Wire wire = null;
try
@@ -2336,7 +2336,7 @@
attrs.add(new Attribute(Capability.PACKAGE_ATTR, pkgName, false));
Requirement req = new RequirementImpl(
module, Capability.PACKAGE_NAMESPACE, dirs, attrs);
- Set<Capability> exports = resolver.getCandidates(module, req, false);
+ Set<Capability> exports = resolver.getCandidates(req, false);
if (exports.size() > 0)
{
boolean classpath = false;
diff --git a/framework/src/main/java/org/apache/felix/framework/ResolverStateImpl.java b/framework/src/main/java/org/apache/felix/framework/ResolverStateImpl.java
index 7143fb6..a81d33d 100644
--- a/framework/src/main/java/org/apache/felix/framework/ResolverStateImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/ResolverStateImpl.java
@@ -167,8 +167,9 @@
//
public synchronized SortedSet<Capability> getCandidates(
- Module module, Requirement req, boolean obeyMandatory)
+ Requirement req, boolean obeyMandatory)
{
+ Module module = req.getModule();
SortedSet<Capability> result = new TreeSet<Capability>(new CandidateComparator());
CapabilitySet capSet = m_capSets.get(req.getNamespace());
diff --git a/framework/src/main/java/org/apache/felix/framework/capabilityset/Capability.java b/framework/src/main/java/org/apache/felix/framework/capabilityset/Capability.java
index 7bb1534..2a77b6a 100644
--- a/framework/src/main/java/org/apache/felix/framework/capabilityset/Capability.java
+++ b/framework/src/main/java/org/apache/felix/framework/capabilityset/Capability.java
@@ -26,6 +26,7 @@
static final String MODULE_NAMESPACE = "module";
static final String HOST_NAMESPACE = "host";
static final String PACKAGE_NAMESPACE = "package";
+ static final String SINGLETON_NAMESPACE = "singleton";
public static final String PACKAGE_ATTR = "package";
public static final String VERSION_ATTR = "version";
diff --git a/framework/src/main/java/org/apache/felix/framework/capabilityset/CapabilitySet.java b/framework/src/main/java/org/apache/felix/framework/capabilityset/CapabilitySet.java
index c8957f9..cd1990a 100644
--- a/framework/src/main/java/org/apache/felix/framework/capabilityset/CapabilitySet.java
+++ b/framework/src/main/java/org/apache/felix/framework/capabilityset/CapabilitySet.java
@@ -156,7 +156,11 @@
{
Set<Capability> matches = new HashSet<Capability>();
- if (sf.getOperation() == SimpleFilter.AND)
+ if (sf.getOperation() == SimpleFilter.MATCH_ALL)
+ {
+ matches.addAll(caps);
+ }
+ else if (sf.getOperation() == SimpleFilter.AND)
{
// Evaluate each subfilter against the remaining capabilities.
// For AND we calculate the intersection of each subfilter.
diff --git a/framework/src/main/java/org/apache/felix/framework/capabilityset/SimpleFilter.java b/framework/src/main/java/org/apache/felix/framework/capabilityset/SimpleFilter.java
index 68815c2..822d9d0 100644
--- a/framework/src/main/java/org/apache/felix/framework/capabilityset/SimpleFilter.java
+++ b/framework/src/main/java/org/apache/felix/framework/capabilityset/SimpleFilter.java
@@ -23,6 +23,7 @@
public class SimpleFilter
{
+ public static final int MATCH_ALL = 0;
public static final int AND = 1;
public static final int OR = 2;
public static final int NOT = 3;
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 f76da63..e1d6275 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
@@ -31,14 +31,19 @@
import java.util.TreeMap;
import java.util.TreeSet;
import org.apache.felix.framework.capabilityset.Capability;
+import org.apache.felix.framework.capabilityset.Directive;
import org.apache.felix.framework.capabilityset.Requirement;
import org.apache.felix.framework.resolver.Resolver.ResolverState;
+import org.apache.felix.framework.util.Util;
+import org.osgi.framework.Constants;
import org.osgi.framework.Version;
public class Candidates
{
private final Module m_root;
+ // Set of all candidate modules.
+ private final Set<Module> m_candidateModules;
// Maps a capability to requirements that match it.
private final Map<Capability, Set<Requirement>> m_dependentMap;
// Maps a requirement to the capability it matches.
@@ -64,12 +69,14 @@
**/
private Candidates(
Module root,
+ Set<Module> candidateModules,
Map<Capability, Set<Requirement>> dependentMap,
Map<Requirement, SortedSet<Capability>> candidateMap,
Map<Capability, Map<String, Map<Version, List<Requirement>>>> hostFragments,
Map<Module, WrappedModule> wrappedHosts, Map<Module, Object> populateResultCache)
{
m_root = root;
+ m_candidateModules = candidateModules;
m_dependentMap = dependentMap;
m_candidateMap = candidateMap;
m_hostFragments = hostFragments;
@@ -85,6 +92,7 @@
public Candidates(ResolverState state, Module root)
{
m_root = root;
+ m_candidateModules = new HashSet<Module>();
m_dependentMap = new HashMap<Capability, Set<Requirement>>();
m_candidateMap = new HashMap<Requirement, SortedSet<Capability>>();
m_hostFragments =
@@ -109,6 +117,7 @@
Requirement req, SortedSet<Capability> candidates)
{
m_root = root;
+ m_candidateModules = new HashSet<Module>();
m_dependentMap = new HashMap<Capability, Set<Requirement>>();
m_candidateMap = new HashMap<Requirement, SortedSet<Capability>>();
m_hostFragments =
@@ -216,7 +225,7 @@
// Get satisfying candidates and populate their candidates if necessary.
ResolveException rethrow = null;
- SortedSet<Capability> candidates = state.getCandidates(module, req, true);
+ SortedSet<Capability> candidates = state.getCandidates(req, true);
for (Iterator<Capability> itCandCap = candidates.iterator(); itCandCap.hasNext(); )
{
Capability candCap = itCandCap.next();
@@ -347,9 +356,17 @@
// Record the candidates.
m_candidateMap.put(req, candidates);
- // Add the requirement as a dependent on the candidates.
+
+ // Make a list of all candidate modules for determining singetons.
+ // Add the requirement as a dependent on the candidates. Keep track
+ // of fragments for hosts.
for (Capability cap : candidates)
{
+ // Remember the module for all capabilities so we can
+ // determine which ones are singletons.
+ m_candidateModules.add(cap.getModule());
+
+ // Record the requirement as dependent on the capability.
Set<Requirement> dependents = m_dependentMap.get(cap);
if (dependents == null)
{
@@ -357,6 +374,7 @@
m_dependentMap.put(cap, dependents);
}
dependents.add(req);
+
// Keep track of hosts and associated fragments.
if (isFragment)
{
@@ -435,11 +453,80 @@
* can attach to two hosts effectively gets multiplied across the two hosts.
* So, any modules being satisfied by the fragment will end up having the
* two hosts as potential candidates, rather than the single fragment.
+ * @param existingSingletons existing resolved singletons.
* @throws ResolveException if the removal of any unselected fragments result
* in the root module being unable to resolve.
**/
- public void mergeFragments() throws ResolveException
+ public void prepare(List<Module> existingSingletons)
{
+ final Map<String, Module> singletons = new HashMap<String, Module>();
+
+ for (Iterator<Module> it = m_candidateModules.iterator(); it.hasNext(); )
+ {
+ Module m = it.next();
+ if (isSingleton(m))
+ {
+ // See if there is an existing singleton for the
+ // module's symbolic name.
+ Module singleton = singletons.get(m.getSymbolicName());
+ // If there is no existing singleton or this module is
+ // a resolved singleton or this module has a higher version
+ // and the existing singleton is not resolved, then select
+ // this module as the singleton.
+ if ((singleton == null)
+ || m.isResolved()
+ || ((m.getVersion().compareTo(singleton.getVersion()) > 0)
+ && !singleton.isResolved()))
+ {
+ singletons.put(m.getSymbolicName(), m);
+ // Remove the singleton module from the candidates
+ // if it wasn't selected.
+ if (singleton != null)
+ {
+ removeModule(singleton);
+ }
+ }
+ else
+ {
+ removeModule(m);
+ }
+ }
+ }
+
+ // If the root is a singleton, then prefer it over any other singleton.
+ if (isSingleton(m_root))
+ {
+ Module singleton = singletons.get(m_root.getSymbolicName());
+ singletons.put(m_root.getSymbolicName(), m_root);
+ if (singleton != null)
+ {
+ if (singleton.isResolved())
+ {
+ throw new ResolveException(
+ "Cannot resolve singleton "
+ + m_root
+ + " because "
+ + singleton
+ + " singleton is already resolved.",
+ m_root, null);
+ }
+ removeModule(singleton);
+ }
+ }
+
+ // Make sure selected singletons do not conflict with existing
+ // singletons passed into this method.
+ for (int i = 0; (existingSingletons != null) && (i < existingSingletons.size()); i++)
+ {
+ Module existing = existingSingletons.get(i);
+ Module singleton = singletons.get(existing.getSymbolicName());
+ if ((singleton != null) && (singleton != existing))
+ {
+ singletons.remove(singleton.getSymbolicName());
+ removeModule(singleton);
+ }
+ }
+
// This method performs the following steps:
// 1. Select the fragments to attach to a given host.
// 2. Wrap hosts and attach fragments.
@@ -507,7 +594,7 @@
// Step 3
for (Module m : unselectedFragments)
{
- unselectFragment(m);
+ removeModule(m);
}
// Step 4
@@ -546,22 +633,30 @@
}
/**
- * Removes a fragment from the internal data structures if it wasn't selected.
- * This process may cause other modules to become unresolved if they depended
- * on fragment capabilities and there is no other candidate.
- * @param fragment the fragment to remove.
- * @throws ResolveException if removing the fragment caused the resolve to fail.
+ * Removes a module from the internal data structures if it wasn't selected
+ * as a fragment or a singleton. This process may cause other modules to
+ * become unresolved if they depended on the module's capabilities and there
+ * is no other candidate.
+ * @param module the module to remove.
+ * @throws ResolveException if removing the module caused the resolve to fail.
**/
- private void unselectFragment(Module fragment) throws ResolveException
+ private void removeModule(Module module) throws ResolveException
{
+ if (m_root.equals(module))
+ {
+// TODO: SINGLETON RESOLVER - Improve this message.
+ String msg = "Unable to resolve " + m_root;
+ ResolveException ex = new ResolveException(msg, m_root, null);
+ throw ex;
+ }
Set<Module> unresolvedModules = new HashSet<Module>();
- remove(fragment, unresolvedModules);
+ remove(module, unresolvedModules);
while (!unresolvedModules.isEmpty())
{
Iterator<Module> it = unresolvedModules.iterator();
- fragment = it.next();
+ module = it.next();
it.remove();
- remove(fragment, unresolvedModules);
+ remove(module, unresolvedModules);
}
}
@@ -686,6 +781,8 @@
**/
public Candidates copy()
{
+ Set<Module> candidateModules = new HashSet<Module>(m_candidateModules);
+
Map<Capability, Set<Requirement>> dependentMap =
new HashMap<Capability, Set<Requirement>>();
for (Entry<Capability, Set<Requirement>> entry : m_dependentMap.entrySet())
@@ -703,7 +800,7 @@
}
return new Candidates(
- m_root, dependentMap, candidateMap,
+ m_root, candidateModules, dependentMap, candidateMap,
m_hostFragments, m_allWrappedHosts, m_populateResultCache);
}
@@ -741,4 +838,32 @@
}
System.out.println("=== END CANDIDATE MAP ===");
}
-}
\ No newline at end of file
+
+ /**
+ * Returns true if the specified module is a singleton
+ * (i.e., directive singleton:=true).
+ *
+ * @param module the module to check for singleton status.
+ * @return true if the module is a singleton, false otherwise.
+ **/
+ private static boolean isSingleton(Module module)
+ {
+ final List<Capability> modCaps =
+ Util.getCapabilityByNamespace(
+ module, Capability.MODULE_NAMESPACE);
+ if (modCaps == null || modCaps.isEmpty())
+ {
+ return false;
+ }
+ final List<Directive> dirs = modCaps.get(0).getDirectives();
+ for (int dirIdx = 0; (dirs != null) && (dirIdx < dirs.size()); dirIdx++)
+ {
+ if (dirs.get(dirIdx).getName().equalsIgnoreCase(Constants.SINGLETON_DIRECTIVE)
+ && Boolean.valueOf((String) dirs.get(dirIdx).getValue()))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/framework/src/main/java/org/apache/felix/framework/resolver/Resolver.java b/framework/src/main/java/org/apache/felix/framework/resolver/Resolver.java
index 2d8f9e0..9d65b4f 100644
--- a/framework/src/main/java/org/apache/felix/framework/resolver/Resolver.java
+++ b/framework/src/main/java/org/apache/felix/framework/resolver/Resolver.java
@@ -33,8 +33,7 @@
public static interface ResolverState
{
- SortedSet<Capability> getCandidates(
- Module module, Requirement req, boolean obeyMandatory);
+ SortedSet<Capability> getCandidates(Requirement req, boolean obeyMandatory);
void checkExecutionEnvironment(Module module) throws ResolveException;
void checkNativeLibraries(Module module) throws ResolveException;
}
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 a7228e7..aeb8449 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
@@ -73,6 +73,8 @@
Candidates allCandidates = new Candidates(state, module);
// Try to populate optional fragments.
+// TODO: SINGLETON RESOLVER - These optional modules will not be considered
+// for singleton calculation -- fix this.
for (Module fragment : fragments)
{
try
@@ -86,7 +88,7 @@
}
// Merge any fragments into hosts.
- allCandidates.mergeFragments();
+ allCandidates.prepare(getResolvedSingletons(state));
// Record the initial candidate permutation.
m_usesPermutations.add(allCandidates);
@@ -211,6 +213,8 @@
try
{
// Try to populate optional fragments.
+// TODO: SINGLETON RESOLVER - These optional modules will not be considered
+// for singleton calculation -- fix this.
for (Module fragment : fragments)
{
try
@@ -224,7 +228,7 @@
}
// Merge any fragments into hosts.
- allCandidates.mergeFragments();
+ allCandidates.prepare(getResolvedSingletons(state));
// Record the initial candidate permutation.
m_usesPermutations.add(allCandidates);
@@ -318,6 +322,25 @@
return null;
}
+ private static List<Module> getResolvedSingletons(ResolverState state)
+ {
+ Requirement req = new RequirementImpl(
+ null,
+ Capability.SINGLETON_NAMESPACE,
+ Collections.EMPTY_LIST,
+ Collections.EMPTY_LIST);
+ SortedSet<Capability> caps = state.getCandidates(req, true);
+ List<Module> singletons = new ArrayList();
+ for (Capability cap : caps)
+ {
+ if (cap.getModule().isResolved())
+ {
+ singletons.add(cap.getModule());
+ }
+ }
+ return singletons;
+ }
+
private static Capability getHostCapability(Module m)
{
for (Capability c : m.getCapabilities())
@@ -390,7 +413,7 @@
attrs.add(new Attribute(Capability.PACKAGE_ATTR, pkgName, false));
Requirement req = new RequirementImpl(
module, Capability.PACKAGE_NAMESPACE, dirs, attrs);
- SortedSet<Capability> candidates = state.getCandidates(module, req, false);
+ SortedSet<Capability> candidates = state.getCandidates(req, false);
// First find a dynamic requirement that matches the capabilities.
Requirement dynReq = null;
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 f718c0e..3ca16aa 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
@@ -112,6 +112,19 @@
owner, Capability.HOST_NAMESPACE, new ArrayList<Directive>(0),
((CapabilityImpl) moduleCap).getAttributes()));
}
+
+ // 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 module or host capabilities
+ // because fragments don't have those capabilities, but fragments
+ // can be singletons too.
+ if (isSingleton(moduleCap))
+ {
+ capList.add(new CapabilityImpl(
+ owner, Capability.SINGLETON_NAMESPACE, new ArrayList<Directive>(0),
+ ((CapabilityImpl) moduleCap).getAttributes()));
+ }
}
// Verify that bundle symbolic name is specified.
@@ -226,6 +239,23 @@
m_isExtension = checkExtensionBundle(headerMap);
}
+ private static boolean isSingleton(Capability cap)
+ {
+ if (cap.getNamespace().equals(Capability.MODULE_NAMESPACE))
+ {
+ final List<Directive> dirs = cap.getDirectives();
+ for (int dirIdx = 0; (dirs != null) && (dirIdx < dirs.size()); dirIdx++)
+ {
+ if (dirs.get(dirIdx).getName().equalsIgnoreCase(Constants.SINGLETON_DIRECTIVE)
+ && Boolean.valueOf((String) dirs.get(dirIdx).getValue()))
+ {
+ 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/util/manifestparser/RequirementImpl.java b/framework/src/main/java/org/apache/felix/framework/util/manifestparser/RequirementImpl.java
index aa1adbb..0f039fe 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/manifestparser/RequirementImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/manifestparser/RequirementImpl.java
@@ -189,6 +189,10 @@
{
sf = new SimpleFilter(null, filters, SimpleFilter.AND);
}
+ else if (filters.isEmpty())
+ {
+ sf = new SimpleFilter(null, null, SimpleFilter.MATCH_ALL);
+ }
return sf;
}