/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.engine.view.client;
import static com.opengamma.engine.view.permission.PortfolioPermission.ALLOW;
import java.util.LinkedHashMap;
import java.util.Map;
import com.google.common.base.Optional;
import com.opengamma.core.position.Portfolio;
import com.opengamma.core.position.PortfolioNode;
import com.opengamma.core.position.impl.SimplePortfolio;
import com.opengamma.core.position.impl.SimplePortfolioNode;
import com.opengamma.engine.view.permission.PortfolioPermission;
import com.opengamma.id.UniqueId;
/**
* Portfolio filter that checks each node and its children to determine
* whether it should remain in the filtered set. The principles for the
* filtering are:
*
* P-root
* - P-child1
* - P-child11
* - P-child2
* - P-child21
* - P-child22
* - P-child3
* - P-child31
*
* If we are permissioned for the whole portfolio, do no filtering.
* If we are permissioned for whole branch e.g. just P-child2 in diagram
* then take that node as the new root. If we are permissioned for discrete
* branches then we need to create artificial parents to fill out the graph
* If we are doing any filtering then aggregates become meaningless (as
* they will include results from missing nodes) so they need to be replaced
* with the artificial nodes.
* Finally, strip off any partially-applicable roots which only have a
* single child as they add clutter without much value
*/
public class NodeCheckingPortfolioFilter implements PortfolioFilter {
/**
* Generates a unique id for the portfolios. As the ids are never
* actually used (for database lookups etc) we can just use an int.
*/
private static int s_portfolioId;
/**
* Generates a unique id for the portfolio nodes. As the ids are never
* actually used (for database lookups etc) we can just use an int.
*/
private static int s_portfolioNodeId;
/**
* The node checker which performs a permission check on an individual
* node disregarding its parents and children.
*/
private final NodeChecker _nodeChecker;
/**
* Constructs the filter with the individual node checker.
*
* @param nodeChecker the node checker
*/
public NodeCheckingPortfolioFilter(NodeChecker nodeChecker) {
_nodeChecker = nodeChecker;
}
@Override
public Portfolio generateRestrictedPortfolio(Portfolio portfolio) {
PortfolioPermissionChecker checker = new PortfolioPermissionChecker(portfolio, _nodeChecker);
PortfolioNode rootNode = portfolio.getRootNode();
Optional<? extends PortfolioNode> newRoot = buildRestrictedRootNode(checker, rootNode);
if (newRoot.isPresent()) {
PortfolioNode node = newRoot.get();
return node.equals(rootNode) ? portfolio : createPortfolioForNode(trimParents(node));
} else {
return new SimplePortfolio("Access Denied");
}
}
/**
* Recursively remove parents whilst there is only a single child.
*
* @param node the node to be trimmed
* @return the trimmed node
*/
private PortfolioNode trimParents(PortfolioNode node) {
return node.getChildNodes().size() == 1 ?
trimParents(node.getChildNodes().get(0)) :
node;
}
private Portfolio createPortfolioForNode(PortfolioNode node) {
return new SimplePortfolio(
UniqueId.of("RESTRICTED_PORTFOLIO", "PF_" + s_portfolioId++),
node.getName(),
new SimplePortfolioNode(node));
}
/**
* Recursively copy or remove nodes depending on whether they are
* accessible or not.
*
* @param checker the checker to use for each node
* @param node the node tree to copy
* @return an optional node tree, empty if there are no permissions, else
* populated with the copied node tree
*/
private Optional<? extends PortfolioNode> buildRestrictedRootNode(PortfolioPermissionChecker checker,
PortfolioNode node) {
switch(checker.permissionCheck(node)) {
case ALLOW:
return Optional.of(node);
case DENY:
return Optional.absent();
default:
SimplePortfolioNode newRoot =
new SimplePortfolioNode(UniqueId.of("RESTRICTED_NODE", "PN_" + s_portfolioNodeId++),
node.getName() + " [restricted]");
newRoot.addPositions(node.getPositions());
for (Map.Entry<PortfolioNode, PortfolioPermission> entry : getAccessibleChildNodes(node, checker).entrySet()) {
PortfolioNode childNode = entry.getValue() == ALLOW ?
entry.getKey() :
buildRestrictedRootNode(checker, entry.getKey()).get();
newRoot.addChildNode(childNode);
}
return Optional.of(newRoot);
}
}
private Map<PortfolioNode, PortfolioPermission> getAccessibleChildNodes(PortfolioNode rootNode, PortfolioPermissionChecker checker) {
Map<PortfolioNode, PortfolioPermission> eligible = new LinkedHashMap<>();
for (PortfolioNode node : rootNode.getChildNodes()) {
PortfolioPermission permission = checker.permissionCheck(node);
if (permission != PortfolioPermission.DENY) {
eligible.put(node, permission);
}
}
return eligible;
}
}