/** * 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.Collections; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.threeten.bp.Instant; import com.google.common.base.Supplier; import com.google.common.collect.Lists; import com.opengamma.core.change.ChangeEvent; import com.opengamma.core.change.ChangeType; import com.opengamma.core.position.Portfolio; 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.SimplePortfolio; import com.opengamma.core.position.impl.SimplePortfolioNode; import com.opengamma.engine.ComputationTargetResolver; import com.opengamma.engine.function.config.FunctionRepositoryFactory; import com.opengamma.engine.value.ValueRequirement; import com.opengamma.engine.view.ViewResultModel; import com.opengamma.engine.view.compilation.CompiledViewDefinition; import com.opengamma.engine.view.cycle.ViewCycle; import com.opengamma.financial.security.lookup.SecurityAttributeMapper; import com.opengamma.id.ObjectId; import com.opengamma.id.UniqueId; import com.opengamma.id.UniqueIdentifiable; import com.opengamma.id.VersionCorrection; import com.opengamma.master.position.ManageablePosition; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.GUIDGenerator; import com.opengamma.web.analytics.formatting.TypeFormatter; /** * Default implementation of {@link AnalyticsView}. This class isn't meant to be thread safe. A thread calling any method that mutates the state must have an exclusive lock. The get methods can safely * be called by multiple concurrent threads. * * @see LockingAnalyticsView * @see com.opengamma.web.analytics Package concurrency notes */ /* package */class SimpleAnalyticsView implements AnalyticsView { private static final Logger s_logger = LoggerFactory.getLogger(SimpleAnalyticsView.class); private static final Portfolio EMPTY_PORTFOLIO = new SimplePortfolio("", new SimplePortfolioNode(UniqueId.of("EMPTY", "EMPTY"), "")); private final ResultsCache _cache = new ResultsCache(); private final ComputationTargetResolver _targetResolver; private final FunctionRepositoryFactory _functions; private final String _viewId; private final ViewportListener _viewportListener; private final VersionCorrection _versionCorrection; private final Supplier<Portfolio> _portfolioSupplier; private final PortfolioEntityExtractor _portfolioEntityExtractor; private final UniqueId _viewDefinitionId; private final ErrorManager _errorManager; private PortfolioAnalyticsGrid _portfolioGrid; private MainAnalyticsGrid<?> _primitivesGrid; private CompiledViewDefinition _compiledViewDefinition; private CompiledViewDefinition _pendingStructureChange; private Portfolio _pendingPortfolio; /** * @param viewId client ID of the view * @param portfolioCallbackId ID that is passed to the listener when the structure of the portfolio grid changes. This class makes no assumptions about its value * @param primitivesCallbackId ID that is passed to the listener when the structure of the primitives grid changes. This class makes no assumptions about its value * @param targetResolver For looking up calculation targets by specification * @param viewportListener Notified when any viewport is created, updated or deleted * @param blotterColumnMapper For populating the blotter columns with details for each different security type * @param portfolioSupplier Supplies an up to date version of the portfolio * @param showBlotterColumns Whether the blotter columns should be shown in the portfolio analytics grid * @param errorManager Holds information about errors that occur compiling and executing the view */ /* package */SimpleAnalyticsView(UniqueId viewDefinitionId, boolean primitivesOnly, VersionCorrection versionCorrection, String viewId, String portfolioCallbackId, String primitivesCallbackId, ComputationTargetResolver targetResolver, FunctionRepositoryFactory functions, ViewportListener viewportListener, SecurityAttributeMapper blotterColumnMapper, Supplier<Portfolio> portfolioSupplier, PortfolioEntityExtractor portfolioEntityExtractor, boolean showBlotterColumns, ErrorManager errorManager) { ArgumentChecker.notNull(viewDefinitionId, "viewDefinitionId"); ArgumentChecker.notEmpty(viewId, "viewId"); ArgumentChecker.notEmpty(portfolioCallbackId, "portfolioCallbackId"); ArgumentChecker.notEmpty(primitivesCallbackId, "primitivesGridId"); ArgumentChecker.notNull(targetResolver, "targetResolver"); ArgumentChecker.notNull(functions, "functions"); ArgumentChecker.notNull(viewportListener, "viewportListener"); ArgumentChecker.notNull(blotterColumnMapper, "blotterColumnMappings"); ArgumentChecker.notNull(versionCorrection, "versionCorrection"); ArgumentChecker.notNull(portfolioSupplier, "portfolioSupplier"); ArgumentChecker.notNull(portfolioEntityExtractor, "portfolioEntityExtractor"); ArgumentChecker.notNull(errorManager, "errorManager"); _errorManager = errorManager; _viewDefinitionId = viewDefinitionId; _versionCorrection = versionCorrection; _viewId = viewId; _targetResolver = targetResolver; _functions = functions; _portfolioSupplier = portfolioSupplier; _portfolioEntityExtractor = portfolioEntityExtractor; Portfolio portfolio; if (primitivesOnly) { portfolio = null; } else { portfolio = EMPTY_PORTFOLIO; } if (showBlotterColumns) { _portfolioGrid = PortfolioAnalyticsGrid.forBlotter(portfolioCallbackId, portfolio, targetResolver, functions, viewportListener, blotterColumnMapper); } else { _portfolioGrid = PortfolioAnalyticsGrid.forAnalytics(portfolioCallbackId, portfolio, targetResolver, functions, viewportListener); } _primitivesGrid = PrimitivesAnalyticsGrid.empty(primitivesCallbackId); _viewportListener = viewportListener; } @Override public List<String> updateStructure(CompiledViewDefinition compiledViewDefinition, Portfolio portfolio) { if (_compiledViewDefinition != null) { // If we are between cycles then hold back the structure change until there's a set of results which use it, // allowing the user to continue browsing the current set of results in the meantime. _pendingStructureChange = compiledViewDefinition; _pendingPortfolio = portfolio; return Collections.emptyList(); } doUpdateStructure(compiledViewDefinition, portfolio); return getGridIds(); } @Override public String viewCompilationFailed(Throwable t) { s_logger.warn("View compilation failed, adding error {}", t); return _errorManager.add(t); } private void doUpdateStructure(CompiledViewDefinition compiledViewDefinition, Portfolio portfolio) { _compiledViewDefinition = compiledViewDefinition; if (portfolio != null) { List<UniqueIdentifiable> entities = PortfolioMapper.flatMap(portfolio.getRootNode(), _portfolioEntityExtractor); _cache.put(entities); } _portfolioGrid = _portfolioGrid.withUpdatedStructure(_compiledViewDefinition, portfolio); _primitivesGrid = new PrimitivesAnalyticsGrid(_compiledViewDefinition, _primitivesGrid.getCallbackId(), _targetResolver, _functions, _viewportListener); } private List<String> getGridIds() { List<String> gridIds = Lists.newArrayList(); //callback ids for grid viewports for (PortfolioGridViewport viewport : _portfolioGrid.getViewports().values()) { gridIds.add(viewport.getStructureCallbackId()); gridIds.add(viewport.getCallbackId()); } //callback ids for depgraph grid viewports for (DependencyGraphGrid grid : _portfolioGrid.getDependencyGraphs().values()) { for (DependencyGraphViewport viewport : grid.getViewports().values()) { gridIds.add(viewport.getStructureCallbackId()); gridIds.add(viewport.getCallbackId()); } } gridIds.add(_portfolioGrid.getCallbackId()); gridIds.add(_primitivesGrid.getCallbackId()); gridIds.addAll(_portfolioGrid.getDependencyGraphCallbackIds()); gridIds.addAll(_primitivesGrid.getDependencyGraphCallbackIds()); return gridIds; } @Override public List<String> updateResults(ViewResultModel results, ViewCycle viewCycle) { List<String> updatedIds = Lists.newArrayList(); boolean structureUpdated; _cache.put(results); if (_pendingStructureChange != null) { doUpdateStructure(_pendingStructureChange, _pendingPortfolio); structureUpdated = true; _pendingStructureChange = null; updatedIds.addAll(getGridIds()); } else { structureUpdated = false; } if (!structureUpdated) { // Individual cell updates PortfolioAnalyticsGrid updatedPortfolioGrid = _portfolioGrid.withUpdatedTickAndPossiblyStructure(_cache); if (updatedPortfolioGrid == _portfolioGrid) { // no change to the grid structure, notify the data has changed updatedIds.addAll(_portfolioGrid.updateResults(_cache, viewCycle)); } else { _portfolioGrid = updatedPortfolioGrid; // grid structure has changed due to the results, notify the grids need to be rebuilt updatedIds.add(_portfolioGrid.getCallbackId()); updatedIds.addAll(_portfolioGrid.getDependencyGraphCallbackIds()); } updatedIds.addAll(_primitivesGrid.updateResults(_cache, viewCycle)); } return updatedIds; } private MainAnalyticsGrid<?> getGrid(GridType gridType) { switch (gridType) { case PORTFOLIO: return _portfolioGrid; case PRIMITIVES: return _primitivesGrid; default: throw new IllegalArgumentException("Unexpected grid type " + gridType); } } @Override public GridStructure getGridStructure(GridType gridType, int viewportId) { GridStructure gridStructure = getGrid(gridType).getViewport(viewportId).getGridStructure(); s_logger.debug("Viewport {} and view {} returning grid structure for the {} grid: {}", viewportId, _viewId, gridType, gridStructure); return gridStructure; } @Override public GridStructure getInitialGridStructure(GridType gridType) { GridStructure gridStructure = getGrid(gridType).getGridStructure(); s_logger.debug("View {} returning grid structure for the {} grid: {}", _viewId, gridType, gridStructure); return gridStructure; } @Override public boolean createViewport(int requestId, GridType gridType, int viewportId, String callbackId, String structureCallbackId, ViewportDefinition viewportDefinition) { boolean hasData = getGrid(gridType).createViewport(viewportId, callbackId, structureCallbackId, viewportDefinition, _cache); s_logger.debug("View {} created viewport ID {} for the {} grid from {}", _viewId, viewportId, gridType, viewportDefinition); return hasData; } @Override public String updateViewport(GridType gridType, int viewportId, ViewportDefinition viewportDefinition) { s_logger.debug("View {} updating viewport {} for {} grid to {}", _viewId, viewportId, gridType, viewportDefinition); return getGrid(gridType).updateViewport(viewportId, viewportDefinition, _cache); } @Override public void deleteViewport(GridType gridType, int viewportId) { s_logger.debug("View {} deleting viewport {} from the {} grid", _viewId, viewportId, gridType); getGrid(gridType).deleteViewport(viewportId); } @Override public ViewportResults getData(GridType gridType, int viewportId) { s_logger.debug("View {} getting data for viewport {} of the {} grid", _viewId, viewportId, gridType); return getGrid(gridType).getData(viewportId); } @Override public void openDependencyGraph(int requestId, GridType gridType, int graphId, String callbackId, int row, int col) { s_logger.debug("View {} opening dependency graph {} for cell ({}, {}) of the {} grid", _viewId, graphId, row, col, gridType); getGrid(gridType).openDependencyGraph(graphId, callbackId, row, col, _compiledViewDefinition, _viewportListener); } @Override public void openDependencyGraph(int requestId, GridType gridType, int graphId, String callbackId, String calcConfigName, ValueRequirement valueRequirement) { getGrid(gridType).openDependencyGraph(graphId, callbackId, calcConfigName, valueRequirement, _compiledViewDefinition, _viewportListener); } @Override public void closeDependencyGraph(GridType gridType, int graphId) { s_logger.debug("View {} closing dependency graph {} of the {} grid", _viewId, graphId, gridType); getGrid(gridType).closeDependencyGraph(graphId); } @Override public GridStructure getGridStructure(GridType gridType, int graphId, int viewportId) { DependencyGraphGridStructure gridStructure = getGrid(gridType).getGridStructure(graphId, viewportId); s_logger.debug("Viewport {} and view {} returning grid structure for dependency graph {} of the {} grid: {}", viewportId, _viewId, graphId, gridType, gridStructure); return gridStructure; } @Override public GridStructure getInitialGridStructure(GridType gridType, int graphId) { DependencyGraphGridStructure gridStructure = getGrid(gridType).getGridStructure(graphId); s_logger.debug("View {} returning grid structure for dependency graph {} of the {} grid: {}", _viewId, graphId, gridType, gridStructure); return gridStructure; } @Override public boolean createViewport(int requestId, GridType gridType, int graphId, int viewportId, String callbackId, String structureCallbackId, ViewportDefinition viewportDefinition) { boolean hasData = getGrid(gridType).createViewport(graphId, viewportId, callbackId, structureCallbackId, viewportDefinition, _cache); s_logger.debug("View {} created viewport ID {} for dependency graph {} of the {} grid using {}", _viewId, viewportId, graphId, gridType, viewportDefinition); return hasData; } @Override public String updateViewport(GridType gridType, int graphId, int viewportId, ViewportDefinition viewportDefinition) { s_logger.debug("View {} updating viewport for dependency graph {} of the {} grid using {}", _viewId, graphId, gridType, viewportDefinition); return getGrid(gridType).updateViewport(graphId, viewportId, viewportDefinition, _cache); } @Override public void deleteViewport(GridType gridType, int graphId, int viewportId) { s_logger.debug("View {} deleting viewport {} from dependency graph {} of the {} grid", _viewId, viewportId, graphId, gridType); getGrid(gridType).deleteViewport(graphId, viewportId); } @Override public ViewportResults getData(GridType gridType, int graphId, int viewportId) { s_logger.debug("View {} getting data for viewport {} of dependency graph {} of the {} grid", _viewId, viewportId, graphId, gridType); return getGrid(gridType).getData(graphId, viewportId); } @Override public List<String> portfolioChanged() { Portfolio portfolio = _portfolioSupplier.get(); List<UniqueIdentifiable> entities = PortfolioMapper.flatMap(portfolio.getRootNode(), _portfolioEntityExtractor); _cache.put(entities); // TODO ignore for now, causes problems when the view take a long time to recompile /*_portfolioGrid = _portfolioGrid.withUpdatedRows(portfolio); // TODO this is pretty conservative, refreshes all grids because the portfolio structure has changed return getGridIds();*/ return Collections.emptyList(); } @Override public List<String> entityChanged(MasterChangeNotification<?> notification) { ChangeEvent event = notification.getEvent(); if (isChangeRelevant(event)) { if (event.getType() == ChangeType.REMOVED) { // TODO clean up trades from cache if this is a position that has been removed _cache.remove(event.getObjectId()); _portfolioGrid = _portfolioGrid.withUpdatedRows(_portfolioSupplier.get()); // return the IDs of all grids because the portfolio structure has changed // TODO if we had separate IDs for rows and columns it would save the client rebuilding the column metadata return getGridIds(); } else { UniqueIdentifiable entity = notification.getEntity(); _cache.put(entity); List<ObjectId> entityIds = Lists.newArrayList(entity.getUniqueId().getObjectId()); // TODO get rid of this duplication when ManageablePosition implements Position // TODO would it be nicer to have a getEntities() method on MasterChangeNotification? // would need different impls for different entity types. probably not worth it if (entity instanceof Position) { for (Trade trade : ((Position) entity).getTrades()) { entityIds.add(trade.getUniqueId().getObjectId()); _cache.put(trade); } } else if (entity instanceof ManageablePosition) { for (Trade trade : ((ManageablePosition) entity).getTrades()) { entityIds.add(trade.getUniqueId().getObjectId()); _cache.put(trade); } } List<String> ids = _portfolioGrid.updateEntities(_cache, entityIds); s_logger.debug("Entity changed {}, firing updates for viewports {}", notification.getEntity().getUniqueId(), ids); return ids; } } else { return Collections.emptyList(); } } /** * Returns true if a change event invalidates any of this view's portfolio, including trades, securities and positions it refers to. * * @param event The event * @return true if the portfolio or positions, trades or securities it refers to have changed */ private boolean isChangeRelevant(ChangeEvent event) { // if the correctedTo time is non-null then we're looking at corrections up to a fixed point in the past and // new corrections can't affect our version if (_versionCorrection.getCorrectedTo() != null) { return false; } // there's no way we can know about an object if it's just been added. and if the portfolio is modified we will // cache any newly added positions etc when traversing the new portfolio structure if (event.getType() == ChangeType.ADDED) { return false; } if (_cache.getEntity(event.getObjectId()) == null) { return false; } Instant versionInstant = _versionCorrection.getVersionAsOf(); Instant eventFrom = event.getVersionFrom(); Instant eventTo = event.getVersionTo(); if (versionInstant == null) { // if the version time is null (latest) and eventTo is null (latest) then handle the change // if the version time is null (latest) and eventTo isn't null the event doesn't affect the latest version return eventTo == null; } // check whether the range of the changed version contains our version instance if (eventFrom.isAfter(versionInstant)) { return false; } if (eventTo != null && eventTo.isBefore(versionInstant)) { return false; } return true; } @Override public ViewportResults getAllGridData(GridType gridType, TypeFormatter.Format format) { GridStructure gridStructure = getGrid(gridType).getGridStructure(); List<Integer> rows = Lists.newArrayList(); for (int i = 0; i < gridStructure.getRowCount(); i++) { rows.add(i); } List<Integer> cols = Lists.newArrayList(); for (int i = 0; i < gridStructure.getColumnCount(); i++) { cols.add(i); } ViewportDefinition viewportDefinition = ViewportDefinition.create(Integer.MIN_VALUE, rows, cols, Lists.<GridCell>newArrayList(), format, false); String callbackId = GUIDGenerator.generate().toString(); String structureCallbackId = GUIDGenerator.generate().toString(); MainGridViewport viewport = getGrid(gridType).createViewport(viewportDefinition, callbackId, structureCallbackId, _cache); return viewport.getData(); } @Override public UniqueId getViewDefinitionId() { return _viewDefinitionId; } @Override public List<ErrorInfo> getErrors() { return _errorManager.get(); } @Override public void deleteError(long id) { _errorManager.delete(id); } }