/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.core.legalentity.impl;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import com.google.common.collect.MapMaker;
import com.opengamma.core.change.ChangeManager;
import com.opengamma.core.legalentity.LegalEntity;
import com.opengamma.core.legalentity.LegalEntitySource;
import com.opengamma.id.ExternalId;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.id.ObjectId;
import com.opengamma.id.UniqueId;
import com.opengamma.id.VersionCorrection;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.ehcache.EHCacheUtils;
import com.opengamma.util.tuple.Pair;
import com.opengamma.util.tuple.Pairs;
/**
* A cache decorating a {@code LegalEntitySource}.
* <p>
* The cache is implemented using {@code EHCache}.
* <p>
* Any requests with a "latest" version/correction or unversioned unique identifier are not cached
* and will always hit the underlying. This should not be an issue in practice as the engine components
* which use the legal entity source will always specify an exact version/correction and versioned unique identifiers.
*/
public class EHCachingLegalEntitySource implements LegalEntitySource {
/**
* Cache key for legal entities.
*/
private static final String LEGALENTITY_CACHE = "legalentity";
/**
* The underlying legal entity source.
*/
private final LegalEntitySource _underlying;
/**
* The cache manager.
*/
private final CacheManager _cacheManager;
/**
* The legal entity cache.
*/
private final Cache _legalentityCache;
/**
* The front cache.
*/
private final ConcurrentMap<Object, LegalEntity> _frontCache = new MapMaker().weakValues().makeMap();
/**
* Creates the cache around an underlying legal entity source.
*
* @param underlying the underlying data, not null
* @param cacheManager the cache manager, not null
*/
public EHCachingLegalEntitySource(final LegalEntitySource underlying, final CacheManager cacheManager) {
ArgumentChecker.notNull(underlying, "underlying");
ArgumentChecker.notNull(cacheManager, "cacheManager");
_underlying = underlying;
_cacheManager = cacheManager;
EHCacheUtils.addCache(cacheManager, LEGALENTITY_CACHE);
_legalentityCache = EHCacheUtils.getCacheFromManager(cacheManager, LEGALENTITY_CACHE);
}
//-------------------------------------------------------------------------
/**
* Gets the underlying source of legal entities.
*
* @return the underlying source of legal entities, not null
*/
protected LegalEntitySource getUnderlying() {
return _underlying;
}
/**
* Gets the cache manager.
*
* @return the cache manager, not null
*/
protected CacheManager getCacheManager() {
return _cacheManager;
}
/**
* For use by test methods only to control the front cache.
*/
void emptyFrontCache() {
_frontCache.clear();
}
/**
* For use by test methods only to control the EH cache.
*/
void emptyEHCache() {
EHCacheUtils.clear(getCacheManager(), LEGALENTITY_CACHE);
}
//-------------------------------------------------------------------------
protected LegalEntity addToFrontCache(LegalEntity legalentity, VersionCorrection versionCorrection) {
if (legalentity.getUniqueId().isLatest()) {
return legalentity;
}
final LegalEntity existing = _frontCache.putIfAbsent(legalentity.getUniqueId(), legalentity);
if (existing != null) {
return existing;
}
if (versionCorrection != null) {
_frontCache.put(Pairs.of(legalentity.getExternalIdBundle(), versionCorrection), legalentity);
_frontCache.put(Pairs.of(legalentity.getUniqueId().getObjectId(), versionCorrection), legalentity);
}
return legalentity;
}
protected LegalEntity addToCache(LegalEntity legalentity, VersionCorrection versionCorrection) {
final LegalEntity front = addToFrontCache(legalentity, null);
if (front == legalentity) {
if (legalentity.getUniqueId().isVersioned()) {
_legalentityCache.put(new Element(legalentity.getUniqueId(), legalentity));
}
if (versionCorrection != null) {
_legalentityCache.put(new Element(Pairs.of(legalentity.getExternalIdBundle(), versionCorrection), legalentity));
_legalentityCache.put(new Element(Pairs.of(legalentity.getUniqueId().getObjectId(), versionCorrection), legalentity));
}
}
return front;
}
//-------------------------------------------------------------------------
@Override
public LegalEntity get(UniqueId uniqueId) {
ArgumentChecker.notNull(uniqueId, "uniqueId");
// check cache, but not if latest
if (uniqueId.isVersioned()) {
LegalEntity cached = _frontCache.get(uniqueId);
if (cached != null) {
return cached;
}
final Element e = _legalentityCache.get(uniqueId);
if (e != null) {
cached = (LegalEntity) e.getObjectValue();
return addToFrontCache(cached, null);
}
}
// query underlying
LegalEntity legalEntity = getUnderlying().get(uniqueId);
return addToCache(legalEntity, null);
}
@Override
public LegalEntity get(ObjectId objectId, VersionCorrection versionCorrection) {
ArgumentChecker.notNull(objectId, "objectId");
ArgumentChecker.notNull(versionCorrection, "versionCorrection");
// latest not in cache, can cache only by uniqueId
if (versionCorrection.containsLatest()) {
return addToCache(getUnderlying().get(objectId, versionCorrection), null);
}
// check cache
final Pair<ObjectId, VersionCorrection> key = Pairs.of(objectId, versionCorrection);
LegalEntity cached = _frontCache.get(key);
if (cached != null) {
return cached;
}
final Element e = _legalentityCache.get(key);
if (e != null) {
cached = (LegalEntity) e.getObjectValue();
return addToFrontCache(cached, versionCorrection);
}
// query underlying
LegalEntity legalEntity = getUnderlying().get(objectId, versionCorrection);
return addToCache(legalEntity, versionCorrection);
}
//-------------------------------------------------------------------------
@Override
public LegalEntity getSingle(ExternalId externalId) {
ArgumentChecker.notNull(externalId, "externalId");
return getSingle(externalId.toBundle(), VersionCorrection.LATEST);
}
@Override
public LegalEntity getSingle(ExternalIdBundle bundle) {
ArgumentChecker.notNull(bundle, "bundle");
return getSingle(bundle, VersionCorrection.LATEST);
}
@Override
public LegalEntity getSingle(ExternalIdBundle bundle, VersionCorrection versionCorrection) {
ArgumentChecker.notNull(bundle, "bundle");
ArgumentChecker.notNull(versionCorrection, "versionCorrection");
// latest not in cache, can cache only by uniqueId
if (versionCorrection.containsLatest()) {
return addToCache(getUnderlying().getSingle(bundle, versionCorrection), null);
}
// check cache
final Pair<ExternalIdBundle, VersionCorrection> key = Pairs.of(bundle, versionCorrection);
LegalEntity cached = _frontCache.get(key);
if (cached != null) {
return cached;
}
final Element e = _legalentityCache.get(key);
if (e != null) {
cached = (LegalEntity) e.getObjectValue();
return addToFrontCache(cached, versionCorrection);
}
// query underlying
LegalEntity legalEntity = getUnderlying().getSingle(bundle, versionCorrection);
return addToCache(legalEntity, versionCorrection);
}
//-------------------------------------------------------------------------
@Override
@SuppressWarnings("deprecation")
public Collection<LegalEntity> get(ExternalIdBundle bundle) {
return getUnderlying().get(bundle);
}
@Override
public Collection<LegalEntity> get(ExternalIdBundle bundle, VersionCorrection versionCorrection) {
return getUnderlying().get(bundle, versionCorrection);
}
//-------------------------------------------------------------------------
@Override
public Map<UniqueId, LegalEntity> get(Collection<UniqueId> uniqueIds) {
Map<UniqueId, LegalEntity> map = getUnderlying().get(uniqueIds);
for (Entry<UniqueId, LegalEntity> entry : map.entrySet()) {
entry.setValue(addToCache(entry.getValue(), null));
}
return map;
}
@Override
public Map<ObjectId, LegalEntity> get(Collection<ObjectId> objectIds, VersionCorrection versionCorrection) {
Map<ObjectId, LegalEntity> map = getUnderlying().get(objectIds, versionCorrection);
for (Entry<ObjectId, LegalEntity> entry : map.entrySet()) {
entry.setValue(addToCache(entry.getValue(), versionCorrection));
}
return map;
}
@Override
public Map<ExternalIdBundle, Collection<LegalEntity>> getAll(Collection<ExternalIdBundle> bundles, VersionCorrection versionCorrection) {
return getUnderlying().getAll(bundles, versionCorrection);
}
@Override
public Map<ExternalIdBundle, LegalEntity> getSingle(Collection<ExternalIdBundle> bundles, VersionCorrection versionCorrection) {
Map<ExternalIdBundle, LegalEntity> map = getUnderlying().getSingle(bundles, versionCorrection);
for (Entry<ExternalIdBundle, LegalEntity> entry : map.entrySet()) {
entry.setValue(addToCache(entry.getValue(), versionCorrection));
}
return map;
}
//-------------------------------------------------------------------------
@Override
public ChangeManager changeManager() {
return getUnderlying().changeManager();
}
/**
* 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.
*/
protected void shutdown() {
_cacheManager.removeCache(LEGALENTITY_CACHE);
_frontCache.clear();
}
@Override
public String toString() {
return getClass().getSimpleName() + "[" + getUnderlying() + "]";
}
}