/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * 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/>.
 * #L%
 */
package org.alfresco.repo.transfer;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.Vector;

import org.alfresco.model.ContentModel;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.descriptor.DescriptorService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.PropertyCheck;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Class to encapsulate the behaviour of "Alien" nodes.
 */
public class AlienProcessorImpl implements AlienProcessor
{
    private NodeService nodeService;
    private BehaviourFilter behaviourFilter;
    private DictionaryService dictionaryService;
    private DescriptorService descriptorService;
    
    private static final Log log = LogFactory.getLog(AlienProcessorImpl.class);
    
    public void init()
    {
        PropertyCheck.mandatory(this, "nodeService", nodeService);
        PropertyCheck.mandatory(this, "behaviourFilter", behaviourFilter);
        PropertyCheck.mandatory(this, "dictionaryService", getDictionaryService());
        PropertyCheck.mandatory(this, "descriptorService", descriptorService);
    }

    public void onCreateChild(ChildAssociationRef childAssocRef, final String repositoryId, boolean isNewNode)
    {
        log.debug("on create child association to transferred node");
        
        ChildAssociationRef currentAssoc = childAssocRef;
        NodeRef parentNodeRef = currentAssoc.getParentRef();
        NodeRef childNodeRef = currentAssoc.getChildRef();
                  
        if(!childAssocRef.isPrimary())
        {
            log.debug("not a primary assoc - do nothing");
            return;
        }
                
        /**
         * check assoc is a cm:contains or subtype of cm:contains
         */
        if(!childAssocRef.getTypeQName().equals(ContentModel.ASSOC_CONTAINS))
        {
            Collection<QName> subAspects = dictionaryService.getSubAspects(ContentModel.ASSOC_CONTAINS, true);
            if(!subAspects.contains(childAssocRef.getTypeQName()))
            {
                log.debug("not a subtype of cm:contains - do nothing");
                return; 
            }
        }
            
        if(!(nodeService.hasAspect(parentNodeRef, TransferModel.ASPECT_TRANSFERRED) || nodeService.hasAspect(parentNodeRef, TransferModel.ASPECT_ALIEN)))
        {
            log.debug("parent was not transferred or alien - do nothing");
            return;
        }   
            
        if(!nodeService.hasAspect(parentNodeRef, TransferModel.ASPECT_ALIEN))
        {
            // parent is not yet an alien invader ...
           String parentFromRepo = (String)nodeService.getProperty(parentNodeRef, TransferModel.PROP_FROM_REPOSITORY_ID);
           {
               if(repositoryId.equalsIgnoreCase(parentFromRepo))
               {
                   log.debug("parent was not alien and this node is from the same repo - do nothing");
                   return;
               }
           }
        }
            
        /**
          * If we get this far then we are going to Make the new child node 
          * an alien node
          */
        setAlien(childNodeRef, repositoryId);
            
        /**
          * Now deal with the parents of this alien node
          */
        while(currentAssoc != null)
        {
            parentNodeRef = currentAssoc.getParentRef();
            childNodeRef = currentAssoc.getChildRef();
        
            if(nodeService.hasAspect(parentNodeRef, TransferModel.ASPECT_TRANSFERRED) || nodeService.hasAspect(parentNodeRef, TransferModel.ASPECT_ALIEN))
            {
                if (!isInvaded(parentNodeRef, repositoryId))
                {
                    if(log.isDebugEnabled())
                    {
                        log.debug("alien invades parent node:" + parentNodeRef + ", repositoryId:" + repositoryId);
                    }
                        
                    final NodeRef newAlien = parentNodeRef; 
                     
                    /**
                     * Parent may be locked or not be editable by the current user 
                     * turn off auditing and lock service for this transaction and 
                     * run as admin.
                     */
                     RunAsWork<Void> actionRunAs = new RunAsWork<Void>()
                     {
                        public Void doWork() throws Exception
                        {
                                getBehaviourFilter().disableBehaviour(newAlien, ContentModel.ASPECT_AUDITABLE);
                                getBehaviourFilter().disableBehaviour(newAlien, ContentModel.ASPECT_LOCKABLE);
                                setAlien(newAlien, repositoryId);
                                return null;
                        }          
                    };
                    AuthenticationUtil.runAs(actionRunAs, AuthenticationUtil.getSystemUserName());
             
                    // Yes the parent has been invaded so step up to the parent's parent             
                    currentAssoc = nodeService.getPrimaryParent(parentNodeRef);
                }
                else
                {
                    log.debug("parent node is already invaded");
                    currentAssoc = null;
                }   
            }
            else
            {
                log.debug("parent is not a transferred node");
                currentAssoc = null;
            }
        }            

        if (log.isTraceEnabled())
        {
            logInvasionHierarchy(childAssocRef.getParentRef(), childAssocRef.getChildRef());
        }
    }

