Package com.opengamma.engine.view.impl

Source Code of com.opengamma.engine.view.impl.ViewProcessImpl

/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.engine.view.impl;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.Lifecycle;
import org.threeten.bp.Instant;

import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.core.change.ChangeEvent;
import com.opengamma.core.change.ChangeListener;
import com.opengamma.engine.management.InternalViewResultListener;
import com.opengamma.engine.marketdata.MarketDataInjector;
import com.opengamma.engine.marketdata.MarketDataPermissionProvider;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.engine.view.ViewComputationResultModel;
import com.opengamma.engine.view.ViewDefinition;
import com.opengamma.engine.view.ViewDeltaResultModel;
import com.opengamma.engine.view.ViewProcess;
import com.opengamma.engine.view.ViewProcessState;
import com.opengamma.engine.view.client.ViewDeltaResultCalculator;
import com.opengamma.engine.view.client.ViewResultMode;
import com.opengamma.engine.view.compilation.CompiledViewDefinitionWithGraphs;
import com.opengamma.engine.view.cycle.ViewCycle;
import com.opengamma.engine.view.cycle.ViewCycleMetadata;
import com.opengamma.engine.view.execution.ViewCycleExecutionOptions;
import com.opengamma.engine.view.execution.ViewExecutionOptions;
import com.opengamma.engine.view.listener.ViewResultListener;
import com.opengamma.engine.view.permission.ViewPermissionContext;
import com.opengamma.engine.view.permission.ViewPermissionProvider;
import com.opengamma.engine.view.worker.ViewExecutionDataProvider;
import com.opengamma.engine.view.worker.ViewProcessWorker;
import com.opengamma.engine.view.worker.ViewProcessWorkerContext;
import com.opengamma.id.ObjectId;
import com.opengamma.id.UniqueId;
import com.opengamma.livedata.UserPrincipal;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.tuple.Pair;

/**
* Default implementation of {@link ViewProcess}.
*/
public class ViewProcessImpl implements ViewProcessInternal, Lifecycle, ViewProcessWorkerContext {

  private static final Logger s_logger = LoggerFactory.getLogger(ViewProcess.class);

  private final UniqueId _viewDefinitionId;
  private final ViewExecutionOptions _executionOptions;
  private final ViewProcessContext _viewProcessContext;
  private final ViewProcessorImpl _viewProcessor;

  /**
   * Manages access to critical regions of the process. Note that the use of {@link Semaphore} rather than, for example, {@link ReentrantLock} allows one thread to acquire the lock and another thread
   * to release it as part of the same sequence of operations. This could be important for {@link #suspend()} and {@link #resume()}.
   */
  private final Semaphore _processLock = new Semaphore(1);

  /**
   * Key is the listener to which events will be dispatched.
   * Value is true iff that listener requires delta calculations to be performed.
   * When there are no listeners remaining that require delta calculations,
   * they will stop being computed to save CPU and heap.
   */
  private final Map<ViewResultListener, Boolean> _listeners = new HashMap<ViewResultListener, Boolean>();
  private volatile int _internalListenerCount; // only safe if used within lock

  private volatile ViewDefinition _currentViewDefinition;

  private volatile ViewProcessState _state = ViewProcessState.STOPPED;

  private volatile ViewProcessWorker _worker;

  private final AtomicReference<Pair<CompiledViewDefinitionWithGraphs, MarketDataPermissionProvider>> _latestCompiledViewDefinition =
      new AtomicReference<Pair<CompiledViewDefinitionWithGraphs, MarketDataPermissionProvider>>();

  private final AtomicReference<ViewComputationResultModel> _latestResult = new AtomicReference<ViewComputationResultModel>();
 
  private final AtomicBoolean _mustCalculateDeltas = new AtomicBoolean(false);

  private final ChangeListener _viewDefinitionChangeListener;

  // BEGIN TEMPORARY -- See ViewProcessorImpl

  private volatile Object _description;

  public Object getDescriptionKey() {
    return _description;
  }

  public void setDescriptionKey(final Object description) {
    _description = description;
  }

  // END TEMPORARY CODE

