felix-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From pde...@apache.org
Subject svn commit: r1564995 [7/17] - in /felix/sandbox/pderop/dependencymanager: ./ core/ core/.externalToolBuilders/ core/.settings/ core/src/ core/src/main/ core/src/main/java/ core/src/main/java/org/ core/src/main/java/org/apache/ core/src/main/java/org/ap...
Date Wed, 05 Feb 2014 23:22:36 GMT
Added: felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/AdapterFilterIndex.java
URL: http://svn.apache.org/viewvc/felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/AdapterFilterIndex.java?rev=1564995&view=auto
==============================================================================
--- felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/AdapterFilterIndex.java (added)
+++ felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/AdapterFilterIndex.java Wed Feb  5 23:22:32 2014
@@ -0,0 +1,223 @@
+/*
+ * 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.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.FilterIndex;
+import org.apache.felix.dm.ServiceUtil;
+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 AdapterFilterIndex extends AbstractFactoryFilterIndex implements FilterIndex, ServiceTrackerCustomizer {
+	// (&(objectClass=foo.Bar)(|(service.id=18233)(org.apache.felix.dependencymanager.aspect=18233)))
+	private static final String FILTER_REGEXP = "\\(&\\(" + Constants.OBJECTCLASS + "=([a-zA-Z\\.\\$0-9]*)\\)\\(\\|\\(" 
+									+ Constants.SERVICE_ID + "=([0-9]*)\\)\\(" 
+									+ DependencyManager.ASPECT + "=([0-9]*)\\)\\)\\)";
+	private static final Pattern PATTERN = Pattern.compile(FILTER_REGEXP);
+    private final Object m_lock = new Object();
+    private ServiceTracker m_tracker;
+    private BundleContext m_context;
+    private final Map /* <String, List<ServiceListener>> */ m_sidToListenersMap = new HashMap();
+    protected final Map /* <ServiceListener, String> */ m_listenerToObjectClassMap = new HashMap();
+
+    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 boolean isApplicable(String clazz, String filter) {
+        return getFilterData(clazz, filter) != null;
+    }
+
+    /** Returns a value object with the relevant filter data, or <code>null</code> if this filter was not valid. */
+    private FilterData getFilterData(String clazz, String filter) {
+        // something like:
+    	// (&(objectClass=foo.Bar)(|(service.id=18233)(org.apache.felix.dependencymanager.aspect=18233)))  
+    	FilterData resultData = null;
+    	if (filter != null) {
+	    	Matcher matcher = PATTERN.matcher(filter);
+	    	if (matcher.matches()) {
+	    		String sid = matcher.group(2);
+	    		String sid2 = matcher.group(3);
+	    		if (sid.equals(sid2)) {
+	    			resultData = new FilterData();
+	    			resultData.serviceId = Long.parseLong(sid);
+	    		}
+	    	}
+    	}
+    	return resultData;
+    }
+
+	public List getAllServiceReferences(String clazz, String filter) {
+		List /* <ServiceReference> */result = new ArrayList();
+		Matcher matcher = PATTERN.matcher(filter);
+		if (matcher.matches()) {
+			FilterData data = getFilterData(clazz, filter);
+			if (data != null) {
+				SortedSet /* <ServiceReference> */list = null;
+				synchronized (m_sidToServiceReferencesMap) {
+					list = (SortedSet) m_sidToServiceReferencesMap.get(Long.valueOf(data.serviceId));
+					if (list != null) {
+						Iterator iterator = list.iterator();
+						while (iterator.hasNext()) {
+							ServiceReference ref = (ServiceReference) iterator.next();
+							String objectClass = matcher.group(1);
+							if (referenceMatchesObjectClass(ref, objectClass)) {
+								result.add(ref);
+							}
+						}
+					}
+				}
+			}
+		}
+		return result;
+	}
+    
+    public void serviceChanged(ServiceEvent event) {
+        ServiceReference reference = event.getServiceReference();
+        Long sid = ServiceUtil.getServiceIdObject(reference);
+        List /* <ServiceListener> */ notificationList = new ArrayList();
+        synchronized (m_sidToListenersMap) {
+            List /* <ServiceListener> */ list = (ArrayList) m_sidToListenersMap.get(sid);
+            if (list != null) {
+            	Iterator iterator = list.iterator();
+            	while (iterator.hasNext()) {
+                	ServiceListener listener = (ServiceListener) iterator.next();
+                	String objectClass = (String) m_listenerToObjectClassMap.get(listener);
+                	if (referenceMatchesObjectClass(reference, objectClass)) {
+                		notificationList.add(listener);
+                	} 
+            	}
+            }
+        }
+        // notify
+        Iterator iterator = notificationList.iterator();
+        while (iterator.hasNext()) {
+        	ServiceListener listener = (ServiceListener) iterator.next();
+        	listener.serviceChanged(event);
+        }
+    }
+
+    public void addServiceListener(ServiceListener listener, String filter) {
+        FilterData data = getFilterData(null, filter);
+        if (data != null) {
+            Long sidObject = Long.valueOf(data.serviceId);
+            synchronized (m_sidToListenersMap) {
+            	List /* <ServiceListener> */ listeners = (List) m_sidToListenersMap.get(sidObject);
+            	if (listeners == null) {
+            		listeners = new ArrayList();
+            		m_sidToListenersMap.put(sidObject, listeners);
+            	}
+            	listeners.add(listener);
+            	m_listenerToFilterMap.put(listener, filter);
+        		Matcher matcher = PATTERN.matcher(filter);
+        		if (matcher.matches()) {
+        			String objectClass = matcher.group(1);
+        			m_listenerToObjectClassMap.put(listener, objectClass);
+        		} else {
+        			throw new IllegalArgumentException("Filter string does not match index pattern");
+        		}
+
+            }
+        }
+    }
+
+    public void removeServiceListener(ServiceListener listener) {
+        synchronized (m_sidToListenersMap) {
+        	m_listenerToObjectClassMap.remove(listener);
+            String filter = (String) m_listenerToFilterMap.remove(listener);
+            if (filter != null) {
+            	// the listener does exist
+            	FilterData data = getFilterData(null, filter);
+            	if (data != null) {
+            		Long sidObject = Long.valueOf(data.serviceId);
+            		List /* ServiceListener */ listeners = (List) m_sidToListenersMap.get(sidObject);
+            		if (listeners != null) {
+            			listeners.remove(listener);
+            		}
+            	}
+            }
+        }
+    }
+
+    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 String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append("AdapterFilterIndex[");
+        sb.append("S2L: " + m_sidToListenersMap.size());
+        sb.append(", S2SR: " + m_sidToServiceReferencesMap.size());
+        sb.append(", L2F: " + m_listenerToFilterMap.size());
+        sb.append("]");
+        return sb.toString();
+    }
+}

