blob: fb893d85ecd297858e1cee57c0b50ffff3aa3f86 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014, 2018 BestSolution.at 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:
* Tom Schindl <tom.schindl@bestsolution.at> - initial API and implementation
* Dirk Fauth <dirk.fauth@googlemail.com> - Bug 513563
* Christoph Läubrich - Bug 572476
*******************************************************************************/
package org.eclipse.e4.core.di.internal.extensions;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.core.di.IInjector;
import org.eclipse.e4.core.di.extensions.Service;
import org.eclipse.e4.core.di.suppliers.ExtendedObjectSupplier;
import org.eclipse.e4.core.di.suppliers.IObjectDescriptor;
import org.eclipse.e4.core.di.suppliers.IRequestor;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
import org.osgi.service.log.Logger;
import org.osgi.service.log.LoggerFactory;
import org.osgi.util.tracker.BundleTracker;
import org.osgi.util.tracker.BundleTrackerCustomizer;
import org.osgi.util.tracker.ServiceTracker;
/**
* Supplier for {@link Service}
*/
@Component(service = { ExtendedObjectSupplier.class, EventHandler.class }, property = {
"dependency.injection.annotation=org.eclipse.e4.core.di.extensions.Service",
"event.topics=" + IEclipseContext.TOPIC_DISPOSE })
public class ServiceSupplier extends ExtendedObjectSupplier implements EventHandler {
LoggerFactory factory;
Logger logger;
private volatile BundleTracker<ServiceSupplierContext> supplierContextTracker;
@Activate
void activate(BundleContext bundleContext) {
this.supplierContextTracker = new BundleTracker<>(bundleContext,
Bundle.RESOLVED | Bundle.STARTING | Bundle.ACTIVE | Bundle.STOPPING,
new BundleTrackerCustomizer<ServiceSupplierContext>() {
@Override
public ServiceSupplierContext addingBundle(Bundle bundle, BundleEvent event) {
return new ServiceSupplierContext(bundle, bundleContext,
(t, e) -> logError("Injection failed", e));//$NON-NLS-1$
}
@Override
public void modifiedBundle(Bundle bundle, BundleEvent event, ServiceSupplierContext object) {
switch (event.getType()) {
case BundleEvent.STOPPING:
object.serviceTracker.values().forEach(ServiceTracker::close);
break;
case BundleEvent.STARTED:
case BundleEvent.STOPPED:
object.refreshServices();
break;
default:
break;
}
}
@Override
public void removedBundle(Bundle bundle, BundleEvent event, ServiceSupplierContext object) {
object.dispose();
}
});
this.supplierContextTracker.open();
}
@Deactivate
void deactivate(BundleContext bundleContext) {
this.supplierContextTracker.close();
}
@Override
public Object get(IObjectDescriptor descriptor, IRequestor requestor, boolean track, boolean group) {
Type desiredType = descriptor.getDesiredType();
Bundle bundle = FrameworkUtil.getBundle(requestor.getRequestingObjectClass());
ServiceSupplierContext supplierContext;
if (bundle == null || (supplierContext = supplierContextTracker.getObject(bundle)) == null
|| supplierContext.disposed) {
// bundle has gone...
return IInjector.NOT_A_VALUE;
}
Service qualifier = descriptor.getQualifier(Service.class);
if (desiredType instanceof ParameterizedType) {
ParameterizedType t = (ParameterizedType) desiredType;
if (t.getRawType() == Collections.class || t.getRawType() == List.class) {
return handleCollection(supplierContext, t.getActualTypeArguments()[0], requestor,
track && qualifier.dynamic(), qualifier);
}
}
return handleSingle(supplierContext, desiredType, requestor, track && qualifier.dynamic(), qualifier);
}
private Object handleSingle(ServiceSupplierContext supplierContext, Type t, IRequestor requestor, boolean track,
Service qualifier) {
Class<?> cls = t instanceof ParameterizedType ? (Class<?>) ((ParameterizedType) t).getRawType() : (Class<?>) t;
try {
Filter filter = supplierContext.getFilter(qualifier.filterExpression());
ServiceSupplierTracker<?> tracker = supplierContext.getTracker(cls);
return tracker.getAndTrack(filter, track ? requestor : null, s -> {
Optional<?> service = s.findFirst();
if (service.isPresent()) {
return service.get();
}
return IInjector.NOT_A_VALUE;
});
} catch (InvalidSyntaxException e) {
logError("Invalid filter expression", e); //$NON-NLS-1$
return IInjector.NOT_A_VALUE;
}
}
private Object handleCollection(ServiceSupplierContext supplierContext, Type t, IRequestor requestor, boolean track,
Service qualifier) {
Class<?> cls = t instanceof ParameterizedType ? (Class<?>) ((ParameterizedType) t).getRawType() : (Class<?>) t;
try {
Filter filter = supplierContext.getFilter(qualifier.filterExpression());
ServiceSupplierTracker<?> tracker = supplierContext.getTracker(cls);
return tracker.getAndTrack(filter, track ? requestor : null, s -> s.collect(Collectors.toList()));
} catch (InvalidSyntaxException e) {
logError("Invalid filter expression", e); //$NON-NLS-1$
return IInjector.NOT_A_VALUE;
}
}
/**
* Method to log an exception.
*
* @param message The log message.
* @param e The exception that should be logged.
*/
void logError(String message, Throwable e) {
Logger log = this.logger;
if (log != null) {
log.error(message, e);
} else {
// fallback if no LogService is available
e.printStackTrace();
}
}
@Override
public void handleEvent(Event event) {
supplierContextTracker.getTracked().values().stream().flatMap(ctx -> ctx.serviceTracker.values().stream())
.forEach(ServiceSupplierTracker::cleanup);
}
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
void setLogger(LoggerFactory factory) {
this.factory = factory;
this.logger = factory.getLogger(getClass());
}
void unsetLogger(LoggerFactory loggerFactory) {
if (this.factory == loggerFactory) {
this.factory = null;
this.logger = null;
}
}
private static final class ServiceSupplierContext {
private Bundle bundle;
private BundleContext serviceBundleContext;
final Map<Class<?>, ServiceSupplierTracker<?>> serviceTracker = new ConcurrentHashMap<>();
volatile boolean disposed;
private UncaughtExceptionHandler exceptionHandler;
ServiceSupplierContext(Bundle bundle, BundleContext serviceBundleContext,
UncaughtExceptionHandler exceptionHandler) {
this.bundle = bundle;
this.serviceBundleContext = serviceBundleContext;
this.exceptionHandler = exceptionHandler;
}
public Filter getFilter(String filterExpression) throws InvalidSyntaxException {
if (filterExpression == null || filterExpression.isEmpty() || disposed) {
return null;
}
return serviceBundleContext.createFilter(filterExpression);
}
@SuppressWarnings("unchecked")
public <T> ServiceSupplierTracker<T> getTracker(Class<T> serviceClass) {
return (ServiceSupplierTracker<T>) serviceTracker.computeIfAbsent(serviceClass, cls -> {
BundleContext bundleContext = bundle.getBundleContext();
if (bundleContext == null || bundle.getState() == Bundle.STOPPING) {
bundleContext = serviceBundleContext;
}
ServiceSupplierTracker<T> tracker = new ServiceSupplierTracker<>(bundleContext, serviceClass,
exceptionHandler);
tracker.open();
return tracker;
});
}
void refreshServices() {
for (Iterator<Entry<Class<?>, ServiceSupplierTracker<?>>> iter = serviceTracker.entrySet().iterator(); iter
.hasNext();) {
Entry<Class<?>, ServiceSupplierTracker<?>> entry = iter.next();
serviceTracker.compute(entry.getKey(), (k, v) -> {
if (v != null) {
v.close();
v.update(null);
}
return null;
});
}
}
void dispose() {
disposed = true;
refreshServices();
}
}
private static final class ServiceSupplierTracker<T> extends ServiceTracker<T, T> {
Set<IRequestor> trackedRequestors = ConcurrentHashMap.newKeySet();
private UncaughtExceptionHandler exceptionHandler;
private AtomicReference<CompletableFuture<?>> pending = new AtomicReference<>();
public ServiceSupplierTracker(BundleContext context, Class<T> clazz,
UncaughtExceptionHandler exceptionHandler) {
super(context, clazz, null);
this.exceptionHandler = exceptionHandler;
}
public synchronized <R> R getAndTrack(Filter f, IRequestor requestor, Function<Stream<T>, R> extractor) {
cleanup();
Stream<T> stream = getTracked().entrySet().stream().filter(entry -> f == null || f.match(entry.getKey()))
.map(Entry::getValue);
R value = extractor.apply(stream);
if (requestor != null) {
trackedRequestors.add(requestor);
}
return value;
}
void cleanup() {
for (Iterator<IRequestor> iterator = trackedRequestors.iterator(); iterator.hasNext();) {
IRequestor requestor = iterator.next();
if (!requestor.isValid()) {
iterator.remove();
}
}
}
@Override
public T addingService(ServiceReference<T> reference) {
T service = super.addingService(reference);
if (service != null) {
update(() -> getService(reference) != null);
}
return service;
}
@Override
public void removedService(ServiceReference<T> reference, T service) {
super.removedService(reference, service);
update(() -> getService(reference) == null);
}
@Override
public void modifiedService(ServiceReference<T> reference, T service) {
super.modifiedService(reference, service);
update(null);
}
private void update(BooleanSupplier check) {
IRequestor[] requestors = trackedRequestors.toArray(IRequestor[]::new);
if (requestors.length == 0) {
return;
}
CompletableFuture<Void> execution = new CompletableFuture<>();
CompletableFuture<?> pendingExecution = pending.getAndSet(execution);
if (pendingExecution != null) {
pendingExecution.cancel(true);
}
execution.completeAsync(() -> {
if (check != null) {
do {
Thread.yield();
} while (!check.getAsBoolean() && !execution.isCancelled());
}
if (!execution.isCancelled()) {
refreshRequestors(requestors);
}
return null;
}).handle((v, e) -> {
if (e instanceof CancellationException) {
// request was canceled
return null;
}
if (e != null) {
exceptionHandler.uncaughtException(Thread.currentThread(), e);
}
pending.compareAndSet(execution, null);
return null;
});
}
private synchronized void refreshRequestors(IRequestor[] requestors) {
for (IRequestor requestor : requestors) {
if (requestor.isValid()) {
try {
requestor.resolveArguments(false);
requestor.execute();
} catch (RuntimeException e) {
exceptionHandler.uncaughtException(Thread.currentThread(), e);
}
} else {
trackedRequestors.remove(requestor);
}
}
}
}
}