You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by pd...@apache.org on 2017/04/26 23:55:58 UTC

svn commit: r1792829 - in /felix/trunk/dependencymanager/org.apache.felix.dependencymanager: src/org/apache/felix/dm/impl/index/multiproperty/ test/test/

Author: pderop
Date: Wed Apr 26 23:55:58 2017
New Revision: 1792829

URL: http://svn.apache.org/viewvc?rev=1792829&view=rev
Log:
FELIX-5619: MultiProperyFilterIndex memory consumption

Added:
    felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyKey.java
    felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/MultiPropertyFilterIndexPerformanceTest.java
    felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/MultiPropertyFilterIndexReferencedTest.java
    felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/MultiPropertyKeyTest.java
    felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/TestReference.java
Modified:
    felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/Filter.java
    felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyFilterIndex.java

Modified: felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/Filter.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/Filter.java?rev=1792829&r1=1792828&r2=1792829&view=diff
==============================================================================
--- felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/Filter.java (original)
+++ felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/Filter.java Wed Apr 26 23:55:58 2017
@@ -111,15 +111,9 @@ public class Filter {
 		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();
+	protected MultiPropertyKey createKey(int filterSize) {
+		MultiPropertyKey multiPropertyKey = new MultiPropertyKey(0);
 		Iterator<String> keys = m_propertyKeys.iterator();
-		
 		while (keys.hasNext()) {
 			String key = keys.next();
 			Property prop = m_properties.get(key);
@@ -127,21 +121,17 @@ public class Filter {
 				Iterator<String> values = prop.getValues().iterator();
 				while (values.hasNext()) {
 					String value = values.next();
-					builder.append(key);
-					builder.append("=");
-					builder.append(value);
-					if (keys.hasNext() || values.hasNext()) {
-						builder.append(";");
-					}
+					multiPropertyKey.append(toKey(key, value));
 				}
 			}
 		}
-		// 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();
-		}
+		return multiPropertyKey;
 	}
 	
+    protected MultiPropertyKey toKey(String key, Object value) {
+    	MultiPropertyKey multiPropertyKey = new MultiPropertyKey(1);    
+    	multiPropertyKey.add(key, value.toString());
+    	return multiPropertyKey;
+    }
+	
 }

