blob: a828713034a789d1dd40d4cc7d064f66f7d12086 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.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 org.osgi.annotation.versioning.ConsumerType;
import org.osgi.annotation.versioning.ProviderType;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
/**
* 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.<br><br>
* <i>With Declarative Services 1.3 supporting field injection with multiple cardinality (leveraging Collections),
* this class should only be used if DS 1.3 cannot be used for some reason.
* DS 1.3 is using the same ordering as {@link ServiceReference#compareTo(Object)}.</i>
* <br><br>
* <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;(Order.DESCENDING);
* </pre>
* <p>2. Define bind/unbind methods that delegate to the RankedServices instance:</p>
* <pre>
* void bindMyService(MyService service, Map&lt;String, Object&gt; props) {
* myServices.bind(service, props);
* }
* void unbindMyService(MyService service, Map&lt;String, Object&gt; 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 changed.</p>
* @param <T> Service type
* @since 2.3
* @see "OSGi Compendium 6.0, Declarative Services 1.3, Reference Field Option, ยง112.3.8.1"
*/
@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 List<T> sortedServices = Collections.emptyList();
private final Order order;
/**
* Instantiate without change listener in ascending order (lowest service ranking first).
* @deprecated Use {@link #RankedServices(Order)} to explicitly give the order.
*/
@Deprecated
public RankedServices() {
this(Order.ASCENDING, null);
}
/**
* Instantiate with change listener in ascending order (lowest service ranking first).
* @param changeListener Change listener
* @deprecated Use {@link #RankedServices(Order order, ChangeListener changeListener)} instead
*/
@Deprecated
public RankedServices(ChangeListener changeListener) {
this(Order.ASCENDING, changeListener);
}
/**
* Instantiate without change listener but with a given order.
* @param order the order in which the services should be returned in {@link #iterator()} and {@link #get()}.
* Either {@link Order#ASCENDING} or {@link Order#DESCENDING}.
* Use {@link Order#DESCENDING} if you want to have the service with the highest ranking returned first
* (this is the service which would also be chosen by {@link BundleContext#getServiceReference(String)}).
* @since 2.4
*/
public RankedServices(Order order) {
this(order, null);
}
/**
* Instantiate with change listener.
* @param order the order in which the services should be returned in {@link #iterator()} and {@link #get()}.
* Either {@link Order#ASCENDING} or {@link Order#DESCENDING}.
* Use {@link Order#DESCENDING} if you want to have the service with the highest ranking returned first
* (this is the service which would also be chosen by {@link BundleContext#getServiceReference(String)}).
* @param changeListener Change listener
* @since 2.4
*/
public RankedServices(Order order, ChangeListener changeListener) {
this.order = order;
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, order), 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, order));
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
* (either ascending or descending depending on the order given in the constructor).
* @return Collection of service instances
* @deprecated Use {@link #getList()} instead
*/
public Collection<T> get() {
return sortedServices;
}
/**
* Lists all services registered in OSGi, sorted by service ranking
* (either ascending or descending depending on the order given in the constructor).
* @return List of service instances
*/
public List<T> getList() {
return sortedServices;
}
/**
* Iterates all services registered in OSGi, sorted by service ranking
* (either ascending or descending depending on the order given in the constructor).
* @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();
}
}