/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.engine.view.compilation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.core.LinkUtils;
import com.opengamma.core.change.ChangeManager;
import com.opengamma.core.change.DummyChangeManager;
import com.opengamma.core.position.PortfolioNode;
import com.opengamma.core.position.Position;
import com.opengamma.core.position.Trade;
import com.opengamma.core.position.impl.AbstractPortfolioNodeTraversalCallback;
import com.opengamma.core.position.impl.PortfolioNodeTraverser;
import com.opengamma.core.security.AbstractSecuritySource;
import com.opengamma.core.security.Security;
import com.opengamma.core.security.SecurityLink;
import com.opengamma.core.security.SecuritySource;
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.tuple.Pair;
import com.opengamma.util.tuple.Pairs;
/**
* Utility to resolve security links in bulk.
*/
public final class SecurityLinkResolver {
/** Logger. */
private static final Logger s_logger = LoggerFactory.getLogger(SecurityLinkResolver.class);
/**
* The executor service.
*/
private final ExecutorService _executorService;
/**
* The caching security source.
*/
private final CachedSecuritySource _securitySource;
/**
* The version-correction.
*/
private final VersionCorrection _versionCorrection;
/**
* Creates an instance.
*
* @param executorService the threading service, not null
* @param securitySource the security source, not null
* @param versionCorrection the version correction, not null
*/
public SecurityLinkResolver(final ExecutorService executorService, final SecuritySource securitySource, final VersionCorrection versionCorrection) {
ArgumentChecker.notNull(executorService, "executorService");
ArgumentChecker.notNull(securitySource, "securitySource");
_executorService = executorService;
_securitySource = new CachedSecuritySource(securitySource);
_versionCorrection = versionCorrection;
}
/**
* Creates an instance.
*
* @param viewCompilationContext the context, not null
* @param versionCorrection the version-correction, not null
*/
public SecurityLinkResolver(final ViewCompilationContext viewCompilationContext, VersionCorrection versionCorrection) {
this(viewCompilationContext.getServices().getExecutorService().asService(), viewCompilationContext.getServices().getFunctionCompilationContext().getSecuritySource(),
versionCorrection);
}
//-------------------------------------------------------------------------
/**
* Resolves security links in bulk.
* <p>
* Some caching of securities occurs within this instance.
*
* @param securityLinks the bundles to lookup, not null
* @throws RuntimeException if unable to resolve all the securities
*/
@SuppressWarnings("unchecked")
public void resolveSecurities(final Collection<SecurityLink> securityLinks) {
ArgumentChecker.noNulls(securityLinks, "securityLinks");
final ExecutorCompletionService<Pair<ObjectId, ExternalIdBundle>> completionService = new ExecutorCompletionService<Pair<ObjectId, ExternalIdBundle>>(_executorService);
// Filter the links down to collections of "identical" ones; resolving the same underlying.
final Map<Pair<ObjectId, ExternalIdBundle>, Object> securityLinkMap = new HashMap<Pair<ObjectId, ExternalIdBundle>, Object>();
for (SecurityLink link : securityLinks) {
final Security security = link.getTarget();
if (security == null) {
final Pair<ObjectId, ExternalIdBundle> key = Pairs.of(link.getObjectId(), link.getExternalId());
if (securityLinkMap.containsKey(key)) {
final Object sameLinkObject = securityLinkMap.get(key);
if (sameLinkObject instanceof Collection<?>) {
final Collection<SecurityLink> sameLinks = (Collection<SecurityLink>) sameLinkObject;
for (SecurityLink sameLink : sameLinks) {
if (sameLink == link) {
link = null;
break;
}
}
if (link != null) {
sameLinks.add(link);
}
} else {
if (sameLinkObject != link) {
final Collection<SecurityLink> sameLinks = new ArrayList<SecurityLink>();
final SecurityLink sameLink = (SecurityLink) sameLinkObject;
sameLinks.add(sameLink);
sameLinks.add(link);
securityLinkMap.put(key, sameLinks);
}
}
} else {
securityLinkMap.put(key, link);
}
} else if (security.getUniqueId() != null) {
_securitySource.addToCache(security);
}
}
s_logger.debug("Submitting {} resolution jobs for {} links", securityLinkMap.size(), securityLinks.size());
// Submit a job for each "unique" link. The job will serially resolve all "identical" links as they will
// be in the cache at that point.
for (Map.Entry<Pair<ObjectId, ExternalIdBundle>, Object> linkEntry : securityLinkMap.entrySet()) {
final Callable<Pair<ObjectId, ExternalIdBundle>> job;
if (linkEntry.getValue() instanceof Collection<?>) {
job = new MultipleSecurityResolutionJob(linkEntry.getKey(), (Collection<SecurityLink>) linkEntry.getValue(), _securitySource, _versionCorrection);
} else {
job = new SingleSecurityResolutionJob(linkEntry.getKey(), (SecurityLink) linkEntry.getValue(), _securitySource, _versionCorrection);
}
linkEntry.setValue(completionService.submit(job));
}
// Wait for the jobs to complete.
while (!securityLinkMap.isEmpty()) {
try {
final Future<Pair<ObjectId, ExternalIdBundle>> future = completionService.take();
final Pair<ObjectId, ExternalIdBundle> key = future.get();
if (securityLinkMap.remove(key) == null) {
s_logger.warn("Completion key {} wasn't in the job map {}", key, securityLinkMap);
throw new OpenGammaRuntimeException("Internal error resolving securities");
}
} catch (InterruptedException ex) {
Thread.interrupted();
s_logger.warn("Interrupted, so didn't finish resolution");
break;
} catch (Exception ex) {
s_logger.warn("Unable to resolve security", ex);
break;
}
}
if (!securityLinkMap.isEmpty()) {
for (Object future : securityLinkMap.values()) {
((Future<SecurityLink>) future).cancel(false);
}
throw new OpenGammaRuntimeException("Unable to resolve all securities. Missing: " + securityLinkMap);
}
}
//-------------------------------------------------------------------------
/**
* Resolves a security link making use of the caching of this instance.
* <p>
* Underlying securities are not resolved.
*
* @param link the link to resolve, not null
* @return the resolved security, not null
* @throws RuntimeException if unable to resolve the security
*/
public Security resolveSecurity(final SecurityLink link) {
return link.resolve(_securitySource, _versionCorrection);
}
/**
* Resolves security links on a position and associated trades.
* <p>
* Underlying securities are not resolved. Some caching of securities occurs within this instance.
*
* @param position the position to resolve, not null
* @throws RuntimeException if unable to resolve all the securities
*/
public void resolveSecurities(final Position position) {
Collection<SecurityLink> links = new ArrayList<SecurityLink>(position.getTrades().size() + 1);
if (LinkUtils.isValid(position.getSecurityLink())) {
links.add(position.getSecurityLink());
} else {
s_logger.warn("Invalid link on position {}", position.getUniqueId());
}
for (Trade trade : position.getTrades()) {
if (LinkUtils.isValid(trade.getSecurityLink())) {
links.add(trade.getSecurityLink());
} else {
s_logger.warn("Invalid link on trade {} within position {}", trade.getUniqueId(), position.getUniqueId());
}
}
resolveSecurities(links);
}
/**
* Resolves security links on the positions and trades of a portfolio node.
* <p>
* Underlying securities are not resolved. Some caching of securities occurs within this instance.
*
* @param node the node to resolve, not null
* @throws RuntimeException if unable to resolve all the securities
*/
public void resolveSecurities(final PortfolioNode node) {
final Collection<SecurityLink> links = new ArrayList<SecurityLink>(256);
PortfolioNodeTraverser.depthFirst(new AbstractPortfolioNodeTraversalCallback() {
@Override
public void preOrderOperation(final PortfolioNode parentNode, final Position position) {
if (LinkUtils.isValid(position.getSecurityLink())) {
links.add(position.getSecurityLink());
} else {
s_logger.warn("Invalid link on position {}", position.getUniqueId());
}
for (Trade trade : position.getTrades()) {
if (LinkUtils.isValid(trade.getSecurityLink())) {
links.add(trade.getSecurityLink());
} else {
s_logger.warn("Invalid link in trade {} associated with position {}", trade.getUniqueId(), position.getUniqueId());
}
}
}
}).traverse(node);
resolveSecurities(links);
}
//-------------------------------------------------------------------------
/**
* A small job that can be run in an executor to resolve a security against a security source.
*/
private static final class SingleSecurityResolutionJob implements Callable<Pair<ObjectId, ExternalIdBundle>> {
private final Pair<ObjectId, ExternalIdBundle> _key;
private final SecurityLink _link;
private final SecuritySource _securitySource;
private final VersionCorrection _versionCorrection;
private SingleSecurityResolutionJob(Pair<ObjectId, ExternalIdBundle> key, SecurityLink link, SecuritySource securitySource, VersionCorrection versionCorrection) {
_key = key;
_securitySource = securitySource;
_link = link;
_versionCorrection = versionCorrection;
}
@Override
public Pair<ObjectId, ExternalIdBundle> call() {
_link.resolve(_securitySource, _versionCorrection);
return _key;
}
}
/**
* Resolves a sequence of links.
*/
private static final class MultipleSecurityResolutionJob implements Callable<Pair<ObjectId, ExternalIdBundle>> {
private final Pair<ObjectId, ExternalIdBundle> _key;
private final Collection<SecurityLink> _links;
private final SecuritySource _securitySource;
private final VersionCorrection _versionCorrection;
private MultipleSecurityResolutionJob(final Pair<ObjectId, ExternalIdBundle> key, final Collection<SecurityLink> link, final SecuritySource securitySource,
final VersionCorrection versionCorrection) {
_key = key;
_links = link;
_securitySource = securitySource;
_versionCorrection = versionCorrection;
}
@Override
public Pair<ObjectId, ExternalIdBundle> call() {
for (SecurityLink link : _links) {
link.resolve(_securitySource, _versionCorrection);
}
return _key;
}
}
//-------------------------------------------------------------------------
/**
* Encapsulates caching.
* <p>
* This is designed to be used by a single resolution pass, for the efficiency of resolving the same security multiple times.
*/
static class CachedSecuritySource extends AbstractSecuritySource implements SecuritySource {
private final SecuritySource _underlying;
private final ConcurrentMap<ObjectId, Security> _objectIdCache = new ConcurrentHashMap<ObjectId, Security>();
private final ConcurrentMap<ExternalIdBundle, Security> _weakIdCache = new ConcurrentHashMap<ExternalIdBundle, Security>();
CachedSecuritySource(SecuritySource underlying) {
_underlying = underlying;
}
void addToCache(Security security) {
if (security.getUniqueId() != null) {
_objectIdCache.put(security.getUniqueId().getObjectId(), security);
}
}
@Override
public Security get(UniqueId uniqueId) {
Security security = _objectIdCache.get(uniqueId.getObjectId());
if (security == null) {
security = _underlying.get(uniqueId);
_objectIdCache.putIfAbsent(uniqueId.getObjectId(), security);
}
return security;
}
@Override
public Security get(ObjectId objectId, VersionCorrection versionCorrection) {
Security security = _objectIdCache.get(objectId);
if (security == null) {
security = _underlying.get(objectId, versionCorrection);
_objectIdCache.putIfAbsent(objectId, security);
}
return security;
}
@Override
public Collection<Security> get(ExternalIdBundle bundle) {
return _underlying.get(bundle);
}
@Override
public Collection<Security> get(ExternalIdBundle bundle, VersionCorrection versionCorrection) {
return _underlying.get(bundle, versionCorrection);
}
@Override
public Security getSingle(ExternalIdBundle bundle) {
Security security = _weakIdCache.get(bundle);
if (security == null) {
security = _underlying.getSingle(bundle);
if (security != null) {
_weakIdCache.putIfAbsent(bundle, security);
}
}
return security;
}
@Override
public Security getSingle(ExternalIdBundle bundle, VersionCorrection versionCorrection) {
Security security = _weakIdCache.get(bundle);
if (security == null) {
security = _underlying.getSingle(bundle, versionCorrection);
if (security != null) {
_weakIdCache.putIfAbsent(bundle, security);
}
}
return security;
}
@Override
public ChangeManager changeManager() {
return DummyChangeManager.INSTANCE;
}
}
}