    /**
     * Puts information about current <code>childRef</code> and its <code>parentRef</code> into log in TRACE level. Information includes 'name', 'fromRepositoryId', 'aliened' and
     * 'invadedBy' properties. Additionally, collects the same information for children of <code>childRef</code>
     * 
     * @param parentRef - {@link NodeRef} instance of child node
     * @param childRef - {@link NodeRef} instance of parent of the <code>childRef</code>
     */
    protected void logInvasionHierarchy(NodeRef parentRef, NodeRef childRef)
    {
        Map<QName, Serializable> properties = nodeService.getProperties(childRef);
        Map<QName, Serializable> parentProperties = nodeService.getProperties(parentRef);
        StringBuilder message = new StringBuilder("Information about '").append(properties.get(ContentModel.PROP_NAME)).append("' node:\n    fromRepositoryId: ").append(
                properties.get(TransferModel.PROP_FROM_REPOSITORY_ID)).append("\n").append("    invadedBy: ").append(properties.get(TransferModel.PROP_INVADED_BY)).append("\n")
                .append("    alien: ").append(nodeService.hasAspect(childRef, TransferModel.ASPECT_ALIEN)).append("\n").append("    repositoryId: ").append(
                        properties.get(TransferModel.PROP_REPOSITORY_ID)).append("\n").append("    parent: ").append(parentProperties.get(ContentModel.PROP_NAME)).append("(")
                .append(parentProperties.get(TransferModel.PROP_FROM_REPOSITORY_ID)).append(")").append(parentProperties.get(TransferModel.PROP_INVADED_BY)).append(": ").append(
                        nodeService.hasAspect(parentRef, TransferModel.ASPECT_ALIEN)).append("\n").append("    children:\n");

        List<ChildAssociationRef> childAssocs = nodeService.getChildAssocs(childRef);

        if ((null != childAssocs) && !childAssocs.isEmpty())
        {
            for (ChildAssociationRef child : childAssocs)
            {
                properties = nodeService.getProperties(child.getChildRef());
                message.append("        ").append(properties.get(ContentModel.PROP_NAME)).append("(").append(properties.get(TransferModel.PROP_FROM_REPOSITORY_ID)).append(")")
                        .append(properties.get(TransferModel.PROP_INVADED_BY)).append(": ").append(nodeService.hasAspect(child.getChildRef(), TransferModel.ASPECT_ALIEN)).append(
                                "\n");
            }
        }

        log.trace(message.toString());
    }
 
