/*
* Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.controller.config.manager.impl.osgi;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.util.tracker.BundleTracker;
import org.osgi.util.tracker.BundleTrackerCustomizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Extensible bundle tracker. Takes several BundleTrackerCustomizers and
* propagates bundle events to all of them.
*
*
* Primary customizer may return tracking object, which will be passed to it
* during invocation of
* {@link BundleTrackerCustomizer#removedBundle(Bundle, BundleEvent, Object)}
*
*
* This extender modifies behavior to not leak platform thread in
* {@link BundleTrackerCustomizer#addingBundle(Bundle, BundleEvent)} but deliver
* this event from its own single threaded executor.
*
*
* If bundle is removed before event for adding bundle was executed, that event
* is cancelled. If addingBundle event is currently in progress or was already
* executed, platform thread is block until addingBundle finishes so bundle
* could be removed correctly in platform thread.
*
*
* Method
* {@link BundleTrackerCustomizer#removedBundle(Bundle, BundleEvent, Object)} is
* never invoked on registered trackers.
*
* @param value
*/
public final class ExtensibleBundleTracker extends BundleTracker> {
private static final ThreadFactory THREAD_FACTORY = new ThreadFactoryBuilder()
.setNameFormat("config-bundle-tracker-%d").build();
private final ExecutorService eventExecutor;
private final BundleTrackerCustomizer primaryTracker;
private final BundleTrackerCustomizer>[] additionalTrackers;
private static final Logger LOG = LoggerFactory.getLogger(ExtensibleBundleTracker.class);
public ExtensibleBundleTracker(final BundleContext context,
final BundleTrackerCustomizer primaryBundleTrackerCustomizer,
final BundleTrackerCustomizer>... additionalBundleTrackerCustomizers) {
this(context, Bundle.ACTIVE, primaryBundleTrackerCustomizer, additionalBundleTrackerCustomizers);
}
public ExtensibleBundleTracker(final BundleContext context, final int bundleState,
final BundleTrackerCustomizer primaryBundleTrackerCustomizer,
final BundleTrackerCustomizer>... additionalBundleTrackerCustomizers) {
super(context, bundleState, null);
this.primaryTracker = primaryBundleTrackerCustomizer;
this.additionalTrackers = additionalBundleTrackerCustomizers;
eventExecutor = Executors.newSingleThreadExecutor(THREAD_FACTORY);
LOG.trace("Registered as extender with context {} and bundle state {}", context, bundleState);
}
@Override
public Future addingBundle(final Bundle bundle, final BundleEvent event) {
LOG.trace("Submiting AddingBundle for bundle {} and event {} to be processed asynchronously", bundle, event);
return eventExecutor.submit(() -> {
T primaryTrackerRetVal = primaryTracker.addingBundle(bundle, event);
forEachAdditionalBundle(tracker -> tracker.addingBundle(bundle, event));
LOG.trace("AddingBundle for {} and event {} finished successfully", bundle, event);
return primaryTrackerRetVal;
});
}
@Override
public void modifiedBundle(final Bundle bundle, final BundleEvent event, final Future object) {
// Intentionally NOOP
}
@Override
public void removedBundle(final Bundle bundle, final BundleEvent event, final Future object) {
if (!object.isDone() && object.cancel(false)) {
// We canceled adding event before it was processed
// so it is safe to return
LOG.trace("Adding Bundle event for {} was cancelled. No additional work required.", bundle);
return;
}
try {
LOG.trace("Invoking removedBundle event for {}", bundle);
primaryTracker.removedBundle(bundle, event, object.get());
forEachAdditionalBundle(tracker -> tracker.removedBundle(bundle, event, null));
LOG.trace("Removed bundle event for {} finished successfully.", bundle);
} catch (final ExecutionException | InterruptedException e) {
LOG.error("Failed to remove bundle {}", bundle, e);
}
}
private void forEachAdditionalBundle(final BundleStrategy lambda) {
for (BundleTrackerCustomizer> trac : additionalTrackers) {
lambda.execute(trac);
}
}
private interface BundleStrategy {
void execute(BundleTrackerCustomizer> tracker);
}
}