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.Stack; import java.util.Vector; import org.alfresco.model.ContentModel; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.Behaviour.NotificationFrequency; 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.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 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()); } public void onCreateChild(ChildAssociationRef childAssocRef, final String repositoryId) { log.debug("on create child association to transferred node"); ChildAssociationRef currentAssoc = childAssocRef; if(!childAssocRef.isPrimary()) { log.debug("not a primary assoc - do nothing"); return; } // TODO Needs to check assoc is a cm:contains or subtype of cm:contains if(!childAssocRef.getTypeQName().equals(ContentModel.ASSOC_CONTAINS)) { Collection subAspects = dictionaryService.getSubAspects(ContentModel.ASSOC_CONTAINS, true); if(!subAspects.contains(childAssocRef.getTypeQName())) { log.debug("not a subtype of cm:contains - do nothing"); return; } } NodeRef parentNodeRef = currentAssoc.getParentRef(); NodeRef childNodeRef = currentAssoc.getChildRef(); if(!nodeService.hasAspect(parentNodeRef, TransferModel.ASPECT_TRANSFERRED)) { log.debug("parent was not transferred - 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 actionRunAs = new RunAsWork() { 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; } } } public void beforeDeleteAlien(NodeRef deletedNodeRef) { log.debug("before delete node - need to check for alien invaders"); Liststuff = (List)nodeService.getProperty(deletedNodeRef, TransferModel.PROP_INVADED_BY); Vector exInvaders = new Vector(stuff); ChildAssociationRef currentAssoc = nodeService.getPrimaryParent(deletedNodeRef); while(currentAssoc != null && exInvaders != null && exInvaders.size() > 0) { NodeRef parentNodeRef = currentAssoc.getParentRef(); NodeRef currentNodeRef = currentAssoc.getChildRef(); /** * Does the parent have alien invaders ? */ if(nodeService.hasAspect(parentNodeRef, TransferModel.ASPECT_ALIEN)) { log.debug("parent node is alien - check siblings"); /** * For each invader of the deletedNode */ Iterator 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. */ //TODO replace with a more efficient query List refs = nodeService.getChildAssocs(parentNodeRef); for(ChildAssociationRef ref : refs) { NodeRef childRef = ref.getChildRef(); ListinvadedBy = (List)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 parentInvaders = (List)nodeService.getProperty(parentNodeRef, TransferModel.PROP_INVADED_BY); final List newInvaders = new ArrayList(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 actionRunAs = new RunAsWork() { 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 boolean isAlien(NodeRef nodeRef) { return nodeService.hasAspect(nodeRef, TransferModel.ASPECT_ALIEN); } public void pruneNode(NodeRef nodeToPrune, String fromRepositoryId) { Stack nodesToPrune = new Stack(); nodesToPrune.add(nodeToPrune); ChildAssociationRef startingParent = nodeService.getPrimaryParent(nodeToPrune); Stack foldersToRecalculate = new Stack(); /** * 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(); log.debug("pruneNode:" + currentNodeRef); if(getNodeService().hasAspect(currentNodeRef, TransferModel.ASPECT_ALIEN)) { // Yes this is an alien node ListinvadedBy = (List)getNodeService().getProperty(currentNodeRef, TransferModel.PROP_INVADED_BY); if(invadedBy.contains(fromRepositoryId)) { // Yes we are invaded by fromRepositoryId if(invadedBy.size() == 1) { // we are invaded by a single repository which must be fromRepositoryId log.debug("pruned - deleted node:" + currentNodeRef); getNodeService().deleteNode(currentNodeRef); } else { log.debug("folder has multiple invaders"); // multiple invasion - so it must be a folder //TODO replace with a more efficient query List refs = getNodeService().getChildAssocs(currentNodeRef); 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)) { foldersToRecalculate.push(currentNodeRef); } } } else { /** * 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); { String fromRepoId = (String)getNodeService().getProperty(currentNodeRef, TransferModel.PROP_FROM_REPOSITORY_ID); if(fromRepositoryId.equalsIgnoreCase(fromRepoId)) { log.debug("folder is from the transferring repository"); // invaded from somewhere else - so it must be a folder List 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)) { foldersToRecalculate.push(currentNodeRef); } } } } } } else { // Current node does not contain alien nodes so it can be deleted. getNodeService().hasAspect(currentNodeRef, TransferModel.ASPECT_TRANSFERRED); { String fromRepoId = (String)getNodeService().getProperty(currentNodeRef, TransferModel.PROP_FROM_REPOSITORY_ID); if(fromRepositoryId.equalsIgnoreCase(fromRepoId)) { // 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 * @param invader * @return true, this node has been invaded by the invader */ private boolean isInvaded(NodeRef nodeRef, String invader) { ListinvadedBy = (List)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 invadedBy = (List)nodeService.getProperty(newAlien, TransferModel.PROP_INVADED_BY); if(invadedBy == null) { invadedBy = new ArrayList(1); } if(!invadedBy.contains(invader)) { invadedBy.add(invader); } /** * Set the invaded by property */ nodeService.setProperty(newAlien, TransferModel.PROP_INVADED_BY, (Serializable) invadedBy); } /** * Recalculate the whether this 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) { ListfolderInvadedBy = (List)nodeService.getProperty(folderNodeRef, TransferModel.PROP_INVADED_BY); boolean stillInvaded = false; //TODO need a more efficient query here List refs = nodeService.getChildAssocs(folderNodeRef); for(ChildAssociationRef ref : refs) { NodeRef childNode = ref.getChildRef(); ListchildInvadedBy = (List)getNodeService().getProperty(childNode, TransferModel.PROP_INVADED_BY); if(childInvadedBy != null && childInvadedBy.contains(fromRepositoryId)) { log.debug("folder is still invaded"); stillInvaded = true; break; } } if(!stillInvaded) { log.debug("folder is no longer invaded by this repo:" + folderNodeRef); folderInvadedBy.remove(fromRepositoryId); if(folderInvadedBy.size() > 0) { if(log.isDebugEnabled()) { log.debug("still invaded by:" + folderInvadedBy); } getNodeService().setProperty(folderNodeRef, TransferModel.PROP_INVADED_BY, (Serializable)folderInvadedBy); } else { log.debug("no longer alien:" + folderNodeRef); getNodeService().removeAspect(folderNodeRef, TransferModel.ASPECT_ALIEN); } } 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; } }