  /**
   * Constructs an instance.
   *
   * @param viewDefinitionId the name of the view definition, not null
   * @param executionOptions the view execution options, not null
   * @param viewProcessContext the process context, not null
   * @param viewProcessor the parent view processor, not null
   */
  public ViewProcessImpl(final UniqueId viewDefinitionId, final ViewExecutionOptions executionOptions,
      final ViewProcessContext viewProcessContext, final ViewProcessorImpl viewProcessor) {
    ArgumentChecker.notNull(viewDefinitionId, "viewDefinitionId");
    ArgumentChecker.notNull(executionOptions, "executionOptions");
    ArgumentChecker.notNull(viewProcessContext, "viewProcessContext");
    ArgumentChecker.notNull(viewProcessor, "viewProcessor");
    _viewDefinitionId = viewDefinitionId;
    _executionOptions = executionOptions;
    _viewProcessContext = viewProcessContext;
    _viewProcessor = viewProcessor;
    if (_viewDefinitionId.isVersioned()) {
      _viewDefinitionChangeListener = null;
    } else {
      final ObjectId viewDefinitionObject = viewDefinitionId.getObjectId();
      _viewDefinitionChangeListener = new ChangeListener() {
        @SuppressWarnings("incomplete-switch")
        @Override
        public void entityChanged(ChangeEvent event) {
          if (viewDefinitionObject.equals(event.getObjectId())) {
            switch (event.getType()) {
              case REMOVED:
                s_logger.error("Shutting down view process after removal of view definition {}", viewDefinitionObject);
                shutdown();
                break;
              case CHANGED:
                viewDefinitionChanged();
                break;
            }
          }
        }
      };
    }
  }

  //-------------------------------------------------------------------------
  @Override
  public UniqueId getUniqueId() {
    return getProcessContext().getProcessId();
  }

  @Override
  public UniqueId getDefinitionId() {
    return _viewDefinitionId;
  }

  @Override
  public ViewDefinition getLatestViewDefinition() {
    if (_currentViewDefinition == null) {
      _currentViewDefinition = getProcessContext().getConfigSource().getConfig(ViewDefinition.class, getDefinitionId());
    }
    return _currentViewDefinition;
  }

  /**
   * Forces the dependency graph to be rebuilt. Invoked when a market data provider becomes available and failed
   * subscriptions need to be retried.
   * @deprecated There should be a better way to do this in the market data layer but PLAT-3908 is a problem.
   * This method will be removed once it's fixed
   */
  @Deprecated
  public void forceGraphRebuild() {
    ViewProcessWorker worker = getWorker();
    if (worker != null) {
      worker.forceGraphRebuild();
    }
  }

  private void viewDefinitionChanged() {
    final ViewDefinition viewDefinition = getProcessContext().getConfigSource().getConfig(ViewDefinition.class, getDefinitionId());
    _currentViewDefinition = viewDefinition;
    ViewProcessWorker worker = getWorker();
    if (worker != null) {
      worker.updateViewDefinition(viewDefinition);
    }
  }

  @Override
  public MarketDataInjector getLiveDataOverrideInjector() {
    return getProcessContext().getLiveDataOverrideInjector();
  }

  @Override
  public ViewProcessState getState() {
    return _state;
  }
 
  public ViewResultListener[] getListenerArray() {
    return _listeners.keySet().toArray(new ViewResultListener[_listeners.size()]);
  }

  @Override
  public void shutdown() {
    if (getState() == ViewProcessState.TERMINATED) {
      return;
    }
    // Must go through the view processor to prevent a client being attached to the terminated view process
    _viewProcessor.shutdownViewProcess(getUniqueId());
  }