Modified: felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyFilterIndex.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyFilterIndex.java?rev=1792829&r1=1792828&r2=1792829&view=diff
==============================================================================
--- felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyFilterIndex.java (original)
+++ felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyFilterIndex.java Wed Apr 26 23:55:58 2017
@@ -1,20 +1,12 @@
 /*
- * 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
+ * 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
+ * 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.
+ * 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;
 
@@ -46,22 +38,29 @@ import org.osgi.framework.ServiceReferen
 @SuppressWarnings("rawtypes")
 public class MultiPropertyFilterIndex implements FilterIndex, ServiceTrackerCustomizer {
 
-    private final Object m_lock = new Object();
-    private ServiceTracker m_tracker;
-    private BundleContext m_context;
+	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<>();
+
+	private final Map<MultiPropertyKey, List<ServiceReference>> m_keyToServiceReferencesMap = new HashMap<>();
+
+	private final Map<MultiPropertyKey, 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;
 		}
@@ -70,6 +69,7 @@ public class MultiPropertyFilterIndex im
 		if (m_configProperties.size() != filterPropertyKeys.size()) {
 			return false;
 		}
+
 		Iterator<String> filterPropertyKeysIterator = filterPropertyKeys.iterator();
 		while (filterPropertyKeysIterator.hasNext()) {
 			String filterPropertyKey = filterPropertyKeysIterator.next();
@@ -81,31 +81,31 @@ public class MultiPropertyFilterIndex im
 			} 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<String> referenceProperties = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
-        for (int i = 0; i < propertyKeys.length; i++) {
-            referenceProperties.add(propertyKeys[i]);
-        }
-        Iterator<String> iterator = m_configProperties.keySet().iterator();
-        while (iterator.hasNext()) {
-            String item = iterator.next();
-            Property configProperty = m_configProperties.get(item);
-            if (!configProperty.isNegate() && !(referenceProperties.contains(item))) {
-                return false;
-            } else if (configProperty.isNegate() && referenceProperties.contains(item)) {
-            	return false;
-            }
-        }
-        return true;
-    }
-	
+
+	public boolean isApplicable(ServiceReference ref) {
+		String[] propertyKeys = ref.getPropertyKeys();
+		TreeSet<String> referenceProperties = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+		for (int i = 0; i < propertyKeys.length; i++) {
+			referenceProperties.add(propertyKeys[i]);
+		}
+		Iterator<String> iterator = m_configProperties.keySet().iterator();
+		while (iterator.hasNext()) {
+			String item = iterator.next();
+			Property configProperty = 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++) {
@@ -134,18 +134,20 @@ public class MultiPropertyFilterIndex im
 			}
 		}
 	}
-	
+
 	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) {
+
+	protected MultiPropertyKey createKeyFromFilter(String clazz, String filterString) {
+		int filterSize = m_configProperties.size();
+		return createFilter(clazz, filterString).createKey(filterSize);
+	}
+
+	//KEYS OF A FILTER
+	private Filter createFilter(String clazz, String filterString) {
 		String filterStringWithObjectClass = filterString;
-		if (clazz != null) {
+		if (clazz != null && !clazz.isEmpty()) {
 			if (filterString != null) {
 				if (!filterStringWithObjectClass.startsWith("(&(objectClass=")) {
 					filterStringWithObjectClass = "(&(objectClass=" + clazz + ")" + filterString + ")";
@@ -156,84 +158,99 @@ public class MultiPropertyFilterIndex im
 		}
 		Filter filter = Filter.parse(filterStringWithObjectClass);
 		return filter;
-    }
-    
-	protected List<String> createKeys(ServiceReference reference) {
-		List<String> results = new ArrayList<>();
-		List<List<String>> sets = new ArrayList<>();
+	}
+
+	public List<MultiPropertyKey> createKeys(ServiceReference reference) {
+		List<MultiPropertyKey> results = new ArrayList<>();
+
 		String[] keys = reference.getPropertyKeys();
 		Arrays.sort(keys, String.CASE_INSENSITIVE_ORDER);
+
+		MultiPropertyKey multiPropertyKey = new MultiPropertyKey(0);
+		List<MultiPropertyKey> permutations = new ArrayList<>();
+
+		List<List<MultiPropertyKey>> sets = new ArrayList<>();
+
 		for (int i = 0; i < keys.length; i++) {
-			List<String> set = new ArrayList<>();
-			String key = keys[i].toLowerCase();
-    		if (m_configProperties.containsKey(key)) {
+			String key = null;
+			boolean hasUpperCase = !keys[i].equals(keys[i].toLowerCase());
+			if(hasUpperCase) {
+				key = keys[i].toLowerCase(); 
+			} else  {
+				key = keys[i];
+			}
+			
+			if (m_configProperties.containsKey(key)) {
 				Object valueObject = reference.getProperty(key);
 				if (valueObject instanceof String[]) {
 					String[] values = (String[]) valueObject;
 					if (m_configProperties.get(key).isPermute()) {
-						set.addAll(getPermutations(key, values));
+						sets.add(getPermutations(key, values));
 					} else {
-						set.addAll(getSingleValues(key, values)); 
+						List<MultiPropertyKey> singleValues = new ArrayList<>();
+						for (int v = 0; v < values.length; v++) {
+							MultiPropertyKey single = new MultiPropertyKey(0);
+							single.append(toKey(key, values[v]));
+							singleValues.add(single);
+						}
+						sets.add(singleValues);
 					}
 				} else {
-					set.add(toKey(key, valueObject));
+					multiPropertyKey.append(toKey(key, (String) valueObject.toString()));
 				}
-				sets.add(set);
-    		}
+			}
 		}
 
-		List<List<String>> reversedSets = new ArrayList<>();
-		int size = sets.size();
-		for (int i = size - 1; i > -1; i--) {
-			reversedSets.add(sets.get(i));
-		}
-		List<List<String>> products = carthesianProduct(0, reversedSets);
-		// convert sets into strings
-		for (int i = 0; i < products.size(); i++) {
-			List<String> set = products.get(i);
-			StringBuilder b = new StringBuilder();
-			for (int j = 0; j < set.size(); j++) {
-				String item = set.get(j);
-				b.append(item);
-				if (j < set.size() - 1) {
-					b.append(";");
+		if (permutations != null && !permutations.isEmpty()) {
+			for (MultiPropertyKey permutation : permutations) {
+				permutation.append(multiPropertyKey);
+				results.add(permutation);
+			}
+		} else {
+			if (!sets.isEmpty()) {
+
+				List<List<MultiPropertyKey>> carthesianProductMultiProperty = carthesianProductMultiProperty(0, sets);
+				for (List<MultiPropertyKey> keyList : carthesianProductMultiProperty) {
+					MultiPropertyKey merged = new MultiPropertyKey(0);
+					merged.append(multiPropertyKey);
+					for (MultiPropertyKey single : keyList) {
+
+						merged.append(single);
+
+					}
+					results.add(merged);
 				}
+
+			} else {
+
+				results.add(multiPropertyKey);
 			}
-			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<List<String>> carthesianProduct(int index, List<List<String>> sets) {
-    	List<List<String>> result = new ArrayList<>();
-    	if (index == sets.size()) {
-    		result.add(new ArrayList<String>());
-    	} else {
-			List<String> set = sets.get(index);
+
+	private List<List<MultiPropertyKey>> carthesianProductMultiProperty(int index, List<List<MultiPropertyKey>> sets) {
+		List<List<MultiPropertyKey>> result = new ArrayList<>();
+		if (index == sets.size()) {
+			result.add(new ArrayList<MultiPropertyKey>());
+		} else {
+			List<MultiPropertyKey> set = sets.get(index);
 			for (int i = 0; i < set.size(); i++) {
-				String object = set.get(i);
-    			List<List<String>> pSets = carthesianProduct(index + 1, sets);
-    			for (int j = 0; j < pSets.size(); j++) {
-    				List<String> pSet = pSets.get(j);
-    				pSet.add(object);
-    				result.add(pSet);
-    			}
-    		}
-    	}
-    	return result;
-    }
-    
-    List<String> getPermutations(String key, String[] values) {
-    	List<String> results = new ArrayList<>();
+				MultiPropertyKey object = set.get(i);
+				List<List<MultiPropertyKey>> pSets = carthesianProductMultiProperty(index + 1, sets);
+				for (int j = 0; j < pSets.size(); j++) {
+					List<MultiPropertyKey> pSet = pSets.get(j);
+					pSet.add(object);
+					result.add(pSet);
+				}
+			}
+		}
+		return result;
+	}
+
+
+	List<MultiPropertyKey> getPermutations(String key, String[] values) {
+		List<MultiPropertyKey> results = new ArrayList<>();
 		Arrays.sort(values, String.CASE_INSENSITIVE_ORDER);
 		for (int v = 0; v < values.length; v++) {
 			String processValue = values[v];
@@ -252,259 +269,249 @@ public class MultiPropertyFilterIndex im
 			}
 		}
 		return results;
-    }
-    
-	private Collection<? extends String> getSingleValues(String key, String[] values) {
-		List<String> results = new ArrayList<>();
-		Arrays.sort(values, String.CASE_INSENSITIVE_ORDER);
-		for (int v = 0; v < values.length; v++) {
-			results.add(toKey(key, values[v]));
+	}
+
+
+	protected MultiPropertyKey toKey(String key, List<String> values) {
+		MultiPropertyKey kvc = new MultiPropertyKey(values.size());
+
+		for (int i = 0; i < values.size(); i++) {
+			kvc.add(key, values.get(i));
 		}
-		return results;
+		return kvc;
 	}
-    
-    protected String toKey(String key, List<String> values) {
-    	StringBuilder builder = new StringBuilder();
-    	for (int i = 0; i < values.size(); i++) {
-    		builder.append(toKey(key, 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();
-    }
-    
-    @SuppressWarnings("unchecked")
+
+	protected MultiPropertyKey toKey(String key, Object value) {
+		MultiPropertyKey kvc = new MultiPropertyKey(1);
+		kvc.add(key, value.toString());
+		return kvc;
+	}
+
 	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 = 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<List<ServiceReference>> iterator = m_keyToServiceReferencesMap.values().iterator();
-            while (iterator.hasNext()) {
-                List<ServiceReference> list = iterator.next();
-                if (list != null) {
-                    Iterator<ServiceReference> i2 = list.iterator();
-                    while (i2.hasNext()) {
-                        ServiceReference ref = 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 = 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 = 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<String> negatePropertyKeyIterator = m_negatePropertyKeys.iterator();
-    	while (negatePropertyKeyIterator.hasNext()) {
-    		String negatePropertyKey = 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);
-    }
+		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<MultiPropertyKey> keys = createKeys(reference);
+
+		synchronized (m_keyToServiceReferencesMap) {
+			for (int i = 0; i < keys.size(); i++) {
+				List<ServiceReference> references = m_keyToServiceReferencesMap.get(keys.get(i));
+				if (references == null) {
+					references = new ArrayList<>(1);
+					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<List<ServiceReference>> iterator = m_keyToServiceReferencesMap.values().iterator();
+			while (iterator.hasNext()) {
+				List<ServiceReference> list = iterator.next();
+				if (list != null) {
+					Iterator<ServiceReference> i2 = list.iterator();
+					while (i2.hasNext()) {
+						ServiceReference ref = 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<MultiPropertyKey> keys = createKeys(reference);
+				for (int i = 0; i < keys.size(); i++) {
+					List<ServiceReference> references = m_keyToServiceReferencesMap.get(keys.get(i));
+					if (references == null) {
+						references = new ArrayList<>(keys.size());
+						m_keyToServiceReferencesMap.put(keys.get(i), references);
+					}
+					references.add(reference);
+				}
+			}
+		}
+	}
+
+	protected void handleServiceRemove(ServiceReference reference) {
+		List<MultiPropertyKey> keys = createKeys(reference);
+		synchronized (m_keyToServiceReferencesMap) {
+			for (int i = 0; i < keys.size(); i++) {
+				List<ServiceReference> references = m_keyToServiceReferencesMap.get(keys.get(i));
+				if (references != null) {
+					references.remove(reference);
+					if (references.isEmpty()) {
+						m_keyToServiceReferencesMap.remove(keys.get(i));
+					} else {
+						((ArrayList) reference).trimToSize();
+					}
+				}
+			}
+		}
+	}
+
+	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<String> negatePropertyKeyIterator = m_negatePropertyKeys.iterator();
+		while (negatePropertyKeyIterator.hasNext()) {
+			String negatePropertyKey = 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);
-        synchronized (m_keyToServiceReferencesMap) {
-            List<ServiceReference> references = 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<ServiceListener> list = new ArrayList<ServiceListener>();
-            synchronized (m_keyToListenersMap) {
-                for (int i = 0; i < keys.size(); i++) {
-                    String key = keys.get(i);
-                    List<ServiceListener> listeners = m_keyToListenersMap.get(key);
-                    if (listeners != null) {
-                        list.addAll(listeners);
-                    }
-                }
-            }
-            if (list != null) {
-                Iterator<ServiceListener> iterator = list.iterator();
-                while (iterator.hasNext()) {
-                    ServiceListener listener = iterator.next();
-                    listener.serviceChanged(event);
-                }
-            }
-        }
-    }
-
-    public void addServiceListener(ServiceListener listener, String filter) {
-        String key = createKeyFromFilter(null, filter);
-        synchronized (m_keyToListenersMap) {
-            List<ServiceListener> listeners = m_keyToListenersMap.get(key);
-            if (listeners == null) {
-                listeners = new CopyOnWriteArrayList<ServiceListener>();
-                m_keyToListenersMap.put(key, listeners);
-            }
-            listeners.add(listener);
-            m_listenerToFilterMap.put(listener, filter);
-        }
-    }
-
-    public void removeServiceListener(ServiceListener listener) {
-        synchronized (m_keyToListenersMap) {
-            String filter = 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 = 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<ServiceListener> 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();
-    }
+		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<>();
+		MultiPropertyKey key = createKeyFromFilter(clazz, filter);
+		synchronized (m_keyToServiceReferencesMap) {
+			List<ServiceReference> references = m_keyToServiceReferencesMap.get(key);
+			if (references != null) {
+				result.addAll(references);
+			}
+		}
+		return result;
+	}
+
+	public void serviceChanged(ServiceEvent event) {
+		if (isApplicable(event.getServiceReference())) {
+			List<MultiPropertyKey> keys = createKeys(event.getServiceReference());
+			List<ServiceListener> list = new ArrayList<ServiceListener>();
+			synchronized (m_keyToListenersMap) {
+				for (int i = 0; i < keys.size(); i++) {
+					//TODO fix
+					MultiPropertyKey key = keys.get(i);
+					List<ServiceListener> listeners = m_keyToListenersMap.get(key);
+					if (listeners != null) {
+						list.addAll(listeners);
+					}
+				}
+			}
+			if (list != null) {
+				Iterator<ServiceListener> iterator = list.iterator();
+				while (iterator.hasNext()) {
+					ServiceListener listener = iterator.next();
+					listener.serviceChanged(event);
+				}
+			}
+		}
+	}
+
+	public void addServiceListener(ServiceListener listener, String filter) {
+		MultiPropertyKey key = createKeyFromFilter(null, filter);
+
+		synchronized (m_keyToListenersMap) {
+			List<ServiceListener> listeners = m_keyToListenersMap.get(key);
+			if (listeners == null) {
+				listeners = new CopyOnWriteArrayList<ServiceListener>();
+				m_keyToListenersMap.put(key, listeners);
+			}
+			listeners.add(listener);
+			m_listenerToFilterMap.put(listener, filter);
+		}
+	}
+
+	public void removeServiceListener(ServiceListener listener) {
+		synchronized (m_keyToListenersMap) {
+			String filter = m_listenerToFilterMap.remove(listener);
+			if (filter != null) {
+				// the listener does exist
+				MultiPropertyKey key = createKeyFromFilter(null, filter);
+
+				boolean result = filter != null;
+				if (result) {
+					List<ServiceListener> listeners = 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<ServiceListener> 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();
+	}
 
 	@Override
-	public void swappedService(ServiceReference reference, Object service,
-			ServiceReference newReference, Object newService) {
+	public void swappedService(ServiceReference reference, Object service, ServiceReference newReference, Object newService) {
 		addedService(newReference, newService);
 		removedService(reference, service);
 	}

Added: felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyKey.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyKey.java?rev=1792829&view=auto
==============================================================================
--- felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyKey.java (added)
+++ felix/trunk/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyKey.java Wed Apr 26 23:55:58 2017
@@ -0,0 +1,123 @@
+package org.apache.felix.dm.impl.index.multiproperty;
+
+import java.util.Arrays;
+
+public class MultiPropertyKey {
+
+	private MultiPropertyKeyPart[] properties;
+
+	public MultiPropertyKey(int filterSize) {
+		properties = new MultiPropertyKeyPart[0];
+	}
+
+	// Initial adding key values.
+	public void add(String key, String value) {
+		int newLength = properties.length + 1;
+		MultiPropertyKeyPart[] tmpKeys = new MultiPropertyKeyPart[newLength];
+		
+		System.arraycopy(properties, 0, tmpKeys, 0, properties.length);
+		tmpKeys[newLength-1] = new MultiPropertyKeyPart(key, value);
+		
+		this.properties = tmpKeys;
+		Arrays.sort(properties);
+	}
+
+	public void append(MultiPropertyKey other) {
+		int newLength = properties.length + other.properties.length;
+		MultiPropertyKeyPart[] tmpKeys = new MultiPropertyKeyPart[newLength];
+		
+		System.arraycopy(properties, 0, tmpKeys, 0, properties.length);
+		System.arraycopy(other.properties, 0, tmpKeys, properties.length, other.properties.length);
+	
+		this.properties = tmpKeys;
+		Arrays.sort(properties);
+	}
+
+	@Override
+	public int hashCode() {
+		return Arrays.hashCode(properties);
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj) {
+			return true;
+		}
+
+		if (!(obj instanceof MultiPropertyKey)) {
+			return false;
+		}
+
+		MultiPropertyKey other = (MultiPropertyKey) obj;
+
+		if (properties.length != other.properties.length) {
+			return false;
+		}
+
+		for (int i = 0; i < properties.length; i++) {
+			if (!(properties[i].equals(other.properties[i]))) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder builder = new StringBuilder();
+		for (int i = 0; i < properties.length; i++) {
+			builder.append(properties[i].key);
+			builder.append('=');
+			builder.append(properties[i].value);
+			if (i < properties.length - 1) {
+				builder.append(';');
+			}
+		}
+		return builder.toString();
+	}
+
+	// (key=value) part of a multi property index (&(key1=val1)(key2=val2) .. )
+	private class MultiPropertyKeyPart implements Comparable<MultiPropertyKeyPart> {
+		
+		private final String key;
+		private final String value;
+
+		public MultiPropertyKeyPart(String key, String value) {
+			this.key = key;
+			this.value = value;
+		}
+
+		@Override
+		public int hashCode() {
+			int h = 0;
+			h += 31 * key.hashCode();
+			h += value.hashCode();
+			return h;
+		}
+
+		@Override
+		public boolean equals(Object obj) {
+			if (this == obj)
+				return true;
+			if (!(obj instanceof MultiPropertyKeyPart))
+				return false;
+
+			MultiPropertyKeyPart object = (MultiPropertyKeyPart) obj;
+
+			if (!(this.key.equals(object.key)))
+				return false;
+			if (!(this.value.equals(object.value)))
+				return false;
+
+			return true;
+		}
+
+		@Override
+		public int compareTo(MultiPropertyKeyPart o) {
+			if (this.key.compareTo(o.key) == 0) {
+				return this.value.compareTo(o.value);
+			}
+			return this.key.compareTo(o.key);
+		}
+	}
+}
\ No newline at end of file

Added: felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/MultiPropertyFilterIndexPerformanceTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/MultiPropertyFilterIndexPerformanceTest.java?rev=1792829&view=auto
==============================================================================
--- felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/MultiPropertyFilterIndexPerformanceTest.java (added)
+++ felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/MultiPropertyFilterIndexPerformanceTest.java Wed Apr 26 23:55:58 2017
@@ -0,0 +1,83 @@
+package test;
+
+import static org.junit.Assert.assertFalse;
+
+import java.util.List;
+
+import org.apache.felix.dm.impl.index.multiproperty.MultiPropertyFilterIndex;
+import org.junit.Test;
+import org.osgi.framework.ServiceReference;
+
+public class MultiPropertyFilterIndexPerformanceTest {
+
+	final int testSize = 500000;
+
+	final int iterations = 10;
+	
+	@Test
+	public void MultiPropertyFilterIndexTest() {
+		MultiPropertyFilterIndex stringIndex;
+
+		for (int i = 0; i < iterations; i++) {
+			stringIndex = new MultiPropertyFilterIndex("component-identifier,model,concept,role");
+			testPerformance(stringIndex, testSize, "bystring");
+		}
+	}
+
+	
+	private void testPerformance(MultiPropertyFilterIndex filterIndex, int runSize, String indexName) {
+		System.gc();
+		long start = System.currentTimeMillis();
+		long memoryBefore = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+
+		for (int i = 0; i < runSize; i++) {
+			TestReference newReference = new TestReference();
+			String[] multiValue = { "CREATES", "UPDATES" };
+			String[] mv2 = { "extra1", "extra2", "extra3" };
+
+			newReference.addProperty("component-identifier",
+					"org.acme.xyz.platform.interfaces.modeldrivenservices.DialogService");
+			newReference.addProperty("model", "//Housing benefit request/Housing benefit request.model" + i);
+			newReference.addProperty("concept",
+					"//Housing benefit request/2000 Requests/40000/Housing benefit request.model#concept" + i);
+			newReference.addProperty("role", multiValue);
+			newReference.addProperty("extra", mv2);
+
+			filterIndex.addedService(newReference, new Object());
+		}
+
+		double writeTime = ((System.currentTimeMillis() - start) / 1000.0);
+		long startReading = System.currentTimeMillis();
+
+		for (int i = 0; i < runSize; i++) {
+			List<ServiceReference> allServiceReferences = filterIndex.getAllServiceReferences(null,
+					"(&(component-identifier=org.acme.xyz.platform.interfaces.modeldrivenservices.DialogService)"
+							+ "(model=//Housing benefit request/Housing benefit request.model" + i + ")"
+							+ "(concept=//Housing benefit request/2000 Requests/40000/Housing benefit request.model#concept"
+							+ i + ")" + "(role=CREATES))");
+
+			if (allServiceReferences.size() != 1) {
+				throw new AssertionError("Failed to find reference in cache");
+			}
+
+		}
+
+		// Sanitiy check
+		List<ServiceReference> allServiceReferences = filterIndex.getAllServiceReferences(null,
+				"(&(model=mymodel1)(concept=abracadabrra)(role=CREATES))");
+		if (allServiceReferences.size() != 0) {
+			assertFalse(allServiceReferences.size() != 0);
+		}
+
+		double readTime = ((System.currentTimeMillis() - startReading) / 1000.0);
+
+		System.gc();
+		long memoryAfter = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+		double consumed = (memoryAfter - memoryBefore) / 1048576.0;
+
+		System.err.println("w: " + writeTime + ", r: " + readTime + ",m: " + consumed + " # of iterations: " + runSize + " -- "
+				+ indexName);
+	}
+
+
+}

Added: felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/MultiPropertyFilterIndexReferencedTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/MultiPropertyFilterIndexReferencedTest.java?rev=1792829&view=auto
==============================================================================
--- felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/MultiPropertyFilterIndexReferencedTest.java (added)
+++ felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/MultiPropertyFilterIndexReferencedTest.java Wed Apr 26 23:55:58 2017
@@ -0,0 +1,275 @@
+package test;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.felix.dm.impl.index.multiproperty.MultiPropertyFilterIndex;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+
+public class MultiPropertyFilterIndexReferencedTest {
+
+	
+	@Test
+	public void noContext() {
+		MultiPropertyFilterIndex singleValueFilterIndex = new MultiPropertyFilterIndex("objectClass,!context");
+
+		TestReference ref1 = new TestReference();
+		ref1.addProperty("service.id", 4711);
+		ref1.addProperty("objectclass", "java.lang.String");
+		ref1.addProperty("context", "context");
+		
+		TestReference ref2 = new TestReference();
+		ref2.addProperty("service.id", 1000);
+		ref2.addProperty("objectclass", "java.lang.String");
+				
+		singleValueFilterIndex.addedService(ref1, new String("Service1"));
+		
+		singleValueFilterIndex.addedService(ref2, new String("Service2"));
+		
+		List<ServiceReference> allServiceReferencesByClass = singleValueFilterIndex.getAllServiceReferences("java.lang.String", null);
+		List<ServiceReference> allServiceReferencesByFilter = singleValueFilterIndex.getAllServiceReferences(null, "(objectClass=java.lang.String)");
+		
+		assertTrue(allServiceReferencesByClass.size() == allServiceReferencesByFilter.size());
+		assertTrue(allServiceReferencesByFilter.size() == 1);
+	}
+	
+	
+	@Test
+	public void noContextCid() {
+		MultiPropertyFilterIndex multiPropertyIndex_new = new MultiPropertyFilterIndex("objectClass,cid,!context");
+
+		TestReference ref1 = new TestReference();
+		ref1.addProperty("service.id", 4711);
+		ref1.addProperty("objectclass", "java.lang.String");
+		ref1.addProperty("context", "context");
+		
+		TestReference ref2 = new TestReference();
+		ref2.addProperty("service.id", 1000);
+		ref2.addProperty("cid", "cid2");
+		ref2.addProperty("objectclass", "java.lang.String");
+		
+		TestReference ref3 = new TestReference();
+		ref3.addProperty("cid", "cid2");
+		ref3.addProperty("rank", "1");
+		ref3.addProperty("service.id", 1000);
+		ref3.addProperty("objectclass", "java.lang.String");
+		
+		multiPropertyIndex_new.addedService(ref1, new String("Service1"));
+		
+		multiPropertyIndex_new.addedService(ref2, new String("Service2"));
+		
+		multiPropertyIndex_new.addedService(ref3, new String("Service3"));
+		
+		List<ServiceReference> result_new = multiPropertyIndex_new.getAllServiceReferences("java.lang.String", "(cid=cid2)");
+		
+		assertTrue(result_new.size() == 2);
+	}
+	
+	
+	
+
+	@Test
+	public void singleKeyfilterIndex() {
+		MultiPropertyFilterIndex multiPropertyIndex = new MultiPropertyFilterIndex("objectClass");
+		TestReference ref1 = new TestReference();
+		ref1.addProperty("service.id", 4711);
+		ref1.addProperty("objectclass", "java.lang.String");
+		
+		TestReference ref2 = new TestReference();
+		ref2.addProperty("service.id", 4711);
+		ref2.addProperty("objectclass", "java.lang.Object");
+		
+		multiPropertyIndex.addedService(ref1, new String("Service1"));
+		multiPropertyIndex.addedService(ref2, new Object());
+
+		//find by classname;
+		assertTrue(multiPropertyIndex.isApplicable("java.lang.String", ""));
+		List<ServiceReference> byClazzName = multiPropertyIndex.getAllServiceReferences("java.lang.String", "");
+		assertTrue(byClazzName.size() == 1);
+		assertTrue(byClazzName.get(0).equals(ref1));
+		
+		//find by filter
+		assertTrue(multiPropertyIndex.isApplicable(null, "(objectClass=java.lang.String)"));
+		byClazzName = multiPropertyIndex.getAllServiceReferences(null, "(objectClass=java.lang.String)");
+		assertTrue(byClazzName.size() == 1);
+		assertTrue(byClazzName.get(0).equals(ref1));
+		
+		//Add extra service
+		TestReference ref3 = new TestReference();
+		ref3.addProperty("service.id", 4712);
+		ref3.addProperty("objectclass", "java.lang.String");
+		multiPropertyIndex.addedService(ref3, new String("Service3"));
+		
+		byClazzName = multiPropertyIndex.getAllServiceReferences("java.lang.String", null);
+		assertTrue(byClazzName.size() == 2);
+		assertTrue(byClazzName.get(0).equals(ref1));
+		assertTrue(byClazzName.get(1).equals(ref3));
+	}
+	
+	@Test
+	public void propertyIndexWithDoubleNoPermutationKeys() {
+		String filterConfig =  "objectClass,#related-concept-absoluteidentifier,#context-concept-absoluteidentifier,StoreClass";
+	
+		MultiPropertyFilterIndex multiPropertyIndex_new = new MultiPropertyFilterIndex(filterConfig);
+	
+		String[] relatedConcepts = {"rel-a", "rel-b"};
+		String[] contextConcepts = {"cca-a", "cca-b"};
+		 
+		TestReference ref1 = new TestReference();
+		ref1.addProperty("service.id", 4711);
+		ref1.addProperty("objectclass", "java.lang.String");
+		ref1.addProperty("related-concept-absoluteidentifier", relatedConcepts);
+		ref1.addProperty("context-concept-absoluteidentifier", contextConcepts);
+		ref1.addProperty("StoreClass", "NoteStore");
+		
+		multiPropertyIndex_new.addedService(ref1, new String("Service1"));
+
+		List<ServiceReference> result_new = multiPropertyIndex_new.getAllServiceReferences("java.lang.String", "(&(context-concept-absoluteidentifier=cca-a)(related-concept-absoluteidentifier=rel-b)(storeclass=NoteStore))");
+
+		assertTrue(result_new.size() == 1);
+	}
+	
+	@Test
+	public void propertyIndexWithDoubleMultiProperty() {
+		String filterConfig =  "objectClass,related-concept-absoluteidentifier,context-concept-absoluteidentifier,StoreClass";
+		MultiPropertyFilterIndex multiPropertyIndex_new = new MultiPropertyFilterIndex(filterConfig);
+		
+	
+		String[] relatedConcepts = {"rel-a", "rel-b"};
+		String[] contextConcepts = {"cca-a", "cca-b"};
+		 
+		
+		TestReference ref1 = new TestReference();
+		ref1.addProperty("service.id", 4711);
+		ref1.addProperty("objectclass", "java.lang.String");
+		ref1.addProperty("related-concept-absoluteidentifier", relatedConcepts);
+		ref1.addProperty("context-concept-absoluteidentifier", contextConcepts);
+		ref1.addProperty("StoreClass", "NoteStore");
+		
+		multiPropertyIndex_new.addedService(ref1, new String("Service1"));
+
+		List<ServiceReference> result_new = multiPropertyIndex_new.getAllServiceReferences("java.lang.String", "(&(context-concept-absoluteidentifier=cca-a)(related-concept-absoluteidentifier=rel-b)(storeclass=NoteStore))");
+
+		assertTrue(result_new.size() == 1);
+	}
+	
+	
+	@Test
+	public void MultiPropertyFilterIndexTypes() {
+		
+		long serviceId = 4711;
+		int ranking = 46;
+		boolean isSomeBoolean = true;
+		String[] interfaces = {"A", "B", "C"};
+		
+		MultiPropertyFilterIndex multiPropertyIndexSingleFilter = new MultiPropertyFilterIndex("objectClass");
+		MultiPropertyFilterIndex multiPropertyIndexMultiple = new MultiPropertyFilterIndex("objectClass,service.id,ranking,interfaces");
+		
+		TestReference newReference = new TestReference();
+		newReference.addProperty("service.id", serviceId);
+		newReference.addProperty("objectclass", "java.lang.Object");
+		newReference.addProperty("ranking", ranking);
+		newReference.addProperty("someboolvalue", isSomeBoolean);
+		newReference.addProperty("interfaces", interfaces);
+		
+		multiPropertyIndexMultiple.addedService(newReference, new Object());
+		multiPropertyIndexSingleFilter.addedService(newReference, new Object());
+	
+		List<ServiceReference> noFilter = multiPropertyIndexSingleFilter.getAllServiceReferences("java.lang.Object", null);
+		assertTrue(noFilter.size() == 1);
+		
+		List<ServiceReference> noClazz = multiPropertyIndexMultiple.getAllServiceReferences(null, "(&(objectClass=java.lang.Object)(&(service.id=4711)(ranking=46)(interfaces=B)))");
+		assertTrue(noClazz.size() == 1);
+		
+		List<ServiceReference> combi = multiPropertyIndexMultiple.getAllServiceReferences("java.lang.Object", "(&(ranking=46)(interfaces=C)(service.id=4711))");
+		assertTrue(combi.size() == 1);
+	}
+	
+	@Test
+	public void MultiPropertyFilterIndexKeyGen() {
+		String key = "(&(objectClass=org.acme.xyz.framework.internationalization.Translatable)(component-identifier=org.acme.xyz.framework.webui.engine.impl.CompoundProcessContextGroupingPanelFactory))";
+		
+		long serviceId = 4711;
+		int ranking = 46;
+		
+		MultiPropertyFilterIndex biIndex = new MultiPropertyFilterIndex("objectClass,component-identifier");
+		TestReference newReference = new TestReference();
+		newReference.addProperty("service.id", serviceId);
+		newReference.addProperty("ranking", ranking);
+		newReference.addProperty("component-identifier", "org.acme.xyz.framework.webui.engine.impl.CompoundProcessContextGroupingPanelFactory");
+		newReference.addProperty("objectclass", "org.acme.xyz.framework.internationalization.Translatable");
+		
+		biIndex.addedService(newReference, new Object());
+	
+		
+		List<ServiceReference> noClazz = biIndex.getAllServiceReferences(null, key);
+		
+		assertTrue(noClazz.size() == 1);
+	}
+	
+	
+	class TestReference implements ServiceReference {
+		Properties props = new Properties();
+	
+		TestReference() {
+		}
+	
+		public void addProperty(String key, String value) {
+			/* Property keys are case-insensitive. -> see @ org.osgi.framework.ServiceReference */
+			props.put(key.toLowerCase(), value);
+		}
+		
+		public void addProperty(String key, long value) {
+			props.put(key, value);
+		}
+		
+		public void addProperty(String key, int value) {
+			props.put(key, value);
+		}
+		
+		public void addProperty(String key, boolean value) {
+			props.put(key, value);
+		}
+	
+		public void addProperty(String key, String[] multiValue) {
+			props.put(key, multiValue);
+		}
+		
+		
+		@Override
+		public Object getProperty(String key) {
+			return props.get(key);
+		}
+
+		@Override
+		public String[] getPropertyKeys() {
+			return props.keySet().toArray(new String[]{});
+		}
+
+		@Override
+		public Bundle getBundle() {
+			return null;
+		}
+
+		@Override
+		public Bundle[] getUsingBundles() {
+			return null;
+		}
+
+		@Override
+		public boolean isAssignableTo(Bundle bundle, String className) {
+			return false;
+		}
+
+		@Override
+		public int compareTo(Object reference) {
+			// TODO Auto-generated method stub
+			return 0;
+		}
+		
+	}
+}