Added: felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/AspectFilterIndex.java
URL: http://svn.apache.org/viewvc/felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/AspectFilterIndex.java?rev=1564995&view=auto
==============================================================================
--- felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/AspectFilterIndex.java (added)
+++ felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/AspectFilterIndex.java Wed Feb  5 23:22:32 2014
@@ -0,0 +1,266 @@
+/*
+ * 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.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.FilterIndex;
+import org.apache.felix.dm.ServiceUtil;
+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 AspectFilterIndex extends AbstractFactoryFilterIndex implements FilterIndex, ServiceTrackerCustomizer {
+	// (&(objectClass=foo.Bar)(|(!(service.ranking=*))(service.ranking<=99))(|(service.id=4451)(org.apache.felix.dependencymanager.aspect=4451)))
+    private static final String FILTER_START = "(&(" + Constants.OBJECTCLASS + "=";
+    private static final String FILTER_SUBSTRING_0 = ")(&(|(!(" + Constants.SERVICE_RANKING + "=*))(" + Constants.SERVICE_RANKING + "<=";
+    private static final String FILTER_SUBSTRING_1 = "))(|(" + Constants.SERVICE_ID + "=";
+    private static final String FILTER_SUBSTRING_2 = ")(" + DependencyManager.ASPECT + "=";
+    private static final String FILTER_END = "))))";
+    private final Object m_lock = new Object();
+    private ServiceTracker m_tracker;
+    private BundleContext m_context;
+    
+    private final Map /* <Long, Map<String, SortedMap<Integer, Collection<ServiceListener>>> */ m_sidToObjectClassToRankingToListenersMap = new HashMap();
+
+    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 boolean isApplicable(String clazz, String filter) {
+        return getFilterData(clazz, filter) != null;
+    }
+
+    /** Returns a value object with the relevant filter data, or <code>null</code> if this filter was not valid. */
+    private FilterData getFilterData(String clazz, String filter) {
+        // something like:
+        // (&(objectClass=foo.Bar)(&(|(!(service.ranking=*))(service.ranking<=9))(|(service.id=37)(org.apache.felix.dependencymanager.aspect=37))))
+        if ((filter != null)
+            && (filter.startsWith(FILTER_START)) // (&(objectClass=
+            && (filter.endsWith(FILTER_END)) // ))))
+            ) {
+            int i0 = filter.indexOf(FILTER_SUBSTRING_0);
+            if (i0 == -1) {
+                return null;
+            }
+            int i1 = filter.indexOf(FILTER_SUBSTRING_1);
+            if (i1 == -1 || i1 <= i0) {
+                return null;
+            }
+            int i2 = filter.indexOf(FILTER_SUBSTRING_2);
+            if (i2 == -1 || i2 <= i1) {
+                return null;
+            }
+            long sid = Long.parseLong(filter.substring(i1 + FILTER_SUBSTRING_1.length(), i2));
+            long sid2 = Long.parseLong(filter.substring(i2 + FILTER_SUBSTRING_2.length(), filter.length() - FILTER_END.length()));
+            if (sid != sid2) {
+                return null;
+            }
+            FilterData result = new FilterData();
+            result.objectClass = filter.substring(FILTER_START.length(), i0);
+            result.serviceId = sid;
+            result.ranking = Integer.parseInt(filter.substring(i0 + FILTER_SUBSTRING_0.length(), i1));
+            return result;
+        }
+        return null;
+    }
+
+    public List getAllServiceReferences(String clazz, String filter) {
+        List /* <ServiceReference> */ result = new ArrayList();
+        FilterData data = getFilterData(clazz, filter);
+        if (data != null) {
+        	SortedSet /* <ServiceReference> */ list = null;
+        	synchronized (m_sidToServiceReferencesMap) {
+        		list = (SortedSet) m_sidToServiceReferencesMap.get(Long.valueOf(data.serviceId));
+        		if (list != null) {
+        			Iterator iterator = list.iterator();
+        			while (iterator.hasNext()) {
+        				ServiceReference reference = (ServiceReference) iterator.next();
+        				if (referenceMatchesObjectClass(reference, data.objectClass) && ServiceUtil.getRanking(reference) <= data.ranking) {
+        					result.add(reference);
+        				}
+        			}
+        		}
+			}
+        }
+        return result;
+    }
+
+    public void serviceChanged(ServiceEvent event) {
+        List list = new ArrayList();
+        ServiceReference reference = event.getServiceReference();
+        Long sidObject = ServiceUtil.getServiceIdObject(reference);
+        int ranking = ServiceUtil.getRanking(reference);
+        String[] objectClasses = (String[]) reference.getProperty(Constants.OBJECTCLASS);
+        
+        synchronized (m_sidToObjectClassToRankingToListenersMap) {
+        	for (int i = 0; i < objectClasses.length; i++) {
+        		// handle each of the object classes separately since aspects only work on one object class at a time
+        		String objectClass = objectClasses[i];
+        		Map /* <String, Map<Integer, Collection<ServiceListener>>> */ objectClassToRankingToListenersMap = (SortedMap) m_sidToObjectClassToRankingToListenersMap.get(sidObject);
+        		if (objectClassToRankingToListenersMap != null) {
+        			SortedMap /* Integer, ServiceListener> */ rankingToListenersMap = (SortedMap) objectClassToRankingToListenersMap.get(objectClass);
+        			if (rankingToListenersMap != null) {
+        				Iterator iterator = rankingToListenersMap.entrySet().iterator();
+        				while (iterator.hasNext()) {
+        					Entry entry = (Entry) iterator.next();
+        					if (ranking <= ((Integer) entry.getKey()).intValue()) {
+        						list.addAll((Collection)entry.getValue());
+        					}
+        				}
+        			}
+        		}
+        	}
+		}
+        Iterator iterator = list.iterator();
+        while (iterator.hasNext()) {
+            ServiceListener listener = (ServiceListener) iterator.next();
+            listener.serviceChanged(event);
+        }
+    }
+
+    public void addServiceListener(ServiceListener listener, String filter) {
+        FilterData data = getFilterData(null, filter);
+        if (data != null) {
+            Long sidObject = Long.valueOf(data.serviceId);
+            synchronized (m_sidToObjectClassToRankingToListenersMap) {
+            	Map /* <String, Map<Integer, Collection<ServiceListener>>> */ objectClassToRankingToListenersMap = (SortedMap) m_sidToObjectClassToRankingToListenersMap.get(sidObject);
+            	if (objectClassToRankingToListenersMap == null) {
+            		objectClassToRankingToListenersMap = new TreeMap();
+            		m_sidToObjectClassToRankingToListenersMap.put(sidObject, objectClassToRankingToListenersMap);
+            	}
+            	
+            	SortedMap /* Integer, ServiceListener> */ rankingToListenersMap = (SortedMap) objectClassToRankingToListenersMap.get(data.objectClass);
+                if (rankingToListenersMap == null) {
+                    rankingToListenersMap = new TreeMap();
+                    objectClassToRankingToListenersMap.put(data.objectClass, rankingToListenersMap);
+                }            	
+            	
+            	Collection listeners = (Collection) rankingToListenersMap.get(Integer.valueOf(data.ranking));
+            	if (listeners == null) {
+            		listeners = new ArrayList();
+            		rankingToListenersMap.put(Integer.valueOf(data.ranking), listeners);
+            	}
+            	
+            	listeners.add(listener);
+                m_listenerToFilterMap.put(listener, filter);
+            }
+        }
+    }
+
+    public void removeServiceListener(ServiceListener listener) {
+    	synchronized (m_sidToObjectClassToRankingToListenersMap) {
+    		String filter = (String) m_listenerToFilterMap.remove(listener);
+    		if (filter != null) {
+    			// the listener does exist
+    			FilterData data = getFilterData(null, filter);
+    			if (data != null) {
+    				// this index is applicable
+    				Long sidObject = Long.valueOf(data.serviceId);
+                	Map /* <String, Map<Integer, Collection<ServiceListener>>> */ objectClassToRankingToListenersMap = (SortedMap) m_sidToObjectClassToRankingToListenersMap.get(sidObject);
+                	if (objectClassToRankingToListenersMap != null) {
+                		SortedMap /* Integer, ServiceListener> */ rankingToListenersMap = (SortedMap) objectClassToRankingToListenersMap.get(data.objectClass);
+                		if (rankingToListenersMap != null) {
+                			Collection listeners = (Collection) rankingToListenersMap.get(Integer.valueOf(data.ranking));
+                			if (listeners != null) {
+                				listeners.remove(listener);
+                			}
+                			// cleanup 
+                			if (listeners.isEmpty()) {
+                				rankingToListenersMap.remove(Integer.valueOf(data.ranking));
+                			}
+                			if (rankingToListenersMap.isEmpty()) {
+                				objectClassToRankingToListenersMap.remove(data.objectClass);
+                			}
+                			if (objectClassToRankingToListenersMap.isEmpty()) {
+                				m_sidToObjectClassToRankingToListenersMap.remove(sidObject);
+                			}
+                		}
+                	}
+    			}
+    		}
+		}
+    }
+
+    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 String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append("AspectFilterIndex[");
+        sb.append("S2R2L: " + m_sidToObjectClassToRankingToListenersMap.size());
+        sb.append(", S2SR: " + m_sidToServiceReferencesMap.size());
+        sb.append(", L2F: " + m_listenerToFilterMap.size());
+        sb.append("]");
+        return sb.toString();
+    }
+}