  protected void shutdownCore() {
    // Caller MUST NOT hold the semaphore
    final boolean isInterrupting;
    final ViewResultListener[] listeners;
    lock();
    try {
      isInterrupting = (getState() == ViewProcessState.RUNNING);
      setState(ViewProcessState.TERMINATED);
      listeners = getListenerArray();
      _listeners.clear();
      terminateComputationJob();
    } finally {
      unlock();
    }
    // [PLAT-1158] The notifications are performed outside of holding the lock which avoids the deadlock problem, but we'll still block
    // for completion which was the thing PLAT-1158 was trying to avoid. This is because the contracts for the order in which
    // notifications can be received is unclear and I don't want to risk introducing a change at this moment in time.
    for (final ViewResultListener listener : listeners) {
      try {
        listener.processTerminated(isInterrupting);
      } catch (final Exception e) {
        logListenerError(listener, e);
      }
    }
  }

  public void triggerCycle() {
    final ViewProcessWorker worker = getWorker();
    if (worker != null) {
      worker.triggerCycle();
    }
  }

  //-------------------------------------------------------------------------
  // Lifecycle
  //-------------------------------------------------------------------------

  @Override
  public void start() {
    // Lifecycle method - nothing to start
  }

  @Override
  public void stop() {
    shutdown();
  }

  @Override
  public boolean isRunning() {
    // This is 'running' from a Lifecycle perspective, not whether the job is running.
    return getState() != ViewProcessState.TERMINATED;
  }

  @Override
  public void suspend() {
    // Caller MUST NOT hold the semaphore
    s_logger.info("Suspending view process {}", getUniqueId());
    lock();
    final ViewProcessWorker worker = getWorker();
    if (worker != null) {
      s_logger.debug("Suspending calculation job");
      setWorker(null);
      worker.terminate();
      try {
        s_logger.debug("Waiting for calculation thread(s) to finish");
        worker.join();
      } catch (final InterruptedException e) {
        s_logger.warn("Interrupted waiting for calculation thread(s)");
        throw new OpenGammaRuntimeException("Couldn't suspend view process", e);
      }
    }
    s_logger.info("View process {} suspended", getUniqueId());
  }

  @Override
  public void resume() {
    // Caller MUST still hold the semaphore (from the previous call to suspend)
    s_logger.info("Resuming view process {}", getUniqueId());
    if (getState() == ViewProcessState.RUNNING) {
      s_logger.info("Restarting computation job for view process {}", getUniqueId());
      startComputationJobImpl();
      s_logger.info("Restarted computation job for view process {}", getUniqueId());
    }
    unlock();
  }

  //-------------------------------------------------------------------------
  @Override
  public String toString() {
    return "ViewProcess[" + getUniqueId() + " on " + getDefinitionId() + "]";
  }

  //-------------------------------------------------------------------------
  /**
   * Gets the execution log mode source.
   *
   * @return the execution log mode source, not null
   */
  public ExecutionLogModeSource getExecutionLogModeSource() {
    return getProcessContext().getExecutionLogModeSource();
  }

  @Override
  public void viewDefinitionCompiled(final ViewExecutionDataProvider dataProvider, final CompiledViewDefinitionWithGraphs compiledViewDefinition) {
    // Caller MUST NOT hold the semaphore
    final ViewResultListener[] listeners;
    final MarketDataPermissionProvider permissionProvider = dataProvider.getPermissionProvider();
    lock();
    try {
      _latestCompiledViewDefinition.set(Pair.of(compiledViewDefinition, permissionProvider));
      listeners = getListenerArray();
    } finally {
      unlock();
    }
    final Set<ValueSpecification> marketData = compiledViewDefinition.getMarketDataRequirements();
    // [PLAT-1158] The notifications are performed outside of holding the lock which avoids the deadlock problem, but we'll still block
    // for completion which was the thing PLAT-1158 was trying to avoid. This is because the contracts for the order in which
    // notifications can be received is unclear and I don't want to risk introducing a change at this moment in time.
    for (final ViewResultListener listener : listeners) {
      try {
        final UserPrincipal listenerUser = listener.getUser();
        final boolean hasMarketDataPermissions = permissionProvider.checkMarketDataPermissions(listenerUser, marketData).isEmpty();
        listener.viewDefinitionCompiled(compiledViewDefinition, hasMarketDataPermissions);
      } catch (final Exception e) {
        logListenerError(listener, e);
      }
    }
    getExecutionLogModeSource().viewDefinitionCompiled(compiledViewDefinition);
  }

