mirror of
				https://github.com/Alfresco/alfresco-community-repo.git
				synced 2025-10-29 15:21:53 +00:00 
			
		
		
		
	27908: ALF-8706: RSOLR 023: Locale is persisted for all nodes
          - Part of ALF-7991: RSOLR 023: Locale support for query: Repository
          - Added alf_node.locale_id mandatory column
          - sys:localizable is now spoofed and mandatory on sys:base
          - sys:locale property is always available
          - The locale set is the I18NUtil.getLocale() (thread then server)
          - TODO: Upgrade scripts, so drop database to take this change
   27909: ALF-8706: RSOLR 023: Locale is persisted for all nodes
          - Part of ALF-7991: RSOLR 023: Locale support for query: Repository
          - Extended locale test to ensure that sys:locale can be set using properties during create
   27910: ALF-8707: RSOLR 023: Upgrade to give nodes their default locales
          - Part of ALF-7991:RSOLR 023: Locale support for query: Repository
          - Upgrade scripts for PostgreSQL and MySQL: patch.db-V4.0-Node-Locale
   27912: WCM QS ML Support for creating missing intermediate directories when adding an "orphan" translation, plus tests
   27913: Fixed failing JbpmEngineUnitTest.
   27914: Build fix. Fixes failing test DiscussionServiceTest.testDeleteTopLevelPost
   27916: WCM QS ML forms config for overrideable name, and passing the initially orphaned flag through to the behaviour
   27917: Enable the WCM QS ML Manage Translations action in the folder details page, as well as the document details page as before
   27919: Quick build fix. Introduction of new property sys:locale on all nodes means that FormService is seeing some off-by-one errors.
   27921: WCM QS ML - Support copying parts of the section collections to the new folder when translating a section, plus tests
   27925: ALF-8707: RSOLR 023: Upgrade to give nodes their default locales
          - Part of ALF-7991: RSOLR 023: Locale support for query: Repository
          - Upgrade scripts for DB2, Oracle and SQLServer
   27927: Update FileFolderService list/paging (RINF 09) & CQ permission checking (RINF 11)
          - also added additional unit tests and update FileFolderPerformanceTester utility (for read / list testing)
   27930: Corrected bean reference: "ServiceRegistry"
   27931: Partially implemented EnvironmentImpl.getPublishingEvents(). This currently only supports searching on publishing event ids.
   27933: Implemented PublishService.getPublishingEvent(String) method.
   27934: WCM QS ML Pull over the locale resolver from Ian, and delegate much of the work to the main class
   27941: ALF-7874 - Mime Type entries for detection and display for some Apple and Adobe formats
   27947: ALF-2707 - Largely fix custom site properties that have multiple values, and custom site properties without a full definition, plus tests.
   27964: ALF-7339:RSOLR 009: Index track and build from SOLR
          - complete
          ALF-7475: RSOLR 010: Index integrity check
          - check ACL info
   27966: RINF 09: FileFolderService list - if skipCount > number of items then return empty list
          - related to ALF-7884 when using CMIS getChildren (via OpenCMIS - eg. http://localhost:8080/alfresco/cmisatom/...)
   27968: ALF-7884 - getChildren() returns an error if skipCount > number of children
   27970: WCM QS Fix issue in the webapp for sites where no port has been given yet
   27972: WPUB: F150: Foundation API: Request that a node be removed from the live environment
          - Initial support for "unpublish"
   27975: ALF-8790:RSOLR 033: Automated build produces SOLR artifacts
          - add configuration per core based on properties files
   27976: ALF-8790: RSOLR 033: Automated build produces SOLR artifacts
          - restructure, added properties files for config, track archived store
   27977: ALF-8790: RSOLR 033: Automated build produces SOLR artifacts
          - Solr zip included in continuous build
   27978: Fixed missing policy event when sys:locale is changed.
   27979: Fixed tests and checks after sys:locale became a mandatory property.
   27982: ALF-8547: WPUB: F163: Foundation API: Create/update/remove a delivery channel
          - Code and tests for channel update and delete
   28007: Implemented starting publishing workflow on schedulePublishingEvent method. Added tests for PublishingEventHelper.
   28008: Updated web-publishing-context.xml to include WorkflowService and workflowEngineId for PublishingEventHelper.
   28009: Added more PublishingEventHelper tests.
   28012: Support for ALF-8792: RSOLR 036: SOLR APIs to support index integrity checking
   28013: Fix failing Web Publishing tests.
   28015: Adding a DoNotCopy CopyBehaviourCallback for the fm:commentsRollup aspect. Part of ALF-8498.
   28016: Bring over the WCM QS ML RootNavInterceptor from Ian, and productise it
   28019: CMIS browser: close invalid connections
   28025: Update the WCM QS ML behaviour, webscript and test to handle more things having locales by default
   28026: ML-WQS:
          - Wired up the RootNavInterceptor to detect locale based on path
          - Wired up the replacement locale resolver to respect the locale determined by the RootNavInterceptor
          - Adjusted "Manage Translations" page to render the data table correctly
          - Adjusted site search box to restrict search to selected site localisation
          - Adjusted top nav component to select menu items only from appropriate site localisation
   28029: Added more tests for PublishingEventHelper and PublishingQueueImpl. Also added WebPublishingTestSuite.
   28034: Support for ALF-8792: RSOLR 036: SOLR APIs to support index integrity checking
          - ACL and ACLTX support
   28036: WCM QS ML UI tweaks for marking something as the initial translation
   28038: ALF-8548: WPUB: F165: Foundation API: Cancel a scheduled publishing event
           - Code and initial test cases
   28043: Added missing JCR mapping for property type d:locale
           - This property type is not new, btw.
   28312: RepoBM: minor cleanup
          - remove hardcoded JMeter install dir
          - add configurable user.dir (for default import of test data) - check that default zip exists
          - update readme
          - default threadfolder should be null (in case of absolute folder paths)
git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@28320 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
		
	
		
			
				
	
	
		
			2450 lines
		
	
	
		
			96 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			2450 lines
		
	
	
		
			96 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.io.Serializable;
 | |
| import java.util.ArrayList;
 | |
| import java.util.Collection;
 | |
| import java.util.Collections;
 | |
| import java.util.Date;
 | |
| import java.util.HashMap;
 | |
| import java.util.HashSet;
 | |
| import java.util.Iterator;
 | |
| import java.util.List;
 | |
| import java.util.Locale;
 | |
| import java.util.Map;
 | |
| import java.util.Set;
 | |
| import java.util.Stack;
 | |
| 
 | |
| import org.alfresco.error.AlfrescoRuntimeException;
 | |
| import org.alfresco.model.ContentModel;
 | |
| import org.alfresco.repo.domain.node.ChildAssocEntity;
 | |
| import org.alfresco.repo.domain.node.Node;
 | |
| import org.alfresco.repo.domain.node.NodeDAO;
 | |
| import org.alfresco.repo.domain.node.NodeDAO.ChildAssocRefQueryCallback;
 | |
| import org.alfresco.repo.domain.qname.QNameDAO;
 | |
| import org.alfresco.repo.node.AbstractNodeServiceImpl;
 | |
| import org.alfresco.repo.node.StoreArchiveMap;
 | |
| import org.alfresco.repo.node.archive.NodeArchiveService;
 | |
| import org.alfresco.repo.node.index.NodeIndexer;
 | |
| import org.alfresco.repo.policy.BehaviourFilter;
 | |
| import org.alfresco.repo.security.authentication.AuthenticationUtil;
 | |
| import org.alfresco.repo.transaction.TransactionalResourceHelper;
 | |
| import org.alfresco.service.cmr.dictionary.AspectDefinition;
 | |
| import org.alfresco.service.cmr.dictionary.AssociationDefinition;
 | |
| import org.alfresco.service.cmr.dictionary.ClassDefinition;
 | |
| import org.alfresco.service.cmr.dictionary.InvalidAspectException;
 | |
| import org.alfresco.service.cmr.dictionary.InvalidTypeException;
 | |
| import org.alfresco.service.cmr.dictionary.PropertyDefinition;
 | |
| import org.alfresco.service.cmr.dictionary.TypeDefinition;
 | |
| import org.alfresco.service.cmr.repository.AssociationExistsException;
 | |
| import org.alfresco.service.cmr.repository.AssociationRef;
 | |
| import org.alfresco.service.cmr.repository.ChildAssociationRef;
 | |
| import org.alfresco.service.cmr.repository.InvalidChildAssociationRefException;
 | |
| import org.alfresco.service.cmr.repository.InvalidNodeRefException;
 | |
| import org.alfresco.service.cmr.repository.InvalidStoreRefException;
 | |
| import org.alfresco.service.cmr.repository.NodeRef;
 | |
| import org.alfresco.service.cmr.repository.NodeRef.Status;
 | |
| import org.alfresco.service.cmr.repository.NodeService;
 | |
| import org.alfresco.service.cmr.repository.Path;
 | |
| import org.alfresco.service.cmr.repository.StoreRef;
 | |
| import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
 | |
| import org.alfresco.service.namespace.QName;
 | |
| import org.alfresco.service.namespace.QNamePattern;
 | |
| import org.alfresco.service.namespace.RegexQNamePattern;
 | |
| import org.alfresco.util.EqualsHelper;
 | |
| import org.alfresco.util.GUID;
 | |
| import org.alfresco.util.Pair;
 | |
| import org.alfresco.util.ParameterCheck;
 | |
| import org.alfresco.util.PropertyMap;
 | |
| import org.apache.commons.logging.Log;
 | |
| import org.apache.commons.logging.LogFactory;
 | |
| import org.springframework.extensions.surf.util.I18NUtil;
 | |
| 
 | |
| /**
 | |
|  * Node service using database persistence layer to fulfill functionality
 | |
|  * 
 | |
|  * @author Derek Hulley
 | |
|  */
 | |
| public class DbNodeServiceImpl extends AbstractNodeServiceImpl
 | |
| {
 | |
|     private static Log logger = LogFactory.getLog(DbNodeServiceImpl.class);
 | |
|     
 | |
|     private QNameDAO qnameDAO;
 | |
|     private NodeDAO nodeDAO;
 | |
|     private StoreArchiveMap storeArchiveMap;
 | |
|     private NodeService avmNodeService;
 | |
|     private NodeIndexer nodeIndexer;
 | |
|     private BehaviourFilter policyBehaviourFilter;
 | |
|     private final static String KEY_PRE_COMMIT_ADD_NODE = "DbNodeServiceImpl.PreCommitAddNode";
 | |
|     private final static String KEY_DELETED_NODES = "DbNodeServiceImpl.DeletedNodes";
 | |
|     
 | |
|     public DbNodeServiceImpl()
 | |
|     {
 | |
|         storeArchiveMap = new StoreArchiveMap();        // in case it is not set
 | |
|     }
 | |
| 
 | |
|     public void setQnameDAO(QNameDAO qnameDAO)
 | |
|     {
 | |
|         this.qnameDAO = qnameDAO;
 | |
|     }
 | |
| 
 | |
|     public void setNodeDAO(NodeDAO nodeDAO)
 | |
|     {
 | |
|         this.nodeDAO = nodeDAO;
 | |
|     }
 | |
| 
 | |
|     public void setStoreArchiveMap(StoreArchiveMap storeArchiveMap)
 | |
|     {
 | |
|         this.storeArchiveMap = storeArchiveMap;
 | |
|     }
 | |
| 
 | |
|     public void setAvmNodeService(NodeService avmNodeService)
 | |
|     {
 | |
|         this.avmNodeService = avmNodeService;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @param nodeIndexer       the indexer that will be notified of node additions,
 | |
|      *                          modifications and deletions
 | |
|      */
 | |
|     public void setNodeIndexer(NodeIndexer nodeIndexer)
 | |
|     {
 | |
|         this.nodeIndexer = nodeIndexer;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * 
 | |
|      * @param policyBehaviourFilter     component used to enable and disable behaviours
 | |
|      */
 | |
|     public void setPolicyBehaviourFilter(BehaviourFilter policyBehaviourFilter)
 | |
|     {
 | |
|         this.policyBehaviourFilter = policyBehaviourFilter;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Performs a null-safe get of the node
 | |
|      * 
 | |
|      * @param nodeRef the node to retrieve
 | |
|      * @return Returns the node entity (never null)
 | |
|      * @throws InvalidNodeRefException if the referenced node could not be found
 | |
|      */
 | |
|     private Pair<Long, NodeRef> getNodePairNotNull(NodeRef nodeRef) throws InvalidNodeRefException
 | |
|     {
 | |
|         ParameterCheck.mandatory("nodeRef", nodeRef);
 | |
|         
 | |
|         Pair<Long, NodeRef> unchecked = nodeDAO.getNodePair(nodeRef);
 | |
|         if (unchecked == null)
 | |
|         {
 | |
|             throw new InvalidNodeRefException("Node does not exist: " + nodeRef, nodeRef);
 | |
|         }
 | |
|         return unchecked;
 | |
|     }
 | |
|     
 | |
|     public boolean exists(StoreRef storeRef)
 | |
|     {
 | |
|         return nodeDAO.exists(storeRef);
 | |
|     }
 | |
|     
 | |
|     public boolean exists(NodeRef nodeRef)
 | |
|     {
 | |
|         ParameterCheck.mandatory("nodeRef", nodeRef);
 | |
|         return nodeDAO.exists(nodeRef);
 | |
|     }
 | |
|     
 | |
|     public Status getNodeStatus(NodeRef nodeRef)
 | |
|     {
 | |
|         ParameterCheck.mandatory("nodeRef", nodeRef);
 | |
|         NodeRef.Status status = nodeDAO.getNodeRefStatus(nodeRef);
 | |
|         return status;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * {@inheritDoc}
 | |
|      */
 | |
|     public List<StoreRef> getStores()
 | |
|     {
 | |
|         // Get the ADM stores
 | |
|         List<Pair<Long, StoreRef>> stores = nodeDAO.getStores();
 | |
|         List<StoreRef> storeRefs = new ArrayList<StoreRef>(50);
 | |
|         for (Pair<Long, StoreRef> pair : stores)
 | |
|         {
 | |
|             StoreRef storeRef = pair.getSecond();
 | |
|             if (storeRef.getProtocol().equals(StoreRef.PROTOCOL_DELETED))
 | |
|             {
 | |
|                 // Ignore
 | |
|                 continue;
 | |
|             }
 | |
|             storeRefs.add(storeRef);
 | |
|         }
 | |
|         // Now get the AVMStores.
 | |
|         List<StoreRef> avmStores = avmNodeService.getStores();
 | |
|         storeRefs.addAll(avmStores);
 | |
|         // Return them all.
 | |
|         return storeRefs;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Defers to the typed service
 | |
|      * @see StoreDaoService#createWorkspace(String)
 | |
|      */
 | |
|     public StoreRef createStore(String protocol, String identifier)
 | |
|     {
 | |
|         StoreRef storeRef = new StoreRef(protocol, identifier);
 | |
|         
 | |
|         // invoke policies
 | |
|         invokeBeforeCreateStore(ContentModel.TYPE_STOREROOT, storeRef);
 | |
|         
 | |
|         // create a new one
 | |
|         Pair<Long, NodeRef> rootNodePair = nodeDAO.newStore(storeRef);
 | |
|         NodeRef rootNodeRef = rootNodePair.getSecond();
 | |
|         
 | |
|         // invoke policies
 | |
|         invokeOnCreateStore(rootNodeRef);
 | |
|         
 | |
|         // Index
 | |
|         ChildAssociationRef assocRef = new ChildAssociationRef(null, null, null, rootNodeRef);
 | |
|         nodeIndexer.indexCreateNode(assocRef);
 | |
|         
 | |
|         // Done
 | |
|         return storeRef;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * @throws UnsupportedOperationException        Always
 | |
|      */
 | |
|     public void deleteStore(StoreRef storeRef) throws InvalidStoreRefException
 | |
|     {
 | |
|         // Delete the index
 | |
|         nodeIndexer.indexDeleteStore(storeRef);
 | |
|         // Rename the store
 | |
|         StoreRef deletedStoreRef = new StoreRef(StoreRef.PROTOCOL_DELETED, GUID.generate());
 | |
|         nodeDAO.moveStore(storeRef, deletedStoreRef);
 | |
|         
 | |
|         // Done
 | |
|         if (logger.isDebugEnabled())
 | |
|         {
 | |
|             logger.debug("Marked store for deletion: " + storeRef + " --> " + deletedStoreRef);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public NodeRef getRootNode(StoreRef storeRef) throws InvalidStoreRefException
 | |
|     {
 | |
|         Pair<Long, NodeRef> rootNodePair = nodeDAO.getRootNode(storeRef);
 | |
|         if (rootNodePair == null)
 | |
|         {
 | |
|             throw new InvalidStoreRefException("Store does not exist: " + storeRef, storeRef);
 | |
|         }
 | |
|         // done
 | |
|         return rootNodePair.getSecond();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @see #createNode(NodeRef, QName, QName, QName, Map)
 | |
|      */
 | |
|     public ChildAssociationRef createNode(
 | |
|             NodeRef parentRef,
 | |
|             QName assocTypeQName,
 | |
|             QName assocQName,
 | |
|             QName nodeTypeQName)
 | |
|     {
 | |
|         return this.createNode(parentRef, assocTypeQName, assocQName, nodeTypeQName, null);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * {@inheritDoc}
 | |
|      */
 | |
|     public ChildAssociationRef createNode(
 | |
|             NodeRef parentRef,
 | |
|             QName assocTypeQName,
 | |
|             QName assocQName,
 | |
|             QName nodeTypeQName,
 | |
|             Map<QName, Serializable> properties)
 | |
|     {
 | |
|         ParameterCheck.mandatory("parentRef", parentRef);
 | |
|         ParameterCheck.mandatory("assocTypeQName", assocTypeQName);
 | |
|         ParameterCheck.mandatory("assocQName", assocQName);
 | |
|         ParameterCheck.mandatory("nodeTypeQName", nodeTypeQName);
 | |
|         if(assocQName.getLocalName().length() > QName.MAX_LENGTH)
 | |
|         {
 | |
|             throw new IllegalArgumentException("Localname is too long");
 | |
|         }
 | |
|         
 | |
|         // Get the parent node
 | |
|         Pair<Long, NodeRef> parentNodePair = getNodePairNotNull(parentRef);
 | |
|         StoreRef parentStoreRef = parentRef.getStoreRef();
 | |
| 
 | |
|         // null property map is allowed
 | |
|         if (properties == null)
 | |
|         {      
 | |
|             properties = Collections.emptyMap();
 | |
|         }
 | |
|         
 | |
|         // get an ID for the node
 | |
|         String newUuid = generateGuid(properties);
 | |
|         
 | |
|         /**
 | |
|          *  Check the parent node has not been deleted in this txn.
 | |
|          */
 | |
|         if(isDeletedNodeRef(parentRef))
 | |
|         {               
 | |
|             throw new InvalidNodeRefException("The parent node has been deleted", parentRef);
 | |
|         }
 | |
|         
 | |
|         // Invoke policy behaviour
 | |
|         invokeBeforeCreateNode(parentRef, assocTypeQName, assocQName, nodeTypeQName);
 | |
|         
 | |
|         // check the node type
 | |
|         TypeDefinition nodeTypeDef = dictionaryService.getType(nodeTypeQName);
 | |
|         if (nodeTypeDef == null)
 | |
|         {
 | |
|             throw new InvalidTypeException(nodeTypeQName);
 | |
|         }
 | |
|         
 | |
|         // Ensure child uniqueness
 | |
|         String newName = extractNameProperty(properties);
 | |
|         
 | |
|         // Get the thread's locale
 | |
|         Locale locale = I18NUtil.getLocale();
 | |
|         
 | |
|         // create the node instance
 | |
|         ChildAssocEntity assoc = nodeDAO.newNode(
 | |
|                 parentNodePair.getFirst(),
 | |
|                 assocTypeQName,
 | |
|                 assocQName,
 | |
|                 parentStoreRef,
 | |
|                 newUuid,
 | |
|                 nodeTypeQName,
 | |
|                 locale,
 | |
|                 newName,
 | |
|                 properties);
 | |
|         ChildAssociationRef childAssocRef = assoc.getRef(qnameDAO);
 | |
|         Pair<Long, NodeRef> childNodePair = assoc.getChildNode().getNodePair();
 | |
|         
 | |
|         addAspectsAndProperties(
 | |
|                     childNodePair,
 | |
|                     nodeTypeQName,
 | |
|                     null,
 | |
|                     Collections.<QName>emptySet(),
 | |
|                     Collections.<QName, Serializable>emptyMap(),
 | |
|                     Collections.<QName>emptySet(),
 | |
|                     properties,
 | |
|                     true,
 | |
|                     false);
 | |
|         
 | |
|         Map<QName, Serializable> propertiesAfter = nodeDAO.getNodeProperties(childNodePair.getFirst());
 | |
|         
 | |
|         // Invoke policy behaviour
 | |
|         invokeOnCreateNode(childAssocRef);
 | |
|         invokeOnCreateChildAssociation(childAssocRef, true);
 | |
|         Map<QName, Serializable> propertiesBefore = PropertyMap.EMPTY_MAP;
 | |
|         invokeOnUpdateProperties(
 | |
|                 childAssocRef.getChildRef(),
 | |
|                 propertiesBefore,
 | |
|                 propertiesAfter);
 | |
|         
 | |
|         untrackDeletedNodeRef(childAssocRef.getChildRef());       
 | |
|         
 | |
|         // Index
 | |
|         nodeIndexer.indexCreateNode(childAssocRef);
 | |
|         
 | |
|         // Ensure that the parent node has the required aspects
 | |
|         addAspectsAndPropertiesAssoc(parentNodePair, assocTypeQName, null, null, null, null, false);
 | |
|         
 | |
|         // done
 | |
|         return childAssocRef;
 | |
|     }
 | |
|     
 | |
|     
 | |
|     /**
 | |
|      * Track a deleted node
 | |
|      * 
 | |
|      * The deleted node set is used to break an infinite loop which can happen when adding a new node into a path containing a 
 | |
|      * deleted node.  This transactional list is used to detect and prevent that from 
 | |
|      * happening.
 | |
|      *  
 | |
|      * @param nodeRef   the deleted node to track
 | |
|      * @return          <tt>true</tt> if the node was not already tracked
 | |
|      */
 | |
|     private boolean trackDeletedNodeRef(NodeRef deletedNodeRef)
 | |
|     {
 | |
|         Set<NodeRef> deletedNodes = TransactionalResourceHelper.getSet(KEY_DELETED_NODES);
 | |
|         return deletedNodes.add(deletedNodeRef);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Untrack a deleted node ref
 | |
|      * 
 | |
|      * Used when a deleted node is restored. 
 | |
|      * 
 | |
|      * @param deletedNodeRef
 | |
|      */
 | |
|     private void untrackDeletedNodeRef(NodeRef deletedNodeRef)
 | |
|     {
 | |
|         Set<NodeRef> deletedNodes = TransactionalResourceHelper.getSet(KEY_DELETED_NODES);
 | |
|         if (deletedNodes.size() > 0)
 | |
|         {
 | |
|             deletedNodes.remove(deletedNodeRef);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     private boolean isDeletedNodeRef(NodeRef deletedNodeRef)
 | |
|     {
 | |
|         Set<NodeRef> deletedNodes = TransactionalResourceHelper.getSet(KEY_DELETED_NODES);
 | |
|         return deletedNodes.contains(deletedNodeRef);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * loose interest in tracking a node ref
 | |
|      * 
 | |
|      * for example if its been deleted or moved
 | |
|      * @param nodeRef the node ref to untrack
 | |
|      */
 | |
|     private void untrackNewNodeRef(NodeRef nodeRef)
 | |
|     {
 | |
|         Set<NodeRef> newNodes = TransactionalResourceHelper.getSet(KEY_PRE_COMMIT_ADD_NODE);
 | |
|         if (newNodes.size() > 0)
 | |
|         {
 | |
|             newNodes.remove(nodeRef);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Adds all the aspects and properties required for the given node, along with mandatory aspects
 | |
|      * and related properties.
 | |
|      * Existing values will not be overridden.  All required pre- and post-update notifications
 | |
|      * are sent for missing aspects.
 | |
|      * 
 | |
|      * @param nodePair              the node to which the details apply
 | |
|      * @param classQName            the type or aspect QName for which the defaults must be applied.
 | |
|      *                              If this is <tt>null</tt> then properties and aspects are only applied
 | |
|      *                              for 'extra' aspects and 'extra' properties.
 | |
|      * @param existingAspects       the existing aspects or <tt>null</tt> to have them fetched
 | |
|      * @param existingProperties    the existing properties or <tt>null</tt> to have them fetched
 | |
|      * @param extraAspects          any aspects that should be added to the 'missing' set (may be <tt>null</tt>)
 | |
|      * @param extraProperties       any properties that should be added the the 'missing' set (may be <tt>null</tt>)
 | |
|      * @param overwriteExistingProperties   <tt>true</tt> if the extra properties must completely overwrite
 | |
|      *                              the existing properties
 | |
|      * @return                      <tt>true</tt> if properties or aspects were added
 | |
|      */
 | |
|     private boolean addAspectsAndProperties(
 | |
|             Pair<Long, NodeRef> nodePair,
 | |
|             QName classQName,
 | |
|             Set<QName> existingAspects,
 | |
|             Map<QName, Serializable> existingProperties,
 | |
|             Set<QName> extraAspects,
 | |
|             Map<QName, Serializable> extraProperties,
 | |
|             boolean overwriteExistingProperties)
 | |
|     {
 | |
|         return addAspectsAndProperties(nodePair, classQName, null, existingAspects, existingProperties, extraAspects, extraProperties, overwriteExistingProperties, true);
 | |
|     }
 | |
|     
 | |
|     private boolean addAspectsAndPropertiesAssoc(
 | |
|             Pair<Long, NodeRef> nodePair,
 | |
|             QName assocTypeQName,
 | |
|             Set<QName> existingAspects,
 | |
|             Map<QName, Serializable> existingProperties,
 | |
|             Set<QName> extraAspects,
 | |
|             Map<QName, Serializable> extraProperties,
 | |
|             boolean overwriteExistingProperties)
 | |
|     {
 | |
|         return addAspectsAndProperties(nodePair, null, assocTypeQName, existingAspects, existingProperties, extraAspects, extraProperties, overwriteExistingProperties, true);
 | |
|     }
 | |
|     
 | |
|     private boolean addAspectsAndProperties(
 | |
|                 Pair<Long, NodeRef> nodePair,
 | |
|                 QName classQName,
 | |
|                 QName assocTypeQName,
 | |
|                 Set<QName> existingAspects,
 | |
|                 Map<QName, Serializable> existingProperties,
 | |
|                 Set<QName> extraAspects,
 | |
|                 Map<QName, Serializable> extraProperties,
 | |
|                 boolean overwriteExistingProperties,
 | |
|                 boolean invokeOnUpdateProperties)
 | |
|     {
 | |
|         ParameterCheck.mandatory("nodePair", nodePair);
 | |
| 
 | |
|         Long nodeId = nodePair.getFirst();
 | |
|         NodeRef nodeRef = nodePair.getSecond();
 | |
|         
 | |
|         // Ensure that have a type that has no mandatory aspects or properties
 | |
|         if (classQName == null)
 | |
|         {
 | |
|             classQName = ContentModel.TYPE_BASE;
 | |
|         }
 | |
|         
 | |
|         // Ensure we have 'extra' aspects and properties to play with
 | |
|         if (extraAspects == null)
 | |
|         {
 | |
|             extraAspects = Collections.emptySet();
 | |
|         }
 | |
|         if (extraProperties == null)
 | |
|         {
 | |
|             extraProperties = Collections.emptyMap();
 | |
|         }
 | |
|         
 | |
|         // Get the existing aspects and properties, if necessary
 | |
|         if (existingAspects == null)
 | |
|         {
 | |
|             existingAspects = nodeDAO.getNodeAspects(nodeId);
 | |
|         }
 | |
|         if (existingProperties == null)
 | |
|         {
 | |
|             existingProperties = nodeDAO.getNodeProperties(nodeId);
 | |
|         }
 | |
|         
 | |
|         // To determine the 'missing' aspects, we need to determine the full set of properties
 | |
|         Map<QName, Serializable> allProperties = new HashMap<QName, Serializable>(37);
 | |
|         allProperties.putAll(existingProperties);
 | |
|         allProperties.putAll(extraProperties);
 | |
|         
 | |
|         // Copy incoming existing values so that we can modify appropriately
 | |
|         existingAspects = new HashSet<QName>(existingAspects);
 | |
|         
 | |
|         // Get the 'missing' aspects and append the 'extra' aspects
 | |
|         Set<QName> missingAspects = getMissingAspects(existingAspects, allProperties, classQName);
 | |
|         missingAspects.addAll(extraAspects);
 | |
|         
 | |
|         if (assocTypeQName != null)
 | |
|         {
 | |
|             missingAspects.addAll(getMissingAspectsAssoc(existingAspects, allProperties, assocTypeQName));
 | |
|         }
 | |
|         
 | |
|         // Notify 'before' adding aspect
 | |
|         for (QName missingAspect : missingAspects)
 | |
|         {
 | |
|             invokeBeforeAddAspect(nodeRef, missingAspect);
 | |
|         }
 | |
|         
 | |
|         // Get all missing properties for aspects that are missing.
 | |
|         //   This will include the type if the type was passed in.
 | |
|         Set<QName> allClassQNames = new HashSet<QName>(13);
 | |
|         allClassQNames.add(classQName);
 | |
|         allClassQNames.addAll(missingAspects);
 | |
|         Map<QName, Serializable> missingProperties = getMissingProperties(existingProperties, allClassQNames);
 | |
|         missingProperties.putAll(extraProperties);
 | |
|         
 | |
|         // Bulk-add the properties
 | |
|         boolean changedProperties = false;
 | |
|         if (overwriteExistingProperties)
 | |
|         {
 | |
|             // Overwrite properties
 | |
|             changedProperties = nodeDAO.setNodeProperties(nodeId, missingProperties);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             // Append properties
 | |
|             changedProperties = nodeDAO.addNodeProperties(nodeId, missingProperties);
 | |
|         }
 | |
|         if (changedProperties && invokeOnUpdateProperties)
 | |
|         {
 | |
|             Map<QName, Serializable> propertiesAfter = nodeDAO.getNodeProperties(nodeId);
 | |
|             invokeOnUpdateProperties(nodeRef, existingProperties, propertiesAfter);
 | |
|         }
 | |
|         // Bulk-add the aspects
 | |
|         boolean changedAspects = nodeDAO.addNodeAspects(nodeId, missingAspects);
 | |
|         if (changedAspects)
 | |
|         {
 | |
|             for (QName missingAspect : missingAspects)
 | |
|             {
 | |
|                 invokeOnAddAspect(nodeRef, missingAspect);
 | |
|             }
 | |
|         }
 | |
|         // Done
 | |
|         return changedAspects || changedProperties;
 | |
|     }
 | |
|     
 | |
|     private Set<QName> getMissingAspectsAssoc(
 | |
|             Set<QName> existingAspects,
 | |
|             Map<QName, Serializable> existingProperties,
 | |
|             QName assocTypeQName)
 | |
|     {
 | |
|             AssociationDefinition assocDef = dictionaryService.getAssociation(assocTypeQName);
 | |
|             if (assocDef == null)
 | |
|             {
 | |
|                 return Collections.emptySet();
 | |
|             }
 | |
|             ClassDefinition classDefinition = assocDef.getSourceClass();
 | |
|             return getMissingAspects(existingAspects, existingProperties, classDefinition.getName());
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Get any aspects that should be added given the type, properties and existing aspects.
 | |
|      * Note that this <b>does not</b> included a search for properties required for the missing
 | |
|      * aspects.
 | |
|      * 
 | |
|      * @param classQName    the type, aspect or association
 | |
|      * @return              Returns any aspects that should be added
 | |
|      */
 | |
|     private Set<QName> getMissingAspects(
 | |
|             Set<QName> existingAspects,
 | |
|             Map<QName, Serializable> existingProperties,
 | |
|             QName classQName)
 | |
|     {
 | |
|         // Copy incoming existing values so that we can modify appropriately
 | |
|         existingAspects = new HashSet<QName>(existingAspects);
 | |
|         
 | |
|         ClassDefinition classDefinition = dictionaryService.getClass(classQName);
 | |
|         if (classDefinition == null)
 | |
|         {
 | |
|             return Collections.emptySet();
 | |
|         }
 | |
| 
 | |
|         Set<QName> missingAspects = new HashSet<QName>(7);
 | |
|         // Check that the aspect itself is present (only applicable for aspects)
 | |
|         if (classDefinition.isAspect() && !existingAspects.contains(classQName))
 | |
|         {
 | |
|             missingAspects.add(classQName);
 | |
|         }
 | |
|         
 | |
|         // Find all aspects that should be present on the class
 | |
|         List<AspectDefinition> defaultAspectDefs = classDefinition.getDefaultAspects();
 | |
|         for (AspectDefinition defaultAspectDef : defaultAspectDefs)
 | |
|         {
 | |
|             QName defaultAspect = defaultAspectDef.getName();
 | |
|             if (!existingAspects.contains(defaultAspect))
 | |
|             {
 | |
|                 missingAspects.add(defaultAspect);
 | |
|             }
 | |
|         }
 | |
|         // Find all aspects that should be present given the existing properties
 | |
|         for (QName existingPropQName : existingProperties.keySet())
 | |
|         {
 | |
|             PropertyDefinition existingPropDef = dictionaryService.getProperty(existingPropQName);
 | |
|             if (existingPropDef == null || !existingPropDef.getContainerClass().isAspect())
 | |
|             {
 | |
|                 continue;           // Property is undefined or belongs to a class
 | |
|             }
 | |
|             QName existingPropDefiningType = existingPropDef.getContainerClass().getName();
 | |
|             if (!existingAspects.contains(existingPropDefiningType))
 | |
|             {
 | |
|                 missingAspects.add(existingPropDefiningType);
 | |
|             }
 | |
|         }
 | |
|         // If there were missing aspects, recurse to find further missing aspects
 | |
|         //    Don't re-add ones we know about or we can end in infinite recursion.
 | |
|         //    Don't send any properties because we don't want to reprocess them each time
 | |
|         Set<QName> allTypesAndAspects = new HashSet<QName>(13);
 | |
|         allTypesAndAspects.add(classQName);
 | |
|         allTypesAndAspects.addAll(existingAspects);
 | |
|         allTypesAndAspects.addAll(missingAspects);
 | |
|         Set<QName> missingAspectsCopy = new HashSet<QName>(missingAspects);
 | |
|         for (QName missingAspect : missingAspectsCopy)
 | |
|         {
 | |
|             Set<QName> furtherMissingAspects = getMissingAspects(
 | |
|                         allTypesAndAspects,
 | |
|                         Collections.<QName, Serializable>emptyMap(),
 | |
|                         missingAspect);
 | |
|             missingAspects.addAll(furtherMissingAspects);
 | |
|             allTypesAndAspects.addAll(furtherMissingAspects);
 | |
|         }
 | |
|         // Done
 | |
|         return missingAspects;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * @param existingProperties    existing node properties
 | |
|      * @param classQNames           the types or aspects to introspect
 | |
|      * @return                      Returns any properties that should be added
 | |
|      */
 | |
|     private Map<QName, Serializable> getMissingProperties(Map<QName, Serializable> existingProperties, Set<QName> classQNames)
 | |
|     {
 | |
|         Map<QName, Serializable> allDefaultProperties = new HashMap<QName, Serializable>(17);
 | |
|         for (QName classQName : classQNames)
 | |
|         {
 | |
|             ClassDefinition classDefinition = dictionaryService.getClass(classQName);
 | |
|             if (classDefinition == null)
 | |
|             {
 | |
|                 continue;
 | |
|             }
 | |
|             // Get the default properties for this type/aspect
 | |
|             Map<QName, Serializable> defaultProperties = getDefaultProperties(classQName);
 | |
|             if (defaultProperties.size() > 0)
 | |
|             {
 | |
|                 allDefaultProperties.putAll(defaultProperties);
 | |
|             }
 | |
|         }
 | |
|         // Work out what is missing
 | |
|         Map<QName, Serializable> missingProperties = new HashMap<QName, Serializable>(allDefaultProperties);
 | |
|         missingProperties.keySet().removeAll(existingProperties.keySet());
 | |
|         // Done
 | |
|         return missingProperties;
 | |
|     }
 | |
|     
 | |
|     public void setChildAssociationIndex(ChildAssociationRef childAssocRef, int index)
 | |
|     {
 | |
|         // get nodes
 | |
|         Pair<Long, NodeRef> parentNodePair = getNodePairNotNull(childAssocRef.getParentRef());
 | |
|         Pair<Long, NodeRef> childNodePair = getNodePairNotNull(childAssocRef.getChildRef());
 | |
|         
 | |
|         Long parentNodeId = parentNodePair.getFirst();
 | |
|         Long childNodeId = childNodePair.getFirst();
 | |
|         QName assocTypeQName = childAssocRef.getTypeQName();
 | |
|         QName assocQName = childAssocRef.getQName();
 | |
|         
 | |
|         // set the index
 | |
|         int updated = nodeDAO.setChildAssocIndex(
 | |
|                 parentNodeId, childNodeId, assocTypeQName, assocQName, index);
 | |
|         if (updated < 1)
 | |
|         {
 | |
|             throw new InvalidChildAssociationRefException(
 | |
|                     "Unable to set child association index: \n" +
 | |
|                     "   assoc: " + childAssocRef + "\n" +
 | |
|                     "   index: " + index,
 | |
|                     childAssocRef);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public QName getType(NodeRef nodeRef) throws InvalidNodeRefException
 | |
|     {
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         return nodeDAO.getNodeType(nodePair.getFirst());
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * @see org.alfresco.service.cmr.repository.NodeService#setType(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
 | |
|      */
 | |
|     public void setType(NodeRef nodeRef, QName typeQName) throws InvalidNodeRefException
 | |
|     {
 | |
|         // check the node type
 | |
|         TypeDefinition nodeTypeDef = dictionaryService.getType(typeQName);
 | |
|         if (nodeTypeDef == null)
 | |
|         {
 | |
|             throw new InvalidTypeException(typeQName);
 | |
|         }
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         
 | |
|         // Invoke policies
 | |
|         invokeBeforeUpdateNode(nodeRef);
 | |
|         
 | |
|         // Set the type
 | |
|         nodeDAO.updateNode(nodePair.getFirst(), null, null, typeQName, null);
 | |
|         
 | |
|         // Add the default aspects and properties required for the given type. Existing values will not be overridden.
 | |
|         addAspectsAndProperties(nodePair, typeQName, null, null, null, null, false);
 | |
|         
 | |
|         // Index
 | |
|         nodeIndexer.indexUpdateNode(nodeRef);
 | |
|         
 | |
|         // Invoke policies
 | |
|         invokeOnUpdateNode(nodeRef);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * @see Node#getAspects()
 | |
|      */
 | |
|     public void addAspect(
 | |
|             NodeRef nodeRef,
 | |
|             QName aspectTypeQName,
 | |
|             Map<QName, Serializable> aspectProperties)
 | |
|             throws InvalidNodeRefException, InvalidAspectException
 | |
|     {
 | |
|         // check that the aspect is legal
 | |
|         AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName);
 | |
|         if (aspectDef == null)
 | |
|         {
 | |
|             throw new InvalidAspectException("The aspect is invalid: " + aspectTypeQName, aspectTypeQName);
 | |
|         }
 | |
|         
 | |
|         // Check the properties
 | |
|         if (aspectProperties == null)
 | |
|         {
 | |
|             // Make a map
 | |
|             aspectProperties = Collections.emptyMap();
 | |
|         }
 | |
|         // Make the properties immutable to be sure that they are not used incorrectly
 | |
|         aspectProperties = Collections.unmodifiableMap(aspectProperties);
 | |
|         
 | |
|         // Invoke policy behaviours
 | |
|         invokeBeforeUpdateNode(nodeRef);
 | |
| 
 | |
|         // Add aspect and defaults
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         boolean modified = addAspectsAndProperties(
 | |
|                     nodePair,
 | |
|                     aspectTypeQName,
 | |
|                     null,
 | |
|                     null,
 | |
|                     Collections.singleton(aspectTypeQName),
 | |
|                     aspectProperties,
 | |
|                     false);
 | |
|         
 | |
|         if (modified)
 | |
|         {                                
 | |
|             // Invoke policy behaviours
 | |
|             invokeOnUpdateNode(nodeRef);
 | |
|             // Index
 | |
|             nodeIndexer.indexUpdateNode(nodeRef);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public void removeAspect(NodeRef nodeRef, QName aspectTypeQName)
 | |
|             throws InvalidNodeRefException, InvalidAspectException
 | |
|     {
 | |
|         /**
 | |
|          * Note: Aspect and property removal is resilient to missing dictionary definitions
 | |
|          */
 | |
|         // get the node
 | |
|         final Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         final Long nodeId = nodePair.getFirst();
 | |
|         
 | |
|         boolean hadAspect = nodeDAO.hasNodeAspect(nodeId, aspectTypeQName);
 | |
|         
 | |
|         // Invoke policy behaviours
 | |
|         invokeBeforeUpdateNode(nodeRef);
 | |
|         if (hadAspect)
 | |
|         {
 | |
|             invokeBeforeRemoveAspect(nodeRef, aspectTypeQName);
 | |
|             nodeDAO.removeNodeAspects(nodeId, Collections.singleton(aspectTypeQName));
 | |
|         }
 | |
|         
 | |
|         AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName);
 | |
|         boolean updated = false;
 | |
|         if (aspectDef != null)
 | |
|         {
 | |
|             // Remove default properties
 | |
|             Map<QName,PropertyDefinition> propertyDefs = aspectDef.getProperties();
 | |
|             Set<QName> propertyToRemoveQNames = propertyDefs.keySet();
 | |
|             nodeDAO.removeNodeProperties(nodeId, propertyToRemoveQNames);
 | |
|             
 | |
|             // Remove child associations
 | |
|             // We have to iterate over the associations and remove all those between the parent and child
 | |
|             final List<Pair<Long, ChildAssociationRef>> assocsToDelete = new ArrayList<Pair<Long, ChildAssociationRef>>(5);
 | |
|             final List<Pair<Long, NodeRef>> nodesToDelete = new ArrayList<Pair<Long, NodeRef>>(5);
 | |
|             NodeDAO.ChildAssocRefQueryCallback callback = new NodeDAO.ChildAssocRefQueryCallback()
 | |
|             {
 | |
|                 public boolean handle(
 | |
|                         Pair<Long, ChildAssociationRef> childAssocPair,
 | |
|                         Pair<Long, NodeRef> parentNodePair,
 | |
|                         Pair<Long, NodeRef> childNodePair
 | |
|                         )
 | |
|                 {
 | |
|                     // Double check that it's not a primary association.  If so, we can't delete it and
 | |
|                     //    have to delete the child node directly and with full archival.
 | |
|                     if (childAssocPair.getSecond().isPrimary())
 | |
|                     {
 | |
|                         nodesToDelete.add(childNodePair);
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         assocsToDelete.add(childAssocPair);
 | |
|                     }
 | |
|                     // More results
 | |
|                     return true;
 | |
|                 }
 | |
| 
 | |
|                 public boolean preLoadNodes()
 | |
|                 {
 | |
|                     return true;
 | |
|                 }
 | |
| 
 | |
|                 public void done()
 | |
|                 {
 | |
|                 }                               
 | |
|             };
 | |
|             // Get all the QNames to remove
 | |
|             Set<QName> assocTypeQNamesToRemove = new HashSet<QName>(aspectDef.getChildAssociations().keySet());
 | |
|             nodeDAO.getChildAssocs(nodeId, assocTypeQNamesToRemove, callback);
 | |
|             // Delete all the collected associations
 | |
|             for (Pair<Long, ChildAssociationRef> assocPair : assocsToDelete)
 | |
|             {
 | |
|                 updated = true;
 | |
|                 Long assocId = assocPair.getFirst();
 | |
|                 ChildAssociationRef assocRef = assocPair.getSecond();
 | |
|                 // delete the association instance - it is not primary
 | |
|                 invokeBeforeDeleteChildAssociation(assocRef);
 | |
|                 nodeDAO.deleteChildAssoc(assocId);
 | |
|                 invokeOnDeleteChildAssociation(assocRef);
 | |
|             }
 | |
|             
 | |
|             // Cascade-delete any nodes that were attached to primary associations
 | |
|             for (Pair<Long, NodeRef> childNodePair : nodesToDelete)
 | |
|             {
 | |
|                 NodeRef childNodeRef = childNodePair.getSecond();
 | |
|                 this.deleteNode(childNodeRef);
 | |
|             }
 | |
|             
 | |
|             // Remove regular associations
 | |
|             Map<QName, AssociationDefinition> nodeAssocDefs = aspectDef.getAssociations();
 | |
|             Set<QName> nodeAssocTypeQNamesToRemove = new HashSet<QName>(13);
 | |
|             for (Map.Entry<QName, AssociationDefinition> entry : nodeAssocDefs.entrySet())
 | |
|             {
 | |
|                 if (entry.getValue().isChild())
 | |
|                 {
 | |
|                     // Not interested in child assocs
 | |
|                     continue;
 | |
|                 }
 | |
|                 nodeAssocTypeQNamesToRemove.add(entry.getKey());
 | |
|             }
 | |
|             int assocsDeleted = nodeDAO.removeNodeAssocsToAndFrom(nodeId, nodeAssocTypeQNamesToRemove);
 | |
|             updated = updated || assocsDeleted > 0;
 | |
|         }
 | |
|         
 | |
|         // Invoke policy behaviours
 | |
|         if (updated)
 | |
|         {
 | |
|             invokeOnUpdateNode(nodeRef);
 | |
|         }
 | |
|         if (hadAspect)
 | |
|         {
 | |
|             invokeOnRemoveAspect(nodeRef, aspectTypeQName);
 | |
|         }
 | |
| 
 | |
|         // Index
 | |
|         nodeIndexer.indexUpdateNode(nodeRef);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Performs a check on the set of node aspects
 | |
|      */
 | |
|     public boolean hasAspect(NodeRef nodeRef, QName aspectQName) throws InvalidNodeRefException, InvalidAspectException
 | |
|     {
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         return nodeDAO.hasNodeAspect(nodePair.getFirst(), aspectQName);
 | |
|     }
 | |
| 
 | |
|     public Set<QName> getAspects(NodeRef nodeRef) throws InvalidNodeRefException
 | |
|     {
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         return nodeDAO.getNodeAspects(nodePair.getFirst());
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Delete Node
 | |
|      */
 | |
|     public void deleteNode(NodeRef nodeRef)
 | |
|     {
 | |
|         // Pair contains NodeId, NodeRef
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         Long nodeId = nodePair.getFirst();
 | |
| 
 | |
|         Boolean requiresDelete = null;
 | |
| 
 | |
|         // get the primary parent-child relationship before it is gone
 | |
|         Pair<Long, ChildAssociationRef> childAssocPair = nodeDAO.getPrimaryParentAssoc(nodeId);
 | |
|         ChildAssociationRef childAssocRef = childAssocPair.getSecond();
 | |
|         // get type and aspect QNames as they will be unavailable after the delete
 | |
|         QName nodeTypeQName = nodeDAO.getNodeType(nodeId);
 | |
|         Set<QName> nodeAspectQNames = nodeDAO.getNodeAspects(nodeId);
 | |
| 
 | |
|         StoreRef storeRef = nodeRef.getStoreRef();
 | |
|         StoreRef archiveStoreRef = storeArchiveMap.get(storeRef);
 | |
| 
 | |
|         /**
 | |
|          *  Work out whether we need to archive or delete the node.
 | |
|          */
 | |
|      
 | |
|         if (archiveStoreRef == null)
 | |
|         {
 | |
|             // The store does not specify archiving
 | |
|             requiresDelete = true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             // get the type and check if we need archiving.
 | |
|             TypeDefinition typeDef = dictionaryService.getType(nodeTypeQName);
 | |
|             if (typeDef != null)
 | |
|             {
 | |
|                 Boolean requiresArchive = typeDef.getArchive();
 | |
|                 if (requiresArchive != null)
 | |
|                 {
 | |
|                     requiresDelete = !requiresArchive;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // If the type hasn't asked for deletion, check whether any applied aspects have
 | |
|             Iterator<QName> i = nodeAspectQNames.iterator();
 | |
|             while ((requiresDelete == null || !requiresDelete) && i.hasNext())
 | |
|             {
 | |
|                 QName nodeAspectQName = i.next();
 | |
|                 AspectDefinition aspectDef = dictionaryService.getAspect(nodeAspectQName);
 | |
|                 if (aspectDef != null)
 | |
|                 {
 | |
|                     Boolean requiresArchive = aspectDef.getArchive();
 | |
|                     if (requiresArchive != null)
 | |
|                     {
 | |
|                         requiresDelete = !requiresArchive;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Now we have worked out whether to archive or delete, go ahead and do it
 | |
|          */
 | |
|         if (requiresDelete == null || requiresDelete)
 | |
|         {
 | |
|             // remove the deleted node from the list of new nodes
 | |
|             untrackNewNodeRef(nodeRef);
 | |
| 
 | |
|             // track the deletion of this node - so we can prevent new associations to it.
 | |
|             trackDeletedNodeRef(nodeRef);
 | |
|             
 | |
|             // Invoke policy behaviours
 | |
|             invokeBeforeDeleteNode(nodeRef);
 | |
| 
 | |
|             // Cascade delecte as required
 | |
|             deletePrimaryChildrenNotArchived(nodePair);
 | |
|             // perform a normal deletion
 | |
|             nodeDAO.deleteNode(nodeId);
 | |
|             // Invoke policy behaviours
 | |
|             invokeOnDeleteNode(childAssocRef, nodeTypeQName, nodeAspectQNames, false);
 | |
| 
 | |
|             // Index
 | |
|             nodeIndexer.indexDeleteNode(childAssocRef);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             /*
 | |
|              *  Go ahead and archive the node
 | |
|              *  
 | |
|              *  Archiving will take responsibility for firing the policy behaviours on 
 | |
|              *  the nodes it modifies. 
 | |
|              */
 | |
|             archiveNode(nodeRef, archiveStoreRef);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * delete primary children - private method for deleteNode.
 | |
|      * 
 | |
|      * recurses through children when deleting a node.   Does not archive.
 | |
|      */
 | |
|     private void deletePrimaryChildrenNotArchived(Pair<Long, NodeRef> nodePair)
 | |
|     {
 | |
|         Long nodeId = nodePair.getFirst();
 | |
|         // Get the node's primary children
 | |
|         final List<Pair<Long, NodeRef>> childNodePairs = new ArrayList<Pair<Long, NodeRef>>(5);
 | |
| 
 | |
|         final Map<Long, ChildAssociationRef> childAssocRefsByChildId = new HashMap<Long, ChildAssociationRef>(5);
 | |
|         NodeDAO.ChildAssocRefQueryCallback callback = new NodeDAO.ChildAssocRefQueryCallback()
 | |
|         {
 | |
|             public boolean handle(
 | |
|                     Pair<Long, ChildAssociationRef> childAssocPair,
 | |
|                     Pair<Long, NodeRef> parentNodePair,
 | |
|                     Pair<Long, NodeRef> childNodePair
 | |
|                     )
 | |
|             {
 | |
|                 // Add it
 | |
|                 childNodePairs.add(childNodePair);
 | |
|                 childAssocRefsByChildId.put(childNodePair.getFirst(), childAssocPair.getSecond());
 | |
|                 // More results
 | |
|                 return true;
 | |
|             }
 | |
| 
 | |
|             public boolean preLoadNodes()
 | |
|             {
 | |
|                 return true;
 | |
|             }
 | |
| 
 | |
|             public void done()
 | |
|             {
 | |
|             }                               
 | |
|        };
 | |
| 
 | |
|        // Get all the QNames to remove
 | |
|        nodeDAO.getChildAssocs(nodeId, null, null, null, Boolean.TRUE, null, callback);
 | |
|        // Each child must be deleted
 | |
|        for (Pair<Long, NodeRef> childNodePair : childNodePairs)
 | |
|        {
 | |
|             // Fire node policies.  This ensures that each node in the hierarchy gets a notification fired.
 | |
|             Long childNodeId = childNodePair.getFirst();
 | |
|             NodeRef childNodeRef = childNodePair.getSecond();
 | |
|             QName childNodeType = nodeDAO.getNodeType(childNodeId);
 | |
|             Set<QName> childNodeQNames = nodeDAO.getNodeAspects(childNodeId);
 | |
|             ChildAssociationRef childParentAssocRef = childAssocRefsByChildId.get(childNodeId);
 | |
|             
 | |
|             // remove the deleted node from the list of new nodes
 | |
|             untrackNewNodeRef(childNodeRef);
 | |
| 
 | |
|             // track the deletion of this node - so we can prevent new associations to it.
 | |
|             trackDeletedNodeRef(childNodeRef);
 | |
|             
 | |
|             invokeBeforeDeleteNode(childNodeRef);
 | |
|             
 | |
|             // Cascade first
 | |
|             // This ensures that the beforeDelete policy is fired for all nodes in the hierarchy before
 | |
|             // the actual delete starts.
 | |
|             deletePrimaryChildrenNotArchived(childNodePair);
 | |
|             // Delete the child
 | |
|             nodeDAO.deleteNode(childNodeId);
 | |
|             invokeOnDeleteNode(childParentAssocRef, childNodeType, childNodeQNames, false);
 | |
|             
 | |
|             // lose interest in tracking this node ref
 | |
|             untrackNewNodeRef(childNodeRef);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public ChildAssociationRef addChild(NodeRef parentRef, NodeRef childRef, QName assocTypeQName, QName assocQName)
 | |
|     {
 | |
|         return addChild(Collections.singletonList(parentRef), childRef, assocTypeQName, assocQName).get(0);
 | |
|     }
 | |
| 
 | |
|     public List<ChildAssociationRef> addChild(Collection<NodeRef> parentRefs, NodeRef childRef, QName assocTypeQName, QName assocQName)
 | |
|     {
 | |
|         // Get the node's name, if present
 | |
|         Pair<Long, NodeRef> childNodePair = getNodePairNotNull(childRef);
 | |
|         Long childNodeId = childNodePair.getFirst();
 | |
|         Map<QName, Serializable> childNodeProperties = nodeDAO.getNodeProperties(childNodePair.getFirst());
 | |
|         String childNodeName = extractNameProperty(childNodeProperties);
 | |
|         if (childNodeName == null)
 | |
|         {
 | |
|             childNodeName = childRef.getId();
 | |
|         }
 | |
| 
 | |
|         List <ChildAssociationRef> childAssociationRefs = new ArrayList<ChildAssociationRef>(parentRefs.size());
 | |
|         List<Pair<Long, NodeRef>> parentNodePairs = new ArrayList<Pair<Long, NodeRef>>(parentRefs.size());
 | |
|         for (NodeRef parentRef : parentRefs)
 | |
|         {
 | |
|             if (isDeletedNodeRef(parentRef))
 | |
|             {
 | |
|                 throw new InvalidNodeRefException("The parent node has been deleted", parentRef);
 | |
|             }
 | |
|             Pair<Long, NodeRef> parentNodePair = getNodePairNotNull(parentRef);
 | |
|             Long parentNodeId = parentNodePair.getFirst();
 | |
|             parentNodePairs.add(parentNodePair);
 | |
| 
 | |
|             // make the association
 | |
|             Pair<Long, ChildAssociationRef> childAssocPair = nodeDAO.newChildAssoc(
 | |
|                     parentNodeId, childNodeId,
 | |
|                     assocTypeQName, assocQName,
 | |
|                     childNodeName);
 | |
| 
 | |
|             childAssociationRefs.add(childAssocPair.getSecond());
 | |
|         }
 | |
|         
 | |
|         // check that the child addition of the child has not created a cyclic relationship
 | |
|         // this functionality is provided for free in getPath
 | |
|         getPaths(childRef, false);
 | |
| 
 | |
|         // Invoke policy behaviours
 | |
|         for (ChildAssociationRef childAssocRef : childAssociationRefs)
 | |
|         {
 | |
|             invokeOnCreateChildAssociation(childAssocRef, false);
 | |
|         }
 | |
|         
 | |
|         // Get the type associated with the association
 | |
|         // The association may be sourced on an aspect, which may itself mandate further aspects
 | |
|         for (Pair<Long, NodeRef> parentNodePair : parentNodePairs)
 | |
|         {
 | |
|             addAspectsAndPropertiesAssoc(parentNodePair, assocTypeQName, null, null, null, null, false);
 | |
|         }
 | |
| 
 | |
|         // Index
 | |
|         for (ChildAssociationRef childAssocRef : childAssociationRefs)
 | |
|         {
 | |
|             nodeIndexer.indexCreateChildAssociation(childAssocRef);
 | |
|         }
 | |
| 
 | |
|         return childAssociationRefs;
 | |
|     }
 | |
| 
 | |
|     public void removeChild(NodeRef parentRef, NodeRef childRef) throws InvalidNodeRefException
 | |
|     {
 | |
|         final Pair<Long, NodeRef> parentNodePair = getNodePairNotNull(parentRef);
 | |
|         final Long parentNodeId = parentNodePair.getFirst();
 | |
|         final Pair<Long, NodeRef> childNodePair = getNodePairNotNull(childRef);
 | |
|         final Long childNodeId = childNodePair.getFirst();
 | |
|         
 | |
|         // Get the primary parent association for the child
 | |
|         Pair<Long, ChildAssociationRef> primaryChildAssocPair = nodeDAO.getPrimaryParentAssoc(childNodeId);
 | |
|         // We can shortcut if our parent is also the primary parent
 | |
|         if (primaryChildAssocPair != null)
 | |
|         {
 | |
|             NodeRef primaryParentNodeRef = primaryChildAssocPair.getSecond().getParentRef();
 | |
|             if (primaryParentNodeRef.equals(parentRef))
 | |
|             {
 | |
|                 // Shortcut - just delete the child node
 | |
|                 deleteNode(childRef);
 | |
|                 return;
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         // We have to iterate over the associations and remove all those between the parent and child
 | |
|         final List<Pair<Long, ChildAssociationRef>> assocsToDelete = new ArrayList<Pair<Long, ChildAssociationRef>>(5);
 | |
|         NodeDAO.ChildAssocRefQueryCallback callback = new NodeDAO.ChildAssocRefQueryCallback()
 | |
|         {
 | |
|             public boolean handle(
 | |
|                     Pair<Long, ChildAssociationRef> childAssocPair,
 | |
|                     Pair<Long, NodeRef> parentNodePair,
 | |
|                     Pair<Long, NodeRef> childNodePair)
 | |
|             {
 | |
|                 // Ignore if the child is not ours (redundant check)
 | |
|                 if (!childNodePair.getFirst().equals(childNodeId))
 | |
|                 {
 | |
|                     return false;
 | |
|                 }
 | |
|                 // Add it
 | |
|                 assocsToDelete.add(childAssocPair);
 | |
|                 // More results
 | |
|                 return true;
 | |
|             }
 | |
| 
 | |
|             public boolean preLoadNodes()
 | |
|             {
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             public void done()
 | |
|             {
 | |
|             }                               
 | |
|         };
 | |
|         nodeDAO.getChildAssocs(parentNodeId, childNodeId, null, null, null, null, callback);
 | |
|         
 | |
|         // Delete all the collected associations
 | |
|         for (Pair<Long, ChildAssociationRef> assocPair : assocsToDelete)
 | |
|         {
 | |
|             Long assocId = assocPair.getFirst();
 | |
|             ChildAssociationRef assocRef = assocPair.getSecond();
 | |
|             // delete the association instance - it is not primary
 | |
|             invokeBeforeDeleteChildAssociation(assocRef);
 | |
|             nodeDAO.deleteChildAssoc(assocId);
 | |
|             invokeOnDeleteChildAssociation(assocRef);
 | |
| 
 | |
|             // Index
 | |
|             nodeIndexer.indexDeleteChildAssociation(assocRef);
 | |
|         }
 | |
| 
 | |
|         // Done
 | |
|     }
 | |
|     
 | |
|     public boolean removeChildAssociation(ChildAssociationRef childAssocRef)
 | |
|     {
 | |
|         Long parentNodeId = getNodePairNotNull(childAssocRef.getParentRef()).getFirst();
 | |
|         Long childNodeId = getNodePairNotNull(childAssocRef.getChildRef()).getFirst();
 | |
|         QName assocTypeQName = childAssocRef.getTypeQName();
 | |
|         QName assocQName = childAssocRef.getQName();
 | |
|         Pair<Long, ChildAssociationRef> assocPair = nodeDAO.getChildAssoc(
 | |
|                 parentNodeId, childNodeId, assocTypeQName, assocQName);
 | |
|         if (assocPair == null)
 | |
|         {
 | |
|             // No association exists
 | |
|             return false;
 | |
|         }
 | |
|         Long assocId = assocPair.getFirst();
 | |
|         ChildAssociationRef assocRef = assocPair.getSecond();
 | |
|         if (assocRef.isPrimary())
 | |
|         {
 | |
|             NodeRef childNodeRef = assocRef.getChildRef();
 | |
|             // Delete the child node
 | |
|             this.deleteNode(childNodeRef);
 | |
|             // Done
 | |
|             return true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             // Delete the association
 | |
|             invokeBeforeDeleteChildAssociation(childAssocRef);
 | |
|             nodeDAO.deleteChildAssoc(assocId);
 | |
|             invokeOnDeleteChildAssociation(childAssocRef);
 | |
|             // Index
 | |
|             nodeIndexer.indexDeleteChildAssociation(childAssocRef);
 | |
|             // Done
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public boolean removeSecondaryChildAssociation(ChildAssociationRef childAssocRef)
 | |
|     {
 | |
|         Long parentNodeId = getNodePairNotNull(childAssocRef.getParentRef()).getFirst();
 | |
|         Long childNodeId = getNodePairNotNull(childAssocRef.getChildRef()).getFirst();
 | |
|         QName assocTypeQName = childAssocRef.getTypeQName();
 | |
|         QName assocQName = childAssocRef.getQName();
 | |
|         Pair<Long, ChildAssociationRef> assocPair = nodeDAO.getChildAssoc(
 | |
|                 parentNodeId, childNodeId, assocTypeQName, assocQName);
 | |
|         if (assocPair == null)
 | |
|         {
 | |
|             // No association exists
 | |
|             return false;
 | |
|         }
 | |
|         Long assocId = assocPair.getFirst();
 | |
|         ChildAssociationRef assocRef = assocPair.getSecond();
 | |
|         if (assocRef.isPrimary())
 | |
|         {
 | |
|             throw new IllegalArgumentException(
 | |
|                     "removeSeconaryChildAssociation can not be applied to a primary association: \n" +
 | |
|                     "   Child Assoc: " + assocRef);
 | |
|         }
 | |
|         // Delete the secondary association
 | |
|         nodeDAO.deleteChildAssoc(assocId);
 | |
|         invokeOnDeleteChildAssociation(childAssocRef);
 | |
|         // Index
 | |
|         nodeIndexer.indexDeleteChildAssociation(childAssocRef);
 | |
|         // Done
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     public Serializable getProperty(NodeRef nodeRef, QName qname) throws InvalidNodeRefException
 | |
|     {
 | |
|         Long nodeId = getNodePairNotNull(nodeRef).getFirst();
 | |
|         // Spoof referencable properties
 | |
|         if (qname.equals(ContentModel.PROP_STORE_PROTOCOL))
 | |
|         {
 | |
|             return nodeRef.getStoreRef().getProtocol();
 | |
|         }
 | |
|         else if (qname.equals(ContentModel.PROP_STORE_IDENTIFIER))
 | |
|         {
 | |
|             return nodeRef.getStoreRef().getIdentifier();
 | |
|         }
 | |
|         else if (qname.equals(ContentModel.PROP_NODE_UUID))
 | |
|         {
 | |
|             return nodeRef.getId();
 | |
|         }
 | |
|         else if (qname.equals(ContentModel.PROP_NODE_DBID))
 | |
|         {
 | |
|             return nodeId;
 | |
|         }
 | |
|         
 | |
|         Serializable property = nodeDAO.getNodeProperty(nodeId, qname);
 | |
|         
 | |
|         // check if we need to provide a spoofed name
 | |
|         if (property == null && qname.equals(ContentModel.PROP_NAME))
 | |
|         {
 | |
|             return nodeRef.getId();
 | |
|         }
 | |
|         
 | |
|         // done
 | |
|         return property;
 | |
|     }
 | |
| 
 | |
|     public Map<QName, Serializable> getProperties(NodeRef nodeRef) throws InvalidNodeRefException
 | |
|     {
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         return getPropertiesImpl(nodePair);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Gets, converts and adds the intrinsic properties to the current node's properties
 | |
|      */
 | |
|     private Map<QName, Serializable> getPropertiesImpl(Pair<Long, NodeRef> nodePair) throws InvalidNodeRefException
 | |
|     {
 | |
|         Long nodeId = nodePair.getFirst();
 | |
|         Map<QName, Serializable> nodeProperties = nodeDAO.getNodeProperties(nodeId);
 | |
|         // done
 | |
|         return nodeProperties;
 | |
|     }
 | |
|     
 | |
|     public Long getNodeAclId(NodeRef nodeRef) throws InvalidNodeRefException
 | |
|     {
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         return getAclIDImpl(nodePair);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Gets, converts and adds the intrinsic properties to the current node's properties
 | |
|      */
 | |
|     private Long getAclIDImpl(Pair<Long, NodeRef> nodePair) throws InvalidNodeRefException
 | |
|     {
 | |
|         Long nodeId = nodePair.getFirst();
 | |
|         Long aclID = nodeDAO.getNodeAclId(nodeId);
 | |
|         // done
 | |
|         return aclID;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Performs additional tasks associated with setting a property.
 | |
|      * 
 | |
|      * @return      Returns <tt>true</tt> if any work was done by this method
 | |
|      */
 | |
|     private boolean setPropertiesCommonWork(Pair<Long, NodeRef> nodePair, Map<QName, Serializable> properties)
 | |
|     {
 | |
|         Long nodeId = nodePair.getFirst();
 | |
| 
 | |
|         boolean changed = false;
 | |
|         // cm:name special handling
 | |
|         if (properties.containsKey(ContentModel.PROP_NAME))
 | |
|         {
 | |
|             String name = extractNameProperty(properties);
 | |
|             Pair<Long, ChildAssociationRef> primaryParentAssocPair = nodeDAO.getPrimaryParentAssoc(nodeId);
 | |
|             if (primaryParentAssocPair != null)
 | |
|             {
 | |
|                 String oldName = extractNameProperty(nodeDAO.getNodeProperties(nodeId));
 | |
|                 String newName = DefaultTypeConverter.INSTANCE.convert(String.class, name);
 | |
|                 changed = setChildNameUnique(nodePair, newName, oldName);
 | |
|             }
 | |
|         }
 | |
|         // Done
 | |
|         return changed;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Gets the properties map, sets the value (null is allowed) and checks that the new set
 | |
|      * of properties is valid.
 | |
|      * 
 | |
|      * @see DbNodeServiceImpl.NullPropertyValue
 | |
|      */
 | |
|     public void setProperty(NodeRef nodeRef, QName qname, Serializable value) throws InvalidNodeRefException
 | |
|     {
 | |
|         ParameterCheck.mandatory("nodeRef", nodeRef);
 | |
|         ParameterCheck.mandatory("qname", qname);
 | |
|         
 | |
|         // The UUID cannot be explicitly changed
 | |
|         if (qname.equals(ContentModel.PROP_NODE_UUID))
 | |
|         {
 | |
|             throw new IllegalArgumentException("The node UUID cannot be changed.");
 | |
|         }
 | |
| 
 | |
|         // get the node
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         
 | |
|         // Invoke policy behaviour
 | |
|         invokeBeforeUpdateNode(nodeRef);
 | |
|         
 | |
|         // cm:name special handling
 | |
|         setPropertiesCommonWork(
 | |
|                     nodePair,
 | |
|                     Collections.singletonMap(qname, value));
 | |
| 
 | |
|         // Add the property and all required defaults
 | |
|         boolean changed = addAspectsAndProperties(
 | |
|                     nodePair, null,
 | |
|                     null, null,
 | |
|                     null, Collections.singletonMap(qname, value), false);
 | |
|         
 | |
|         if (changed)
 | |
|         {
 | |
|             // Invoke policy behaviour
 | |
|             invokeOnUpdateNode(nodeRef);
 | |
|             // Index
 | |
|             nodeIndexer.indexUpdateNode(nodeRef);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Ensures that all required properties are present on the node and copies the
 | |
|      * property values to the <code>Node</code>.
 | |
|      * <p>
 | |
|      * To remove a property, <b>remove it from the map</b> before calling this method.
 | |
|      * Null-valued properties are allowed.
 | |
|      * <p>
 | |
|      * If any of the values are null, a marker object is put in to mimic nulls.  They will be turned back into
 | |
|      * a real nulls when the properties are requested again.
 | |
|      * 
 | |
|      * @see Node#getProperties()
 | |
|      */
 | |
|     public void setProperties(NodeRef nodeRef, Map<QName, Serializable> properties) throws InvalidNodeRefException
 | |
|     {
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         
 | |
|         // Invoke policy behaviours
 | |
|         invokeBeforeUpdateNode(nodeRef);
 | |
| 
 | |
|         // SetProperties common tasks
 | |
|         setPropertiesCommonWork(nodePair, properties);
 | |
|         
 | |
|         // Set properties and defaults, overwriting the existing properties
 | |
|         boolean changed = addAspectsAndProperties(nodePair, null, null, null, null, properties, true);
 | |
|         
 | |
|         if (changed)
 | |
|         {
 | |
|             // Invoke policy behaviours
 | |
|             invokeOnUpdateNode(nodeRef);
 | |
|             // Index
 | |
|             nodeIndexer.indexUpdateNode(nodeRef);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     public void addProperties(NodeRef nodeRef, Map<QName, Serializable> properties) throws InvalidNodeRefException
 | |
|     {
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         
 | |
|         // Invoke policy behaviours
 | |
|         invokeBeforeUpdateNode(nodeRef);
 | |
|         
 | |
|         // cm:name special handling
 | |
|         setPropertiesCommonWork(nodePair, properties);
 | |
| 
 | |
|         // Add properties and defaults
 | |
|         boolean changed = addAspectsAndProperties(nodePair, null, null, null, null, properties, false);
 | |
|         
 | |
|         if (changed)
 | |
|         {
 | |
|             // Invoke policy behaviours
 | |
|             invokeOnUpdateNode(nodeRef);
 | |
|             // Index
 | |
|             nodeIndexer.indexUpdateNode(nodeRef);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     public void removeProperty(NodeRef nodeRef, QName qname) throws InvalidNodeRefException
 | |
|     {
 | |
|         // Get the node
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         Long nodeId = nodePair.getFirst();
 | |
|         
 | |
|         // Invoke policy behaviours
 | |
|         invokeBeforeUpdateNode(nodeRef);
 | |
|         
 | |
|         // Get the values before
 | |
|         Map<QName, Serializable> propertiesBefore = getPropertiesImpl(nodePair);
 | |
|         
 | |
|         // cm:name special handling
 | |
|         if (qname.equals(ContentModel.PROP_NAME))
 | |
|         {
 | |
|             String oldName = extractNameProperty(nodeDAO.getNodeProperties(nodeId));
 | |
|             String newName = null;
 | |
|             setChildNameUnique(nodePair, newName, oldName);
 | |
|         }
 | |
| 
 | |
|         // Remove
 | |
|         nodeDAO.removeNodeProperties(nodeId, Collections.singleton(qname));
 | |
|         
 | |
|         // Invoke policy behaviours
 | |
|         Map<QName, Serializable> propertiesAfter = getPropertiesImpl(nodePair);
 | |
|         invokeOnUpdateNode(nodeRef);
 | |
|         invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter);
 | |
|         
 | |
|         // Index
 | |
|         nodeIndexer.indexUpdateNode(nodeRef);
 | |
|     }
 | |
| 
 | |
|     public Collection<NodeRef> getParents(NodeRef nodeRef) throws InvalidNodeRefException
 | |
|     {
 | |
|         List<ChildAssociationRef> parentAssocs = getParentAssocs(
 | |
|                 nodeRef,
 | |
|                 RegexQNamePattern.MATCH_ALL,
 | |
|                 RegexQNamePattern.MATCH_ALL);
 | |
|         
 | |
|         // Copy into the set to avoid duplicates
 | |
|         Set<NodeRef> parentNodeRefs = new HashSet<NodeRef>(parentAssocs.size());
 | |
|         for (ChildAssociationRef parentAssoc : parentAssocs)
 | |
|         {
 | |
|             NodeRef parentNodeRef = parentAssoc.getParentRef();
 | |
|             parentNodeRefs.add(parentNodeRef);
 | |
|         }
 | |
|         // Done
 | |
|         return new ArrayList<NodeRef>(parentNodeRefs);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Filters out any associations if their qname is not a match to the given pattern.
 | |
|      */
 | |
|     public List<ChildAssociationRef> getParentAssocs(
 | |
|             final NodeRef nodeRef,
 | |
|             final QNamePattern typeQNamePattern,
 | |
|             final QNamePattern qnamePattern)
 | |
|     {
 | |
|         // Get the node
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         Long nodeId = nodePair.getFirst();
 | |
|         
 | |
|         final List<ChildAssociationRef> results = new ArrayList<ChildAssociationRef>(10);
 | |
|         // We have a callback handler to filter results
 | |
|         ChildAssocRefQueryCallback callback = new ChildAssocRefQueryCallback()
 | |
|         {
 | |
|             public boolean preLoadNodes()
 | |
|             {
 | |
|                 return false;
 | |
|             }
 | |
|             
 | |
|             public boolean handle(
 | |
|                     Pair<Long, ChildAssociationRef> childAssocPair,
 | |
|                     Pair<Long, NodeRef> parentNodePair,
 | |
|                     Pair<Long, NodeRef> childNodePair)
 | |
|             {
 | |
|                 if (!typeQNamePattern.isMatch(childAssocPair.getSecond().getTypeQName()))
 | |
|                 {
 | |
|                     return true;
 | |
|                 }
 | |
|                 if (!qnamePattern.isMatch(childAssocPair.getSecond().getQName()))
 | |
|                 {
 | |
|                     return true;
 | |
|                 }
 | |
|                 results.add(childAssocPair.getSecond());
 | |
|                 return true;
 | |
|             }
 | |
| 
 | |
|             public void done()
 | |
|             {
 | |
|             }                               
 | |
|         };
 | |
|         
 | |
|         // Get the assocs pointing to it
 | |
|         QName typeQName = (typeQNamePattern instanceof QName) ? (QName) typeQNamePattern : null;
 | |
|         QName qname = (qnamePattern instanceof QName) ? (QName) qnamePattern : null;
 | |
|         
 | |
|         nodeDAO.getParentAssocs(nodeId, typeQName, qname, null, callback);
 | |
|         // done
 | |
|         return results;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Filters out any associations if their qname is not a match to the given pattern.
 | |
|      */
 | |
|     public List<ChildAssociationRef> getChildAssocs(NodeRef nodeRef, final QNamePattern typeQNamePattern, final QNamePattern qnamePattern)
 | |
|     {
 | |
|        return getChildAssocs(nodeRef, typeQNamePattern, qnamePattern, true) ;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Filters out any associations if their qname is not a match to the given pattern.
 | |
|      */
 | |
|     public List<ChildAssociationRef> getChildAssocs(
 | |
|             NodeRef nodeRef,
 | |
|             final QNamePattern typeQNamePattern,
 | |
|             final QNamePattern qnamePattern,
 | |
|             final boolean preload)
 | |
|     {
 | |
|         // Get the node
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         Long nodeId = nodePair.getFirst();
 | |
| 
 | |
|         final List<ChildAssociationRef> results = new ArrayList<ChildAssociationRef>(10);
 | |
|         // We have a callback handler to filter results
 | |
|         ChildAssocRefQueryCallback callback = new ChildAssocRefQueryCallback()
 | |
|         {
 | |
|             public boolean preLoadNodes()
 | |
|             {
 | |
|                 return preload;
 | |
|             }
 | |
|             
 | |
|             public boolean handle(
 | |
|                     Pair<Long, ChildAssociationRef> childAssocPair,
 | |
|                     Pair<Long, NodeRef> parentNodePair,
 | |
|                     Pair<Long, NodeRef> childNodePair)
 | |
|             {
 | |
|                 if (!typeQNamePattern.isMatch(childAssocPair.getSecond().getTypeQName()))
 | |
|                 {
 | |
|                     return true;
 | |
|                 }
 | |
|                 if (!qnamePattern.isMatch(childAssocPair.getSecond().getQName()))
 | |
|                 {
 | |
|                     return true;
 | |
|                 }
 | |
|                 results.add(childAssocPair.getSecond());
 | |
|                 return true;
 | |
|             }
 | |
| 
 | |
|             public void done()
 | |
|             {
 | |
|             }                               
 | |
|         };
 | |
|         
 | |
|         // Get the assocs pointing to it
 | |
|         QName typeQName = (typeQNamePattern instanceof QName) ? (QName) typeQNamePattern : null;
 | |
|         QName qname = (qnamePattern instanceof QName) ? (QName) qnamePattern : null;
 | |
|         
 | |
|         nodeDAO.getChildAssocs(nodeId, null, typeQName, qname, null, null, callback);
 | |
|         // sort the results
 | |
|         List<ChildAssociationRef> orderedList = reorderChildAssocs(results);
 | |
|         // Done
 | |
|         return orderedList;
 | |
|     }
 | |
|     
 | |
|     public List<ChildAssociationRef> getChildAssocs(NodeRef nodeRef, Set<QName> childNodeTypeQNames)
 | |
|     {
 | |
|         // Get the node
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         Long nodeId = nodePair.getFirst();
 | |
| 
 | |
|         final List<ChildAssociationRef> results = new ArrayList<ChildAssociationRef>(100);
 | |
|         
 | |
|         NodeDAO.ChildAssocRefQueryCallback callback = new NodeDAO.ChildAssocRefQueryCallback()
 | |
|         {
 | |
|             public boolean handle(
 | |
|                     Pair<Long, ChildAssociationRef> childAssocPair,
 | |
|                     Pair<Long, NodeRef> parentNodePair,
 | |
|                     Pair<Long, NodeRef> childNodePair)
 | |
|             {
 | |
|                 results.add(childAssocPair.getSecond());
 | |
|                 // More results
 | |
|                 return true;
 | |
|             }
 | |
| 
 | |
|             public boolean preLoadNodes()
 | |
|             {
 | |
|                 return true;
 | |
|             }
 | |
| 
 | |
|             public void done()
 | |
|             {
 | |
|             }                               
 | |
|         };
 | |
|         // Get all child associations with the specific qualified name
 | |
|         nodeDAO.getChildAssocsByChildTypes(nodeId, childNodeTypeQNames, callback);
 | |
|         // Sort the results
 | |
|         List<ChildAssociationRef> orderedList = reorderChildAssocs(results);
 | |
|         // Done
 | |
|         return orderedList;
 | |
|     }
 | |
| 
 | |
|     private List<ChildAssociationRef> reorderChildAssocs(Collection<ChildAssociationRef> childAssocRefs)
 | |
|     {
 | |
|         // shortcut if there are no assocs
 | |
|         if (childAssocRefs.size() == 0)
 | |
|         {
 | |
|             return Collections.emptyList();
 | |
|         }
 | |
|         // sort results
 | |
|         ArrayList<ChildAssociationRef> orderedList = new ArrayList<ChildAssociationRef>(childAssocRefs);
 | |
|         Collections.sort(orderedList);
 | |
|         
 | |
|         // list of results
 | |
|         int nthSibling = 0;
 | |
|         Iterator<ChildAssociationRef> iterator = orderedList.iterator();
 | |
|         while(iterator.hasNext())
 | |
|         {
 | |
|             ChildAssociationRef childAssocRef = iterator.next();
 | |
|             childAssocRef.setNthSibling(nthSibling);
 | |
|             nthSibling++;
 | |
|         }
 | |
|         // done
 | |
|         return orderedList;
 | |
|     }
 | |
| 
 | |
|     public NodeRef getChildByName(NodeRef nodeRef, QName assocTypeQName, String childName)
 | |
|     {
 | |
|         // Get the node
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         Long nodeId = nodePair.getFirst();
 | |
| 
 | |
|         Pair<Long, ChildAssociationRef> childAssocPair = nodeDAO.getChildAssoc(nodeId, assocTypeQName, childName);
 | |
|         if (childAssocPair != null)
 | |
|         {
 | |
|             return childAssocPair.getSecond().getChildRef();
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             return null;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public List<ChildAssociationRef> getChildrenByName(NodeRef nodeRef, QName assocTypeQName, Collection<String> childNames)
 | |
|     {
 | |
|         // Get the node
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         Long nodeId = nodePair.getFirst();
 | |
| 
 | |
|         final List<ChildAssociationRef> results = new ArrayList<ChildAssociationRef>(100);
 | |
|         
 | |
|         NodeDAO.ChildAssocRefQueryCallback callback = new NodeDAO.ChildAssocRefQueryCallback()
 | |
|         {
 | |
|             public boolean handle(
 | |
|                     Pair<Long, ChildAssociationRef> childAssocPair,
 | |
|                     Pair<Long, NodeRef> parentNodePair,
 | |
|                     Pair<Long, NodeRef> childNodePair)
 | |
|             {
 | |
|                 results.add(childAssocPair.getSecond());
 | |
|                 // More results
 | |
|                 return true;
 | |
|             }
 | |
| 
 | |
|             public boolean preLoadNodes()
 | |
|             {
 | |
|                 return true;
 | |
|             }            
 | |
| 
 | |
|             public void done()
 | |
|             {
 | |
|             }                               
 | |
|         };
 | |
|         // Get all child associations with the specific qualified name
 | |
|         nodeDAO.getChildAssocs(nodeId, assocTypeQName, childNames, callback);
 | |
|         // Sort the results
 | |
|         List<ChildAssociationRef> orderedList = reorderChildAssocs(results);
 | |
|         // Done
 | |
|         return orderedList;
 | |
|     }
 | |
| 
 | |
|     public ChildAssociationRef getPrimaryParent(NodeRef nodeRef) throws InvalidNodeRefException
 | |
|     {
 | |
|         // Get the node
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         Long nodeId = nodePair.getFirst();
 | |
| 
 | |
|         // get the primary parent assoc
 | |
|         Pair<Long, ChildAssociationRef> assocPair = nodeDAO.getPrimaryParentAssoc(nodeId);
 | |
| 
 | |
|         // done - the assoc may be null for a root node
 | |
|         ChildAssociationRef assocRef = null;
 | |
|         if (assocPair == null)
 | |
|         {
 | |
|             assocRef = new ChildAssociationRef(null, null, null, nodeRef);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             assocRef = assocPair.getSecond();
 | |
|         }
 | |
|         return assocRef;
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public AssociationRef createAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName)
 | |
|             throws InvalidNodeRefException, AssociationExistsException
 | |
|     {
 | |
|         Pair<Long, NodeRef> sourceNodePair = getNodePairNotNull(sourceRef);
 | |
|         long sourceNodeId = sourceNodePair.getFirst();
 | |
|         Pair<Long, NodeRef> targetNodePair = getNodePairNotNull(targetRef);
 | |
|         long targetNodeId = targetNodePair.getFirst();
 | |
| 
 | |
|         // we are sure that the association doesn't exist - make it
 | |
|         Long assocId = nodeDAO.newNodeAssoc(sourceNodeId, targetNodeId, assocTypeQName, -1);
 | |
|         AssociationRef assocRef = new AssociationRef(assocId, sourceRef, assocTypeQName, targetRef);
 | |
| 
 | |
|         // Invoke policy behaviours
 | |
|         invokeOnCreateAssociation(assocRef);
 | |
|         
 | |
|         // Add missing aspects
 | |
|         addAspectsAndPropertiesAssoc(sourceNodePair, assocTypeQName, null, null, null, null, false);
 | |
| 
 | |
|         return assocRef;
 | |
|     }   
 | |
|     
 | |
|     @Override
 | |
|     public void setAssociations(NodeRef sourceRef, QName assocTypeQName, List<NodeRef> targetRefs)
 | |
|     {
 | |
|         Pair<Long, NodeRef> sourceNodePair = getNodePairNotNull(sourceRef);
 | |
|         Long sourceNodeId = sourceNodePair.getFirst();
 | |
|         // First get the existing associations
 | |
|         Collection<Pair<Long, AssociationRef>> assocsBefore = nodeDAO.getTargetNodeAssocs(sourceNodeId, assocTypeQName);
 | |
|         Map<NodeRef, Long> targetRefsBefore = new HashMap<NodeRef, Long>(assocsBefore.size());
 | |
|         Map<NodeRef, Long> toRemoveMap = new HashMap<NodeRef, Long>(assocsBefore.size());
 | |
|         for (Pair<Long, AssociationRef> assocBeforePair : assocsBefore)
 | |
|         {
 | |
|             Long id = assocBeforePair.getFirst();
 | |
|             NodeRef nodeRef = assocBeforePair.getSecond().getTargetRef();
 | |
|             targetRefsBefore.put(nodeRef, id);
 | |
|             toRemoveMap.put(nodeRef, id);
 | |
|         }
 | |
|         // Work out which associations need to be removed
 | |
|         toRemoveMap.keySet().removeAll(targetRefs);
 | |
|         List<Long> toRemoveIds = new ArrayList<Long>(toRemoveMap.values());
 | |
|         nodeDAO.removeNodeAssocs(toRemoveIds);
 | |
|         
 | |
|         // Work out which associations need to be added
 | |
|         Set<NodeRef> toAdd = new HashSet<NodeRef>(targetRefs);
 | |
|         toAdd.removeAll(targetRefsBefore.keySet());
 | |
|         
 | |
|         // Iterate over the desired result and create new or reset indexes
 | |
|         int assocIndex = 1;
 | |
|         for (NodeRef targetNodeRef : targetRefs)
 | |
|         {
 | |
|             Long id = targetRefsBefore.get(targetNodeRef);
 | |
|             // Is this an existing assoc?
 | |
|             if (id != null)
 | |
|             {
 | |
|                 // Update it
 | |
|                 nodeDAO.setNodeAssocIndex(id, assocIndex);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 Long targetNodeId = getNodePairNotNull(targetNodeRef).getFirst();
 | |
|                 nodeDAO.newNodeAssoc(sourceNodeId, targetNodeId, assocTypeQName, assocIndex);
 | |
|             }
 | |
|             assocIndex++;
 | |
|         }
 | |
|         
 | |
|         // Invoke policy behaviours
 | |
|         for (NodeRef targetNodeRef : toAdd)
 | |
|         {
 | |
|             AssociationRef assocRef = new AssociationRef(sourceRef, assocTypeQName, targetNodeRef);
 | |
|             invokeOnCreateAssociation(assocRef);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public Collection<ChildAssociationRef> getChildAssocsWithoutParentAssocsOfType(NodeRef parent, QName assocTypeQName)
 | |
|     {
 | |
|         // Get the parent node
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(parent);
 | |
|         Long parentNodeId = nodePair.getFirst();
 | |
| 
 | |
|         final List<ChildAssociationRef> results = new ArrayList<ChildAssociationRef>(100);
 | |
| 
 | |
|         NodeDAO.ChildAssocRefQueryCallback callback = new NodeDAO.ChildAssocRefQueryCallback()
 | |
|         {
 | |
|             public boolean handle(Pair<Long, ChildAssociationRef> childAssocPair, Pair<Long, NodeRef> parentNodePair,
 | |
|                     Pair<Long, NodeRef> childNodePair)
 | |
|             {
 | |
|                 results.add(childAssocPair.getSecond());
 | |
|                 // More results
 | |
|                 return true;
 | |
|             }
 | |
| 
 | |
|             public boolean preLoadNodes()
 | |
|             {
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             public void done()
 | |
|             {
 | |
|             }                               
 | |
|         };
 | |
| 
 | |
|         // Get the child associations that meet the criteria
 | |
|         nodeDAO.getChildAssocsWithoutParentAssocsOfType(parentNodeId, assocTypeQName, callback);
 | |
| 
 | |
|         // done
 | |
|         return results;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Specific properties <b>not</b> supported by {@link #getChildAssocsByPropertyValue(NodeRef, QName, Serializable)}
 | |
|      */
 | |
|     private static List<QName> getChildAssocsByPropertyValueBannedProps = new ArrayList<QName>();
 | |
|     static 
 | |
|     {
 | |
|         getChildAssocsByPropertyValueBannedProps.add(ContentModel.PROP_NODE_DBID);
 | |
|         getChildAssocsByPropertyValueBannedProps.add(ContentModel.PROP_NODE_UUID);
 | |
|         getChildAssocsByPropertyValueBannedProps.add(ContentModel.PROP_NAME);
 | |
|         getChildAssocsByPropertyValueBannedProps.add(ContentModel.PROP_MODIFIED);
 | |
|         getChildAssocsByPropertyValueBannedProps.add(ContentModel.PROP_MODIFIER);
 | |
|         getChildAssocsByPropertyValueBannedProps.add(ContentModel.PROP_CREATED);
 | |
|         getChildAssocsByPropertyValueBannedProps.add(ContentModel.PROP_CREATOR);
 | |
|     }
 | |
|     
 | |
|     @Override
 | |
|     public List<ChildAssociationRef> getChildAssocsByPropertyValue(
 | |
|             NodeRef nodeRef,
 | |
|             QName propertyQName, 
 | |
|             Serializable value)
 | |
|     {
 | |
|         // Get the node
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         Long nodeId = nodePair.getFirst();
 | |
| 
 | |
|         // Check the QName is not one of the "special" system maintained ones.
 | |
|         
 | |
|         if (getChildAssocsByPropertyValueBannedProps.contains(propertyQName))
 | |
|         {
 | |
|             throw new IllegalArgumentException(
 | |
|                     "getChildAssocsByPropertyValue does not allow search of system maintained properties: " + propertyQName);
 | |
|         }
 | |
|                 
 | |
|         final List<ChildAssociationRef> results = new ArrayList<ChildAssociationRef>(10);
 | |
|         // We have a callback handler to filter results
 | |
|         ChildAssocRefQueryCallback callback = new ChildAssocRefQueryCallback()
 | |
|         {
 | |
|             public boolean preLoadNodes()
 | |
|             {
 | |
|                 return false;
 | |
|             }
 | |
|             
 | |
|             public boolean handle(
 | |
|                     Pair<Long, ChildAssociationRef> childAssocPair,
 | |
|                     Pair<Long, NodeRef> parentNodePair,
 | |
|                     Pair<Long, NodeRef> childNodePair)
 | |
|             {
 | |
|                 results.add(childAssocPair.getSecond());
 | |
|                 return true;
 | |
|             }
 | |
| 
 | |
|             public void done()
 | |
|             {
 | |
|             }                               
 | |
|         };
 | |
|         
 | |
|         // Get the assocs pointing to it
 | |
|         nodeDAO.getChildAssocsByPropertyValue(nodeId, propertyQName, value, callback);
 | |
|         
 | |
|         // sort the results
 | |
|         List<ChildAssociationRef> orderedList = reorderChildAssocs(results);
 | |
|         
 | |
|         // Done
 | |
|         return orderedList;
 | |
|     }
 | |
| 
 | |
|     public void removeAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName)
 | |
|             throws InvalidNodeRefException
 | |
|     {
 | |
|         Pair<Long, NodeRef> sourceNodePair = getNodePairNotNull(sourceRef);
 | |
|         Long sourceNodeId = sourceNodePair.getFirst();
 | |
|         Pair<Long, NodeRef> targetNodePair = getNodePairNotNull(targetRef);
 | |
|         Long targetNodeId = targetNodePair.getFirst();
 | |
| 
 | |
|         // delete it
 | |
|         int assocsDeleted = nodeDAO.removeNodeAssoc(sourceNodeId, targetNodeId, assocTypeQName);
 | |
|         
 | |
|         if (assocsDeleted > 0)
 | |
|         {
 | |
|             AssociationRef assocRef = new AssociationRef(sourceRef, assocTypeQName, targetRef);
 | |
|             // Invoke policy behaviours
 | |
|             invokeOnDeleteAssociation(assocRef);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     public AssociationRef getAssoc(Long id)
 | |
|     {
 | |
|         return nodeDAO.getNodeAssoc(id).getSecond();
 | |
|     }
 | |
| 
 | |
|     public List<AssociationRef> getTargetAssocs(NodeRef sourceRef, QNamePattern qnamePattern)
 | |
|     {
 | |
|         Pair<Long, NodeRef> sourceNodePair = getNodePairNotNull(sourceRef);
 | |
|         Long sourceNodeId = sourceNodePair.getFirst();
 | |
| 
 | |
|         QName qnameFilter = null;
 | |
|         if (qnamePattern instanceof QName)
 | |
|         {
 | |
|             qnameFilter = (QName) qnamePattern;
 | |
|         }
 | |
|         Collection<Pair<Long, AssociationRef>> assocPairs = nodeDAO.getTargetNodeAssocs(sourceNodeId, qnameFilter);
 | |
|         List<AssociationRef> nodeAssocRefs = new ArrayList<AssociationRef>(assocPairs.size());
 | |
|         for (Pair<Long, AssociationRef> assocPair : assocPairs)
 | |
|         {
 | |
|             AssociationRef assocRef = assocPair.getSecond();
 | |
|             // check qname pattern, if not already filtered
 | |
|             if (qnameFilter == null && !qnamePattern.isMatch(assocRef.getTypeQName()))
 | |
|             {
 | |
|                 continue;   // the assoc name doesn't match the pattern given 
 | |
|             }
 | |
|             nodeAssocRefs.add(assocRef);
 | |
|         }
 | |
|         // done
 | |
|         return nodeAssocRefs;
 | |
|     }
 | |
| 
 | |
|     public List<AssociationRef> getSourceAssocs(NodeRef targetRef, QNamePattern qnamePattern)
 | |
|     {
 | |
|         Pair<Long, NodeRef> targetNodePair = getNodePairNotNull(targetRef);
 | |
|         Long targetNodeId = targetNodePair.getFirst();
 | |
|         
 | |
|         QName qnameFilter = null;
 | |
|         if (qnamePattern instanceof QName)
 | |
|         {
 | |
|             qnameFilter = (QName) qnamePattern;
 | |
|         }
 | |
|         Collection<Pair<Long, AssociationRef>> assocPairs = nodeDAO.getSourceNodeAssocs(targetNodeId, qnameFilter);
 | |
|         List<AssociationRef> nodeAssocRefs = new ArrayList<AssociationRef>(assocPairs.size());
 | |
|         for (Pair<Long, AssociationRef> assocPair : assocPairs)
 | |
|         {
 | |
|             AssociationRef assocRef = assocPair.getSecond();
 | |
|             // check qname pattern, if not already filtered
 | |
|             if (qnameFilter == null && !qnamePattern.isMatch(assocRef.getTypeQName()))
 | |
|             {
 | |
|                 continue;   // the assoc name doesn't match the pattern given 
 | |
|             }
 | |
|             nodeAssocRefs.add(assocRef);
 | |
|         }
 | |
|         // done
 | |
|         return nodeAssocRefs;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * @see #getPaths(NodeRef, boolean)
 | |
|      * @see #prependPaths(Node, Path, Collection, Stack, boolean)
 | |
|      */
 | |
|     public Path getPath(NodeRef nodeRef) throws InvalidNodeRefException
 | |
|     {
 | |
|         List<Path> paths = getPaths(nodeRef, true);   // checks primary path count
 | |
|         if (paths.size() == 1)
 | |
|         {
 | |
|             return paths.get(0);   // we know there is only one
 | |
|         }
 | |
|         throw new RuntimeException("Primary path count not checked");  // checked by getPaths()
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * When searching for <code>primaryOnly == true</code>, checks that there is exactly
 | |
|      * one path.
 | |
|      * @see #prependPaths(Node, Path, Collection, Stack, boolean)
 | |
|      */
 | |
|     public List<Path> getPaths(NodeRef nodeRef, boolean primaryOnly) throws InvalidNodeRefException
 | |
|     {
 | |
|         // get the starting node
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         
 | |
|         return nodeDAO.getPaths(nodePair, primaryOnly);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Archives the node without the <b>cm:auditable</b> aspect behaviour
 | |
|      */
 | |
|     private void archiveNode(NodeRef nodeRef, StoreRef archiveStoreRef)
 | |
|     {
 | |
|         boolean wasDisabled = policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE);
 | |
|         try
 | |
|         {
 | |
|             archiveNodeImpl(nodeRef, archiveStoreRef);
 | |
|         }
 | |
|         finally
 | |
|         {
 | |
|             if (!wasDisabled)
 | |
|             {
 | |
|                 policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_AUDITABLE);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     private void archiveNodeImpl(NodeRef nodeRef, StoreRef archiveStoreRef)
 | |
|     {
 | |
|         Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
 | |
|         Long nodeId = nodePair.getFirst();
 | |
|         Pair<Long, ChildAssociationRef> primaryParentAssocPair = nodeDAO.getPrimaryParentAssoc(nodeId);
 | |
|         Set<QName> newAspects = new HashSet<QName>(5);
 | |
|         Map<QName, Serializable> existingProperties = nodeDAO.getNodeProperties(nodeId);
 | |
|         Map<QName, Serializable> newProperties = new HashMap<QName, Serializable>(11);
 | |
|         
 | |
|         // add the aspect
 | |
|         newAspects.add(ContentModel.ASPECT_ARCHIVED);
 | |
|         newProperties.put(ContentModel.PROP_ARCHIVED_BY, AuthenticationUtil.getFullyAuthenticatedUser());
 | |
|         newProperties.put(ContentModel.PROP_ARCHIVED_DATE, new Date());
 | |
|         newProperties.put(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC, primaryParentAssocPair.getSecond());
 | |
|         Serializable originalOwner = existingProperties.get(ContentModel.PROP_OWNER);
 | |
|         Serializable originalCreator = existingProperties.get(ContentModel.PROP_CREATOR);
 | |
|         if (originalOwner != null || originalCreator != null)
 | |
|         {
 | |
|             newProperties.put(
 | |
|                     ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER,
 | |
|                     originalOwner != null ? originalOwner : originalCreator);
 | |
|         }
 | |
|         
 | |
|         // change the node ownership
 | |
|         newAspects.add(ContentModel.ASPECT_OWNABLE);
 | |
|         newProperties.put(ContentModel.PROP_OWNER, AuthenticationUtil.getFullyAuthenticatedUser());
 | |
|         
 | |
|         // Set the aspects and properties
 | |
|         nodeDAO.addNodeProperties(nodeId, newProperties);
 | |
|         nodeDAO.addNodeAspects(nodeId, newAspects);
 | |
|         
 | |
|         // move the node
 | |
|         Pair<Long, NodeRef> archiveStoreRootNodePair = nodeDAO.getRootNode(archiveStoreRef);
 | |
|         moveNode(
 | |
|                 nodeRef,
 | |
|                 archiveStoreRootNodePair.getSecond(),
 | |
|                 ContentModel.ASSOC_CHILDREN,
 | |
|                 NodeArchiveService.QNAME_ARCHIVED_ITEM);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * {@inheritDoc}
 | |
|      * 
 | |
|      * Archives the node without the <b>cm:auditable</b> aspect behaviour
 | |
|      */
 | |
|     public NodeRef restoreNode(NodeRef archivedNodeRef, NodeRef destinationParentNodeRef, QName assocTypeQName, QName assocQName)
 | |
|     {
 | |
|         boolean wasDisabled = policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE);
 | |
|         try
 | |
|         {
 | |
|             return restoreNodeImpl(archivedNodeRef, destinationParentNodeRef, assocTypeQName, assocQName);
 | |
|         }
 | |
|         finally
 | |
|         {
 | |
|             if (!wasDisabled)
 | |
|             {
 | |
|                 policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_AUDITABLE);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     private NodeRef restoreNodeImpl(NodeRef archivedNodeRef, NodeRef destinationParentNodeRef, QName assocTypeQName, QName assocQName)
 | |
|     {
 | |
|         Pair<Long, NodeRef> archivedNodePair = getNodePairNotNull(archivedNodeRef);
 | |
|         Long archivedNodeId = archivedNodePair.getFirst();
 | |
|         Set<QName> existingAspects = nodeDAO.getNodeAspects(archivedNodeId);
 | |
|         Set<QName> newAspects = new HashSet<QName>(5);
 | |
|         Map<QName, Serializable> existingProperties = nodeDAO.getNodeProperties(archivedNodeId);
 | |
|         Map<QName, Serializable> newProperties = new HashMap<QName, Serializable>(11);
 | |
|         
 | |
|         // the node must be a top-level archive node
 | |
|         if (!existingAspects.contains(ContentModel.ASPECT_ARCHIVED))
 | |
|         {
 | |
|             throw new AlfrescoRuntimeException("The node to restore is not an archive node");
 | |
|         }
 | |
|         ChildAssociationRef originalPrimaryParentAssocRef = (ChildAssociationRef) existingProperties.get(
 | |
|                 ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC);
 | |
|         Serializable originalOwner = existingProperties.get(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER);
 | |
|         // remove the archived aspect
 | |
|         Set<QName> removePropertyQNames = new HashSet<QName>(11);
 | |
|         removePropertyQNames.add(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC);
 | |
|         removePropertyQNames.add(ContentModel.PROP_ARCHIVED_BY);
 | |
|         removePropertyQNames.add(ContentModel.PROP_ARCHIVED_DATE);
 | |
|         removePropertyQNames.add(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER);
 | |
|         nodeDAO.removeNodeProperties(archivedNodeId, removePropertyQNames);
 | |
|         nodeDAO.removeNodeAspects(archivedNodeId, Collections.singleton(ContentModel.ASPECT_ARCHIVED));
 | |
|         
 | |
|         // restore the original ownership
 | |
|         if (originalOwner != null)
 | |
|         {
 | |
|             newAspects.add(ContentModel.ASPECT_OWNABLE);
 | |
|             newProperties.put(ContentModel.PROP_OWNER, originalOwner);
 | |
|         }
 | |
|         
 | |
|         if (destinationParentNodeRef == null)
 | |
|         {
 | |
|             // we must restore to the original location
 | |
|             destinationParentNodeRef = originalPrimaryParentAssocRef.getParentRef();
 | |
|         }
 | |
|         // check the associations
 | |
|         if (assocTypeQName == null)
 | |
|         {
 | |
|             assocTypeQName = originalPrimaryParentAssocRef.getTypeQName();
 | |
|         }
 | |
|         if (assocQName == null)
 | |
|         {
 | |
|             assocQName = originalPrimaryParentAssocRef.getQName();
 | |
|         }
 | |
| 
 | |
|         // move the node to the target parent, which may or may not be the original parent
 | |
|         ChildAssociationRef newChildAssocRef = moveNode(
 | |
|                 archivedNodeRef,
 | |
|                 destinationParentNodeRef,
 | |
|                 assocTypeQName,
 | |
|                 assocQName);
 | |
| 
 | |
|         // the node reference has changed due to the store move
 | |
|         NodeRef restoredNodeRef = newChildAssocRef.getChildRef();
 | |
|         invokeOnRestoreNode(newChildAssocRef);
 | |
|         // done
 | |
|         if (logger.isDebugEnabled())
 | |
|         {
 | |
|             logger.debug("Restored node: \n" +
 | |
|                     "   original noderef: " + archivedNodeRef + "\n" +
 | |
|                     "   restored noderef: " + restoredNodeRef + "\n" +
 | |
|                     "   new parent: " + destinationParentNodeRef);
 | |
|         }
 | |
|         return restoredNodeRef;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Move Node
 | |
|      * 
 | |
|      * Drops the old primary association and creates a new one
 | |
|      */
 | |
|     public ChildAssociationRef moveNode(
 | |
|             NodeRef nodeToMoveRef,
 | |
|             NodeRef newParentRef,
 | |
|             QName assocTypeQName,
 | |
|             QName assocQName)
 | |
|     {
 | |
|         if (isDeletedNodeRef(newParentRef))
 | |
|         {
 | |
|             throw new InvalidNodeRefException("The parent node has been deleted", newParentRef);
 | |
|         }
 | |
| 
 | |
|         Pair<Long, NodeRef> nodeToMovePair = getNodePairNotNull(nodeToMoveRef);
 | |
|         Pair<Long, NodeRef> parentNodePair = getNodePairNotNull(newParentRef);
 | |
|         
 | |
|         Long nodeToMoveId = nodeToMovePair.getFirst();
 | |
|         QName nodeToMoveTypeQName = nodeDAO.getNodeType(nodeToMoveId);
 | |
|         NodeRef oldNodeToMoveRef = nodeToMovePair.getSecond();
 | |
|         Long parentNodeId = parentNodePair.getFirst();
 | |
|         NodeRef parentNodeRef = parentNodePair.getSecond();
 | |
|         StoreRef oldStoreRef = oldNodeToMoveRef.getStoreRef();
 | |
|         StoreRef newStoreRef = parentNodeRef.getStoreRef();
 | |
|         NodeRef newNodeToMoveRef = new NodeRef(newStoreRef, oldNodeToMoveRef.getId());
 | |
|         Pair<Long, NodeRef> newNodeToMovePair = new Pair<Long, NodeRef>(nodeToMoveId, newNodeToMoveRef);
 | |
|         
 | |
|         // Get the primary parent association
 | |
|         Pair<Long, ChildAssociationRef> oldParentAssocPair = nodeDAO.getPrimaryParentAssoc(nodeToMoveId);
 | |
|         if (oldParentAssocPair == null)
 | |
|         {
 | |
|             // The node doesn't have parent.  Moving it is not possible.
 | |
|             throw new IllegalArgumentException("Node " + nodeToMoveId + " doesn't have a parent.  Use 'addChild' instead of move.");
 | |
|         }
 | |
|         ChildAssociationRef oldParentAssocRef = oldParentAssocPair.getSecond();
 | |
|         
 | |
|         // Shortcut this whole process if nothing has changed
 | |
|         if (EqualsHelper.nullSafeEquals(oldParentAssocRef.getParentRef(), newParentRef) &&
 | |
|                 EqualsHelper.nullSafeEquals(oldParentAssocRef.getTypeQName(), assocTypeQName) &&
 | |
|                 EqualsHelper.nullSafeEquals(oldParentAssocRef.getQName(), assocQName))
 | |
|         {
 | |
|             // It's all just the same
 | |
|             return oldParentAssocRef;
 | |
|         }
 | |
|         
 | |
|         boolean movingStore = !oldStoreRef.equals(newStoreRef);
 | |
|         
 | |
|         // Invoke "Before"policy behaviour
 | |
|         if (movingStore)
 | |
|         {
 | |
|             // remove the deleted node from the list of new nodes
 | |
|             untrackNewNodeRef(nodeToMoveRef);
 | |
| 
 | |
|             // track the deletion of this node - so we can prevent new associations to it.
 | |
|             trackDeletedNodeRef(nodeToMoveRef);
 | |
|             
 | |
|             invokeBeforeDeleteNode(nodeToMoveRef);
 | |
|             invokeBeforeCreateNode(newParentRef, assocTypeQName, assocQName, nodeToMoveTypeQName);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             invokeBeforeDeleteChildAssociation(oldParentAssocRef);
 | |
|         }
 | |
|         
 | |
|         // Move node under the new parent
 | |
|         Pair<Long, ChildAssociationRef> newParentAssocPair = nodeDAO.moveNode(
 | |
|                 nodeToMoveId,
 | |
|                 parentNodeId,
 | |
|                 assocTypeQName,
 | |
|                 assocQName);
 | |
|         ChildAssociationRef newParentAssocRef = newParentAssocPair.getSecond();
 | |
| 
 | |
|         // Handle indexing differently if it is a store move
 | |
|         if (movingStore)
 | |
|         {
 | |
|             // The association existed before and the node is moving to a new store
 | |
|             nodeIndexer.indexDeleteNode(oldParentAssocRef);
 | |
|             nodeIndexer.indexCreateNode(newParentAssocRef);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             // The node is in the same store and is just having it's child association modified
 | |
|             nodeIndexer.indexUpdateChildAssociation(oldParentAssocRef, newParentAssocRef);
 | |
|         }
 | |
|         
 | |
|         // Call behaviours
 | |
|         if (movingStore)
 | |
|         {
 | |
|             Set<QName> nodeToMoveAspectQNames = nodeDAO.getNodeAspects(nodeToMoveId);
 | |
|             // The Node changes NodeRefs, so this is really the deletion of the old node and creation
 | |
|             // of a node in a new store as far as the clients are concerned.
 | |
|             invokeOnDeleteNode(oldParentAssocRef, nodeToMoveTypeQName, nodeToMoveAspectQNames, true);
 | |
|             invokeOnCreateNode(newParentAssocRef);
 | |
|             
 | |
|             // Pull children to the new store
 | |
|             pullNodeChildrenToSameStore(newNodeToMovePair);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             invokeOnCreateChildAssociation(newParentAssocRef, false);
 | |
|             invokeOnDeleteChildAssociation(oldParentAssocRef);
 | |
|             invokeOnMoveNode(oldParentAssocRef, newParentAssocRef);
 | |
|         }
 | |
|         
 | |
|         // Done
 | |
|         return newParentAssocRef;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * This process is less invasive than the <b>move</b> method as the child associations
 | |
|      * do not need to be remade.
 | |
|      */
 | |
|     private void pullNodeChildrenToSameStore(Pair<Long, NodeRef> nodePair)
 | |
|     {
 | |
|         Long nodeId = nodePair.getFirst();
 | |
|         // Get the node's children, but only one's that aren't in the same store
 | |
|         final List<Pair<Long, NodeRef>> childNodePairs = new ArrayList<Pair<Long, NodeRef>>(5);
 | |
|         NodeDAO.ChildAssocRefQueryCallback callback = new NodeDAO.ChildAssocRefQueryCallback()
 | |
|         {
 | |
|             public boolean handle(
 | |
|                     Pair<Long, ChildAssociationRef> childAssocPair,
 | |
|                     Pair<Long, NodeRef> parentNodePair,
 | |
|                     Pair<Long, NodeRef> childNodePair
 | |
|                     )
 | |
|             {
 | |
|                 // Add it
 | |
|                 childNodePairs.add(childNodePair);
 | |
|                 // More results
 | |
|                 return true;
 | |
|             }
 | |
| 
 | |
|             public boolean preLoadNodes()
 | |
|             {
 | |
|                 return true;
 | |
|             }
 | |
| 
 | |
|             public void done()
 | |
|             {
 | |
|             }                               
 | |
|         };
 | |
|         // We only need to move child nodes that are not already in the same store
 | |
|         nodeDAO.getChildAssocs(nodeId, null, null, null, Boolean.TRUE, Boolean.FALSE, callback);
 | |
|         // Each child must be moved to the same store as the parent
 | |
|         for (Pair<Long, NodeRef> oldChildNodePair : childNodePairs)
 | |
|         {
 | |
|             Long childNodeId = oldChildNodePair.getFirst();
 | |
|             NodeRef childNodeRef = oldChildNodePair.getSecond();
 | |
|             NodeRef.Status childNodeStatus = nodeDAO.getNodeRefStatus(childNodeRef);
 | |
|             if (childNodeStatus == null || childNodeStatus.isDeleted())
 | |
|             {
 | |
|                 // Node has already been deleted.
 | |
|                 continue;
 | |
|             } 
 | |
|             
 | |
|             QName childNodeTypeQName = nodeDAO.getNodeType(childNodeId);
 | |
|             Set<QName> childNodeAspectQNames = nodeDAO.getNodeAspects(childNodeId);
 | |
|             Pair<Long, ChildAssociationRef> oldParentAssocPair = nodeDAO.getPrimaryParentAssoc(childNodeId);
 | |
|             Pair<Long, NodeRef> newChildNodePair = oldChildNodePair;
 | |
|             Pair<Long, ChildAssociationRef> newParentAssocPair = oldParentAssocPair;
 | |
|             ChildAssociationRef newParentAssocRef = newParentAssocPair.getSecond();
 | |
|             
 | |
|             // remove the deleted node from the list of new nodes
 | |
|             untrackNewNodeRef(childNodeRef);
 | |
| 
 | |
|             // track the deletion of this node - so we can prevent new associations to it.
 | |
|             trackDeletedNodeRef(childNodeRef);
 | |
|             
 | |
|             // Fire node policies.  This ensures that each node in the hierarchy gets a notification fired.
 | |
|             invokeBeforeDeleteNode(childNodeRef);
 | |
|             invokeBeforeCreateNode(
 | |
|                         newParentAssocRef.getParentRef(),
 | |
|                         newParentAssocRef.getTypeQName(),
 | |
|                         newParentAssocRef.getQName(),
 | |
|                         childNodeTypeQName);
 | |
|             // Move the node as this gives back the primary parent association
 | |
|             newParentAssocPair = nodeDAO.moveNode(childNodeId, nodeId, null,null);
 | |
|             // Index
 | |
|             nodeIndexer.indexCreateNode(newParentAssocPair.getSecond());
 | |
|             // Fire node policies.  This ensures that each node in the hierarchy gets a notification fired.
 | |
|             invokeOnDeleteNode(oldParentAssocPair.getSecond(), childNodeTypeQName, childNodeAspectQNames, true);
 | |
|             invokeOnCreateNode(newParentAssocPair.getSecond());
 | |
|             // Cascade
 | |
|             pullNodeChildrenToSameStore(newChildNodePair);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     public NodeRef getStoreArchiveNode(StoreRef storeRef)
 | |
|     {
 | |
|         StoreRef archiveStoreRef = storeArchiveMap.get(storeRef);
 | |
|         if (archiveStoreRef == null)
 | |
|         {
 | |
|             // no mapping for the given store
 | |
|             return null;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             return getRootNode(archiveStoreRef);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private String extractNameProperty(Map<QName, Serializable> properties)
 | |
|     {
 | |
|         Serializable nameValue = properties.get(ContentModel.PROP_NAME);
 | |
|         String name = (String) DefaultTypeConverter.INSTANCE.convert(String.class, nameValue);
 | |
|         return name;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Ensures name uniqueness for the child and the child association.  Note that nothing is done if the
 | |
|      * association type doesn't enforce name uniqueness.
 | |
|      * 
 | |
|      * @return          Returns <tt>true</tt> if the child association <b>cm:name</b> was written
 | |
|      */
 | |
|     private boolean setChildNameUnique(Pair<Long, NodeRef> childNodePair, String newName, String oldName)
 | |
|     {
 | |
|         if (newName == null)
 | |
|         {
 | |
|             newName = childNodePair.getSecond().getId();            // Use the node's GUID
 | |
|         }
 | |
|         Long childNodeId = childNodePair.getFirst();
 | |
|         
 | |
|         if (EqualsHelper.nullSafeEquals(newName, oldName))
 | |
|         {
 | |
|             // The name has not changed
 | |
|             return false;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             nodeDAO.setChildAssocsUniqueName(childNodeId, newName);
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
| }
 |