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 <[email protected]> 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> + * @Reference(name = "myService", referenceInterface = MyService.class, + * cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, policy = ReferencePolicy.DYNAMIC) + * private final RankedServices<MyService> myServices = new RankedServices<MyService>(); + * </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. */ [email protected]("2.2") [email protected]("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 "[email protected]" <[email protected]>.