Added: felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/MultiPropertyKeyTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/MultiPropertyKeyTest.java?rev=1792829&view=auto
==============================================================================
--- felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/MultiPropertyKeyTest.java (added)
+++ felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/MultiPropertyKeyTest.java Wed Apr 26 23:55:58 2017
@@ -0,0 +1,98 @@
+package test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
+import org.apache.felix.dm.impl.index.multiproperty.MultiPropertyFilterIndex;
+import org.apache.felix.dm.impl.index.multiproperty.MultiPropertyKey;
+import org.junit.Test;
+
+public class MultiPropertyKeyTest {
+
+	@Test
+	public void sameKeysAndValues() {
+		MultiPropertyKey actual = new MultiPropertyKey(2);
+		actual.add("key1", "abc");
+		actual.add("key2", "efg");
+
+		MultiPropertyKey other = new MultiPropertyKey(2);
+		other.add("key1", "abc");
+		other.add("key2", "efg");
+
+		assertTrue(actual.equals(other));
+		assertTrue(actual.hashCode() == other.hashCode());
+	}
+
+	@Test
+	public void sameKeysAndValuesDifferentOrder() {
+		MultiPropertyKey actual = new MultiPropertyKey(2);
+		actual.add("key1", "abc");
+		actual.add("key2", "efg");
+
+		MultiPropertyKey other = new MultiPropertyKey(2);
+		other.add("key2", "efg");
+		other.add("key1", "abc");
+
+		assertTrue(actual.equals(other));
+		assertTrue(actual.hashCode() == other.hashCode());
+	}
+
+	@Test
+	public void sameKeysDifferentValues() {
+		MultiPropertyKey actual = new MultiPropertyKey(2);
+		actual.add("key1", "abc");
+		actual.add("key2", "efg");
+
+		MultiPropertyKey other = new MultiPropertyKey(2);
+		other.add("key1", "efg");
+		other.add("key2", "abc");
+
+		assertFalse(actual.equals(other));
+		assertFalse(actual.hashCode() == other.hashCode());
+	}
+
+	@Test
+	public void subSetOfKeyValues() {
+		MultiPropertyKey actual = new MultiPropertyKey(3);
+		actual.add("key1", "abc");
+		actual.add("key2", "efg");
+		actual.add("key3", "hij");
+
+		MultiPropertyKey other = new MultiPropertyKey(2);
+		other.add("key1", "abc");
+		other.add("key2", "efg");
+
+		assertFalse(actual.equals(other));
+		assertFalse(actual.hashCode() == other.hashCode());
+	}
+
+	@Test
+	public void noPermutations() {
+		MultiPropertyFilterIndex singleValueFilterIndex = new MultiPropertyFilterIndex("objectClass,#nopermutation");
+
+		TestReference ref1 = new TestReference();
+		ref1.addProperty("service.id", 4711);
+		ref1.addProperty("objectclass", "java.lang.String");
+		ref1.addProperty("nopermutation", new String[] { "a", "b", "c", "d" });
+
+		List<MultiPropertyKey> keys = singleValueFilterIndex.createKeys(ref1);
+
+		assertTrue(keys.size() == 4);
+	}
+
+	@Test
+	public void permutations() {
+		MultiPropertyFilterIndex singleValueFilterIndex = new MultiPropertyFilterIndex("objectClass,permutation");
+
+		TestReference ref1 = new TestReference();
+		ref1.addProperty("service.id", 4711);
+		ref1.addProperty("objectclass", "java.lang.String");
+		ref1.addProperty("permutation", new String[] { "a", "b", "c", "d" });
+
+		List<MultiPropertyKey> keys = singleValueFilterIndex.createKeys(ref1);
+
+		assertTrue(keys.size() == 10);
+	}
+}

