/* * 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); } }