Skip to content

Commit

Permalink
Add ServiceCaller.use() methods to simply obtain rarely used services
Browse files Browse the repository at this point in the history
  • Loading branch information
HannesWell committed Nov 10, 2022
1 parent d1c1ac4 commit 21f16fa
Showing 1 changed file with 123 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@
*******************************************************************************/
package org.eclipse.core.runtime;

import java.lang.StackWalker.Option;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.osgi.framework.*;
import org.osgi.util.tracker.ServiceTracker;

Expand Down Expand Up @@ -96,6 +99,9 @@
* @since 3.13
*/
public class ServiceCaller<S> {

private static final StackWalker STACK_WALKER = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE);

/**
* Calls an OSGi service by dynamically looking it up and passing it to the
* given consumer.
Expand Down Expand Up @@ -160,6 +166,123 @@ public static <S> boolean callOnce(Class<?> caller, Class<S> serviceType, String
}).orElse(Boolean.FALSE);
}

/**
* Not thread save.
* <p>
* Service registrations are obtained upon creation, service instances are get
* on the first request (i.e. the first instance when {@link #first()} or
* {@link #all()} is called. Services that are unregistered in the meantime are
* discarded and newly registered services are ignored.
* </p>
*
* @param <S> the service type for this caller
* @since 3.17
*/
public interface ServiceUsageScope<S> extends AutoCloseable { // TODO:? , Supplier<S>

/**
* Gets the first available service implementation.
*/
Optional<S> first();

Stream<S> all();

@Override
void close(); // do not throw (checked) exception
}

/**
* @since 3.17
*/
public static <T> ServiceUsageScope<T> use(Class<T> service) {
Class<?> callerClass = STACK_WALKER.getCallerClass();
try {
return useService(callerClass, service, null);
} catch (InvalidSyntaxException e) {
throw new AssertionError("Impossible exception was thrown"); //$NON-NLS-1$
}
}

/**
* @since 3.17
*/
public static <S> ServiceUsageScope<S> use(Class<S> service, String filter) throws InvalidSyntaxException {
Class<?> callerClass = STACK_WALKER.getCallerClass();
return useService(callerClass, service, filter);
}

private static <S> ServiceUsageScope<S> useService(Class<?> caller, Class<S> serviceType, String filter)
throws InvalidSyntaxException {
Bundle bundle = FrameworkUtil.getBundle(caller);
if (bundle == null) {
throw new IllegalStateException(); // TODO: handle differently?
}
BundleContext ctx = bundle.getBundleContext();
if (ctx == null) {
throw new IllegalStateException(); // TODO: handle differently?
}
return use(serviceType, filter, ctx);
}

/**
* @since 3.17
*/
public static <S> ServiceUsageScope<S> use(Class<S> service, BundleContext context) {
try {
return use(service, null, context);
} catch (InvalidSyntaxException e) {
throw new AssertionError("Impossible exception was thrown"); //$NON-NLS-1$
}
}

/**
* @since 3.17
*/
public static <S> ServiceUsageScope<S> use(Class<S> service, String filter, BundleContext context)
throws InvalidSyntaxException {
ServiceReference<?>[] references = context.getServiceReferences(service.getName(), filter);
Object[] instances = new Object[references.length];
return new ServiceUsageScope<>() {
private Optional<S> first;

@Override
public Optional<S> first() {
if (first == null) {
first = references.length == 0 ? Optional.empty() : all().findFirst();
}
return first;
// TODO: if the ServiceUsageScope itself would be the 'Optional' the name of the
// missing service could be mentioned in the exception.
// throw new NoSuchElementException("Service '" + serviceName + "' is not available"); //$NON-NLS-1$//$NON-NLS-2$
}

@Override

public Stream<S> all() {
if (references.length == 0) {
return Stream.empty();
}
return IntStream.range(0, references.length).mapToObj(i -> {
if (instances[i] == null) {
instances[i] = context.getService(references[i]);
}
@SuppressWarnings("unchecked")
S instance = (S) instances[i];
return instance;
}).filter(Objects::nonNull);
}

@Override
public void close() {
for (int i = 0; i < references.length; i++) {
if (instances[i] != null) {
context.ungetService(references[i]);
}
}
}
};
}

private static int getRank(ServiceReference<?> ref) {
Object rank = ref.getProperty(Constants.SERVICE_RANKING);
if (rank instanceof Integer) {
Expand Down

0 comments on commit 21f16fa

Please sign in to comment.