Added: felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/BundleContextInterceptor.java
URL: http://svn.apache.org/viewvc/felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/BundleContextInterceptor.java?rev=1564995&view=auto
==============================================================================
--- felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/BundleContextInterceptor.java (added)
+++ felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/BundleContextInterceptor.java Wed Feb  5 23:22:32 2014
@@ -0,0 +1,163 @@
+/*
+ * 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.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+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;
+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 BundleContextInterceptor extends BundleContextInterceptorBase {
+	protected static final String INDEX_LOG_TRESHOLD = "org.apache.felix.dm.index.log.treshold";
+    private final ServiceRegistryCache m_cache;
+    private final boolean m_perfmon = System.getProperty(INDEX_LOG_TRESHOLD) != null;
+	private Logger m_logger;
+	private long m_treshold;
+
+    public BundleContextInterceptor(ServiceRegistryCache cache, BundleContext context) {
+        super(context);
+        m_cache = cache;
+		if (m_perfmon) {
+			m_treshold = Long.parseLong(System.getProperty(INDEX_LOG_TRESHOLD));
+			m_logger = new Logger(context);
+		}
+    }
+
+    public void addServiceListener(ServiceListener listener, String filter) throws InvalidSyntaxException {
+        FilterIndex filterIndex = m_cache.hasFilterIndexFor(null, filter);
+        if (filterIndex != null) {
+            filterIndex.addServiceListener(listener, filter);
+        }
+        else {
+//            System.out.println("BCI:Listener " + listener.getClass().getName() + " filter " + filter);
+            m_context.addServiceListener(listener, filter);
+        }
+    }
+
+    public void addServiceListener(ServiceListener listener) {
+        FilterIndex filterIndex = m_cache.hasFilterIndexFor(null, null);
+        if (filterIndex != null) {
+            filterIndex.addServiceListener(listener, null);
+        }
+        else {
+//            System.out.println("BCI:Listener " + listener.getClass().getName() + " without filter");
+            m_context.addServiceListener(listener);
+        }
+    }
+
+    public void removeServiceListener(ServiceListener listener) {
+    	// remove servicelistener. although it would be prettier to find the correct filterindex first it's
+    	// probaby faster to do a brute force removal.
+    	Iterator filterIndexIterator = m_cache.getFilterIndices().iterator();
+    	while (filterIndexIterator.hasNext()) {
+    		((FilterIndex) filterIndexIterator.next()).removeServiceListener(listener);
+    	}
+    	m_context.removeServiceListener(listener);
+    }
+
+    public ServiceReference[] getServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
+    	long start = 0L;
+    	if (m_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) {
+            List /* <ServiceReference> */ result = filterIndex.getAllServiceReferences(clazz, filter);
+            Iterator iterator = result.iterator();
+            while (iterator.hasNext()) {
+                ServiceReference reference = (ServiceReference) iterator.next();
+                String[] list = (String[]) reference.getProperty(Constants.OBJECTCLASS);
+                for (int i = 0; i < list.length; i++) {
+                    if (!reference.isAssignableTo(m_context.getBundle(), list[i])) {
+                        iterator.remove();
+                        break;
+                    }
+                }
+            }
+            if (m_perfmon) {
+	        	long duration = System.currentTimeMillis() - start;
+	        	if (duration > m_treshold) {
+	        		m_logger.log(org.apache.felix.dm.impl.Logger.LOG_DEBUG, "Indexed filter exceeds lookup time treshold (" + duration + "ms.): " + clazz + " " + filter);
+	        	}
+            }
+            if (result == null || result.size() == 0) {
+                return null;
+            }
+            return (ServiceReference[]) result.toArray(new ServiceReference[result.size()]);
+        }
+        else {
+            // if they don't know, we ask the real bundle context instead
+            ServiceReference[] serviceReferences = m_context.getServiceReferences(clazz, filter);
+            if (m_perfmon) {
+	        	long duration = System.currentTimeMillis() - start;
+	        	if (duration > m_treshold) {
+	        		m_logger.log(org.apache.felix.dm.impl.Logger.LOG_DEBUG, "Unindexed filter exceeds lookup time treshold (" + duration + "ms.): " + clazz + " " + filter);
+	        	}
+            }
+        	return serviceReferences;
+        }
+    }
+
+    public ServiceReference[] getAllServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
+        // 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) {
+            List /* <ServiceReference> */ result = filterIndex.getAllServiceReferences(clazz, filter);
+            if (result == null || result.size() == 0) {
+                return null;
+            }
+            return (ServiceReference[]) result.toArray(new ServiceReference[result.size()]);
+        }
+        else {
+            // if they don't know, we ask the real bundle context instead
+            return m_context.getAllServiceReferences(clazz, filter);
+        }
+    }
+
+    public ServiceReference getServiceReference(String clazz) {
+        ServiceReference[] references;
+        try {
+            references = getServiceReferences(clazz, null);
+            if (references == null || references.length == 0) {
+                return null;
+            }
+            Arrays.sort(references);
+            return references[references.length - 1];
+        }
+        catch (InvalidSyntaxException e) {
+            throw new Error("Invalid filter syntax thrown for null filter.", e);
+        }
+    }
+
+    public void serviceChanged(ServiceEvent event) {
+        m_cache.serviceChangedForFilterIndices(event);
+    }
+}

