mirror of
				https://github.com/Alfresco/alfresco-community-repo.git
				synced 2025-10-29 15:21:53 +00:00 
			
		
		
		
	51903 to 54309 git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@54310 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
		
			
				
	
	
		
			296 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			296 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (C) 2005-2010 Alfresco Software Limited.
 | 
						|
 *
 | 
						|
 * This file is part of Alfresco
 | 
						|
 *
 | 
						|
 * Alfresco is free software: you can redistribute it and/or modify
 | 
						|
 * it under the terms of the GNU Lesser General Public License as published by
 | 
						|
 * the Free Software Foundation, either version 3 of the License, or
 | 
						|
 * (at your option) any later version.
 | 
						|
 *
 | 
						|
 * Alfresco is distributed in the hope that it will be useful,
 | 
						|
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
 * GNU Lesser General Public License for more details.
 | 
						|
 *
 | 
						|
 * You should have received a copy of the GNU Lesser General Public License
 | 
						|
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | 
						|
 */
 | 
						|
package org.alfresco.repo.node.db;
 | 
						|
 | 
						|
import java.util.ArrayList;
 | 
						|
import java.util.Collection;
 | 
						|
import java.util.HashMap;
 | 
						|
import java.util.List;
 | 
						|
import java.util.Map;
 | 
						|
 | 
						|
import org.alfresco.repo.domain.node.NodeDAO;
 | 
						|
import org.alfresco.service.cmr.repository.AssociationRef;
 | 
						|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
 | 
						|
import org.alfresco.service.cmr.repository.NodeRef;
 | 
						|
import org.alfresco.service.namespace.QName;
 | 
						|
import org.alfresco.util.Pair;
 | 
						|
import org.springframework.dao.ConcurrencyFailureException;
 | 
						|
 | 
						|
/**
 | 
						|
 * Class that walks down a hierarchy gathering view details for later processing.
 | 
						|
 * <p/>
 | 
						|
 * This class is not threadsafe and should be used sequentially on a single
 | 
						|
 * thread and then discarded.
 | 
						|
 * 
 | 
						|
 * @author Derek Hulley
 | 
						|
 * @since 4.1.1
 | 
						|
 */
 | 
						|