  @Override
  public void viewDefinitionCompilationFailed(final Instant valuationTime, final Exception exception) {
    // Caller MUST NOT hold the semaphore
    s_logger.error("View definition compilation failed for " + valuationTime + ": ", exception);
    final ViewResultListener[] listeners;
    lock();
    try {
      listeners = getListenerArray();
    } finally {
      unlock();
    }
    // [PLAT-1158] The notifications are performed outside of holding the lock which avoids the deadlock problem, but we'll still block
    // for completion which was the thing PLAT-1158 was trying to avoid. This is because the contracts for the order in which
    // notifications can be received is unclear and I don't want to risk introducing a change at this moment in time.
    for (final ViewResultListener listener : listeners) {
      try {
        listener.viewDefinitionCompilationFailed(valuationTime, exception);
      } catch (final Exception e) {
        logListenerError(listener, e);
      }
    }
  }

  @Override
  public void cycleCompleted(final ViewCycle cycle) {
    // Caller MUST NOT hold the semaphore
    s_logger.debug("View cycle {} completed on view process {}", cycle.getUniqueId(), getUniqueId());
    final ViewComputationResultModel result;
    ViewDeltaResultModel deltaResult = null;
    final ViewResultListener[] listeners;
    lock();
    try {
      result = cycle.getResultModel();
      if (_mustCalculateDeltas.get()) {
        // We swap these first so that in the callback the process is consistent.
        final ViewComputationResultModel previousResult = _latestResult.getAndSet(result);
        // [PLAT-1158] Is the cost of computing the delta going to be high; should we offload that to a slave thread before dispatching to the listeners?
        deltaResult = ViewDeltaResultCalculator.computeDeltaModel(cycle.getCompiledViewDefinition().getViewDefinition(), previousResult, result);
      }
      listeners = getListenerArray();
    } finally {
      unlock();
    }
    // [PLAT-1158] The notifications are performed outside of holding the lock which avoids the deadlock problem, but we'll still block
    // for completion which was the thing PLAT-1158 was trying to avoid. This is because the contracts for the order in which
    // notifications can be received is unclear and I don't want to risk introducing a change at this moment in time.
    for (final ViewResultListener listener : listeners) {
      try {
        listener.cycleCompleted(result, deltaResult);
      } catch (final Exception e) {
        logListenerError(listener, e);
      }
    }
  }

  @Override
  public void cycleStarted(final ViewCycleMetadata cycleInfo) {
    // Caller MUST NOT hold the semaphore
    s_logger.debug("View cycle {} initiated on view process {}", cycleInfo, getUniqueId());
    final ViewResultListener[] listeners;
    lock();
    try {
      listeners = getListenerArray();
    } finally {
      unlock();
    }
    // [PLAT-1158] The notifications are performed outside of holding the lock which avoids the deadlock problem, but we'll still block
    // for completion which was the thing PLAT-1158 was trying to avoid. This is because the contracts for the order in which
    // notifications can be received is unclear and I don't want to risk introducing a change at this moment in time.
    for (final ViewResultListener listener : listeners) {
      try {
        listener.cycleStarted(cycleInfo);
      } catch (final Exception e) {
        logListenerError(listener, e);
      }
    }
  }

  @Override
  public void cycleFragmentCompleted(final ViewComputationResultModel fullFragment, ViewDefinition viewDefinition) {
    // Caller MUST NOT hold the semaphore
    s_logger.debug("Result fragment from cycle {} received on view process {}", fullFragment.getViewCycleId(), getUniqueId());
    final ViewDeltaResultModel deltaFragment;
    final ViewResultListener[] listeners;
    lock();
    try {
      // [PLAT-1158] Is the cost of computing the delta going to be high; should we offload that to a slave thread before dispatching to the listeners?
      final ViewComputationResultModel previousResult = _latestResult.get();
      deltaFragment = ViewDeltaResultCalculator.computeDeltaModel(viewDefinition, previousResult, fullFragment);
      listeners = getListenerArray();
    } finally {
      unlock();
    }
    // [PLAT-1158] The notifications are performed outside of holding the lock which avoids the deadlock problem, but we'll still block
    // for completion which was the thing PLAT-1158 was trying to avoid. This is because the contracts for the order in which
    // notifications can be received is unclear and I don't want to risk introducing a change at this moment in time.
    for (final ViewResultListener listener : listeners) {
      try {
        listener.cycleFragmentCompleted(fullFragment, deltaFragment);
      } catch (final Exception e) {
        logListenerError(listener, e);
      }
    }
  }

