Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Blewitt2020-06-05 13:29:59 +0000
committerThomas Watson2020-06-25 14:25:48 +0000
commita0d6fe0ccc9164c09c5b2d527ea0eb19d781f5ec (patch)
tree34d2ca3ab6c349a2b25deef5a83f770333168e64
parent679677d560276061994e396b641a9239d3dd781e (diff)
downloadrt.equinox.bundles-a0d6fe0ccc9164c09c5b2d527ea0eb19d781f5ec.tar.gz
rt.equinox.bundles-a0d6fe0ccc9164c09c5b2d527ea0eb19d781f5ec.tar.xz
rt.equinox.bundles-a0d6fe0ccc9164c09c5b2d527ea0eb19d781f5ec.zip
Bug 563987 - ServiceCaller for one-shot service lookupI20200625-1800
There are several times where looking up an OSGi service on demand instead of having a persistent service association may make sense. For example, if static debug options are used for the platform then it may not be desirable to look them up each time the debug flag is accessed. Alternatively, error logs may choose to defer looking up the log until the error situation occurs. By providing a wrapper for doing service lookup on demand, we ensure that we follow OSGi guidelines for returning the service after use, and by providing a functional API we can pass in the code required that needs the log with minimal caller involvement. Additionally the ServiceCaller provides a simple way to cache a single instance of the service for cases where the service is used often Change-Id: I6f301cd85e52dc5d25949d93970cdf4e7b331272 Signed-off-by: Alex Blewitt <alex.blewitt@gmail.com> Signed-off-by: Thomas Watson <tjwatson@us.ibm.com>
-rw-r--r--bundles/org.eclipse.equinox.common.tests/META-INF/MANIFEST.MF1
-rw-r--r--bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/RuntimeTests.java1
-rw-r--r--bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/ServiceCallerTest.java213
-rw-r--r--bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/StatusTest.java6
-rw-r--r--bundles/org.eclipse.equinox.common/META-INF/MANIFEST.MF2
-rw-r--r--bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ServiceCaller.java319
-rw-r--r--bundles/org.eclipse.equinox.registry/META-INF/MANIFEST.MF4
7 files changed, 542 insertions, 4 deletions
diff --git a/bundles/org.eclipse.equinox.common.tests/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.common.tests/META-INF/MANIFEST.MF
index 7b6555c4f..c5b6a6e2c 100644
--- a/bundles/org.eclipse.equinox.common.tests/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.common.tests/META-INF/MANIFEST.MF
@@ -12,6 +12,7 @@ Require-Bundle: org.junit,
org.eclipse.equinox.registry;bundle-version="3.8.200"
Import-Package: org.eclipse.osgi.service.localization,
org.osgi.framework,
+ org.eclipse.core.runtime,
org.osgi.service.log,
org.osgi.service.packageadmin,
org.osgi.util.tracker
diff --git a/bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/RuntimeTests.java b/bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/RuntimeTests.java
index e07eded3e..3f358c413 100644
--- a/bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/RuntimeTests.java
+++ b/bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/RuntimeTests.java
@@ -21,6 +21,7 @@ import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({
CoreExceptionTest.class,
+ ServiceCallerTest.class,
OperationCanceledExceptionTest.class,
PathTest.class,
PluginVersionIdentifierTest.class,
diff --git a/bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/ServiceCallerTest.java b/bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/ServiceCallerTest.java
new file mode 100644
index 000000000..fb71dd2fc
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/ServiceCallerTest.java
@@ -0,0 +1,213 @@
+/*******************************************************************************
+ * Copyright (c) 2020 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
+ *******************************************************************************/
+package org.eclipse.equinox.common.tests;
+
+import static org.junit.Assert.assertNotEquals;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.core.runtime.ServiceCaller;
+import org.eclipse.core.tests.harness.CoreTest;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceRegistration;
+
+public class ServiceCallerTest extends CoreTest {
+ static class ServiceExampleFactory implements ServiceFactory<IServiceExample> {
+ final Map<Bundle, AtomicInteger> createCount = new ConcurrentHashMap<>();
+ volatile ServiceExample lastCreated;
+
+ @Override
+ public IServiceExample getService(Bundle bundle, ServiceRegistration<IServiceExample> registration) {
+ createCount.computeIfAbsent(bundle, (s) -> new AtomicInteger()).incrementAndGet();
+ return lastCreated = new ServiceExample();
+ }
+
+ @Override
+ public void ungetService(Bundle bundle, ServiceRegistration<IServiceExample> registration,
+ IServiceExample service) {
+ // do nothing
+ }
+
+ int getCreateCount(Bundle b) {
+ AtomicInteger result = createCount.get(b);
+ return result == null ? 0 : result.get();
+ }
+ }
+ /**
+ * Need a zero argument constructor to satisfy the test harness. This
+ * constructor should not do any real work nor should it be called by user code.
+ */
+ public ServiceCallerTest() {
+ super(null);
+ }
+
+ public ServiceCallerTest(String name) {
+ super(name);
+ }
+
+ public void testCallOnce() {
+ Bundle bundle = FrameworkUtil.getBundle(ServiceCallerTest.class);
+ assertNotNull("Test only works under an OSGi runtime", bundle);
+ BundleContext context = bundle.getBundleContext();
+ ServiceExampleFactory factory = new ServiceExampleFactory();
+ ServiceRegistration<IServiceExample> reg = null;
+ try {
+ reg = context.registerService(IServiceExample.class, factory, null);
+ ServiceCaller.callOnce(IServiceExample.class, IServiceExample.class, IServiceExample::call);
+ ServiceExample lastCreated1 = factory.lastCreated;
+ assertTrue("Service called successfully", lastCreated1.called);
+
+ Bundle[] users = reg.getReference().getUsingBundles();
+ assertNull("Didn't expect users.", users);
+
+ ServiceCaller.callOnce(IServiceExample.class, IServiceExample.class, IServiceExample::call);
+ ServiceExample lastCreated2 = factory.lastCreated;
+ assertTrue("Service called successfully", lastCreated2.called);
+
+ assertNotEquals("Should have new service each call", lastCreated1, lastCreated2);
+
+ assertEquals("Unexpected createCount", 2, factory.getCreateCount(bundle));
+ } finally {
+ reg.unregister();
+ }
+ }
+
+ public void testCall() throws IOException, BundleException, ClassNotFoundException {
+ Bundle bundle = FrameworkUtil.getBundle(ServiceCallerTest.class);
+ assertNotNull("Test only works under an OSGi runtime", bundle);
+ BundleContext context = bundle.getBundleContext();
+
+ Bundle testBundle = StatusTest.installTestClassBundle(context);
+ testBundle.start();
+
+ ServiceCaller<IServiceExample> thisClassCaller;
+ ServiceCaller<IServiceExample> thisClassCallerFilter;
+ ServiceCaller<IServiceExample> otherClassCaller;
+
+ thisClassCaller = new ServiceCaller<>(getClass(), IServiceExample.class);
+ thisClassCallerFilter = new ServiceCaller<>(getClass(), IServiceExample.class, "(test=value2)");
+ Class<?> testClass = testBundle.loadClass(TestClass.class.getName());
+ otherClassCaller = new ServiceCaller<>(testClass, IServiceExample.class);
+
+ assertFalse("Should not be called.", thisClassCaller.call(IServiceExample::call));
+ assertFalse("Should not be called.", thisClassCallerFilter.call(IServiceExample::call));
+ assertFalse("Should not be called.", otherClassCaller.call(IServiceExample::call));
+
+ ServiceExampleFactory factory = new ServiceExampleFactory();
+ Dictionary<String, String> props = new Hashtable<>();
+ props.put("test", "value1");
+ ServiceRegistration<IServiceExample> reg = context.registerService(IServiceExample.class, factory, props);
+ try {
+ assertTrue("Call returned false.", thisClassCaller.call(IServiceExample::call));
+ assertTrue("Service called successfully", factory.lastCreated.called);
+
+ assertFalse("Should not be called.", thisClassCallerFilter.call(IServiceExample::call));
+
+ factory.lastCreated.called = false;
+ assertTrue("Call returned false.", otherClassCaller.call(IServiceExample::call));
+ assertTrue("Service called successfully", factory.lastCreated.called);
+
+ assertTrue("Call returned false.", thisClassCaller.call(IServiceExample::call));
+ assertTrue("Call returned false.", otherClassCaller.call(IServiceExample::call));
+
+ assertEquals("Wrong createCount", 1, factory.getCreateCount(bundle));
+ assertEquals("Wrong createCount", 1, factory.getCreateCount(testBundle));
+
+ Bundle[] users = reg.getReference().getUsingBundles();
+ assertNotNull("Didn't expect users.", users);
+
+ Collection<Bundle> userCollection = Arrays.asList(users);
+ assertTrue("Missing bundle.", userCollection.contains(bundle));
+ assertTrue("Missing bundle.", userCollection.contains(testBundle));
+
+ reg.unregister();
+ assertFalse("Should not be called.", thisClassCaller.call(IServiceExample::call));
+ assertFalse("Should not be called.", otherClassCaller.call(IServiceExample::call));
+
+ props.put("test", "value2");
+ reg = context.registerService(IServiceExample.class, factory, props);
+
+ assertTrue("Call returned false.", thisClassCaller.call(IServiceExample::call));
+ assertTrue("Call returned false.", thisClassCallerFilter.call(IServiceExample::call));
+ assertTrue("Call returned false.", otherClassCaller.call(IServiceExample::call));
+ assertTrue("Call returned false.", thisClassCaller.call(IServiceExample::call));
+ assertTrue("Call returned false.", thisClassCallerFilter.call(IServiceExample::call));
+ assertTrue("Call returned false.", otherClassCaller.call(IServiceExample::call));
+ assertEquals("Wrong createCount", 2, factory.getCreateCount(bundle));
+ assertEquals("Wrong createCount", 2, factory.getCreateCount(testBundle));
+
+ testBundle.stop();
+ assertFalse("Should Not be called.", otherClassCaller.call(IServiceExample::call));
+
+ testBundle.start();
+ assertTrue("Call returned false.", otherClassCaller.call(IServiceExample::call));
+ assertEquals("Wrong createCount", 3, factory.getCreateCount(testBundle));
+
+ thisClassCaller.unget();
+ thisClassCallerFilter.unget();
+ assertTrue("Call returned false.", thisClassCaller.call(IServiceExample::call));
+ assertTrue("Call returned false.", thisClassCallerFilter.call(IServiceExample::call));
+ assertEquals("Wrong createCount", 3, factory.getCreateCount(bundle));
+
+ props.put("test", "value3");
+ reg.setProperties(props);
+ assertTrue("Call returned false.", thisClassCaller.call(IServiceExample::call));
+ assertFalse("Should not be called.", thisClassCallerFilter.call(IServiceExample::call));
+ } finally {
+ testBundle.uninstall();
+ thisClassCaller.unget();
+ otherClassCaller.unget();
+ try {
+ reg.unregister();
+ } catch (IllegalStateException e) {
+ // ignore
+ }
+ }
+ }
+
+ public void testInvalidFilter() {
+ try {
+ new ServiceCaller<>(getClass(), IServiceExample.class, "invalid filter");
+ fail("Expected an exception on invalid filter.");
+ } catch (IllegalArgumentException e) {
+ assertTrue("Unexpected cause.", e.getCause() instanceof InvalidSyntaxException);
+ }
+ }
+
+ interface IServiceExample {
+ void call();
+ }
+
+ static class ServiceExample implements IServiceExample {
+ boolean called = false;
+
+ @Override
+ public void call() {
+ called = true;
+ }
+
+ }
+}
diff --git a/bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/StatusTest.java b/bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/StatusTest.java
index 41e4405c0..15d623546 100644
--- a/bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/StatusTest.java
+++ b/bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/StatusTest.java
@@ -146,12 +146,16 @@ public class StatusTest extends CoreTest {
private Bundle installNoBSNBundle() throws IOException, BundleException {
BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
+ return installTestClassBundle(bc);
+ }
+
+ public static Bundle installTestClassBundle(BundleContext bc) throws IOException, BundleException {
File noNameBSNFile = bc.getDataFile("noNameBSN.jar");
noNameBSNFile.delete();
URI noNameBSNJar = URI.create("jar:" + noNameBSNFile.toURI().toASCIIString());
try (FileSystem zipfs = FileSystems.newFileSystem(noNameBSNJar, Collections.singletonMap("create", "true"))) {
- URL testClassURL = getClass().getResource("TestClass.class");
+ URL testClassURL = StatusTest.class.getResource("TestClass.class");
Path testClassPath = zipfs.getPath(testClassURL.getPath().substring(1));
// copy a file into the zip file
Files.createDirectories(testClassPath.getParent());
diff --git a/bundles/org.eclipse.equinox.common/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.common/META-INF/MANIFEST.MF
index 9ab1e6089..0f35a0805 100644
--- a/bundles/org.eclipse.equinox.common/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.common/META-INF/MANIFEST.MF
@@ -14,7 +14,7 @@ Export-Package: org.eclipse.core.internal.boot;x-friends:="org.eclipse.core.reso
org.eclipse.core.runtime.compatibility,
org.eclipse.core.filesystem,
org.eclipse.equinox.security",
- org.eclipse.core.runtime;common=split;version="3.5.0";mandatory:=common,
+ org.eclipse.core.runtime;common=split;version="3.6.0";mandatory:=common,
org.eclipse.core.text;version="3.12.0",
org.eclipse.equinox.events;version="1.0.0"
Bundle-Vendor: %providerName
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ServiceCaller.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ServiceCaller.java
new file mode 100644
index 000000000..b05c05a10
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/ServiceCaller.java
@@ -0,0 +1,319 @@
+/*******************************************************************************
+ * Copyright (c) 2020 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
+ *******************************************************************************/
+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
+ * <ul>
+ * <li> Single invocations which happen only once or very rarely.
+ * In this case, maintaining a cache of the service is not worth the overhead.</li>
+ * <li> Multiple invocations that happen often and rapidly. In this case, maintaining
+ * a cache of the service is worth the overhead.</li>
+ * </ul>
+ *
+ * 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.
+ * <p>
+ * Single invocation example:
+ * <pre>
+ * ServiceCaller.callOnce(MyClass.class, ILog.class, (logger) -> logger.info("All systems go!"));
+ * </pre>
+ * 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:
+ *
+ * <pre>
+ * Bundle bundle = FrameworkUtil.getBundle(BadExample.class);
+ * BundleContext context = bundle == null ? null : bundle.getBundleContext();
+ * ServiceReference&lt;Service&gt; 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);
+ * }
+ * </pre>
+ * 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:
+ * <pre>
+ * static final ServiceCaller&lt;ILog&gt; log = new ServiceCaller(MyClass.class, ILog.class);
+ * static void info(String msg) {
+ * log.call(logger -> logger.info(msg);
+ * }
+ * </pre>
+ *
+ * Note that this class is intended for simple service usage patterns only. More advanced cases that
+ * require tracking of service ranking or additional service property matching must use other
+ * mechanisms such as the {@link ServiceTracker} or declarative services.
+ *
+ * @param <Service> the service type for this caller
+ * @since 3.13
+ */
+public class ServiceCaller<Service> {
+ /**
+ * 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 exception thrown by the consumer is rethrown by this method.
+ * If the service is found, call the service and return true.
+ * @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 <Service> 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 null
+ */
+ public static <Service> boolean callOnce(Class<?> caller, Class<Service> serviceType, Consumer<Service> consumer) {
+ return new ServiceCaller<>(caller, serviceType).getCallUnget(consumer);
+ }
+
+ class ReferenceAndService implements SynchronousBundleListener, ServiceListener {
+ final BundleContext context;
+ final ServiceReference<Service> ref;
+ final Service instance;
+
+ public ReferenceAndService(final BundleContext context, ServiceReference<Service> ref, Service instance) {
+ this.context = context;
+ this.ref = ref;
+ this.instance = instance;
+ }
+
+ 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) {
+ untrack();
+ }
+ }
+
+ @Override
+ public void serviceChanged(ServiceEvent e) {
+ if (e.getType() == ServiceEvent.UNREGISTERING) {
+ untrack();
+ }
+ if (filter != null && e.getType() == ServiceEvent.MODIFIED_ENDMATCH) {
+ untrack();
+ }
+ }
+
+ // must hold monitor on ServiceCaller.this when calling track
+ Optional<ReferenceAndService> track() {
+ try {
+ ServiceCaller.this.service = this;
+ // Filter specific to this service reference ID
+ context.addServiceListener(this, "(&" //$NON-NLS-1$
+ + "(objectClass=" + serviceType.getName() + ")" // //$NON-NLS-1$ //$NON-NLS-2$
+ + "(service.id=" + ref.getProperty(Constants.SERVICE_ID) + ")" // //$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
+ untrack();
+ return Optional.empty();
+ }
+ } 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;
+ return Optional.empty();
+ }
+ 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
+ }
+ }
+ }
+ }
+
+ final Bundle bundle;
+ final Class<Service> serviceType;
+ final String filter;
+ 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
+ */
+ public ServiceCaller(Class<?> caller, Class<Service> 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}.
+ */
+ public ServiceCaller(Class<?> caller, Class<Service> serviceType, String filter) {
+ this.serviceType = Objects.requireNonNull(serviceType);
+ this.bundle = Objects.requireNonNull(FrameworkUtil.getBundle(Objects.requireNonNull(caller)));
+ this.filter = filter;
+ if (filter != null) {
+ try {
+ FrameworkUtil.createFilter(filter);
+ } catch (InvalidSyntaxException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+ }
+
+ private boolean getCallUnget(Consumer<Service> consumer) {
+ return getCurrent().map((r) -> {
+ try {
+ consumer.accept(r.instance);
+ return Boolean.TRUE;
+ } finally {
+ r.unget();
+ }
+ }).orElse(Boolean.FALSE);
+ }
+
+ private BundleContext getContext() {
+ if (System.getSecurityManager() != null) {
+ return AccessController.doPrivileged((PrivilegedAction<BundleContext>) () -> 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 exception thrown by the consumer is rethrown by this method.
+ * Subsequent calls to this method will attempt to reuse the previously acquired service instance until one
+ * of the following occurs:
+ * <ul>
+ * <li>The {@link #unget()} method is called.</li>
+ * <li>The service is unregistered.</li>
+ * <li>The caller bundle is stopped.</li>
+ * </ul>
+ *
+ * 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<Service> consumer) {
+ return trackCurrent().map((r) -> {
+ consumer.accept(r.instance);
+ return Boolean.TRUE;
+ }).orElse(Boolean.FALSE);
+ }
+
+ private Optional<ReferenceAndService> 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<ReferenceAndService> getCurrent() {
+ BundleContext context = getContext();
+ return getServiceReference(context).map((r) -> {
+ Service current = context.getService(r);
+ return current == null ? null : new ReferenceAndService(context, r, current);
+ });
+ }
+
+ private Optional<ServiceReference<Service>> 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();
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.registry/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.registry/META-INF/MANIFEST.MF
index 486d296f0..37b535894 100644
--- a/bundles/org.eclipse.equinox.registry/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.registry/META-INF/MANIFEST.MF
@@ -8,10 +8,10 @@ Export-Package: org.eclipse.core.internal.adapter;x-internal:=true,
org.eclipse.core.internal.registry;x-friends:="org.eclipse.core.runtime",
org.eclipse.core.internal.registry.osgi;x-friends:="org.eclipse.core.runtime",
org.eclipse.core.internal.registry.spi;x-internal:=true,
- org.eclipse.core.runtime;registry=split;version="3.5.0";mandatory:=registry,
+ org.eclipse.core.runtime;registry=split;version="3.6.0";mandatory:=registry,
org.eclipse.core.runtime.dynamichelpers;version="3.4.0",
org.eclipse.core.runtime.spi;version="3.4.0"
-Require-Bundle: org.eclipse.equinox.common;bundle-version="[3.7.0,4.0.0)"
+Require-Bundle: org.eclipse.equinox.common;bundle-version="[3.13.0,4.0.0)"
Bundle-Vendor: %providerName
Bundle-Activator: org.eclipse.core.internal.registry.osgi.Activator
Import-Package: javax.xml.parsers,

Back to the top