blob: 91bb74ab36f9eafb59f285513dd32d734f5478d8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.felix.dm.impl.index;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.felix.dm.FilterIndex;
import org.apache.felix.dm.tracker.ServiceTracker;
import org.apache.felix.dm.tracker.ServiceTrackerCustomizer;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
/**
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class MultiPropertyExactFilter implements FilterIndex, ServiceTrackerCustomizer {
private final Object m_lock = new Object();
private ServiceTracker m_tracker;
private BundleContext m_context;
private final List /* <String> */ m_propertyKeys;
private final Map /* <String, List<ServiceReference>> */ m_keyToServiceReferencesMap = new HashMap();
private final Map /* <String, List<ServiceListener>> */ m_keyToListenersMap = new HashMap();
private final Map /* <ServiceListener, String> */ m_listenerToFilterMap = new HashMap();
public MultiPropertyExactFilter(String[] propertyKeys) {
String[] keys = (String[]) Arrays.copyOf(propertyKeys, propertyKeys.length);
Arrays.sort(keys);
m_propertyKeys = Arrays.asList(keys);
}
public void open(BundleContext context) {
synchronized (m_lock) {
if (m_context != null) {
throw new IllegalStateException("Filter already open.");
}
try {
m_tracker = new ServiceTracker(context, context.createFilter("(" + Constants.OBJECTCLASS + "=*)"), this);
}
catch (InvalidSyntaxException e) {
throw new Error();
}
m_context = context;
}
m_tracker.open(true, true);
}
public void close() {
ServiceTracker tracker;
synchronized (m_lock) {
if (m_context == null) {
throw new IllegalStateException("Filter already closed.");
}
tracker = m_tracker;
m_tracker = null;
m_context = null;
}
tracker.close();
}
public List /* <ServiceReference> */ getAllServiceReferences(String clazz, String filter) {
List /* <ServiceReference> */ result = new ArrayList();
List keys = createKeysFromFilter(clazz, filter);
Iterator iterator = keys.iterator();
while (iterator.hasNext()) {
String key = (String) iterator.next();
ServiceReference reference;
synchronized (m_keyToServiceReferencesMap) {
List references = (List) m_keyToServiceReferencesMap.get(key);
if (references != null) {
result.addAll(references);
}
}
}
return result;
}
public Object addingService(ServiceReference reference) {
BundleContext context;
synchronized (m_lock) {
context = m_context;
}
if (context != null) {
return context.getService(reference);
}
else {
throw new IllegalStateException("No valid bundle context.");
}
}
public void addedService(ServiceReference reference, Object service) {
if (isApplicable(reference.getPropertyKeys())) {
add(reference);
}
}
public void modifiedService(ServiceReference reference, Object service) {
if (isApplicable(reference.getPropertyKeys())) {
modify(reference);
}
}
public void removedService(ServiceReference reference, Object service) {
if (isApplicable(reference.getPropertyKeys())) {
remove(reference);
}
}
public void add(ServiceReference reference) {
List /* <String> */ keys = createKeys(reference);
synchronized (m_keyToServiceReferencesMap) {
for (int i = 0; i < keys.size(); i++) {
List /* <ServiceReference> */ references = (List) m_keyToServiceReferencesMap.get(keys.get(i));
if (references == null) {
references = new ArrayList();
m_keyToServiceReferencesMap.put(keys.get(i), references);
}
references.add(reference);
}
}
}
public void modify(ServiceReference reference) {
List /* <String> */ keys = createKeys(reference);
synchronized (m_keyToServiceReferencesMap) {
// TODO this is a quite expensive linear scan over the existing collection
// because we first need to remove any existing references and they can be
// all over the place :)
Iterator iterator = m_keyToServiceReferencesMap.values().iterator();
while (iterator.hasNext()) {
List /* <ServiceReference> */ list = (List) iterator.next();
if (list != null) {
Iterator i2 = list.iterator();
while (i2.hasNext()) {
ServiceReference ref = (ServiceReference) i2.next();
if (ref.equals(reference)) {
i2.remove();
}
}
}
}
for (int i = 0; i < keys.size(); i++) {
List /* <ServiceReference> */ references = (List) m_keyToServiceReferencesMap.get(keys.get(i));
if (references == null) {
references = new ArrayList();
m_keyToServiceReferencesMap.put(keys.get(i), references);
}
references.add(reference);
}
}
}
public void remove(ServiceReference reference) {
List /* <String> */ keys = createKeys(reference);
synchronized (m_keyToServiceReferencesMap) {
for (int i = 0; i < keys.size(); i++) {
List /* <ServiceReference> */ references = (List) m_keyToServiceReferencesMap.get(keys.get(i));
if (references != null) {
references.remove(reference);
}
}
}
}
public boolean isApplicable(String[] propertyKeys) {
List list = Arrays.asList(propertyKeys);
Iterator iterator = m_propertyKeys.iterator();
while (iterator.hasNext()) {
String item = (String) iterator.next();
if (!(list.contains(item))) {
return false;
}
}
return true;
}
public static void main(String[] args) {
System.out.println("" + (new MultiPropertyExactFilter(new String[] { "objectClass", "repository", "path", "name" })).isApplicable(null, "(objectClass=abc)"));
}
public boolean isApplicable(String clazz, String filter) {
// "(&(a=b)(c=d))"
// "(&(&(a=b)(c=d))(objC=aaa))"
// startsWith "(&" en split in "(x=y)" -> elke x bestaat in m_propertykeys
// (&(objectClass=xyz)(&(a=x)(b=y)))
Set /* <String> */found = new HashSet();
if (filter != null && filter.startsWith("(&(objectClass=") && filter.contains(")(&(") && filter.endsWith(")))")) {
int i1 = filter.indexOf(")(&(");
String className = filter.substring("(&(objectClass=".length(), i1);
if (!m_propertyKeys.contains("objectClass")) {
return false;
}
else {
found.add("objectClass");
}
String[] parts = filter.substring(i1 + ")(&(".length(), filter.length() - ")))".length()).split("\\)\\(");
for (int i = 0; i < parts.length; i++) {
String part = parts[i];
String[] tuple = part.split("=");
if (!m_propertyKeys.contains(tuple[0])) {
return false;
}
else {
found.add(tuple[0]);
}
// TODO check value tuple[1]
}
return (found.size() == m_propertyKeys.size());
}
else if (filter != null && filter.startsWith("(&(") && filter.endsWith("))")) {
String[] parts = filter.substring(3, filter.length() - 2).split("\\)\\(");
for (int i = 0; i < parts.length; i++) {
String part = parts[i];
String[] tuple = part.split("=");
if (!m_propertyKeys.contains(tuple[0])) {
return false;
}
else {
found.add(tuple[0]);
}
// TODO check value tuple[1]
}
return (found.size() == m_propertyKeys.size());
}
else if (filter != null && filter.startsWith("(") && filter.endsWith(")") && m_propertyKeys.size() == 1) { // TODO quick hack
String part = filter.substring(1, filter.length() - 1);
String[] tuple = part.split("=");
if (!m_propertyKeys.contains(tuple[0])) {
return false;
}
else {
return true;
}
}
else if (clazz != null && filter == null && m_propertyKeys.size() == 1 && m_propertyKeys.get(0).equals(Constants.OBJECTCLASS)) {
return true;
}
return false;
}
private List /* <String> */ createKeys(ServiceReference reference) {
List /* <String> */ results = new ArrayList();
results.add("");
String[] keys = reference.getPropertyKeys();
Arrays.sort(keys);
StringBuffer result = new StringBuffer();
for (int i = 0; i < keys.length; i++) {
String key = keys[i];
if (m_propertyKeys.contains(key)) {
Object value = reference.getProperty(key);
if (value instanceof String[]) {
String[] values = (String[]) value;
List newResults = new ArrayList();
for (int j = 0; j < values.length; j++) {
String val = values[j];
for (int k = 0; k < results.size(); k++) {
String head = (String) results.get(k);
if (head != null && head.length() > 0) {
head = head + ";";
}
newResults.add(head + key + "=" + val);
}
}
results = newResults;
}
else {
for (int k = 0; k < results.size(); k++) {
String head = (String) results.get(k);
if (head != null && head.length() > 0) {
head = head + ";";
}
results.set(k, head + key + "=" + value);
}
}
}
}
return results;
}
private List /* <String> */ createKeysFromFilter(String clazz, String filter) {
List result = new ArrayList();
StringBuffer index = new StringBuffer();
Iterator iterator = m_propertyKeys.iterator();
while (iterator.hasNext()) {
String key = (String) iterator.next();
if (index.length() > 0) {
index.append(';');
}
index.append(key);
index.append('=');
String value = null;
if (clazz != null && Constants.OBJECTCLASS.equals(key)) {
value = clazz;
} // (&(obC=a)(&(a=b)(c=d)))
if (filter != null) {
String startString = "(" + key + "=";
int i1 = filter.indexOf(startString);
if (i1 != -1) {
int i2 = filter.indexOf(")(", i1);
if (i2 == -1) {
if (filter.endsWith(")))")) {
i2 = filter.length() - 3;
}
else if (filter.endsWith("))")) {
i2 = filter.length() - 2;
}
else {
i2 = filter.length() - 1;
}
}
String value2 = filter.substring(i1 + startString.length(), i2);
if (value != null && !value.equals(value2)) {
// corner case: someone specified a clazz and
// also a filter containing a different clazz
return result;
}
value = value2;
}
}
index.append(value);
}
result.add(index.toString());
return result;
}
public void serviceChanged(ServiceEvent event) {
if (isApplicable(event.getServiceReference().getPropertyKeys())) {
List /* <String> */ keys = createKeys(event.getServiceReference());
List list = new ArrayList();
synchronized (m_keyToListenersMap) {
for (int i = 0; i < keys.size(); i++) {
String key = (String) keys.get(i);
List listeners = (List) m_keyToListenersMap.get(key);
if (listeners != null) {
list.addAll(listeners);
}
}
}
if (list != null) {
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
ServiceListener listener = (ServiceListener) iterator.next();
listener.serviceChanged(event);
}
}
}
}
public void addServiceListener(ServiceListener listener, String filter) {
List keys = createKeysFromFilter(null, filter);
Iterator iterator = keys.iterator();
while (iterator.hasNext()) {
String key = (String) iterator.next();
synchronized (m_keyToListenersMap) {
List /* <ServiceListener> */ listeners = (List) m_keyToListenersMap.get(key);
if (listeners == null) {
listeners = new CopyOnWriteArrayList();
m_keyToListenersMap.put(key, listeners);
}
listeners.add(listener);
m_listenerToFilterMap.put(listener, filter);
}
}
}
public void removeServiceListener(ServiceListener listener) {
synchronized (m_keyToListenersMap) {
String filter = (String) m_listenerToFilterMap.remove(listener);
List keys = createKeysFromFilter(null, filter);
Iterator iterator = keys.iterator();
while (iterator.hasNext()) {
String key = (String) iterator.next();
boolean result = filter != null;
if (result) {
List /* <ServiceListener> */ listeners = (List) m_keyToListenersMap.get(key);
if (listeners != null) {
listeners.remove(listener);
}
// TODO actually, if listeners == null that would be strange....
}
}
}
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("MultiPropertyExactFilter[");
sb.append("K2L: " + m_keyToListenersMap.size());
sb.append(", K2SR: " + m_keyToServiceReferencesMap.size());
sb.append(", L2F: " + m_listenerToFilterMap.size());
sb.append("]");
return sb.toString();
}
}