public class NodeHierarchyWalker
 | 
						|
{
 | 
						|
    private final NodeDAO nodeDAO;
 | 
						|
    /** Store for all nodes by ID */
 | 
						|
    private final Map<Long, VisitedNode> nodesVisitedById = new HashMap<Long, VisitedNode>(59);
 | 
						|
    /** Store for all nodes by ID */
 | 
						|
    private final Map<NodeRef, VisitedNode> nodesVisitedByNodeRef = new HashMap<NodeRef, VisitedNode>(59);
 | 
						|
    /** Store all the nodes visited from the leaf nodes up */
 | 
						|
    private final List<VisitedNode> nodesLeafToParent = new ArrayList<VisitedNode>(67);
 | 
						|
    /** Store all the nodes visited from parent down */
 | 
						|
    private final List<VisitedNode> nodesParentToLeaf = new ArrayList<VisitedNode>(67);
 | 
						|
 | 
						|
    /**
 | 
						|
     * @param nodeDAO           the low-leve query service
 | 
						|
     */
 | 
						|
    public NodeHierarchyWalker(NodeDAO nodeDAO)
 | 
						|
    {
 | 
						|
        this.nodeDAO = nodeDAO;
 | 
						|
    }
 | 
						|
    
 | 
						|
    /**
 | 
						|
     * @return                  the node data for the node ID or <tt>null</tt> if not visited
 | 
						|
     */
 | 
						|
    public VisitedNode getNode(Long id)
 | 
						|
    {
 | 
						|
        return nodesVisitedById.get(id);
 | 
						|
    }
 | 
						|
    
 | 
						|
    /**
 | 
						|
     * @return                  the node data for the node reference or <tt>null</tt> if not visited
 | 
						|
     */
 | 
						|
    public VisitedNode getNode(NodeRef nodeRef)
 | 
						|
    {
 | 
						|
        return nodesVisitedByNodeRef.get(nodeRef);
 | 
						|
    }
 | 
						|
    
 | 
						|
    /**
 | 
						|
     * Return the IDs of the nodes visited in desired order
 | 
						|
     * 
 | 
						|
     * @param leafFirst         <tt>true</tt> to list the leaf nodes first
 | 
						|
     * @return                  the IDs of the nodes visited
 | 
						|
     */
 | 
						|
    public List<VisitedNode> getNodes(boolean leafFirst)
 | 
						|
    {
 | 
						|
        if (leafFirst)
 | 
						|
        {
 | 
						|
            return nodesLeafToParent;
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            return nodesParentToLeaf;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    /**
 | 
						|
     * Walk a hierachy
 | 
						|
     */
 | 
						|
    public void walkHierarchy(Pair<Long, NodeRef> nodePair, Pair<Long, ChildAssociationRef> parentAssocPair)
 | 
						|
    {
 | 
						|
        Long nodeId = nodePair.getFirst();
 | 
						|
        NodeRef nodeRef = nodePair.getSecond();
 | 
						|
        QName nodeType = nodeDAO.getNodeType(nodeId);
 | 
						|
        Long nodeAclId = nodeDAO.getNodeAclId(nodeId);
 | 
						|
        // Record the first node (parent)
 | 
						|
        VisitedNode visitedNode = new VisitedNode(nodeId, nodeRef, nodeType, nodeAclId, parentAssocPair);
 | 
						|
        nodesVisitedById.put(nodeId, visitedNode);
 | 
						|
        nodesVisitedByNodeRef.put(nodeRef, visitedNode);
 | 
						|
        // Now walk
 | 
						|
        walkNode(nodeId);
 | 
						|
    }
 | 
						|
    
 | 
						|
    /**
 | 
						|
     * Recursive method to gather data about nodes from the leafs upwards
 | 
						|
     */
 | 
						|
    private void walkNode(Long nodeId)
 | 
						|
    {
 | 
						|
        VisitedNode nodeVisited = nodesVisitedById.get(nodeId);
 | 
						|
        if (nodeVisited == null)
 | 
						|
        {
 | 
						|
            throw new IllegalStateException("Parent node has not been visited: " + nodeId);
 | 
						|
        }
 | 
						|
        nodesParentToLeaf.add(nodeVisited);
 | 
						|
        
 | 
						|
        final List<Long> nodesVisitedWorking = new ArrayList<Long>(59);
 | 
						|
        // We have to get to the bottom of the hierarchy
 | 
						|
        NodeDAO.ChildAssocRefQueryCallback walkChildAssocs = new NodeDAO.ChildAssocRefQueryCallback()
 | 
						|
        {
 | 
						|
            public final boolean preLoadNodes()
 | 
						|
            {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
 | 
						|
            @Override
 | 
						|
            public final boolean orderResults()
 | 
						|
            {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
 | 
						|
            public final boolean handle(
 | 
						|
                    Pair<Long, ChildAssociationRef> childAssocPair,
 | 
						|
                    Pair<Long, NodeRef> parentNodePair,
 | 
						|
                    Pair<Long, NodeRef> childNodePair
 | 
						|
                    )
 | 
						|
            {
 | 
						|
                if (childAssocPair.getSecond().isPrimary())
 | 
						|
                {
 | 
						|
                    Long childNodeId = childNodePair.getFirst();
 | 
						|
                    NodeRef childNodeRef = childNodePair.getSecond();
 | 
						|
                    QName childNodeType = nodeDAO.getNodeType(childNodeId);
 | 
						|
                    Long childNodeAclId = nodeDAO.getNodeAclId(childNodeId);
 | 
						|
                    // Keep the IDs of the nodes for recursion
 | 
						|
                    nodesVisitedWorking.add(childNodeId);
 | 
						|
                    // We have a node in the hierarchy to record
 | 
						|
                    VisitedNode visitedNode = new VisitedNode(childNodeId, childNodeRef, childNodeType, childNodeAclId, childAssocPair);
 | 
						|
                    nodesVisitedById.put(childNodeId, visitedNode);
 | 
						|
                    nodesVisitedByNodeRef.put(childNodeRef, visitedNode);
 | 
						|
                }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    Long parentNodeId = parentNodePair.getFirst();
 | 
						|
                    // We don't recurse down secondary associations, so the parent
 | 
						|
                    // must be a previously-visted node
 | 
						|
                    VisitedNode nodeVisitedWorking = nodesVisitedById.get(parentNodeId);
 | 
						|
                    if (nodeVisitedWorking == null)
 | 
						|
                    {
 | 
						|
                        // We came here how?
 | 
						|
                        throw new IllegalStateException(
 | 
						|
                                "Came to secondary association without having found primary parent before: \n" +
 | 
						|
                                "   parent: " + parentNodePair + "\n" +
 | 
						|
                                "   child:  " + childNodePair);
 | 
						|
                    }
 | 
						|
                    // Record the secondary association
 | 
						|
                    nodeVisitedWorking.secondaryChildAssocs.add(childAssocPair);
 | 
						|
                }
 | 
						|
                // Record this node
 | 
						|
                // More results
 | 
						|
                return true;
 | 
						|
            }
 | 
						|
 | 
						|
            public final void done()
 | 
						|
            {
 | 
						|
            }                               
 | 
						|
        };
 | 
						|
 | 
						|
        // Gather all child associations
 | 
						|
        nodeDAO.getChildAssocs(nodeId, null, null, null, null, null, walkChildAssocs);
 | 
						|
        
 | 
						|
        // Dig down to primary children
 | 
						|
        for (Long visitedNodeId : nodesVisitedWorking)
 | 
						|
        {
 | 
						|
            walkNode(visitedNodeId);
 | 
						|
        }
 | 
						|
        
 | 
						|
        // The bottom has been reached.
 | 
						|
        nodesLeafToParent.add(nodeVisited);
 | 
						|
 | 
						|
        // Record parent associations
 | 
						|
        NodeDAO.ChildAssocRefQueryCallback getParentAssocs = new NodeDAO.ChildAssocRefQueryCallback()
 | 
						|
        {
 | 
						|
            @Override
 | 
						|
            public final boolean preLoadNodes()
 | 
						|
            {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
            @Override
 | 
						|
            public boolean orderResults()
 | 
						|
            {
 | 
						|
                return false;
 | 
						|
            }
 | 
						|
            @Override
 | 
						|
            public boolean handle(
 | 
						|
                    Pair<Long, ChildAssociationRef> childAssocPair,
 | 
						|
                    Pair<Long, NodeRef> parentNodePair, Pair<Long, NodeRef> childNodePair)
 | 
						|
            {
 | 
						|
                VisitedNode visitedNode = nodesVisitedById.get(childNodePair.getFirst());
 | 
						|
                if (visitedNode == null)
 | 
						|
                {
 | 
						|
                    throw new IllegalStateException("Querying upwards found nodes not visited: " + childNodePair);
 | 
						|
                }
 | 
						|
                if (childAssocPair.getSecond().isPrimary())
 | 
						|
                {
 | 
						|
                    // Double check the primary association
 | 
						|
                    if (!visitedNode.primaryParentAssocPair.equals(childAssocPair))
 | 
						|
                    {
 | 
						|
                        // The primary parent association for the node has changed
 | 
						|
                        throw new ConcurrencyFailureException("Node parent changed while hierarchy was being examined: " + childNodePair);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    // Record all secondary parent associations
 | 
						|
                    visitedNode.secondaryParentAssocs.add(childAssocPair);
 | 
						|
                }
 | 
						|
                // More results
 | 
						|
                return true;
 | 
						|
            }
 | 
						|
            @Override
 | 
						|
            public void done()
 | 
						|
            {
 | 
						|
            }
 | 
						|
        };
 | 
						|
        nodeDAO.getParentAssocs(nodeId, null, null, null, getParentAssocs);
 | 
						|
        
 | 
						|
        VisitedNode visitedNode = nodesVisitedById.get(nodeId);
 | 
						|
        if (visitedNode == null)
 | 
						|
        {
 | 
						|
            throw new IllegalStateException("Querying upwards found nodes not visited: " + nodeId);
 | 
						|
        }
 | 
						|
        
 | 
						|
        Collection<Pair<Long, AssociationRef>> targetAssocs = nodeDAO.getTargetNodeAssocs(nodeId, null);
 | 
						|
        visitedNode.targetAssocs.addAll(targetAssocs);
 | 
						|
        Collection<Pair<Long, AssociationRef>> sourceAssocs = nodeDAO.getSourceNodeAssocs(nodeId, null);
 | 
						|
        visitedNode.sourceAssocs.addAll(sourceAssocs);
 | 
						|
    }
 | 
						|
    
 | 
						|
    /**
 | 
						|
     * Carries data about a node in the hierarchy
 | 
						|
     * 
 | 
						|
     * @author Derek Hulley
 | 
						|
     * @since 4.1.1
 | 
						|
     */
 | 
						|
    public class VisitedNode
 | 
						|
    {
 | 
						|
        public final Long id;
 | 
						|
        public final NodeRef nodeRef;
 | 
						|
        public final QName nodeType;
 | 
						|
        public final Long aclId;
 | 
						|
        public final Pair<Long, ChildAssociationRef> primaryParentAssocPair;
 | 
						|
        public final List<Pair<Long, ChildAssociationRef>> secondaryParentAssocs;
 | 
						|
        public final List<Pair<Long, ChildAssociationRef>> secondaryChildAssocs;
 | 
						|
        public final List<Pair<Long, AssociationRef>> targetAssocs;
 | 
						|
        public final List<Pair<Long, AssociationRef>> sourceAssocs;
 | 
						|
        
 | 
						|
        private VisitedNode(
 | 
						|
                Long id,
 | 
						|
                NodeRef nodeRef,
 | 
						|
                QName type,
 | 
						|
                Long aclId,
 | 
						|
                Pair<Long, ChildAssociationRef> primaryParentAssocPair)
 | 
						|
        {
 | 
						|
            this.id = id;
 | 
						|
            this.nodeRef = nodeRef;
 | 
						|
            this.nodeType = type;
 | 
						|
            this.aclId = aclId;
 | 
						|
            this.primaryParentAssocPair = primaryParentAssocPair;
 | 
						|
            this.secondaryParentAssocs = new ArrayList<Pair<Long,ChildAssociationRef>>(17);
 | 
						|
            this.secondaryChildAssocs = new ArrayList<Pair<Long,ChildAssociationRef>>(17);
 | 
						|
            this.targetAssocs = new ArrayList<Pair<Long,AssociationRef>>();
 | 
						|
            this.sourceAssocs = new ArrayList<Pair<Long,AssociationRef>>();
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |