Resolved FELIX-4014 (possible deadlock on ServiceDependencyImpl.handleAspectAwareRemoved), FELIX-4097 (debug logging in ServiceDependency), FELIX-4098 (wrong swap order with multiple threads), FELIX-4099 (support for negate in multi property filter index).
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1488970 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/DependencyManager.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/DependencyManager.java
index 7a148fe..8a76180 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dm/DependencyManager.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/DependencyManager.java
@@ -39,9 +39,9 @@
import org.apache.felix.dm.impl.dependencies.ServiceDependencyImpl;
import org.apache.felix.dm.impl.dependencies.TemporalServiceDependencyImpl;
import org.apache.felix.dm.impl.index.AspectFilterIndex;
-import org.apache.felix.dm.impl.index.MultiPropertyExactFilter;
import org.apache.felix.dm.impl.index.AdapterFilterIndex;
import org.apache.felix.dm.impl.index.ServiceRegistryCache;
+import org.apache.felix.dm.impl.index.multiproperty.MultiPropertyFilterIndex;
import org.apache.felix.dm.impl.metatype.PropertyMetaDataImpl;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
@@ -110,8 +110,7 @@
m_serviceRegistryCache.addFilterIndex(new AdapterFilterIndex());
}
else {
- String[] propList = props[i].split(",");
- m_serviceRegistryCache.addFilterIndex(new MultiPropertyExactFilter(propList));
+ m_serviceRegistryCache.addFilterIndex(new MultiPropertyFilterIndex(props[i]));
}
}
}
@@ -146,7 +145,6 @@
private BundleContext createContext(BundleContext context) {
if (m_serviceRegistryCache != null) {
-// System.out.println("DM: Enabling bundle context interceptor for bundle #" + context.getBundle().getBundleId());
return m_serviceRegistryCache.createBundleContextInterceptor(context);
}
else {
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/ServiceDependency.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/ServiceDependency.java
index aa9400a..dde8953 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dm/ServiceDependency.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/ServiceDependency.java
@@ -214,4 +214,11 @@
* @param isInstanceBound <code>true</code> if this dependency should be instance bound
*/
public ServiceDependency setInstanceBound(boolean isInstanceBound);
+
+ /**
+ * Enabled debug logging for this dependency instance. The logging is prefixed with the given identifier.
+ * @param identifier
+ * @return this service dependency.
+ */
+ public ServiceDependency setDebug(String identifier);
}
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/dependencies/ResourceDependencyImpl.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/dependencies/ResourceDependencyImpl.java
index 9b11fbb..e2bca98 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/dependencies/ResourceDependencyImpl.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/dependencies/ResourceDependencyImpl.java
@@ -200,6 +200,10 @@
long counter;
Object[] services;
synchronized (this) {
+ if (m_resources.indexOf(resource) == -1) {
+ m_logger.log(Logger.LOG_WARNING, "handleResourceRemoved called for unknown resource: " + resource);
+ return;
+ }
m_resourceProperties.remove(m_resources.indexOf(resource));
m_resources.remove(resource);
counter = m_resources.size();
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/dependencies/ServiceDependencyImpl.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/dependencies/ServiceDependencyImpl.java
index b3db89e..1ffe7f7 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/dependencies/ServiceDependencyImpl.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/dependencies/ServiceDependencyImpl.java
@@ -28,6 +28,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -83,6 +84,16 @@
private String m_propagateCallbackMethod;
private final Map m_sr = new HashMap(); /* <DependencyService, Set<Tuple<ServiceReference, Object>> */
private Map m_componentByRank = new HashMap(); /* <Component, Map<Long, Map<Integer, Tuple>>> */
+ private boolean m_debug = false;
+ private String m_debugKey = null;
+
+ private final LinkedList m_injectionQueue = new LinkedList();
+
+ public ServiceDependency setDebug(String identifier) {
+ this.m_debug = true;
+ this.m_debugKey = identifier;
+ return this;
+ }
private static final Comparator COMPARATOR = new Comparator() {
public int getRank(ServiceReference ref) {
@@ -416,7 +427,13 @@
}
}
if (needsStarting) {
- m_tracker.open();
+ // when the swapped callback is set, also track the aspects
+ boolean trackAllServices = false;
+ boolean trackAllAspects = false;
+ if (m_callbackSwapped != null) {
+ trackAllAspects = true;
+ }
+ m_tracker.open(trackAllServices, trackAllAspects);
}
}
@@ -448,6 +465,9 @@
}
public void addedService(ServiceReference ref, Object service) {
+ if (m_debug) {
+ log("addedservice: " + ref);
+ }
boolean makeAvailable = makeAvailable();
Object[] services;
@@ -458,20 +478,32 @@
DependencyService ds = (DependencyService) services[i];
if (makeAvailable) {
if (ds.isInstantiated() && isInstanceBound() && isRequired()) {
- invokeAdded(ds, ref, service);
+ if (m_debug) {
+ log("invoke added: " + ref);
+ }
+ invokeAdded(ds, ref, service); //**
}
- // The dependency callback will be defered until all required dependency are available.
- ds.dependencyAvailable(this);
+ // The dependency callback will be deferred until all required dependency are available.
+ if (m_debug) {
+ log("dependency available: " + ref);
+ }
+ ds.dependencyAvailable(this); //**
if (!isRequired()) {
// For optional dependency, we always invoke callback, because at this point, we know
// that the service has been started, and the service start method has been called.
// (See the ServiceImpl.bindService method, which will activate optional dependencies using
- // startTrackingOptional() method).
- invokeAdded(ds, ref, service);
+ // startTrackingOptional() method).
+ if (m_debug) {
+ log("invoke added: " + ref);
+ }
+ invokeAdded(ds, ref, service); //**
}
}
else {
- ds.dependencyChanged(this);
+ if (m_debug) {
+ log("dependency changed: " + ref);
+ }
+ ds.dependencyChanged(this); //**
// At this point, either the dependency is optional (meaning that the service has been started,
// because if not, then our dependency would not be active); or the dependency is required,
// meaning that either the service is not yet started, or already started.
@@ -479,7 +511,10 @@
// we only try to invoke the method here if we are really already instantiated
if (ds.isInstantiated() && ds.getCompositionInstances().length > 0) {
- invokeAdded(ds, ref, service);
+ if (m_debug) {
+ log("invoke added: " + ref);
+ }
+ invokeAdded(ds, ref, service); //**
}
}
}
@@ -500,8 +535,13 @@
}
public void removedService(ServiceReference ref, Object service) {
+ if (m_debug) {
+ log("removedservice: " + ref);
+ }
boolean makeUnavailable = makeUnavailable();
-
+ if (m_debug) {
+ log("make unavailable: " + makeUnavailable);
+ }
Object[] services;
synchronized (this) {
services = m_services.toArray();
@@ -511,6 +551,8 @@
DependencyService ds = (DependencyService) services[i];
if (makeUnavailable) {
ds.dependencyUnavailable(this);
+ // when the dependency is optional or the dependency is instance bound and the component is instantiated (and the dependency is required)
+ // then remove is invoked. In other cases the removed has been when the component was unconfigured.
if (!isRequired() || (ds.isInstantiated() && isInstanceBound())) {
invokeRemoved(ds, ref, service);
}
@@ -526,6 +568,9 @@
}
public void invokeAdded(DependencyService dependencyService, ServiceReference reference, Object service) {
+ if (m_debug) {
+ log("invoke added");
+ }
boolean added = false;
synchronized (m_sr) {
Set set = (Set) m_sr.get(dependencyService);
@@ -546,14 +591,39 @@
}
}
- private void handleAspectAwareAdded(DependencyService dependencyService, ServiceReference reference, Object service) {
+ private synchronized void waitForCallbackLock(Runnable runnable) {
+ while (m_injectionQueue.indexOf(runnable) != 0) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ private synchronized void enqueueCallback(Runnable runnable) {
+ m_injectionQueue.addLast(runnable);
+ }
+
+ private synchronized void releaseCallback(Runnable runnable) {
+ m_injectionQueue.remove(runnable);
+ notifyAll();
+ }
+
+ private void handleAspectAwareAdded(final DependencyService dependencyService, final ServiceReference reference, final Object service) {
+ if (m_debug) {
+ log("aspectawareadded: " + reference.getProperty("service.ranking"));
+ }
if (componentIsDependencyManagerFactory(dependencyService)) {
// component is either aspect or adapter factory instance, these must be ignored.
return;
}
boolean invokeAdded = false;
+ boolean invokeSwapped = false;
Integer ranking = ServiceUtil.getRankingAsInteger(reference);
- Tuple highestRankedService = null;
+ Tuple newHighestRankedService = null;
+ Tuple prevHighestRankedService = null;
+ Runnable callbackRunnable = null;
+ Map rankings = null;
synchronized (m_componentByRank) {
Long originalServiceId = ServiceUtil.getServiceIdAsLong(reference);
Map componentMap = (Map) m_componentByRank.get(dependencyService); /* <Long, Map<Integer, Tuple>> */
@@ -562,7 +632,7 @@
componentMap = new HashMap(); /* <Long, Map<Integer, Tuple>> */
m_componentByRank.put(dependencyService, componentMap);
}
- Map rankings = (Map) componentMap.get(originalServiceId); /* <Integer, Tuple> */
+ rankings = (Map) componentMap.get(originalServiceId); /* <Integer, Tuple> */
if (rankings == null) {
// new component added
rankings = new HashMap(); /* <Integer, Tuple> */
@@ -572,14 +642,84 @@
}
if (!invokeAdded) {
- highestRankedService = swapHighestRankedService(dependencyService, originalServiceId, reference, service, ranking);
+ // current highest ranked
+ prevHighestRankedService = (Tuple)getHighestRankedService(dependencyService, originalServiceId).getValue();
+ newHighestRankedService = swapHighestRankedService(dependencyService, originalServiceId, reference, service, ranking);
+ if (m_debug) {
+ log("prevhigh: " + prevHighestRankedService.getServiceReference().getProperty("service.ranking") + ", new high: " + newHighestRankedService.getServiceReference().getProperty("service.ranking"));
+ }
+ if (!prevHighestRankedService.getServiceReference().equals(newHighestRankedService.getServiceReference())) {
+ // new highest ranked service
+ if (m_debug) {
+ log("New highest ranked to swap to");
+ }
+ invokeSwapped = true;
+ } else {
+ if (m_debug) {
+ log("Ignoring lower ranked or irrelevant swap");
+ }
+ }
+ }
+ if (m_debug) {
+ log(m_componentByRank.toString());
+ }
+
+ // up until this point should be synchronized on m_componentsByRank to keep integrity of the administration and consequences
+ // then the do phase comes, here we want to guarantee the effects of this operation are done like they were synchronized, however
+ // synchronization on m_componentsByRank to too course grained here, so we'd like to switch to synchronization on the
+ // original service id, therefore we're using our own guarded block to ensure the correct order.
+
+ if (invokeAdded) {
+ if (m_debug) {
+ log("invoke added: " + reference.getProperty("service.ranking"));
+ }
+ callbackRunnable = createCallbackRunnable(dependencyService, reference, service, m_callbackAdded);
+ enqueueCallback(callbackRunnable);
+ } else if (invokeSwapped) {
+ if (m_debug) {
+ log("invoke swapped: " + newHighestRankedService.getServiceReference().getProperty("service.ranking") + " replacing " + prevHighestRankedService.getServiceReference().getProperty("service.ranking"));
+ }
+ callbackRunnable = createSwapRunnable(dependencyService, prevHighestRankedService.getServiceReference(), prevHighestRankedService.getService(), newHighestRankedService.getServiceReference(), newHighestRankedService.getService());
+ enqueueCallback(callbackRunnable);
+ }
+ }
+ if (callbackRunnable != null) {
+ waitForCallbackLock(callbackRunnable);
+ synchronized (rankings) {
+ releaseCallback(callbackRunnable);
+ execute(callbackRunnable);
}
}
- if (invokeAdded) {
- invoke(dependencyService, reference, service, m_callbackAdded);
- } else {
- invokeSwappedCallback(dependencyService, highestRankedService.getServiceReference(), highestRankedService.getService(), reference, service);
- }
+ }
+
+ private Runnable createCallbackRunnable(final DependencyService dependencyService, final ServiceReference reference, final Object service, final String callback) {
+ return new Runnable() {
+ public void run() {
+ invoke(dependencyService, reference, service, callback);
+ }
+ public String toString() {
+ return callback + " on " + dependencyService;
+ }
+ };
+ }
+
+ private Runnable createSwapRunnable(final DependencyService dependencyService, final ServiceReference prevReference, final Object prevService, final ServiceReference newReference, final Object newService) {
+ return new Runnable() {
+ public void run() {
+ invokeSwappedCallback(dependencyService, prevReference, prevService, newReference, newService);
+ }
+ public String toString() {
+ return "swap on " + dependencyService;
+ }
+ };
+ }
+
+ private void execute(Runnable runnable) {
+ runnable.run();
+ }
+
+ private void log(String message) {
+ m_logger.log(Logger.LOG_DEBUG, message);
}
private boolean componentIsDependencyManagerFactory(DependencyService dependencyService) {
@@ -594,13 +734,12 @@
}
private Tuple swapHighestRankedService(DependencyService dependencyService, Long serviceId, ServiceReference newReference, Object newService, Integer newRanking) {
- // does a component with a higher ranking exists
+ // does a component with a higher ranking exist
synchronized (m_componentByRank) {
Map componentMap = (Map) m_componentByRank.get(dependencyService); /* <Long, Map<Integer, Tuple>> */
Map rankings = (Map) componentMap.get(serviceId); /* <Integer, Tuple> */
- Entry highestEntry = getHighestRankedService(dependencyService, serviceId); /* <Integer, Tuple> */
- rankings.remove(highestEntry.getKey());
rankings.put(newRanking, new Tuple(newReference, newService));
+ Entry highestEntry = getHighestRankedService(dependencyService, serviceId); /* <Integer, Tuple> */
return (Tuple) highestEntry.getValue();
}
}
@@ -643,6 +782,9 @@
// 1) a call to added(original-service)
// 2) that causes a swap
// 3) a call to removed(aspect-service) <-- that's what we're talking about
+ if (m_debug) {
+ log(m_componentByRank.toString());
+ }
return (componentMap != null && rankings != null && rankings.size() == 1 && ((Entry)rankings.entrySet().iterator().next()).getKey()
.equals(ServiceUtil.getRankingAsInteger(reference)));
}
@@ -653,11 +795,17 @@
}
public void invokeRemoved(DependencyService dependencyService, ServiceReference reference, Object service) {
+ if (m_debug) {
+ log("invoke removed");
+ }
boolean removed = false;
synchronized (m_sr) {
Set set = (Set) m_sr.get(dependencyService);
removed = (set != null && set.remove(new Tuple(reference, service)));
}
+ if (m_debug) {
+ log("removed: " + removed);
+ }
if (removed) {
if (m_callbackSwapped != null) {
handleAspectAwareRemoved(dependencyService, reference, service);
@@ -669,37 +817,94 @@
}
private void handleAspectAwareRemoved(DependencyService dependencyService, ServiceReference reference, Object service) {
+ if (m_debug) {
+ log("aspectawareremoved: " + reference.getProperty("service.ranking"));
+ }
if (componentIsDependencyManagerFactory(dependencyService)) {
// component is either aspect or adapter factory instance, these must be ignored.
return;
}
+ // we might need to swap here too!
+ boolean invokeRemoved = false;
Long serviceId = ServiceUtil.getServiceIdAsLong(reference);
- synchronized (m_componentByRank) {
- if (isLastService(dependencyService, reference, service, serviceId)) {
- invoke(dependencyService, reference, service, m_callbackRemoved);
- }
- Long originalServiceId = ServiceUtil.getServiceIdAsLong(reference);
- Map componentMap = (Map) m_componentByRank.get(dependencyService); /* <Long, Map<Integer, Tuple>> */
- if (componentMap != null) {
- Map rankings = (Map) componentMap.get(originalServiceId); /* <Integer, Tuple> */
- for (Iterator entryIterator = rankings.entrySet().iterator(); entryIterator.hasNext(); ) {
- Entry mapEntry = (Entry) entryIterator.next();
- if (((Tuple)mapEntry.getValue()).getServiceReference().equals(reference)) {
- // remove the reference
- rankings.remove(mapEntry.getKey());
- }
+ Tuple prevHighestRankedService = null;
+ Tuple newHighestRankedService = null;
+ boolean invokeSwapped = false;
+ Map rankings = null;
+ Runnable callbackRunnable = null;
+ synchronized (m_componentByRank) {
+ Long originalServiceId = ServiceUtil.getServiceIdAsLong(reference);
+ if (isLastService(dependencyService, reference, service, serviceId)) {
+ invokeRemoved = true;
+ } else {
+ // not the last service, but should we swap?
+ prevHighestRankedService = (Tuple)getHighestRankedService(dependencyService, originalServiceId).getValue();
+ if (prevHighestRankedService.getServiceReference().equals(reference)) {
+ // swapping out
+ if (m_debug) {
+ log("Swap out on remove!");
}
- if (rankings.size() == 0) {
- componentMap.remove(originalServiceId);
- }
- if (componentMap.size() == 0) {
- m_componentByRank.remove(dependencyService);
- }
+ invokeSwapped = true;
}
}
+ if (m_debug) {
+ log("is last service: " + invokeRemoved);
+ }
+ // cleanup
+ Map componentMap = (Map) m_componentByRank.get(dependencyService); /* <Long, Map<Integer, Tuple>> */
+ if (componentMap != null) {
+ rankings = (Map) componentMap.get(originalServiceId); /* <Integer, Tuple> */
+ List rankingsToRemove = new ArrayList();
+ for (Iterator entryIterator = rankings.entrySet().iterator(); entryIterator.hasNext(); ) {
+ Entry mapEntry = (Entry) entryIterator.next();
+ if (((Tuple)mapEntry.getValue()).getServiceReference().equals(reference)) {
+ // remove the reference
+ // rankings.remove(mapEntry.getKey());
+ rankingsToRemove.add(mapEntry.getKey());
+ }
+ }
+ for (Iterator rankingIterator = rankingsToRemove.iterator(); rankingIterator.hasNext(); ) {
+ rankings.remove(rankingIterator.next());
+ }
+ if (rankings.size() == 0) {
+ componentMap.remove(originalServiceId);
+ }
+ if (componentMap.size() == 0) {
+ m_componentByRank.remove(dependencyService);
+ }
+ }
+ // determine current highest ranked service
+ if (invokeSwapped) {
+ newHighestRankedService = (Tuple)getHighestRankedService(dependencyService, originalServiceId).getValue();
+ }
+ if (invokeRemoved) {
+ // handle invoke outside the sync block since we won't know what will happen there
+ if (m_debug) {
+ log("invoke removed: " + reference.getProperty("service.ranking"));
+ }
+ callbackRunnable = createCallbackRunnable(dependencyService, reference, service, m_callbackRemoved);
+ enqueueCallback(callbackRunnable);
+ } else if (invokeSwapped) {
+ if (m_debug) {
+ log("invoke swapped: " + newHighestRankedService.getServiceReference().getProperty("service.ranking") + " replacing " + prevHighestRankedService.getServiceReference().getProperty("service.ranking"));
+ }
+ callbackRunnable = createSwapRunnable(dependencyService, prevHighestRankedService.getServiceReference(), prevHighestRankedService.getService(), newHighestRankedService.getServiceReference(), newHighestRankedService.getService());
+ enqueueCallback(callbackRunnable);
+ }
+ }
+ if (callbackRunnable != null) {
+ waitForCallbackLock(callbackRunnable);
+ synchronized (rankings) {
+ releaseCallback(callbackRunnable);
+ execute(callbackRunnable);
+ }
+ }
}
public void invoke(DependencyService dependencyService, ServiceReference reference, Object service, String name) {
+ if (m_debug) {
+ log("invoke: " + name);
+ }
if (name != null) {
dependencyService.invokeCallbackMethod(getCallbackInstances(dependencyService), name,
new Class[][] {
@@ -724,11 +929,26 @@
throw new IllegalStateException("Attempt to swap a service for a service with the same rank! previousReference: " + previousReference + ", currentReference: " + currentServiceReference);
}
- component.invokeCallbackMethod(getCallbackInstances(component), m_callbackSwapped, new Class[][] { { m_trackedServiceName, m_trackedServiceName },
- { Object.class, Object.class }, { ServiceReference.class, m_trackedServiceName, ServiceReference.class, m_trackedServiceName },
- { ServiceReference.class, Object.class, ServiceReference.class, Object.class } }, new Object[][] { { previous, current },
- { previous, current }, { previousReference, previous, currentServiceReference, current },
- { previousReference, previous, currentServiceReference, current } });
+ component.invokeCallbackMethod(getCallbackInstances(component), m_callbackSwapped, new Class[][] {
+ { m_trackedServiceName, m_trackedServiceName },
+ { Object.class, Object.class },
+ { ServiceReference.class, m_trackedServiceName, ServiceReference.class, m_trackedServiceName },
+ { ServiceReference.class, Object.class, ServiceReference.class, Object.class },
+ { Component.class, m_trackedServiceName, m_trackedServiceName },
+ { Component.class, Object.class, Object.class },
+ { Component.class, ServiceReference.class, m_trackedServiceName, ServiceReference.class, m_trackedServiceName },
+ { Component.class, ServiceReference.class, Object.class, ServiceReference.class, Object.class }
+ },
+ new Object[][] {
+ { previous, current },
+ { previous, current },
+ { previousReference, previous, currentServiceReference, current },
+ { previousReference, previous, currentServiceReference, current },
+ { component, previous, current },
+ { component, previous, current },
+ { component, previousReference, previous, currentServiceReference, current },
+ { component, previousReference, previous, currentServiceReference, current }
+ });
}
protected synchronized boolean makeAvailable() {
@@ -1066,6 +1286,9 @@
}
public void invokeAdded(DependencyService service) {
+ if (m_debug) {
+ log("invoke added due to configure. (component is activated)");
+ }
ServiceReference[] refs = m_tracker.getServiceReferences();
if (refs != null) {
for (int i = 0; i < refs.length; i++) {
@@ -1077,20 +1300,26 @@
}
public void invokeRemoved(DependencyService service) {
+ if (m_debug) {
+ log("invoke removed due to unconfigure. (component is destroyed)");
+ }
Set references = null;
+ Object[] tupleArray = null;
synchronized (m_sr) {
references = (Set) m_sr.get(service);
+ // is this null check necessary ??
+ if (references != null) {
+ tupleArray = references.toArray(new Tuple[references.size()]);
+ }
}
- Tuple[] refs = (Tuple[]) (references != null ? references.toArray(new Tuple[references.size()]) : new Tuple[0]);
+
+ Tuple[] refs = (Tuple[]) (tupleArray != null ? tupleArray : new Tuple[0]);
for (int i = 0; i < refs.length; i++) {
ServiceReference sr = refs[i].getServiceReference();
Object svc = refs[i].getService();
invokeRemoved(service, sr, svc);
}
- if (references != null) {
- references.clear();
- }
}
public Dictionary getProperties() {
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/BundleContextInterceptor.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/BundleContextInterceptor.java
index e39de62..1f25811 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/BundleContextInterceptor.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/BundleContextInterceptor.java
@@ -21,8 +21,10 @@
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
import org.apache.felix.dm.FilterIndex;
+import org.apache.felix.dm.impl.Logger;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
@@ -34,11 +36,16 @@
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class BundleContextInterceptor extends BundleContextInterceptorBase {
+ private static final String INDEX_LOG_BAD_PERFORMING_FILTERS = "org.apache.felix.dependencymanager.index.logbadperformingfilters";
+ private static AtomicLong maxLookupTime = new AtomicLong(0L);
private final ServiceRegistryCache m_cache;
+ private final boolean perfmon = "true".equals(System.getProperty(INDEX_LOG_BAD_PERFORMING_FILTERS));
+ private final Logger m_logger;
- public BundleContextInterceptor(ServiceRegistryCache cache, BundleContext context) {
+ public BundleContextInterceptor(ServiceRegistryCache cache, BundleContext context, Logger logger) {
super(context);
m_cache = cache;
+ m_logger = logger;
}
public void addServiceListener(ServiceListener listener, String filter) throws InvalidSyntaxException {
@@ -74,6 +81,10 @@
}
public ServiceReference[] getServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
+ long start = 0L;
+ if (perfmon) {
+ start = System.currentTimeMillis();
+ }
// first we ask the cache if there is an index for our request (class and filter combination)
FilterIndex filterIndex = m_cache.hasFilterIndexFor(clazz, filter);
if (filterIndex != null) {
@@ -89,6 +100,13 @@
}
}
}
+ if (perfmon) {
+ long duration = System.currentTimeMillis() - start;
+ if (maxLookupTime.get() < duration) {
+ maxLookupTime.set(duration);
+ m_logger.log(org.apache.felix.dm.impl.Logger.LOG_DEBUG, "new worst performing filter (" + duration + "ms.): " + clazz + " " + filter);
+ }
+ }
if (result == null || result.size() == 0) {
return null;
}
@@ -96,7 +114,15 @@
}
else {
// if they don't know, we ask the real bundle context instead
- return m_context.getServiceReferences(clazz, filter);
+ ServiceReference[] serviceReferences = m_context.getServiceReferences(clazz, filter);
+ if (perfmon) {
+ long duration = System.currentTimeMillis() - start;
+ if (maxLookupTime.get() < duration) {
+ maxLookupTime.set(duration);
+ System.out.println("new worst performing filter (" + duration + "ms.): " + clazz + " " + filter);
+ }
+ }
+ return serviceReferences;
}
}
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/MultiPropertyExactFilter.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/MultiPropertyExactFilter.java
deleted file mode 100644
index f0b7705..0000000
--- a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/MultiPropertyExactFilter.java
+++ /dev/null
@@ -1,430 +0,0 @@
-/*
- * 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.dm.impl.index;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-import org.apache.felix.dm.FilterIndex;
-import org.apache.felix.dm.tracker.ServiceTracker;
-import org.apache.felix.dm.tracker.ServiceTrackerCustomizer;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceEvent;
-import org.osgi.framework.ServiceListener;
-import org.osgi.framework.ServiceReference;
-
-/**
- * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
- */
-public class MultiPropertyExactFilter implements FilterIndex, ServiceTrackerCustomizer {
- private final Object m_lock = new Object();
- private ServiceTracker m_tracker;
- private BundleContext m_context;
- private final TreeSet /* <String> */ m_propertyKeys = new TreeSet(String.CASE_INSENSITIVE_ORDER);
- private final Map /* <String, List<ServiceReference>> */ m_keyToServiceReferencesMap = new HashMap();
- private final Map /* <String, List<ServiceListener>> */ m_keyToListenersMap = new HashMap();
- private final Map /* <ServiceListener, String> */ m_listenerToFilterMap = new HashMap();
-
- public MultiPropertyExactFilter(String[] propertyKeys) {
- for (int i = 0; i < propertyKeys.length; i++) {
- m_propertyKeys.add(propertyKeys[i]);
- }
- }
-
- public void open(BundleContext context) {
- synchronized (m_lock) {
- if (m_context != null) {
- throw new IllegalStateException("Filter already open.");
- }
- try {
- m_tracker = new ServiceTracker(context, context.createFilter("(" + Constants.OBJECTCLASS + "=*)"), this);
- }
- catch (InvalidSyntaxException e) {
- throw new Error();
- }
- m_context = context;
- }
- m_tracker.open(true, true);
- }
-
- public void close() {
- ServiceTracker tracker;
- synchronized (m_lock) {
- if (m_context == null) {
- throw new IllegalStateException("Filter already closed.");
- }
- tracker = m_tracker;
- m_tracker = null;
- m_context = null;
- }
- tracker.close();
- }
-
- public List /* <ServiceReference> */ getAllServiceReferences(String clazz, String filter) {
- List /* <ServiceReference> */ result = new ArrayList();
- List keys = createKeysFromFilter(clazz, filter);
- Iterator iterator = keys.iterator();
- while (iterator.hasNext()) {
- String key = (String) iterator.next();
- ServiceReference reference;
- synchronized (m_keyToServiceReferencesMap) {
- List references = (List) m_keyToServiceReferencesMap.get(key);
- if (references != null) {
- result.addAll(references);
- }
- }
- }
- return result;
- }
-
- public Object addingService(ServiceReference reference) {
- BundleContext context;
- synchronized (m_lock) {
- context = m_context;
- }
- if (context != null) {
- return context.getService(reference);
- }
- else {
- throw new IllegalStateException("No valid bundle context.");
- }
- }
-
- public void addedService(ServiceReference reference, Object service) {
- if (isApplicable(reference.getPropertyKeys())) {
- add(reference);
- }
- }
-
- public void modifiedService(ServiceReference reference, Object service) {
- if (isApplicable(reference.getPropertyKeys())) {
- modify(reference);
- }
- }
-
- public void removedService(ServiceReference reference, Object service) {
- if (isApplicable(reference.getPropertyKeys())) {
- remove(reference);
- }
- }
-
- public void add(ServiceReference reference) {
- List /* <String> */ keys = createKeys(reference);
- synchronized (m_keyToServiceReferencesMap) {
- for (int i = 0; i < keys.size(); i++) {
- List /* <ServiceReference> */ references = (List) m_keyToServiceReferencesMap.get(keys.get(i));
- if (references == null) {
- references = new ArrayList();
- m_keyToServiceReferencesMap.put(keys.get(i), references);
- }
- references.add(reference);
- }
- }
- }
-
- public void modify(ServiceReference reference) {
- List /* <String> */ keys = createKeys(reference);
- synchronized (m_keyToServiceReferencesMap) {
- // TODO this is a quite expensive linear scan over the existing collection
- // because we first need to remove any existing references and they can be
- // all over the place :)
- Iterator iterator = m_keyToServiceReferencesMap.values().iterator();
- while (iterator.hasNext()) {
- List /* <ServiceReference> */ list = (List) iterator.next();
- if (list != null) {
- Iterator i2 = list.iterator();
- while (i2.hasNext()) {
- ServiceReference ref = (ServiceReference) i2.next();
- if (ref.equals(reference)) {
- i2.remove();
- }
- }
- }
- }
-
- for (int i = 0; i < keys.size(); i++) {
- List /* <ServiceReference> */ references = (List) m_keyToServiceReferencesMap.get(keys.get(i));
- if (references == null) {
- references = new ArrayList();
- m_keyToServiceReferencesMap.put(keys.get(i), references);
- }
- references.add(reference);
- }
- }
- }
-
- public void remove(ServiceReference reference) {
- List /* <String> */ keys = createKeys(reference);
- synchronized (m_keyToServiceReferencesMap) {
- for (int i = 0; i < keys.size(); i++) {
- List /* <ServiceReference> */ references = (List) m_keyToServiceReferencesMap.get(keys.get(i));
- if (references != null) {
- references.remove(reference);
- }
- }
- }
- }
-
- public boolean isApplicable(String[] propertyKeys) {
- TreeSet list = new TreeSet(String.CASE_INSENSITIVE_ORDER);
- for (int i = 0; i < propertyKeys.length; i++) {
- list.add(propertyKeys[i]);
- }
- Iterator iterator = m_propertyKeys.iterator();
- while (iterator.hasNext()) {
- String item = (String) iterator.next();
- if (!(list.contains(item))) {
- return false;
- }
- }
- return true;
- }
-
- public boolean isApplicable(String clazz, String filter) {
- // "(&(a=b)(c=d))"
- // "(&(&(a=b)(c=d))(objC=aaa))"
- // startsWith "(&" en split in "(x=y)" -> elke x bestaat in m_propertykeys
-
- // (&(objectClass=xyz)(&(a=x)(b=y)))
-
- Set /* <String> */found = new HashSet();
- if (filter != null && filter.startsWith("(&(") && filter.substring(3, 14).equalsIgnoreCase(Constants.OBJECTCLASS) && filter.substring(14, 15).equals("=") && filter.contains(")(&(") && filter.endsWith(")))")) {
- int i1 = filter.indexOf(")(&(");
- String className = filter.substring(("(&(" + Constants.OBJECTCLASS + "=").length(), i1);
- if (!m_propertyKeys.contains(Constants.OBJECTCLASS)) {
- return false;
- }
- else {
- found.add(Constants.OBJECTCLASS);
- }
- String[] parts = filter.substring(i1 + ")(&(".length(), filter.length() - ")))".length()).split("\\)\\(");
- for (int i = 0; i < parts.length; i++) {
- String part = parts[i];
- String[] tuple = part.split("=");
- if (!m_propertyKeys.contains(tuple[0])) {
- return false;
- }
- else {
- found.add(tuple[0]);
- }
- // TODO check value tuple[1]
- }
- return found.size() == m_propertyKeys.size();
- }
- else if (filter != null && filter.startsWith("(&(") && filter.endsWith("))")) {
- String[] parts = filter.substring(3, filter.length() - 2).split("\\)\\(");
- for (int i = 0; i < parts.length; i++) {
- String part = parts[i];
- String[] tuple = part.split("=");
- if (!m_propertyKeys.contains(tuple[0])) {
- return false;
- }
- else {
- found.add(tuple[0]);
- }
- // TODO check value tuple[1]
- }
- return found.size() == m_propertyKeys.size();
- }
- else if (filter != null && filter.startsWith("(") && filter.endsWith(")") && m_propertyKeys.size() == 1) { // TODO quick hack
- String part = filter.substring(1, filter.length() - 1);
- String[] tuple = part.split("=");
- if (!m_propertyKeys.contains(tuple[0])) {
- return false;
- }
- else {
- return true;
- }
- }
- else if (clazz != null && filter == null && m_propertyKeys.size() == 1 && Constants.OBJECTCLASS.equalsIgnoreCase((String) m_propertyKeys.first())) {
- return true;
- }
- return false;
- }
-
- private List /* <String> */ createKeys(ServiceReference reference) {
- List /* <String> */ results = new ArrayList();
-
- results.add(""); // ???
-
- String[] keys = reference.getPropertyKeys();
- Arrays.sort(keys, String.CASE_INSENSITIVE_ORDER);
- StringBuffer result = new StringBuffer();
- for (int i = 0; i < keys.length; i++) {
- String key = keys[i].toLowerCase();
- if (m_propertyKeys.contains(key)) {
- Object value = reference.getProperty(key);
- if (value instanceof String[]) {
- String[] values = (String[]) value;
- List newResults = new ArrayList();
- for (int j = 0; j < values.length; j++) {
- String val = values[j];
- for (int k = 0; k < results.size(); k++) {
- String head = (String) results.get(k);
- if (head != null && head.length() > 0) {
- head = head + ";";
- }
- newResults.add(head + key + "=" + val);
- }
- }
- results = newResults;
- }
- else {
- for (int k = 0; k < results.size(); k++) {
- String head = (String) results.get(k);
- if (head != null && head.length() > 0) {
- head = head + ";";
- }
- results.set(k, head + key + "=" + value);
- }
- }
- }
- }
- return results;
- }
-
- private List /* <String> */ createKeysFromFilter(String clazz, String filter) {
- List result = new ArrayList();
- StringBuffer index = new StringBuffer();
- Iterator iterator = m_propertyKeys.iterator();
- while (iterator.hasNext()) {
- String key = ((String) iterator.next()).toLowerCase();
- if (index.length() > 0) {
- index.append(';');
- }
- index.append(key);
- index.append('=');
- String value = null;
- if (clazz != null && Constants.OBJECTCLASS.equalsIgnoreCase(key)) {
- value = clazz;
- } // (&(obC=a)(&(a=b)(c=d)))
- if (filter != null) {
- String startString = "(" + key + "=";
- int i1 = filter.toLowerCase().indexOf(startString);
- if (i1 != -1) {
- int i2 = filter.indexOf(")(", i1);
- if (i2 == -1) {
- if (filter.endsWith(")))")) {
- i2 = filter.length() - 3;
- }
- else if (filter.endsWith("))")) {
- i2 = filter.length() - 2;
- }
- else {
- i2 = filter.length() - 1;
- }
- }
- String value2 = filter.substring(i1 + startString.length(), i2);
- if (value != null && !value.equals(value2)) {
- // corner case: someone specified a clazz and
- // also a filter containing a different clazz
- return result;
- }
- value = value2;
- }
- }
- index.append(value);
- }
- result.add(index.toString());
- return result;
- }
-
- public void serviceChanged(ServiceEvent event) {
- if (isApplicable(event.getServiceReference().getPropertyKeys())) {
- List /* <String> */ keys = createKeys(event.getServiceReference());
- List list = new ArrayList();
- synchronized (m_keyToListenersMap) {
- for (int i = 0; i < keys.size(); i++) {
- String key = (String) keys.get(i);
- List listeners = (List) m_keyToListenersMap.get(key);
- if (listeners != null) {
- list.addAll(listeners);
- }
- }
- }
- if (list != null) {
- Iterator iterator = list.iterator();
- while (iterator.hasNext()) {
- ServiceListener listener = (ServiceListener) iterator.next();
- listener.serviceChanged(event);
- }
- }
- }
- }
-
- public void addServiceListener(ServiceListener listener, String filter) {
- List keys = createKeysFromFilter(null, filter);
- Iterator iterator = keys.iterator();
- while (iterator.hasNext()) {
- String key = (String) iterator.next();
- synchronized (m_keyToListenersMap) {
- List /* <ServiceListener> */ listeners = (List) m_keyToListenersMap.get(key);
- if (listeners == null) {
- listeners = new CopyOnWriteArrayList();
- m_keyToListenersMap.put(key, listeners);
- }
- listeners.add(listener);
- m_listenerToFilterMap.put(listener, filter);
- }
- }
- }
-
- public void removeServiceListener(ServiceListener listener) {
- synchronized (m_keyToListenersMap) {
- String filter = (String) m_listenerToFilterMap.remove(listener);
- if (filter != null) {
- // the listener does exist
- List keys = createKeysFromFilter(null, filter);
- Iterator iterator = keys.iterator();
- while (iterator.hasNext()) {
- String key = (String) iterator.next();
-
- boolean result = filter != null;
- if (result) {
- List /* <ServiceListener> */ listeners = (List) m_keyToListenersMap.get(key);
- if (listeners != null) {
- listeners.remove(listener);
- }
- // TODO actually, if listeners == null that would be strange....
- }
- }
- }
- }
- }
-
- public String toString() {
- StringBuffer sb = new StringBuffer();
- sb.append("MultiPropertyExactFilter[");
- sb.append("K2L: " + m_keyToListenersMap.size());
- sb.append(", K2SR: " + m_keyToServiceReferencesMap.size());
- sb.append(", L2F: " + m_listenerToFilterMap.size());
- sb.append("]");
- return sb.toString();
- }
-}
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/ServiceRegistryCache.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/ServiceRegistryCache.java
index 41f7fd6..bbb0995 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/ServiceRegistryCache.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/ServiceRegistryCache.java
@@ -18,6 +18,7 @@
*/
package org.apache.felix.dm.impl.index;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -25,6 +26,7 @@
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.felix.dm.FilterIndex;
+import org.apache.felix.dm.impl.Logger;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
@@ -34,7 +36,8 @@
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class ServiceRegistryCache implements ServiceListener/*, CommandProvider*/ {
- private final List /* <FilterIndex> */ m_filterIndexList = new CopyOnWriteArrayList();
+ private static final String INDEX_PERFLOG = "org.apache.felix.dependencymanager.index.logmissingindices";
+ private final List /* <FilterIndex> */ m_filterIndexList = new CopyOnWriteArrayList();
private final BundleContext m_context;
private final FilterIndexBundleContext m_filterIndexBundleContext;
private final Map /* <BundleContext, BundleContextInterceptor> */ m_bundleContextInterceptorMap = new HashMap();
@@ -42,10 +45,16 @@
private long m_arrayVersion = -1;
private BundleContextInterceptor[] m_interceptors = null;
private ServiceRegistration m_registration;
-
+ private boolean m_dumpUnIndexedFilters = "true".equals(System.getProperty(INDEX_PERFLOG));
+ private List m_unindexedFilters = new ArrayList();
+ private Logger m_logger;
public ServiceRegistryCache(BundleContext context) {
m_context = context;
+ // only obtain the logservice when we actually want to log something.
+ if (System.getProperty(INDEX_PERFLOG) != null) {
+ m_logger = new Logger(context);
+ }
m_filterIndexBundleContext = new FilterIndexBundleContext(m_context);
}
@@ -89,7 +98,7 @@
synchronized (m_bundleContextInterceptorMap) {
BundleContextInterceptor bundleContextInterceptor = (BundleContextInterceptor) m_bundleContextInterceptorMap.get(context);
if (bundleContextInterceptor == null) {
- bundleContextInterceptor = new BundleContextInterceptor(this, context);
+ bundleContextInterceptor = new BundleContextInterceptor(this, context, m_logger);
m_bundleContextInterceptorMap.put(context, bundleContextInterceptor);
m_currentVersion++;
// TODO figure out a good way to clean up bundle contexts that are no longer valid so they can be garbage collected
@@ -106,6 +115,13 @@
return filterIndex;
}
}
+ if (m_dumpUnIndexedFilters) {
+ String filterStr = clazz + ":" + filter;
+ if (!m_unindexedFilters.contains(filterStr)) {
+ m_unindexedFilters.add(filterStr);
+ m_logger.log(Logger.LOG_DEBUG, "No filter index for " + filterStr);
+ }
+ }
return null;
}
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/Filter.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/Filter.java
new file mode 100644
index 0000000..66f4731
--- /dev/null
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/Filter.java
@@ -0,0 +1,149 @@
+/*
+ * 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.dm.impl.index.multiproperty;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class Filter {
+
+ private boolean m_valid = true;
+ private Map /* <String, Property> */ m_properties = new HashMap();
+ private Set /* <String> */ m_propertyKeys = new TreeSet(String.CASE_INSENSITIVE_ORDER);
+
+ private Filter() {
+
+ }
+
+ // Sample valid filter string (&(objectClass=OBJECTCLASS)(&(model=MODEL)(concept=CONCEPT)(role=ROLE)(!(context=*))))
+ public static Filter parse(String filterString) {
+ Filter filter = new Filter();
+ StringTokenizer tokenizer = new StringTokenizer(filterString, "(&|=)", true);
+
+ String token = null;
+ String prevToken = null;
+ String key = null;
+ StringBuilder valueBuilder = new StringBuilder();
+ boolean negate = false;
+
+ while (tokenizer.hasMoreTokens()) {
+ prevToken = token;
+ token = tokenizer.nextToken();
+ if (token.equals("|")) {
+ // we're not into OR's
+ filter.m_valid = false;
+ break;
+ }
+ if (token.equals("!")) {
+ negate = true;
+ } else if (token.equals("=")) {
+ key = prevToken.toLowerCase();
+ } else if (key != null) {
+ if (!token.equals(")")) {
+ valueBuilder.append(token); // might be superseded by a &
+ }
+ if (token.equals(")")) {
+ // set complete
+ if (filter.m_properties.containsKey(key)) {
+ // set current property to multivalue
+ Property property = (Property) filter.m_properties.get(key);
+ property.addValue(valueBuilder.toString(), negate);
+ } else {
+ Property property = new Property(negate, key, valueBuilder.toString());
+ filter.m_properties.put(key, property);
+ filter.m_propertyKeys.add(key);
+ }
+ negate = false;
+ key = null;
+ valueBuilder = new StringBuilder();
+ }
+ }
+ }
+ return filter;
+ }
+
+ public boolean containsProperty(String propertyKey) {
+ return m_properties.containsKey(propertyKey);
+ }
+
+ public Set /* <String> */ getPropertyKeys() {
+ return m_properties.keySet();
+ }
+
+ public Property getProperty(String key) {
+ return (Property) m_properties.get(key);
+ }
+
+ public boolean isValid() {
+ if (!m_valid) {
+ return m_valid;
+ } else {
+ // also check the properties
+ Iterator propertiesIterator = m_properties.values().iterator();
+ while (propertiesIterator.hasNext()) {
+ Property property = (Property) propertiesIterator.next();
+ if (!property.isValid()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ public static void main(String args[]) {
+ Filter parser = Filter.parse("(&(objectClass=OBJECTCLASS)(&(a=x)(a=n)(a=y)(b=y)(c=z)))");
+ System.out.println("key: " + parser.createKey());
+ }
+
+ protected String createKey() {
+ StringBuilder builder = new StringBuilder();
+ Iterator keys = m_propertyKeys.iterator();
+
+ while (keys.hasNext()) {
+ String key = (String) keys.next();
+ Property prop = (Property) m_properties.get(key);
+ if (!prop.isWildcard()) {
+ Iterator values = prop.getValues().iterator();
+ while (values.hasNext()) {
+ String value = (String) values.next();
+ builder.append(key);
+ builder.append("=");
+ builder.append(value);
+ if (keys.hasNext() || values.hasNext()) {
+ builder.append(";");
+ }
+ }
+ }
+ }
+ // strip the final ';' in case the last key was a wildcard property
+ if (builder.charAt(builder.length() - 1) == ';') {
+ return builder.toString().substring(0, builder.length() - 1);
+ } else {
+ return builder.toString();
+ }
+ }
+
+}
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyFilterIndex.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyFilterIndex.java
new file mode 100644
index 0000000..513be72
--- /dev/null
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyFilterIndex.java
@@ -0,0 +1,486 @@
+/*
+ * 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.dm.impl.index.multiproperty;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.felix.dm.FilterIndex;
+import org.apache.felix.dm.tracker.ServiceTracker;
+import org.apache.felix.dm.tracker.ServiceTrackerCustomizer;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class MultiPropertyFilterIndex implements FilterIndex, ServiceTrackerCustomizer {
+
+ private final Object m_lock = new Object();
+ private ServiceTracker m_tracker;
+ private BundleContext m_context;
+ private Map /* <String, Property> */ m_configProperties = new LinkedHashMap();
+ private List /* <String> */ m_negatePropertyKeys = new ArrayList();
+ private final Map /* <String, List<ServiceReference>> */ m_keyToServiceReferencesMap = new HashMap();
+ private final Map /* <String, List<ServiceListener>> */ m_keyToListenersMap = new HashMap();
+ private final Map /* <ServiceListener, String> */ m_listenerToFilterMap = new HashMap();
+
+ public MultiPropertyFilterIndex(String configString) {
+ parseConfig(configString);
+ }
+
+ public boolean isApplicable(String clazz, String filterString) {
+ Filter filter = createFilter(clazz, filterString);
+
+ if (!filter.isValid()) {
+ return false;
+ }
+ // compare property keys to the ones in the configuration
+ Set /* <String> */ filterPropertyKeys = filter.getPropertyKeys();
+ if (m_configProperties.size() != filterPropertyKeys.size()) {
+ return false;
+ }
+ Iterator filterPropertyKeysIterator = filterPropertyKeys.iterator();
+ while (filterPropertyKeysIterator.hasNext()) {
+ String filterPropertyKey = (String) filterPropertyKeysIterator.next();
+ if (!m_configProperties.containsKey(filterPropertyKey)) {
+ return false;
+ } else if (((Property)m_configProperties.get(filterPropertyKey)).isNegate() != filter.getProperty(filterPropertyKey).isNegate()) {
+ // negation should be equal
+ return false;
+ } else if (!filter.getProperty(filterPropertyKey).isNegate() && filter.getProperty(filterPropertyKey).getValue().equals("*")) {
+ // no wildcards without negation allowed
+ return false;
+ }
+ }
+ // our properties match so we're applicable
+ return true;
+ }
+
+ public boolean isApplicable(ServiceReference ref) {
+ String[] propertyKeys = ref.getPropertyKeys();
+ TreeSet referenceProperties = new TreeSet(String.CASE_INSENSITIVE_ORDER);
+ for (int i = 0; i < propertyKeys.length; i++) {
+ referenceProperties.add(propertyKeys[i]);
+ }
+ Iterator iterator = m_configProperties.keySet().iterator();
+ while (iterator.hasNext()) {
+ String item = (String) iterator.next();
+ Property configProperty = (Property) m_configProperties.get(item);
+ if (!configProperty.isNegate() && !(referenceProperties.contains(item))) {
+ return false;
+ } else if (configProperty.isNegate() && referenceProperties.contains(item)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void parseConfig(String configString) {
+ String[] propertyConfigs = configString.split(",");
+ for (int i = 0; i < propertyConfigs.length; i++) {
+ String propertyConfig = propertyConfigs[i];
+ Property property = new Property();
+ String key;
+ String value = null;
+ if (propertyConfig.startsWith("!")) {
+ property.setNegate(true);
+ key = propertyConfig.substring(1);
+ } else {
+ key = propertyConfig;
+ }
+ if (key.endsWith("*")) {
+ key = key.substring(0, key.indexOf("*"));
+ value = "*";
+ }
+ property.setKey(key.toLowerCase());
+ property.addValue(value, property.isNegate());
+ m_configProperties.put(key.toLowerCase(), property);
+ if (property.isNegate()) {
+ m_negatePropertyKeys.add(key);
+ }
+ }
+ }
+
+ protected Collection /* <Property> */ getProperties() {
+ return m_configProperties.values();
+ }
+
+ protected String createKeyFromFilter(String clazz, String filterString) {
+ return createFilter(clazz, filterString).createKey();
+ }
+
+ private Filter createFilter(String clazz, String filterString) {
+ String filterStringWithObjectClass = filterString;
+ if (clazz != null) {
+ if (filterString != null) {
+ if (!filterStringWithObjectClass.startsWith("(&(objectClass=")) {
+ filterStringWithObjectClass = "(&(objectClass=" + clazz + ")" + filterString + ")";
+ }
+ } else {
+ filterStringWithObjectClass = "(objectClass=" + clazz + ")";
+ }
+ }
+ Filter filter = Filter.parse(filterStringWithObjectClass);
+ return filter;
+ }
+
+ protected List createKeys(ServiceReference reference) {
+ List /* <String> */ results = new ArrayList();
+ List sets = new ArrayList();
+ String[] keys = reference.getPropertyKeys();
+ Arrays.sort(keys, String.CASE_INSENSITIVE_ORDER);
+ for (int i = 0; i < keys.length; i++) {
+ List set = new ArrayList();
+ String key = keys[i].toLowerCase();
+ if (m_configProperties.containsKey(key)) {
+ Object valueObject = reference.getProperty(key);
+ if (valueObject instanceof String[]) {
+ set.addAll(getPermutations(key, (String[]) valueObject));
+ } else {
+ set.add(toKey(key, valueObject));
+ }
+ sets.add(set);
+ }
+ }
+
+ List reversedSets = new ArrayList();
+ int size = sets.size();
+ for (int i = size - 1; i > -1; i--) {
+ reversedSets.add(sets.get(i));
+ }
+ List products = carthesianProduct(0, reversedSets);
+ // convert sets into strings
+ for (int i = 0; i < products.size(); i++) {
+ List set = (List) products.get(i);
+ StringBuilder b = new StringBuilder();
+ for (int j = 0; j < set.size(); j++) {
+ String item = (String) set.get(j);
+ b.append(item);
+ if (j < set.size() - 1) {
+ b.append(";");
+ }
+ }
+ results.add(b.toString());
+ }
+
+ return results;
+ }
+
+ /**
+ * Note that we calculate the carthesian product for multi value properties. Use filters on these sparingly since memory
+ * consumption can get really high when multiple properties have a lot of values.
+ *
+ * @param index
+ * @param sets
+ * @return
+ */
+ private List carthesianProduct(int index, List sets) {
+ List result = new ArrayList();
+ if (index == sets.size()) {
+ result.add(new ArrayList());
+ } else {
+ List set = (List) sets.get(index);
+ for (int i = 0; i < set.size(); i++) {
+ Object object = set.get(i);
+ List pSets = carthesianProduct(index + 1, sets);
+ for (int j = 0; j < pSets.size(); j++) {
+ List pSet = (List) pSets.get(j);
+ pSet.add(object);
+ result.add(pSet);
+ }
+ }
+ }
+ return result;
+ }
+
+ List getPermutations(String key, String[] values) {
+ List results = new ArrayList();
+ Arrays.sort(values, String.CASE_INSENSITIVE_ORDER);
+ for (int v = 0; v < values.length; v++) {
+ String processValue = values[v];
+ List /* <String> */ items = new ArrayList();
+ items.add(processValue);
+ // per value get combinations
+ List /* <String> */ subItems = new ArrayList(items);
+ for (int w = v; w < values.length; w++) {
+ // make a copy of the current list
+ subItems = new ArrayList(subItems);
+ if (w != v) {
+ String value = values[w];
+ subItems.add(value);
+ }
+ results.add(toKey(key, subItems));
+ }
+ }
+ return results;
+ }
+
+ protected String toKey(String key, List values) {
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < values.size(); i++) {
+ builder.append(toKey(key, (String) values.get(i)));
+ if (i < values.size() - 1) {
+ builder.append(";");
+ }
+ }
+ return builder.toString();
+ }
+
+ protected String toKey(String key, Object value) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(key);
+ builder.append("=");
+ builder.append(value.toString());
+ return builder.toString();
+ }
+
+ public Object addingService(ServiceReference reference) {
+ BundleContext context;
+ synchronized (m_lock) {
+ context = m_context;
+ }
+ if (context != null) {
+ return context.getService(reference);
+ }
+ else {
+ throw new IllegalStateException("No valid bundle context.");
+ }
+ }
+
+ public void addedService(ServiceReference reference, Object service) {
+ if (isApplicable(reference) && shouldBeIndexed(reference)) {
+ handleServiceAdd(reference);
+ }
+ }
+
+ public void modifiedService(ServiceReference reference, Object service) {
+ if (isApplicable(reference)) {
+ handleServicePropertiesChange(reference);
+ }
+ }
+
+ public void removedService(ServiceReference reference, Object service) {
+ if (isApplicable(reference) && shouldBeIndexed(reference)) {
+ handleServiceRemove(reference);
+ }
+ }
+
+ protected void handleServiceAdd(ServiceReference reference) {
+ List /* <String> */ keys = createKeys(reference);
+ synchronized (m_keyToServiceReferencesMap) {
+ for (int i = 0; i < keys.size(); i++) {
+ List /* <ServiceReference> */ references = (List) m_keyToServiceReferencesMap.get(keys.get(i));
+ if (references == null) {
+ references = new ArrayList();
+ m_keyToServiceReferencesMap.put(keys.get(i), references);
+ }
+ references.add(reference);
+ }
+ }
+ }
+
+ protected void handleServicePropertiesChange(ServiceReference reference) {
+
+ synchronized (m_keyToServiceReferencesMap) {
+ // TODO this is a quite expensive linear scan over the existing collection
+ // because we first need to remove any existing references and they can be
+ // all over the place :)
+ Iterator iterator = m_keyToServiceReferencesMap.values().iterator();
+ while (iterator.hasNext()) {
+ List /* <ServiceReference> */ list = (List) iterator.next();
+ if (list != null) {
+ Iterator i2 = list.iterator();
+ while (i2.hasNext()) {
+ ServiceReference ref = (ServiceReference) i2.next();
+ if (ref.equals(reference)) {
+ i2.remove();
+ }
+ }
+ }
+ }
+ // only re-add the reference when it is still applicable for this filter index
+ if (shouldBeIndexed(reference)) {
+ List /* <String> */ keys = createKeys(reference);
+ for (int i = 0; i < keys.size(); i++) {
+ List /* <ServiceReference> */ references = (List) m_keyToServiceReferencesMap.get(keys.get(i));
+ if (references == null) {
+ references = new ArrayList();
+ m_keyToServiceReferencesMap.put(keys.get(i), references);
+ }
+ references.add(reference);
+ }
+ }
+ }
+ }
+
+ protected void handleServiceRemove(ServiceReference reference) {
+ List /* <String> */ keys = createKeys(reference);
+ synchronized (m_keyToServiceReferencesMap) {
+ for (int i = 0; i < keys.size(); i++) {
+ List /* <ServiceReference> */ references = (List) m_keyToServiceReferencesMap.get(keys.get(i));
+ if (references != null) {
+ references.remove(reference);
+ if (references.isEmpty()) {
+ m_keyToServiceReferencesMap.remove(keys.get(i));
+ }
+ }
+ }
+ }
+ }
+
+ protected boolean shouldBeIndexed(ServiceReference reference) {
+ // is already applicable, so we should only check whether there's a negate field in the filter which has a value in the reference
+ Iterator negatePropertyKeyIterator = m_negatePropertyKeys.iterator();
+ while (negatePropertyKeyIterator.hasNext()) {
+ String negatePropertyKey = (String) negatePropertyKeyIterator.next();
+ if (reference.getProperty(negatePropertyKey) != null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void open(BundleContext context) {
+ synchronized (m_lock) {
+ if (m_context != null) {
+ throw new IllegalStateException("Filter already open.");
+ }
+ try {
+ m_tracker = new ServiceTracker(context, context.createFilter("(" + Constants.OBJECTCLASS + "=*)"), this);
+ }
+ catch (InvalidSyntaxException e) {
+ throw new Error();
+ }
+ m_context = context;
+ }
+ m_tracker.open(true, true);
+ }
+
+ public void close() {
+ ServiceTracker tracker;
+ synchronized (m_lock) {
+ if (m_context == null) {
+ throw new IllegalStateException("Filter already closed.");
+ }
+ tracker = m_tracker;
+ m_tracker = null;
+ m_context = null;
+ }
+ tracker.close();
+ }
+
+ public List /* <ServiceReference> */ getAllServiceReferences(String clazz, String filter) {
+ List /* <ServiceReference> */ result = new ArrayList();
+ String key = createKeyFromFilter(clazz, filter);
+ ServiceReference reference;
+ synchronized (m_keyToServiceReferencesMap) {
+ List references = (List) m_keyToServiceReferencesMap.get(key);
+ if (references != null) {
+ result.addAll(references);
+ }
+ }
+ return result;
+ }
+
+ public void serviceChanged(ServiceEvent event) {
+ if (isApplicable(event.getServiceReference())) {
+ List /* <String> */ keys = createKeys(event.getServiceReference());
+ List list = new ArrayList();
+ synchronized (m_keyToListenersMap) {
+ for (int i = 0; i < keys.size(); i++) {
+ String key = (String) keys.get(i);
+ List listeners = (List) m_keyToListenersMap.get(key);
+ if (listeners != null) {
+ list.addAll(listeners);
+ }
+ }
+ }
+ if (list != null) {
+ Iterator iterator = list.iterator();
+ while (iterator.hasNext()) {
+ ServiceListener listener = (ServiceListener) iterator.next();
+ listener.serviceChanged(event);
+ }
+ }
+ }
+ }
+
+ public void addServiceListener(ServiceListener listener, String filter) {
+ String key = createKeyFromFilter(null, filter);
+ synchronized (m_keyToListenersMap) {
+ List /* <ServiceListener> */ listeners = (List) m_keyToListenersMap.get(key);
+ if (listeners == null) {
+ listeners = new CopyOnWriteArrayList();
+ m_keyToListenersMap.put(key, listeners);
+ }
+ listeners.add(listener);
+ m_listenerToFilterMap.put(listener, filter);
+ }
+ }
+
+ public void removeServiceListener(ServiceListener listener) {
+ synchronized (m_keyToListenersMap) {
+ String filter = (String) m_listenerToFilterMap.remove(listener);
+ if (filter != null) {
+ // the listener does exist
+ String key = createKeyFromFilter(null, filter);
+
+ boolean result = filter != null;
+ if (result) {
+ List /* <ServiceListener> */ listeners = (List) m_keyToListenersMap.get(key);
+ if (listeners != null) {
+ listeners.remove(listener);
+ if (listeners.isEmpty()) {
+ m_keyToListenersMap.remove(key);
+ }
+ }
+ // TODO actually, if listeners == null that would be strange....
+ }
+ }
+ }
+ }
+
+ protected Collection getServiceListeners() {
+ return m_listenerToFilterMap.keySet();
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(" dMultiPropertyExactFilter[");
+ sb.append("K2L: " + m_keyToListenersMap.size());
+ sb.append(", K2SR: " + m_keyToServiceReferencesMap.size());
+ sb.append(", L2F: " + m_listenerToFilterMap.size());
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/Property.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/Property.java
new file mode 100644
index 0000000..e4b466d
--- /dev/null
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/Property.java
@@ -0,0 +1,99 @@
+/*
+ * 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.dm.impl.index.multiproperty;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+import java.util.Set;
+import java.util.TreeSet;
+
+public class Property {
+ boolean m_negate;
+ boolean m_valid = true;
+ String m_key;
+ String m_value;
+ Set m_values = new TreeSet(String.CASE_INSENSITIVE_ORDER);
+
+ public Property() {
+ }
+
+ public Property(boolean negate, String key, String value) {
+ super();
+ this.m_negate = negate;
+ this.m_key = key.toLowerCase();
+ this.m_values.add(value);
+ this.m_value = value;
+ }
+
+ public void setNegate(boolean negate) {
+ this.m_negate = negate;
+ }
+
+ public void setKey(String key) {
+ this.m_key = key.toLowerCase();
+ }
+
+ public void addValue(String value, boolean negate) {
+ if (this.m_negate != negate) {
+ // multiproperty with different negations, causes invalid configuration.
+ m_valid = false;
+ }
+ if (this.m_value == null) {
+ this.m_value = value;
+ }
+ m_values.add(value);
+ }
+
+ public boolean isNegate() {
+ return m_negate;
+ }
+
+ public String getKey() {
+ return m_key;
+ }
+
+ public String getValue() {
+ return m_value;
+ }
+
+ public Set getValues() {
+ return m_values;
+ }
+
+ public boolean isWildcard() {
+ return "*".equals(m_value);
+ }
+
+ public boolean isMultiValue() {
+ return m_values.size() > 1;
+ }
+
+ public String toString() {
+ return "Property [negate=" + m_negate + ", key=" + m_key + ", values="
+ + m_values + "]";
+ }
+
+ public boolean isValid() {
+ return m_valid;
+ }
+}
\ No newline at end of file