  @Override
  public void cycleExecutionFailed(final ViewCycleExecutionOptions executionOptions, final Exception exception) {
    // Caller MUST NOT hold the semaphore
    s_logger.error("Cycle execution failed for " + executionOptions + ": ", exception);
    final ViewResultListener[] listeners;
    lock();
    try {
      listeners = getListenerArray();
    } finally {
      unlock();
    }
    // [PLAT-1158] The notifications are performed outside of holding the lock which avoids the deadlock problem, but we'll still block
    // for completion which was the thing PLAT-1158 was trying to avoid. This is because the contracts for the order in which
    // notifications can be received is unclear and I don't want to risk introducing a change at this moment in time.
    for (final ViewResultListener listener : listeners) {
      try {
        listener.cycleExecutionFailed(executionOptions, exception);
      } catch (final Exception e) {
        logListenerError(listener, e);
      }
    }
  }

  @Override
  public void workerCompleted() {
    // Caller MUST NOT hold the semaphore
    s_logger.debug("Computation job completed on view {}. No further cycles to run.", this);
    final ViewResultListener[] listeners;
    lock();
    try {
      setState(ViewProcessState.FINISHED);
      listeners = getListenerArray();
    } finally {
      unlock();
    }
    // [PLAT-1158] The notifications are performed outside of holding the lock which avoids the deadlock problem, but we'll still block
    // for completion which was the thing PLAT-1158 was trying to avoid. This is because the contracts for the order in which
    // notifications can be received is unclear and I don't want to risk introducing a change at this moment in time.
    for (final ViewResultListener listener : listeners) {
      try {
        listener.processCompleted();
      } catch (final Exception e) {
        logListenerError(listener, e);
      }
    }
  }

  //-------------------------------------------------------------------------
  private void lock() {
    try {
      s_logger.debug("Attempt to acquire lock by thread {}", Thread.currentThread().getName());
      _processLock.acquire();
      s_logger.debug("Lock acquired by thread {}", Thread.currentThread().getName());
    } catch (final InterruptedException e) {
      throw new OpenGammaRuntimeException("Interrupted", e);
    }
  }

  private void unlock() {
    _processLock.release();
    s_logger.debug("Lock released by thread " + Thread.currentThread().getName());
  }

  /**
   * Sets the current view process state.
   *
   * @param state the new view process state
   */
  private void setState(final ViewProcessState state) {
    _state = state;
  }

  /**
   * Sets the current view process worker. This is the component responsible for coordinating the execution of the process, for example this might be a single local thread, a pool of local threads or
   * a proxy to remotely executing code.
   * <p>
   * External visibility for testing.
   *
   * @return the current view process worker.
   */
  public ViewProcessWorker getWorker() {
    return _worker;
  }

  /**
   * Sets the current worker
   *
   * @param worker the current worker
   */
  private void setWorker(final ViewProcessWorker worker) {
    _worker = worker;
  }

  @Override
  public ViewProcessContext getProcessContext() {
    return _viewProcessContext;
  }

