Package com.opengamma.web.analytics

Source Code of com.opengamma.web.analytics.ResultsCache$ResultKey

/**
* Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.web.analytics;

import java.math.BigDecimal;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.buffer.CircularFifoBuffer;
import org.threeten.bp.Duration;
import org.threeten.bp.Instant;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.opengamma.engine.value.ComputedValueResult;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.engine.view.AggregatedExecutionLog;
import com.opengamma.engine.view.ViewResultEntry;
import com.opengamma.engine.view.ViewResultModel;
import com.opengamma.financial.analytics.LocalDateLabelledMatrix1D;
import com.opengamma.id.ObjectId;
import com.opengamma.id.UniqueIdentifiable;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.money.CurrencyAmount;

/**
* <p>Cache of results from a running view process. This is intended for use with view clients where the first
* set of results is a full set and subsequent results are deltas. This cache maintains a full set of results
* which includes every target that has ever had a value calculated. It also keeps track of which values were
* updated in the previous calculation cycle.</p>
* <p>This class isn't thread safe.</p>
*/
/* package */ class ResultsCache {

  /** Maximum number of history values stored for each item. */
  private static final int MAX_HISTORY_SIZE = 20;

  // is this likely to change? will it ever by dynamic? i.e. client specifies what types it wants history for?
  /** Types of result values for which history is stored. */
  private static final Set<Class<?>> s_historyTypes =
      ImmutableSet.of(Double.class, BigDecimal.class, CurrencyAmount.class, LocalDateLabelledMatrix1D.class);
  /** Empty result for types that don't have history, makes for cleaner code than using null. */
  private static final Result s_emptyResult = Result.empty();
  /** Empty result for types that have history, makes for cleaner code than using null. */
  private static final Result s_emptyResultWithHistory = Result.emptyWithHistory();

  /** The cached results. */
  private final Map<ResultKey, CacheItem> _results = Maps.newHashMap();

  /** Cache of portfolio entities, i.e. trades, positions, securities */
  private final Map<ObjectId, CacheItem> _entities = Maps.newHashMap();

  /** ID that's incremented each time results are received, used for keeping track of which items were updated. */
  private long _lastUpdateId;

  /** Duration of the last calculation cycle. */
  private Duration _lastCalculationDuration = Duration.ZERO;
  /** Last valuation time */
  private Instant _valuationTime = Instant.MIN;

  /**
   * Puts a set of main grid results into the cache.
   * @param results The results, not null
   */
  /* package */ void put(ViewResultModel results) {
    ArgumentChecker.notNull(results, "results");
    _lastUpdateId++;
    _lastCalculationDuration = results.getCalculationDuration();
    _valuationTime = results.getViewCycleExecutionOptions().getValuationTime();
    List<ViewResultEntry> allResults = results.getAllResults();
    Set<ResultKey> updatedKeys = Sets.newHashSet();
    for (ViewResultEntry result : allResults) {
      put(result.getCalculationConfiguration(), result.getComputedValue());
      updatedKeys.add(new ResultKey(result.getCalculationConfiguration(), result.getComputedValue().getSpecification()));
    }
    // duplicate the last history item for anything that hasn't changed this cycle
    for (Map.Entry<ResultKey, CacheItem> entry : _results.entrySet()) {
      if (entry.getValue().getHistory() != null && !updatedKeys.contains(entry.getKey())) {
        entry.getValue().valueUnchanged();
      }
    }
  }

  /**
   * Puts a set of dependency graph results into the cache.
   * @param calcConfigName The name of the calculation configuration used to calculate the results
   * @param results The results
   * @param duration Duration of the calculation cycle that produced the results
   */
  /* package */ void put(String calcConfigName, Map<ValueSpecification, ComputedValueResult> results, Duration duration) {
    _lastUpdateId++;
    _lastCalculationDuration = duration;
    for (ComputedValueResult result : results.values()) {
      put(calcConfigName, result);
    }
  }

  /**
   * Puts a single value into the cache.
   * @param calcConfigName The name of the calculation configuration used to calculate the results
   * @param result The result value and associated data
   */
  private void put(String calcConfigName, ComputedValueResult result) {
    ValueSpecification spec = result.getSpecification();
    Object value = result.getValue();
    ResultKey key = new ResultKey(calcConfigName, spec);
    CacheItem cacheResult = _results.get(key);
    if (cacheResult == null) {
      _results.put(key, new CacheItem(value, result.getAggregatedExecutionLog(), _lastUpdateId));
    } else {
      cacheResult.setLatestValue(value, result.getAggregatedExecutionLog(), _lastUpdateId);
    }
  }

  /* package */ void put(List<UniqueIdentifiable> entities) {
    ArgumentChecker.notNull(entities, "entities");
    _lastUpdateId++;
    for (UniqueIdentifiable entity : entities) {
      // TODO why is this failing sometimes?
      //ArgumentChecker.notNull(entity, "entity");
      if (entity != null) {
        putEntity(entity);
      }
    }
  }

  /* package */ void put(UniqueIdentifiable entity) {
    ArgumentChecker.notNull(entity, "entity");
    ++_lastUpdateId;
    putEntity(entity);
  }

  /* package */ void remove(ObjectId id) {
    ++_lastUpdateId;
    _entities.remove(id);
  }

  private void putEntity(UniqueIdentifiable entity) {
    ObjectId id = entity.getUniqueId().getObjectId();
    CacheItem cacheResult = _entities.get(id);
    if (cacheResult == null) {
      _entities.put(id, new CacheItem(entity, null, _lastUpdateId));
    } else {
      cacheResult.setLatestValue(entity, null, _lastUpdateId);
    }
  }

  /* package */ Result getEntity(ObjectId id) {
    CacheItem item = _entities.get(id);
    if (item != null) {
      // flag whether this result was updated by the last set of results that were put into the cache
      boolean updatedByLastResults = (item.getLastUpdateId() == _lastUpdateId);
      return Result.forValue(item.getValue(), null, null, updatedByLastResults);
    } else {
      return s_emptyResult;
    }
  }

  /**
   * Returns a cache result for a value specification and calculation configuration.
   * @param calcConfigName The calculation configuration name
   * @param valueSpec The value specification
   * @param columnType The expected type of the value, used to decide whether empty history should be provided for
   * a value that isn't in the cache but would have history if it were. Can be null in which case no history is
   * provided for missing values.
   * @return A cache result, not null
   */
  /* package */ Result getResult(String calcConfigName, ValueSpecification valueSpec, Class<?> columnType) {
    CacheItem item = _results.get(new ResultKey(calcConfigName, valueSpec));
    if (item != null) {
      // flag whether this result was updated by the last set of results that were put into the cache
      boolean updatedByLastResults = (item.getLastUpdateId() == _lastUpdateId);
      return Result.forValue(item.getValue(), item.getHistory(), item.getAggregatedExecutionLog(), updatedByLastResults);
    } else {
      if (s_historyTypes.contains(columnType)) {
        return s_emptyResultWithHistory;
      } else {
        return s_emptyResult;
      }
    }
  }

  /**
   * @return Duration of the last calculation cycle
   */
  /* package */ Duration getLastCalculationDuration() {
    return _lastCalculationDuration;
  }
 
  /**
   * Gets the lastCalculationTime.
   * @return the lastCalculationTime
   */
  /* package */ Instant getValuationTime() {
    return _valuationTime;
  }

  /**
   * Returns empty history appropriate for the type. For types that support history it will be an empty collection,
   * for types that don't it will be null.
   * @param type The type, possibly null
   * @return The history, possibly null
   */
  /* package */ Collection<Object> emptyHistory(Class<?> type) {
    if (s_historyTypes.contains(type)) {
      return Collections.emptyList();
    } else {
      return null;
    }
  }

  /**
   * An item from the cache including its history and a flag indicating whether it was updated by the most recent
   * calculation cycle. Instances of this class are intended for users of the cache.
   */
  /* package */ static final class Result {

    private final Object _value;
    private final Collection<Object> _history;
    private final boolean _updated;
    private final AggregatedExecutionLog _aggregatedExecutionLog;

    private Result(Object value, Collection<Object> history, AggregatedExecutionLog aggregatedExecutionLog, boolean updated) {
      _value = value;
      _history = history;
      _aggregatedExecutionLog = aggregatedExecutionLog;
      _updated = updated;
    }

    /**
     * @return The most recent value, null if no value has ever been calculated for the requirement
     */
    /* package */ Object getValue() {
      return _value;
    }

    /**
     * @return The history for the value, empty if no value has been calculated, null if history isn't stored for the
     * requirement
     */
    /* package */ Collection<Object> getHistory() {
      return _history;
    }

    /**
     * @return true if the value was updated by the most recent calculation cycle
     */
    /* package */ boolean isUpdated() {
      return _updated;
    }

    private static Result forValue(Object value,
                                   Collection<Object> history,
                                   AggregatedExecutionLog aggregatedExecutionLog,
                                   boolean updated) {
      ArgumentChecker.notNull(value, "value");
      return new Result(value, history, aggregatedExecutionLog, updated);
    }

    /**
     * @return A result with no value and no history, for value requirements that never have history
     */
    private static Result empty() {
      return new Result(null, null, null, false);
    }

    /**
     * @return A result with no value and empty history, for value requirements that can have history
     */
    private static Result emptyWithHistory() {
      return new Result(null, Collections.emptyList(), null, false);
    }

    /* package */ AggregatedExecutionLog getAggregatedExecutionLog() {
      return _aggregatedExecutionLog;
    }
  }

  /**
   * An item stored in the cache, this is an internal implementation detail.
   */
  private static final class CacheItem {

    private Collection<Object> _history;
    private Object _latestValue;
    private long _lastUpdateId = -1;
    private AggregatedExecutionLog _aggregatedExecutionLog;

    private CacheItem(Object value, AggregatedExecutionLog executionLog, long lastUpdateId) {
      setLatestValue(value, executionLog, lastUpdateId);
    }

    /**
     * Sets the latest value and the ID of the update that calculated it.
     * @param latestValue The value
     * @param executionLog The execution log associated generated when calculating the value
     * @param lastUpdateId ID of the set of results that calculated it
     */
    @SuppressWarnings("unchecked")
    private void setLatestValue(Object latestValue, AggregatedExecutionLog executionLog, long lastUpdateId) {
      ArgumentChecker.notNull(latestValue, "latestValue");
      _latestValue = latestValue;
      _lastUpdateId = lastUpdateId;
      _aggregatedExecutionLog = executionLog;
      // this can happen if the first value is an error and then real values arrive. this is possible if market
      // data subscriptions take time to set up. in that case the history will initially be null (because error
      // sentinel types aren't in s_historyTypes) and then when a valid value arrives the type can be checked and
      // history created if required
      if (_history == null && s_historyTypes.contains(latestValue.getClass())) {
        _history = new CircularFifoBuffer(MAX_HISTORY_SIZE);
      }
      if (_history != null) {
        _history.add(latestValue);
      }
    }

    private Object getValue() {
      return _latestValue;
    }

    /* package */ Collection<Object> getHistory() {
      if (_history != null) {
        return Collections.unmodifiableCollection(_history);
      } else {
        return null;
      }
    }

    /**
     * @return ID of the set of results that updated this item, used to decide whether the item was updated by
     * the most recent calculation cycle.
     */
    private long getLastUpdateId() {
      return _lastUpdateId;
    }

    private AggregatedExecutionLog getAggregatedExecutionLog() {
      return _aggregatedExecutionLog;
    }

    /**
     * Invoked when a calculation cycle completes and doesn't update the value for an item. The latest value is
     * inserted into the history again to ensure the history is up to date.
     */
    private void valueUnchanged() {
      _history.add(_latestValue);
    }
  }

  /**
   * Immutable key for items in the cache, this is in implelemtation detail.
   */
  private static final class ResultKey {

    private final String _calcConfigName;
    private final ValueSpecification _valueSpec;

    private ResultKey(String calcConfigName, ValueSpecification valueSpec) {
      _calcConfigName = calcConfigName;
      _valueSpec = valueSpec;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) {
        return true;
      }
      if (o == null || getClass() != o.getClass()) {
        return false;
      }
      ResultKey resultKey = (ResultKey) o;
      if (!_calcConfigName.equals(resultKey._calcConfigName)) {
        return false;
      }
      return _valueSpec.equals(resultKey._valueSpec);
    }

    @Override
    public int hashCode() {
      int result = _calcConfigName.hashCode();
      result = 31 * result + _valueSpec.hashCode();
      return result;
    }

    @Override
    public String toString() {
      return _valueSpec.toString() + "/" + _calcConfigName;
    }

  }
}
TOP

Related Classes of com.opengamma.web.analytics.ResultsCache$ResultKey

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.