Package com.opengamma.master.cache

Source Code of com.opengamma.master.cache.AbstractEHCachingMaster

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

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.CopyStrategyConfiguration;
import net.sf.ehcache.config.SearchAttribute;
import net.sf.ehcache.config.Searchable;
import net.sf.ehcache.constructs.blocking.SelfPopulatingCache;
import net.sf.ehcache.search.Result;
import net.sf.ehcache.search.Results;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.threeten.bp.Instant;

import com.opengamma.DataNotFoundException;
import com.opengamma.core.change.BasicChangeManager;
import com.opengamma.core.change.ChangeEvent;
import com.opengamma.core.change.ChangeListener;
import com.opengamma.core.change.ChangeManager;
import com.opengamma.id.ObjectId;
import com.opengamma.id.ObjectIdentifiable;
import com.opengamma.id.UniqueId;
import com.opengamma.id.VersionCorrection;
import com.opengamma.master.AbstractChangeProvidingMaster;
import com.opengamma.master.AbstractDocument;
import com.opengamma.util.ArgumentChecker;

/**
* A cache decorating a master, mainly intended to reduce the frequency and repetition of queries to the underlying
* master.
* <p>
* The cache is implemented using {@code EHCache}.
*
* TODO Check whether misses are cached by SelfPopulatingCache
* TODO remove redundant cleanCache calls
* TODO externalise configuration in xml file
*
*
* @param <D> the document type returned by the master
*/
public abstract class AbstractEHCachingMaster<D extends AbstractDocument> implements AbstractChangeProvidingMaster<D> {

  /** Logger. */
  private static final Logger s_logger = LoggerFactory.getLogger(AbstractEHCachingMaster.class);
  /** Cache name. */
  private static final String CACHE_NAME_SUFFIX = "UidToDocumentCache";
  /** Check cached results against results from underlying */
  public static final boolean TEST_AGAINST_UNDERLYING = false; //s_logger.isDebugEnabled();

  /** The underlying master. */
  private final AbstractChangeProvidingMaster<D> _underlying;
  /** The cache manager. */
  private final CacheManager _cacheManager;
  /** Listens for changes in the underlying security source. */
  private final ChangeListener _changeListener;
  /** The local change manager. */
  private final ChangeManager _changeManager;
  /** The document cache indexed by UniqueId. */
  private final Ehcache _uidToDocumentCache;

  /**
   * Creates an instance over an underlying source specifying the cache manager.
   *
   * @param name          the cache name, not empty
   * @param underlying    the underlying source, not null
   * @param cacheManager  the cache manager, not null
   */
  public AbstractEHCachingMaster(final String name, final AbstractChangeProvidingMaster<D> underlying, final CacheManager cacheManager) {
    ArgumentChecker.notEmpty(name, "name");
    ArgumentChecker.notNull(underlying, "underlying");
    ArgumentChecker.notNull(cacheManager, "cacheManager");

    _underlying = underlying;
    _cacheManager = cacheManager;

    // Load cache configuration
    if (cacheManager.getCache(name + CACHE_NAME_SUFFIX) == null) {
      // If cache config not found, set up programmatically
      s_logger.warn("Could not load a cache configuration for " + name + CACHE_NAME_SUFFIX
                  + ", building a default configuration programmatically instead");
      getCacheManager().addCache(new Cache(tweakCacheConfiguration(new CacheConfiguration(name + CACHE_NAME_SUFFIX,
                                                                                          10000))));
    }
    _uidToDocumentCache = new SelfPopulatingCache(_cacheManager.getCache(name + CACHE_NAME_SUFFIX),
                                                  new UidToDocumentCacheEntryFactory<>(_underlying));
    getCacheManager().replaceCacheWithDecoratedCache(_cacheManager.getCache(name + CACHE_NAME_SUFFIX),
                                                     getUidToDocumentCache());

    // Listen to change events from underlying, clean this cache accordingly and relay events to our change listeners
    _changeManager = new BasicChangeManager();
    _changeListener = new ChangeListener() {
      @Override
      public void entityChanged(ChangeEvent event) {
        final ObjectId oid = event.getObjectId();
        final Instant versionFrom = event.getVersionFrom();
        final Instant versionTo = event.getVersionTo();
        cleanCaches(oid, versionFrom, versionTo);
        _changeManager.entityChanged(event.getType(), event.getObjectId(),
            event.getVersionFrom(), event.getVersionTo(), event.getVersionInstant());
      }
    };
    underlying.changeManager().addChangeListener(_changeListener);
  }

