/** * Copyright (c) Codice Foundation * <p> * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation, either version 3 of the * License, or any later version. * <p> * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. */ package ddf.catalog.util.impl; import java.util.Collections; import java.util.Comparator; import java.util.SortedSet; import java.util.concurrent.ConcurrentSkipListSet; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The ServiceSelector maintains a sorted set of bound OSGi services and provides user access to * those services. It covers 2 use-cases: point-to-point and pub-sub. * * In the point-to-point scenario a user needs to select a single service from the set of available * services. The appropriate service to select is determined by the ServiceSelectionStrategy * implementation that is provided to the ServiceSelector constructor. It can be retrieved by * calling the 'getService()' method. * * In the pub-sub scenario, the user needs to access all of the service implementations in the * internal set. An unmodifiable view of the internal set can be retrieved by a call to * 'getAllServices()'. * * In both scenarios, the set order is determined by the Comparator object supplied to the * ServiceSelector at creation. * * OSGi requires a type converter to be registered for this class. Typically, it will * also be used a reference-listener. * * Example: * <pre> * {@Code * * <type-converters> * <bean id="serviceSelectorConverter" class="ddf.catalog.util.impl.ServiceSelectorConverter"/> * </type-converters> * * <bean id="geoCoderFactory" class="ddf.catalog.util.impl.ServiceSelector"/> * * <reference-list id="geoCoderList" interface="org.codice.ddf.spatial.geocoder.GeoCoder" * availability="optional"> * <reference-listener bind-method="bindService" unbind-method="unbindService" * ref="geoCoderFactory"/> * </reference-list> * * } * </pre> * * @param <T> - The type of the service to be served up by this implementation of ServiceSelector * {@link ddf.catalog.util.impl.ServiceSelectionStrategy} * {@link ddf.catalog.util.impl.ServiceSelectorConverter} * {@link ddf.catalog.util.impl.ServiceComparator} */ public class ServiceSelector<T> { private ServiceSelectionStrategy<T> serviceSelectionStrategy; private SortedSet<ServiceReference<T>> serviceSet; private T service; private static final Logger LOGGER = LoggerFactory.getLogger(ServiceSelector.class); /** * * This default constructor is equivalent to calling: * new ServiceSelector(new ddf.catalog.util.impl.ServiceComparator(), * new FirstElementServiceSelectionStrategy()) * */ public ServiceSelector() { this(new ServiceComparator(), new FirstElementServiceSelectionStrategy()); } /** * * This constructor allows the user to set the comparator to be used by this * ServiceSelector which allows them to set the internal set order. It uses * a FirstElementServiceSelectionStrategy. * * @param serviceComparator - The comparator used to determine the internal set order. */ public ServiceSelector(Comparator serviceComparator) { this(serviceComparator, new FirstElementServiceSelectionStrategy<T>()); } public ServiceSelector(ServiceSelectionStrategy serviceSelectionStrategy) { this(new ServiceComparator(), serviceSelectionStrategy); } public ServiceSelector(Comparator serviceComparator, ServiceSelectionStrategy serviceSelectionStrategy) { if (serviceComparator == null) { throw new IllegalArgumentException( "ServiceSelector(): constructor argument 'serviceComparator' may not be null."); } if (serviceSelectionStrategy == null) { throw new IllegalArgumentException( "ServiceSelector(): constructor argument 'serviceSelectionStrategy' may not be null."); } this.serviceSet = new ConcurrentSkipListSet<ServiceReference<T>>(serviceComparator); this.serviceSelectionStrategy = serviceSelectionStrategy; } BundleContext getBundleContext() { Bundle cxfBundle = FrameworkUtil.getBundle(ServiceSelector.class); if (cxfBundle != null) { return cxfBundle.getBundleContext(); } return null; } public T getService() { return this.service; } public SortedSet<ServiceReference<T>> getAllServices() { return Collections.unmodifiableSortedSet(serviceSet); } public void bindService(ServiceReference serviceReference) { LOGGER.trace("Entering: bindService(ServiceReference)"); if (serviceReference != null) { serviceSet.add(serviceReference); resetService(); } LOGGER.trace("Exiting: bindService(ServiceReference)"); } public void unbindService(ServiceReference serviceReference) { LOGGER.trace("Entering: unbindService(ServiceReference)"); if (serviceReference != null) { serviceSet.remove(serviceReference); resetService(); } LOGGER.trace("Exiting: unbindService(ServiceReference)"); } private void resetService() { if (serviceSet.isEmpty()) { this.service = null; return; } SortedSet<ServiceReference<T>> unmodifiableServiceSet = Collections.unmodifiableSortedSet( this.serviceSet); ServiceReference<T> preferredServiceReference = serviceSelectionStrategy.selectService( unmodifiableServiceSet); this.service = null; if (preferredServiceReference != null) { //extract the preferred Service from the stored ServiceReferences; BundleContext bundleContext = this.getBundleContext(); if (bundleContext != null) { this.service = (T) bundleContext.getService(preferredServiceReference); } } } }