Skip to content

Commit

Permalink
Add convenient way to get rarely fetched and locally used OSGi services
Browse files Browse the repository at this point in the history
  • Loading branch information
HannesWell committed Oct 27, 2022
1 parent 2dbea48 commit 0719121
Showing 1 changed file with 125 additions and 2 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.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.osgi.framework.*;
import org.osgi.util.tracker.ServiceTracker;

Expand Down Expand Up @@ -90,6 +93,9 @@
* @since 3.13
*/
public class ServiceCaller<Service> {

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.
* <p>
Expand Down Expand Up @@ -140,6 +146,123 @@ public static <Service> boolean callOnce(Class<?> caller, Class<Service> service
return new ServiceCaller<>(caller, serviceType, filter).getCallUnget(consumer);
}

/**
* 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 #isAvailable()} or
* {@link #get()} 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 ServiceUsage<S> extends AutoCloseable, Supplier<S> {

/**
* Returns true if any service is available.
*
* @return true if any instance is available.
*/
boolean isAvailable();

/**
* Gets the first available service implementation.
*/
@Override
S get() throws NoSuchElementException;

Stream<S> all();

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

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

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

private static <S> ServiceUsage<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 useService(serviceType, filter, ctx);
}

// TODO: consider to provide this as public overload if somebody already has a
// context?
private static <S> ServiceUsage<S> useService(Class<S> serviceType, String filter, BundleContext context)
throws InvalidSyntaxException {
String serviceName = serviceType.getName();
@SuppressWarnings("unchecked")
ServiceReference<S>[] references = (ServiceReference<S>[]) context.getServiceReferences(serviceName, filter);
@SuppressWarnings("unchecked")
S[] instances = (S[]) new Object[references.length];

return new ServiceUsage<>() {
@Override
public boolean isAvailable() {
return references.length > 0 && all().findAny().isPresent();
}

@Override
public S get() {
if (references.length > 0) {
Optional<S> first = all().findFirst();
if (first.isPresent()) {
return first.get();
}
}
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]);
}
return instances[i];
}).filter(Objects::nonNull);
}

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

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

0 comments on commit 0719121

Please sign in to comment.