  private CacheConfiguration tweakCacheConfiguration(CacheConfiguration cacheConfiguration) {

    // Set searchable index
    Searchable uidToDocumentCacheSearchable = new Searchable();
    uidToDocumentCacheSearchable.addSearchAttribute(new SearchAttribute().name("ObjectId")
                                                        .expression("value.getObjectId().toString()"));
    uidToDocumentCacheSearchable.addSearchAttribute(new SearchAttribute().name("VersionFromInstant")
                                                        .className("com.opengamma.master.cache.InstantExtractor"));
    uidToDocumentCacheSearchable.addSearchAttribute(new SearchAttribute().name("VersionToInstant")
                                                        .className("com.opengamma.master.cache.InstantExtractor"));
    uidToDocumentCacheSearchable.addSearchAttribute(new SearchAttribute().name("CorrectionFromInstant")
                                                        .className("com.opengamma.master.cache.InstantExtractor"));
    uidToDocumentCacheSearchable.addSearchAttribute(new SearchAttribute().name("CorrectionToInstant")
                                                        .className("com.opengamma.master.cache.InstantExtractor"));
    cacheConfiguration.addSearchable(uidToDocumentCacheSearchable);

    // Make copies of cached objects
    CopyStrategyConfiguration copyStrategyConfiguration = new CopyStrategyConfiguration();
    copyStrategyConfiguration.setClass("com.opengamma.master.cache.JodaBeanCopyStrategy");
    cacheConfiguration.addCopyStrategy(copyStrategyConfiguration);
    cacheConfiguration.setCopyOnRead(true);
    cacheConfiguration.setCopyOnWrite(true);

    cacheConfiguration.setStatistics(true);

    return cacheConfiguration;
  }

  //-------------------------------------------------------------------------

  @Override
  public D get(ObjectIdentifiable objectId, VersionCorrection versionCorrection) {
    ArgumentChecker.notNull(objectId, "objectId");
    ArgumentChecker.notNull(versionCorrection, "versionCorrection");

    // Search through attributes for specified oid, versions/corrections
    Results results = getUidToDocumentCache().createQuery()
        .includeKeys().includeValues()
        .includeAttribute(getUidToDocumentCache().getSearchAttribute("ObjectId"))
        .includeAttribute(getUidToDocumentCache().getSearchAttribute("VersionFromInstant"))
        .includeAttribute(getUidToDocumentCache().getSearchAttribute("VersionToInstant"))
        .includeAttribute(getUidToDocumentCache().getSearchAttribute("CorrectionFromInstant"))
        .includeAttribute(getUidToDocumentCache().getSearchAttribute("CorrectionToInstant"))
        .addCriteria(getUidToDocumentCache().getSearchAttribute("ObjectId").eq(objectId.toString()))
        .addCriteria(getUidToDocumentCache().getSearchAttribute("VersionFromInstant")
            .le(versionCorrection.withLatestFixed(InstantExtractor.MAX_INSTANT).getVersionAsOf().toString()))
        .addCriteria(getUidToDocumentCache().getSearchAttribute("VersionToInstant")
            .gt(versionCorrection.withLatestFixed(InstantExtractor.MAX_INSTANT.minusNanos(1)).getVersionAsOf().toString()))
        .addCriteria(getUidToDocumentCache().getSearchAttribute("CorrectionFromInstant")
            .le(versionCorrection.withLatestFixed(InstantExtractor.MAX_INSTANT).getCorrectedTo().toString()))
        .addCriteria(getUidToDocumentCache().getSearchAttribute("CorrectionToInstant")
            .gt(versionCorrection.withLatestFixed(InstantExtractor.MAX_INSTANT.minusNanos(1)).getCorrectedTo().toString()))
        .execute();

    // Found a matching cached document
    if (results.size() == 1 && results.all().get(0).getValue() != null) {
      @SuppressWarnings("unchecked")
      D result = (D) results.all().get(0).getValue();

      // Debug: check result against underlying
      if (TEST_AGAINST_UNDERLYING) {
        D check = getUnderlying().get(objectId, versionCorrection);
        if (!result.equals(check)) {
          s_logger.error(getUidToDocumentCache().getName() + " returned:\n" + result + "\nbut the underlying master returned:\n" + check);
        }
      }

      // Return cached value
      return result;

    // No cached document found, fetch from underlying by oid/vc instead
    // Note: no self-populating by oid/vc, and no caching of misses by oid/vc
    } else if (results.size() == 0) {
      // Get from underlying by oid/vc, throwing exception if not there
      D result = _underlying.get(objectId, versionCorrection);

      // Explicitly insert in cache
      getUidToDocumentCache().put(new Element(result.getUniqueId(), result));

      return result;

    // Invalid result
    } else {
      throw new DataNotFoundException("Unable to uniquely identify a document with ObjectId " + objectId
                                      + " and VersionCorrection " + versionCorrection
                                      + " because more than one cached search result matches: " + results);
    }
  }

