Package org.springframework.osgi.extender.internal.dependencies.startup

Source Code of org.springframework.osgi.extender.internal.dependencies.startup.DependencyWaiterApplicationContextExecutor

/*
* Copyright 2006-2008 the original author or authors.
*
* 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.springframework.osgi.extender.internal.dependencies.startup;

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.springframework.beans.BeansException;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.task.TaskExecutor;
import org.springframework.osgi.context.DelegatedExecutionOsgiBundleApplicationContext;
import org.springframework.osgi.context.OsgiBundleApplicationContextExecutor;
import org.springframework.osgi.context.event.OsgiBundleApplicationContextEventMulticaster;
import org.springframework.osgi.context.event.OsgiBundleContextFailedEvent;
import org.springframework.osgi.extender.internal.util.concurrent.Counter;
import org.springframework.osgi.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/>
*
* Supports both asynch and synch behaviour.
*
* @author Hal Hildebrand
* @author Costin Leau
*/
public class DependencyWaiterApplicationContextExecutor implements OsgiBundleApplicationContextExecutor,
    ContextExecutorStateAccessor {

  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 hold the thread 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 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;
        }
        // otherwise update the state
        state = ContextState.STARTED;
      }

      // Continue with the refresh process...
      synchronized (delegateContext.getMonitor()) {
        delegateContext.completeRefresh();
      }
    }
  }


  public DependencyWaiterApplicationContextExecutor(DelegatedExecutionOsgiBundleApplicationContext delegateContext,
      boolean syncWait, List dependencyFactories) {
    this.delegateContext = delegateContext;
    this.delegateContext.setExecutor(this);
    this.synchronousWait = syncWait;
    this.dependencyFactories = dependencyFactories;

  }

  /**
   * 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");
      watchdogTask = new WatchDogTask();

      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.
   *
   * 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();

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

      synchronized (delegateContext.getMonitor()) {
        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();
          }
        };

      DependencyServiceManager dl = createDependencyServiceListener(task);
      dl.findServiceDependencies();

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

  }

  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;

    synchronized (monitor) {

      // no need for cleanup
      if (state.isDown()) {
        return;
      }

      if (debug)
        log.debug("Closing appCtx for " + getDisplayName());

      if (dependencyDetector != null) {
        dependencyDetector.deregister();
      }

      if (state == ContextState.STARTED) {
        if (debug)
          log.debug("Shutting down normaly appCtx " + getDisplayName());
        // close the context only if it was actually started
        state = ContextState.STOPPED;
        normalShutdown = true;
      }
      else {
        if (debug)
          log.debug("No need to stop context (it hasn't been started yet)");
        state = ContextState.INTERRUPTED;
      }
    }
    try {
      if (normalShutdown) {
        synchronized (delegateContext.getMonitor()) {
          delegateContext.normalClose();
        }
      }
    }
    catch (Exception ex) {
      log.fatal("Could not succesfully close context " + delegateContext, ex);
    }
    finally {
      monitorCounter.decrement();
    }

  }

  /**
   * Fail creating the context. Figure out unsatisfied dependencies and
   * provide a very nice log message before closing the appContext.
   *
   * Normally this method is called when an exception is caught.
   *
   * @param t - the offending Throwable which caused our demise
   */
  private void fail(Throwable t) {

    // this will not thrown any exceptions (it just logs them)
    close();

    StringBuffer buf = new StringBuffer();
    synchronized (monitor) {
      if (dependencyDetector == null || dependencyDetector.getUnsatisfiedDependencies().isEmpty()) {
        buf.append("none");
      }
      else {
        for (Iterator dependencies = dependencyDetector.getUnsatisfiedDependencies().keySet().iterator(); dependencies.hasNext();) {
          MandatoryServiceDependency dependency = (MandatoryServiceDependency) dependencies.next();
          buf.append(dependency.toString());
          if (dependencies.hasNext()) {
            buf.append(", ");
          }
        }
      }
    }

    StringBuffer message = new StringBuffer();
    message.append("Unable to create application context for [");
    message.append(getBundleSymbolicName());
    message.append("], unsatisfied dependencies: ");
    message.append(buf.toString());

    log.error(message.toString(), t);

    // send notification
    delegatedMulticaster.multicastEvent(new OsgiBundleContextFailedEvent(delegateContext,
      delegateContext.getBundle(), t));

    // rethrow the exception wrapped to the caller (and prevent bundles
    // started in sync mode to complete).
    // throw new ApplicationContextException("cannot refresh context", t);
  }

  /**
   * Cancel waiting due to timeout.
   */
  private void timeout() {
    synchronized (monitor) {
      // deregister listener to get an accurate snapshot of the
      // unsatisfied dependencies.

      if (dependencyDetector != null) {
        dependencyDetector.deregister();
      }

      log.warn("Timeout occured before finding service dependencies for [" + delegateContext.getDisplayName()
          + "]");

      ApplicationContextException e = new ApplicationContextException("Application context initializition for '"
          + OsgiStringUtils.nullSafeSymbolicName(getBundle()) + "' has timed out");
      e.fillInStackTrace();
      fail(e);

    }
  }

  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 satified; 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();
    }

  }

  private String getBundleSymbolicName() {
    return OsgiStringUtils.nullSafeSymbolicName(getBundle());
  }

  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;
  }
}
TOP

Related Classes of org.springframework.osgi.extender.internal.dependencies.startup.DependencyWaiterApplicationContextExecutor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.