    public void beforeDeleteAlien(NodeRef deletedNodeRef, ChildAssociationRef oldAssoc)
    {
        log.debug("before delete node - need to check for alien invaders");

        List<String>stuff = (List<String>)nodeService.getProperty(deletedNodeRef, TransferModel.PROP_INVADED_BY);
        if (stuff == null) return;
        Vector<String> exInvaders = new Vector<String>(stuff);
        
        /**
         * some fudge to get this to run after the node has been moved.
         */
        ChildAssociationRef currentAssoc;
        if(oldAssoc != null)
        {
            currentAssoc = oldAssoc;
        }
        else
        {
            currentAssoc = nodeService.getPrimaryParent(deletedNodeRef);
        }
     
        while(currentAssoc != null && exInvaders != null && exInvaders.size() > 0)
        {
            NodeRef parentNodeRef = currentAssoc.getParentRef();
            NodeRef currentNodeRef;
            
            if(currentAssoc == oldAssoc)
            { 
                currentNodeRef = deletedNodeRef;
            }
            else
            {
                currentNodeRef = currentAssoc.getChildRef();   
            }
            
            /**
             * Does the parent have alien invaders ?
             */
            if(nodeService.hasAspect(parentNodeRef, TransferModel.ASPECT_ALIEN))
            {
                log.debug("parent node is invaded by aliens");
                
                /**
                 * Remove the parent's origin from the list of exInvaders since the parent also invades.
                 */
                String parentRepoId; 
                if(nodeService.hasAspect(parentNodeRef, TransferModel.ASPECT_TRANSFERRED))
                {
                    parentRepoId = (String)nodeService.getProperty(parentNodeRef, TransferModel.PROP_FROM_REPOSITORY_ID);
                }                
                else
                {
                    parentRepoId = descriptorService.getCurrentRepositoryDescriptor().getId(); 
                }
                
                exInvaders.remove(parentRepoId);
                
                /**
                 * For each invader of the deletedNode
                 */
                Iterator<String> i = exInvaders.listIterator();
                while(i.hasNext())
                {
                    String exInvader = i.next();
                    log.debug("Checking exInvader:" + exInvader);
                    
                    /**
                      * Check the siblings of this node to see whether there are any other alien nodes for this invader.
                      */
                    //List<ChildAssociationRef> refs = nodeService.getChildAssocs(parentNodeRef);
                    List<ChildAssociationRef> refs = nodeService.getChildAssocsByPropertyValue(parentNodeRef, TransferModel.PROP_INVADED_BY, exInvader);
                    
                    for(ChildAssociationRef ref : refs)
                    {
                        NodeRef childRef = ref.getChildRef();
                        List<String>invadedBy = (List<String>)nodeService.getProperty(childRef, TransferModel.PROP_INVADED_BY);
                        
                        if(childRef.equals(currentNodeRef))
                        {
                                // do nothing - this is the node we are working with.
                        }    
                        else 
                        {
                            if(invadedBy != null && invadedBy.contains(exInvader))
                            {
                                // There is a sibling so remove this from the list of ex invaders.
                                log.debug("yes there is a sibling so it remains an invader");
                                i.remove();
                                break;
                            }
                        }
                    } // for each child assoc
                    
                } // for each invader
                
                log.debug("end of checking siblings");
                
                if(exInvaders.size() > 0)
                {
                    log.debug("removing invaders from parent node:" + parentNodeRef);
                    List<String> parentInvaders = (List<String>)nodeService.getProperty(parentNodeRef, TransferModel.PROP_INVADED_BY);
                    
                    final List<String> newInvaders = new ArrayList<String>(10);
                    for(String invader : parentInvaders)
                    {
                        if(exInvaders.contains(invader))
                        {
                            log.debug("removing invader:" + invader);
                        }
                        else
                        {
                            newInvaders.add(invader);
                        }
                    }
                        
                    final NodeRef oldAlien = parentNodeRef;
                  
                    /**
                     * Parent may be locked or not be editable by the current user 
                     * turn off auditing and lock service for this transaction and 
                     * run as admin.
                     */
                    RunAsWork<Void> actionRunAs = new RunAsWork<Void>()
                    {
                        public Void doWork() throws Exception
                        {
                            behaviourFilter.disableBehaviour(oldAlien, ContentModel.ASPECT_AUDITABLE);
                            behaviourFilter.disableBehaviour(oldAlien, ContentModel.ASPECT_LOCKABLE);
                            if(newInvaders.size() > 0)
                            {
                                nodeService.setProperty(oldAlien, TransferModel.PROP_INVADED_BY, (Serializable)newInvaders);
                            }
                            else
                            {
                                log.debug("parent node is no longer alien nodeRef" + oldAlien);
                                nodeService.removeAspect(oldAlien, TransferModel.ASPECT_ALIEN);
                            }
                            return null;
                        }          
                    };
                    AuthenticationUtil.runAs(actionRunAs, AuthenticationUtil.getSystemUserName());
                }
                
                /**
                 * Now step up to the parent's parent
                 */
                 currentAssoc = nodeService.getPrimaryParent(parentNodeRef);
            }
            else
            {
                log.debug("parent is not an alien node");
                currentAssoc = null;
            }
        } // end of while              
    }
    
    
    public void afterMoveAlien(ChildAssociationRef newAssocRef)
    {
        log.debug("after move alien: newAssocRef");
        
        NodeRef parentNodeRef = newAssocRef.getParentRef();
        NodeRef childNodeRef = newAssocRef.getChildRef();
        
        List<String> childInvadedBy = (List<String>)nodeService.getProperty(childNodeRef, TransferModel.PROP_INVADED_BY);
        
        if(nodeService.hasAspect(parentNodeRef, TransferModel.ASPECT_TRANSFERRED) || nodeService.hasAspect(parentNodeRef, TransferModel.ASPECT_ALIEN))
        {
            List<String>aliensToAdd = new ArrayList<String>();
            
            log.debug("new parent is transferred or alien");
                          
            /**
             * check assoc is a cm:contains or subtype of cm:contains
             */
            if(!newAssocRef.getTypeQName().equals(ContentModel.ASSOC_CONTAINS))
            {
                Collection<QName> subAspects = dictionaryService.getSubAspects(ContentModel.ASSOC_CONTAINS, true);
                if(!subAspects.contains(newAssocRef.getTypeQName()))
                {
                    log.debug("not a subtype of cm:contains - may need to uninvade");
                   
                    String parentRepoId = descriptorService.getCurrentRepositoryDescriptor().getId(); 
                    retreatDownwards(childNodeRef, parentRepoId);            
                    
                    return; 
                }
            }
                                
            if(nodeService.hasAspect(parentNodeRef, TransferModel.ASPECT_ALIEN))
            {
                // parent is already alien
                List<String>parentInvadedBy = (List<String>)nodeService.getProperty(parentNodeRef, TransferModel.PROP_INVADED_BY);
                for(String invader : childInvadedBy)
                {
                    if(!parentInvadedBy.contains(invader))
                    {
                        aliensToAdd.add(invader);
                    }
                }
            }
            else
            {
                // parent is transfered but does not yet contain aliens
               String parentFromRepo = (String)nodeService.getProperty(parentNodeRef, TransferModel.PROP_FROM_REPOSITORY_ID);
               {
                   for(String invader : childInvadedBy)
                   {
                       if(invader.equalsIgnoreCase(parentFromRepo))
                       {
                           // The invader is the same repo
                           log.debug("child node is from the same repo as a non invaded node");
                           retreatDownwards(childNodeRef, parentFromRepo);
                       }
                       else
                       {
                           aliensToAdd.add(invader);
                       }
                   }
               }
            }
            
            /**
              * Now deal with the parents of this alien node
              */
            ChildAssociationRef currentAssoc = newAssocRef;
            while(currentAssoc != null && aliensToAdd.size() > 0)
            {
                parentNodeRef = currentAssoc.getParentRef();
                childNodeRef = currentAssoc.getChildRef();
            
                // if parent node is transferred or alien
                if(nodeService.hasAspect(parentNodeRef, TransferModel.ASPECT_TRANSFERRED) || nodeService.hasAspect(parentNodeRef, TransferModel.ASPECT_ALIEN))
                {
                    Iterator<String> i = aliensToAdd.iterator();
                    
                    // for each alien repo id to add
                    while(i.hasNext())
                    {
                        String alienRepoId = (String)i.next();
                    
                        if (!isInvaded(parentNodeRef, alienRepoId))
                        {
                            if(log.isDebugEnabled())
                            {
                                log.debug("alien invades parent node:" + parentNodeRef + ", repositoryId:" + alienRepoId);
                            }
                            
                            final NodeRef newAlien = parentNodeRef; 
                            final String fAlien = alienRepoId;
                            /**
                             * Parent may be locked or not be editable by the current user 
                             * turn off auditing and lock service for this transaction and 
                             * run as admin.
                             */
                            RunAsWork<Void> actionRunAs = new RunAsWork<Void>()
                            {
                                public Void doWork() throws Exception
                                {
                                    getBehaviourFilter().disableBehaviour(newAlien, ContentModel.ASPECT_AUDITABLE);
                                    getBehaviourFilter().disableBehaviour(newAlien, ContentModel.ASPECT_LOCKABLE);
                                    setAlien(newAlien, fAlien);
                                    return null;
                                }          
                            };
                            AuthenticationUtil.runAs(actionRunAs, AuthenticationUtil.getSystemUserName());
                        }
                        else
                        {
                            log.debug("parent node is already invaded by:" + alienRepoId);
                            i.remove();
                        }
                        
                        // Yes the parent has been invaded so step up to the parent's parent             
                        currentAssoc = nodeService.getPrimaryParent(parentNodeRef);
                    }
                }
                else
                {
                    log.debug("parent is not a transferred node");
                    currentAssoc = null;
                }
            }            
        }
        else
        {
            log.debug("parent was not transferred or alien");
            
            String parentRepoId = descriptorService.getCurrentRepositoryDescriptor().getId(); 
            retreatDownwards(childNodeRef, parentRepoId);
            
            return;
        }   
    } // after move alien 
    
