/**
* Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.portfolio.save;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import org.apache.commons.lang.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.opengamma.core.position.Portfolio;
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.id.ExternalId;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.id.ObjectId;
import com.opengamma.id.UniqueId;
import com.opengamma.master.DocumentVisibility;
import com.opengamma.master.portfolio.ManageablePortfolio;
import com.opengamma.master.portfolio.ManageablePortfolioNode;
import com.opengamma.master.portfolio.PortfolioDocument;
import com.opengamma.master.portfolio.PortfolioMaster;
import com.opengamma.master.portfolio.PortfolioSearchRequest;
import com.opengamma.master.portfolio.PortfolioSearchResult;
import com.opengamma.master.position.ManageablePosition;
import com.opengamma.master.position.ManageableTrade;
import com.opengamma.master.position.PositionDocument;
import com.opengamma.master.position.PositionMaster;
import com.opengamma.master.position.PositionSearchRequest;
import com.opengamma.master.position.PositionSearchResult;
import com.opengamma.master.security.ManageableSecurityLink;
import com.opengamma.util.tuple.Pair;
/**
* Utility to save a portfolio.
*/
public class SavePortfolio {
private static final Logger s_logger = LoggerFactory.getLogger(SavePortfolio.class);
private final ExecutorService _executor;
private final PortfolioMaster _portfolios;
private final PositionMaster _positions;
private final Map<UniqueId, ObjectId> _positionMap = new HashMap<UniqueId, ObjectId>();
private final boolean _rewriteExistingPositions;
private static final ConcurrentMap<ExternalId, ObjectId> s_cache = new ConcurrentHashMap<ExternalId, ObjectId>();
private static final ObjectId MISSING = ObjectId.of("SavePortfolio", "MISSING_VALUE");
// TODO: cache this properly with EHCache or something or there may be a memory leak
public SavePortfolio(final ExecutorService executor, final PortfolioMaster portfolios, final PositionMaster positions) {
this(executor, portfolios, positions, false);
}
protected SavePortfolio(final ExecutorService executor, final PortfolioMaster portfolios, final PositionMaster positions, final boolean rewriteExistingPositions) {
_executor = executor;
_portfolios = portfolios;
_positions = positions;
_rewriteExistingPositions = rewriteExistingPositions;
}
protected ExternalIdBundle mapSecurityKey(final ExternalIdBundle securityKey) {
return null;
}
protected ManageablePosition createManageablePosition(final Position position) {
final ManageablePosition manageablePosition = new ManageablePosition();
manageablePosition.setQuantity(position.getQuantity());
manageablePosition.setSecurityLink(new ManageableSecurityLink(position.getSecurityLink()));
manageablePosition.setAttributes(position.getAttributes());
final Collection<Trade> trades = position.getTrades();
final List<ManageableTrade> manageableTrades = new ArrayList<ManageableTrade>(trades.size());
for (Trade trade : trades) {
final ManageableTrade mtrade = new ManageableTrade(trade);
final ExternalIdBundle replacementKey = mapSecurityKey(mtrade.getSecurityLink().getExternalId());
if (replacementKey != null) {
mtrade.getSecurityLink().setExternalId(replacementKey);
}
mtrade.setAttributes(trade.getAttributes());
manageableTrades.add(mtrade);
}
manageablePosition.setTrades(manageableTrades);
final String providerIdFieldName = manageablePosition.providerId().name();
if (position.getAttributes().containsKey(providerIdFieldName)) {
// this is here to preserve the provider id when round-tripping to and from the resolved vs managed positions.
manageablePosition.setProviderId(ExternalId.parse(position.getAttributes().get(providerIdFieldName)));
} else {
manageablePosition.setProviderId(position.getUniqueId().toExternalId());
}
return manageablePosition;
}
private void populatePositionMapCache(final PortfolioNode node) {
final List<Future<Pair<UniqueId, ObjectId>>> futures = new LinkedList<Future<Pair<UniqueId, ObjectId>>>();
PortfolioNodeTraverser.depthFirst(new AbstractPortfolioNodeTraversalCallback() {
@Override
public void preOrderOperation(final PortfolioNode parentNode, final Position position) {
final ExternalId positionId = position.getUniqueId().toExternalId();
ObjectId id = s_cache.get(positionId);
if (id == null) {
futures.add(_executor.submit(new Callable<Pair<UniqueId, ObjectId>>() {
@Override
public Pair<UniqueId, ObjectId> call() throws Exception {
final PositionSearchRequest searchRequest = new PositionSearchRequest();
searchRequest.setPositionProviderId(positionId);
final PositionSearchResult searchResult = _positions.search(searchRequest);
ObjectId id = null;
if (searchResult.getFirstPosition() != null) {
id = searchResult.getFirstPosition().getUniqueId().getObjectId();
s_logger.debug("Found position {} in master at {}", position, id);
}
if (id == null) {
s_cache.putIfAbsent(positionId, MISSING);
} else {
s_cache.putIfAbsent(positionId, id);
}
return Pair.of(position.getUniqueId(), id);
}
}));
} else if (id == MISSING) {
_positionMap.put(position.getUniqueId(), null);
} else {
_positionMap.put(position.getUniqueId(), id);
}
}
}).traverse(node);
if (futures.isEmpty()) {
return;
}
s_logger.info("{} operations to populate cache", futures.size());
Iterator<Future<Pair<UniqueId, ObjectId>>> futureItr = futures.iterator();
while (futureItr.hasNext()) {
final Future<Pair<UniqueId, ObjectId>> future = futureItr.next();
try {
final Pair<UniqueId, ObjectId> value = future.get();
futureItr.remove();
_positionMap.put(value.getFirst(), value.getSecond());
} catch (final InterruptedException e) {
s_logger.warn("Interrupted", e);
break;
} catch (final ExecutionException e) {
s_logger.warn("Exception", e);
break;
}
}
futureItr = futures.iterator();
while (futureItr.hasNext()) {
final Future<?> future = futureItr.next();
future.cancel(false);
}
}
protected ObjectId mapPositionIdentifier(final Position position) {
ObjectId id = _positionMap.get(position.getUniqueId());
if (id == null) {
s_logger.debug("Adding position {} to master", position);
id = _positions.add(new PositionDocument(createManageablePosition(position))).getUniqueId().getObjectId();
_positionMap.put(position.getUniqueId(), id);
s_cache.put(position.getUniqueId().toExternalId(), id);
} else {
s_logger.debug("Position {} already in master at {}", position, id);
}
return id;
}
private ManageablePortfolioNode createManageablePortfolioNode(final PortfolioNode node) {
final ManageablePortfolioNode manageableNode = new ManageablePortfolioNode();
manageableNode.setName(node.getName());
final List<PortfolioNode> childNodes = node.getChildNodes();
final List<ManageablePortfolioNode> manageableChildNodes = new ArrayList<ManageablePortfolioNode>(childNodes.size());
// TODO: put a hook here so a sub-class can choose to flatten the portfolio if it wishes
for (PortfolioNode childNode : childNodes) {
manageableChildNodes.add(createManageablePortfolioNode(childNode));
}
manageableNode.setChildNodes(manageableChildNodes);
final List<Position> positions = node.getPositions();
final List<ObjectId> positionIdentifiers = new ArrayList<ObjectId>(positions.size());
for (Position position : positions) {
positionIdentifiers.add(mapPositionIdentifier(position));
}
manageableNode.setPositionIds(positionIdentifiers);
return manageableNode;
}
private ManageablePortfolio createManageablePortfolio(final Portfolio portfolio) {
if (!_rewriteExistingPositions) {
populatePositionMapCache(portfolio.getRootNode());
}
final ManageablePortfolio manageablePortfolio = new ManageablePortfolio();
manageablePortfolio.setName(getPortfolioName(portfolio));
manageablePortfolio.setRootNode(createManageablePortfolioNode(portfolio.getRootNode()));
manageablePortfolio.setAttributes(portfolio.getAttributes());
return manageablePortfolio;
}
protected String getPortfolioName(final Portfolio portfolio) {
return portfolio.getName();
}
private boolean nodesEqual(final ManageablePortfolioNode node1, final ManageablePortfolioNode node2) {
if (!ObjectUtils.equals(node1.getName(), node2.getName())) {
return false;
}
final List<ManageablePortfolioNode> children1 = node1.getChildNodes(), children2 = node2.getChildNodes();
if (children1.size() != children2.size()) {
return false;
}
if (!ObjectUtils.equals(node1.getPositionIds(), node2.getPositionIds())) {
return false;
}
final Iterator<ManageablePortfolioNode> itr1 = children1.iterator(), itr2 = children2.iterator();
while (itr1.hasNext() && itr2.hasNext()) {
if (!nodesEqual(itr1.next(), itr2.next())) {
return false;
}
}
return true;
}
public UniqueId savePortfolio(final Portfolio portfolio, final boolean updateMatchingName) {
return savePortfolio(portfolio, updateMatchingName, DocumentVisibility.VISIBLE);
}
public UniqueId savePortfolio(final Portfolio portfolio, final boolean updateMatchingName, final DocumentVisibility visibility) {
s_logger.debug("Saving portfolio '{}'", portfolio.getName());
final PortfolioSearchRequest request = new PortfolioSearchRequest();
request.setName(getPortfolioName(portfolio));
request.setVisibility(visibility); // Any existing match needs to be at least as visible
final PortfolioSearchResult result = _portfolios.search(request);
final ManageablePortfolio manageablePortfolio = createManageablePortfolio(portfolio);
PortfolioDocument document;
if (updateMatchingName) {
document = result.getFirstDocument();
// TODO why did this assume document will never be null? is that valid or have I broken something?
if (document != null) {
final ManageablePortfolio resultPortfolio = document.getPortfolio();
if (nodesEqual(manageablePortfolio.getRootNode(), resultPortfolio.getRootNode())) {
s_logger.debug("Found existing match at {}", document.getUniqueId());
return document.getUniqueId();
}
}
} else {
document = null;
for (PortfolioDocument resultDocument : result.getDocuments()) {
final ManageablePortfolio resultPortfolio = resultDocument.getPortfolio();
if (manageablePortfolio.getName().equals(resultPortfolio.getName()) && nodesEqual(manageablePortfolio.getRootNode(), resultPortfolio.getRootNode())) {
s_logger.debug("Found existing match at {}", resultDocument.getUniqueId());
return resultDocument.getUniqueId();
}
}
}
if (document == null) {
s_logger.debug("Adding to master");
document = new PortfolioDocument(manageablePortfolio);
document.setVisibility(visibility);
document = _portfolios.add(document);
} else {
s_logger.debug("Updating {} within master", document.getUniqueId());
// Retain existing visibility
document.setPortfolio(manageablePortfolio);
document = _portfolios.update(document);
}
s_logger.info("Portfolio '{}' saved as {}", manageablePortfolio.getName(), document.getUniqueId());
return document.getUniqueId();
}
}