  /**
   * Attaches a listener to the view process.
   * <p>
   * The method operates with set semantics, so duplicate notifications for the same listener have no effect.
   *
   *
   * @param listener the listener, not null
   * @param resultMode the result mode for the listener, not null
   * @param fragmentResultMode the fragment result mode for the listener, not null
   * @return the permission context for the process, not null
   */
  public ViewPermissionContext attachListener(final ViewResultListener listener,
                                              final ViewResultMode resultMode,
                                              final ViewResultMode fragmentResultMode) {
    ArgumentChecker.notNull(listener, "listener");
    ArgumentChecker.notNull(resultMode, "resultMode");
    ArgumentChecker.notNull(fragmentResultMode, "fragmentResultMode");
    // Caller MUST NOT hold the semaphore
    Pair<CompiledViewDefinitionWithGraphs, MarketDataPermissionProvider> latestCompilation = null;
    ViewComputationResultModel latestResult = null;
    boolean listenerRequiresDeltas = doesListenerRequireDeltas(resultMode, fragmentResultMode);
    lock();
    try {
      if (_listeners.put(listener, listenerRequiresDeltas) == null) {
        if (listenerRequiresDeltas) {
          _mustCalculateDeltas.set(true);
        }
       
        // keep track of number of internal listeners
        if (listener instanceof InternalViewResultListener) {
          _internalListenerCount++;
        }
        // exclude internal listeners from test
        if ((_listeners.size() - _internalListenerCount) == 1) {
          try {
            startComputationJobIfRequired();
          } catch (final Exception e) {
            // Roll-back
            _listeners.remove(listener);
            s_logger.error("Failed to start computation job while adding listener for view process {}", this);
            throw new OpenGammaRuntimeException("Failed to start computation job while adding listener for view process " + toString(), e);
          }
        }
        // Push any initial state to listener
        latestCompilation = _latestCompiledViewDefinition.get();
        if (latestCompilation != null) {
          latestResult = _latestResult.get();
        }
      }
    } finally {
      unlock();
    }
    // REVIEW 2013-04-01 Andrew -- The listener is in the set, but has not received its compilation message yet; it's possible for a calc thread
    // to post its first result before the compilation notification has happened.
    if (latestCompilation != null) {
      // [PLAT-1158] The initial notification is performed outside of holding the lock which avoids the deadlock problem, but we'll still
      // block for completion which was the thing PLAT-1158 was trying to avoid. This is because the contracts for the order in which
      // notifications can be received is unclear and I don't want to risk introducing a change at this moment in time.
      try {
        final CompiledViewDefinitionWithGraphs compiledViewDefinition = latestCompilation.getFirst();
        final MarketDataPermissionProvider permissionProvider = latestCompilation.getSecond();
        final Set<ValueSpecification> marketData = compiledViewDefinition.getMarketDataRequirements();
        final Set<ValueSpecification> deniedRequirements =
            permissionProvider.checkMarketDataPermissions(listener.getUser(), marketData);
        final boolean hasMarketDataPermissions = deniedRequirements.isEmpty();
        listener.viewDefinitionCompiled(compiledViewDefinition, hasMarketDataPermissions);
        if (latestResult != null) {
          listener.cycleCompleted(latestResult, null);
        }
      } catch (final Exception e) {
        s_logger.error("Failed to push initial state to listener during attachment");
        logListenerError(listener, e);
      }
    }
    return new ViewPermissionContext(
        getProcessContext().getViewPermissionProvider(),
        getProcessContext().getViewPortfolioPermissionProvider());
  }

  private static boolean doesListenerRequireDeltas(ViewResultMode resultMode, ViewResultMode fragmentResultMode) {
    boolean requiresDeltas = false;
    switch(resultMode) {
      case BOTH:
      case DELTA_ONLY:
      case FULL_THEN_DELTA:
        requiresDeltas = true;
    }
    switch(fragmentResultMode) {
      case BOTH:
      case DELTA_ONLY:
      case FULL_THEN_DELTA:
        requiresDeltas = true;
    }
    return requiresDeltas;
  }

  /**
   * Removes a listener from the view process. Removal of the last listener generating execution demand will cause the process to stop.
   * We allow instances extending InternalViewResultListener to be ignored for the purposes of reference counting.  This allows e.g. JMX MBeans
   * to track view events without affecting execution.
   * <p>
   * The method operates with set semantics, so duplicate notifications for the same listener have no effect.
   *
   * @param listener the listener, not null
   */
  public void detachListener(final ViewResultListener listener) {
    ArgumentChecker.notNull(listener, "listener");
    // Caller MUST NOT hold the semaphore
    lock();
    try {
      if (_listeners.remove(listener) != null) {
        // keep track of internal listeners so they can be excluded from reference count
        if (listener instanceof InternalViewResultListener) {
          _internalListenerCount--;
        }
        // exclude internal listeners from the count
        if ((_listeners.size() - _internalListenerCount) == 0) {
          stopComputationJobIfRequired();
        }
       
        checkIfDeltasRequired();
      }
    } finally {
      unlock();
    }
  }
 