Added: felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/BundleContextInterceptorBase.java
URL: http://svn.apache.org/viewvc/felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/BundleContextInterceptorBase.java?rev=1564995&view=auto
==============================================================================
--- felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/BundleContextInterceptorBase.java (added)
+++ felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/BundleContextInterceptorBase.java Wed Feb  5 23:22:32 2014
@@ -0,0 +1,163 @@
+/*
+ * 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.io.File;
+import java.io.InputStream;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * Base class for bundle context interceptors that keep track of service listeners and delegate incoming changes to them.
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public abstract class BundleContextInterceptorBase implements BundleContext, ServiceListener {
+    protected final BundleContext m_context;
+    /** Keeps track of all service listeners and their optional filters. */
+    private final Map /* <ServiceListener, String> */m_serviceListenerFilterMap = new HashMap();
+    private long m_currentVersion = 0;
+    private long m_entryVersion = -1;
+    private Entry[] m_serviceListenerFilterMapEntries;
+
+    public BundleContextInterceptorBase(BundleContext context) {
+        m_context = context;
+    }
+
+    public String getProperty(String key) {
+        return m_context.getProperty(key);
+    }
+
+    public Bundle getBundle() {
+        return m_context.getBundle();
+    }
+
+    public Bundle installBundle(String location) throws BundleException {
+        return m_context.installBundle(location);
+    }
+
+    public Bundle installBundle(String location, InputStream input) throws BundleException {
+        return m_context.installBundle(location, input);
+    }
+
+    public Bundle getBundle(long id) {
+        return m_context.getBundle(id);
+    }
+
+    public Bundle[] getBundles() {
+        return m_context.getBundles();
+    }
+
+    public void addServiceListener(ServiceListener listener, String filter) throws InvalidSyntaxException {
+        synchronized (m_serviceListenerFilterMap) {
+            m_serviceListenerFilterMap.put(listener, filter);
+            m_currentVersion++;
+        }
+    }
+
+    public void addServiceListener(ServiceListener listener) {
+        synchronized (m_serviceListenerFilterMap) {
+            m_serviceListenerFilterMap.put(listener, null);
+            m_currentVersion++;
+        }
+    }
+
+    public void removeServiceListener(ServiceListener listener) {
+        synchronized (m_serviceListenerFilterMap) {
+            m_serviceListenerFilterMap.remove(listener);
+            m_currentVersion++;
+        }
+    }
+
+    public void addBundleListener(BundleListener listener) {
+        m_context.addBundleListener(listener);
+    }
+
+    public void removeBundleListener(BundleListener listener) {
+        m_context.removeBundleListener(listener);
+    }
+
+    public void addFrameworkListener(FrameworkListener listener) {
+        m_context.addFrameworkListener(listener);
+    }
+
+    public void removeFrameworkListener(FrameworkListener listener) {
+        m_context.removeFrameworkListener(listener);
+    }
+
+    public ServiceRegistration registerService(String[] clazzes, Object service, Dictionary properties) {
+        return m_context.registerService(clazzes, service, properties);
+    }
+
+    public ServiceRegistration registerService(String clazz, Object service, Dictionary properties) {
+        return m_context.registerService(clazz, service, properties);
+    }
+
+    public ServiceReference[] getServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
+        return m_context.getServiceReferences(clazz, filter);
+    }
+
+    public ServiceReference[] getAllServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
+        return m_context.getAllServiceReferences(clazz, filter);
+    }
+
+    public ServiceReference getServiceReference(String clazz) {
+        return m_context.getServiceReference(clazz);
+    }
+
+    public Object getService(ServiceReference reference) {
+        return m_context.getService(reference);
+    }
+
+    public boolean ungetService(ServiceReference reference) {
+        return m_context.ungetService(reference);
+    }
+
+    public File getDataFile(String filename) {
+        return m_context.getDataFile(filename);
+    }
+
+    public Filter createFilter(String filter) throws InvalidSyntaxException {
+        return m_context.createFilter(filter);
+    }
+
+    protected Entry[] synchronizeCollection() {
+        // lazy copy on write: we make a new copy only if writes have changed the collection
+        synchronized (m_serviceListenerFilterMap) {
+            if (m_currentVersion != m_entryVersion) {
+                m_serviceListenerFilterMapEntries = (Entry[]) m_serviceListenerFilterMap.entrySet().toArray(new Entry[m_serviceListenerFilterMap.size()]);
+                m_entryVersion = m_currentVersion;
+            }
+        }
+        return m_serviceListenerFilterMapEntries;
+    }
+}