Added: felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/TestReference.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/TestReference.java?rev=1792829&view=auto
==============================================================================
--- felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/TestReference.java (added)
+++ felix/trunk/dependencymanager/org.apache.felix.dependencymanager/test/test/TestReference.java Wed Apr 26 23:55:58 2017
@@ -0,0 +1,69 @@
+package test;
+
+import java.util.Properties;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+
+class TestReference implements ServiceReference {
+	Properties props = new Properties();
+
+	public TestReference() {
+	}
+
+	public void addProperty(String key, String value) {
+		/*
+		 * Property keys are case-insensitive. -> see @
+		 * org.osgi.framework.ServiceReference
+		 */
+		props.put(key.toLowerCase(), value);
+	}
+
+	public void addProperty(String key, long value) {
+		props.put(key, value);
+	}
+
+	public void addProperty(String key, int value) {
+		props.put(key, value);
+	}
+
+	public void addProperty(String key, boolean value) {
+		props.put(key, value);
+	}
+
+	public void addProperty(String key, String[] multiValue) {
+		props.put(key, multiValue);
+	}
+
+	@Override
+	public Object getProperty(String key) {
+		return props.get(key);
+	}
+
+	@Override
+	public String[] getPropertyKeys() {
+		return props.keySet().toArray(new String[] {});
+	}
+
+	@Override
+	public Bundle getBundle() {
+		return null;
+	}
+
+	@Override
+	public Bundle[] getUsingBundles() {
+		return null;
+	}
+
+	@Override
+	public boolean isAssignableTo(Bundle bundle, String className) {
+		return false;
+	}
+
+	@Override
+	public int compareTo(Object reference) {
+		// TODO Auto-generated method stub
+		return 0;
+	}
+
+}
\ No newline at end of file