You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by to...@apache.org on 2017/06/08 07:10:19 UTC

svn commit: r1798025 - in /jackrabbit/oak/trunk/oak-core-spi/src: main/java/org/apache/jackrabbit/oak/osgi/ main/java/org/apache/jackrabbit/oak/spi/whiteboard/ test/java/org/apache/jackrabbit/oak/osgi/ test/java/org/apache/jackrabbit/oak/spi/whiteboard/

Author: tomekr
Date: Thu Jun  8 07:10:18 2017
New Revision: 1798025

URL: http://svn.apache.org/viewvc?rev=1798025&view=rev
Log:
OAK-6313: Support properties filtering in the Whiteboard track method

Added:
    jackrabbit/oak/trunk/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/whiteboard/DefaultWhiteboardTest.java
Modified:
    jackrabbit/oak/trunk/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/osgi/OsgiUtil.java
    jackrabbit/oak/trunk/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/osgi/OsgiWhiteboard.java
    jackrabbit/oak/trunk/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/DefaultWhiteboard.java
    jackrabbit/oak/trunk/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/Whiteboard.java
    jackrabbit/oak/trunk/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/osgi/OsgiUtilTest.java

Modified: jackrabbit/oak/trunk/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/osgi/OsgiUtil.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/osgi/OsgiUtil.java?rev=1798025&r1=1798024&r2=1798025&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/osgi/OsgiUtil.java (original)
+++ jackrabbit/oak/trunk/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/osgi/OsgiUtil.java Thu Jun  8 07:10:18 2017
@@ -18,8 +18,14 @@
 package org.apache.jackrabbit.oak.osgi;
 
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.service.component.ComponentContext;
 
+import java.util.Map;
+
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
@@ -153,4 +159,46 @@ public class OsgiUtil {
         return string;
     }
 