Added: felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/FilterIndexBundleContext.java
URL: http://svn.apache.org/viewvc/felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/FilterIndexBundleContext.java?rev=1564995&view=auto
==============================================================================
--- felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/FilterIndexBundleContext.java (added)
+++ felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/FilterIndexBundleContext.java Wed Feb  5 23:22:32 2014
@@ -0,0 +1,64 @@
+/*
+ * 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.Map.Entry;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class FilterIndexBundleContext extends BundleContextInterceptorBase {
+    public FilterIndexBundleContext(BundleContext context) {
+        super(context);
+    }
+
+    public void serviceChanged(ServiceEvent event) {
+        Entry[] entries = synchronizeCollection();
+        for (int i = 0; i < entries.length; i++) {
+            Entry serviceListenerFilterEntry = entries[i];
+            ServiceListener serviceListener = (ServiceListener) serviceListenerFilterEntry.getKey();
+            String filter = (String) serviceListenerFilterEntry.getValue();
+            if (filter == null) {
+                serviceListener.serviceChanged(event);
+            }
+            else {
+                // call service changed on the listener if the filter matches the event
+                // TODO review if we can be smarter here
+                try {
+                    if ("(objectClass=*)".equals(filter)) {
+                        serviceListener.serviceChanged(event);
+                    }
+                    else {
+                        if (m_context.createFilter(filter).match(event.getServiceReference())) {
+                            serviceListener.serviceChanged(event);
+                        }
+                    }
+                }
+                catch (InvalidSyntaxException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+}

Added: felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/ServiceRegistryCache.java
URL: http://svn.apache.org/viewvc/felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/ServiceRegistryCache.java?rev=1564995&view=auto
==============================================================================
--- felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/ServiceRegistryCache.java (added)
+++ felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/ServiceRegistryCache.java Wed Feb  5 23:22:32 2014
@@ -0,0 +1,177 @@
+/*
+ * 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.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+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;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * @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 final BundleContext m_context;
+    private final FilterIndexBundleContext m_filterIndexBundleContext;
+    private final Map /* <BundleContext, BundleContextInterceptor> */ m_bundleContextInterceptorMap = new HashMap();
+    private long m_currentVersion = 0;
+    private long m_arrayVersion = -1;
+    private BundleContextInterceptor[] m_interceptors = null;
+    private ServiceRegistration m_registration;
+    
+    public ServiceRegistryCache(BundleContext context) {
+        m_context = context;
+        m_filterIndexBundleContext = new FilterIndexBundleContext(m_context);
+    }
+    
+    public void open() {
+        m_context.addServiceListener(this);
+//        m_registration = m_context.registerService(CommandProvider.class.getName(), this, null);
+    }
+    
+    public void close() {
+//        m_registration.unregister();
+        m_context.removeServiceListener(this);
+    }
+    
+    public void addFilterIndex(FilterIndex index) {
+        m_filterIndexList.add(index);
+        index.open(m_filterIndexBundleContext);
+    }
+    
+    public void removeFilterIndex(FilterIndex index) {
+        index.close();
+        m_filterIndexList.remove(index);
+    }
+
+    public void serviceChanged(ServiceEvent event) {
+        // any incoming event is first dispatched to the list of filter indices
+        m_filterIndexBundleContext.serviceChanged(event);
+        // and then all the other listeners can access it
+        synchronized (m_bundleContextInterceptorMap) {
+            if (m_currentVersion != m_arrayVersion) {
+                // if our copy is out of date, we make a new one
+                m_interceptors = (BundleContextInterceptor[]) m_bundleContextInterceptorMap.values().toArray(new BundleContextInterceptor[m_bundleContextInterceptorMap.size()]);
+                m_arrayVersion = m_currentVersion;
+            }
+        }
+        
+        serviceChangedForFilterIndices(event);
+    }
+    
+    /** Creates an interceptor for a bundle context that uses our cache. */
+    public BundleContext createBundleContextInterceptor(BundleContext context) {
+        synchronized (m_bundleContextInterceptorMap) {
+            BundleContextInterceptor bundleContextInterceptor = (BundleContextInterceptor) m_bundleContextInterceptorMap.get(context);
+            if (bundleContextInterceptor == null) {
+                bundleContextInterceptor = new BundleContextInterceptor(this, context);
+                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
+            }
+            return bundleContextInterceptor;
+        }
+    }
+
+    public FilterIndex hasFilterIndexFor(String clazz, String filter) {
+        Iterator iterator = m_filterIndexList.iterator();
+        while (iterator.hasNext()) {
+            FilterIndex filterIndex = (FilterIndex) iterator.next();
+            if (filterIndex.isApplicable(clazz, filter)) {
+                return filterIndex;
+            }
+        }
+        return null;
+    }
+
+    public void serviceChangedForFilterIndices(ServiceEvent event) {
+        Iterator iterator = m_filterIndexList.iterator();
+        while (iterator.hasNext()) {
+            FilterIndex filterIndex = (FilterIndex) iterator.next();
+            filterIndex.serviceChanged(event);
+        }
+    }
+
+//    public void _sc(CommandInterpreter ci) {
+//        ci.println(toString());
+//    }
+//    
+//    public void _fi(CommandInterpreter ci) {
+//        String arg = ci.nextArgument();
+//        if (arg != null) {
+//            int x = Integer.parseInt(arg);
+//            FilterIndex filterIndex = (FilterIndex) m_filterIndexList.get(x);
+//            String a1 = ci.nextArgument();
+//            String a2 = null;
+//            if (a1 != null) {
+//                if ("-".equals(a1)) {
+//                    a1 = null;
+//                }
+//                a2 = ci.nextArgument();
+//            }
+//            if (filterIndex.isApplicable(a1, a2)) {
+//                List /* <ServiceReference> */ references = filterIndex.getAllServiceReferences(a1, a2);
+//                ci.println("Found " + references.size() + " references:");
+//                for (int i = 0; i < references.size(); i++) {
+//                    ci.println("" + i + " - " + references.get(i));
+//                }
+//            }
+//            else {
+//                ci.println("Filter not applicable.");
+//            }
+//        }
+//        else {
+//            ci.println("FilterIndices:");
+//            Iterator iterator = m_filterIndexList.iterator();
+//            int index = 0;
+//            while (iterator.hasNext()) {
+//                FilterIndex filterIndex = (FilterIndex) iterator.next();
+//                ci.println("" + index + " " + filterIndex);
+//                index++;
+//            }
+//        }
+//    }
+//    
+//    public String getHelp() {
+//        return "I'm not going to help you!";
+//    }
+    
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append("ServiceRegistryCache[");
+        sb.append("FilterIndices: " + m_filterIndexList.size());
+        sb.append(", BundleContexts intercepted: " + m_bundleContextInterceptorMap.size());
+        sb.append("]");
+        return sb.toString();
+    }
+
+	public List getFilterIndices() {
+		return m_filterIndexList;
+	}
+}