  protected void checkIfDeltasRequired() {
    boolean deltasRequired = false;
    for (Boolean requiresDeltas : _listeners.values()) {
      if (requiresDeltas) {
        deltasRequired = true;
        break;
      }
    }
    if (!deltasRequired) {
      _latestResult.set(null);
    }
    _mustCalculateDeltas.set(deltasRequired);
  }

  public boolean hasExecutionDemand() {
    // Caller MUST NOT hold the semaphore
    lock();
    try {
      return (_listeners.size() - _internalListenerCount) > 0;
    } finally {
      unlock();
    }
  }

  public ViewExecutionOptions getExecutionOptions() {
    return _executionOptions;
  }

  private void startComputationJobImpl() {
    // Caller MUST hold the semaphore
    if (_viewDefinitionChangeListener != null) {
      getProcessContext().getConfigSource().changeManager().addChangeListener(_viewDefinitionChangeListener);
    }
    final ViewDefinition viewDefinition = getLatestViewDefinition();
    boolean rollback = true;
    try {
      final ViewProcessWorker worker = getProcessContext().getViewProcessWorkerFactory().createWorker(this, getExecutionOptions(), viewDefinition);
      setWorker(worker);
      rollback = false;
    } catch (final Exception e) {
      s_logger.error("Failed to start computation job for view process " + toString(), e);
      throw new OpenGammaRuntimeException("Failed to start computation job for view process " + toString(), e);
    } finally {
      if (rollback && (_viewDefinitionChangeListener != null)) {
        getProcessContext().getConfigSource().changeManager().removeChangeListener(_viewDefinitionChangeListener);
      }
    }
  }

  /**
   * Starts the background job responsible for running computation cycles for this view process.
   */
  private void startComputationJobIfRequired() {
    // Caller MUST hold the semaphore
    s_logger.info("Starting computation on view process {}...", this);
    switch (getState()) {
      case STOPPED:
        // Normal state of play. Continue as normal.
        break;
      case RUNNING:
        return;
      case FINISHED:
        throw new IllegalStateException("The computation job has already been run.");
      case TERMINATED:
        throw new IllegalStateException("A terminated view process cannot be used.");
    }
    setState(ViewProcessState.RUNNING);
    startComputationJobImpl();
    s_logger.info("Started computation job for view process {}", this);
  }

  /**
   * Instructs the background computation job to finish. The background job might actually terminate asynchronously, but any outstanding result will be discarded. A replacement background computation
   * job may be started immediately.
   */
  private void stopComputationJobIfRequired() {
    // Caller MUST hold the semaphore
    if (getLatestViewDefinition().isPersistent()) {
      return;
    }
    s_logger.info("Stopping computation on view process {}...", this);
    if (getState() != ViewProcessState.RUNNING) {
      return;
    }
    terminateComputationJob();
    setState(ViewProcessState.STOPPED);
    s_logger.info("Stopped.");
  }

  private void logListenerError(final ViewResultListener listener, final Exception e) {
    s_logger.error("Error while calling listener " + listener, e);
  }

  private void terminateComputationJob() {
    final ViewProcessWorker worker = getWorker();
    if (worker == null) {
      return;
    }
    if (_viewDefinitionChangeListener != null) {
      getProcessContext().getConfigSource().changeManager().removeChangeListener(_viewDefinitionChangeListener);
    }
    worker.terminate();
    // Let go of the worker and allow it to die on its own. A computation cycle might be taking place, but it will
    // not update the view process with its result because it has been terminated. As far as the view process is
    // concerned, live computation has now stopped, and it may be started again immediately in a new thread.
    // There is no need to slow things down by attempting to join the job.
    setWorker(null);
  }

}
TOP

Related Classes of com.opengamma.engine.view.impl.ViewProcessImpl

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.