You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by gn...@apache.org on 2014/03/31 18:18:18 UTC
svn commit: r1583367 [3/4] - in /felix/trunk/pojosr: ./ src/ src/main/
src/main/java/ src/main/java/de/ src/main/java/de/kalpatec/
src/main/java/de/kalpatec/pojosr/ src/main/java/de/kalpatec/pojosr/framework/
src/main/java/de/kalpatec/pojosr/framework/...
Added: felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/CapabilitySet.java
URL: http://svn.apache.org/viewvc/felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/CapabilitySet.java?rev=1583367&view=auto
==============================================================================
--- felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/CapabilitySet.java (added)
+++ felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/CapabilitySet.java Mon Mar 31 16:18:17 2014
@@ -0,0 +1,578 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.capabilityset;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+
+import de.kalpatec.pojosr.framework.felix.framework.util.StringComparator;
+import org.osgi.framework.wiring.BundleCapability;
+
+public class CapabilitySet
+{
+ private final Map<String, Map<Object, Set<BundleCapability>>> m_indices;
+ private final Set<BundleCapability> m_capSet = new HashSet<BundleCapability>();
+
+ public CapabilitySet(List<String> indexProps, boolean caseSensitive)
+ {
+ m_indices = (caseSensitive)
+ ? new TreeMap<String, Map<Object, Set<BundleCapability>>>()
+ : new TreeMap<String, Map<Object, Set<BundleCapability>>>(
+ new StringComparator(false));
+ for (int i = 0; (indexProps != null) && (i < indexProps.size()); i++)
+ {
+ m_indices.put(
+ indexProps.get(i), new HashMap<Object, Set<BundleCapability>>());
+ }
+ }
+
+ public void addCapability(BundleCapability cap)
+ {
+ m_capSet.add(cap);
+
+ // Index capability.
+ for (Entry<String, Map<Object, Set<BundleCapability>>> entry : m_indices.entrySet())
+ {
+ Object value = cap.getAttributes().get(entry.getKey());
+ if (value != null)
+ {
+ if (value.getClass().isArray())
+ {
+ value = convertArrayToList(value);
+ }
+
+ Map<Object, Set<BundleCapability>> index = entry.getValue();
+
+ if (value instanceof Collection)
+ {
+ Collection c = (Collection) value;
+ for (Object o : c)
+ {
+ indexCapability(index, cap, o);
+ }
+ }
+ else
+ {
+ indexCapability(index, cap, value);
+ }
+ }
+ }
+ }
+
+ private void indexCapability(
+ Map<Object, Set<BundleCapability>> index, BundleCapability cap, Object capValue)
+ {
+ Set<BundleCapability> caps = index.get(capValue);
+ if (caps == null)
+ {
+ caps = new HashSet<BundleCapability>();
+ index.put(capValue, caps);
+ }
+ caps.add(cap);
+ }
+
+ public void removeCapability(BundleCapability cap)
+ {
+ if (m_capSet.remove(cap))
+ {
+ for (Entry<String, Map<Object, Set<BundleCapability>>> entry : m_indices.entrySet())
+ {
+ Object value = cap.getAttributes().get(entry.getKey());
+ if (value != null)
+ {
+ if (value.getClass().isArray())
+ {
+ value = convertArrayToList(value);
+ }
+
+ Map<Object, Set<BundleCapability>> index = entry.getValue();
+
+ if (value instanceof Collection)
+ {
+ Collection c = (Collection) value;
+ for (Object o : c)
+ {
+ deindexCapability(index, cap, o);
+ }
+ }
+ else
+ {
+ deindexCapability(index, cap, value);
+ }
+ }
+ }
+ }
+ }
+
+ private void deindexCapability(
+ Map<Object, Set<BundleCapability>> index, BundleCapability cap, Object value)
+ {
+ Set<BundleCapability> caps = index.get(value);
+ if (caps != null)
+ {
+ caps.remove(cap);
+ if (caps.isEmpty())
+ {
+ index.remove(value);
+ }
+ }
+ }
+
+ public Set<BundleCapability> match(SimpleFilter sf, boolean obeyMandatory)
+ {
+ Set<BundleCapability> matches = match(m_capSet, sf);
+ return matches;
+ /* return (obeyMandatory)
+ ? matchMandatory(matches, sf)
+ : matches;*/
+ }
+
+ private Set<BundleCapability> match(Set<BundleCapability> caps, SimpleFilter sf)
+ {
+ Set<BundleCapability> matches = new HashSet<BundleCapability>();
+
+ if (sf.getOperation() == SimpleFilter.MATCH_ALL)
+ {
+ matches.addAll(caps);
+ }
+ else if (sf.getOperation() == SimpleFilter.AND)
+ {
+ // Evaluate each subfilter against the remaining capabilities.
+ // For AND we calculate the intersection of each subfilter.
+ // We can short-circuit the AND operation if there are no
+ // remaining capabilities.
+ List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+ for (int i = 0; (caps.size() > 0) && (i < sfs.size()); i++)
+ {
+ matches = match(caps, sfs.get(i));
+ caps = matches;
+ }
+ }
+ else if (sf.getOperation() == SimpleFilter.OR)
+ {
+ // Evaluate each subfilter against the remaining capabilities.
+ // For OR we calculate the union of each subfilter.
+ List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+ for (int i = 0; i < sfs.size(); i++)
+ {
+ matches.addAll(match(caps, sfs.get(i)));
+ }
+ }
+ else if (sf.getOperation() == SimpleFilter.NOT)
+ {
+ // Evaluate each subfilter against the remaining capabilities.
+ // For OR we calculate the union of each subfilter.
+ matches.addAll(caps);
+ List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+ for (int i = 0; i < sfs.size(); i++)
+ {
+ matches.removeAll(match(caps, sfs.get(i)));
+ }
+ }
+ else
+ {
+ Map<Object, Set<BundleCapability>> index = m_indices.get(sf.getName());
+ if ((sf.getOperation() == SimpleFilter.EQ) && (index != null))
+ {
+ Set<BundleCapability> existingCaps = index.get(sf.getValue());
+ if (existingCaps != null)
+ {
+ matches.addAll(existingCaps);
+ matches.retainAll(caps);
+ }
+ }
+ else
+ {
+ for (Iterator<BundleCapability> it = caps.iterator(); it.hasNext(); )
+ {
+ BundleCapability cap = it.next();
+ Object lhs = cap.getAttributes().get(sf.getName());
+ if (lhs != null)
+ {
+ if (compare(lhs, sf.getValue(), sf.getOperation()))
+ {
+ matches.add(cap);
+ }
+ }
+ }
+ }
+ }
+
+ return matches;
+ }
+
+ /* public static boolean matches(BundleCapability cap, SimpleFilter sf)
+ {
+ return matchesInternal(cap, sf) && matchMandatory(cap, sf);
+ }
+*/
+ private static boolean matchesInternal(BundleCapability cap, SimpleFilter sf)
+ {
+ boolean matched = true;
+
+ if (sf.getOperation() == SimpleFilter.MATCH_ALL)
+ {
+ matched = true;
+ }
+ else if (sf.getOperation() == SimpleFilter.AND)
+ {
+ // Evaluate each subfilter against the remaining capabilities.
+ // For AND we calculate the intersection of each subfilter.
+ // We can short-circuit the AND operation if there are no
+ // remaining capabilities.
+ List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+ for (int i = 0; matched && (i < sfs.size()); i++)
+ {
+ matched = matchesInternal(cap, sfs.get(i));
+ }
+ }
+ else if (sf.getOperation() == SimpleFilter.OR)
+ {
+ // Evaluate each subfilter against the remaining capabilities.
+ // For OR we calculate the union of each subfilter.
+ matched = false;
+ List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+ for (int i = 0; !matched && (i < sfs.size()); i++)
+ {
+ matched = matchesInternal(cap, sfs.get(i));
+ }
+ }
+ else if (sf.getOperation() == SimpleFilter.NOT)
+ {
+ // Evaluate each subfilter against the remaining capabilities.
+ // For OR we calculate the union of each subfilter.
+ List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+ for (int i = 0; i < sfs.size(); i++)
+ {
+ matched = !(matchesInternal(cap, sfs.get(i)));
+ }
+ }
+ else
+ {
+ matched = false;
+ Object lhs = cap.getAttributes().get(sf.getName());
+ if (lhs != null)
+ {
+ matched = compare(lhs, sf.getValue(), sf.getOperation());
+ }
+ }
+
+ return matched;
+ }
+
+ /* private static Set<BundleCapability> matchMandatory(
+ Set<BundleCapability> caps, SimpleFilter sf)
+ {
+ for (Iterator<BundleCapability> it = caps.iterator(); it.hasNext(); )
+ {
+ BundleCapability cap = it.next();
+ if (!matchMandatory(cap, sf))
+ {
+ it.remove();
+ }
+ }
+ return caps;
+ }
+
+ private static boolean matchMandatory(BundleCapability cap, SimpleFilter sf)
+ {
+ Map<String, Object> attrs = cap.getAttributes();
+ for (Entry<String, Object> entry : attrs.entrySet())
+ {
+ if (((BundleCapability) cap).isAttributeMandatory(entry.getKey())
+ && !matchMandatoryAttrbute(entry.getKey(), sf))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean matchMandatoryAttrbute(String attrName, SimpleFilter sf)
+ {
+ if ((sf.getName() != null) && sf.getName().equals(attrName))
+ {
+ return true;
+ }
+ else if (sf.getOperation() == SimpleFilter.AND)
+ {
+ List list = (List) sf.getValue();
+ for (int i = 0; i < list.size(); i++)
+ {
+ SimpleFilter sf2 = (SimpleFilter) list.get(i);
+ if ((sf2.getName() != null)
+ && sf2.getName().equals(attrName))
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }*/
+
+ private static final Class<?>[] STRING_CLASS = new Class[] { String.class };
+
+ private static boolean compare(Object lhs, Object rhsUnknown, int op)
+ {
+ if (lhs == null)
+ {
+ return false;
+ }
+
+ // If this is a PRESENT operation, then just return true immediately
+ // since we wouldn't be here if the attribute wasn't present.
+ if (op == SimpleFilter.PRESENT)
+ {
+ return true;
+ }
+
+ // If the type is comparable, then we can just return the
+ // result immediately.
+ if (lhs instanceof Comparable)
+ {
+ // Spec says SUBSTRING is false for all types other than string.
+ if ((op == SimpleFilter.SUBSTRING) && !(lhs instanceof String))
+ {
+ return false;
+ }
+
+ Object rhs;
+ if (op == SimpleFilter.SUBSTRING)
+ {
+ rhs = rhsUnknown;
+ }
+ else
+ {
+ try
+ {
+ rhs = coerceType(lhs, (String) rhsUnknown);
+ }
+ catch (Exception ex)
+ {
+ return false;
+ }
+ }
+
+ switch (op)
+ {
+ case SimpleFilter.EQ:
+ try
+ {
+ return (((Comparable) lhs).compareTo(rhs) == 0);
+ }
+ catch (Exception ex)
+ {
+ return false;
+ }
+ case SimpleFilter.GTE:
+ try
+ {
+ return (((Comparable) lhs).compareTo(rhs) >= 0);
+ }
+ catch (Exception ex)
+ {
+ return false;
+ }
+ case SimpleFilter.LTE:
+ try
+ {
+ return (((Comparable) lhs).compareTo(rhs) <= 0);
+ }
+ catch (Exception ex)
+ {
+ return false;
+ }
+ case SimpleFilter.APPROX:
+ return compareApproximate(((Comparable) lhs), rhs);
+ case SimpleFilter.SUBSTRING:
+ return SimpleFilter.compareSubstring((List<String>) rhs, (String) lhs);
+ default:
+ throw new RuntimeException(
+ "Unknown comparison operator: " + op);
+ }
+ }
+ // Booleans do not implement comparable, so special case them.
+ else if (lhs instanceof Boolean)
+ {
+ Object rhs;
+ try
+ {
+ rhs = coerceType(lhs, (String) rhsUnknown);
+ }
+ catch (Exception ex)
+ {
+ return false;
+ }
+
+ switch (op)
+ {
+ case SimpleFilter.EQ:
+ case SimpleFilter.GTE:
+ case SimpleFilter.LTE:
+ case SimpleFilter.APPROX:
+ return (lhs.equals(rhs));
+ default:
+ throw new RuntimeException(
+ "Unknown comparison operator: " + op);
+ }
+ }
+
+ // If the LHS is not a comparable or boolean, check if it is an
+ // array. If so, convert it to a list so we can treat it as a
+ // collection.
+ if (lhs.getClass().isArray())
+ {
+ lhs = convertArrayToList(lhs);
+ }
+
+ // If LHS is a collection, then call compare() on each element
+ // of the collection until a match is found.
+ if (lhs instanceof Collection)
+ {
+ for (Iterator iter = ((Collection) lhs).iterator(); iter.hasNext();)
+ {
+ if (compare(iter.next(), rhsUnknown, op))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // Spec says SUBSTRING is false for all types other than string.
+ if ((op == SimpleFilter.SUBSTRING) && !(lhs instanceof String))
+ {
+ return false;
+ }
+
+ // Since we cannot identify the LHS type, then we can only perform
+ // equality comparison.
+ try
+ {
+ return lhs.equals(coerceType(lhs, (String) rhsUnknown));
+ }
+ catch (Exception ex)
+ {
+ return false;
+ }
+ }
+
+ private static boolean compareApproximate(Object lhs, Object rhs)
+ {
+ if (rhs instanceof String)
+ {
+ return removeWhitespace((String) lhs)
+ .equalsIgnoreCase(removeWhitespace((String) rhs));
+ }
+ else if (rhs instanceof Character)
+ {
+ return Character.toLowerCase(((Character) lhs))
+ == Character.toLowerCase(((Character) rhs));
+ }
+ return lhs.equals(rhs);
+ }
+
+ private static String removeWhitespace(String s)
+ {
+ StringBuffer sb = new StringBuffer(s.length());
+ for (int i = 0; i < s.length(); i++)
+ {
+ if (!Character.isWhitespace(s.charAt(i)))
+ {
+ sb.append(s.charAt(i));
+ }
+ }
+ return sb.toString();
+ }
+
+ private static Object coerceType(Object lhs, String rhsString) throws Exception
+ {
+ // If the LHS expects a string, then we can just return
+ // the RHS since it is a string.
+ if (lhs.getClass() == rhsString.getClass())
+ {
+ return rhsString;
+ }
+
+ // Try to convert the RHS type to the LHS type by using
+ // the string constructor of the LHS class, if it has one.
+ Object rhs = null;
+ try
+ {
+ // The Character class is a special case, since its constructor
+ // does not take a string, so handle it separately.
+ if (lhs instanceof Character)
+ {
+ rhs = new Character(rhsString.charAt(0));
+ }
+ else
+ {
+ // Spec says we should trim number types.
+ if ((lhs instanceof Number) || (lhs instanceof Boolean))
+ {
+ rhsString = rhsString.trim();
+ }
+ Constructor ctor = lhs.getClass().getConstructor(STRING_CLASS);
+ ctor.setAccessible(true);
+ rhs = ctor.newInstance(new Object[] { rhsString });
+ }
+ }
+ catch (Exception ex)
+ {
+ throw new Exception(
+ "Could not instantiate class "
+ + lhs.getClass().getName()
+ + " from string constructor with argument '"
+ + rhsString + "' because " + ex);
+ }
+
+ return rhs;
+ }
+
+ /**
+ * This is an ugly utility method to convert an array of primitives to an
+ * array of primitive wrapper objects. This method simplifies processing
+ * LDAP filters since the special case of primitive arrays can be ignored.
+ *
+ * @param array
+ * An array of primitive types.
+ * @return An corresponding array using pritive wrapper objects.
+ **/
+ private static List convertArrayToList(Object array)
+ {
+ int len = Array.getLength(array);
+ List list = new ArrayList(len);
+ for (int i = 0; i < len; i++)
+ {
+ list.add(Array.get(array, i));
+ }
+ return list;
+ }
+}
\ No newline at end of file
Added: felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Directive.java
URL: http://svn.apache.org/viewvc/felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Directive.java?rev=1583367&view=auto
==============================================================================
--- felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Directive.java (added)
+++ felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Directive.java Mon Mar 31 16:18:17 2014
@@ -0,0 +1,46 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.capabilityset;
+
+public class Directive
+{
+ private final String m_name;
+ private final Object m_value;
+
+ public Directive(String name, Object value)
+ {
+ m_name = name;
+ m_value = value;
+ }
+
+ public String getName()
+ {
+ return m_name;
+ }
+
+ public Object getValue()
+ {
+ return m_value;
+ }
+
+ public String toString()
+ {
+ return m_name + "=" + m_value;
+ }
+}
\ No newline at end of file
Added: felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Requirement.java
URL: http://svn.apache.org/viewvc/felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Requirement.java?rev=1583367&view=auto
==============================================================================
--- felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Requirement.java (added)
+++ felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Requirement.java Mon Mar 31 16:18:17 2014
@@ -0,0 +1,34 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.capabilityset;
+
+import java.util.List;
+
+public interface Requirement
+{
+ String getNamespace();
+
+ SimpleFilter getFilter();
+
+ boolean isOptional();
+
+ Directive getDirective(String name);
+
+ List<Directive> getDirectives();
+}
\ No newline at end of file
Added: felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/SimpleFilter.java
URL: http://svn.apache.org/viewvc/felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/SimpleFilter.java?rev=1583367&view=auto
==============================================================================
--- felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/SimpleFilter.java (added)
+++ felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/SimpleFilter.java Mon Mar 31 16:18:17 2014
@@ -0,0 +1,556 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.capabilityset;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SimpleFilter
+{
+ public static final int MATCH_ALL = 0;
+ public static final int AND = 1;
+ public static final int OR = 2;
+ public static final int NOT = 3;
+ public static final int EQ = 4;
+ public static final int LTE = 5;
+ public static final int GTE = 6;
+ public static final int SUBSTRING = 7;
+ public static final int PRESENT = 8;
+ public static final int APPROX = 9;
+
+ private final String m_name;
+ private final Object m_value;
+ private final int m_op;
+
+ public SimpleFilter(String attr, Object value, int op)
+ {
+ m_name = attr;
+ m_value = value;
+ m_op = op;
+ }
+
+ public String getName()
+ {
+ return m_name;
+ }
+
+ public Object getValue()
+ {
+ return m_value;
+ }
+
+ public int getOperation()
+ {
+ return m_op;
+ }
+
+ public String toString()
+ {
+ String s = null;
+ switch (m_op)
+ {
+ case AND:
+ s = "(&" + toString((List) m_value) + ")";
+ break;
+ case OR:
+ s = "(|" + toString((List) m_value) + ")";
+ break;
+ case NOT:
+ s = "(!" + toString((List) m_value) + ")";
+ break;
+ case EQ:
+ s = "(" + m_name + "=" + toEncodedString(m_value) + ")";
+ break;
+ case LTE:
+ s = "(" + m_name + "<=" + toEncodedString(m_value) + ")";
+ break;
+ case GTE:
+ s = "(" + m_name + ">=" + toEncodedString(m_value) + ")";
+ break;
+ case SUBSTRING:
+ s = "(" + m_name + "=" + unparseSubstring((List<String>) m_value)
+ + ")";
+ break;
+ case PRESENT:
+ s = "(" + m_name + "=*)";
+ break;
+ case APPROX:
+ s = "(" + m_name + "~=" + toEncodedString(m_value) + ")";
+ break;
+ }
+ return s;
+ }
+
+ private static String toString(List list)
+ {
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < list.size(); i++)
+ {
+ sb.append(list.get(i).toString());
+ }
+ return sb.toString();
+ }
+
+ private static String toDecodedString(String s, int startIdx, int endIdx)
+ {
+ StringBuffer sb = new StringBuffer(endIdx - startIdx);
+ boolean escaped = false;
+ for (int i = 0; i < (endIdx - startIdx); i++)
+ {
+ char c = s.charAt(startIdx + i);
+ if (!escaped && (c == '\\'))
+ {
+ escaped = true;
+ }
+ else
+ {
+ escaped = false;
+ sb.append(c);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ private static String toEncodedString(Object o)
+ {
+ if (o instanceof String)
+ {
+ String s = (String) o;
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < s.length(); i++)
+ {
+ char c = s.charAt(i);
+ if ((c == '\\') || (c == '(') || (c == ')') || (c == '*'))
+ {
+ sb.append('\\');
+ }
+ sb.append(c);
+ }
+
+ o = sb.toString();
+ }
+
+ return o.toString();
+ }
+
+ public static SimpleFilter parse(String filter)
+ {
+ int idx = skipWhitespace(filter, 0);
+
+ if ((filter == null) || (filter.length() == 0)
+ || (idx >= filter.length()))
+ {
+ throw new IllegalArgumentException("Null or empty filter.");
+ }
+ else if (filter.charAt(idx) != '(')
+ {
+ throw new IllegalArgumentException("Missing opening parenthesis: "
+ + filter);
+ }
+
+ SimpleFilter sf = null;
+ List stack = new ArrayList();
+ boolean isEscaped = false;
+ while (idx < filter.length())
+ {
+ if (sf != null)
+ {
+ throw new IllegalArgumentException(
+ "Only one top-level operation allowed: " + filter);
+ }
+
+ if (!isEscaped && (filter.charAt(idx) == '('))
+ {
+ // Skip paren and following whitespace.
+ idx = skipWhitespace(filter, idx + 1);
+
+ if (filter.charAt(idx) == '&')
+ {
+ int peek = skipWhitespace(filter, idx + 1);
+ if (filter.charAt(peek) == '(')
+ {
+ idx = peek - 1;
+ stack.add(0, new SimpleFilter(null, new ArrayList(),
+ SimpleFilter.AND));
+ }
+ else
+ {
+ stack.add(0, new Integer(idx));
+ }
+ }
+ else if (filter.charAt(idx) == '|')
+ {
+ int peek = skipWhitespace(filter, idx + 1);
+ if (filter.charAt(peek) == '(')
+ {
+ idx = peek - 1;
+ stack.add(0, new SimpleFilter(null, new ArrayList(),
+ SimpleFilter.OR));
+ }
+ else
+ {
+ stack.add(0, new Integer(idx));
+ }
+ }
+ else if (filter.charAt(idx) == '!')
+ {
+ int peek = skipWhitespace(filter, idx + 1);
+ if (filter.charAt(peek) == '(')
+ {
+ idx = peek - 1;
+ stack.add(0, new SimpleFilter(null, new ArrayList(),
+ SimpleFilter.NOT));
+ }
+ else
+ {
+ stack.add(0, new Integer(idx));
+ }
+ }
+ else
+ {
+ stack.add(0, new Integer(idx));
+ }
+ }
+ else if (!isEscaped && (filter.charAt(idx) == ')'))
+ {
+ Object top = stack.remove(0);
+ if (top instanceof SimpleFilter)
+ {
+ if (!stack.isEmpty()
+ && (stack.get(0) instanceof SimpleFilter))
+ {
+ ((List) ((SimpleFilter) stack.get(0)).m_value).add(top);
+ }
+ else
+ {
+ sf = (SimpleFilter) top;
+ }
+ }
+ else if (!stack.isEmpty()
+ && (stack.get(0) instanceof SimpleFilter))
+ {
+ ((List) ((SimpleFilter) stack.get(0)).m_value)
+ .add(SimpleFilter.subfilter(filter,
+ ((Integer) top).intValue(), idx));
+ }
+ else
+ {
+ sf = SimpleFilter.subfilter(filter,
+ ((Integer) top).intValue(), idx);
+ }
+ }
+ else if (!isEscaped && (filter.charAt(idx) == '\\'))
+ {
+ isEscaped = true;
+ }
+ else
+ {
+ isEscaped = false;
+ }
+
+ idx = skipWhitespace(filter, idx + 1);
+ }
+
+ if (sf == null)
+ {
+ throw new IllegalArgumentException("Missing closing parenthesis: "
+ + filter);
+ }
+
+ return sf;
+ }
+
+ private static SimpleFilter subfilter(String filter, int startIdx,
+ int endIdx)
+ {
+ final String opChars = "=<>~";
+
+ // Determine the ending index of the attribute name.
+ int attrEndIdx = startIdx;
+ for (int i = 0; i < (endIdx - startIdx); i++)
+ {
+ char c = filter.charAt(startIdx + i);
+ if (opChars.indexOf(c) >= 0)
+ {
+ break;
+ }
+ else if (!Character.isWhitespace(c))
+ {
+ attrEndIdx = startIdx + i + 1;
+ }
+ }
+ if (attrEndIdx == startIdx)
+ {
+ throw new IllegalArgumentException("Missing attribute name: "
+ + filter.substring(startIdx, endIdx));
+ }
+ String attr = filter.substring(startIdx, attrEndIdx);
+
+ // Skip the attribute name and any following whitespace.
+ startIdx = skipWhitespace(filter, attrEndIdx);
+
+ // Determine the operator type.
+ int op = -1;
+ switch (filter.charAt(startIdx))
+ {
+ case '=':
+ op = EQ;
+ startIdx++;
+ break;
+ case '<':
+ if (filter.charAt(startIdx + 1) != '=')
+ {
+ throw new IllegalArgumentException("Unknown operator: "
+ + filter.substring(startIdx, endIdx));
+ }
+ op = LTE;
+ startIdx += 2;
+ break;
+ case '>':
+ if (filter.charAt(startIdx + 1) != '=')
+ {
+ throw new IllegalArgumentException("Unknown operator: "
+ + filter.substring(startIdx, endIdx));
+ }
+ op = GTE;
+ startIdx += 2;
+ break;
+ case '~':
+ if (filter.charAt(startIdx + 1) != '=')
+ {
+ throw new IllegalArgumentException("Unknown operator: "
+ + filter.substring(startIdx, endIdx));
+ }
+ op = APPROX;
+ startIdx += 2;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown operator: "
+ + filter.substring(startIdx, endIdx));
+ }
+
+ // Parse value.
+ Object value = toDecodedString(filter, startIdx, endIdx);
+
+ // Check if the equality comparison is actually a substring
+ // or present operation.
+ if (op == EQ)
+ {
+ String valueStr = filter.substring(startIdx, endIdx);
+ List<String> values = parseSubstring(valueStr);
+ if ((values.size() == 2) && (values.get(0).length() == 0)
+ && (values.get(1).length() == 0))
+ {
+ op = PRESENT;
+ }
+ else if (values.size() > 1)
+ {
+ op = SUBSTRING;
+ value = values;
+ }
+ }
+
+ return new SimpleFilter(attr, value, op);
+ }
+
+ public static List<String> parseSubstring(String value)
+ {
+ List<String> pieces = new ArrayList();
+ StringBuffer ss = new StringBuffer();
+ // int kind = SIMPLE; // assume until proven otherwise
+ boolean wasStar = false; // indicates last piece was a star
+ boolean leftstar = false; // track if the initial piece is a star
+ boolean rightstar = false; // track if the final piece is a star
+
+ int idx = 0;
+
+ // We assume (sub)strings can contain leading and trailing blanks
+ boolean escaped = false;
+ loop: for (;;)
+ {
+ if (idx >= value.length())
+ {
+ if (wasStar)
+ {
+ // insert last piece as "" to handle trailing star
+ rightstar = true;
+ }
+ else
+ {
+ pieces.add(ss.toString());
+ // accumulate the last piece
+ // note that in the case of
+ // (cn=); this might be
+ // the string "" (!=null)
+ }
+ ss.setLength(0);
+ break loop;
+ }
+
+ // Read the next character and account for escapes.
+ char c = value.charAt(idx++);
+ if (!escaped && ((c == '(') || (c == ')')))
+ {
+ throw new IllegalArgumentException("Illegal value: " + value);
+ }
+ else if (!escaped && (c == '*'))
+ {
+ if (wasStar)
+ {
+ // encountered two successive stars;
+ // I assume this is illegal
+ throw new IllegalArgumentException(
+ "Invalid filter string: " + value);
+ }
+ if (ss.length() > 0)
+ {
+ pieces.add(ss.toString()); // accumulate the pieces
+ // between '*' occurrences
+ }
+ ss.setLength(0);
+ // if this is a leading star, then track it
+ if (pieces.size() == 0)
+ {
+ leftstar = true;
+ }
+ wasStar = true;
+ }
+ else if (!escaped && (c == '\\'))
+ {
+ escaped = true;
+ }
+ else
+ {
+ escaped = false;
+ wasStar = false;
+ ss.append(c);
+ }
+ }
+ if (leftstar || rightstar || pieces.size() > 1)
+ {
+ // insert leading and/or trailing "" to anchor ends
+ if (rightstar)
+ {
+ pieces.add("");
+ }
+ if (leftstar)
+ {
+ pieces.add(0, "");
+ }
+ }
+ return pieces;
+ }
+
+ public static String unparseSubstring(List<String> pieces)
+ {
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < pieces.size(); i++)
+ {
+ if (i > 0)
+ {
+ sb.append("*");
+ }
+ sb.append(toEncodedString(pieces.get(i)));
+ }
+ return sb.toString();
+ }
+
+ public static boolean compareSubstring(List<String> pieces, String s)
+ {
+ // Walk the pieces to match the string
+ // There are implicit stars between each piece,
+ // and the first and last pieces might be "" to anchor the match.
+ // assert (pieces.length > 1)
+ // minimal case is <string>*<string>
+
+ boolean result = true;
+ int len = pieces.size();
+
+ // Special case, if there is only one piece, then
+ // we must perform an equality test.
+ if (len == 1)
+ {
+ return s.equals(pieces.get(0));
+ }
+
+ // Otherwise, check whether the pieces match
+ // the specified string.
+
+ int index = 0;
+
+ loop: for (int i = 0; i < len; i++)
+ {
+ String piece = pieces.get(i);
+
+ // If this is the first piece, then make sure the
+ // string starts with it.
+ if (i == 0)
+ {
+ if (!s.startsWith(piece))
+ {
+ result = false;
+ break loop;
+ }
+ }
+
+ // If this is the last piece, then make sure the
+ // string ends with it.
+ if (i == len - 1)
+ {
+ if (s.endsWith(piece))
+ {
+ result = true;
+ }
+ else
+ {
+ result = false;
+ }
+ break loop;
+ }
+
+ // If this is neither the first or last piece, then
+ // make sure the string contains it.
+ if ((i > 0) && (i < (len - 1)))
+ {
+ index = s.indexOf(piece, index);
+ if (index < 0)
+ {
+ result = false;
+ break loop;
+ }
+ }
+
+ // Move string index beyond the matching piece.
+ index += piece.length();
+ }
+
+ return result;
+ }
+
+ private static int skipWhitespace(String s, int startIdx)
+ {
+ int len = s.length();
+ while ((startIdx < len) && Character.isWhitespace(s.charAt(startIdx)))
+ {
+ startIdx++;
+ }
+ return startIdx;
+ }
+}
\ No newline at end of file
Added: felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/EventDispatcher.java
URL: http://svn.apache.org/viewvc/felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/EventDispatcher.java?rev=1583367&view=auto
==============================================================================
--- felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/EventDispatcher.java (added)
+++ felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/EventDispatcher.java Mon Mar 31 16:18:17 2014
@@ -0,0 +1,1044 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.EventListener;
+import java.util.EventObject;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import org.osgi.framework.AllServiceListener;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServicePermission;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.SynchronousBundleListener;
+import org.osgi.framework.hooks.service.ListenerHook;
+import org.osgi.framework.launch.Framework;
+
+import de.kalpatec.pojosr.framework.felix.framework.ServiceRegistry;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class EventDispatcher
+{
+ private final ServiceRegistry m_registry;
+ private Map<BundleContext, List<ListenerInfo>> m_fwkListeners = Collections.EMPTY_MAP;
+ private Map<BundleContext, List<ListenerInfo>> m_bndlListeners = Collections.EMPTY_MAP;
+ private Map<BundleContext, List<ListenerInfo>> m_syncBndlListeners = Collections.EMPTY_MAP;
+ private Map<BundleContext, List<ListenerInfo>> m_svcListeners = Collections.EMPTY_MAP;
+ // A single thread is used to deliver events for all dispatchers.
+ private static Thread m_thread = null;
+ private final static String m_threadLock = new String("thread lock");
+ private static int m_references = 0;
+ private static volatile boolean m_stopping = false;
+ // List of requests.
+ private static final List<Request> m_requestList = new ArrayList<Request>();
+ // Pooled requests to avoid memory allocation.
+ private static final List<Request> m_requestPool = new ArrayList<Request>();
+
+ private static final boolean m_sync = "true".equalsIgnoreCase(System
+ .getProperty("de.kalpatec.pojosr.framework.events.sync"));
+
+ public EventDispatcher(ServiceRegistry registry)
+ {
+ m_registry = registry;
+ }
+
+ public void startDispatching()
+ {
+ synchronized (m_threadLock)
+ {
+ // Start event dispatching thread if necessary.
+ if (m_thread == null || !m_thread.isAlive())
+ {
+ m_stopping = false;
+
+ if (!m_sync)
+ {
+ m_thread = new Thread(new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ EventDispatcher.run();
+ }
+ finally
+ {
+ // Ensure we update state even if stopped by
+ // external cause
+ // e.g. an Applet VM forceably killing threads
+ synchronized (m_threadLock)
+ {
+ m_thread = null;
+ m_stopping = false;
+ m_references = 0;
+ m_threadLock.notifyAll();
+ }
+ }
+ }
+ }, "FelixDispatchQueue");
+ m_thread.start();
+ }
+ }
+
+ // reference counting and flags
+ m_references++;
+ }
+ }
+
+ public void stopDispatching()
+ {
+ synchronized (m_threadLock)
+ {
+ // Return if already dead or stopping.
+ if (m_thread == null || m_stopping)
+ {
+ return;
+ }
+
+ // decrement use counter, don't continue if there are users
+ m_references--;
+ if (m_references > 0)
+ {
+ return;
+ }
+
+ m_stopping = true;
+ }
+
+ // Signal dispatch thread.
+ synchronized (m_requestList)
+ {
+ m_requestList.notify();
+ }
+
+ // Use separate lock for shutdown to prevent any chance of nested lock
+ // deadlock
+ synchronized (m_threadLock)
+ {
+ while (m_thread != null)
+ {
+ try
+ {
+ m_threadLock.wait();
+ }
+ catch (InterruptedException ex)
+ {
+ }
+ }
+ }
+ }
+
+ public Filter addListener(BundleContext bc, Class clazz, EventListener l, Filter filter)
+ {
+ // Verify the listener.
+ if (l == null)
+ {
+ throw new IllegalArgumentException("Listener is null");
+ }
+ else if (!clazz.isInstance(l))
+ {
+ throw new IllegalArgumentException(
+ "Listener not of type " + clazz.getName());
+ }
+
+ // See if we can simply update the listener, if so then
+ // return immediately.
+ Filter oldFilter = updateListener(bc, clazz, l, filter);
+ if (oldFilter != null)
+ {
+ return oldFilter;
+ }
+
+ // Lock the object to add the listener.
+ synchronized (this)
+ {
+ // Verify that the bundle context is still valid.
+ try
+ {
+ bc.getBundle();
+ }
+ catch (IllegalStateException ex)
+ {
+ // Bundle context is no longer valid, so just return.
+ }
+
+ Map<BundleContext, List<ListenerInfo>> listeners = null;
+ Object acc = null;
+
+ if (clazz == FrameworkListener.class)
+ {
+ listeners = m_fwkListeners;
+ }
+ else if (clazz == BundleListener.class)
+ {
+ if (SynchronousBundleListener.class.isInstance(l))
+ {
+ listeners = m_syncBndlListeners;
+ }
+ else
+ {
+ listeners = m_bndlListeners;
+ }
+ }
+ else if (clazz == ServiceListener.class)
+ {
+ // Remember security context for filtering service events.
+ /* Object sm = System.getSecurityManager();
+ if (sm != null)
+ {
+ acc = ((SecurityManager) sm).getSecurityContext();
+ }*/
+ // We need to create a Set for keeping track of matching service
+ // registrations so we can fire ServiceEvent.MODIFIED_ENDMATCH
+ // events. We need a Set even if filter is null, since the
+ // listener can be updated and have a filter added later.
+ listeners = m_svcListeners;
+ }
+ else
+ {
+ throw new IllegalArgumentException("Unknown listener: " + l.getClass());
+ }
+
+ // Add listener.
+ ListenerInfo info =
+ new ListenerInfo(bc.getBundle(), bc, clazz, l, filter, acc, false);
+ listeners = addListenerInfo(listeners, info);
+
+ if (clazz == FrameworkListener.class)
+ {
+ m_fwkListeners = listeners;
+ }
+ else if (clazz == BundleListener.class)
+ {
+ if (SynchronousBundleListener.class.isInstance(l))
+ {
+ m_syncBndlListeners = listeners;
+ }
+ else
+ {
+ m_bndlListeners = listeners;
+ }
+ }
+ else if (clazz == ServiceListener.class)
+ {
+ m_svcListeners = listeners;
+ }
+ }
+ return null;
+ }
+
+ public ListenerHook.ListenerInfo removeListener(
+ BundleContext bc, Class clazz, EventListener l)
+ {
+ ListenerHook.ListenerInfo returnInfo = null;
+
+ // Verify listener.
+ if (l == null)
+ {
+ throw new IllegalArgumentException("Listener is null");
+ }
+ else if (!clazz.isInstance(l))
+ {
+ throw new IllegalArgumentException(
+ "Listener not of type " + clazz.getName());
+ }
+
+ // Lock the object to remove the listener.
+ synchronized (this)
+ {
+ Map<BundleContext, List<ListenerInfo>> listeners = null;
+
+ if (clazz == FrameworkListener.class)
+ {
+ listeners = m_fwkListeners;
+ }
+ else if (clazz == BundleListener.class)
+ {
+ if (SynchronousBundleListener.class.isInstance(l))
+ {
+ listeners = m_syncBndlListeners;
+ }
+ else
+ {
+ listeners = m_bndlListeners;
+ }
+ }
+ else if (clazz == ServiceListener.class)
+ {
+ listeners = m_svcListeners;
+ }
+ else
+ {
+ throw new IllegalArgumentException("Unknown listener: " + l.getClass());
+ }
+
+ // Try to find the instance in our list.
+ int idx = -1;
+ for (Entry<BundleContext, List<ListenerInfo>> entry : listeners.entrySet())
+ {
+ List<ListenerInfo> infos = entry.getValue();
+ for (int i = 0; i < infos.size(); i++)
+ {
+ ListenerInfo info = infos.get(i);
+ if (info.getBundleContext().equals(bc)
+ && (info.getListenerClass() == clazz)
+ && (info.getListener() == l))
+ {
+ // For service listeners, we must return some info about
+ // the listener for the ListenerHook callback.
+ if (ServiceListener.class == clazz)
+ {
+ returnInfo = new ListenerInfo(infos.get(i), true);
+ }
+ idx = i;
+ break;
+ }
+ }
+ }
+
+ // If we have the instance, then remove it.
+ if (idx >= 0)
+ {
+ listeners = removeListenerInfo(listeners, bc, idx);
+ }
+
+ if (clazz == FrameworkListener.class)
+ {
+ m_fwkListeners = listeners;
+ }
+ else if (clazz == BundleListener.class)
+ {
+ if (SynchronousBundleListener.class.isInstance(l))
+ {
+ m_syncBndlListeners = listeners;
+ }
+ else
+ {
+ m_bndlListeners = listeners;
+ }
+ }
+ else if (clazz == ServiceListener.class)
+ {
+ m_svcListeners = listeners;
+ }
+ }
+
+ // Return information about the listener; this is null
+ // for everything but service listeners.
+ return returnInfo;
+ }
+
+ public void removeListeners(BundleContext bc)
+ {
+ if (bc == null)
+ {
+ return;
+ }
+
+ synchronized (this)
+ {
+ // Remove all framework listeners associated with the specified bundle.
+ m_fwkListeners = removeListenerInfos(m_fwkListeners, bc);
+
+ // Remove all bundle listeners associated with the specified bundle.
+ m_bndlListeners = removeListenerInfos(m_bndlListeners, bc);
+
+ // Remove all synchronous bundle listeners associated with
+ // the specified bundle.
+ m_syncBndlListeners = removeListenerInfos(m_syncBndlListeners, bc);
+
+ // Remove all service listeners associated with the specified bundle.
+ m_svcListeners = removeListenerInfos(m_svcListeners, bc);
+ }
+ }
+
+ public Filter updateListener(BundleContext bc, Class clazz, EventListener l, Filter filter)
+ {
+ if (clazz == ServiceListener.class)
+ {
+ synchronized (this)
+ {
+ // Verify that the bundle context is still valid.
+ try
+ {
+ bc.getBundle();
+ }
+ catch (IllegalStateException ex)
+ {
+ // Bundle context is no longer valid, so just return.
+ }
+
+ // See if the service listener is already registered; if so then
+ // update its filter per the spec.
+ List<ListenerInfo> infos = m_svcListeners.get(bc);
+ for (int i = 0; (infos != null) && (i < infos.size()); i++)
+ {
+ ListenerInfo info = infos.get(i);
+ if (info.getBundleContext().equals(bc)
+ && (info.getListenerClass() == clazz)
+ && (info.getListener() == l))
+ {
+ // The spec says to update the filter in this case.
+ Filter oldFilter = info.getParsedFilter();
+ ListenerInfo newInfo = new ListenerInfo(
+ info.getBundle(),
+ info.getBundleContext(),
+ info.getListenerClass(),
+ info.getListener(),
+ filter,
+ info.getSecurityContext(),
+ info.isRemoved());
+ m_svcListeners = updateListenerInfo(m_svcListeners, i, newInfo);
+ return oldFilter;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns all existing service listener information into a collection of
+ * ListenerHook.ListenerInfo objects. This is used the first time a listener
+ * hook is registered to synchronize it with the existing set of listeners.
+ *
+ * @return Returns all existing service listener information into a
+ * collection of ListenerHook.ListenerInfo objects
+ *
+ */
+ public Collection<ListenerHook.ListenerInfo> getAllServiceListeners()
+ {
+ List<ListenerHook.ListenerInfo> listeners = new ArrayList<ListenerHook.ListenerInfo>();
+ synchronized (this)
+ {
+ for (Entry<BundleContext, List<ListenerInfo>> entry : m_svcListeners.entrySet())
+ {
+ listeners.addAll(entry.getValue());
+ }
+ }
+ return listeners;
+ }
+
+ public void fireFrameworkEvent(FrameworkEvent event)
+ {
+ // Take a snapshot of the listener array.
+ Map<BundleContext, List<ListenerInfo>> listeners = null;
+ synchronized (this)
+ {
+ listeners = m_fwkListeners;
+ }
+
+ // Fire all framework listeners on a separate thread.
+ fireEventAsynchronously(this, Request.FRAMEWORK_EVENT, listeners, event);
+ }
+
+ public void fireBundleEvent(BundleEvent event)
+ {
+ // Take a snapshot of the listener array.
+ Map<BundleContext, List<ListenerInfo>> listeners = null;
+ Map<BundleContext, List<ListenerInfo>> syncListeners = null;
+ synchronized (this)
+ {
+ listeners = m_bndlListeners;
+ syncListeners = m_syncBndlListeners;
+ }
+
+ // Create a whitelist of bundle context for bundle listeners,
+ // if we have hooks.
+ Set<BundleContext> whitelist = createWhitelistFromHooks(event, event.getBundle(),
+ listeners, syncListeners, org.osgi.framework.hooks.bundle.EventHook.class);
+
+ // If we have a whitelist, then create copies of only the whitelisted
+ // listeners.
+ if (whitelist != null)
+ {
+ Map<BundleContext, List<ListenerInfo>> copy =
+ new HashMap<BundleContext, List<ListenerInfo>>();
+ for (BundleContext bc : whitelist)
+ {
+ List<ListenerInfo> infos = listeners.get(bc);
+ if (infos != null)
+ {
+ copy.put(bc, infos);
+ }
+ }
+ listeners = copy;
+ copy = new HashMap<BundleContext, List<ListenerInfo>>();
+ for (BundleContext bc : whitelist)
+ {
+ List<ListenerInfo> infos = syncListeners.get(bc);
+ if (infos != null)
+ {
+ copy.put(bc, infos);
+ }
+ }
+ syncListeners = copy;
+ }
+
+ // Fire synchronous bundle listeners immediately on the calling thread.
+ fireEventImmediately(
+ this, Request.BUNDLE_EVENT, syncListeners, event, null);
+
+ // The spec says that asynchronous bundle listeners do not get events
+ // of types STARTING, STOPPING, or LAZY_ACTIVATION.
+ if ((event.getType() != BundleEvent.STARTING)
+ && (event.getType() != BundleEvent.STOPPING)
+ && (event.getType() != BundleEvent.LAZY_ACTIVATION))
+ {
+ // Fire asynchronous bundle listeners on a separate thread.
+ fireEventAsynchronously(
+ this, Request.BUNDLE_EVENT, listeners, event);
+ }
+ }
+
+ public void fireServiceEvent(
+ final ServiceEvent event, final Dictionary oldProps, final Framework felix)
+ {
+ // Take a snapshot of the listener array.
+ Map<BundleContext, List<ListenerInfo>> listeners = null;
+ synchronized (this)
+ {
+ listeners = m_svcListeners;
+ }
+
+ // Use service registry hooks to filter target listeners.
+ listeners = filterListenersUsingHooks(event, felix, listeners);
+
+ // Fire all service events immediately on the calling thread.
+ fireEventImmediately(
+ this, Request.SERVICE_EVENT, listeners, event, oldProps);
+ }
+
+// TODO: OSGi R4.3 - This is ugly and inefficient.
+ private Map<BundleContext, List<ListenerInfo>> filterListenersUsingHooks(
+ ServiceEvent event, Framework felix, Map<BundleContext, List<ListenerInfo>> listeners)
+ {
+ Set<ServiceReference<org.osgi.framework.hooks.service.EventHook>> ehs =
+ m_registry.getHooks(org.osgi.framework.hooks.service.EventHook.class);
+ if ((ehs != null) && !ehs.isEmpty())
+ {
+ // Create a whitelist of bundle context for bundle listeners,
+ // if we have hooks.
+ Set<BundleContext> whitelist = createWhitelistFromHooks(event, felix,
+ listeners, null, org.osgi.framework.hooks.service.EventHook.class);
+
+ // If we have a whitelist, then create copies of only the whitelisted
+ // listeners.
+ if (whitelist != null)
+ {
+ Map<BundleContext, List<ListenerInfo>> copy =
+ new HashMap<BundleContext, List<ListenerInfo>>();
+ for (BundleContext bc : whitelist)
+ {
+ copy.put(bc, listeners.get(bc));
+ }
+ listeners = copy;
+ }
+ }
+
+ Set<ServiceReference<org.osgi.framework.hooks.service.EventListenerHook>> elhs =
+ m_registry.getHooks(org.osgi.framework.hooks.service.EventListenerHook.class);
+ if ((elhs != null) && !elhs.isEmpty())
+ {
+ // Create shrinkable map with shrinkable collections.
+ Map<BundleContext, Collection<ListenerHook.ListenerInfo>> shrinkableMap =
+ new HashMap<BundleContext, Collection<ListenerHook.ListenerInfo>>();
+ for (Entry<BundleContext, List<ListenerInfo>> entry : listeners.entrySet())
+ {
+ Collection shrinkableCollection =
+ new ShrinkableCollection(new ArrayList(entry.getValue()));
+ shrinkableMap.put(
+ entry.getKey(),
+ (Collection<ListenerHook.ListenerInfo>) shrinkableCollection);
+ }
+ shrinkableMap =
+ new ShrinkableMap<BundleContext, Collection<ListenerHook.ListenerInfo>>(shrinkableMap);
+ for (ServiceReference<org.osgi.framework.hooks.service.EventListenerHook> sr : elhs)
+ {
+ if (felix != null)
+ {
+ org.osgi.framework.hooks.service.EventListenerHook elh = null;
+ try
+ {
+ elh = m_registry.getService(felix, sr);
+ }
+ catch (Exception ex)
+ {
+ // If we can't get the hook, then ignore it.
+ }
+ if (elh != null)
+ {
+ try
+ {
+ elh.event(event, shrinkableMap);
+ }
+ catch (Throwable th)
+ {
+ System.out.println("Problem invoking event hook");
+ th.printStackTrace();
+ }
+ finally
+ {
+ m_registry.ungetService(felix, sr);
+ }
+ }
+ }
+ }
+// TODO: OSGi R4.3 - Should check and only do this if there was a change.
+// Also, it is inefficient to have to create new lists for the values.
+ Map<BundleContext, List<ListenerInfo>> newMap =
+ new HashMap<BundleContext, List<ListenerInfo>>();
+ for (Entry entry : shrinkableMap.entrySet())
+ {
+ if (!((Collection) entry.getValue()).isEmpty())
+ {
+ newMap.put((BundleContext) entry.getKey(),
+ new ArrayList<ListenerInfo>((Collection) entry.getValue()));
+ }
+ }
+ listeners = newMap;
+ }
+
+ return listeners;
+ }
+
+ private <T> Set<BundleContext> createWhitelistFromHooks(
+ EventObject event, Bundle bundle,
+ Map<BundleContext, List<ListenerInfo>> listeners1,
+ Map<BundleContext, List<ListenerInfo>> listeners2,
+ Class<T> hookClass)
+ {
+ // Create a whitelist of bundle context, if we have hooks.
+ Set<BundleContext> whitelist = null;
+ Set<ServiceReference<T>> hooks = m_registry.getHooks(hookClass);
+ if ((hooks != null) && !hooks.isEmpty())
+ {
+ whitelist = new HashSet<BundleContext>();
+ for (Entry<BundleContext, List<ListenerInfo>> entry : listeners1.entrySet())
+ {
+ whitelist.add(entry.getKey());
+ }
+ if (listeners2 != null)
+ {
+ for (Entry<BundleContext, List<ListenerInfo>> entry : listeners2.entrySet())
+ {
+ whitelist.add(entry.getKey());
+ }
+ }
+
+ int originalSize = whitelist.size();
+ ShrinkableCollection<BundleContext> shrinkable =
+ new ShrinkableCollection<BundleContext>(whitelist);
+ for (ServiceReference<T> sr : hooks)
+ {
+ if (bundle != null)
+ {
+ T eh = null;
+ try
+ {
+ eh = m_registry.getService(bundle, sr);
+ }
+ catch (Exception ex)
+ {
+ // If we can't get the hook, then ignore it.
+ }
+ if (eh != null)
+ {
+ try
+ {
+ if (eh instanceof org.osgi.framework.hooks.service.EventHook)
+ {
+ ((org.osgi.framework.hooks.service.EventHook) eh).event((ServiceEvent) event, shrinkable);
+ }
+ else if (eh instanceof org.osgi.framework.hooks.bundle.EventHook)
+ {
+ ((org.osgi.framework.hooks.bundle.EventHook) eh).event((BundleEvent) event, shrinkable);
+ }
+ }
+ catch (Throwable th)
+ {
+ System.out.println("Problem invoking event hook");
+ th.printStackTrace();
+ }
+ finally
+ {
+ m_registry.ungetService(bundle, sr);
+ }
+ }
+ }
+ }
+ // If the whitelist hasn't changed, then null it to avoid having
+ // to do whitelist lookups during event delivery.
+ if (originalSize == whitelist.size())
+ {
+ whitelist = null;
+ }
+ }
+ return whitelist;
+ }
+
+ private static void fireEventAsynchronously(
+ EventDispatcher dispatcher, int type,
+ Map<BundleContext, List<ListenerInfo>> listeners,
+ EventObject event)
+ {
+ if (!m_sync)
+ {
+ // TODO: should possibly check this within thread lock, seems to be
+ // ok
+ // though without
+ // If dispatch thread is stopped, then ignore dispatch request.
+ if (m_stopping || m_thread == null)
+ {
+ return;
+ }
+
+ // First get a request from the pool or create one if necessary.
+ Request req = null;
+ synchronized (m_requestPool)
+ {
+ if (m_requestPool.size() > 0)
+ {
+ req = m_requestPool.remove(0);
+ }
+ else
+ {
+ req = new Request();
+ }
+ }
+
+ // Initialize dispatch request.
+ req.m_dispatcher = dispatcher;
+ req.m_type = type;
+ req.m_listeners = listeners;
+ req.m_event = event;
+
+ // Lock the request list.
+ synchronized (m_requestList)
+ {
+ // Add our request to the list.
+ m_requestList.add(req);
+ // Notify the dispatch thread that there is work to do.
+ m_requestList.notify();
+ }
+ }
+ else
+ {
+ fireEventImmediately(dispatcher, type, listeners, event, null);
+ }
+ }
+
+ private static void fireEventImmediately(
+ EventDispatcher dispatcher, int type,
+ Map<BundleContext, List<ListenerInfo>> listeners,
+ EventObject event, Dictionary oldProps)
+ {
+ if (!listeners.isEmpty())
+ {
+ // Notify appropriate listeners.
+ for (Entry<BundleContext, List<ListenerInfo>> entry : listeners.entrySet())
+ {
+ for (ListenerInfo info : entry.getValue())
+ {
+ Bundle bundle = info.getBundle();
+ EventListener l = info.getListener();
+ Filter filter = info.getParsedFilter();
+ Object acc = info.getSecurityContext();
+
+ try
+ {
+ if (type == Request.FRAMEWORK_EVENT)
+ {
+ invokeFrameworkListenerCallback(bundle, l, event);
+ }
+ else if (type == Request.BUNDLE_EVENT)
+ {
+ invokeBundleListenerCallback(bundle, l, event);
+ }
+ else if (type == Request.SERVICE_EVENT)
+ {
+ invokeServiceListenerCallback(
+ bundle, l, filter, acc, event, oldProps);
+ }
+ }
+ catch (Throwable th)
+ {
+ if ((type != Request.FRAMEWORK_EVENT)
+ || (((FrameworkEvent) event).getType() != FrameworkEvent.ERROR))
+ {
+ System.out.println("EventDispatcher: Error during dispatch.");
+ th.printStackTrace();
+ dispatcher.fireFrameworkEvent(
+ new FrameworkEvent(FrameworkEvent.ERROR, bundle, th));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static void invokeFrameworkListenerCallback(
+ Bundle bundle, final EventListener l, final EventObject event)
+ {
+ // The spec says only active bundles receive asynchronous events,
+ // but we will include starting bundles too otherwise
+ // it is impossible to see everything.
+ if ((bundle.getState() == Bundle.STARTING)
+ || (bundle.getState() == Bundle.ACTIVE))
+ {
+ ((FrameworkListener) l).frameworkEvent((FrameworkEvent) event);
+
+ }
+ }
+
+ private static void invokeBundleListenerCallback(
+ Bundle bundle, final EventListener l, final EventObject event)
+ {
+ // A bundle listener is either synchronous or asynchronous.
+ // If the bundle listener is synchronous, then deliver the
+ // event to bundles with a state of STARTING, STOPPING, or
+ // ACTIVE. If the listener is asynchronous, then deliver the
+ // event only to bundles that are STARTING or ACTIVE.
+ if (((SynchronousBundleListener.class.isAssignableFrom(l.getClass()))
+ && ((bundle.getState() == Bundle.STARTING)
+ || (bundle.getState() == Bundle.STOPPING)
+ || (bundle.getState() == Bundle.ACTIVE)))
+ || ((bundle.getState() == Bundle.STARTING)
+ || (bundle.getState() == Bundle.ACTIVE)))
+ {
+ ((BundleListener) l).bundleChanged((BundleEvent) event);
+ }
+ }
+
+ private static void invokeServiceListenerCallback(Bundle bundle,
+ final EventListener l, Filter filter, Object acc,
+ final EventObject event, final Dictionary oldProps)
+ {
+ // Service events should be delivered to STARTING,
+ // STOPPING, and ACTIVE bundles.
+ if ((bundle.getState() != Bundle.STARTING)
+ && (bundle.getState() != Bundle.STOPPING)
+ && (bundle.getState() != Bundle.ACTIVE))
+ {
+ return;
+ }
+
+ // Check that the bundle has permission to get at least
+ // one of the service interfaces; the objectClass property
+ // of the service stores its service interfaces.
+ ServiceReference ref = ((ServiceEvent) event).getServiceReference();
+
+ boolean hasPermission = true;
+ if (hasPermission)
+ {
+ // Dispatch according to the filter.
+ boolean matched = (filter == null)
+ || filter.match(((ServiceEvent) event).getServiceReference());
+
+ if (matched)
+ {
+ ((ServiceListener) l)
+ .serviceChanged((ServiceEvent) event);
+ }
+ // We need to send an MODIFIED_ENDMATCH event if the listener
+ // matched previously.
+ else if (((ServiceEvent) event).getType() == ServiceEvent.MODIFIED)
+ {
+ if (filter.match(oldProps))
+ {
+ final ServiceEvent se = new ServiceEvent(
+ ServiceEvent.MODIFIED_ENDMATCH,
+ ((ServiceEvent) event).getServiceReference());
+ ((ServiceListener) l).serviceChanged(se);
+
+ }
+ }
+ }
+ }
+
+ private static Map<BundleContext, List<ListenerInfo>> addListenerInfo(
+ Map<BundleContext, List<ListenerInfo>> listeners, ListenerInfo info)
+ {
+ // Make a copy of the map, since we will be mutating it.
+ Map<BundleContext, List<ListenerInfo>> copy =
+ new HashMap<BundleContext, List<ListenerInfo>>(listeners);
+ // Remove the affected entry and make a copy so we can modify it.
+ List<ListenerInfo> infos = copy.remove(info.getBundleContext());
+ if (infos == null)
+ {
+ infos = new ArrayList<ListenerInfo>();
+ }
+ else
+ {
+ infos = new ArrayList<ListenerInfo>(infos);
+ }
+ // Add the new listener info.
+ infos.add(info);
+ // Put the listeners back into the copy of the map and return it.
+ copy.put(info.getBundleContext(), infos);
+ return copy;
+ }
+
+ private static Map<BundleContext, List<ListenerInfo>> updateListenerInfo(
+ Map<BundleContext, List<ListenerInfo>> listeners, int idx,
+ ListenerInfo info)
+ {
+ // Make a copy of the map, since we will be mutating it.
+ Map<BundleContext, List<ListenerInfo>> copy =
+ new HashMap<BundleContext, List<ListenerInfo>>(listeners);
+ // Remove the affected entry and make a copy so we can modify it.
+ List<ListenerInfo> infos = copy.remove(info.getBundleContext());
+ if (infos != null)
+ {
+ infos = new ArrayList<ListenerInfo>(infos);
+ // Update the new listener info.
+ infos.set(idx, info);
+ // Put the listeners back into the copy of the map and return it.
+ copy.put(info.getBundleContext(), infos);
+ return copy;
+ }
+ return listeners;
+ }
+
+ private static Map<BundleContext, List<ListenerInfo>> removeListenerInfo(
+ Map<BundleContext, List<ListenerInfo>> listeners, BundleContext bc, int idx)
+ {
+ // Make a copy of the map, since we will be mutating it.
+ Map<BundleContext, List<ListenerInfo>> copy =
+ new HashMap<BundleContext, List<ListenerInfo>>(listeners);
+ // Remove the affected entry and make a copy so we can modify it.
+ List<ListenerInfo> infos = copy.remove(bc);
+ if (infos != null)
+ {
+ infos = new ArrayList<ListenerInfo>(infos);
+ // Remove the listener info.
+ infos.remove(idx);
+ if (!infos.isEmpty())
+ {
+ // Put the listeners back into the copy of the map and return it.
+ copy.put(bc, infos);
+ }
+ return copy;
+ }
+ return listeners;
+ }
+
+ private static Map<BundleContext, List<ListenerInfo>> removeListenerInfos(
+ Map<BundleContext, List<ListenerInfo>> listeners, BundleContext bc)
+ {
+ // Make a copy of the map, since we will be mutating it.
+ Map<BundleContext, List<ListenerInfo>> copy =
+ new HashMap<BundleContext, List<ListenerInfo>>(listeners);
+ // Remove the affected entry and return the copy.
+ copy.remove(bc);
+ return copy;
+ }
+
+ /**
+ * This is the dispatching thread's main loop.
+ *
+ */
+ private static void run()
+ {
+ Request req = null;
+ while (true)
+ {
+ // Lock the request list so we can try to get a
+ // dispatch request from it.
+ synchronized (m_requestList)
+ {
+ // Wait while there are no requests to dispatch. If the
+ // dispatcher thread is supposed to stop, then let the
+ // dispatcher thread exit the loop and stop.
+ while (m_requestList.isEmpty() && !m_stopping)
+ {
+ // Wait until some signals us for work.
+ try
+ {
+ m_requestList.wait();
+ }
+ catch (InterruptedException ex)
+ {
+ // Not much we can do here except for keep waiting.
+ }
+ }
+
+ // If there are no events to dispatch and shutdown
+ // has been called then exit, otherwise dispatch event.
+ if (m_requestList.isEmpty() && m_stopping)
+ {
+ return;
+ }
+
+ // Get the dispatch request.
+ req = m_requestList.remove(0);
+ }
+
+ // Deliver event outside of synchronized block
+ // so that we don't block other requests from being
+ // queued during event processing.
+ // NOTE: We don't catch any exceptions here, because
+ // the invoked method shields us from exceptions by
+ // catching Throwables when it invokes callbacks.
+ fireEventImmediately(
+ req.m_dispatcher, req.m_type, req.m_listeners,
+ req.m_event, null);
+
+ // Put dispatch request in cache.
+ synchronized (m_requestPool)
+ {
+ req.m_dispatcher = null;
+ req.m_type = -1;
+ req.m_listeners = null;
+ req.m_event = null;
+ m_requestPool.add(req);
+ }
+ }
+ }
+
+ private static class Request
+ {
+ public static final int FRAMEWORK_EVENT = 0;
+ public static final int BUNDLE_EVENT = 1;
+ public static final int SERVICE_EVENT = 2;
+ public EventDispatcher m_dispatcher = null;
+ public int m_type = -1;
+ public Map<BundleContext, List<ListenerInfo>> m_listeners = null;
+ public EventObject m_event = null;
+ }
+}
Added: felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/ListenerHookInfoImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/ListenerHookInfoImpl.java?rev=1583367&view=auto
==============================================================================
--- felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/ListenerHookInfoImpl.java (added)
+++ felix/trunk/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/ListenerHookInfoImpl.java Mon Mar 31 16:18:17 2014
@@ -0,0 +1,85 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.util;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.hooks.service.ListenerHook;
+
+public class ListenerHookInfoImpl implements ListenerHook.ListenerInfo
+{
+ private final BundleContext m_context;
+ private final ServiceListener m_listener;
+ private final String m_filter;
+ private boolean m_removed;
+
+ public ListenerHookInfoImpl(BundleContext context,
+ ServiceListener listener, String filter, boolean removed)
+ {
+ m_context = context;
+ m_listener = listener;
+ m_filter = filter;
+ m_removed = removed;
+ }
+
+ public BundleContext getBundleContext()
+ {
+ return m_context;
+ }
+
+ public String getFilter()
+ {
+ return m_filter;
+ }
+
+ public boolean isRemoved()
+ {
+ return m_removed;
+ }
+
+ public boolean equals(Object obj)
+ {
+ if (obj == this)
+ {
+ return true;
+ }
+
+ if (!(obj instanceof ListenerHookInfoImpl))
+ {
+ return false;
+ }
+
+ ListenerHookInfoImpl other = (ListenerHookInfoImpl) obj;
+ return other.m_listener == m_listener
+ && (m_filter == null ? other.m_filter == null : m_filter
+ .equals(other.m_filter));
+ }
+
+ public int hashCode()
+ {
+ int rc = 17;
+
+ rc = 37 * rc + m_listener.hashCode();
+ if (m_filter != null)
+ {
+ rc = 37 * rc + m_filter.hashCode();
+ }
+ return rc;
+ }
+}
\ No newline at end of file