Added: felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/Filter.java
URL: http://svn.apache.org/viewvc/felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/Filter.java?rev=1564995&view=auto
==============================================================================
--- felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/Filter.java (added)
+++ felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/Filter.java Wed Feb  5 23:22:32 2014
@@ -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();
+		}
+	}
+	
+}

Added: felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyFilterIndex.java
URL: http://svn.apache.org/viewvc/felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyFilterIndex.java?rev=1564995&view=auto
==============================================================================
--- felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyFilterIndex.java (added)
+++ felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyFilterIndex.java Wed Feb  5 23:22:32 2014
@@ -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();
+    }
+}

Added: felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/Property.java
URL: http://svn.apache.org/viewvc/felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/Property.java?rev=1564995&view=auto
==============================================================================
--- felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/Property.java (added)
+++ felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/Property.java Wed Feb  5 23:22:32 2014
@@ -0,0 +1,102 @@
+/*
+ * 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) {
+			// value has not bee set yet
+			this.m_value = value;
+		}
+		if (value != null) {
+			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

Added: felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/metatype/AttributeDefinitionImpl.java
URL: http://svn.apache.org/viewvc/felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/metatype/AttributeDefinitionImpl.java?rev=1564995&view=auto
==============================================================================
--- felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/metatype/AttributeDefinitionImpl.java (added)
+++ felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/metatype/AttributeDefinitionImpl.java Wed Feb  5 23:22:32 2014
@@ -0,0 +1,76 @@
+/*
+ * 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.metatype;
+
+import org.osgi.service.metatype.AttributeDefinition;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AttributeDefinitionImpl implements AttributeDefinition {
+    private PropertyMetaDataImpl m_propertyMetaData;
+    private Resource m_resource;
+
+    public AttributeDefinitionImpl(PropertyMetaDataImpl propertyMetaData, Resource resource) {
+        m_propertyMetaData = propertyMetaData;
+        m_resource = resource;
+    }
+
+    public int getCardinality() {
+        return m_propertyMetaData.getCardinality();
+    }
+
+    public String[] getDefaultValue() {
+        return m_propertyMetaData.getDefaults();
+    }
+
+    public String getDescription() {
+        return m_resource.localize(m_propertyMetaData.getDescription());
+    }
+
+    public String getID() {
+        return m_propertyMetaData.getId();
+    }
+
+    public String getName() {
+        return m_resource.localize(m_propertyMetaData.getHeading());
+    }
+
+    public String[] getOptionLabels() {
+        String[] labels = m_propertyMetaData.getOptionLabels();
+        if (labels != null) {
+            for (int i = 0; i < labels.length; i++) {
+                labels[i] = m_resource.localize(labels[i]);
+            }
+        }
+        return labels;
+    }
+
+    public String[] getOptionValues() {
+        return m_propertyMetaData.getOptionValues();
+    }
+
+    public int getType() {
+        return m_propertyMetaData.getType();
+    }
+
+    public String validate(String value) {
+        return null;
+    }
+}

Added: felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/metatype/MetaTypeProviderImpl.java
URL: http://svn.apache.org/viewvc/felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/metatype/MetaTypeProviderImpl.java?rev=1564995&view=auto
==============================================================================
--- felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/metatype/MetaTypeProviderImpl.java (added)
+++ felix/sandbox/pderop/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/metatype/MetaTypeProviderImpl.java Wed Feb  5 23:22:32 2014
@@ -0,0 +1,258 @@
+/*
+ * 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.metatype;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+
+import org.apache.felix.dm.PropertyMetaData;
+import org.apache.felix.dm.impl.Logger;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.log.LogService;
+import org.osgi.service.metatype.MetaTypeProvider;
+import org.osgi.service.metatype.ObjectClassDefinition;
+
+/**
+ * When a ConfigurationDepdendency is configured with properties metadata, we provide
+ * a specific ManagedService which also implements the MetaTypeProvider interface. This interface
+ * allows the MetaTypeService to retrieve our properties metadata, which will then be handled by webconsole.
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class MetaTypeProviderImpl implements MetaTypeProvider, ManagedService, ManagedServiceFactory {
+    private ManagedService m_managedServiceDelegate;
+    private ManagedServiceFactory m_managedServiceFactoryDelegate;
+    private List m_propertiesMetaData = new ArrayList();
+    private String m_description;
+    private String m_heading;
+    private String m_localization;
+    private Map m_localesProperties = new HashMap();
+    private Logger m_logger;
+    private BundleContext m_bctx;
+    private String m_pid;
+
+    public MetaTypeProviderImpl(String pid, BundleContext ctx, Logger logger, ManagedService msDelegate, ManagedServiceFactory msfDelegate) {
+        m_pid = pid;
+        m_bctx = ctx;
+        m_logger = logger;
+        m_managedServiceDelegate = msDelegate;
+        m_managedServiceFactoryDelegate = msfDelegate;
+        // Set the default localization file base name (see core specification, in section Localization on page 68).
+        // By default, this file can be stored in OSGI-INF/l10n/bundle.properties (and corresponding localized version
+        // in OSGI-INF/l10n/bundle_en_GB_welsh.properties,  OSGI-INF/l10n/bundle_en_GB.properties, etc ...
+        // This default localization property file name can be overriden using the PropertyMetaData.setLocalization method.
+        m_localization = (String) m_bctx.getBundle().getHeaders().get(Constants.BUNDLE_LOCALIZATION);
+        if (m_localization == null) {
+            m_localization = Constants.BUNDLE_LOCALIZATION_DEFAULT_BASENAME;
+        }
+    }
+
+    /**
+     * Registers the metatype information of a given configuration property
+     * @param property
+     */
+    public void add(PropertyMetaData property) {
+        m_propertiesMetaData.add(property);
+    }
+
+    /**
+     * A human readable description of the PID this annotation is associated with. Example: "Configuration for the PrinterService bundle".
+     * @return A human readable description of the PID this annotation is associated with (may be localized)
+     */
+    public void setDescription(String description) {
+        m_description = description;
+    }
+
+    /**
+     * The label used to display the tab name (or section) where the properties are displayed. Example: "Printer Service".
+     * @return The label used to display the tab name where the properties are displayed (may be localized)
+     */
+    public void setName(String heading) {
+        m_heading = heading;
+    }
+
+    /**
+     * Points to the basename of the Properties file that can localize the Meta Type informations.
+     * By default, (e.g. <code>setLocalization("person")</code> would match person_du_NL.properties in the root bundle directory.
+     * The default localization base name for the properties is OSGI-INF/l10n/bundle, but can
+     * be overridden by the manifest Bundle-Localization header (see core specification, in section Localization on page 68).
+     */
+    public void setLocalization(String path) {
+        if (path.endsWith(".properties")) {
+            throw new IllegalArgumentException(
+                "path must point to the base name of the propertie file, "
+                + "excluding local suffixes. For example: "
+                + "foo/bar/person is valid and matches the property file \"foo/bar/person_bundle_en_GB_welsh.properties\"");
+        }
+        m_localization = path.startsWith("/") ? path.substring(1) : path;
+    }
+
+    // --------------- MetaTypeProvider interface -------------------------------------------------
+
+    /**
+     * Returns all the Locales our bundle is containing. For instance, if our bundle contains the following localization files:
+     * OSGI-INF/l10n/bundle_en_GB_welsh.properties and OSGI-INF/l10n/bundle_en_GB.properties, then this method will return
+     * "en_GB", "en_GB_welsh" ...
+     * @return the list of Locale supported by our bundle.
+     */
+    public String[] getLocales() {
+        int lastSlash = m_localization.lastIndexOf("/");
+        String path = (lastSlash == -1) ? "/" : ("/" + m_localization.substring(0, lastSlash - 1));
+        String base = (lastSlash == -1) ? m_localization : m_localization.substring(lastSlash + 1);
+        Enumeration e = m_bctx.getBundle().findEntries(path,
+            base + "*.properties", false);
+        if (e == null) {
+            return null;
+        }
+        
+        TreeSet set = new TreeSet();
+        while (e.hasMoreElements()) {
+            // We have found a locale property file in the form of "path/file[_language[_ country[_variation]].properties"
+            // And now, we have to get the "language[_country[_variation]]" part ...
+            URL url = (URL) e.nextElement();
+            String name = url.getPath();
+            name = name.substring(name.lastIndexOf("/") + 1);
+            int underscore = name.indexOf("_");
+            if (underscore != -1) {
+                name = name.substring(underscore + 1, name.length() - ".properties".length());
+            }
+            if (name.length() > 0) {
+                set.add(name);
+            }
+        }
+
+        String[] locales = (String[]) set.toArray(new String[set.size()]);
+        return locales.length == 0 ? null : locales;
+    }
+
+    /**
+     * Returns the ObjectClassDefinition for a given Pid/Locale.
+     */
+    public ObjectClassDefinition getObjectClassDefinition(String id, String locale) {
+        try {
+            // Check if the id matches our PID
+            if (!id.equals(m_pid)) {
+                m_logger.log(LogService.LOG_ERROR, "id " + id + " does not match pid " + m_pid);
+                return null;
+            }
+
+            Properties localeProperties = getLocaleProperties(locale);
+            return new ObjectClassDefinitionImpl(m_pid, m_heading,
+                m_description, m_propertiesMetaData, new Resource(localeProperties));
+        }
+
+        catch (Throwable t) {
+            m_logger.log(
+                Logger.LOG_ERROR,
+                "Unexpected exception while geting ObjectClassDefinition for " + id + " (locale="
+                    + locale + ")", t);
+            return null;
+        }
+    }
+
+    /**
+     * We also implements the ManagedService and we just delegates the configuration handling to
+     * our associated ConfigurationDependency.
+     */
+    public void updated(Dictionary properties) throws ConfigurationException {
+        m_managedServiceDelegate.updated(properties);
+    }
+
+    /**
+     * Gets the properties for a given Locale.
+     * @param locale
+     * @return
+     * @throws IOException
+     */
+    private synchronized Properties getLocaleProperties(String locale) throws IOException {
+        locale = locale == null ? Locale.getDefault().toString() : locale;
+        Properties properties = (Properties) m_localesProperties.get(locale);
+        if (properties == null) {
+            properties = new Properties();
+            URL url = m_bctx.getBundle().getEntry(m_localization + ".properties");
+            if (url != null) {
+                loadLocale(properties, url);
+            }
+
+            String path = m_localization;
+            StringTokenizer tok = new StringTokenizer(locale, "_");
+            while (tok.hasMoreTokens()) {
+                path += "_" + tok.nextToken();
+                url = m_bctx.getBundle().getEntry(path + ".properties");
+                if (url != null) {
+                    properties = new Properties(properties);
+                    loadLocale(properties, url);
+                }
+            }
+            m_localesProperties.put(locale, properties);
+        }
+        return properties;
+    }
+
+    /**
+     * Loads a Locale Properties file.
+     * @param properties
+     * @param url
+     * @throws IOException
+     */
+    private void loadLocale(Properties properties, URL url) throws IOException {
+        InputStream in = null;
+        try {
+            in = url.openStream();
+            properties.load(in);
+        }
+        finally {
+            if (in != null) {
+                try {
+                    in.close();
+                }
+                catch (IOException ignored) {
+                }
+            }
+        }
+    }
+
+    // ManagedServiceFactory implementation
+    public void deleted(String pid) {
+        m_managedServiceFactoryDelegate.deleted(pid);
+    }
+
+    public String getName() {
+        return m_pid;
+    }
+
+    public void updated(String pid, Dictionary properties) throws ConfigurationException {
+        m_managedServiceFactoryDelegate.updated(pid, properties);
+    }
+}



Mime
View raw message