/** * Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.web.analytics; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; 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.PortfolioMapper; import com.opengamma.core.position.impl.PortfolioMapperFunction; import com.opengamma.core.security.Security; import com.opengamma.engine.ComputationTargetSpecification; import com.opengamma.engine.target.ComputationTargetType; import com.opengamma.engine.value.ValueProperties; import com.opengamma.engine.value.ValuePropertyNames; import com.opengamma.engine.value.ValueRequirementNames; import com.opengamma.engine.value.ValueSpecification; import com.opengamma.engine.view.ViewCalculationConfiguration; import com.opengamma.engine.view.ViewCalculationConfiguration.MergedOutput; import com.opengamma.engine.view.ViewDefinition; import com.opengamma.engine.view.compilation.CompiledViewDefinition; import com.opengamma.financial.security.FinancialSecurity; import com.opengamma.id.UniqueId; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.tuple.Pair; import com.opengamma.util.tuple.Triple; /** * The structure of the grid that displays portfolio data and analytics. Contains the column definitions and * the portfolio tree structure. */ public class PortfolioGridStructure extends MainGridStructure { /** Definition of the view driving the grid. */ private final ViewDefinition _viewDef; /** Meta data for exploded child columns, keyed by the specification of the parent column. */ private final Map<ColumnSpecification, SortedSet<ColumnMeta>> _inlineColumnMeta; /** Rows in the grid. */ private final List<PortfolioGridRow> _rows; /* package */ PortfolioGridStructure(List<PortfolioGridRow> rows, GridColumnGroup fixedColumns, GridColumnGroups nonFixedColumns, AnalyticsNode rootNode, TargetLookup targetLookup, UnversionedValueMappings valueMappings, ViewDefinition viewDef) { this(rows, fixedColumns, nonFixedColumns, rootNode, targetLookup, valueMappings, viewDef, Collections.<ColumnSpecification, SortedSet<ColumnMeta>>emptyMap()); } /* package */ PortfolioGridStructure(List<PortfolioGridRow> rows, GridColumnGroup fixedColumns, GridColumnGroups nonFixedColumns, AnalyticsNode rootNode, TargetLookup targetLookup, UnversionedValueMappings valueMappings, ViewDefinition viewDef, Map<ColumnSpecification, SortedSet<ColumnMeta>> inlineColumnMeta) { super(fixedColumns, nonFixedColumns, targetLookup, rootNode, valueMappings); ArgumentChecker.notNull(rows, "rows"); ArgumentChecker.notNull(inlineColumnMeta, "inlineColumnCounts"); _inlineColumnMeta = inlineColumnMeta; _rows = rows; _viewDef = viewDef; } /* package */ static PortfolioGridStructure create(Portfolio portfolio, UnversionedValueMappings valueMappings) { ArgumentChecker.notNull(valueMappings, "valueMappings"); // TODO these can be empty, not used any more List<PortfolioGridRow> rows = buildRows(portfolio); TargetLookup targetLookup = new TargetLookup(valueMappings, rows); AnalyticsNode rootNode = AnalyticsNode.portfolioRoot(portfolio); return new PortfolioGridStructure(rows, GridColumnGroup.empty(), GridColumnGroups.empty(), rootNode, targetLookup, valueMappings, new ViewDefinition("empty", "dummy")); } /* package */ PortfolioGridStructure withUpdatedRows(Portfolio portfolio) { AnalyticsNode rootNode = AnalyticsNode.portfolioRoot(portfolio); List<PortfolioGridRow> rows = buildRows(portfolio); GridColumnGroup fixedColumns = buildFixedColumns(rows); TargetLookup targetLookup = new TargetLookup(super.getValueMappings(), rows); List<GridColumnGroup> analyticsColumns = buildAnalyticsColumns(_viewDef, targetLookup); GridColumnGroups nonFixedColumns = new GridColumnGroups(analyticsColumns); return new PortfolioGridStructure(rows, fixedColumns, nonFixedColumns, rootNode, targetLookup, super.getValueMappings(), _viewDef); } /* package */ PortfolioGridStructure withUpdatedStructure(CompiledViewDefinition compiledViewDef, Portfolio portfolio) { AnalyticsNode rootNode = AnalyticsNode.portfolioRoot(portfolio); List<PortfolioGridRow> rows = buildRows(portfolio); GridColumnGroup fixedColumns = buildFixedColumns(rows); UnversionedValueMappings valueMappings = new UnversionedValueMappings(compiledViewDef); TargetLookup targetLookup = new TargetLookup(valueMappings, rows); ViewDefinition viewDef = compiledViewDef.getViewDefinition(); List<GridColumnGroup> analyticsColumns = buildAnalyticsColumns(viewDef, targetLookup); GridColumnGroups nonFixedColumns = new GridColumnGroups(analyticsColumns); return new PortfolioGridStructure(rows, fixedColumns, nonFixedColumns, rootNode, targetLookup, valueMappings, viewDef); } /* package */ PortfolioGridStructure withUpdatedStructure(ResultsCache cache) { Map<ColumnSpecification, SortedSet<ColumnMeta>> inlineColumnMeta = Maps.newHashMap(); for (GridColumn column : getColumnStructure().getColumns()) { ColumnSpecification colSpec = column.getSpecification(); if (Inliner.isDisplayableInline(column.getUnderlyingType(), column.getSpecification())) { // ordered set of the union of the column metadata for the whole set. need this to figure out how many unique // columns are required SortedSet<ColumnMeta> allColumnMeta = Sets.newTreeSet(); // traverse every result in the column and get the column metadata for (Iterator<Pair<String, ValueSpecification>> it = getTargetLookup().getTargetsForColumn(colSpec); it.hasNext(); ) { Pair<String, ValueSpecification> target = it.next(); if (target != null) { ResultsCache.Result result = cache.getResult(target.getFirst(), target.getSecond(), column.getType()); Object value = result.getValue(); allColumnMeta.addAll(Inliner.columnMeta(value)); } } if (!allColumnMeta.isEmpty()) { inlineColumnMeta.put(colSpec, allColumnMeta); } } } // TODO implement equals() and always return a new instance? conceptually a bit neater but less efficient if (!inlineColumnMeta.equals(_inlineColumnMeta)) { List<GridColumnGroup> analyticsColumns = buildAnalyticsColumns(_viewDef, getTargetLookup(), inlineColumnMeta); return new PortfolioGridStructure(_rows, buildFixedColumns(_rows), new GridColumnGroups(analyticsColumns), getRootNode(), getTargetLookup(), getValueMappings(), _viewDef, inlineColumnMeta); } else { return this; } } /* package */ PortfolioGridStructure withNode(AnalyticsNode node) { return new PortfolioGridStructure(_rows, getFixedColumns(), getNonFixedColumns(), node, getTargetLookup(), super.getValueMappings(), _viewDef); } /* package */ static GridColumnGroup buildFixedColumns(List<PortfolioGridRow> rows) { GridColumn labelColumn = new GridColumn("Name", "", null, new PortfolioLabelRenderer(rows)); return new GridColumnGroup("fixed", ImmutableList.of(labelColumn), false); } /* package */ static List<GridColumnGroup> buildAnalyticsColumns(ViewDefinition viewDef, TargetLookup targetLookup) { return buildAnalyticsColumns(viewDef, targetLookup, Collections.<ColumnSpecification, SortedSet<ColumnMeta>>emptyMap()); } /** * @param viewDef The view definition * @return Columns for displaying calculated analytics data, one group per calculation configuration */ /* package */ static List<GridColumnGroup> buildAnalyticsColumns(ViewDefinition viewDef, TargetLookup targetLookup, Map<ColumnSpecification, SortedSet<ColumnMeta>> inlineColumnMeta) { List<GridColumnGroup> columnGroups = Lists.newArrayList(); Set<Triple<String, String, ValueProperties>> columnSpecs = Sets.newHashSet(); for (ViewCalculationConfiguration calcConfig : viewDef.getAllCalculationConfigurations()) { List<ColumnSpecification> allSpecs = Lists.newArrayList(); for (ViewCalculationConfiguration.Column column : calcConfig.getColumns()) { allSpecs.add(new ColumnSpecification(calcConfig.getName(), column.getValueName(), column.getProperties(), column.getHeader())); } for (Pair<String, ValueProperties> output : calcConfig.getAllPortfolioRequirements()) { allSpecs.add(new ColumnSpecification(calcConfig.getName(), output.getFirst(), output.getSecond())); } for (MergedOutput output : calcConfig.getMergedOutputs()) { ValueProperties constraints = ValueProperties.with(ValuePropertyNames.NAME, output.getMergedOutputName()).get(); allSpecs.add(new ColumnSpecification(calcConfig.getName(), ValueRequirementNames.MERGED_OUTPUT, constraints, output.getMergedOutputName())); } List<GridColumn> columns = Lists.newArrayList(); for (ColumnSpecification columnSpec : allSpecs) { Class<?> columnType = ValueTypes.getTypeForValueName(columnSpec.getValueName()); // ensure column isn't a duplicate. can't use a set of col specs because we need to treat columns as duplicates // even if they have different headers if (columnSpecs.add(Triple.of(columnSpec.getCalcConfigName(), columnSpec.getValueName(), columnSpec.getValueProperties()))) { SortedSet<ColumnMeta> meta = inlineColumnMeta.get(columnSpec); if (meta == null) { // column can't be inlined columns.add(GridColumn.forSpec(columnSpec, columnType, targetLookup)); } else { int inlineIndex = 0; for (ColumnMeta columnMeta : meta) { String header; if (inlineIndex++ == 0) { header = columnSpec.getHeader() + " / " + columnMeta.getHeader(); } else { header = columnMeta.getHeader(); } columns.add(GridColumn.forSpec(header, columnSpec, columnMeta.getType(), columnMeta.getUnderlyingType(), targetLookup, columnMeta.getKey(), inlineIndex)); } } } } if (!columns.isEmpty()) { columnGroups.add(new GridColumnGroup(calcConfig.getName(), columns, true)); } } return columnGroups; } /* package */ static List<PortfolioGridRow> buildRows(final Portfolio portfolio) { if (portfolio == null) { return Collections.emptyList(); } PortfolioMapperFunction<List<PortfolioGridRow>> targetFn = new PortfolioMapperFunction<List<PortfolioGridRow>>() { @Override public List<PortfolioGridRow> apply(PortfolioNode node) { ComputationTargetSpecification target = new ComputationTargetSpecification(ComputationTargetType.PORTFOLIO_NODE, node.getUniqueId()); String nodeName; // if the parent ID is null it's the root node if (node.getParentNodeId() == null) { // the root node is called "Root" which isn't any use for displaying in the UI, use the portfolio name instead nodeName = portfolio.getName(); } else { nodeName = node.getName(); } return Lists.newArrayList(new PortfolioGridRow(target, nodeName, node.getUniqueId())); } @Override public List<PortfolioGridRow> apply(PortfolioNode parentNode, Position position) { ComputationTargetSpecification nodeSpec = ComputationTargetSpecification.of(parentNode); // TODO I don't think toLatest() will do long term. resolution time available on the result model UniqueId positionId = position.getUniqueId(); ComputationTargetSpecification target = nodeSpec.containing(ComputationTargetType.POSITION, positionId.toLatest()); Security security = position.getSecurity(); List<PortfolioGridRow> rows = Lists.newArrayList(); UniqueId nodeId = parentNode.getUniqueId(); if (isFungible(position.getSecurity())) { rows.add(new PortfolioGridRow(target, security.getName(), security.getUniqueId(), nodeId, positionId)); for (Trade trade : position.getTrades()) { String tradeDate = trade.getTradeDate().toString(); rows.add(new PortfolioGridRow(ComputationTargetSpecification.of(trade), tradeDate, security.getUniqueId(), nodeId, positionId, trade.getUniqueId())); } } else { Collection<Trade> trades = position.getTrades(); if (trades.isEmpty()) { rows.add(new PortfolioGridRow(target, security.getName(), security.getUniqueId(), nodeId, positionId)); } else { // there is never more than one trade on a position in an OTC security UniqueId tradeId = trades.iterator().next().getUniqueId(); rows.add(new PortfolioGridRow(target, security.getName(), security.getUniqueId(), nodeId, positionId, tradeId)); } } return rows; } }; List<List<PortfolioGridRow>> rows = PortfolioMapper.map(portfolio.getRootNode(), targetFn); Iterable<PortfolioGridRow> flattenedRows = Iterables.concat(rows); return Lists.newArrayList(flattenedRows); } /** * @param security A security * @return true if the security is fungible, false if OTC */ private static boolean isFungible(Security security) { if (security instanceof FinancialSecurity) { return !((FinancialSecurity) security).accept(new OtcSecurityVisitor()); } else { return false; } } }