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