/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.core.position.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.MapMaker;
import com.opengamma.core.change.ChangeManager;
import com.opengamma.core.position.Portfolio;
import com.opengamma.core.position.PortfolioNode;
import com.opengamma.core.position.Position;
import com.opengamma.core.position.PositionSource;
import com.opengamma.core.position.Trade;
import com.opengamma.id.ObjectId;
import com.opengamma.id.UniqueId;
import com.opengamma.id.VersionCorrection;
import com.opengamma.id.VersionCorrectionUtils;
import com.opengamma.id.VersionCorrectionUtils.VersionCorrectionLockListener;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.WeakInstanceCache;
import com.opengamma.util.ehcache.EHCacheUtils;
import com.opengamma.util.map.HashMap2;
import com.opengamma.util.map.Map2;
import com.opengamma.util.map.WeakValueHashMap2;
import com.opengamma.util.tuple.Pair;
import com.opengamma.util.tuple.Pairs;
/**
* A cache decorating a {@code PositionSource}.
* <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 position source will always specify an exact version/correction and versioned unique identifiers.
*/
public class EHCachingPositionSource implements PositionSource {
private static final Logger s_logger = LoggerFactory.getLogger(EHCachingPositionSource.class);
/**
* Cache key for portfolios.
*/
private static final String PORTFOLIO_CACHE = "portfolio";
/**
* Cache key for nodes.
*/
private static final String PORTFOLIONODE_CACHE = "portfolioNode";
/**
* Cache key for positions.
*/
private static final String POSITION_CACHE = "position";
/**
* Cache key for trades.
*/
private static final String TRADE_CACHE = "trade";
/**
* The underlying position source.
*/
private final PositionSource _underlying;
/**
* The cache manager.
*/
private final CacheManager _cacheManager;
/**
* The portfolio cache.
*/
private final Cache _portfolioCache;
/**
* The node cache.
*/
private final Cache _portfolioNodeCache;
/**
* The position cache.
*/
private final Cache _positionCache;
/**
* The trade cache.
*/
private final Cache _tradeCache;
private static class CachedPortfolio implements Portfolio {
private final Map<String, String> _attributes;
private final UniqueId _uniqueId;
private final PortfolioNode _rootNode;
private final String _name;
public CachedPortfolio(final Portfolio original, final PortfolioNode replacementRoot) {
_attributes = original.getAttributes();
_uniqueId = original.getUniqueId();
_rootNode = replacementRoot;
_name = original.getName();
}
@Override
public Map<String, String> getAttributes() {
return _attributes;
}
@Override
public void setAttributes(Map<String, String> attributes) {
throw new UnsupportedOperationException();
}
@Override
public void addAttribute(String key, String value) {
throw new UnsupportedOperationException();
}
@Override
public UniqueId getUniqueId() {
return _uniqueId;
}
@Override
public PortfolioNode getRootNode() {
return _rootNode;
}
@Override
public String getName() {
return _name;
}
}
private final ConcurrentMap<UniqueId, Object> _frontPositionOrTradeCache = new MapMaker().weakValues().makeMap();
private final Map2<VersionCorrection, UniqueId, Object> _frontCacheByUID = new WeakValueHashMap2<VersionCorrection, UniqueId, Object>(HashMap2.STRONG_KEYS);
private final Map2<VersionCorrection, ObjectId, Object> _frontCacheByOID = new WeakValueHashMap2<VersionCorrection, ObjectId, Object>(HashMap2.STRONG_KEYS);
private final VersionCorrectionLockListener _frontCacheCleaner = new VersionCorrectionLockListener() {
@Override
public void versionCorrectionUnlocked(final VersionCorrection unlocked, final Collection<VersionCorrection> stillLocked) {
_frontCacheByUID.retainAllKey1(stillLocked);
_frontCacheByOID.retainAllKey1(stillLocked);
}
};
private final WeakInstanceCache<PortfolioNode> _nodes = new WeakInstanceCache<PortfolioNode>();
/**
* Creates the cache around an underlying position source.
*
* @param underlying the underlying data, not null
* @param cacheManager the cache manager, not null
*/
public EHCachingPositionSource(final PositionSource underlying, final CacheManager cacheManager) {
ArgumentChecker.notNull(underlying, "underlying");
ArgumentChecker.notNull(cacheManager, "cacheManager");
_underlying = underlying;
_cacheManager = cacheManager;
EHCacheUtils.addCache(cacheManager, PORTFOLIO_CACHE);
EHCacheUtils.addCache(cacheManager, PORTFOLIONODE_CACHE);
EHCacheUtils.addCache(cacheManager, POSITION_CACHE);
EHCacheUtils.addCache(cacheManager, TRADE_CACHE);
_portfolioCache = EHCacheUtils.getCacheFromManager(cacheManager, PORTFOLIO_CACHE);
_portfolioNodeCache = EHCacheUtils.getCacheFromManager(cacheManager, PORTFOLIONODE_CACHE);
_positionCache = EHCacheUtils.getCacheFromManager(cacheManager, POSITION_CACHE);
_tradeCache = EHCacheUtils.getCacheFromManager(cacheManager, TRADE_CACHE);
VersionCorrectionUtils.addVersionCorrectionLockListener(_frontCacheCleaner);
}
//-------------------------------------------------------------------------
/**
* Gets the underlying source of positions.
*
* @return the underlying source of positions, not null
*/
protected PositionSource getUnderlying() {
return _underlying;
}
/**
* Gets the cache manager.
*
* @return the cache manager, not null
*/
protected CacheManager getCacheManager() {
return _cacheManager;
}
protected Position addToFrontCache(Position position, final VersionCorrection versionCorrection) {
final Object f = _frontPositionOrTradeCache.putIfAbsent(position.getUniqueId(), position);
Position effectivePosition = (f instanceof Position ? (Position) f : position);
_frontCacheByOID.put(versionCorrection, effectivePosition.getUniqueId().getObjectId(), effectivePosition);
return effectivePosition;
}
protected PortfolioNode addToFrontCache(PortfolioNode node, final VersionCorrection versionCorrection) {
final List<Position> nodePositions = node.getPositions();
List<Position> newPositions = null;
for (int i = 0; i < nodePositions.size(); i++) {
final Position nodePosition = nodePositions.get(i);
final Position newPosition = addToFrontCache(nodePosition, versionCorrection);
if (newPosition != nodePosition) {
if (newPositions == null) {
newPositions = new ArrayList<Position>(nodePositions.size());
for (int j = 0; j < i; j++) {
newPositions.add(nodePositions.get(j));
}
}
newPositions.add(newPosition);
} else {
if (newPositions != null) {
newPositions.add(nodePosition);
}
}
}
final List<PortfolioNode> nodeChildren = node.getChildNodes();
List<PortfolioNode> newChildren = null;
for (int i = 0; i < nodeChildren.size(); i++) {
final PortfolioNode nodeChild = nodeChildren.get(i);
final PortfolioNode newChild = addToFrontCache(nodeChild, versionCorrection);
if (newChild != nodeChild) {
if (newChildren == null) {
newChildren = new ArrayList<PortfolioNode>(nodeChildren.size());
for (int j = 0; j < i; j++) {
newChildren.add(nodeChildren.get(j));
}
}
newChildren.add(newChild);
} else {
if (newChildren != null) {
newChildren.add(nodeChild);
}
}
}
PortfolioNode resultNode = node;
if ((newPositions != null) || (newChildren != null)) {
final SimplePortfolioNode newNode = new SimplePortfolioNode(resultNode.getUniqueId(), resultNode.getName());
newNode.setParentNodeId(resultNode.getParentNodeId());
newNode.addPositions((newPositions != null) ? newPositions : resultNode.getPositions());
newNode.addChildNodes((newChildren != null) ? newChildren : resultNode.getChildNodes());
resultNode = newNode;
}
resultNode = _nodes.get(resultNode);
final Object f = _frontCacheByUID.putIfAbsent(versionCorrection, resultNode.getUniqueId(), resultNode);
if (f instanceof PortfolioNode) {
resultNode = (PortfolioNode) f;
}
return resultNode;
}
protected Portfolio addToFrontCache(Portfolio portfolio, final VersionCorrection versionCorrection) {
final PortfolioNode newRoot = addToFrontCache(portfolio.getRootNode(), versionCorrection);
if (newRoot != portfolio.getRootNode()) {
final Portfolio newPortfolio = new CachedPortfolio(portfolio, newRoot);
return newPortfolio;
} else {
return portfolio;
}
}
@Override
public Portfolio getPortfolio(final UniqueId uniqueId, final VersionCorrection versionCorrection) {
if (versionCorrection.containsLatest()) {
s_logger.debug("getPortfolioByUniqueId: Skipping cache for {}/{}", uniqueId, versionCorrection);
return getUnderlying().getPortfolio(uniqueId, versionCorrection);
}
Object f;
final Pair<UniqueId, VersionCorrection> key;
if (uniqueId.isVersioned()) {
f = _frontCacheByUID.get(versionCorrection, uniqueId);
if (f instanceof Portfolio) {
s_logger.debug("getPortfolioByUniqueId: Front cache hit on {}/{}", uniqueId, versionCorrection);
return (Portfolio) f;
}
key = Pairs.of(uniqueId, versionCorrection);
final Element e = _portfolioCache.get(key);
if (e != null) {
s_logger.debug("getPortfolioByUniqueId: EHCache hit on {}/{}", uniqueId, versionCorrection);
Portfolio portfolio = (Portfolio) e.getObjectValue();
f = _frontCacheByUID.putIfAbsent(versionCorrection, uniqueId, portfolio);
if (f instanceof Portfolio) {
s_logger.debug("getPortfolioByUniqueId: Late front cache hit on {}/{}", uniqueId, versionCorrection);
return (Portfolio) f;
}
portfolio = addToFrontCache(portfolio, versionCorrection);
return portfolio;
} else {
s_logger.debug("getPortfolioByUniqueId: Cache miss on {}/{}", uniqueId, versionCorrection);
}
} else {
s_logger.debug("getPortfolioByUniqueId: Pass through on {}/{}", uniqueId, versionCorrection);
key = null;
}
Portfolio portfolio = getUnderlying().getPortfolio(uniqueId, versionCorrection);
f = _frontCacheByUID.putIfAbsent(versionCorrection, portfolio.getUniqueId(), portfolio);
if (f instanceof Portfolio) {
s_logger.debug("getPortfolioByUniqueId: Late front cache hit on {}/{}", uniqueId, versionCorrection);
return (Portfolio) f;
}
portfolio = addToFrontCache(portfolio, versionCorrection);
if (key != null) {
_portfolioCache.put(new Element(key, portfolio));
} else {
_portfolioCache.put(new Element(Pairs.of(portfolio.getUniqueId(), versionCorrection), portfolio));
}
return portfolio;
}
@Override
public Portfolio getPortfolio(final ObjectId objectId, final VersionCorrection versionCorrection) {
if (versionCorrection.containsLatest()) {
s_logger.debug("getPortfolioByObjectId: Skipping cache for {}/{}", objectId, versionCorrection);
return getUnderlying().getPortfolio(objectId, versionCorrection);
}
Object f = _frontCacheByOID.get(versionCorrection, objectId);
if (f instanceof Portfolio) {
s_logger.debug("getPortfolioByObjectId: Front cache hit on {}/{}", objectId, versionCorrection);
return (Portfolio) f;
}
final Pair<ObjectId, VersionCorrection> key = Pairs.of(objectId, versionCorrection);
final Element e = _portfolioCache.get(key);
if (e != null) {
s_logger.debug("getPortfolioByObjectId: EHCache hit on {}/{}", objectId, versionCorrection);
Portfolio portfolio = (Portfolio) e.getObjectValue();
f = _frontCacheByUID.putIfAbsent(versionCorrection, portfolio.getUniqueId(), portfolio);
if (f instanceof Portfolio) {
s_logger.debug("getPortfolioByObjectId: Late front cache hit on {}/{}", objectId, versionCorrection);
portfolio = (Portfolio) f;
_frontCacheByOID.put(versionCorrection, objectId, portfolio);
return portfolio;
} else {
portfolio = addToFrontCache(portfolio, versionCorrection);
_frontCacheByOID.put(versionCorrection, objectId, portfolio);
return portfolio;
}
} else {
s_logger.debug("getPortfolioByObjectId: Cache miss on {}/{}", objectId, versionCorrection);
Portfolio portfolio = getUnderlying().getPortfolio(objectId, versionCorrection);
f = _frontCacheByUID.putIfAbsent(versionCorrection, portfolio.getUniqueId(), portfolio);
if (f instanceof Portfolio) {
s_logger.debug("getPortfolioByObjectId: Late front cache hit on {}/{}", objectId, versionCorrection);
portfolio = (Portfolio) f;
_frontCacheByOID.put(versionCorrection, objectId, portfolio);
return portfolio;
} else {
portfolio = addToFrontCache(portfolio, versionCorrection);
_frontCacheByOID.put(versionCorrection, objectId, portfolio);
_portfolioCache.put(new Element(key, portfolio));
_portfolioCache.put(new Element(Pairs.of(portfolio.getUniqueId(), versionCorrection), portfolio));
return portfolio;
}
}
}
@Override
public PortfolioNode getPortfolioNode(final UniqueId uniqueId, final VersionCorrection versionCorrection) {
if (versionCorrection.containsLatest()) {
s_logger.debug("getPortfolioNode: Skipping cache for {}/{}", uniqueId, versionCorrection);
return getUnderlying().getPortfolioNode(uniqueId, versionCorrection);
}
Object f;
final Pair<UniqueId, VersionCorrection> key;
if (uniqueId.isVersioned()) {
f = _frontCacheByUID.get(versionCorrection, uniqueId);
if (f instanceof PortfolioNode) {
s_logger.debug("getPortfolioNode: Front cache hit on {}/{}", uniqueId, versionCorrection);
return (PortfolioNode) f;
}
key = Pairs.of(uniqueId, versionCorrection);
final Element e = _portfolioNodeCache.get(key);
if (e != null) {
s_logger.debug("getPortfolioNode: EHCache hit on {}/{}", uniqueId, versionCorrection);
final PortfolioNode node = (PortfolioNode) e.getObjectValue();
return addToFrontCache(node, versionCorrection);
} else {
s_logger.debug("getPortfolioNode: EHCache miss on {}/{}", uniqueId, versionCorrection);
}
} else {
s_logger.debug("getPortfolioNode: Pass through on {}/{}", uniqueId, versionCorrection);
key = null;
}
final PortfolioNode node = getUnderlying().getPortfolioNode(uniqueId, versionCorrection);
f = addToFrontCache(node, versionCorrection);
if (f != node) {
s_logger.debug("getPortfolioNode: Late front cache hit on {}/{}", uniqueId, versionCorrection);
return (PortfolioNode) f;
}
if (key != null) {
_portfolioNodeCache.put(new Element(key, node));
} else {
_portfolioNodeCache.put(new Element(Pairs.of(node.getUniqueId(), versionCorrection), node));
}
return node;
}
@Override
public Position getPosition(final UniqueId uniqueId) {
Object f;
if (uniqueId.isVersioned()) {
f = _frontPositionOrTradeCache.get(uniqueId);
if (f instanceof Position) {
s_logger.debug("getPositionByUniqueId: Front cache hit on {}", uniqueId);
return (Position) f;
}
final Element e = _positionCache.get(uniqueId);
if (e != null) {
s_logger.debug("getPositionByUniqueId: EHCache hit on {}", uniqueId);
final Position position = (Position) e.getObjectValue();
f = _frontPositionOrTradeCache.putIfAbsent(uniqueId, position);
if (f instanceof Position) {
s_logger.debug("getPositionByUniqueId: Late front cache hit on {}", uniqueId);
return (Position) f;
} else {
return position;
}
}
} else {
s_logger.debug("getPositionByUniqueId: Pass through on {}", uniqueId);
}
final Position position = getUnderlying().getPosition(uniqueId);
f = _frontPositionOrTradeCache.putIfAbsent(position.getUniqueId(), position);
if (f instanceof Position) {
s_logger.debug("getPositionByUniqueId: Late front cache hit on {}", uniqueId);
return (Position) f;
}
_positionCache.put(new Element(position.getUniqueId(), position));
return position;
}
@Override
public Position getPosition(final ObjectId positionId, final VersionCorrection versionCorrection) {
if (versionCorrection.containsLatest()) {
s_logger.debug("getPositionByObjectId: Skipping cache for {}/{}", positionId, versionCorrection);
return getUnderlying().getPosition(positionId, versionCorrection);
}
Object f = _frontCacheByOID.get(versionCorrection, positionId);
if (f instanceof Position) {
s_logger.debug("getPositionByObjectId: Front cache hit on {}/{}", positionId, versionCorrection);
return (Position) f;
}
final Pair<ObjectId, VersionCorrection> key = Pairs.of(positionId, versionCorrection);
final Element e = _positionCache.get(key);
if (e != null) {
s_logger.debug("getPositionByObjectId: EHCache hit on {}/{}", positionId, versionCorrection);
final Position position = (Position) e.getObjectValue();
f = _frontCacheByOID.putIfAbsent(versionCorrection, positionId, position);
if (f instanceof Position) {
s_logger.debug("getPositionByObjectId: Late front cache hit on {}/{}", positionId, versionCorrection);
return (Position) f;
} else {
return position;
}
} else {
s_logger.debug("getPositionByObjectId: Cache miss on {}/{}", positionId, versionCorrection);
final Position position = getUnderlying().getPosition(positionId, versionCorrection);
f = _frontPositionOrTradeCache.putIfAbsent(position.getUniqueId(), position);
if (f instanceof Position) {
s_logger.debug("getPositionByObjectId: Late front cache hit on {}/{}", positionId, versionCorrection);
_frontCacheByOID.put(versionCorrection, positionId, f);
return (Position) f;
} else {
_frontCacheByOID.put(versionCorrection, positionId, position);
_positionCache.put(new Element(key, position));
_positionCache.put(new Element(position.getUniqueId(), position));
return position;
}
}
}
@Override
public Trade getTrade(final UniqueId uniqueId) {
Object f;
if (uniqueId.isVersioned()) {
f = _frontPositionOrTradeCache.get(uniqueId);
if (f instanceof Trade) {
s_logger.debug("getTradeByUniqueId: Front cache hit on {}", uniqueId);
return (Trade) f;
}
final Element e = _tradeCache.get(uniqueId);
if (e != null) {
s_logger.debug("getTradeByUniqueId: EHCache hit on {}", uniqueId);
final Trade trade = (Trade) e.getObjectValue();
f = _frontPositionOrTradeCache.putIfAbsent(uniqueId, trade);
if (f instanceof Trade) {
s_logger.debug("getTradeByUniqueId: Late front cache hit on {}", uniqueId);
return (Trade) f;
} else {
return trade;
}
} else {
s_logger.debug("getTradeByUniqueId: Cache miss on {}", uniqueId);
}
} else {
s_logger.debug("getTradeByUniqueId: Pass through on {}", uniqueId);
}
final Trade trade = getUnderlying().getTrade(uniqueId);
f = _frontPositionOrTradeCache.putIfAbsent(trade.getUniqueId(), trade);
if (f instanceof Trade) {
s_logger.debug("getTradeByUniqueId: Late front cache hit on {}", uniqueId);
return (Trade) f;
}
_tradeCache.put(new Element(trade.getUniqueId(), trade));
return trade;
}
@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(PORTFOLIO_CACHE);
_cacheManager.removeCache(PORTFOLIONODE_CACHE);
_cacheManager.removeCache(POSITION_CACHE);
_cacheManager.removeCache(TRADE_CACHE);
_frontCacheByUID.clear();
_frontCacheByOID.clear();
}
@Override
public String toString() {
return getClass().getSimpleName() + "[" + getUnderlying() + "]";
}
}