You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:25:19 UTC

[sling-org-apache-sling-commons-osgi] 05/11: SLING-4521 Commons OSGi: Add helper class for thread-safe access list of ranked service dependencies

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.commons.osgi-2.3.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-commons-osgi.git

commit 609eaf5fbfe7c96b02c73da4dbbcae8282623919
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Thu Mar 19 11:31:57 2015 +0000

    SLING-4521 Commons OSGi: Add helper class for thread-safe access list of ranked service dependencies
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/commons/osgi@1667709 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  13 ++
 .../apache/sling/commons/osgi/RankedServices.java  | 151 +++++++++++++++++++++
 .../apache/sling/commons/osgi/package-info.java    |   2 +-
 .../sling/commons/osgi/RankedServicesTest.java     |  81 +++++++++++
 4 files changed, 246 insertions(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 8f846e0..8d1035f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -104,6 +104,7 @@
             <groupId>org.jmock</groupId>
             <artifactId>jmock-junit4</artifactId>
         </dependency>
+
         <dependency>
             <!-- Not used by our code, but need a non-bundle jar for unit tests -->
             <groupId>javax.jcr</groupId>
@@ -118,6 +119,18 @@
             <version>2.0.6</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.9.5</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>15.0</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/src/main/java/org/apache/sling/commons/osgi/RankedServices.java b/src/main/java/org/apache/sling/commons/osgi/RankedServices.java
new file mode 100644
index 0000000..ee45f2e
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/osgi/RankedServices.java
@@ -0,0 +1,151 @@
+/*
+ * 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.sling.commons.osgi;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import aQute.bnd.annotation.ConsumerType;
+import aQute.bnd.annotation.ProviderType;
+
+/**
+ * Helper class that collects all services registered via OSGi bind/unbind methods.
+ * The services are ordered by service ranking and can be iterated directly using this object instance.
+ * Implementation is thread-safe.
+ * <p>Usage example:</p>
+ * <p>1. Define a dynamic reference with cardinality OPTIONAL_MULTIPLE in your service:
+ * <pre>
+ * &#64;Reference(name = "myService", referenceInterface = MyService.class,
+ *     cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, policy = ReferencePolicy.DYNAMIC)
+ * private final RankedServices&lt;MyService&gt; myServices = new RankedServices&lt;MyService&gt;();
+ * </pre>
+ * <p>2. Define bind/unbind methods that delegate to the RankedServices instance:</p>
+ * <pre>
+ * void bindMyService(MyService service, Map<String, Object> props) {
+ *   myServices.bind(service, props);
+ * }
+ * void unbindMyService(MyService service, Map<String, Object> props) {
+ *   myServices.unbind(service, props);
+ * }
+ * </pre>
+ * <p>To access the list of referenced services you can access them in a thread-safe manner:</p>
+ * <pre>
+ * for (MyService service : myServices) {
+ *   // your code...
+ * }
+ * </pre>
+ * <p>Optionally you can pass in a {@link ChangeListener} instance to get notified when the list
+ * of referenced services has chagned.</p>
+ * @param <T> Service type
+ * @since 2.3
+ */
+@ProviderType
+public final class RankedServices<T> implements Iterable<T> {
+
+  private final ChangeListener changeListener;
+  private final SortedMap<Comparable<Object>, T> serviceMap = new TreeMap<Comparable<Object>, T>();
+  private volatile Collection<T> sortedServices = Collections.emptyList();
+
+  /**
+   * Instantiate without change listener.
+   */
+  public RankedServices() {
+    this(null);
+  }
+
+  /**
+   * Instantiate without change listener.
+   * @param changeListener Change listener
+   */
+  public RankedServices(ChangeListener changeListener) {
+    this.changeListener = changeListener;
+  }
+
+  /**
+   * Handle bind service event.
+   * @param service Service instance
+   * @param props Service reference properties
+   */
+  public void bind(T service, Map<String, Object> props) {
+    synchronized (serviceMap) {
+      serviceMap.put(ServiceUtil.getComparableForServiceRanking(props), service);
+      updateSortedServices();
+    }
+  }
+
+  /**
+   * Handle unbind service event.
+   * @param service Service instance
+   * @param props Service reference properties
+   */
+  public void unbind(T service, Map<String, Object> props) {
+    synchronized (serviceMap) {
+      serviceMap.remove(ServiceUtil.getComparableForServiceRanking(props));
+      updateSortedServices();
+    }
+  }
+
+  /**
+   * Update list of sorted services by copying it from the array and making it unmodifiable.
+   */
+  private void updateSortedServices() {
+    List<T> copiedList = new ArrayList<T>(serviceMap.values());
+    sortedServices = Collections.unmodifiableList(copiedList);
+    if (changeListener != null) {
+      changeListener.changed();
+    }
+  }
+
+  /**
+   * Lists all services registered in OSGi, sorted by service ranking.
+   * @return Collection of service instances
+   */
+  public Collection<T> get() {
+    return sortedServices;
+  }
+
+  /**
+   * Iterates all services registered in OSGi, sorted by service ranking.
+   * @return Iterator with service instances.
+   */
+  public Iterator<T> iterator() {
+    return sortedServices.iterator();
+  }
+
+  /**
+   * Notification for changes on services list.
+   */
+  @ConsumerType
+  public interface ChangeListener {
+
+    /**
+     * Is called when the list of ranked services was changed due to bundle bindings/unbindings.
+     * This method is called within a synchronized block, so it's code should be kept as efficient as possible.
+     */
+    void changed();
+
+  }
+
+}
diff --git a/src/main/java/org/apache/sling/commons/osgi/package-info.java b/src/main/java/org/apache/sling/commons/osgi/package-info.java
index ccd9e91..243300d 100644
--- a/src/main/java/org/apache/sling/commons/osgi/package-info.java
+++ b/src/main/java/org/apache/sling/commons/osgi/package-info.java
@@ -16,5 +16,5 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-@aQute.bnd.annotation.Version("2.2")
+@aQute.bnd.annotation.Version("2.3")
 package org.apache.sling.commons.osgi;
diff --git a/src/test/java/org/apache/sling/commons/osgi/RankedServicesTest.java b/src/test/java/org/apache/sling/commons/osgi/RankedServicesTest.java
new file mode 100644
index 0000000..867e165
--- /dev/null
+++ b/src/test/java/org/apache/sling/commons/osgi/RankedServicesTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.sling.commons.osgi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.util.Map;
+
+import org.apache.sling.commons.osgi.RankedServices.ChangeListener;
+import org.junit.Test;
+import org.osgi.framework.Constants;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterators;
+
+public class RankedServicesTest {
+
+  private static final String SERVICE_1 = "service1";
+  private static final Map<String, Object> SERVICE_1_PROPS = ImmutableMap.<String, Object>builder()
+      .put(Constants.SERVICE_RANKING, 50).put(Constants.SERVICE_ID, 1L).build();
+  private static final String SERVICE_2 = "service2";
+  private static final Map<String, Object> SERVICE_2_PROPS = ImmutableMap.<String, Object>builder()
+      .put(Constants.SERVICE_RANKING, 10).put(Constants.SERVICE_ID, 2L).build();
+  private static final String SERVICE_3 = "service3";
+  private static final Map<String, Object> SERVICE_3_PROPS = ImmutableMap.<String, Object>builder()
+      .put(Constants.SERVICE_RANKING, 100).put(Constants.SERVICE_ID, 3L).build();
+
+  @Test
+  public void testSortedServices() {
+    RankedServices<Comparable> underTest = new RankedServices<Comparable>();
+    assertEquals(0, underTest.get().size());
+
+    underTest.bind(SERVICE_1, SERVICE_1_PROPS);
+    assertEquals(1, underTest.get().size());
+    Comparable[] services = Iterators.toArray(underTest.get().iterator(), Comparable.class);
+    assertSame(SERVICE_1, services[0]);
+
+    underTest.bind(SERVICE_2, SERVICE_2_PROPS);
+    underTest.bind(SERVICE_3, SERVICE_3_PROPS);
+    assertEquals(3, underTest.get().size());
+    services = Iterators.toArray(underTest.get().iterator(), Comparable.class);
+    assertSame(SERVICE_2, services[0]);
+    assertSame(SERVICE_1, services[1]);
+    assertSame(SERVICE_3, services[2]);
+
+    underTest.unbind(SERVICE_2, SERVICE_2_PROPS);
+    assertEquals(2, underTest.get().size());
+    services = Iterators.toArray(underTest.get().iterator(), Comparable.class);
+    assertSame(SERVICE_1, services[0]);
+    assertSame(SERVICE_3, services[1]);
+  }
+
+  @Test
+  public void testChangeListener() {
+    ChangeListener changeListener = mock(ChangeListener.class);
+
+    RankedServices<Comparable> underTest = new RankedServices<Comparable>(changeListener);
+    underTest.bind(SERVICE_1, SERVICE_1_PROPS);
+    verify(changeListener).changed();
+  }
+
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.