    /**
     * Top down un-invasion
     * <p>
     * Steps down the tree retreating from all the invaded nodes.
     * <p>
     * The retreat will stop is there is a "sub-invasion".
     * <p>   
     * @param nodeRef the top of the tree
     * @param fromRepositoryId the repository that is retreating.
     */
    private void retreatDownwards(NodeRef nodeRef, String fromRepositoryId)
    {
        Stack<NodeRef> nodesToRetreat = new Stack<NodeRef>();
        nodesToRetreat.add(nodeRef);
        
        /**
         * Now go and do the retreat.        
         */
        while(!nodesToRetreat.isEmpty())
        {
            if(log.isDebugEnabled())
            {
                log.debug("retreat :" + nodeRef + ", repoId:" + fromRepositoryId);
            }
            
            /**
             *  for the current node and all alien children
             *  
             *  if they are "from" the retreating repository then 
             */
            NodeRef currentNodeRef = nodesToRetreat.pop();
            
            log.debug("retreatNode:" + currentNodeRef);
            
            if(getNodeService().hasAspect(currentNodeRef, TransferModel.ASPECT_ALIEN))
            {
                // Yes this is an alien node
                List<String>invadedBy = (List<String>)getNodeService().getProperty(currentNodeRef, TransferModel.PROP_INVADED_BY);
                
                String parentRepoId; 
                if(nodeService.hasAspect(currentNodeRef, TransferModel.ASPECT_TRANSFERRED))
                {
                    log.debug("node is transferred");
                    parentRepoId = (String)nodeService.getProperty(currentNodeRef, TransferModel.PROP_FROM_REPOSITORY_ID);
                }                
                else
                {
                    log.debug("node is local");
                    parentRepoId = descriptorService.getCurrentRepositoryDescriptor().getId(); 
                }
                
                if(fromRepositoryId.equalsIgnoreCase(parentRepoId))
                {
                    // This node is "owned" by the retreating repo
                    // Yes we are invaded by fromRepositoryId
                    if(invadedBy.size() == 1)
                    {
                        // we are invaded by a single repository which must be fromRepositoryId
                        log.debug("no longe alien:" + currentNodeRef);
                        getNodeService().removeAspect(currentNodeRef, TransferModel.ASPECT_ALIEN);
                    }
                    else
                    {
                       invadedBy.remove(parentRepoId);
                       getNodeService().setProperty(currentNodeRef, TransferModel.PROP_INVADED_BY, (Serializable)invadedBy);
                    }
                    
                    //List<ChildAssociationRef> refs = getNodeService().getChildAssocs(currentNodeRef);
                    List<ChildAssociationRef> refs = nodeService.getChildAssocsByPropertyValue(currentNodeRef, TransferModel.PROP_INVADED_BY, fromRepositoryId);
                    for(ChildAssociationRef ref : refs)
                    {
                        if(log.isDebugEnabled())
                        {
                            log.debug("will need to check child:" + ref);
                        }
                        nodesToRetreat.push(ref.getChildRef());        
                    }        
                }
            } 
        }
    } // retreatDownwards
   
