All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.core.runtime.ServiceCaller Maven / Gradle / Ivy

Go to download

AspectJ tools most notably contains the AspectJ compiler (AJC). AJC applies aspects to Java classes during compilation, fully replacing Javac for plain Java classes and also compiling native AspectJ or annotation-based @AspectJ syntax. Furthermore, AJC can weave aspects into existing class files in a post-compile binary weaving step. This library is a superset of AspectJ weaver and hence also of AspectJ runtime.

There is a newer version: 1.9.22.1
Show newest version
/*******************************************************************************
 * Copyright (c) 2020, 2023 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 * 
 * Contributors:
 *     Alex Blewitt - initial API and implementation
 *     Alexander Fedorov (ArSysOp) - documentation improvements
 *******************************************************************************/
package org.eclipse.core.runtime;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import org.osgi.framework.*;
import org.osgi.util.tracker.ServiceTracker;

/**
 * {@code ServiceCaller} provides functional methods for invoking OSGi services
 * in two different ways
 * 
    *
  • Single invocations which happen only once or very rarely. In this case, * maintaining a cache of the service is not worth the overhead.
  • *
  • Multiple invocations that happen often and rapidly. In this case, * maintaining a cache of the service is worth the overhead.
  • *
*

* For single invocations of a service the static method * {@link ServiceCaller#callOnce(Class, Class, Consumer)} can be used. This * method will wrap a call to the consumer of the service with the necessary * OSGi service registry calls to ensure the service exists and will do the * proper get and release service operations surround the calls to the service. * By wrapping a call around the service we can ensure that it is correctly * released after use. *

*

* Single invocation example: *

* *
 * ServiceCaller.callOnce(MyClass.class, ILog.class, (logger) -> logger.info("All systems go!"));
 * 
*

* Note that it is generally more efficient to use a long-running service * utility, such as {@link ServiceTracker} or declarative services, but there * are cases where a single one-shot lookup is preferable, especially if the * service is not required after use. Examples might include logging unlikely * conditions or processing debug options that are only read once. *

*

* This allows boilerplate code to be reduced at call sites, which would * otherwise have to do something like: *

* *
 * Bundle bundle = FrameworkUtil.getBundle(BadExample.class);
 * BundleContext context = bundle == null ? null : bundle.getBundleContext();
 * ServiceReference<Service> reference = context == null ? null : context.getServiceReference(serviceType);
 * try {
 * 	Service service = reference == null ? null : context.getService(reference);
 * 	if (service != null)
 * 		consumer.accept(service);
 * } finally {
 * 	context.ungetService(reference);
 * }
 * 
*

* For cases where a service is used much more often a {@code ServiceCaller} * instance can be used to cache and track the available service. This may be * useful for cases that cannot use declarative services and that want to avoid * using something like a {@link ServiceTracker} that does not easily allow for * lazy instantiation of the service instance. For example, if logging is used * more often then something like the following could be used: *

* *
 * static final ServiceCaller<ILog> log = new ServiceCaller(MyClass.class, ILog.class);
 * 
 * static void info(String msg) {
 * 	log.call(logger -> logger.info(msg));
 * }
 * 
*

* Note that this class is intended for simple service usage patterns only. More * advanced cases should use other mechanisms such as the {@link ServiceTracker} * or declarative services. *

* * @param the service type for this caller * @since 3.13 */ public class ServiceCaller { /** * Calls an OSGi service by dynamically looking it up and passing it to the * given consumer. *

* If not running under OSGi, the caller bundle is not active or the service is * not available, return false. If the service is found, call the service and * return true. *

*

* Any runtime exception thrown by the consumer is rethrown by this method. If * the consumer throws a checked exception, it can be propagated using a * sneakyThrow inside a try/catch block: *

* *
	 * callOnce(MyClass.class, Callable.class, (callable) -> {
	 *   try {
	 *     callable.call();
	 *   } catch (Exception e) {
	 *     sneakyThrow(e);
	 *   }
	 * });
	 * ...
	 * {@literal @}SuppressWarnings("unchecked")
	 * static <E extends Throwable> void sneakyThrow(Throwable e) throws E {
	 *   throw (E) e;
	 * }
	 * 
* * @param caller a class from the bundle that will use service * @param serviceType the OSGi service type to look up * @param consumer the consumer of the OSGi service * @param the OSGi service type to look up * @return true if the OSGi service was located and called successfully, false * otherwise * @throws NullPointerException if any of the parameters are {@code null} * @throws IllegalStateException if the bundle associated with the caller class * cannot be determined */ public static boolean callOnce(Class caller, Class serviceType, Consumer consumer) { return callOnce(caller, serviceType, null, consumer); } /** * As {@link #callOnce(Class, Class, Consumer)} with an additional OSGi filter. * * @param caller a class from the bundle that will use service * @param serviceType the OSGi service type to look up * @param consumer the consumer of the OSGi service * @param filter an OSGi filter to restrict the services found * @param the OSGi service type to look up * @return true if the OSGi service was located and called successfully, false * otherwise * @throws NullPointerException if any of the parameters are {@code null} * @throws IllegalStateException if the bundle associated with the caller class * cannot be determined */ public static boolean callOnce(Class caller, Class serviceType, String filter, Consumer consumer) { return new ServiceCaller<>(caller, serviceType, filter).getCurrent().map(r -> { try { consumer.accept(r.instance); return Boolean.TRUE; } finally { r.unget(); } }).orElse(Boolean.FALSE); } private static int getRank(ServiceReference ref) { Object rank = ref.getProperty(Constants.SERVICE_RANKING); if (rank instanceof Integer) { return ((Integer) rank).intValue(); } return 0; } private class ReferenceAndService implements SynchronousBundleListener, ServiceListener { final BundleContext context; final ServiceReference ref; final S instance; final int rank; public ReferenceAndService(final BundleContext context, ServiceReference ref, S instance) { this.context = context; this.ref = ref; this.instance = instance; this.rank = getRank(ref); } void unget() { untrack(); try { context.ungetService(ref); } catch (IllegalStateException e) { // ignore; just trying to cleanup but context is not valid now } } @Override public void bundleChanged(BundleEvent e) { if (bundle.equals(e.getBundle()) && e.getType() == BundleEvent.STOPPING) { unget(); } } @Override public void serviceChanged(ServiceEvent e) { if (requiresUnget(e)) { unget(); } } private boolean requiresUnget(ServiceEvent e) { if (e.getServiceReference().equals(ref)) { return (e.getType() == ServiceEvent.UNREGISTERING) || (filter != null && e.getType() == ServiceEvent.MODIFIED_ENDMATCH) || (e.getType() == ServiceEvent.MODIFIED && getRank(ref) != rank); // if rank changed: untrack to force a new ReferenceAndService with new rank } return e.getType() == ServiceEvent.MODIFIED && getRank(e.getServiceReference()) > rank; } // must hold monitor on ServiceCaller.this when calling track Optional track() { try { ServiceCaller.this.service = this; context.addServiceListener(this, "(&" //$NON-NLS-1$ + "(objectClass=" + serviceType.getName() + ")" // //$NON-NLS-1$ //$NON-NLS-2$ + (filter == null ? "" : filter) // //$NON-NLS-1$ + ")"); //$NON-NLS-1$ context.addBundleListener(this); if ((ref.getBundle() == null || context.getBundle() == null) && ServiceCaller.this.service == this) { // service should have been untracked but we may have missed the event // before we could added the listeners unget(); } if (getRank(ref) != rank) { // ranking has changed; unget to force reget in case the ranking is not the // highest unget(); } } catch (InvalidSyntaxException e) { // really should never happen with our own filter above. ServiceCaller.this.service = null; throw new IllegalStateException(e); } catch (IllegalStateException e) { // bundle was stopped before we could get listeners added/removed ServiceCaller.this.service = null; } // Note that we always return this ReferenceAndService // even for cases where the instance was unget // It is way complicated to try again and // even if we did the returned value can become // stale right after return. return Optional.of(this); } void untrack() { synchronized (ServiceCaller.this) { if (ServiceCaller.this.service == this) { ServiceCaller.this.service = null; } try { context.removeServiceListener(this); context.removeBundleListener(this); } catch (IllegalStateException e) { // context is invalid; // ignore - the listeners already got cleaned up } } } } private final Bundle bundle; private final Class serviceType; private final String filter; private volatile ReferenceAndService service = null; /** * Creates a {@code ServiceCaller} instance for invoking an OSGi service many * times with a consumer function. * * @param caller a class from the bundle that will consume the service * @param serviceType the OSGi service type to look up * @throws NullPointerException if any of the parameters are {@code null} * @throws IllegalStateException if the bundle associated with the caller class * cannot be determined */ public ServiceCaller(Class caller, Class serviceType) { this(caller, serviceType, null); } /** * Creates a {@code ServiceCaller} instance for invoking an OSGi service many * times with a consumer function. * * @param caller a class from the bundle that will consume the service * @param serviceType the OSGi service type to look up * @param filter the service filter used to look up the service. May be * {@code null}. * @throws NullPointerException if any of the parameters are {@code null} * @throws IllegalStateException if the bundle associated with the caller class * cannot be determined */ public ServiceCaller(Class caller, Class serviceType, String filter) { this.serviceType = Objects.requireNonNull(serviceType); this.bundle = Optional.of(Objects.requireNonNull(caller)).map(FrameworkUtil::getBundle) .orElseThrow(IllegalStateException::new); this.filter = filter; if (filter != null) { try { FrameworkUtil.createFilter(filter); } catch (InvalidSyntaxException e) { throw new IllegalArgumentException(e); } } } private BundleContext getContext() { if (System.getSecurityManager() != null) { return AccessController.doPrivileged((PrivilegedAction) () -> bundle.getBundleContext()); } return bundle.getBundleContext(); } /** * Calls an OSGi service by dynamically looking it up and passing it to the * given consumer. If not running under OSGi, the caller bundle is not active or * the service is not available, return false. Any runtime exception thrown by * the consumer is rethrown by this method. (For handling checked exceptions, * see {@link #callOnce(Class, Class, Consumer)} for a solution.) Subsequent * calls to this method will attempt to reuse the previously acquired service * instance until one of the following occurs: *
    *
  • The {@link #unget()} method is called.
  • *
  • The service is unregistered.
  • *
  • The service properties change such that this {@code ServiceCaller} filter * no longer matches. *
  • The caller bundle is stopped.
  • *
  • The service rankings have changed.
  • *
* * After one of these conditions occur subsequent calls to this method will try * to acquire the another service instance. * * @param consumer the consumer of the OSGi service * @return true if the OSGi service was located and called successfully, false * otherwise */ public boolean call(Consumer consumer) { return trackCurrent().map(r -> { consumer.accept(r.instance); return Boolean.TRUE; }).orElse(Boolean.FALSE); } /** * Return the currently available service. * * @return the currently available service or empty if the service cannot be * found. */ public Optional current() { return trackCurrent().map(r -> r.instance); } private Optional trackCurrent() { ReferenceAndService current = service; if (current != null) { return Optional.of(current); } return getCurrent().flatMap(r -> { synchronized (ServiceCaller.this) { if (service != null) { // another thread beat us // unget this instance and return existing r.unget(); return Optional.of(service); } return r.track(); } }); } private Optional getCurrent() { BundleContext context = getContext(); return getServiceReference(context).map(r -> { S current = context.getService(r); return current == null ? null : new ReferenceAndService(context, r, current); }); } private Optional> getServiceReference(BundleContext context) { if (context == null) { return Optional.empty(); } if (filter == null) { return Optional.ofNullable(context.getServiceReference(serviceType)); } try { return context.getServiceReferences(serviceType, filter).stream().findFirst(); } catch (InvalidSyntaxException e) { // should not happen; filter was checked at construction return Optional.empty(); } } /** * Releases the cached service object, if it exists. Another invocation of * {@link #call(Consumer)} will lazily get the service instance again and cache * the new instance if found. */ public void unget() { ReferenceAndService current = service; if (current != null) { current.unget(); } } }