  @SuppressWarnings("unchecked")
  @Override
  public D get(UniqueId uniqueId) {
    ArgumentChecker.notNull(uniqueId, "uniqueId");

    // Get from cache, which in turn self-populates from the underlying master
    Element element;
    try {
      element = getUidToDocumentCache().get(uniqueId);
    } catch (CacheException e) {
      throw new DataNotFoundException(e.getMessage());
    }

    if (element != null && element.getObjectValue() != null) {

      // Debug: check result against underlying
      if (TEST_AGAINST_UNDERLYING) {
        D check = getUnderlying().get(uniqueId);
        if (!((D) element.getObjectValue()).equals(check)) {
          s_logger.error(getUidToDocumentCache().getName() + " returned:\n" + ((D) element.getObjectValue()) + "\nbut the underlying master returned:\n"  + check);
        }
      }
      return (D) element.getObjectValue();
    } else {
      throw new DataNotFoundException("No document found with the specified UniqueId");
    }
  }

  @Override
  public Map<UniqueId, D> get(Collection<UniqueId> uniqueIds) {
    ArgumentChecker.notNull(uniqueIds, "uniqueIds");

    Map<UniqueId, D> result = new HashMap<>();
    for (UniqueId uniqueId : uniqueIds) {
      try {
        D object = get(uniqueId);
        result.put(uniqueId, object);
      } catch (DataNotFoundException ex) {
        // do nothing
      }
    }

    return result;
  }

  //-------------------------------------------------------------------------

  @Override
  public D add(D document) {
    ArgumentChecker.notNull(document, "document");

    // Add document to underlying master
    D result = getUnderlying().add(document);

    // Store document in UniqueId cache
    getUidToDocumentCache().put(new Element(result.getUniqueId(), result));

    return result;
  }

  @Override
  public D update(D document) {
    ArgumentChecker.notNull(document, "document");
    ArgumentChecker.notNull(document.getObjectId(), "document.objectId");

    // Flush previous latest version (and all its corrections) from cache
    cleanCaches(document.getObjectId(), Instant.now(), InstantExtractor.MAX_INSTANT);

    // Update document in underlying master
    D result = getUnderlying().update(document);

    // Store document in UniqueId cache
    getUidToDocumentCache().put(new Element(result.getUniqueId(), result));

    return result;
  }

  @Override
  public void remove(ObjectIdentifiable objectId) {
    ArgumentChecker.notNull(objectId, "objectId");

    // Remove document from underlying master
    getUnderlying().remove(objectId);

    // Adjust version/correction validity of latest version in Oid cache
    // Note: cleanCaches is already triggered by underlying master, so this is probably redundant
    cleanCaches(objectId.getObjectId(), Instant.now(), null);
  }

  @Override
  public D correct(D document) {
    ArgumentChecker.notNull(document, "document");
    ArgumentChecker.notNull(document.getUniqueId(), "document.uniqueId");

    // Flush the previous latest correction from cache
    getUidToDocumentCache().remove(document.getUniqueId());

    // Correct document in underlying master
    D result = getUnderlying().correct(document);

    // Store latest correction in UniqueId cache
    getUidToDocumentCache().put(new Element(result.getUniqueId(), result));

    return result;
  }

  @Override
  public List<UniqueId> replaceVersion(UniqueId uniqueId, List<D> replacementDocuments) {
    ArgumentChecker.notNull(uniqueId, "uniqueId");
    ArgumentChecker.notNull(replacementDocuments, "replacementDocuments");

    // Flush the original version from cache
    getUidToDocumentCache().remove(uniqueId);

    // Replace version in underlying master
    List<UniqueId> results = getUnderlying().replaceVersion(uniqueId, replacementDocuments);

    // Don't cache replacementDocuments, whose version, correction instants may have been altered by underlying master

    return results;
  }