+    /**
+     * Create a {@link Filter} using the passed Class as an objectClass and the map
+     * as the filter attributes.
+     * @param clazz the target objectClass
+     * @param attributes target attributes (null value for the absence)
+     * @return OSGi filter representing the input
+     */
+    public static Filter getFilter(Class<?> clazz, Map<String, String> attributes) {
+        StringBuilder filterBuilder = new StringBuilder("(&");
+        appendLdapFilterAttribute(filterBuilder, Constants.OBJECTCLASS, clazz.getName());
+        for (Map.Entry<String, String> e : attributes.entrySet()) {
+            appendLdapFilterAttribute(filterBuilder, e.getKey(), e.getValue());
+        }
+        filterBuilder.append(')');
+        try {
+            return FrameworkUtil.createFilter(filterBuilder.toString());
+        } catch(InvalidSyntaxException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    static StringBuilder appendLdapFilterAttribute(StringBuilder filterBuilder, String key, String value) {
+        if (value == null) {
+            filterBuilder.append("(!(").append(key).append("=*))");
+        } else {
+            filterBuilder.append("(").append(key).append("=");
+            appendEscapedLdapValue(filterBuilder, value);
+            filterBuilder.append(")");
+        }
+        return filterBuilder;
+    }
+
+    static StringBuilder appendEscapedLdapValue(StringBuilder filterBuilder, String value) {
+        for (int i = 0; i < value.length(); i++) {
+            char c = value.charAt(i);
+            if ((c == '\\') || (c == '(') || (c == ')') || (c == '*')) {
+                filterBuilder.append('\\');
+            }
+            filterBuilder.append(c);
+        }
+        return filterBuilder;
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/osgi/OsgiWhiteboard.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/osgi/OsgiWhiteboard.java?rev=1798025&r1=1798024&r2=1798025&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/osgi/OsgiWhiteboard.java (original)
+++ jackrabbit/oak/trunk/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/osgi/OsgiWhiteboard.java Thu Jun  8 07:10:18 2017
@@ -22,7 +22,9 @@ import static com.google.common.collect.
 import static com.google.common.collect.Maps.newHashMap;
 import static com.google.common.collect.Maps.newTreeMap;
 import static java.util.Collections.emptyList;
+import static java.util.Collections.emptyMap;
 import static java.util.Collections.singletonList;
+import static org.apache.jackrabbit.oak.osgi.OsgiUtil.getFilter;
 
 import java.util.Collections;
 import java.util.Dictionary;
@@ -33,11 +35,13 @@ import java.util.SortedMap;
 import java.util.concurrent.atomic.AtomicReference;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 import org.apache.jackrabbit.oak.spi.whiteboard.Registration;
 import org.apache.jackrabbit.oak.spi.whiteboard.Tracker;
 import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.Filter;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.util.tracker.ServiceTracker;
@@ -101,6 +105,15 @@ public class OsgiWhiteboard implements W
      */
     @Override
     public <T> Tracker<T> track(final Class<T> type) {
+        return track(type, emptyMap());
+    }
+
+    @Override
+    public <T> Tracker<T> track(Class<T> type, Map<String, String> filterProperties) {
+        return track(type, getFilter(type, filterProperties));
+    }
+
+    private <T> Tracker<T> track(Class<T> type, Filter filter) {
         checkNotNull(type);
         final AtomicReference<List<T>> list =
                 new AtomicReference<List<T>>(Collections.<T>emptyList());
@@ -143,8 +156,8 @@ public class OsgiWhiteboard implements W
                         context.ungetService(reference);
                     }
                 };
-        final ServiceTracker tracker =
-                new ServiceTracker(context, type.getName(), customizer);
+
+        final ServiceTracker tracker = new ServiceTracker(context, filter, customizer);
         tracker.open();
         return new Tracker<T>() {
             @Override

Modified: jackrabbit/oak/trunk/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/DefaultWhiteboard.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/DefaultWhiteboard.java?rev=1798025&r1=1798024&r2=1798025&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/DefaultWhiteboard.java (original)
+++ jackrabbit/oak/trunk/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/DefaultWhiteboard.java Thu Jun  8 07:10:18 2017
@@ -16,9 +16,10 @@
  */
 package org.apache.jackrabbit.oak.spi.whiteboard;
 
+import javax.annotation.Nonnull;
+
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.collect.Lists.newArrayList;
 import static com.google.common.collect.Maps.newHashMap;
 import static com.google.common.collect.Sets.newIdentityHashSet;
 import static java.util.Collections.emptyList;
@@ -26,13 +27,14 @@ import static java.util.Collections.empt
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 public class DefaultWhiteboard implements Whiteboard {
 
-    private final Map<Class<?>, Set<Object>> registry = newHashMap();
+    private final Map<Class<?>, Set<Service>> registry = newHashMap();
 
-    private synchronized <T> void registered(Class<T> type, T service) {
-        Set<Object> services = registry.get(type);
+    private synchronized <T> void registered(Class<T> type, Service service) {
+        Set<Service> services = registry.get(type);
         if (services == null) {
             services = newIdentityHashSet();
             registry.put(type, services);
@@ -40,8 +42,8 @@ public class DefaultWhiteboard implement
         services.add(service);
     }
 
-    private synchronized <T> void unregistered(Class<T> type, T service) {
-        Set<Object> services = registry.get(type);
+    private synchronized <T> void unregistered(Class<T> type, Service service) {
+        Set<Service> services = registry.get(type);
         if (services != null) {
             services.remove(service);
         }
@@ -49,9 +51,26 @@ public class DefaultWhiteboard implement
 
     @SuppressWarnings("unchecked")
     private synchronized <T> List<T> lookup(Class<T> type) {
-        Set<Object> services = registry.get(type);
+        Set<Service> services = registry.get(type);
         if (services != null) {
-            return (List<T>) newArrayList(services);
+            return (List<T>) services
+                    .stream()
+                    .map(Service::getService)
+                    .collect(Collectors.toList());
+        } else {
+            return emptyList();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private synchronized <T> List<T> lookup(Class<T> type, Map<String, String> filterProperties) {
+        Set<Service> services = registry.get(type);
+        if (services != null) {
+            return (List<T>) services
+                    .stream()
+                    .filter(s -> s.matches(filterProperties))
+                    .map(Service::getService)
+                    .collect(Collectors.toList());
         } else {
             return emptyList();
         }
@@ -65,11 +84,14 @@ public class DefaultWhiteboard implement
         checkNotNull(type);
         checkNotNull(service);
         checkArgument(type.isInstance(service));
-        registered(type, service);
+
+        Service s = new Service(service, properties);
+
+        registered(type, s);
         return new Registration() {
             @Override
             public void unregister() {
-                unregistered(type, service);
+                unregistered(type, s);
             }
         };
     }
@@ -88,4 +110,53 @@ public class DefaultWhiteboard implement
         };
     }
 
+    @Override
+    public <T> Tracker<T> track(Class<T> type, Map<String, String> filterProperties) {
+
+        checkNotNull(type);
+        return new Tracker<T>() {
+            @Override
+            public List<T> getServices() {
+                return lookup(type, filterProperties);
+            }
+            @Override
+            public void stop() {
+            }
+        };
+    }
+
+    private static class Service {
+
+        private final Object service;
+
+        private final Map<?, ?> properties;
+
+        private Service(@Nonnull Object service, Map<?, ?> properties) {
+            checkNotNull(service);
+            this.service = service;
+            this.properties = properties;
+        }
+
+        private Object getService() {
+            return service;
+        }
+
+        private boolean matches(Map<String, String> properties) {
+            return properties.entrySet().stream()
+                    .allMatch(this::propertyMatches);
+        }
+
+        private  boolean propertyMatches(Map.Entry<String, String> filterEntry) {
+            String key = filterEntry.getKey();
+            String expectedValue = filterEntry.getValue();
+            if (properties == null || !properties.containsKey(key)) {
+                return expectedValue == null;
+            }
+            Object value = properties.get(key);
+            if (value == null) {
+                return expectedValue == null;
+            }
+            return value.toString().equals(expectedValue);
+        }
+    }
 }

Modified: jackrabbit/oak/trunk/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/Whiteboard.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/Whiteboard.java?rev=1798025&r1=1798024&r2=1798025&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/Whiteboard.java (original)
+++ jackrabbit/oak/trunk/oak-core-spi/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/Whiteboard.java Thu Jun  8 07:10:18 2017
@@ -39,4 +39,15 @@ public interface Whiteboard {
      */
     <T> Tracker<T> track(Class<T> type);
 
+    /**
+     * Starts tracking services of the given type, with given attributes.
+     *
+     * @param type type of the services to track
+     * @param filterProperties only services with these properties will be tracked.
+     *                         Null keys are not permitted. Null values means that
+     *                         the property should be absent.
+     * @return service tracker
+     */
+    <T> Tracker<T> track(Class<T> type, Map<String, String> filterProperties);
+
 }

Modified: jackrabbit/oak/trunk/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/osgi/OsgiUtilTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/osgi/OsgiUtilTest.java?rev=1798025&r1=1798024&r2=1798025&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/osgi/OsgiUtilTest.java (original)
+++ jackrabbit/oak/trunk/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/osgi/OsgiUtilTest.java Thu Jun  8 07:10:18 2017
@@ -17,12 +17,20 @@
 
 package org.apache.jackrabbit.oak.osgi;
 
+import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils;
 import org.junit.Test;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.service.component.ComponentContext;
 
 import java.util.Dictionary;
+import java.util.Map;
 
+import static com.google.common.collect.Maps.newLinkedHashMap;
+import static org.apache.jackrabbit.oak.osgi.OsgiUtil.appendEscapedLdapValue;
+import static org.apache.jackrabbit.oak.osgi.OsgiUtil.appendLdapFilterAttribute;
+import static org.apache.jackrabbit.oak.osgi.OsgiUtil.getFilter;
 import static org.apache.jackrabbit.oak.osgi.OsgiUtil.lookup;
 import static org.apache.jackrabbit.oak.osgi.OsgiUtil.lookupConfigurationThenFramework;
 import static org.apache.jackrabbit.oak.osgi.OsgiUtil.lookupFrameworkThenConfiguration;
@@ -204,4 +212,27 @@ public class OsgiUtilTest {
         assertEquals("fvalue", lookupFrameworkThenConfiguration(componentContext, "cname", "fname"));
     }
 
+    @Test
+    public void filterBuilding() throws InvalidSyntaxException {
+        StringBuilder b = new StringBuilder();
+
+        assertEquals("foo\\\\bar\\(foo\\)bar\\*foo", appendEscapedLdapValue(b, "foo\\bar(foo)bar*foo").toString());
+        b.setLength(0);
+
+        assertEquals("(foo=bar)", appendLdapFilterAttribute(b, "foo", "bar").toString());
+        b.setLength(0);
+
+        assertEquals("(foo=\\(bar\\))", appendLdapFilterAttribute(b, "foo", "(bar)").toString());
+        b.setLength(0);
+
+        assertEquals("(!(foo=*))", appendLdapFilterAttribute(b, "foo", null).toString());
+        b.setLength(0);
+
+        Map<String, String> m = newLinkedHashMap();
+        m.put("foo", "bar");
+        m.put("empty", null);
+        m.put("escaped", "*xyz)");
+        assertEquals(FrameworkUtil.createFilter("(&(objectClass=java.lang.String)(foo=bar)(!(empty=*))(escaped=\\*xyz\\)))"), getFilter(String.class, m));
+        b.setLength(0);
+    }
 }

Added: jackrabbit/oak/trunk/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/whiteboard/DefaultWhiteboardTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/whiteboard/DefaultWhiteboardTest.java?rev=1798025&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/whiteboard/DefaultWhiteboardTest.java (added)
+++ jackrabbit/oak/trunk/oak-core-spi/src/test/java/org/apache/jackrabbit/oak/spi/whiteboard/DefaultWhiteboardTest.java Thu Jun  8 07:10:18 2017
@@ -0,0 +1,148 @@
+/*
+ * 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.jackrabbit.oak.spi.whiteboard;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.collect.ImmutableSet.of;
+import static java.util.Collections.singletonMap;
+import static java.util.stream.Collectors.toSet;
+import static org.junit.Assert.assertEquals;
+
+public class DefaultWhiteboardTest {
+
+    private Whiteboard whiteboard;
+
+    @Before
+    public void createWhiteboard() {
+        whiteboard = new DefaultWhiteboard();
+    }
+
+    @Test
+    public void filteredTracker() {
+        whiteboard.register(Service1.class, new Service1("s1"), ImmutableMap.of());
+        whiteboard.register(Service2.class, new Service2("s2"), ImmutableMap.of("role", "myrole"));
+        whiteboard.register(Service3.class, new Service3("s3_1"), ImmutableMap.of());
+        whiteboard.register(Service3.class, new Service3("s3_2"), ImmutableMap.of("role", "myrole"));
+        whiteboard.register(Service3.class, new Service3("s3_3"), ImmutableMap.of("role", "myotherrole", "id", 1024));
+
+        assertEquals(of("s1"), track(Service1.class));
+        assertEquals(of("s1"), track(Service1.class, singletonMap("role", null)));
+        assertEquals(of(), track(Service1.class, ImmutableMap.of("role", "myrole")));
+
+        assertEquals(of("s2"), track(Service2.class));
+        assertEquals(of(), track(Service2.class, singletonMap("role", null)));
+        assertEquals(of("s2"), track(Service2.class, ImmutableMap.of("role", "myrole")));
+
+        assertEquals(of("s3_1", "s3_2", "s3_3"), track(Service3.class));
+        assertEquals(of("s3_1"), track(Service3.class, singletonMap("role", null)));
+        assertEquals(of("s3_2"), track(Service3.class, ImmutableMap.of("role", "myrole")));
+        assertEquals(of("s3_3"), track(Service3.class, ImmutableMap.of("role", "myotherrole")));
+        assertEquals(of("s3_3"), track(Service3.class, ImmutableMap.of("role", "myotherrole", "id", "1024")));
+        assertEquals(of("s3_3"), track(Service3.class, ImmutableMap.of("id", "1024")));
+        assertEquals(of(), track(Service3.class, ImmutableMap.of("id", "2048")));
+    }
+
+    @Test
+    public void sameServiceRegisteredAgain() {
+        Service1 s1 = new Service1("s1");
+
+        whiteboard.register(Service1.class, s1, ImmutableMap.of());
+        whiteboard.register(Service1.class, s1, ImmutableMap.of());
+        whiteboard.register(Service1.class, s1, ImmutableMap.of());
+
+        assertEquals(of("s1"), track(Service1.class));
+    }
+
+    @Test
+    public void unregister() {
+        Registration r1 = whiteboard.register(Service1.class, new Service1("s1"), ImmutableMap.of());
+        Registration r2 = whiteboard.register(Service2.class, new Service2("s2"), ImmutableMap.of("role", "myrole"));
+        Registration r3_1 = whiteboard.register(Service3.class, new Service3("s3_1"), ImmutableMap.of());
+        Registration r3_2 = whiteboard.register(Service3.class, new Service3("s3_2"), ImmutableMap.of("role", "myrole"));
+        Registration r3_3 = whiteboard.register(Service3.class, new Service3("s3_3"), ImmutableMap.of("role", "myotherrole", "id", 1024));
+
+        assertEquals(of("s1"), track(Service1.class));
+        r1.unregister();
+        assertEquals(of(), track(Service1.class));
+
+        assertEquals(of("s2"), track(Service2.class));
+        r2.unregister();
+        assertEquals(of(), track(Service2.class));
+
+        assertEquals(of("s3_1", "s3_2", "s3_3"), track(Service3.class));
+        r3_1.unregister();
+        assertEquals(of("s3_2", "s3_3"), track(Service3.class));
+        r3_2.unregister();
+        assertEquals(of("s3_3"), track(Service3.class));
+        r3_3.unregister();
+        assertEquals(of(), track(Service3.class));
+    }
+
+    private <T extends Service> Set<String> track(Class<T> clazz) {
+        return track(clazz, null);
+    }
+
+    private <T extends Service> Set<String> track(Class<T> clazz, Map<String, String> properties) {
+        final Tracker<T> tracker;
+        if (properties == null) {
+            tracker = whiteboard.track(clazz);
+        } else {
+            tracker = whiteboard.track(clazz, properties);
+        }
+        try {
+            return tracker.getServices().stream().map(Service::getId).collect(toSet());
+        } finally {
+            tracker.stop();
+        }
+    }
+
+    public abstract static class Service {
+        private final String id;
+
+        private Service(String id) {
+            this.id = id;
+        }
+
+        public String getId() {
+            return id;
+        }
+    }
+
+    private final static class Service1 extends Service {
+        private Service1(String id) {
+            super(id);
+        }
+    }
+
+    private final static class Service2 extends Service {
+        private Service2(String id) {
+            super(id);
+        }
+    }
+
+    private final static class Service3 extends Service {
+        private Service3(String id) {
+            super(id);
+        }
+    }
+}