    public boolean isAlien(NodeRef nodeRef)
    {
        return nodeService.hasAspect(nodeRef, TransferModel.ASPECT_ALIEN);
    }

    public void pruneNode(NodeRef nodeToPrune, String fromRepositoryId)
    {
        Stack<NodeRef> nodesToPrune = new Stack<NodeRef>();
        nodesToPrune.add(nodeToPrune);
        
        ChildAssociationRef startingParent = nodeService.getPrimaryParent(nodeToPrune);
        
        Stack<NodeRef> foldersToRecalculate = new Stack<NodeRef>();

        /**
         * Now go and do the pruning.        
         */
        while(!nodesToPrune.isEmpty())
        {
            /**
             *  for all alien children
             * 
             *  if from the repo with no (other) aliens - delete
             *  
             *  if from the repo with multiple alien invasions - leave alone but process children
             */
            NodeRef currentNodeRef = nodesToPrune.pop();

            Map<QName, Serializable> properties = null;
            if (log.isDebugEnabled())
            {
                properties = nodeService.getProperties(currentNodeRef);
            }

            if (log.isDebugEnabled())
            {
                log.debug("Current nodeRef (name: \"" + properties.get(ContentModel.PROP_NAME) + "\", fromRepositoryId: \"" + properties.get(TransferModel.PROP_FROM_REPOSITORY_ID)
                        + "\", manifestId: \"" + fromRepositoryId + "\")" + currentNodeRef);
            }

            log.debug("pruneNode:" + currentNodeRef);

            if(getNodeService().hasAspect(currentNodeRef, TransferModel.ASPECT_ALIEN))
            {
                if (log.isDebugEnabled())
                {
                    log.debug("Current nodeRef has ASPECT_ALIEN (name: \"" + properties.get(ContentModel.PROP_NAME) + "\", fromRepositoryId: \""
                            + properties.get(TransferModel.PROP_FROM_REPOSITORY_ID) + "\", manifestId: \"" + fromRepositoryId + "\")");
                }

                // Yes this is an alien node
                List<String> invadedBy = (List<String>)getNodeService().getProperty(currentNodeRef, TransferModel.PROP_INVADED_BY);
                String initialRepoId = (String) getNodeService().getProperty(currentNodeRef, TransferModel.PROP_FROM_REPOSITORY_ID);

                if (log.isDebugEnabled())
                {
                    log.debug("Current nodeRef has PROP_INVADED_BY (name: \"" + properties.get(ContentModel.PROP_NAME) + "\", fromRepositoryId: \""
                            + properties.get(TransferModel.PROP_FROM_REPOSITORY_ID) + "\", manifestId: \"" + fromRepositoryId + "\"): " + invadedBy);
                }

                if ((null != invadedBy) && invadedBy.contains(fromRepositoryId))
                {
                    if (log.isDebugEnabled())
                    {
                        log.debug("Current nodeRef's PROP_INVADED_BY contains current manifestId (name: \"" + properties.get(ContentModel.PROP_NAME) + "\", fromRepositoryId: \""
                                + properties.get(TransferModel.PROP_FROM_REPOSITORY_ID) + "\", manifestId: \"" + fromRepositoryId + "\")");
                    }

                    // Yes we are invaded by fromRepositoryId
                    if ((1 == invadedBy.size()) && fromRepositoryId.equalsIgnoreCase(initialRepoId))
                    {
                        if (log.isDebugEnabled())
                        {
                            log
                                    .debug("Current nodeRef has only 1 element in PROP_INVADED_BY. Also MANIFEST_ID and INITIAL_REPOSITORY_ID are the same. Deleting the node... (name: \""
                                            + properties.get(ContentModel.PROP_NAME)
                                            + "\", fromRepositoryId: \""
                                            + properties.get(TransferModel.PROP_FROM_REPOSITORY_ID)
                                            + "\", manifestId: \"" + fromRepositoryId + "\")");
                        }

                        // we are invaded by a single repository which must be fromRepositoryId
                        getNodeService().deleteNode(currentNodeRef);
                    }
                    else
                    {
                        if (log.isDebugEnabled())
                        {
                            log.debug("Current 'nodeRef' has more than 1 element in PROP_INVADED_BY. Adding its children to 'nodesToPrune' list... (name: \""
                                    + properties.get(ContentModel.PROP_NAME) + "\", fromRepositoryId: \"" + properties.get(TransferModel.PROP_FROM_REPOSITORY_ID)
                                    + "\", manifestId: \"" + fromRepositoryId + "\")");
                        }

                        // multiple invasion - so it must be a folder
                        List<ChildAssociationRef> refs = nodeService.getChildAssocsByPropertyValue(currentNodeRef, TransferModel.PROP_INVADED_BY, fromRepositoryId);
                        for(ChildAssociationRef ref : refs)
                        {
                            if(log.isDebugEnabled())
                            {
                                log.debug("will need to check child:" + ref);
                            }
                            nodesToPrune.push(ref.getChildRef());        
                        }
                        
                        /**
                         * Yes we might do something to the children of this node.
                         */
                        if(!foldersToRecalculate.contains(currentNodeRef))
                        {
                            if (log.isDebugEnabled())
                            {
                                log.debug("Current 'nodeRef' is not in 'foldersToRecalculate' list. Adding it to the list... (name: \"" + properties.get(ContentModel.PROP_NAME)
                                        + "\", fromRepositoryId: \"" + properties.get(TransferModel.PROP_FROM_REPOSITORY_ID) + "\", manifestId: \"" + fromRepositoryId + "\")");
                            }

                            foldersToRecalculate.push(currentNodeRef);
                        }
                    }
                }
                else
                {
                    if (log.isDebugEnabled())
                    {
                        log.debug("Current \"nodeRef\"'s PROP_INVADED_BY does not contain current 'manifestId' (name: \"" + properties.get(ContentModel.PROP_NAME)
                                + "\", fromRepositoryId: \"" + properties.get(TransferModel.PROP_FROM_REPOSITORY_ID) + "\", manifestId: \"" + fromRepositoryId + "\")");
                    }

                    /**
                     * Current node has been invaded by another repository  
                     *
                     * Need to check fromRepositoryId since its children may need to be pruned
                     */
                    getNodeService().hasAspect(currentNodeRef, TransferModel.ASPECT_TRANSFERRED);
                    {
                        if(fromRepositoryId.equalsIgnoreCase(initialRepoId))
                        {
                            if (log.isDebugEnabled())
                            {
                                log.debug("folder is from the transferring repository");
                                log.debug("Current nodeRef has more than 1 element in PROP_INVADED_BY. Adding its children to 'nodesToPrune' list... (name: \""
                                        + properties.get(ContentModel.PROP_NAME) + "\", fromRepositoryId: \"" + properties.get(TransferModel.PROP_FROM_REPOSITORY_ID)
                                        + "\", manifestId: \"" + fromRepositoryId + "\")");
                            }
                            // invaded from somewhere else - so it must be a folder
                            List<ChildAssociationRef> refs = getNodeService().getChildAssocs(currentNodeRef);
                            for(ChildAssociationRef ref : refs)
                            {
                                if(log.isDebugEnabled())
                                {
                                    log.debug("will need to check child:" + ref);
                                }
                                nodesToPrune.push(ref.getChildRef()); 
                                
                                /**
                                 * This folder can't be deleted so its invaded flag needs to be re-calculated 
                                 */
                                if(!foldersToRecalculate.contains(currentNodeRef))
                                {
                                    if (log.isDebugEnabled())
                                    {
                                        log.debug("Current 'nodeRef' is not in 'foldersToRecalculate' list. Adding it to the list... (name: \""
                                                + properties.get(ContentModel.PROP_NAME) + "\", fromRepositoryId: \"" + properties.get(TransferModel.PROP_FROM_REPOSITORY_ID)
                                                + "\", manifestId: \"" + fromRepositoryId + "\")");
                                    }

                                    foldersToRecalculate.push(currentNodeRef);
                                }
                            }
                        }
                    }        
                }
            }
            else
            {
                // Current node does not contain alien nodes so it can be deleted.
                getNodeService().hasAspect(currentNodeRef, TransferModel.ASPECT_TRANSFERRED);
                {
                    if (log.isDebugEnabled())
                    {
                        log.debug("Current 'nodeRef' does not have ASPECT_ALIEN (name: \"" + properties.get(ContentModel.PROP_NAME) + "\", fromRepositoryId: \""
                                + properties.get(TransferModel.PROP_FROM_REPOSITORY_ID) + "\", manifestId: \"" + fromRepositoryId + "\")");
                    }

                    String initialRepoId = (String) getNodeService().getProperty(currentNodeRef, TransferModel.PROP_REPOSITORY_ID);
                    if(fromRepositoryId.equalsIgnoreCase(initialRepoId))
                    {
                        if (log.isDebugEnabled())
                        {
                            log.debug("Current \"nodeRef\"'s has PROP_FROM_REPOSITORY_ID equal to current 'manifestId'. Deleting the node... (name: \""
                                    + properties.get(ContentModel.PROP_NAME) + "\", fromRepositoryId: \"" + properties.get(TransferModel.PROP_FROM_REPOSITORY_ID)
                                    + "\", manifestId: \"" + fromRepositoryId + "\")");
                            // we are invaded by a single repository
                            log.debug("pruned - deleted non alien node:" + currentNodeRef);
                        }

                        getNodeService().deleteNode(currentNodeRef);
                    }
                }
            }
        }
        
        /**
         * Now recalculate the "invadedBy" flag for those folders we could not delete.
         */
        while(!foldersToRecalculate.isEmpty())
        {
            NodeRef folderNodeRef = foldersToRecalculate.pop();
            
            log.debug("recalculate invadedBy :" + folderNodeRef);
            
            recalcInvasion(folderNodeRef, fromRepositoryId);
        }
        
        /**
         * Now ripple up the invaded flag - may be a alien retreat.
         */
        log.debug("now ripple upwards");
        
        ChildAssociationRef ripple = startingParent;
        while(ripple != null)
        {
            if(log.isDebugEnabled())
            {
                log.debug("Checking parent:" + ripple);
            }
            
            if(nodeService.hasAspect(ripple.getParentRef(), TransferModel.ASPECT_ALIEN))
            {
                if(recalcInvasion(ripple.getParentRef(), fromRepositoryId))
                {
                    log.debug("parent is still invaded");
                    ripple = null;   
                }
                else
                {
                    log.debug("parent is no longer invaded");
                    ripple = nodeService.getPrimaryParent(ripple.getParentRef());            
                }
            }
            else
            {
                ripple = null;
            } 
        }
        
        log.debug("pruneNode: end");        
    }
    
