/* * Copyright 2007 Alin Dreghiciu. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. * * See the License for the specific language governing permissions and * limitations under the License. */ package org.ops4j.pax.swissbox.extender; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.ops4j.lang.NullArgumentException; import org.ops4j.pax.swissbox.lifecycle.AbstractLifecycle; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; import org.osgi.framework.BundleListener; import org.osgi.framework.SynchronousBundleListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Watches bundles life cycle events. Once a bundle becomes active a scanning process will be performed and each bundle * resource found during scanning will be registered. Once a bundle stops the registered resources for that bundle will * be unregistered. * If the bundle watcher is stopped all bundle resources will be unregistered. * * @author Alin Dreghiciu * @since October 14, 2007 */ public class BundleWatcher<T> extends AbstractLifecycle { /** * Logger. */ private static final Logger LOG = LoggerFactory.getLogger( BundleWatcher.class ); /** * Bundle context in use. Constructor parameter. Cannot be null. */ private final BundleContext m_context; /** * Bundle scanner used to scan bundles. Constructor parameter. Cannot be null. */ private final BundleScanner<T> m_scanner; /** * Bundle observers for scanned entries. Cannot be null but can be empty. */ private final List<BundleObserver<T>> m_observers; /** * Mapping between bundle and scanned resources. Cannot be null. */ private Map<Bundle, List<T>> m_mappings; /** * Bundle listener for bundle events. Cannot be null. */ private BundleListener m_bundleListener; /** * A Service for running multithreaded tasks. */ private final ExecutorService executorService; /** * Create a new bundle watcher. * * @param context a bundle context. Cannot be null. * @param scanner a bundle scanner. Cannot be null. */ public BundleWatcher( final BundleContext context, final BundleScanner<T> scanner ) { this( context, scanner, (BundleObserver<T>[]) null ); } /** * Create a new bundle watcher. * * @param context a bundle context. Cannot be null. * @param scanner a bundle scanner. Cannot be null. * @param observers list of observers */ public BundleWatcher( final BundleContext context, final BundleScanner<T> scanner, final BundleObserver<T>... observers ) { LOG.debug( "Creating bundle watcher with scanner [" + scanner + "]..." ); NullArgumentException.validateNotNull( context, "Context" ); NullArgumentException.validateNotNull( scanner, "Bundle scanner" ); m_context = context; m_scanner = scanner; m_observers = new ArrayList<BundleObserver<T>>(); if( observers != null ) { m_observers.addAll( Arrays.asList( observers ) ); } executorService = Executors.newScheduledThreadPool(3, new ThreadFactory() { private final AtomicInteger count = new AtomicInteger(); public Thread newThread(Runnable r) { final Thread t = Executors.defaultThreadFactory().newThread(r); t.setName("BundleWatcher" + ": " + count.incrementAndGet()); t.setDaemon(true); return t; } }); } void destroy() { executorService.shutdown(); // wait for the queued tasks to execute try { executorService.awaitTermination(60, TimeUnit.SECONDS); } catch (InterruptedException e) { // ignore } } /** * Registers a listener for bundle events and scans already active bundles. */ @Override protected void onStart() { m_mappings = new HashMap<Bundle, List<T>>(); // listen to bundles events m_context.addBundleListener( m_bundleListener = new SynchronousBundleListener() { public void bundleChanged( final BundleEvent bundleEvent ) { switch( bundleEvent.getType() ) { case BundleEvent.STARTED: register( bundleEvent.getBundle() ); break; case BundleEvent.STOPPED: unregister( bundleEvent.getBundle() ); break; } } } ); // scan already started bundles Bundle[] bundles = m_context.getBundles(); if( bundles != null ) { for( Bundle bundle : bundles ) { if( bundle.getState() == Bundle.ACTIVE ) { register( bundle ); } } } } /** * Un-register the bundle listener, releases resources */ @Override protected void onStop() { m_context.removeBundleListener( m_bundleListener ); final Bundle[] toBeRemoved = m_mappings.keySet().toArray( new Bundle[m_mappings.keySet().size()] ); for( Bundle bundle : toBeRemoved ) { unregister( bundle ); } m_bundleListener = null; m_mappings = null; } /** * Scans entries using the bundle scanner and registers the result of scanning process. * Then notify the observers. If an exception appears during notification, it is ignored. * * @param bundle registered bundle */ private void register( final Bundle bundle ) { LOG.debug( "Scanning bundle [" + bundle.getSymbolicName() + "]" ); final List<T> resources = m_scanner.scan( bundle ); m_mappings.put( bundle, resources ); if( resources != null && resources.size() > 0 ) { LOG.debug( "Found resources " + resources ); for( final BundleObserver<T> observer : m_observers ) { try { //here the executor service completes the job in an extra thread. executorService.submit(new Runnable() { public void run() { try { observer.addingEntries( bundle, Collections.unmodifiableList( resources ) ); } catch (Throwable t) { LOG.error( "Exception in executor thread", t ); } } }); } catch( Throwable ignore ) { LOG.error( "Ignored exception during register", ignore ); } } } } /** * Un-registers each entry from the unregistered bundle by first notifying the observers. If an exception appears * during notification, it is ignored. * * @param bundle the un-registred bundle */ private void unregister( final Bundle bundle ) { if (bundle == null) return; // no need to go any further, system probably stopped. LOG.debug( "Releasing bundle [" + bundle.getSymbolicName() + "]" ); final List<T> resources = m_mappings.get( bundle ); if( resources != null && resources.size() > 0 ) { LOG.debug( "Un-registering " + resources ); for( BundleObserver<T> observer : m_observers ) { try { observer.removingEntries( bundle, Collections.unmodifiableList( resources ) ); } catch( Throwable ignore ) { LOG.error( "Ignored exception during un-register", ignore ); } } } m_mappings.remove( bundle ); } }