/** * 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.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; 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.security.Security; import com.opengamma.core.security.SecurityLink; import com.opengamma.engine.ComputationTargetSpecification; import com.opengamma.engine.MemoryUtils; import com.opengamma.engine.depgraph.DependencyGraphBuilder; import com.opengamma.engine.target.ComputationTargetReference; import com.opengamma.engine.target.ComputationTargetRequirement; import com.opengamma.engine.target.ComputationTargetType; import com.opengamma.engine.value.ValueProperties; import com.opengamma.engine.value.ValuePropertyNames; import com.opengamma.engine.value.ValueRequirement; import com.opengamma.engine.value.ValueRequirementNames; import com.opengamma.engine.view.ResultModelDefinition; import com.opengamma.engine.view.ResultOutputMode; import com.opengamma.engine.view.ViewCalculationConfiguration; import com.opengamma.engine.view.ViewCalculationConfiguration.MergedOutput; import com.opengamma.id.UniqueId; import com.opengamma.util.tuple.Pair; import com.opengamma.util.tuple.Pairs; /** * Portfolio tree traversal callback methods that construct value requirements for the specified portfolio's nodes, positions and trades (as per options specified in the result model definition). The * value requirements are added to the specified dependency graph builder, possibly triggering the background compilation of dependency graphs for each stage in a portfolio tree while this traversal * is still ongoing. The pre-order method for a portfolio node sets up an empty requirements container for that node, which is filled up as its children are traversed (if aggregation is specified in * the result model definition), and then added to the dependency graph's list of targets in the post-order method for that portfolio node. */ /* package */final class PortfolioCompilerTraversalCallback extends AbstractPortfolioNodeTraversalCallback { private static final Logger s_logger = LoggerFactory.getLogger(PortfolioCompilerTraversalCallback.class); private static final class NodeData { private final ComputationTargetSpecification _targetSpec; private final Set<Pair<String, ValueProperties>> _requirements = Sets.newHashSet(); private final boolean _excluded; public NodeData(final PortfolioNode node, final boolean excluded) { _targetSpec = ComputationTargetSpecification.of(node); _excluded = excluded; } public synchronized void addRequirements(final Set<Pair<String, ValueProperties>> requirements) { _requirements.addAll(requirements); } public Set<Pair<String, ValueProperties>> getRequirements() { return _requirements; } public ComputationTargetSpecification getTargetSpecification() { return _targetSpec; } public boolean isExcluded() { return _excluded; } } private final Set<UniqueId> _includeEvents; private final Set<UniqueId> _excludeEvents; private Map<String, Set<Pair<String, ValueProperties>>> _portfolioRequirementsBySecurityType; private final List<MergedOutput> _mergedOutputs; private final Set<ValueRequirement> _alreadyAdded; private final DependencyGraphBuilder _builder; private final ConcurrentMap<ComputationTargetReference, UniqueId> _resolutions; private final boolean _outputAggregates; private final boolean _outputPositions; private final boolean _outputTrades; /** * This map persists gathered information for each portfolio node and position across multiple traversal steps, thus allowing child nodes/positions to insert aggregate requirements into their parent * node. */ private final ConcurrentMap<UniqueId, NodeData> _nodeData = new ConcurrentHashMap<UniqueId, NodeData>(); public PortfolioCompilerTraversalCallback(final ViewCalculationConfiguration calculationConfiguration, final DependencyGraphBuilder builder, final Set<ValueRequirement> alreadyAdded, final ConcurrentMap<ComputationTargetReference, UniqueId> resolutions, final Set<UniqueId> includeEvents, final Set<UniqueId> excludeEvents) { _portfolioRequirementsBySecurityType = calculationConfiguration.getPortfolioRequirementsBySecurityType(); _mergedOutputs = calculationConfiguration.getMergedOutputs(); final ResultModelDefinition resultModelDefinition = calculationConfiguration.getViewDefinition().getResultModelDefinition(); _outputAggregates = resultModelDefinition.getAggregatePositionOutputMode() != ResultOutputMode.NONE; _outputPositions = resultModelDefinition.getPositionOutputMode() != ResultOutputMode.NONE; _outputTrades = resultModelDefinition.getTradeOutputMode() != ResultOutputMode.NONE; _builder = builder; _alreadyAdded = alreadyAdded; _resolutions = resolutions; _includeEvents = includeEvents; _excludeEvents = excludeEvents; } public Map<String, Set<Pair<String, ValueProperties>>> getPortfolioRequirementsBySecurityType() { return _portfolioRequirementsBySecurityType; } public void setPortfolioRequirementsBySecurityType(Map<String, Set<Pair<String, ValueProperties>>> portfolioRequirementsBySecurityType) { _portfolioRequirementsBySecurityType = portfolioRequirementsBySecurityType; } public void reset() { _nodeData.clear(); } /** * Add the specified value requirement to the dep graph builder, triggering graph building by background threads. * <p> * If supplied, the {@link #_alreadyAdded} set member is used to identify anything that has already been added from the specific requirements of a view or as part of invalidating a previous graph. * See the notes in {@link DependencyGraphBuilder} for the hazards of requesting the same value requirement multiple times. * * @param valueRequirement the value requirement to add */ protected void addValueRequirement(final ValueRequirement valueRequirement) { if ((_alreadyAdded == null) || !_alreadyAdded.contains(valueRequirement)) { _builder.addTarget(valueRequirement); } else { s_logger.debug("Suppressing {} from the incremental requirement set", valueRequirement); } } /** * Store details of the security link in the resolution cache. The link is assumed to be a record of the link to the object, for example is it held by strong (object id) or weak (external id) * reference. * <p> * Securities are already resolved when the functions see the positions, so the logging target resolver will not capture any uses of the security. * * @param link the link to store - the identifier is taken from this along with the resolved unique identifier */ private void store(final SecurityLink link) { final ComputationTargetReference key; final UniqueId uid; if (link.getTarget() != null) { uid = link.getTarget().getUniqueId(); if (link.getObjectId() != null) { key = new ComputationTargetSpecification(ComputationTargetType.SECURITY, uid.toLatest()); } else if (!link.getExternalId().isEmpty()) { key = new ComputationTargetRequirement(ComputationTargetType.SECURITY, link.getExternalId()); } else { return; } if (uid == null) { throw new IllegalArgumentException("Provided a SecurityLink " + link + " where the UniqueId could not be identified. Error in underlying Source/Master."); } final UniqueId existing = _resolutions.putIfAbsent(MemoryUtils.instance(key), uid); assert (existing == null) || existing.equals(uid); } } /** * Store details of the position lookup in the resolution cache. Positions are referenced from portfolio nodes by object identifier. * * @param position the position to store */ private void store(final Position position) { _resolutions.putIfAbsent(MemoryUtils.instance(new ComputationTargetSpecification(ComputationTargetType.POSITION, position.getUniqueId().toLatest())), position.getUniqueId()); } /** * The pre-order operation for a portfolio node, which adds the aggregate value requirements for the current portfolio node to the graph builder's set of value requirements. * * @param node the portfolio node being traversed */ @Override public void preOrderOperation(final PortfolioNode node) { // If a sub-set of nodes is to be considered, fail/return quickly boolean nodeExcluded = false; if (_excludeEvents != null) { if (_excludeEvents.contains(node.getUniqueId())) { if ((node.getParentNodeId() != null) && (_nodeData.get(node.getParentNodeId()) != null)) { nodeExcluded = true; } else { return; } } } // Initialise an empty set of requirements for the current portfolio node // This will be filled in as the traversal of this portfolio node's children proceeds, and retrieved during // this portfolio node's post-order traversal. final NodeData nodeData = new NodeData(node, nodeExcluded); _nodeData.put(node.getUniqueId(), nodeData); if (_outputAggregates && !nodeExcluded) { // Retrieve the required aggregate outputs (by 'aggregate' sec type) for the current calc configuration final Set<Pair<String, ValueProperties>> requiredOutputs = _portfolioRequirementsBySecurityType.get(ViewCalculationConfiguration.SECURITY_TYPE_AGGREGATE_ONLY); if ((requiredOutputs != null) && !requiredOutputs.isEmpty()) { // Add the aggregate value requirements for the current portfolio node to the graph builder's set of value requirements, // building them using the retrieved required aggregate outputs and the newly created computation target spec // for this portfolio node. final ComputationTargetSpecification targetSpec = nodeData.getTargetSpecification(); for (final Pair<String, ValueProperties> requiredOutput : requiredOutputs) { addValueRequirement(new ValueRequirement(requiredOutput.getFirst(), targetSpec, requiredOutput.getSecond())); } } } } /** * The pre-order operation for a position in a portfolio. which adds the value requirements for the current position and/or its trades to the graph builder's set of value requirements (if the result * model specifies it), and also adds aggregate value requirements to the parent's requirements (again, if the result model specifies it) to be reaped post-order. * * @param position the position being traversed */ @Override public void preOrderOperation(final PortfolioNode parentNode, final Position position) { // If a sub-set of positions is to be considered, fail/return quickly boolean positionExcluded = false; NodeData nodeData = null; if (_includeEvents != null) { if (!_includeEvents.contains(position.getUniqueId())) { return; } } else if (_excludeEvents != null) { nodeData = _nodeData.get(parentNode.getUniqueId()); if (nodeData == null) { // Node is wholly excluded if it has no entry in the map return; } positionExcluded = nodeData.isExcluded(); } // Get this position's security or return immediately if not available final Security security = position.getSecurity(); if (security == null) { return; } if (!positionExcluded) { store(position); store(position.getSecurityLink()); } // Identify this position's security type final String securityType = security.getSecurityType(); Set<Pair<String, ValueProperties>> requiredOutputs; // Are we interested in producing results for positions? if (_outputPositions || _outputAggregates) { // Get all known required outputs for this security type in the current calculation configuration requiredOutputs = _portfolioRequirementsBySecurityType.get(securityType); // Check that there's at least one required output to deal with if ((requiredOutputs != null) && !requiredOutputs.isEmpty()) { if (nodeData == null) { nodeData = _nodeData.get(parentNode.getUniqueId()); } // Are we interested in aggregate results for the parent? If so, pass on requirements to parent portfolio node if (_outputAggregates) { nodeData.addRequirements(requiredOutputs); } // Are we interested in any results at all for this position? if (_outputPositions && !positionExcluded) { final ComputationTargetSpecification positionSpec = nodeData.getTargetSpecification().containing(ComputationTargetType.POSITION, position.getUniqueId().toLatest()); // Add the value requirements for the current position to the graph builder's set of value requirements, // building them using the retrieved required outputs for this security type and the newly created computation // target spec for this position. for (final Pair<String, ValueProperties> requiredOutput : requiredOutputs) { addValueRequirement(new ValueRequirement(requiredOutput.getFirst(), positionSpec, requiredOutput.getSecond())); } } } for (MergedOutput mergedOutput : _mergedOutputs) { if (nodeData == null) { nodeData = _nodeData.get(parentNode.getUniqueId()); } final ValueProperties constraints = ValueProperties.with(ValuePropertyNames.NAME, mergedOutput.getMergedOutputName()).get(); if (_outputAggregates) { nodeData.addRequirements(ImmutableSet.of(Pairs.of(ValueRequirementNames.MERGED_OUTPUT, constraints))); } if (_outputPositions && !positionExcluded) { final ComputationTargetSpecification positionSpec = nodeData.getTargetSpecification().containing(ComputationTargetType.POSITION, position.getUniqueId().toLatest()); addValueRequirement(new ValueRequirement(ValueRequirementNames.MERGED_OUTPUT, positionSpec, constraints)); } } } if (_outputTrades && !positionExcluded) { final Collection<Trade> trades = position.getTrades(); if (!trades.isEmpty()) { requiredOutputs = _portfolioRequirementsBySecurityType.get(securityType); // Check that there's at least one required output to deal with if ((requiredOutputs != null) && !requiredOutputs.isEmpty()) { // Add value requirements for each trade for (final Trade trade : trades) { // TODO: [PLAT-2286] Scope the trade underneath it's parent portfolio node and position final ComputationTargetSpecification tradeSpec = ComputationTargetSpecification.of(trade); // Add the value requirements for the current trade to the graph builder's set of value requirements, // building them using the retrieved required outputs icw trades for this security type and the newly // created computation target spec for this trade. for (final Pair<String, ValueProperties> requiredOutput : requiredOutputs) { addValueRequirement(new ValueRequirement(requiredOutput.getFirst(), tradeSpec, requiredOutput.getSecond())); } } } for (MergedOutput mergedOutput : _mergedOutputs) { for (final Trade trade : trades) { final ValueProperties constraints = ValueProperties.with(ValuePropertyNames.NAME, mergedOutput.getMergedOutputName()).get(); final ComputationTargetSpecification tradeSpec = ComputationTargetSpecification.of(trade); addValueRequirement(new ValueRequirement(ValueRequirementNames.MERGED_OUTPUT, tradeSpec, constraints)); } } for (final Trade trade : trades) { store(trade.getSecurityLink()); } } } } /** * The post-order operation for a portfolio node, which adds the value requirements gathered while traversing this portfolio node's children to the graph builder's set of value requirements. This * portfolio node's requirements are also passed up into its own parent node's requirments. * * @param node the portfolio node being traversed */ @Override public void postOrderOperation(final PortfolioNode node) { // Retrieve this portfolio node's value requirements (gathered during traversal of this portfolio node's children) final NodeData nodeData = _nodeData.remove(node.getUniqueId()); if (nodeData == null) { // Totally excluded return; } final Set<Pair<String, ValueProperties>> nodeRequirements = nodeData.getRequirements(); if (node.getParentNodeId() != null) { // Retrieve the parent portfolio node's requirements final NodeData parentNodeData = _nodeData.get(node.getParentNodeId()); parentNodeData.addRequirements(nodeRequirements); } if (!nodeData.isExcluded()) { final ComputationTargetSpecification targetSpec = nodeData.getTargetSpecification(); // Add the value requirements for the current portfolio node to the graph builder's set of value requirements, // building them using the requirements gathered during its children's traversal and the newly created computation // target spec for this portfolio node. for (final Pair<String, ValueProperties> requiredOutput : nodeRequirements) { addValueRequirement(new ValueRequirement(requiredOutput.getFirst(), targetSpec, requiredOutput.getSecond())); } } } }