  @Override
  public List<UniqueId> replaceAllVersions(ObjectIdentifiable objectId, List<D> replacementDocuments) {
    ArgumentChecker.notNull(objectId, "objectId");
    ArgumentChecker.notNull(replacementDocuments, "replacementDocuments");

    // Flush all existing versions from cache
    cleanCaches(objectId.getObjectId(), null, null);

    // Replace all versions in underlying master
    List<UniqueId> results = getUnderlying().replaceAllVersions(objectId, replacementDocuments);

    // Don't cache replacementDocuments, whose version, correction instants may have been altered by underlying master

    return results;
  }

  @Override
  public List<UniqueId> replaceVersions(ObjectIdentifiable objectId, List<D> replacementDocuments) {
    ArgumentChecker.notNull(objectId, "objectId");
    ArgumentChecker.notNull(replacementDocuments, "replacementDocuments");

    // Flush all existing versions from cache
    cleanCaches(objectId.getObjectId(), null, null);

    // Replace versions in underlying master
    List<UniqueId> results = getUnderlying().replaceVersions(objectId, replacementDocuments);

    // Don't cache replacementDocuments, whose version, correction instants may have been altered by underlying master

    return results;
  }

  @Override
  public UniqueId replaceVersion(D replacementDocument) {
    ArgumentChecker.notNull(replacementDocument, "replacementDocument");

    final List<UniqueId> result =
        replaceVersion(replacementDocument.getUniqueId(), Collections.singletonList(replacementDocument));
    if (result.isEmpty()) {
      return null;
    } else {
      return result.get(0);
    }
  }

  @Override
  public void removeVersion(UniqueId uniqueId) {
    replaceVersion(uniqueId, Collections.<D>emptyList());
  }

  @Override
  public UniqueId addVersion(ObjectIdentifiable objectId, D documentToAdd) {
    final List<UniqueId> result = replaceVersions(objectId, Collections.singletonList(documentToAdd));
    if (result.isEmpty()) {
      return null;
    } else {
      return result.get(0);
    }
  }

  //-------------------------------------------------------------------------

  private void cleanCaches(ObjectId objectId, Instant fromVersion, Instant toVersion) {

    Results results = getUidToDocumentCache().createQuery().includeKeys()
        .includeAttribute(getUidToDocumentCache().getSearchAttribute("ObjectId"))
        .includeAttribute(getUidToDocumentCache().getSearchAttribute("VersionFromInstant"))
        .includeAttribute(getUidToDocumentCache().getSearchAttribute("VersionToInstant"))
        .addCriteria(getUidToDocumentCache().getSearchAttribute("ObjectId")
                         .eq(objectId.toString()))
        .addCriteria(getUidToDocumentCache().getSearchAttribute("VersionFromInstant")
                         .le((fromVersion != null ? fromVersion : InstantExtractor.MIN_INSTANT).toString()))
        .addCriteria(getUidToDocumentCache().getSearchAttribute("VersionToInstant")
                         .ge((toVersion != null ? toVersion : InstantExtractor.MAX_INSTANT).toString()))
        .execute();

    for (Result result : results.all()) {
      getUidToDocumentCache().remove(result.getKey());
    }
  }

  /**
   * Call this at the end of a unit test run to clear the state of EHCache.
   * It should not be part of a generic lifecycle method.
   */
  public void shutdown() {
    getUnderlying().changeManager().removeChangeListener(_changeListener);
    getCacheManager().removeCache(getUidToDocumentCache().getName());
  }

  //-------------------------------------------------------------------------

  /**
   * Gets the underlying source of items.
   *
   * @return the underlying source of items, not null
   */
  protected AbstractChangeProvidingMaster<D> getUnderlying() {
    return _underlying;
  }

  /**
   * Gets the cache manager.
   *
   * @return the cache manager, not null
   */
  protected CacheManager getCacheManager() {
    return _cacheManager;
  }

  /**
   * Gets the document by UniqueId cache.
   *
   * @return the cache, not null
   */
  protected Ehcache getUidToDocumentCache() {
    return _uidToDocumentCache;
  }

  @Override
  public ChangeManager changeManager() {
    return _changeManager;
  }

  //-------------------------------------------------------------------------

  @Override
  public String toString() {
    return getClass().getSimpleName() + "[" + getUnderlying() + "]";
  }

}
TOP

Related Classes of com.opengamma.master.cache.AbstractEHCachingMaster

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.