    /**
     * Is this node invaded ?
     * @param nodeRef NodeRef
     * @param invader String
     * @return true, this node has been invaded by the invader
     */
    private boolean isInvaded(NodeRef nodeRef, String invader)
    {
        List<String>invadedBy = (List<String>)nodeService.getProperty(nodeRef, TransferModel.PROP_INVADED_BY);
        
        if(invadedBy == null)
        {
            return false;
        }
        
        return invadedBy.contains(invader);
    }
    
    /**
     * Mark the specified node as an alien node, invaded by the specified invader.
     * @param newAlien node that has been invaded.
     * @param invader the repository id of the invading repo.
     */
    private void setAlien(NodeRef newAlien, String invader)
    {
        // Introduce a Multi-valued property
        List<String> invadedBy = (List<String>)nodeService.getProperty(newAlien, 
                TransferModel.PROP_INVADED_BY);
        
        if(invadedBy == null)
        {
            invadedBy = new ArrayList<String>(1);
        }
        
        if(!invadedBy.contains(invader))
        {
            invadedBy.add(invader);
        }
        
        /**
         * Set the invaded by property
         */
        nodeService.setProperty(newAlien, TransferModel.PROP_INVADED_BY, (Serializable) invadedBy);  
    }
    
    /**
     * Determine whether the specified node is invaded by the specified repository
     * @param folderNodeRef the node to re-calculate
     * @param fromRepositoryId the repository who is transferring.
     * 
     * @return true - still invaded, false, no longer invaded
     */
    private boolean recalcInvasion(NodeRef folderNodeRef, String fromRepositoryId)
    {
        if (log.isTraceEnabled())
        {
            log.trace("#################");
            log.trace("#RECALC INVASION#");
            log.trace("#################");
        }

        List<String> folderInvadedBy = (List<String>)nodeService.getProperty(folderNodeRef, TransferModel.PROP_INVADED_BY);

        if (log.isDebugEnabled())
        {
            log.debug("Node(" + nodeService.getProperty(folderNodeRef, ContentModel.PROP_NAME) + ")" + folderInvadedBy + ": checking '" + fromRepositoryId + "' id...");
        }

        boolean stillInvaded = false;
        boolean hasAlienChild = false;

        //TODO need a more efficient query here
        List<ChildAssociationRef> refs = nodeService.getChildAssocs(folderNodeRef);

        if (log.isDebugEnabled())
        {
            log.debug("Children count: " + refs.size());
            log.debug("Is alien: " + nodeService.hasAspect(folderNodeRef, TransferModel.ASPECT_ALIEN));
        }

        String parentRepositoryId = (String) nodeService.getProperty(folderNodeRef, TransferModel.PROP_FROM_REPOSITORY_ID);
        for (ChildAssociationRef ref : refs)
        {
            NodeRef childNode = ref.getChildRef();

            if (log.isTraceEnabled())
            {
                logInvasionHierarchy(folderNodeRef, childNode);
            }

            Map<QName, Serializable> properties = nodeService.getProperties(childNode);
            List<String> childInvadedBy = (List<String>) properties.get(TransferModel.PROP_INVADED_BY);
            String childRepositoryId = (String) properties.get(TransferModel.PROP_FROM_REPOSITORY_ID);

            hasAlienChild = hasAlienChild || !parentRepositoryId.equalsIgnoreCase(childRepositoryId);

            if (!stillInvaded && (null != childInvadedBy) && (childInvadedBy.contains(fromRepositoryId) || fromRepositoryId.equalsIgnoreCase(childRepositoryId)))
            {
                if (log.isDebugEnabled())
                {
                    log.debug("This child contains current 'fromRepositoryId'. Current folder is still invaded by this repository");
                }

                stillInvaded = true;
            }
        }

        if (!stillInvaded)
        {
            if (log.isDebugEnabled())
            {
                log.debug("Current folder is not invaded by this repository. Updating 'invadedBy' property...");
                log.debug("folder is no longer invaded by this repo:" + folderNodeRef);
            }

            folderInvadedBy.remove(fromRepositoryId);
            if (folderInvadedBy.size() > 0)
            {
                if (log.isDebugEnabled())
                {
                    log.debug("Current folder HAS ANOTHER invasions. Updating the 'invadedBy' property...");
                    log.debug("still invaded by:" + folderInvadedBy);
                }

                getNodeService().setProperty(folderNodeRef, TransferModel.PROP_INVADED_BY, (Serializable) folderInvadedBy);
            }
            else if (!hasAlienChild)
            {
                if (log.isDebugEnabled())
                {
                    log.debug("no longer alien:" + folderNodeRef);
                    log.debug("This invasion was the last one for the current folder. Removing aspect 'ALIEN' completely...");
                }

                getNodeService().removeAspect(folderNodeRef, TransferModel.ASPECT_ALIEN);
            }
        }

        if (log.isTraceEnabled())
        {
            log.trace("#################");
            log.trace("#   COMPLETED   #");
            log.trace("#################");
        }

        return stillInvaded;
    }
            
    public void setNodeService(NodeService nodeService)
    {
        this.nodeService = nodeService;
    }

    public NodeService getNodeService()
    {
        return nodeService;
    }

    public void setBehaviourFilter(BehaviourFilter behaviourFilter)
    {
        this.behaviourFilter = behaviourFilter;
    }

    public BehaviourFilter getBehaviourFilter()
    {
        return behaviourFilter;
    }

    public void setDictionaryService(DictionaryService dictionaryService)
    {
        this.dictionaryService = dictionaryService;
    }

    public DictionaryService getDictionaryService()
    {
        return dictionaryService;
    }
    
    public void setDescriptorService(DescriptorService descriptorService)
    {
        this.descriptorService = descriptorService;
    }

    public DescriptorService getDescriptorService()
    {
        return descriptorService;
    }
}
