blob: f3601e97288d54dbdbd301dd389a6cc7774091c0 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2006, 2010 VMware Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0
* is available at http://www.opensource.org/licenses/apache2.0.php.
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
* VMware Inc.
*****************************************************************************/
package org.eclipse.gemini.blueprint.extender.internal.activator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.gemini.blueprint.context.ConfigurableOsgiBundleApplicationContext;
import org.eclipse.gemini.blueprint.context.DelegatedExecutionOsgiBundleApplicationContext;
import org.eclipse.gemini.blueprint.context.event.OsgiBundleApplicationContextEventMulticaster;
import org.eclipse.gemini.blueprint.extender.OsgiApplicationContextCreator;
import org.eclipse.gemini.blueprint.extender.OsgiBeanFactoryPostProcessor;
import org.eclipse.gemini.blueprint.extender.internal.dependencies.shutdown.ShutdownSorter;
import org.eclipse.gemini.blueprint.extender.internal.dependencies.startup.DependencyWaiterApplicationContextExecutor;
import org.eclipse.gemini.blueprint.extender.internal.support.ExtenderConfiguration;
import org.eclipse.gemini.blueprint.extender.internal.support.OsgiBeanFactoryPostProcessorAdapter;
import org.eclipse.gemini.blueprint.extender.internal.util.concurrent.Counter;
import org.eclipse.gemini.blueprint.extender.support.ApplicationContextConfiguration;
import org.eclipse.gemini.blueprint.util.OsgiBundleUtils;
import org.eclipse.gemini.blueprint.util.OsgiStringUtils;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import static org.eclipse.gemini.blueprint.extender.internal.util.concurrent.RunnableTimedExecution.execute;
/**
* Manager handling the startup/shutdown threading issues regarding OSGi contexts. Used by {@link ContextLoaderListener}
* .
*
* @author Costin Leau
*/
public class LifecycleManager implements DisposableBean {
/** logger */
private static final Log log = LogFactory.getLog(LifecycleManager.class);
/**
* The contexts we are currently managing. Keys are bundle ids, values are ServiceDependentOsgiApplicationContexts
* for the application context
*/
private final Map<Long, ConfigurableOsgiBundleApplicationContext> managedContexts =
new ConcurrentHashMap<Long, ConfigurableOsgiBundleApplicationContext>(16);
/** listener counter - used to properly synchronize shutdown */
private Counter contextsStarted = new Counter("contextsStarted");
// "Spring Application Context Creation Timer"
private final Timer timer = new Timer("Spring DM Context Creation Timer", true);
/** Task executor used for bootstraping the Spring contexts in async mode */
private final TaskExecutor taskExecutor;
/** ApplicationContext Creator */
private final OsgiApplicationContextCreator contextCreator;
/** BFPP list */
private final List<OsgiBeanFactoryPostProcessor> postProcessors;
/** shutdown task executor */
private final TaskExecutor shutdownTaskExecutor;
/**
* Task executor which uses the same thread for running tasks. Used when doing a synchronous wait-for-dependencies.
*/
private final TaskExecutor sameThreadTaskExecutor = new SyncTaskExecutor();
private final OsgiBundleApplicationContextEventMulticaster multicaster;
private final ExtenderConfiguration extenderConfiguration;
private final BundleContext bundleContext;
private final OsgiContextProcessor processor;
private final ApplicationContextConfigurationFactory contextConfigurationFactory;
private final VersionMatcher versionMatcher;
private final TypeCompatibilityChecker typeChecker;
LifecycleManager(ExtenderConfiguration extenderConfiguration, VersionMatcher versionMatcher,
ApplicationContextConfigurationFactory appCtxCfgFactory, OsgiApplicationContextCreator osgiApplicationContextCreator, OsgiContextProcessor processor,
TypeCompatibilityChecker checker, BundleContext context) {
this.versionMatcher = versionMatcher;
this.extenderConfiguration = extenderConfiguration;
this.contextConfigurationFactory = appCtxCfgFactory;
this.contextCreator = osgiApplicationContextCreator;
this.processor = processor;
this.taskExecutor = extenderConfiguration.getTaskExecutor();
this.shutdownTaskExecutor = extenderConfiguration.getShutdownTaskExecutor();
this.multicaster = extenderConfiguration.getEventMulticaster();
this.postProcessors = extenderConfiguration.getPostProcessors();
this.typeChecker = checker;
this.bundleContext = context;
}
/**
* Context creation is a potentially long-running activity (certainly more than we want to do on the synchronous
* event callback).
*
* <p/> Based on our configuration, the context can be started on the same thread or on a different one.
*
* <p/> Kick off a background activity to create an application context for the given bundle if needed.
*
* <b>Note:</b> Make sure to do the fastest filtering first to avoid slow-downs on platforms with a big number of
* plugins and wiring (i.e. Eclipse platform).
*
* @param bundle
*/
public void maybeCreateApplicationContextFor(Bundle bundle) {
boolean debug = log.isDebugEnabled();
String bundleString = "[" + OsgiStringUtils.nullSafeNameAndSymName(bundle) + "]";
final Long bundleId = bundle.getBundleId();
if (managedContexts.containsKey(bundleId)) {
if (debug) {
log.debug("Bundle " + bundleString + " is already managed; ignoring...");
}
return;
}
if (!versionMatcher.matchVersion(bundle)) {
return;
}
BundleContext localBundleContext = OsgiBundleUtils.getBundleContext(bundle);
if (localBundleContext == null) {
if (debug)
log.debug("Bundle " + bundleString + " has no bundle context; skipping...");
return;
}
// initialize context
final DelegatedExecutionOsgiBundleApplicationContext localApplicationContext;
if (debug)
log.debug("Inspecting bundle " + bundleString);
try {
localApplicationContext = contextCreator.createApplicationContext(localBundleContext);
} catch (Exception ex) {
log.error("Cannot create application context for bundle " + bundleString, ex);
return;
}
if (localApplicationContext == null) {
log.debug("No application context created for bundle " + bundleString);
return;
}
if (typeChecker != null) {
if (!typeChecker.isTypeCompatible(localBundleContext)) {
log.info("Bundle " + OsgiStringUtils.nullSafeName(bundle) + " is not type compatible with extender "
+ OsgiStringUtils.nullSafeName(bundleContext.getBundle()) + "; ignoring bundle...");
return;
}
}
log.debug("Bundle " + OsgiStringUtils.nullSafeName(bundle) + " is type compatible with extender "
+ OsgiStringUtils.nullSafeName(bundleContext.getBundle()) + "; processing bundle...");
// create a dedicated hook for this application context
BeanFactoryPostProcessor processingHook =
new OsgiBeanFactoryPostProcessorAdapter(localBundleContext, postProcessors);
// add in the post processors
localApplicationContext.addBeanFactoryPostProcessor(processingHook);
// add the context to the tracker
managedContexts.put(bundleId, localApplicationContext);
localApplicationContext.setDelegatedEventMulticaster(multicaster);
ApplicationContextConfiguration config = contextConfigurationFactory.createConfiguration(bundle);
final boolean asynch = config.isCreateAsynchronously();
// create refresh runnable
Runnable contextRefresh = new Runnable() {
public void run() {
// post refresh events are caught through events
if (log.isTraceEnabled()) {
log.trace("Calling pre-refresh on processor " + processor);
}
processor.preProcessRefresh(localApplicationContext);
localApplicationContext.refresh();
}
};
// executor used for creating the appCtx
// chosen based on the sync/async configuration
TaskExecutor executor = null;
String creationType;
// synch/asynch context creation
if (asynch) {
// for the async stuff use the executor
executor = taskExecutor;
creationType = "Asynchronous";
} else {
// for the sync stuff, use this thread
executor = sameThreadTaskExecutor;
creationType = "Synchronous";
}
if (debug) {
log.debug(creationType + " context creation for bundle " + bundleString);
}
// wait/no wait for dependencies behaviour
if (config.isWaitForDependencies()) {
DependencyWaiterApplicationContextExecutor appCtxExecutor =
new DependencyWaiterApplicationContextExecutor(localApplicationContext, !asynch,
extenderConfiguration.getDependencyFactories());
long timeout;
// check whether a timeout has been defined
if (config.isTimeoutDeclared()) {
timeout = config.getTimeout();
if (debug)
log.debug("Setting bundle-defined, wait-for-dependencies/graceperiod timeout value=" + timeout
+ " ms, for bundle " + bundleString);
} else {
timeout = extenderConfiguration.getDependencyWaitTime();
if (debug)
log.debug("Setting globally defined wait-for-dependencies/graceperiod timeout value=" + timeout
+ " ms, for bundle " + bundleString);
}
appCtxExecutor.setTimeout(timeout);
appCtxExecutor.setWatchdog(timer);
appCtxExecutor.setTaskExecutor(executor);
appCtxExecutor.setMonitoringCounter(contextsStarted);
// set events publisher
appCtxExecutor.setDelegatedMulticaster(this.multicaster);
contextsStarted.increment();
} else {
// do nothing; by default contexts do not wait for services.
}
executor.execute(contextRefresh);
}
/**
* Closing an application context is a potentially long-running activity, however, we *have* to do it synchronously
* during the event process as the BundleContext object is not valid once we return from this method.
*
* @param bundle
*/
protected void maybeCloseApplicationContextFor(Bundle bundle) {
final ConfigurableOsgiBundleApplicationContext context = managedContexts.remove(bundle.getBundleId());
if (context == null) {
return;
}
maybeClose(context);
}
protected void maybeClose(final ConfigurableOsgiBundleApplicationContext context) {
final String displayName = context.getDisplayName();
Runnable shutdownTask = new Runnable() {
private final String toString = "Closing runnable for context " + displayName;
public void run() {
closeApplicationContext(context);
}
public String toString() {
return toString;
}
};
if (extenderConfiguration.shouldShutdownAsynchronously()) {
execute(shutdownTask, extenderConfiguration.getShutdownWaitTime(), shutdownTaskExecutor);
} else {
try {
shutdownTask.run();
} catch (Exception e) {
log.error(displayName + " context shutdown failed.", e);
}
}
}
/**
* Closes an application context. This is a convenience methods that invokes the event notification as well.
*
* @param ctx
*/
private void closeApplicationContext(ConfigurableOsgiBundleApplicationContext ctx) {
if (log.isDebugEnabled()) {
log.debug("Closing application context " + ctx.getDisplayName());
}
if (log.isTraceEnabled()) {
log.trace("Calling pre-close on processor " + processor);
}
processor.preProcessClose(ctx);
try {
ctx.close();
} finally {
if (log.isTraceEnabled()) {
log.trace("Calling post close on processor " + processor);
}
processor.postProcessClose(ctx);
}
}
public void destroy() {
// first stop the watchdog
stopTimer();
// get hold of the needed bundles
List<Bundle> bundles = new ArrayList<Bundle>(managedContexts.size());
for (ConfigurableOsgiBundleApplicationContext context : managedContexts.values()) {
bundles.add(context.getBundle());
}
boolean debug = log.isDebugEnabled();
if (debug) {
log.debug("Starting shutdown procedure for bundles " + bundles);
}
while (!bundles.isEmpty()) {
Collection<Bundle> candidates = ShutdownSorter.getBundles(bundles);
if (debug)
log.debug("Staging shutdown for bundles " + candidates);
final List<Runnable> taskList = new ArrayList<Runnable>(candidates.size());
final List<ConfigurableOsgiBundleApplicationContext> closedContexts =
Collections.synchronizedList(new ArrayList<ConfigurableOsgiBundleApplicationContext>());
final Object[] contextClosingDown = new Object[1];
for (Bundle shutdownBundle : candidates) {
final ConfigurableOsgiBundleApplicationContext context = getManagedContext(shutdownBundle);
if (context != null) {
closedContexts.add(context);
// add a new runnable
taskList.add(new Runnable() {
private final String toString = "Closing runnable for context " + context.getDisplayName();
public void run() {
contextClosingDown[0] = context;
// eliminate context
closedContexts.remove(context);
closeApplicationContext(context);
}
public String toString() {
return toString;
}
});
}
}
// tasks
final Runnable[] tasks = taskList.toArray(new Runnable[taskList.size()]);
for (Runnable task : tasks) {
if (extenderConfiguration.shouldShutdownAsynchronously()) {
if (execute(task, extenderConfiguration.getShutdownWaitTime(),
shutdownTaskExecutor)) {
if (debug) {
log.debug(contextClosingDown[0] + " context did not close successfully; forcing shutdown...");
}
}
} else {
try {
task.run();
} catch (Exception e) {
log.error(contextClosingDown[0] + " context close failed.", e);
}
}
}
}
this.managedContexts.clear();
// before bailing out; wait for the threads that might be left by
// the task executor
stopTaskExecutor();
}
public ConfigurableOsgiBundleApplicationContext getManagedContext(Bundle bundle) {
ConfigurableOsgiBundleApplicationContext context = null;
try {
Long id = new Long(bundle.getBundleId());
context = (ConfigurableOsgiBundleApplicationContext) managedContexts.get(id);
} catch (IllegalStateException _) {
// ignore
}
return context;
}
/**
* Do some additional waiting so the service dependency listeners detect the shutdown.
*/
private void stopTaskExecutor() {
boolean debug = log.isDebugEnabled();
if (debug)
log.debug("Waiting for " + contextsStarted + " service dependency listener(s) to stop...");
contextsStarted.waitForZero(extenderConfiguration.getShutdownWaitTime());
if (!contextsStarted.isZero()) {
if (debug)
log.debug(contextsStarted.getValue()
+ " service dependency listener(s) did not responded in time; forcing them to shutdown...");
extenderConfiguration.setForceThreadShutdown(true);
}
else
log.debug("All listeners closed");
}
/**
* Cancel any tasks scheduled for the timer.
*/
private void stopTimer() {
if (log.isDebugEnabled())
log.debug("Canceling timer tasks");
timer.cancel();
}
}