/******************************************************************************
* Copyright (c) 2006, 2010 VMware Inc., Oracle 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.
* Oracle Inc.
*****************************************************************************/
package org.eclipse.gemini.blueprint.extender.internal.dependencies.startup;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Iterator;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.Bundle;
import org.osgi.framework.Filter;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.task.TaskExecutor;
import org.eclipse.gemini.blueprint.context.DelegatedExecutionOsgiBundleApplicationContext;
import org.eclipse.gemini.blueprint.context.OsgiBundleApplicationContextExecutor;
import org.eclipse.gemini.blueprint.context.event.OsgiBundleApplicationContextEventMulticaster;
import org.eclipse.gemini.blueprint.context.event.OsgiBundleContextFailedEvent;
import org.eclipse.gemini.blueprint.extender.OsgiServiceDependencyFactory;
import org.eclipse.gemini.blueprint.extender.event.BootstrappingDependenciesFailedEvent;
import org.eclipse.gemini.blueprint.extender.internal.util.concurrent.Counter;
import org.eclipse.gemini.blueprint.service.importer.event.OsgiServiceDependencyEvent;
import org.eclipse.gemini.blueprint.util.OsgiFilterUtils;
import org.eclipse.gemini.blueprint.util.OsgiStringUtils;
import org.springframework.util.Assert;
/**
* Dependency waiter executor that breaks the 'traditional' {@link ConfigurableApplicationContext#refresh()} in two
* pieces so that beans are not actually created unless the OSGi service imported are present.
*
* <p/> <p/> <p/> Supports both asynch and synch behaviour.
*
* @author Hal Hildebrand
* @author Costin Leau
*/
public class DependencyWaiterApplicationContextExecutor implements OsgiBundleApplicationContextExecutor,
ContextExecutorAccessor {
private static final Log log = LogFactory.getLog(DependencyWaiterApplicationContextExecutor.class);
/**
* this class monitor. Since multiple threads will access this object, we have to use synchronization to guarantee
* thread visibility
*/
private final Object monitor = new Object();
/** waiting timeout */
private long timeout;
/** the timer used for executing the timeout */
// NOTE: the dog is not managed by this application so do not cancel it
private Timer watchdog;
/** watchdog task */
private TimerTask watchdogTask;
/** OSGi service dependencyDetector used for detecting dependencies */
protected DependencyServiceManager dependencyDetector;
protected final DelegatedExecutionOsgiBundleApplicationContext delegateContext;
/** State of the associated context from the executor POV. */
private ContextState state = ContextState.INITIALIZED;
private TaskExecutor taskExecutor;
/**
* A synchronized counter used by the Listener to determine the number of children to wait for when shutting down.
*/
private Counter monitorCounter;
/** Should the waiting be synchrous or not ? */
private final boolean synchronousWait;
/** Counter used when waiting for dependencies to appear */
private final Counter waitBarrier = new Counter("syncCounterWait");
/** delegated multicaster */
private OsgiBundleApplicationContextEventMulticaster delegatedMulticaster;
private List<OsgiServiceDependencyFactory> dependencyFactories;
/**
* The task for the watch dog.
*
* @author Hal Hildebrand
*/
private class WatchDogTask extends TimerTask {
public void run() {
timeout();
}
}
/**
* Create the Runnable action which will complete the context creation process. This process can be called
* synchronously or asynchronously, depending on context configuration and availability of dependencies.
*
* @author Hal Hildebrand
* @author Costin Leau
*/
private class CompleteRefreshTask implements Runnable {
public void run() {
boolean debug = log.isDebugEnabled();
if (debug) {
log.debug("Completing refresh for " + getDisplayName());
}
synchronized (monitor) {
if (state != ContextState.DEPENDENCIES_RESOLVED) {
logWrongState(ContextState.DEPENDENCIES_RESOLVED);
return;
}
}
// Continue with the refresh process...
try {
delegateContext.completeRefresh();
} catch (Throwable th) {
fail(th, true);
}
// Once we are done, tell the world
synchronized (monitor) {
// Close might have been called in the meantime
if (state != ContextState.DEPENDENCIES_RESOLVED) {
return;
}
state = ContextState.STARTED;
}
}
}
public DependencyWaiterApplicationContextExecutor(DelegatedExecutionOsgiBundleApplicationContext delegateContext,
boolean syncWait, List<OsgiServiceDependencyFactory> dependencyFactories) {
this.delegateContext = delegateContext;
this.delegateContext.setExecutor(this);
this.synchronousWait = syncWait;
this.dependencyFactories = dependencyFactories;
synchronized (monitor) {
watchdogTask = new WatchDogTask();
}
}
/**
* Provide a continuation like approach to the application context. Will execute just some parts of refresh and then
* leave the rest of to be executed after a number of conditions have been met.
*/
public void refresh() throws BeansException, IllegalStateException {
if (log.isDebugEnabled())
log.debug("Starting first stage of refresh for " + getDisplayName());
// sanity check
init();
// start the first stage
stageOne();
}
/**
* Do some sanity checks
*/
protected void init() {
synchronized (monitor) {
Assert.notNull(watchdog, "watchdog timer required");
Assert.notNull(monitorCounter, " monitorCounter required");
if (state != ContextState.INTERRUPTED && state != ContextState.STOPPED)
state = ContextState.INITIALIZED;
else {
RuntimeException ex = new IllegalStateException("cannot refresh an interrupted/closed context");
log.fatal(ex);
throw ex;
}
}
}
/**
* Start the first stage of the application context refresh. Determines the service dependencies and if there are
* any, registers a OSGi service dependencyDetector which will continue the refresh process asynchronously. <p/>
* Based on the {@link #synchronousWait}, the current thread can simply end if there are any dependencies (the
* default) or wait to either timeout or have all its dependencies met.
*/
protected void stageOne() {
boolean debug = log.isDebugEnabled();
boolean skipExceptionEvent = true;
try {
if (debug)
log.debug("Calling preRefresh on " + getDisplayName());
synchronized (monitor) {
// check before kicking the pedal
if (state != ContextState.INITIALIZED) {
logWrongState(ContextState.INITIALIZED);
return;
}
state = ContextState.RESOLVING_DEPENDENCIES;
}
delegateContext.startRefresh();
if (debug)
log.debug("Pre-refresh completed; determining dependencies...");
Runnable task = null;
if (synchronousWait) {
task = new Runnable() {
public void run() {
// inform the waiting thread through the counter
waitBarrier.decrement();
}
};
} else
task = new Runnable() {
public void run() {
// no waiting involved, just call stageTwo
stageTwo();
}
};
skipExceptionEvent = false;
DependencyServiceManager dl = createDependencyServiceListener(task);
dl.findServiceDependencies();
skipExceptionEvent = true;
// all dependencies are met, just go with stageTwo
if (dl.isSatisfied()) {
log.info("No outstanding OSGi service dependencies, completing initialization for " + getDisplayName());
stageTwo();
} else {
// there are dependencies not met
// register a listener to look for them
synchronized (monitor) {
dependencyDetector = dl;
}
if (debug)
log.debug("Registering service dependency dependencyDetector for " + getDisplayName());
dependencyDetector.register();
if (synchronousWait) {
waitBarrier.increment();
if (debug)
log.debug("Synchronous wait-for-dependencies; waiting...");
// if waiting times out...
if (waitBarrier.waitForZero(timeout)) {
timeout();
} else
stageTwo();
} else {
// start the watchdog (we're asynch)
startWatchDog();
}
}
} catch (Throwable e) {
fail(e, skipExceptionEvent);
}
}
protected void stageTwo() {
boolean debug = log.isDebugEnabled();
if (debug)
log.debug("Starting stage two for " + getDisplayName());
synchronized (monitor) {
if (state != ContextState.RESOLVING_DEPENDENCIES) {
logWrongState(ContextState.RESOLVING_DEPENDENCIES);
return;
}
stopWatchDog();
state = ContextState.DEPENDENCIES_RESOLVED;
}
// always delegate to the taskExecutor since we might be called by the
// OSGi platform listener
taskExecutor.execute(new CompleteRefreshTask());
}
/**
* The application context is being shutdown. Deregister the listener and prevent classes from being loaded since
* it's Doom's day.
*/
public void close() {
boolean debug = log.isDebugEnabled();
boolean normalShutdown = false;
stopWatchDog();
synchronized (monitor) {
// no need for cleanup
if (state.isDown()) {
return;
}
if (debug) {
log.debug("Closing appCtx for " + getDisplayName());
}
// It's possible for the delegateContext to already be in startRefresh() or completeRefresh().
// If this is the case then its important to wait for these tasks to complete and then close normally
// If we simply exit then the bundle may suddenly become invalid under our feet, e.g. if this
// was triggered by a Bundle update or uninstall.
// Context is in stageOne(), wait until stageOne() is complete
// and destroy singletons
if (state == ContextState.RESOLVING_DEPENDENCIES) {
if (debug)
log.debug("Cleaning up appCtx " + getDisplayName());
if (delegateContext.isActive()) {
try {
delegateContext.getBeanFactory().destroySingletons();
} catch (Exception ex) {
log.trace("Caught exception while interrupting context refresh ", ex);
}
state = ContextState.INTERRUPTED;
}
}
// Context is in stageTwo(), wait until stageTwo() is complete and
// close normally.
else if (state == ContextState.DEPENDENCIES_RESOLVED) {
if (debug)
log.debug("Shutting down appCtx " + getDisplayName() + " once stageTwo() is complete");
state = ContextState.STOPPED;
normalShutdown = true;
}
// Context is running, shut it down
else if (state == ContextState.STARTED) {
if (debug)
log.debug("Shutting down normally appCtx " + getDisplayName());
state = ContextState.STOPPED;
normalShutdown = true;
}
// Something else going on
else {
if (debug)
log.debug("No need to stop context (it hasn't been started yet)");
state = ContextState.INTERRUPTED;
}
// Clean up the detector
if (dependencyDetector != null) {
dependencyDetector.deregister();
}
}
try {
if (normalShutdown) {
delegateContext.normalClose();
}
} catch (Exception ex) {
log.fatal("Could not succesfully close context " + delegateContext, ex);
} finally {
monitorCounter.decrement();
}
}
public void fail(Throwable t) {
fail(t, false);
}
/**
* Fail creating the context. Figure out unsatisfied dependencies and provide a very nice log message before closing
* the appContext.
*
* <p/> Normally this method is called when an exception is caught.
*
* @param t - the offending Throwable which caused our demise
*/
private void fail(Throwable t, boolean skipEvent) {
// this will not thrown any exceptions (it just logs them)
close();
StringBuilder buf = new StringBuilder();
synchronized (monitor) {
if (dependencyDetector == null || dependencyDetector.getUnsatisfiedDependencies().isEmpty()) {
buf.append("none");
} else {
for (Iterator<MandatoryServiceDependency> iterator =
dependencyDetector.getUnsatisfiedDependencies().keySet().iterator(); iterator.hasNext();) {
MandatoryServiceDependency dependency = iterator.next();
buf.append(dependency.toString());
if (iterator.hasNext()) {
buf.append(", ");
}
}
}
}
final StringBuilder message = new StringBuilder();
message.append("Unable to create application context for [");
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
message.append(OsgiStringUtils.nullSafeSymbolicName(getBundle()));
return null;
}
});
} else {
message.append(OsgiStringUtils.nullSafeSymbolicName(getBundle()));
}
message.append("], unsatisfied dependencies: ");
message.append(buf.toString());
log.error(message.toString(), t);
// send notification
if (!skipEvent) {
delegatedMulticaster.multicastEvent(new OsgiBundleContextFailedEvent(delegateContext, delegateContext
.getBundle(), t));
}
}
/**
* Cancel waiting due to timeout.
*/
private void timeout() {
ApplicationContextException e;
List<OsgiServiceDependencyEvent> events = null;
String filterAsString = null;
synchronized (monitor) {
// deregister listener to get an accurate snapshot of the
// unsatisfied dependencies.
if (dependencyDetector != null) {
dependencyDetector.deregister();
events = dependencyDetector.getUnsatisfiedDependenciesAsEvents();
filterAsString = dependencyDetector.createUnsatisfiedDependencyFilter();
}
}
Filter filter = (filterAsString != null ? OsgiFilterUtils.createFilter(filterAsString) : null);
log.warn("Timeout occurred before finding service dependencies for [" + delegateContext.getDisplayName() + "]");
String bundleName = null;
if (System.getSecurityManager() != null) {
bundleName = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return OsgiStringUtils.nullSafeSymbolicName(getBundle());
}
});
} else {
bundleName = OsgiStringUtils.nullSafeSymbolicName(getBundle());
}
// generate exception
e =
new ApplicationContextException("Application context " + "initialization for '" + bundleName
+ "' has timed out waiting for " + filterAsString);
e.fillInStackTrace();
// send notification
delegatedMulticaster.multicastEvent(new BootstrappingDependenciesFailedEvent(delegateContext, delegateContext
.getBundle(), e, events, filter));
fail(e, true);
}
protected DependencyServiceManager createDependencyServiceListener(Runnable task) {
return new DependencyServiceManager(this, delegateContext, dependencyFactories, task, timeout);
}
/**
* Schedule the watchdog task.
*/
protected void startWatchDog() {
boolean started = false;
synchronized (monitor) {
if (watchdogTask != null) {
started = true;
watchdog.schedule(watchdogTask, timeout);
}
}
boolean debug = log.isDebugEnabled();
if (debug) {
if (started)
log.debug("Asynch wait-for-dependencies started...");
else
log.debug("Dependencies satisfied; no need to start a watchdog...");
}
}
protected void stopWatchDog() {
boolean stopped = false;
synchronized (monitor) {
if (watchdogTask != null) {
watchdogTask.cancel();
watchdogTask = null;
stopped = true;
}
}
if (stopped && log.isDebugEnabled()) {
log.debug("Cancelled dependency watchdog...");
}
}
/**
* Sets the timeout (in ms) for waiting for service dependencies.
*
* @param timeout
*/
public void setTimeout(long timeout) {
synchronized (monitor) {
this.timeout = timeout;
}
}
public void setTaskExecutor(TaskExecutor taskExec) {
synchronized (monitor) {
this.taskExecutor = taskExec;
}
}
private Bundle getBundle() {
synchronized (monitor) {
return delegateContext.getBundle();
}
}
private String getDisplayName() {
synchronized (monitor) {
return delegateContext.getDisplayName();
}
}
public void setWatchdog(Timer watchdog) {
synchronized (monitor) {
this.watchdog = watchdog;
}
}
/**
* Reduce the code pollution.
*
* @param expected the expected value for the context state.
*/
private void logWrongState(ContextState expected) {
log.error("Expecting state (" + expected + ") not (" + state + ") for context [" + getDisplayName()
+ "]; assuming an interruption and bailing out");
}
/**
* Pass in the context counter. Used by the listener to track the number of contexts started.
*
* @param asynchCounter
*/
public void setMonitoringCounter(Counter contextsStarted) {
this.monitorCounter = contextsStarted;
}
/**
* Sets the multicaster for delegating failing events.
*
* @param multicaster
*/
public void setDelegatedMulticaster(OsgiBundleApplicationContextEventMulticaster multicaster) {
this.delegatedMulticaster = multicaster;
}
//
// accessor interface implementations
//
public ContextState getContextState() {
synchronized (monitor) {
return state;
}
}
public OsgiBundleApplicationContextEventMulticaster getEventMulticaster() {
return this.delegatedMulticaster;
}
}