/****************************************************************************** * HierarchyImpl.java - created by aaronz on 30 June 2007 * * Copyright (c) 2007 Centre for Academic Research in Educational Technologies * Licensed under the Educational Community License version 1.0 * * A copy of the Educational Community License has been included in this * distribution and is available at: http://www.opensource.org/licenses/ecl1.php * * Contributors: * Aaron Zeckoski (azeckoski@unicon.net) * Antranig Basman (antranig@caret.cam.ac.uk) * *****************************************************************************/ package org.sakaiproject.hierarchy.impl; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.genericdao.api.search.Order; import org.sakaiproject.genericdao.api.search.Restriction; import org.sakaiproject.genericdao.api.search.Search; import org.sakaiproject.hierarchy.HierarchyService; import org.sakaiproject.hierarchy.dao.HierarchyDao; import org.sakaiproject.hierarchy.dao.model.HierarchyNodeMetaData; import org.sakaiproject.hierarchy.dao.model.HierarchyNodePermission; import org.sakaiproject.hierarchy.dao.model.HierarchyPersistentNode; import org.sakaiproject.hierarchy.impl.utils.HierarchyImplUtils; import org.sakaiproject.hierarchy.model.HierarchyNode; import org.sakaiproject.db.api.SqlService; /** * The default implementation of the Hierarchy interface * * @author Aaron Zeckoski (aaronz@vt.edu) */ public class HierarchyServiceImpl implements HierarchyService { private static Log log = LogFactory.getLog(HierarchyServiceImpl.class); private static int ORACLE_IN_CLAUSE_SIZE_LIMIT = 1000; private boolean oracle = false; private HierarchyDao dao; public void setDao(HierarchyDao dao) { this.dao = dao; } private SqlService sqlService; public void setSqlService(SqlService sqlService) { this.sqlService = sqlService; } // private SessionManager sessionManager; // public void setSessionManager(SessionManager sessionManager) { // this.sessionManager = sessionManager; // } public void init() { log.info("init"); if(sqlService != null && "oracle".equalsIgnoreCase(sqlService.getVendor())){ this.oracle = true; } // handle any DB migration/cleanup which needs to happen (mostly for upgrades) dao.fixupDatabase(); } public HierarchyNode createHierarchy(String hierarchyId) { if (hierarchyId.length() < 1 || hierarchyId.length() > 250) { throw new IllegalArgumentException("Invalid hierarchyId (" + hierarchyId + "): length must be 1 to 250 chars"); } long count = dao.countBySearch(HierarchyNodeMetaData.class, new Search("hierarchyId", hierarchyId) ); if (count > 0) { throw new IllegalArgumentException("Invalid hierarchyId (" + hierarchyId + "): this id is already in use, you must use a unique id when creating a new hierarchy"); } HierarchyPersistentNode pNode = new HierarchyPersistentNode(); // no children or parents to // start HierarchyNodeMetaData metaData = new HierarchyNodeMetaData(pNode, hierarchyId, Boolean.TRUE, null); // getCurrentUserId()); saveNodeAndMetaData(pNode, metaData); return HierarchyImplUtils.makeNode(pNode, metaData); } public HierarchyNode setHierarchyRootNode(String hierarchyId, String nodeId) { HierarchyNodeMetaData metaData = getNodeMeta(nodeId); HierarchyNodeMetaData rootMetaData = getRootNodeMetaByHierarchy(hierarchyId); Set<HierarchyNodeMetaData> entities = new HashSet<HierarchyNodeMetaData>(); if (rootMetaData != null) { if (metaData.getId().equals(rootMetaData.getId())) { // this node is already the root node return HierarchyImplUtils.makeNode(metaData); } else if (!metaData.getHierarchyId().equals(rootMetaData.getHierarchyId())) { throw new IllegalArgumentException("Cannot move a node from one hierarchy (" + metaData.getHierarchyId() + ") to another (" + hierarchyId + ") and replace the root node, this could orphan nodes"); } rootMetaData.setIsRootNode(Boolean.FALSE); entities.add(metaData); } if (metaData.getNode().getParentIds() != null) { throw new IllegalArgumentException("Cannot assign a node (" + nodeId + ") to the hierarchy rootNode when it has parents"); } metaData.setIsRootNode(Boolean.TRUE); entities.add(metaData); dao.saveSet(entities); return HierarchyImplUtils.makeNode(metaData); } @SuppressWarnings("rawtypes") public void destroyHierarchy(String hierarchyId) { List<HierarchyNodeMetaData> l = dao.findBySearch(HierarchyNodeMetaData.class, new Search("hierarchyId", hierarchyId) ); if (l.isEmpty()) { throw new IllegalArgumentException("Could not find hierarchy to remove with the following id: " + hierarchyId); } Set<HierarchyPersistentNode> nodes = new HashSet<HierarchyPersistentNode>(); Set<HierarchyNodeMetaData> nodesMetaData = new HashSet<HierarchyNodeMetaData>(); for (int i = 0; i < l.size(); i++) { HierarchyNodeMetaData nmd = (HierarchyNodeMetaData) l.get(i); nodesMetaData.add(nmd); nodes.add(nmd.getNode()); } Set[] entitySets = new Set[] { nodesMetaData, nodes }; dao.deleteMixedSet(entitySets); } public HierarchyNode getRootNode(String hierarchyId) { HierarchyNodeMetaData metaData = getRootNodeMetaByHierarchy(hierarchyId); if (metaData == null) { return null; } return HierarchyImplUtils.makeNode(metaData); } public HierarchyNode getNodeById(String nodeId) { HierarchyNodeMetaData metaData = getNodeMeta(nodeId); return HierarchyImplUtils.makeNode(metaData); } public Map<String, HierarchyNode> getNodesByIds(String[] nodeIds) { List<HierarchyNodeMetaData> nodeMetas = getNodeMetas(nodeIds); Map<String, HierarchyNode> m = new HashMap<String, HierarchyNode>(); for (HierarchyNodeMetaData metaData : nodeMetas) { HierarchyNode node = HierarchyImplUtils.makeNode(metaData); m.put(node.id, node); } return m; } public Set<HierarchyNode> getChildNodes(String nodeId, boolean directOnly) { Set<HierarchyNode> children = new HashSet<HierarchyNode>(); HierarchyNodeMetaData parentMetaData = getNodeMeta(nodeId); String childIdString = null; if (directOnly) { childIdString = parentMetaData.getNode().getDirectChildIds(); } else { childIdString = parentMetaData.getNode().getChildIds(); } if (childIdString == null) { return children; } Set<String> childrenIds = HierarchyImplUtils.makeNodeIdSet(childIdString); List<HierarchyNodeMetaData> childNodeMetas = getNodeMetas(childrenIds); for (HierarchyNodeMetaData metaData : childNodeMetas) { children.add(HierarchyImplUtils.makeNode(metaData)); } return children; } public Set<HierarchyNode> getParentNodes(String nodeId, boolean directOnly) { Set<HierarchyNode> parents = new HashSet<HierarchyNode>(); HierarchyNodeMetaData parentMetaData = getNodeMeta(nodeId); String parentIdString = null; if (directOnly) { parentIdString = parentMetaData.getNode().getDirectParentIds(); } else { parentIdString = parentMetaData.getNode().getParentIds(); } if (parentIdString == null) { return parents; } Set<String> parentsIds = HierarchyImplUtils.makeNodeIdSet(parentIdString); List<HierarchyNodeMetaData> parentNodeMetas = getNodeMetas(parentsIds); for (HierarchyNodeMetaData metaData : parentNodeMetas) { parents.add(HierarchyImplUtils.makeNode(metaData)); } return parents; } public HierarchyNode addNode(String hierarchyId, String parentNodeId) { if (parentNodeId == null) { throw new RuntimeException("Setting parentNodeId to null is not yet supported"); } // validate the parent node and hierarchy (this needs to be cached for sure) HierarchyNodeMetaData parentNodeMeta = getNodeMeta(parentNodeId); if (parentNodeMeta == null) { throw new IllegalArgumentException("Invalid parent node id, cannot find node with id: " + parentNodeId); } if (!parentNodeMeta.getHierarchyId().equals(hierarchyId)) { throw new IllegalArgumentException("Invalid hierarchy id, cannot find node (" + parentNodeId + ") in this hierarchy: " + hierarchyId); } // get the set of all nodes above the new node (these will have to be updated) Set<String> parentNodeIds = HierarchyImplUtils.makeNodeIdSet(parentNodeMeta.getNode().getParentIds()); parentNodeIds.add(parentNodeId); // create the new node and assign the new parents from our parent HierarchyPersistentNode pNode = new HierarchyPersistentNode(HierarchyImplUtils .makeSingleEncodedNodeIdString(parentNodeId), HierarchyImplUtils .makeEncodedNodeIdString(parentNodeIds)); HierarchyNodeMetaData metaData = new HierarchyNodeMetaData(pNode, hierarchyId, Boolean.FALSE, null); // getCurrentUserId()); // save this new node (perhaps we should be saving all of these in one massive update?) -AZ saveNodeAndMetaData(pNode, metaData); String newNodeId = pNode.getId().toString(); // update all the links in the tree for this new node List<HierarchyPersistentNode> pNodesList = getNodes(parentNodeIds); Set<HierarchyPersistentNode> pNodes = new HashSet<HierarchyPersistentNode>(); for (HierarchyPersistentNode node : pNodesList) { if (node.getId().toString().equals(parentNodeId)) { // special case for our parent, update direct children node.setDirectChildIds( HierarchyImplUtils.addSingleNodeIdToEncodedString( node.getDirectChildIds(), newNodeId)); } // update the children for each node node.setChildIds( HierarchyImplUtils.addSingleNodeIdToEncodedString(node.getChildIds(), newNodeId)); // add to the set of node to be saved pNodes.add(node); } dao.saveSet(pNodes); return HierarchyImplUtils.makeNode(pNode, metaData); } public HierarchyNode removeNode(String nodeId) { if (nodeId == null) { throw new NullPointerException("nodeId to remove cannot be null"); } // validate the node HierarchyNodeMetaData metaData = getNodeMeta(nodeId); if (metaData == null) { throw new IllegalArgumentException("Invalid node id, cannot find node with id: " + nodeId); } if (metaData.getIsRootNode().booleanValue()) { throw new IllegalArgumentException("Cannot remove the root node (" + nodeId + "), " + "you must remove the entire hierarchy (" + metaData.getHierarchyId() + ") to remove this root node"); } // get the set of all nodes above the current node (these will have to be updated) HierarchyNode currentNode = HierarchyImplUtils.makeNode(metaData); if (currentNode.childNodeIds.size() != 0) { throw new IllegalArgumentException("Cannot remove a node with children nodes, " + "reduce the children on this node from " + currentNode.childNodeIds.size() + " to 0 before attempting to remove it"); } if (currentNode.directParentNodeIds.size() > 1) { throw new IllegalArgumentException("Cannot remove a node with multiple parents, " + "reduce the parents on this node to 1 before attempting to remove it"); } // get the "main" parent node String currentParentNodeId = getParentNodeId(currentNode); // update all the links in the tree for this removed node List<HierarchyPersistentNode> pNodesList = getNodes(currentNode.parentNodeIds); Set<HierarchyPersistentNode> pNodes = new HashSet<HierarchyPersistentNode>(); for (HierarchyPersistentNode pNode : pNodesList) { if (pNode.getId().toString().equals(currentParentNodeId)) { // special case for our parent, update direct children Set<String> nodeChildren = HierarchyImplUtils.makeNodeIdSet(pNode.getDirectChildIds()); nodeChildren.remove(nodeId); pNode.setDirectChildIds(HierarchyImplUtils.makeEncodedNodeIdString(nodeChildren)); } // update the children for each node Set<String> nodeChildren = HierarchyImplUtils.makeNodeIdSet(pNode.getChildIds()); nodeChildren.remove(nodeId); pNode.setChildIds(HierarchyImplUtils.makeEncodedNodeIdString(nodeChildren)); // add to the set of nodes to be saved pNodes.add(pNode); } dao.saveSet(pNodes); return HierarchyImplUtils.makeNode(getNodeMeta(currentParentNodeId)); } public HierarchyNode saveNodeMetaData(String nodeId, String title, String description, String permToken) { if (nodeId == null) { throw new NullPointerException("nodeId to remove cannot be null"); } // validate the node HierarchyNodeMetaData metaData = getNodeMeta(nodeId); if (metaData == null) { throw new IllegalArgumentException("Invalid node id, cannot find node with id: " + nodeId); } // update the node meta data if (title != null) { if (title.equals("")) { metaData.setTitle(null); } else { metaData.setTitle(title); } } if (description != null) { if (description.equals("")) { metaData.setDescription(null); } else { metaData.setDescription(description); } } if (permToken != null) { if (permToken.equals("")) { metaData.setPermToken(null); } else { metaData.setPermToken(permToken); } } // save the node meta data dao.save(metaData); return HierarchyImplUtils.makeNode(metaData); } public HierarchyNode setNodeDisabled(String nodeId, Boolean isDisabled) { if (nodeId == null) { throw new NullPointerException("nodeId cannot be null"); } // validate the node HierarchyNodeMetaData metaData = getNodeMeta(nodeId); if (metaData == null) { throw new IllegalArgumentException("Invalid node id, cannot find node with id: " + nodeId); } // update the node's isDisabled setting if (isDisabled != null) { metaData.setIsDisabled(isDisabled); } // save the node meta data dao.save(metaData); return HierarchyImplUtils.makeNode(metaData); } public HierarchyNode addChildRelation(String nodeId, String childNodeId) { if (nodeId == null || childNodeId == null) { throw new NullPointerException("nodeId (" + nodeId + ") and childNodeId (" + childNodeId + ") cannot be null"); } if (nodeId.equals(childNodeId)) { throw new IllegalArgumentException("nodeId and childNodeId cannot be the same: " + nodeId); } HierarchyNodeMetaData metaData = getNodeMeta(nodeId); if (metaData == null) { throw new IllegalArgumentException("Invalid nodeId: " + nodeId); } HierarchyNodeMetaData addMetaData = getNodeMeta(childNodeId); if (addMetaData == null) { throw new IllegalArgumentException("Invalid childNodeId: " + childNodeId); } HierarchyNode currentNode = HierarchyImplUtils.makeNode(metaData); // only add this if it is not already in there if (!currentNode.directChildNodeIds.contains(childNodeId)) { // first check for a cycle if (currentNode.childNodeIds.contains(childNodeId) || currentNode.parentNodeIds.contains(childNodeId)) { throw new IllegalArgumentException("Cannot add " + childNodeId + " as a child of " + nodeId + " because it is already in the node tree directly above or below this node"); } // now we go ahead and update this node and all the related nodes HierarchyNode addNode = HierarchyImplUtils.makeNode(addMetaData); Set<HierarchyPersistentNode> pNodes = new HashSet<HierarchyPersistentNode>(); // update the current node metaData.getNode().setDirectChildIds( HierarchyImplUtils.addSingleNodeIdToEncodedString( metaData.getNode().getDirectChildIds(), childNodeId)); metaData.getNode().setChildIds( HierarchyImplUtils.addSingleNodeIdToEncodedString( metaData.getNode().getChildIds(), childNodeId)); pNodes.add(metaData.getNode()); // update the add node addMetaData.getNode().setDirectParentIds( HierarchyImplUtils.addSingleNodeIdToEncodedString( addMetaData.getNode().getDirectParentIds(), nodeId)); addMetaData.getNode().setParentIds( HierarchyImplUtils.addSingleNodeIdToEncodedString( addMetaData.getNode().getParentIds(),nodeId)); pNodes.add(addMetaData.getNode()); // update the parents of the current node (they have new children) List<HierarchyPersistentNode> pNodesList = getNodes(currentNode.parentNodeIds); Set<String> nodesToAdd = addNode.childNodeIds; nodesToAdd.add(addNode.id); for (HierarchyPersistentNode pNode : pNodesList) { // update the children for each node Set<String> nodeChildren = HierarchyImplUtils.makeNodeIdSet(pNode.getChildIds()); nodeChildren.addAll(nodesToAdd); pNode.setChildIds(HierarchyImplUtils.makeEncodedNodeIdString(nodeChildren)); // add to the set of nodes to be saved pNodes.add(pNode); } // update the children of the add node (they have new parants) pNodesList = getNodes(addNode.childNodeIds); nodesToAdd = currentNode.parentNodeIds; nodesToAdd.add(currentNode.id); for (HierarchyPersistentNode pNode : pNodesList) { // update the parents for each node Set<String> parents = HierarchyImplUtils.makeNodeIdSet(pNode.getParentIds()); parents.addAll(nodesToAdd); pNode.setParentIds(HierarchyImplUtils.makeEncodedNodeIdString(parents)); // add to the set of nodes to be saved pNodes.add(pNode); } dao.saveSet(pNodes); } return HierarchyImplUtils.makeNode(metaData); } public HierarchyNode removeChildRelation(String nodeId, String childNodeId) { if (nodeId == null || childNodeId == null) { throw new NullPointerException("nodeId (" + nodeId + ") and childNodeId (" + childNodeId + ") cannot be null"); } if (nodeId.equals(childNodeId)) { throw new IllegalArgumentException("nodeId and childNodeId cannot be the same: " + nodeId); } HierarchyNodeMetaData metaData = getNodeMeta(nodeId); if (metaData == null) { throw new IllegalArgumentException("Invalid nodeId: " + nodeId); } HierarchyNodeMetaData removeMetaData = getNodeMeta(childNodeId); if (removeMetaData == null) { throw new IllegalArgumentException("Invalid childNodeId: " + childNodeId); } HierarchyNode currentNode = HierarchyImplUtils.makeNode(metaData); // only do something if this child is a direct child of this node if (currentNode.directChildNodeIds.contains(childNodeId)) { // first check for orphaning HierarchyNode removeNode = HierarchyImplUtils.makeNode(removeMetaData); if (removeNode.directParentNodeIds.size() <= 1) { throw new IllegalArgumentException("Cannot remove " + childNodeId + " as a child of " + nodeId + " because it would orphan the child node, you need to use the remove method" + "if you want to remove a node or add this node as the child of another node first"); } // now we go ahead and update this node and all the related nodes Set<HierarchyPersistentNode> pNodes = new HashSet<HierarchyPersistentNode>(); Set<String> nodes = null; // update the current node nodes = HierarchyImplUtils.makeNodeIdSet(metaData.getNode().getChildIds()); nodes.remove(childNodeId); metaData.getNode().setChildIds(HierarchyImplUtils.makeEncodedNodeIdString(nodes)); nodes = HierarchyImplUtils.makeNodeIdSet(metaData.getNode().getDirectChildIds()); nodes.remove(childNodeId); metaData.getNode().setDirectChildIds(HierarchyImplUtils.makeEncodedNodeIdString(nodes)); pNodes.add(metaData.getNode()); // update the remove node nodes = HierarchyImplUtils.makeNodeIdSet(removeMetaData.getNode().getParentIds()); nodes.remove(nodeId); removeMetaData.getNode().setParentIds(HierarchyImplUtils.makeEncodedNodeIdString(nodes)); nodes = HierarchyImplUtils.makeNodeIdSet(removeMetaData.getNode().getDirectParentIds()); nodes.remove(nodeId); removeMetaData.getNode().setDirectParentIds(HierarchyImplUtils.makeEncodedNodeIdString(nodes)); pNodes.add(removeMetaData.getNode()); // update the parents of the current node (they have less children) List<HierarchyPersistentNode> pNodesList = getNodes(currentNode.parentNodeIds); Set<String> nodesToRemove = removeNode.childNodeIds; nodesToRemove.add(removeNode.id); for (HierarchyPersistentNode pNode : pNodesList) { // update the children for each node Set<String> children = HierarchyImplUtils.makeNodeIdSet(pNode.getChildIds()); children.removeAll(nodesToRemove); // add back in all the children of the currentNode because we may have // taken out part of the tree below where if it connects to the children of removeNode children.addAll(currentNode.childNodeIds); pNode.setChildIds(HierarchyImplUtils.makeEncodedNodeIdString(children)); // add to the set of nodes to be saved pNodes.add(pNode); } // update the children of the remove node (they have lost parents) pNodesList = getNodes(removeNode.childNodeIds); nodesToRemove = currentNode.parentNodeIds; nodesToRemove.add(currentNode.id); for (HierarchyPersistentNode pNode : pNodesList) { // update the parents for each node Set<String> parents = HierarchyImplUtils.makeNodeIdSet(pNode.getParentIds()); parents.removeAll(nodesToRemove); // add back in all the parents of the removeNode because we will have // taken out part of the tree above where it reconnects on the way to the root parents.addAll(removeNode.parentNodeIds); pNode.setParentIds(HierarchyImplUtils.makeEncodedNodeIdString(parents)); // add to the set of nodes to be saved pNodes.add(pNode); } dao.saveSet(pNodes); } return HierarchyImplUtils.makeNode(metaData); } public HierarchyNode addParentRelation(String nodeId, String parentNodeId) { // TODO Not implemented yet - not sure we even want to allow this throw new RuntimeException("This method is not implemented yet"); } public HierarchyNode removeParentRelation(String nodeId, String parentNodeId) { // TODO Not implemented yet - not sure this is even a good idea throw new RuntimeException("This method is not implemented yet"); } public Set<String> getNodesWithToken(String hierarchyId, String permToken) { if (permToken == null || permToken.equals("")) { throw new NullPointerException("permToken cannot be null or empty string"); } List<HierarchyNodeMetaData> l = dao.findBySearch(HierarchyNodeMetaData.class, new Search("hierarchyId", hierarchyId) ); if (l.isEmpty()) { throw new IllegalArgumentException("Could not find hierarchy with the following id: " + hierarchyId); } List<HierarchyNodeMetaData> nodeIdsList = dao.findBySearch(HierarchyNodeMetaData.class, new Search(new Restriction[] { new Restriction("hierarchyId", hierarchyId), new Restriction("permToken", permToken) }, new Order("node.id"))); Set<String> nodeIds = new TreeSet<String>(); for (Iterator<HierarchyNodeMetaData> iter = nodeIdsList.iterator(); iter.hasNext();) { HierarchyNodeMetaData metaData = iter.next(); nodeIds.add(metaData.getNode().getId().toString()); } return nodeIds; } public Map<String, Set<String>> getNodesWithTokens(String hierarchyId, String[] permTokens) { // it would be better if this were more efficient... if (permTokens == null) { throw new NullPointerException("permTokens cannot be null"); } Map<String, Set<String>> tokenNodes = new HashMap<String, Set<String>>(); for (int i = 0; i < permTokens.length; i++) { Set<String> nodeIds = getNodesWithToken(hierarchyId, permTokens[i]); tokenNodes.put(permTokens[i], nodeIds); } return tokenNodes; } // PERMISSIONS public void assignUserNodePerm(String userId, String nodeId, String hierarchyPermission, boolean cascade) { if (userId == null || "".equals(userId) || nodeId == null || "".equals(nodeId) || hierarchyPermission == null || "".equals(hierarchyPermission)) { throw new IllegalArgumentException("Invalid arguments to assignUserNodePerm, no arguments can be null or blank: userId="+userId+", nodeId="+nodeId+", hierarchyPermission="+hierarchyPermission); } HierarchyNodePermission nodePerm = dao.findOneBySearch(HierarchyNodePermission.class, new Search( new Restriction[] { new Restriction("userId", userId), new Restriction("nodeId", nodeId), new Restriction("permission", hierarchyPermission) })); if (nodePerm == null) { // validate the nodeId Long nodeIdeNum; try { nodeIdeNum = new Long(nodeId); } catch (NumberFormatException e) { throw new IllegalArgumentException("Node id ("+nodeId+") provided is invalid, must be a valid identifier from an existing node"); } // check it exists HierarchyPersistentNode pNode = dao.findById(HierarchyPersistentNode.class, nodeIdeNum); if (pNode == null) { throw new IllegalArgumentException("Node id ("+nodeId+") provided is invalid, node does not exist"); } // create the perm dao.create( new HierarchyNodePermission(userId, nodeId, hierarchyPermission) ); } else { // permission already set, do nothing } if (cascade) { // cascade the permission creation HierarchyNode node = getNodeById(nodeId); if (node != null && node.childNodeIds != null && node.childNodeIds.size() > 0) { List<String> nodeIdsList = new ArrayList<String>(node.childNodeIds); int i = 0; List<HierarchyNodePermission> nodePerms = new ArrayList<HierarchyNodePermission>(); do{ int arraySize = nodeIdsList.size() - i; if(oracle && arraySize > ORACLE_IN_CLAUSE_SIZE_LIMIT){ arraySize = ORACLE_IN_CLAUSE_SIZE_LIMIT; } // get all the permissions which are related to the nodes under this one List<HierarchyNodePermission> nodePermsItteration = dao.findBySearch(HierarchyNodePermission.class, new Search( new Restriction[] { new Restriction("userId", userId), new Restriction("permission", hierarchyPermission), new Restriction("nodeId", nodeIdsList.subList(i, i + arraySize).toArray()) })); nodePerms.addAll(nodePermsItteration); i += arraySize; }while(i < nodeIdsList.size()); Set<HierarchyNodePermission> allPerms = new HashSet<HierarchyNodePermission>(); if (nodePerms.size() == 0) { // add all new ones for (String childNodeId : node.childNodeIds) { allPerms.add( new HierarchyNodePermission(userId, childNodeId, hierarchyPermission) ); } } else { // only add the missing ones Set<String> existingPermNodeIds = new HashSet<String>(); for (HierarchyNodePermission hierNodePerm : nodePerms) { existingPermNodeIds.add( hierNodePerm.getNodeId() ); } for (String childNodeId : node.childNodeIds) { if (! existingPermNodeIds.contains(nodeId)) { allPerms.add( new HierarchyNodePermission(userId, childNodeId, hierarchyPermission) ); } } } if (nodePerms.size() == node.childNodeIds.size() || allPerms.size() == 0) { // nothing to do here, all permissions already exist or there are none to add } else { // save the new permissions dao.saveSet( new HashSet<HierarchyNodePermission>(allPerms) ); } } } } public void removeUserNodePerm(String userId, String nodeId, String hierarchyPermission, boolean cascade) { if (userId == null || "".equals(userId) || nodeId == null || "".equals(nodeId) || hierarchyPermission == null || "".equals(hierarchyPermission)) { throw new IllegalArgumentException("Invalid arguments to removeUserNodePerm, no arguments can be null or blank: userId="+userId+", nodeId="+nodeId+", hierarchyPermission="+hierarchyPermission); } if (! cascade) { // delete the current permission if it can be found HierarchyNodePermission nodePerm = dao.findOneBySearch(HierarchyNodePermission.class, new Search( new Restriction[] { new Restriction("userId", userId), new Restriction("nodeId", nodeId), new Restriction("permission", hierarchyPermission) })); if (nodePerm == null) { // not found, nothing to do } else { dao.delete(nodePerm); } } else { // cascade the permission removal and delete current one as well HierarchyNode node = getNodeById(nodeId); if (node != null) { HashSet<String> nodeIdsSet = new HashSet<String>(); nodeIdsSet.add(nodeId); // add in child nodes if there are any if (node.childNodeIds != null && node.childNodeIds.size() > 0) { nodeIdsSet.addAll(node.childNodeIds); } List<String> nodeIdsList = new ArrayList<String>(nodeIdsSet); int i = 0; do{ int arraySize = nodeIdsList.size() - i; if(oracle && arraySize > ORACLE_IN_CLAUSE_SIZE_LIMIT){ arraySize = ORACLE_IN_CLAUSE_SIZE_LIMIT; } // get all the permissions which are related to the nodes under this one List<HierarchyNodePermission> nodePerms = dao.findBySearch(HierarchyNodePermission.class, new Search( new Restriction[] { new Restriction("userId", userId), new Restriction("permission", hierarchyPermission), new Restriction("nodeId", nodeIdsList.subList(i, i + arraySize).toArray()) })); if (nodePerms.size() > 0) { // delete all as one operation dao.deleteSet( new HashSet<HierarchyNodePermission>(nodePerms) ); } i += arraySize; }while(i < nodeIdsList.size()); } } } public boolean checkUserNodePerm(String userId, String nodeId, String hierarchyPermission) { if (userId == null || "".equals(userId) || nodeId == null || "".equals(nodeId) || hierarchyPermission == null || "".equals(hierarchyPermission)) { throw new IllegalArgumentException("Invalid arguments to checkUserNodePerm, no arguments can be null or blank: userId="+userId+", nodeId="+nodeId+", hierarchyPermission="+hierarchyPermission); } boolean allowed = false; HierarchyNodePermission nodePerm = dao.findOneBySearch(HierarchyNodePermission.class, new Search( new Restriction[] { new Restriction("userId", userId), new Restriction("nodeId", nodeId), new Restriction("permission", hierarchyPermission) })); if (nodePerm != null) { allowed = true; } return allowed; } public Set<HierarchyNode> getNodesForUserPerm(String userId, String hierarchyPermission) { if (userId == null || "".equals(userId) || hierarchyPermission == null || "".equals(hierarchyPermission)) { throw new IllegalArgumentException("Invalid arguments to getNodesForUserPerm, no arguments can be null or blank: userId="+userId+", hierarchyPermission="+hierarchyPermission); } Set<HierarchyNode> nodes = new HashSet<HierarchyNode>(); List<HierarchyNodePermission> nodePerms = dao.findBySearch(HierarchyNodePermission.class, new Search( new Restriction[] { new Restriction("userId", userId), new Restriction("permission", hierarchyPermission) })); Set<String> nodeIds = new HashSet<String>(); for (HierarchyNodePermission nodePerm : nodePerms) { nodeIds.add( nodePerm.getNodeId() ); } List<HierarchyNodeMetaData> nodeMetas = getNodeMetas(nodeIds); for (HierarchyNodeMetaData metaData : nodeMetas) { nodes.add( HierarchyImplUtils.makeNode(metaData) ); } return nodes; } public Set<String> getUserIdsForNodesPerm(String[] nodeIds, String hierarchyPermission) { if (nodeIds == null || hierarchyPermission == null || "".equals(hierarchyPermission)) { throw new IllegalArgumentException("Invalid arguments to getUserIdsForNodesPerm, no arguments can be null or blank: hierarchyPermission="+hierarchyPermission); } Set<String> userIds = new HashSet<String>(); if (nodeIds.length > 0) { List<String> nodeIdsList = new ArrayList<String>(Arrays.asList(nodeIds)); int i = 0; do{ int arraySize = nodeIdsList.size() - i; if(oracle && arraySize > ORACLE_IN_CLAUSE_SIZE_LIMIT){ arraySize = ORACLE_IN_CLAUSE_SIZE_LIMIT; } List<HierarchyNodePermission> nodePerms = dao.findBySearch(HierarchyNodePermission.class, new Search( new Restriction[] { new Restriction("nodeId", nodeIdsList.subList(i, i + arraySize).toArray()), new Restriction("permission", hierarchyPermission) })); for (HierarchyNodePermission nodePerm : nodePerms) { userIds.add( nodePerm.getUserId() ); } i += arraySize; }while(i < nodeIdsList.size()); } return userIds; } public Set<String> getPermsForUserNodes(String userId, String[] nodeIds) { if (userId == null || "".equals(userId) || nodeIds == null ) { throw new IllegalArgumentException("Invalid arguments to getPermsForUserNodes, no arguments can be null or blank: userId="+userId); } Set<String> perms = new HashSet<String>(); if (nodeIds.length > 0) { List<String> nodeIdsList = new ArrayList<String>(Arrays.asList(nodeIds)); int i = 0; do{ int arraySize = nodeIdsList.size() - i; if(oracle && arraySize > ORACLE_IN_CLAUSE_SIZE_LIMIT){ arraySize = ORACLE_IN_CLAUSE_SIZE_LIMIT; } List<HierarchyNodePermission> nodePerms = dao.findBySearch(HierarchyNodePermission.class, new Search( new Restriction[] { new Restriction("userId", userId), new Restriction("nodeId", nodeIdsList.subList(i, i + arraySize).toArray()) })); for (HierarchyNodePermission nodePerm : nodePerms) { perms.add( nodePerm.getPermission() ); } i += arraySize; }while(i < nodeIdsList.size()); } return perms; } public Map<String, Map<String, Set<String>>> getUsersAndPermsForNodes(String... nodeIds) { if (nodeIds == null || nodeIds.length == 0) { throw new IllegalArgumentException("Invalid arguments to getUsersAndPermsForNodes, no arguments can be null or blank: nodeIds="+nodeIds); } Map<String, Map<String, Set<String>>> m = new HashMap<String, Map<String,Set<String>>>(); for (String nodeId : nodeIds) { m.put(nodeId, new HashMap<String, Set<String>>()); } List<HierarchyNodePermission> nodePerms = new ArrayList<HierarchyNodePermission>(); List<String> nodeIdsList = new ArrayList<String>(Arrays.asList(nodeIds)); int i = 0; do{ int arraySize = nodeIdsList.size() - i; if(oracle && arraySize > ORACLE_IN_CLAUSE_SIZE_LIMIT){ arraySize = ORACLE_IN_CLAUSE_SIZE_LIMIT; } List<HierarchyNodePermission> nodePermsItteration = dao.findBySearch(HierarchyNodePermission.class, new Search("nodeId", nodeIdsList.subList(i, i + arraySize).toArray())); nodePerms.addAll(nodePermsItteration); i += arraySize; }while(i < nodeIdsList.size()); // nodeId -> (map of userId -> Set(permission)) for (HierarchyNodePermission nodePerm : nodePerms) { String nodeId = nodePerm.getNodeId(); if (! m.containsKey(nodeId)) { continue; // this should not really happen but better safe than sorry } String userId = nodePerm.getUserId(); if (! m.get(nodeId).containsKey(userId) ) { m.get(nodeId).put(userId, new HashSet<String>() ); } m.get(nodeId).get(userId).add( nodePerm.getPermission() ); } return m; } public Map<String, Map<String, Set<String>>> getNodesAndPermsForUser(String... userIds) { if (userIds == null || userIds.length == 0) { throw new IllegalArgumentException("Invalid arguments to getNodesAndPermsForUser, no arguments can be null or blank: userIds="+userIds); } Map<String, Map<String, Set<String>>> m = new HashMap<String, Map<String,Set<String>>>(); for (String userId : userIds) { m.put(userId, new HashMap<String, Set<String>>()); } List<String> userIdsList = new ArrayList<String>(Arrays.asList(userIds)); int i = 0; List<HierarchyNodePermission> nodePerms = new ArrayList<HierarchyNodePermission>(); do{ int arraySize = userIdsList.size() - i; if(oracle && arraySize > ORACLE_IN_CLAUSE_SIZE_LIMIT){ arraySize = ORACLE_IN_CLAUSE_SIZE_LIMIT; } List<HierarchyNodePermission> nodePermsItteration = dao.findBySearch(HierarchyNodePermission.class, new Search("userId", userIdsList.subList(i, i + arraySize).toArray())); nodePerms.addAll(nodePermsItteration); i += arraySize; }while(i < userIdsList.size()); // userId -> (map of nodeId -> Set(permission)) for (HierarchyNodePermission nodePerm : nodePerms) { String userId = nodePerm.getUserId(); if (! m.containsKey(userId)) { continue; // this should not really happen but better safe than sorry } String nodeId = nodePerm.getNodeId(); if (! m.get(userId).containsKey(nodeId) ) { m.get(userId).put(nodeId, new HashSet<String>() ); } m.get(userId).get(nodeId).add( nodePerm.getPermission() ); } return m; } // PRIVATE /** * Convenience method to save a node and metadata in one transaction * * @param pNode * @param metaData */ @SuppressWarnings("rawtypes") private void saveNodeAndMetaData(HierarchyPersistentNode pNode, HierarchyNodeMetaData metaData) { Set<HierarchyPersistentNode> pNodes = new HashSet<HierarchyPersistentNode>(); pNodes.add(pNode); Set<HierarchyNodeMetaData> metaDatas = new HashSet<HierarchyNodeMetaData>(); metaDatas.add(metaData); Set[] entitySets = new Set[] { pNodes, metaDatas }; dao.saveMixedSet(entitySets); /* NORMALLY the code below should not be needed, however, * we are seeing weird cases where the line above fails to create the metadata * so the code below is meant to detect that case and correct it by saving * each separately and realigning the ids manually */ if (metaData.getId() == null) { // something went wrong and we're not sure what so delete pNode if (pNode.getId() != null) { dao.delete(pNode); } throw new RuntimeException("Metadata didn't save, node was removed: "+pNode); } else if (pNode.getId() == null) { // something went wrong and we're not sure what so delete metadata if (metaData.getId() != null) { dao.delete(metaData); } throw new RuntimeException("Metadata didn't save, metaData was removed: "+metaData); } else if (!metaData.getId().equals(pNode.getId())) { // the indexes are off... let's try to get them back in sync int i = 0; if (pNode.getId() > metaData.getId()) { while (i < 100 && metaData.getId() != null && pNode.getId() != metaData.getId()) { // need to keep saving metaData until it's sequence has caught up dao.delete(metaData); // set ID back to null to make it save with a new incremented ID metaData.setId(null); dao.save(metaData); i++; } } else { while (i < 100 && pNode.getId() != null && pNode.getId() != metaData.getId()) { // need to keep saving node until it's sequence has caught up dao.delete(pNode); // set ID back to null to make it save with a new incremented ID pNode.setId(null); dao.save(pNode); i++; } } if (pNode.getId() == null || metaData.getId() == null || pNode.getId() != metaData.getId()) { // ok we tried, it didn't work, so throw the exception throw new RuntimeException("Node ID: " + pNode.getId() + " doesn't match Metadata ID: " + metaData.getId()); } } } /** * Fetch node data from storage * * @param nodeId * @return a {@link HierarchyNodeMetaData} or null if not found */ private HierarchyNodeMetaData getNodeMeta(String nodeId) { List<HierarchyNodeMetaData> l = dao.findBySearch(HierarchyNodeMetaData.class, new Search("node.id", new Long(nodeId))); if (l.size() > 1) { throw new IllegalStateException("Invalid hierarchy state: more than one node with id: " + nodeId); } else if (l.size() == 1) { return l.get(0); } else { return null; } } /** * Find the current root node * * @param hierarchyId * @return the root {@link HierarchyNodeMetaData} of the hierarchy */ private HierarchyNodeMetaData getRootNodeMetaByHierarchy(String hierarchyId) { List<HierarchyNodeMetaData> l = dao.findBySearch(HierarchyNodeMetaData.class, new Search(new Restriction[] { new Restriction("hierarchyId", hierarchyId), new Restriction("isRootNode", Boolean.TRUE) }) ); if (l.size() > 1) { throw new IllegalStateException("Invalid hierarchy state: more than one root node for hierarchyId: " + hierarchyId); } else if (l.size() == 1) { return l.get(0); } else { return null; } } /** * Get all nodes and meta data based on a set of nodeIds * * @param nodeIds * @return */ private List<HierarchyNodeMetaData> getNodeMetas(Set<String> nodeIds) { return getNodeMetas(nodeIds.toArray(new String[] {})); } private List<HierarchyNodeMetaData> getNodeMetas(String[] nodeIds) { List<HierarchyNodeMetaData> l = null; if (nodeIds == null || nodeIds.length == 0) { l = new ArrayList<HierarchyNodeMetaData>(); } else { Long[] pNodeIds = new Long[nodeIds.length]; for (int i = 0; i < nodeIds.length; i++) { pNodeIds[i] = new Long(nodeIds[i]); } l = new ArrayList<HierarchyNodeMetaData>(); List<Long> nodeIdsList = new ArrayList<Long>(Arrays.asList(pNodeIds)); int i = 0; do{ int arraySize = nodeIdsList.size() - i; if(oracle && arraySize > ORACLE_IN_CLAUSE_SIZE_LIMIT){ arraySize = ORACLE_IN_CLAUSE_SIZE_LIMIT; } List<HierarchyNodeMetaData> lIterration = dao.findBySearch(HierarchyNodeMetaData.class, new Search("node.id", nodeIdsList.subList(i, i + arraySize).toArray()) ); l.addAll(lIterration); i += arraySize; }while(i < nodeIdsList.size()); } return l; } /** * Get all nodes only based on a set of nodeIds * * @param nodeIds * @return */ private List<HierarchyPersistentNode> getNodes(Set<String> nodeIds) { return getNodes(nodeIds.toArray(new String[] {})); } private List<HierarchyPersistentNode> getNodes(String[] nodeIds) { List<HierarchyPersistentNode> l = null; if (nodeIds == null || nodeIds.length == 0) { l = new ArrayList<HierarchyPersistentNode>(); } else { Long[] pNodeIds = new Long[nodeIds.length]; for (int i = 0; i < nodeIds.length; i++) { pNodeIds[i] = new Long(nodeIds[i]); } l = new ArrayList<HierarchyPersistentNode>(); List<Long> nodeIdsList = new ArrayList<Long>(Arrays.asList(pNodeIds)); int i = 0; do{ int arraySize = nodeIdsList.size() - i; if(oracle && arraySize > ORACLE_IN_CLAUSE_SIZE_LIMIT){ arraySize = ORACLE_IN_CLAUSE_SIZE_LIMIT; } List<HierarchyPersistentNode> lIterration = dao.findBySearch(HierarchyPersistentNode.class, new Search("id", nodeIdsList.subList(i, i + arraySize).toArray()) ); l.addAll(lIterration); i += arraySize; }while(i < nodeIdsList.size()); } return l; } /** * Find the direct parent node id for a node * @param node * @return the node if or null if none exists */ private String getParentNodeId(HierarchyNode node) { String parentNodeId = null; if (node.directParentNodeIds != null && node.directParentNodeIds.size() > 0) { parentNodeId = node.directParentNodeIds.iterator().next(); } return parentNodeId; } }