| /******************************************************************************* |
| * Copyright (c) 2010-2014 SAP AG and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * SAP AG - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.skalli.services; |
| |
| import java.net.URL; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.Comparator; |
| import java.util.Enumeration; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.NoSuchElementException; |
| import java.util.Set; |
| import java.util.SortedSet; |
| import java.util.TreeSet; |
| |
| import org.eclipse.skalli.commons.URLUtils; |
| import org.eclipse.skalli.services.extension.ExtensionService; |
| import org.eclipse.skalli.services.extension.ExtensionServices; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.FrameworkUtil; |
| import org.osgi.framework.InvalidSyntaxException; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.framework.Version; |
| |
| /** |
| * Utility class that provides various methods to retrieve, |
| * filter and iterate OSGi service instances, retrieve bundles |
| * and find resources in bundles. |
| * <p> |
| * Note: For retrieving {@link ExtensionService extension services} use |
| * the helper methods in {@link ExtensionServices}, which provide |
| * caching and a direct mapping from extension classes to the |
| * corresponding extension services. |
| */ |
| public class Services { |
| |
| /** |
| * Regular expression to search for Skalli bundles, see {@link #getBundlesMatching(String)}. |
| */ |
| public static final String SKALLI_BUNDLE_PATTERN = "org\\.eclipse\\.skalli\\..*"; //$NON-NLS-1$ |
| |
| /** |
| * The API version of Skalli, which is by definition the bundle version of the |
| * <tt>org.eclipse.skalli.api</tt> bundle<tt>. |
| */ |
| public static final Version API_VERSION = getBundleVersion(Services.class); |
| |
| // no instances, please! |
| private Services() { |
| } |
| |
| /** |
| * Returns the currently registered instance of the given OSGi |
| * <code>service</code> interface, if any. |
| * |
| * @param <T> |
| * type parameter representing an OSGi service interface. |
| * @param service |
| * the OSGi service interface for which an instance is to be |
| * returned. |
| * @return the registered instance of <code>service</code>, or |
| * <code>null</code>. |
| * @throws IllegalStateException |
| * if there is more than one instance of the service registered. |
| */ |
| public static <T> T getService(Class<T> service) { |
| return getService(service, false, null); |
| } |
| |
| /** |
| * Returns the currently registered instance of the given OSGi |
| * <code>service</code> interface, if any. |
| * |
| * @param <T> |
| * type parameter representing an OSGi service interface. |
| * @param service |
| * the OSGi service interface for which an instance is to be |
| * returned. |
| * @param filter |
| * the OSGi {@link org.osgi.framework.Filter filter} to use when |
| * looking up the service. It must contain the <code>objectClass</code> as well. |
| * @return the registered instance of <code>service</code>, or |
| * <code>null</code>. |
| * @throws IllegalStateException |
| * if there is more than one instance of the service registered. |
| */ |
| public static <T> T getService(Class<T> service, String filter) { |
| return getService(service, false, filter); |
| } |
| |
| /** |
| * Returns the currently registered instance of the given OSGi |
| * <code>service</code> interface. |
| * |
| * @param <T> |
| * type parameter representing an OSGi service interface. |
| * @param service |
| * the OSGi service interface for which an instance is to be |
| * returned. |
| * @return the registered instance of <code>service</code>. |
| * @throws IllegalStateException |
| * if there is none or more than one instance of the service |
| * registered. |
| */ |
| public static <T> T getRequiredService(Class<T> service) { |
| return getService(service, true, null); |
| } |
| |
| /** |
| * Returns all instances of a given OSGi <code>service</code>. |
| * |
| * @param <T> |
| * type parameter representing an OSGi service interface. |
| * @param service |
| * the OSGi service interface for which instances are to be returned. |
| * @return a set of service instances. |
| */ |
| public static <T> Set<T> getServices(Class<T> service) { |
| return getServices(service, null, null); |
| } |
| |
| /** |
| * Returns all instances of a given OSGi <code>service</code>. |
| * |
| * @param <T> |
| * type parameter representing an OSGi service interface. |
| * @param service |
| * the OSGi service interface for which instances are to be returned. |
| * @param comparator the comparator that will be used to order this set. |
| * If {@code null}, the {@linkplain Comparable natural |
| * ordering} of the elements will be used. |
| * @return a set of service instances. |
| */ |
| public static <T> Set<T> getServices(Class<T> service, Comparator<? super T> comparator) { |
| return getServices(service, null, comparator); |
| } |
| |
| /** |
| * Returns instances of a given OSGi <code>service</code> matching |
| * a certain filter condition. |
| * |
| * @param <T> |
| * type parameter representing an OSGi service interface. |
| * @param service |
| * the OSGi service interface for which instances are to be returned. |
| * @param filter |
| * a filter that determines which instances of the service to |
| * accept, or <code>null</code>. In no filter is specified, all |
| * instances of the service are returned. |
| * @return a set of matching service instances. |
| */ |
| public static <T> Set<T> getServices(Class<T> service, ServiceFilter<T> filter) { |
| return getServices(service, filter, null); |
| } |
| |
| /** |
| * Returns instances of a given OSGi <code>service</code> matching |
| * a certain filter condition. |
| * |
| * @param <T> |
| * type parameter representing an OSGi service interface. |
| * @param service |
| * the OSGi service interface for which instances are to be returned. |
| * @param filter |
| * a filter that determines which instances of the service to |
| * accept, or <code>null</code>. In no filter is specified, all |
| * instances of the service are returned. |
| * @param comparator the comparator that will be used to order this set. |
| * If {@code null}, the {@linkplain Comparable natural |
| * ordering} of the elements will be used. |
| * @return a set of matching service instances. |
| */ |
| public static <T> Set<T> getServices(Class<T> service, ServiceFilter<T> filter, |
| Comparator<? super T> comparator) { |
| if (comparator == null) { |
| comparator = new Comparator<T>() { |
| @Override |
| public int compare(T o1, T o2) { |
| return o1.getClass().getName().compareTo(o2.getClass().getName()); |
| } |
| }; |
| } |
| BundleContext context = getBundleContext(); |
| Set<T> serviceInstances = new TreeSet<T>(comparator); |
| List<ServiceReference<?>> serviceRefs = getServiceReferences(service); |
| if (serviceRefs != null) { |
| for (ServiceReference<?> serviceRef : serviceRefs) { |
| T serviceInstance = service.cast(context.getService(serviceRef)); |
| if (serviceInstance != null && (filter == null || filter.accept(serviceInstance))) { |
| serviceInstances.add(serviceInstance); |
| } |
| } |
| } |
| return serviceInstances; |
| } |
| |
| /** |
| * Returns references to all implementations of a given OSGi <code>service</code>. |
| * |
| * @param <T> |
| * type parameter representing an OSGi service interface. |
| * @param service |
| * the OSGi service interface for which instances are to be returned. |
| * @return a list of service references, or an empty list. |
| */ |
| public static <T> List<ServiceReference<?>> getServiceReferences(Class<T> service) { |
| return getServiceReferences(service, null); |
| } |
| |
| /** |
| * Returns references to all implementations of a given OSGi <code>service</code> matching |
| * the given filter expression; |
| * |
| * @param <T> |
| * type parameter representing an OSGi service interface. |
| * @param service |
| * the OSGi service interface for which instances are to be returned. |
| * @param filter |
| * the OSGi {@link org.osgi.framework.Filter filter} to use when |
| * looking up the service. It must contain the <code>objectClass</code> as well. |
| * @return a list of service references, or an empty list. |
| */ |
| public static <T> List<ServiceReference<?>> getServiceReferences(Class<T> service, String filter) { |
| List<ServiceReference<?>> ret = new ArrayList<ServiceReference<?>>(); |
| try { |
| BundleContext context = getBundleContext(); |
| ServiceReference<?>[] serviceRefs = context.getAllServiceReferences(service.getName(), filter); |
| if (serviceRefs != null) { |
| for (ServiceReference<?> serviceRef: serviceRefs) { |
| ret.add(serviceRef); |
| } |
| } |
| } catch (InvalidSyntaxException e) { |
| throw new IllegalArgumentException(e); |
| } |
| return ret; |
| } |
| |
| /** |
| * Returns an iterator for all implementations of the given OSGi <code>service</code>. |
| * |
| * @param <T> type parameter representing an OSGi service interface. |
| * @param service the OSGi service interface for which implementations are |
| * to be returned. |
| */ |
| public static <T> Iterator<T> getServiceIterator(Class<T> service) { |
| BundleContext context = getBundleContext(); |
| try { |
| ServiceReference<?>[] serviceRefs = context.getAllServiceReferences(service.getName(), null); |
| if (serviceRefs != null) { |
| return new ServiceIterator<T>(service, serviceRefs, context); |
| } |
| } catch (InvalidSyntaxException e) { |
| throw new IllegalArgumentException(e); |
| } |
| return new ServiceIterator<T>(service, null, context); |
| } |
| |
| /** |
| * Returns all registered bundles. |
| * |
| * @return a set of bundles, or an empty set. The bundles in the result |
| * are sorted according to their symbolic names. |
| */ |
| public static SortedSet<Bundle> getBundles() { |
| SortedSet<Bundle> bundles = createSortedBundleSet(); |
| for (Bundle bundle: getBundleContext().getBundles()) { |
| bundles.add(bundle); |
| } |
| return bundles; |
| } |
| |
| /** |
| * Returns all registered bundles matching given filters. |
| * |
| * @param mode determines whether the method should collect all bundles |
| * matching any of the given filters ({@link FilterMode#ALL}), or whether it should stop |
| * when the first filter found matching bundles ({@link FilterMode#SHORT_CIRCUIT}) |
| * or when any matching bundle is found ({@link FilterMode#FIRST_MATCHING}). |
| * @param filters a collection of bundle filters. If no filters are specified |
| * {@link BundleFilter#AcceptAll() all available bundles} are returned. |
| */ |
| public static SortedSet<Bundle> getBundles(FilterMode mode, BundleFilter... filters) { |
| SortedSet<Bundle> allBundles = getBundles(); |
| if (filters == null || filters.length == 0) { |
| return allBundles; |
| } |
| SortedSet<Bundle> bundles = createSortedBundleSet(); |
| for (BundleFilter filter: filters) { |
| if (FilterMode.SHORT_CIRCUIT.equals(mode) && bundles.size() > 0) { |
| return bundles; |
| } |
| for (Bundle bundle: allBundles) { |
| if (filter.accept(bundle)) { |
| bundles.add(bundle); |
| if (FilterMode.FIRST_MATCHING.equals(mode)) { |
| return bundles; |
| } |
| } |
| } |
| } |
| return bundles; |
| } |
| |
| /** |
| * Returns the version of the bundle to which the given class belongs. |
| * <p> |
| * Note: In order to retrieve the API version of Skalli use |
| * the constant {@link #API_VERSION}. |
| * |
| * @param clazz the class for which to resolve the bundle version. |
| * @return the version of a bundle, or {@link Version#emptyVersion}. |
| */ |
| public static Version getBundleVersion(Class<?> clazz) { |
| Bundle bundle = FrameworkUtil.getBundle(clazz); |
| return bundle != null? bundle.getVersion() : Version.emptyVersion; |
| } |
| |
| /** |
| * Scans bundles for resources matching the given <code>path</code> |
| * and <code>pattern</code>. Searches bundles based on a given list |
| * of bundle filters and in the order defined by their symbolic names. |
| * <p> |
| * In order to search specifically in extension bundles, i.e. bundles providing an |
| * {@link org.eclipse.skalli.services.extension.ExtensionService}), you may use the convenience method |
| * {@link org.eclipse.skalli.services.extension.ExtensionServices#findExtensionResources(String,String,boolean)}. |
| * |
| * @param path The path name in which to look. The path is always relative |
| * to the root of a bundle and may begin with "/". A |
| * path value of "/" indicates the root of a bundle. |
| * @param filePattern The file name pattern for selecting entries in the |
| * specified path. The pattern is only matched against the last |
| * element of the entry path. If the entry is a directory then the |
| * trailing "/" is not used for pattern matching. Substring |
| * matching is supported, as specified in the Filter specification, |
| * using the wildcard character ("*"). If null is |
| * specified, this is equivalent to "*" and matches all |
| * files. |
| * @param recurse If {@code true}, recurse into subdirectories. Otherwise |
| * only return entries from the specified path. |
| * @param mode determines whether the method should collect all resources from |
| * all accepted bundles ({@link FilterMode#ALL}), |
| * or whether it should stop when |
| * the first filter yielded a non-empty amount of matching |
| * resources ({@link FilterMode#SHORT_CIRCUIT}), |
| * or when the first matching resource is found |
| * ({@link FilterMode#FIRST_MATCHING}). |
| * @param filters a collection of filters determining the bundles to search |
| * for matching resources. If no filters are specified |
| * {@link BundleFilter#AcceptAll() all available bundles} are searched. |
| * @return a list of matching resources, or an empty list. |
| * |
| * @see Bundle#findEntries(String, String, boolean) |
| */ |
| public static List<URL> findResources(String path, String pattern, boolean recursive, FilterMode mode, |
| BundleFilter... filters) { |
| if (filters == null || filters.length == 0) { |
| filters = new BundleFilter[]{ new BundleFilter.AcceptAll() }; |
| } |
| List<URL> resources = new ArrayList<URL>(); |
| SortedSet<Bundle> bundles = getBundles(); |
| for (BundleFilter filter: filters) { |
| if (FilterMode.SHORT_CIRCUIT.equals(mode) && resources.size() > 0) { |
| return resources; |
| } |
| for (Bundle bundle: bundles) { |
| if (filter.accept(bundle)) { |
| Enumeration<URL> urls = bundle.findEntries(path, pattern, recursive); |
| if (urls != null) { |
| resources.addAll(URLUtils.asURLs(urls)); |
| if (FilterMode.FIRST_MATCHING.equals(mode)) { |
| return resources; |
| } |
| } |
| } |
| } |
| } |
| return resources; |
| } |
| |
| private static final BundleContext getBundleContext() { |
| return FrameworkUtil.getBundle(Services.class).getBundleContext(); |
| } |
| |
| private static SortedSet<Bundle> createSortedBundleSet() { |
| return new TreeSet<Bundle>(new Comparator<Bundle>() { |
| @Override |
| public int compare(Bundle o1, Bundle o2) { |
| return o1.getSymbolicName().compareTo(o2.getSymbolicName()); |
| } |
| }); |
| } |
| |
| private static <T> T getService(Class<T> serviceClass, boolean required, String filter) { |
| T serviceInstance = null; |
| try { |
| BundleContext context = getBundleContext(); |
| ServiceReference<?>[] serviceRefs = context.getAllServiceReferences(serviceClass.getName(), filter); |
| if (serviceRefs != null) { |
| if (serviceRefs.length > 1) { |
| throw new IllegalStateException(MessageFormat.format( |
| "Multiple implementations for service {0} registered", |
| serviceClass.getName())); |
| } |
| serviceInstance = serviceClass.cast(context.getService(serviceRefs[0])); |
| } |
| } catch (InvalidSyntaxException e) { |
| throw new IllegalArgumentException(e); |
| } |
| if (required && serviceInstance == null) { |
| throw new IllegalStateException(MessageFormat.format( |
| "No implementation for required service {0} registered", |
| serviceClass.getName())); |
| } |
| return serviceInstance; |
| } |
| |
| private static class ServiceIterator<T> implements Iterator<T> { |
| private int i = 0; |
| private final Class<T> serviceClass; |
| private final BundleContext context; |
| private final ServiceReference<?>[] serviceRefs; |
| |
| public ServiceIterator(Class<T> serviceClass, ServiceReference<?>[] serviceRefs, BundleContext context) { |
| this.serviceClass = serviceClass; |
| this.context = context; |
| this.serviceRefs = serviceRefs; |
| } |
| |
| @Override |
| public boolean hasNext() { |
| return serviceRefs != null ? i < serviceRefs.length : false; |
| } |
| |
| @Override |
| public T next() { |
| if (!hasNext()) { |
| throw new NoSuchElementException("Iteration has no more elements"); |
| } |
| return serviceClass.cast(context.getService(serviceRefs[i++])); |
| } |
| |
| @Override |
| public void remove() { |
| throw new UnsupportedOperationException("Remove operation is not supported by this Iterator"); |
| } |
| } |
| |
| } |