diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java index 9d4d3abd81..0726a6a0dc 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java @@ -1,1823 +1,1823 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -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.Map; -import java.util.Set; -import java.util.Stack; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.domain.ChildAssoc; -import org.alfresco.repo.domain.Node; -import org.alfresco.repo.domain.NodeAssoc; -import org.alfresco.repo.domain.NodeStatus; -import org.alfresco.repo.domain.PropertyValue; -import org.alfresco.repo.domain.Store; -import org.alfresco.repo.node.AbstractNodeServiceImpl; -import org.alfresco.repo.node.StoreArchiveMap; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.service.cmr.dictionary.AspectDefinition; -import org.alfresco.service.cmr.dictionary.AssociationDefinition; -import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; -import org.alfresco.service.cmr.dictionary.ClassDefinition; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -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.CyclicChildRelationshipException; -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.NodeService; -import org.alfresco.service.cmr.repository.Path; -import org.alfresco.service.cmr.repository.StoreExistsException; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.repository.NodeRef.Status; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.QNamePattern; -import org.alfresco.util.ParameterCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.util.Assert; - -/** - * 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 static Log loggerPaths = LogFactory.getLog(DbNodeServiceImpl.class.getName() + ".paths"); - - private NodeDaoService nodeDaoService; - private StoreArchiveMap storeArchiveMap; - private NodeService avmNodeService; - - public DbNodeServiceImpl() - { - storeArchiveMap = new StoreArchiveMap(); // in case it is not set - } - - public void setNodeDaoService(NodeDaoService nodeDaoService) - { - this.nodeDaoService = nodeDaoService; - } - - public void setStoreArchiveMap(StoreArchiveMap storeArchiveMap) - { - this.storeArchiveMap = storeArchiveMap; - } - - public void setAvmNodeService(NodeService avmNodeService) - { - this.avmNodeService = avmNodeService; - } - - /** - * 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 Node getNodeNotNull(NodeRef nodeRef) throws InvalidNodeRefException - { - Node unchecked = nodeDaoService.getNode(nodeRef); - if (unchecked == null) - { - throw new InvalidNodeRefException("Node does not exist: " + nodeRef, nodeRef); - } - return unchecked; - } - - public boolean exists(StoreRef storeRef) - { - Store store = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier()); - boolean exists = (store != null); - // done - return exists; - } - - public boolean exists(NodeRef nodeRef) - { - Node node = nodeDaoService.getNode(nodeRef); - boolean exists = (node != null); - // done - return exists; - } - - public Status getNodeStatus(NodeRef nodeRef) - { - NodeStatus nodeStatus = nodeDaoService.getNodeStatus(nodeRef, false); - if (nodeStatus == null) // node never existed - { - return null; - } - else - { - return new NodeRef.Status( - nodeStatus.getTransaction().getChangeTxnId(), - nodeStatus.isDeleted()); - } - } - - /** - * @see NodeDaoService#getStores() - */ - public List getStores() - { - List stores = nodeDaoService.getStores(); - List storeRefs = new ArrayList(stores.size()); - for (Store store : stores) - { - storeRefs.add(store.getStoreRef()); - } - // Now get the AVMStores. - List 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); - // check that the store does not already exist - Store store = nodeDaoService.getStore(protocol, identifier); - if (store != null) - { - throw new StoreExistsException("Unable to create a store that already exists: " + storeRef, storeRef); - } - - // invoke policies - invokeBeforeCreateStore(ContentModel.TYPE_STOREROOT, storeRef); - - // create a new one - store = nodeDaoService.createStore(protocol, identifier); - // get the root node - Node rootNode = store.getRootNode(); - // assign the root aspect - this is expected of all roots, even store roots - addAspect(rootNode.getNodeRef(), - ContentModel.ASPECT_ROOT, - Collections.emptyMap()); - - // invoke policies - invokeOnCreateStore(rootNode.getNodeRef()); - - // done - if (!store.getStoreRef().equals(storeRef)) - { - throw new RuntimeException("Incorrect store reference"); - } - return storeRef; - } - - public NodeRef getRootNode(StoreRef storeRef) throws InvalidStoreRefException - { - Store store = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier()); - if (store == null) - { - throw new InvalidStoreRefException("Store does not exist", storeRef); - } - // get the root - Node node = store.getRootNode(); - if (node == null) - { - throw new InvalidStoreRefException("Store does not have a root node", storeRef); - } - NodeRef nodeRef = node.getNodeRef(); - // done - return nodeRef; - } - - /** - * @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); - } - - /** - * @see org.alfresco.service.cmr.repository.NodeService#createNode(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName, java.util.Map) - */ - public ChildAssociationRef createNode( - NodeRef parentRef, - QName assocTypeQName, - QName assocQName, - QName nodeTypeQName, - Map properties) - { - Assert.notNull(parentRef); - Assert.notNull(assocTypeQName); - Assert.notNull(assocQName); - - // null property map is allowed - if (properties == null) - { - properties = new HashMap(); - } - else - { - // Copy the incomming property map since we may need to modify it later - properties = new HashMap(properties); - } - - // Invoke policy behaviour - invokeBeforeUpdateNode(parentRef); - invokeBeforeCreateNode(parentRef, assocTypeQName, assocQName, nodeTypeQName); - - // get the store that the parent belongs to - StoreRef storeRef = parentRef.getStoreRef(); - Store store = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier()); - if (store == null) - { - throw new RuntimeException("No store found for parent node: " + parentRef); - } - - // check the node type - TypeDefinition nodeTypeDef = dictionaryService.getType(nodeTypeQName); - if (nodeTypeDef == null) - { - throw new InvalidTypeException(nodeTypeQName); - } - - // get/generate an ID for the node - String newId = generateGuid(properties); - - // create the node instance - Node childNode = nodeDaoService.newNode(store, newId, nodeTypeQName); - - // get the parent node - Node parentNode = getNodeNotNull(parentRef); - - // Set the default property values - addDefaultPropertyValues(nodeTypeDef, properties); - - // Add the default aspects to the node - addDefaultAspects(nodeTypeDef, childNode, properties); - - // set the properties - it is a new node so only set properties if there are any - Map propertiesBefore = getPropertiesImpl(childNode); - Map propertiesAfter = null; - if (properties.size() > 0) - { - propertiesAfter = setPropertiesImpl(childNode, properties); - } - - // create the association - ChildAssoc childAssoc = nodeDaoService.newChildAssoc( - parentNode, - childNode, - true, - assocTypeQName, - assocQName); - setChildUniqueName(childNode); // ensure uniqueness - ChildAssociationRef childAssocRef = childAssoc.getChildAssocRef(); - - // Invoke policy behaviour - invokeOnCreateNode(childAssocRef); - invokeOnUpdateNode(parentRef); - if (propertiesAfter != null) - { - invokeOnUpdateProperties(childAssocRef.getChildRef(), propertiesBefore, propertiesAfter); - } - - // done - return childAssocRef; - } - - /** - * Add the default aspects to a given node - * - * @param nodeTypeDef - */ - private void addDefaultAspects(ClassDefinition classDefinition, Node node, Map properties) - { - NodeRef nodeRef = node.getNodeRef(); - - // get the mandatory aspects for the node type - List defaultAspectDefs = classDefinition.getDefaultAspects(); - - // add all the aspects to the node - Set nodeAspects = node.getAspects(); - for (AspectDefinition defaultAspectDef : defaultAspectDefs) - { - invokeBeforeAddAspect(nodeRef, defaultAspectDef.getName()); - nodeAspects.add(defaultAspectDef.getName()); - addDefaultPropertyValues(defaultAspectDef, properties); - invokeOnAddAspect(nodeRef, defaultAspectDef.getName()); - - // Now add any default aspects for this aspect - addDefaultAspects(defaultAspectDef, node, properties); - } - } - - /** - * Drops the old primary association and creates a new one - */ - public ChildAssociationRef moveNode( - NodeRef nodeToMoveRef, - NodeRef newParentRef, - QName assocTypeQName, - QName assocQName) - throws InvalidNodeRefException - { - Assert.notNull(nodeToMoveRef); - Assert.notNull(newParentRef); - Assert.notNull(assocTypeQName); - Assert.notNull(assocQName); - - // check the node references - Node nodeToMove = getNodeNotNull(nodeToMoveRef); - Node newParentNode = getNodeNotNull(newParentRef); - // get the primary parent assoc - ChildAssoc oldAssoc = nodeDaoService.getPrimaryParentAssoc(nodeToMove); - ChildAssociationRef oldAssocRef = oldAssoc.getChildAssocRef(); - // get the old parent - Node oldParentNode = oldAssoc.getParent(); - - boolean movingStore = !nodeToMoveRef.getStoreRef().equals(newParentRef.getStoreRef()); - - // data needed for policy invocation - QName nodeToMoveTypeQName = nodeToMove.getTypeQName(); - Set nodeToMoveAspects = nodeToMove.getAspects(); - - // Invoke policy behaviour - if (movingStore) - { - invokeBeforeDeleteNode(nodeToMoveRef); - invokeBeforeCreateNode(newParentRef, assocTypeQName, assocQName, nodeToMoveTypeQName); - } - else - { - invokeBeforeDeleteChildAssociation(oldAssocRef); - invokeBeforeCreateChildAssociation(newParentRef, nodeToMoveRef, assocTypeQName, assocQName); - invokeBeforeUpdateNode(oldParentNode.getNodeRef()); // old parent will be updated - invokeBeforeUpdateNode(newParentRef); // new parent ditto - } - - // remove the child assoc from the old parent - // don't cascade as we will still need the node afterwards - nodeDaoService.deleteChildAssoc(oldAssoc, false); - - // create a new assoc - ChildAssoc newAssoc = nodeDaoService.newChildAssoc( - newParentNode, - nodeToMove, - true, - assocTypeQName, - assocQName); - setChildUniqueName(nodeToMove); // ensure uniqueness - ChildAssociationRef newAssocRef = newAssoc.getChildAssocRef(); - - // If the node is moving stores, then drag the node hierarchy with it - if (movingStore) - { - // do the move - Store newStore = newParentNode.getStore(); - moveNodeToStore(nodeToMove, newStore); - // the node reference will have changed too - nodeToMoveRef = nodeToMove.getNodeRef(); - } - - // check that no cyclic relationships have been created - getPaths(nodeToMoveRef, false); - - // invoke policy behaviour - if (movingStore) - { - // TODO for now indicate that the node has been archived to prevent the version history from being removed - // in the future a onMove policy could be added and remove the need for onDelete and onCreate to be fired here - invokeOnDeleteNode(oldAssocRef, nodeToMoveTypeQName, nodeToMoveAspects, true); - invokeOnCreateNode(newAssoc.getChildAssocRef()); - } - else - { - invokeOnCreateChildAssociation(newAssoc.getChildAssocRef()); - invokeOnDeleteChildAssociation(oldAssoc.getChildAssocRef()); - invokeOnUpdateNode(oldParentNode.getNodeRef()); - invokeOnUpdateNode(newParentRef); - } - invokeOnMoveNode(oldAssocRef, newAssocRef); - - // update the node status - nodeDaoService.recordChangeId(nodeToMoveRef); - - // done - return newAssoc.getChildAssocRef(); - } - - public void setChildAssociationIndex(ChildAssociationRef childAssocRef, int index) - { - // get nodes - Node parentNode = getNodeNotNull(childAssocRef.getParentRef()); - Node childNode = getNodeNotNull(childAssocRef.getChildRef()); - - ChildAssoc assoc = nodeDaoService.getChildAssoc( - parentNode, - childNode, - childAssocRef.getTypeQName(), - childAssocRef.getQName()); - if (assoc == null) - { - throw new InvalidChildAssociationRefException("Unable to set child association index: \n" + - " assoc: " + childAssocRef + "\n" + - " index: " + index, - childAssocRef); - } - // set the index - assoc.setIndex(index); - } - - public QName getType(NodeRef nodeRef) throws InvalidNodeRefException - { - Node node = getNodeNotNull(nodeRef); - return node.getTypeQName(); - } - - /** - * @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); - } - - // Invoke policies - invokeBeforeUpdateNode(nodeRef); - - // Get the node and set the new type - Node node = getNodeNotNull(nodeRef); - node.setTypeQName(typeQName); - - // Add the default aspects to the node (update the properties with any new default values) - Map properties = this.getPropertiesImpl(node); - addDefaultAspects(nodeTypeDef, node, properties); - this.setProperties(nodeRef, properties); - - // Invoke policies - invokeOnUpdateNode(nodeRef); - } - - /** - * @see Node#getAspects() - */ - public void addAspect( - NodeRef nodeRef, - QName aspectTypeQName, - Map 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); - } - - // Invoke policy behaviours - invokeBeforeUpdateNode(nodeRef); - invokeBeforeAddAspect(nodeRef, aspectTypeQName); - - Node node = getNodeNotNull(nodeRef); - - // attach the properties to the current node properties - Map nodeProperties = getPropertiesImpl(node); - - if (aspectProperties != null) - { - nodeProperties.putAll(aspectProperties); - } - - // Set any default property values that appear on the aspect - addDefaultPropertyValues(aspectDef, nodeProperties); - - // Add any dependant aspect - addDefaultAspects(aspectDef, node, nodeProperties); - - // Set the property values back on the node - setProperties(nodeRef, nodeProperties); - - // physically attach the aspect to the node - if (node.getAspects().add(aspectTypeQName) == true) - { - // Invoke policy behaviours - invokeOnUpdateNode(nodeRef); - invokeOnAddAspect(nodeRef, aspectTypeQName); - - // update the node status - nodeDaoService.recordChangeId(nodeRef); - } - } - - /** - * @see Node#getAspects() - */ - public void removeAspect(NodeRef nodeRef, QName aspectTypeQName) - throws InvalidNodeRefException, InvalidAspectException - { - // Invoke policy behaviours - invokeBeforeUpdateNode(nodeRef); - invokeBeforeRemoveAspect(nodeRef, aspectTypeQName); - - // get the aspect - AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName); - if (aspectDef == null) - { - throw new InvalidAspectException(aspectTypeQName); - } - // get the node - Node node = getNodeNotNull(nodeRef); - - // remove the aspect, if present - boolean removed = node.getAspects().remove(aspectTypeQName); - // if the aspect was present, remove the associated properties - if (removed) - { - Map nodeProperties = node.getProperties(); - Map propertyDefs = aspectDef.getProperties(); - for (QName propertyName : propertyDefs.keySet()) - { - nodeProperties.remove(propertyName); - } - - // Invoke policy behaviours - invokeOnUpdateNode(nodeRef); - invokeOnRemoveAspect(nodeRef, aspectTypeQName); - - // update the node status - nodeDaoService.recordChangeId(nodeRef); - } - } - - /** - * Performs a check on the set of node aspects - * - * @see Node#getAspects() - */ - public boolean hasAspect(NodeRef nodeRef, QName aspectRef) throws InvalidNodeRefException, InvalidAspectException - { - Node node = getNodeNotNull(nodeRef); - Set aspectQNames = node.getAspects(); - boolean hasAspect = aspectQNames.contains(aspectRef); - // done - return hasAspect; - } - - public Set getAspects(NodeRef nodeRef) throws InvalidNodeRefException - { - Node node = getNodeNotNull(nodeRef); - Set aspectQNames = node.getAspects(); - // copy the set to ensure initialization - Set ret = new HashSet(aspectQNames.size()); - ret.addAll(aspectQNames); - // done - return ret; - } - - public void deleteNode(NodeRef nodeRef) - { - boolean isArchivedNode = false; - boolean requiresDelete = false; - - // Invoke policy behaviours - invokeBeforeDeleteNode(nodeRef); - - // get the node - Node node = getNodeNotNull(nodeRef); - // get the primary parent-child relationship before it is gone - ChildAssociationRef childAssocRef = getPrimaryParent(nodeRef); - // get type and aspect QNames as they will be unavailable after the delete - QName nodeTypeQName = node.getTypeQName(); - Set nodeAspectQNames = node.getAspects(); - - // check if we need to archive the node - StoreRef archiveStoreRef = null; - if (nodeAspectQNames.contains(ContentModel.ASPECT_TEMPORARY)) - { - // the node has the temporary aspect meaning - // it can not be archived - requiresDelete = true; - isArchivedNode = false; - } - else - { - StoreRef storeRef = nodeRef.getStoreRef(); - archiveStoreRef = storeArchiveMap.getArchiveMap().get(storeRef); - // get the type and check if we need archiving - TypeDefinition typeDef = dictionaryService.getType(node.getTypeQName()); - if (typeDef == null || !typeDef.isArchive() || archiveStoreRef == null) - { - requiresDelete = true; - } - } - - if (requiresDelete) - { - // perform a normal deletion - nodeDaoService.deleteNode(node, true); - isArchivedNode = false; - } - else - { - // archive it - archiveNode(nodeRef, archiveStoreRef); - isArchivedNode = true; - } - - // Invoke policy behaviours - invokeOnDeleteNode(childAssocRef, nodeTypeQName, nodeAspectQNames, isArchivedNode); - } - - public ChildAssociationRef addChild(NodeRef parentRef, NodeRef childRef, QName assocTypeQName, QName assocQName) - { - // Invoke policy behaviours - invokeBeforeUpdateNode(parentRef); - invokeBeforeCreateChildAssociation(parentRef, childRef, assocTypeQName, assocQName); - - // get the parent node and ensure that it is a container node - Node parentNode = getNodeNotNull(parentRef); - // get the child node - Node childNode = getNodeNotNull(childRef); - // make the association - ChildAssoc assoc = nodeDaoService.newChildAssoc( - parentNode, - childNode, - false, - assocTypeQName, - assocQName); - // ensure name uniqueness - setChildUniqueName(childNode); - ChildAssociationRef assocRef = assoc.getChildAssocRef(); - NodeRef childNodeRef = assocRef.getChildRef(); - - // check that the child addition of the child has not created a cyclic relationship - // this functionality is provided for free in getPath - getPaths(childNodeRef, false); - - // Invoke policy behaviours - invokeOnCreateChildAssociation(assocRef); - invokeOnUpdateNode(parentRef); - - return assoc.getChildAssocRef(); - } - - public void removeChild(NodeRef parentRef, NodeRef childRef) throws InvalidNodeRefException - { - Node parentNode = getNodeNotNull(parentRef); - Node childNode = getNodeNotNull(childRef); - Long childNodeId = childNode.getId(); - - // get all the child assocs - ChildAssociationRef primaryAssocRef = null; - Collection assocs = nodeDaoService.getChildAssocs(parentNode); - assocs = new HashSet(assocs); // copy set as we will be modifying it - for (ChildAssoc assoc : assocs) - { - if (!assoc.getChild().getId().equals(childNodeId)) - { - continue; // not a matching association - } - ChildAssociationRef assocRef = assoc.getChildAssocRef(); - // Is this a primary association? - if (assoc.getIsPrimary()) - { - // keep the primary associaton for last - primaryAssocRef = assocRef; - } - else - { - // delete the association instance - it is not primary - invokeBeforeDeleteChildAssociation(assocRef); - nodeDaoService.deleteChildAssoc(assoc, true); // cascade - invokeOnDeleteChildAssociation(assocRef); - } - } - // remove the child if the primary association was a match - if (primaryAssocRef != null) - { - deleteNode(primaryAssocRef.getChildRef()); - } - - // Invoke policy behaviours - invokeOnUpdateNode(parentRef); - - // done - } - - public Map getProperties(NodeRef nodeRef) throws InvalidNodeRefException - { - Node node = getNodeNotNull(nodeRef); - return getPropertiesImpl(node); - } - - private Map getPropertiesImpl(Node node) throws InvalidNodeRefException - { - NodeRef nodeRef = node.getNodeRef(); - - Map nodeProperties = node.getProperties(); - Map ret = new HashMap(nodeProperties.size()); - // copy values - for (Map.Entry entry: nodeProperties.entrySet()) - { - QName propertyQName = entry.getKey(); - PropertyValue propertyValue = entry.getValue(); - // get the property definition - PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); - // convert to the correct type - Serializable value = makeSerializableValue(propertyDef, propertyValue); - // copy across - ret.put(propertyQName, value); - } - // spoof referencable properties - addReferencableProperties(nodeRef, node.getId(), ret); - // done - return ret; - } - - public Serializable getProperty(NodeRef nodeRef, QName qname) throws InvalidNodeRefException - { - // 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(); - } - - // get the property from the node - Node node = getNodeNotNull(nodeRef); - - if (qname.equals(ContentModel.PROP_NODE_DBID)) - { - return node.getId(); - } - - Map properties = node.getProperties(); - PropertyValue propertyValue = properties.get(qname); - - // get the property definition - PropertyDefinition propertyDef = dictionaryService.getProperty(qname); - // convert to the correct type - Serializable value = makeSerializableValue(propertyDef, propertyValue); - // done - return value; - } - - /** - * Ensures that all required properties are present on the node and copies the - * property values to the Node. - *

- * To remove a property, remove it from the map before calling this method. - * Null-valued properties are allowed. - *

- * 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 properties) throws InvalidNodeRefException - { - Node node = getNodeNotNull(nodeRef); - - // Invoke policy behaviours - invokeBeforeUpdateNode(nodeRef); - - // Do the set properties - Map propertiesBefore = getPropertiesImpl(node); - Map propertiesAfter = setPropertiesImpl(node, properties); - - setChildUniqueName(node); // ensure uniqueness - - // Invoke policy behaviours - invokeOnUpdateNode(nodeRef); - invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter); - } - - /** - * Does the work of setting the property values. Returns a map containing the state of the properties after the set - * operation is complete. - * - * @param node the node - * @param properties the map of property values - * @return the map of property values after the set operation is complete - * @throws InvalidNodeRefException - */ - private Map setPropertiesImpl(Node node, Map properties) throws InvalidNodeRefException - { - ParameterCheck.mandatory("properties", properties); - - // remove referencable properties - removeReferencableProperties(properties); - - // copy properties onto node - Map nodeProperties = node.getProperties(); - nodeProperties.clear(); - - // check the property type and copy the values across - for (QName propertyQName : properties.keySet()) - { - PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); - Serializable value = properties.get(propertyQName); - // get a persistable value - PropertyValue propertyValue = makePropertyValue(propertyDef, value); - nodeProperties.put(propertyQName, propertyValue); - } - - // update the node status - NodeRef nodeRef = node.getNodeRef(); - nodeDaoService.recordChangeId(nodeRef); - - // Return the properties after - return Collections.unmodifiableMap(properties); - } - - /** - * 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 - { - Assert.notNull(qname); - - // Invoke policy behaviours - invokeBeforeUpdateNode(nodeRef); - - // get the node - Node node = getNodeNotNull(nodeRef); - - // Do the set operation - Map propertiesBefore = getPropertiesImpl(node); - Map propertiesAfter = setPropertyImpl(node, qname, value); - - if (qname.equals(ContentModel.PROP_NAME)) - { - setChildUniqueName(node); // ensure uniqueness - } - - // Invoke policy behaviours - invokeOnUpdateNode(nodeRef); - invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter); - } - - /** - * Does the work of setting a property value. Returns the values of the properties after the set operation is - * complete. - * - * @param node the node - * @param qname the qname of the property - * @param value the value of the property - * @return the values of the properties after the set operation is complete - * @throws InvalidNodeRefException - */ - public Map setPropertyImpl(Node node, QName qname, Serializable value) throws InvalidNodeRefException - { - NodeRef nodeRef = node.getNodeRef(); - - Map properties = node.getProperties(); - PropertyDefinition propertyDef = dictionaryService.getProperty(qname); - // get a persistable value - PropertyValue propertyValue = makePropertyValue(propertyDef, value); - properties.put(qname, propertyValue); - - // update the node status - nodeDaoService.recordChangeId(nodeRef); - - return getPropertiesImpl(node); - } - - /** - * Transforms {@link Node#getParentAssocs()} to a new collection - */ - public Collection getParents(NodeRef nodeRef) throws InvalidNodeRefException - { - Node node = getNodeNotNull(nodeRef); - // get the assocs pointing to it - Collection parentAssocs = node.getParentAssocs(); - // list of results - Collection results = new ArrayList(parentAssocs.size()); - for (ChildAssoc assoc : parentAssocs) - { - // get the parent - Node parentNode = assoc.getParent(); - results.add(parentNode.getNodeRef()); - } - // done - return results; - } - - /** - * Filters out any associations if their qname is not a match to the given pattern. - */ - public List getParentAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern, QNamePattern qnamePattern) - { - Node node = getNodeNotNull(nodeRef); - // get the assocs pointing to it - Collection parentAssocs = node.getParentAssocs(); - // shortcut if there are no assocs - if (parentAssocs.size() == 0) - { - return Collections.emptyList(); - } - // list of results - List results = new ArrayList(parentAssocs.size()); - for (ChildAssoc assoc : parentAssocs) - { - // does the qname match the pattern? - if (!qnamePattern.isMatch(assoc.getQname()) || !typeQNamePattern.isMatch(assoc.getTypeQName())) - { - // no match - ignore - continue; - } - results.add(assoc.getChildAssocRef()); - } - // done - return results; - } - - /** - * Filters out any associations if their qname is not a match to the given pattern. - */ - public List getChildAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern, QNamePattern qnamePattern) - { - Node node = getNodeNotNull(nodeRef); - // get the assocs pointing from it - Collection childAssocRefs = nodeDaoService.getChildAssocRefs(node); - // shortcut if there are no assocs - if (childAssocRefs.size() == 0) - { - return Collections.emptyList(); - } - // sort results - ArrayList orderedList = new ArrayList(childAssocRefs); - Collections.sort(orderedList); - - // list of results - int nthSibling = 0; - Iterator iterator = orderedList.iterator(); - while(iterator.hasNext()) - { - ChildAssociationRef childAssocRef = iterator.next(); - // does the qname match the pattern? - if (!qnamePattern.isMatch(childAssocRef.getQName()) || !typeQNamePattern.isMatch(childAssocRef.getTypeQName())) - { - // no match - remove - iterator.remove(); - } - else - { - childAssocRef.setNthSibling(nthSibling); - nthSibling++; - } - } - // done - return orderedList; - } - - public NodeRef getChildByName(NodeRef nodeRef, QName assocTypeQName, String childName) - { - Node node = getNodeNotNull(nodeRef); - ChildAssoc childAssoc = nodeDaoService.getChildAssoc(node, assocTypeQName, childName); - if (childAssoc != null) - { - return childAssoc.getChild().getNodeRef(); - } - else - { - return null; - } - } - - public ChildAssociationRef getPrimaryParent(NodeRef nodeRef) throws InvalidNodeRefException - { - Node node = getNodeNotNull(nodeRef); - // get the primary parent assoc - ChildAssoc assoc = nodeDaoService.getPrimaryParentAssoc(node); - - // done - the assoc may be null for a root node - ChildAssociationRef assocRef = null; - if (assoc == null) - { - assocRef = new ChildAssociationRef(null, null, null, nodeRef); - } - else - { - assocRef = assoc.getChildAssocRef(); - } - return assocRef; - } - - public AssociationRef createAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName) - throws InvalidNodeRefException, AssociationExistsException - { - // Invoke policy behaviours - invokeBeforeUpdateNode(sourceRef); - - Node sourceNode = getNodeNotNull(sourceRef); - Node targetNode = getNodeNotNull(targetRef); - // see if it exists - NodeAssoc assoc = nodeDaoService.getNodeAssoc(sourceNode, targetNode, assocTypeQName); - if (assoc != null) - { - throw new AssociationExistsException(sourceRef, targetRef, assocTypeQName); - } - // we are sure that the association doesn't exist - make it - assoc = nodeDaoService.newNodeAssoc(sourceNode, targetNode, assocTypeQName); - AssociationRef assocRef = assoc.getNodeAssocRef(); - - // Invoke policy behaviours - invokeOnUpdateNode(sourceRef); - invokeOnCreateAssociation(assocRef); - - return assocRef; - } - - public void removeAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName) - throws InvalidNodeRefException - { - Node sourceNode = getNodeNotNull(sourceRef); - Node targetNode = getNodeNotNull(targetRef); - // get the association - NodeAssoc assoc = nodeDaoService.getNodeAssoc(sourceNode, targetNode, assocTypeQName); - if (assoc == null) - { - // nothing to remove - return; - } - AssociationRef assocRef = assoc.getNodeAssocRef(); - - // Invoke policy behaviours - invokeBeforeUpdateNode(sourceRef); - - // delete it - nodeDaoService.deleteNodeAssoc(assoc); - - // Invoke policy behaviours - invokeOnUpdateNode(sourceRef); - invokeOnDeleteAssociation(assocRef); - } - - public List getTargetAssocs(NodeRef sourceRef, QNamePattern qnamePattern) - { - Node sourceNode = getNodeNotNull(sourceRef); - // get all assocs to target - Collection assocs = nodeDaoService.getTargetNodeAssocs(sourceNode); - List nodeAssocRefs = new ArrayList(assocs.size()); - for (NodeAssoc assoc : assocs) - { - // check qname pattern - if (!qnamePattern.isMatch(assoc.getTypeQName())) - { - continue; // the assoc name doesn't match the pattern given - } - nodeAssocRefs.add(assoc.getNodeAssocRef()); - } - // done - return nodeAssocRefs; - } - - public List getSourceAssocs(NodeRef targetRef, QNamePattern qnamePattern) - { - Node targetNode = getNodeNotNull(targetRef); - // get all assocs to source - Collection assocs = nodeDaoService.getSourceNodeAssocs(targetNode); - List nodeAssocRefs = new ArrayList(assocs.size()); - for (NodeAssoc assoc : assocs) - { - // check qname pattern - if (!qnamePattern.isMatch(assoc.getTypeQName())) - { - continue; // the assoc name doesn't match the pattern given - } - nodeAssocRefs.add(assoc.getNodeAssocRef()); - } - // done - return nodeAssocRefs; - } - - /** - * Recursive method used to build up paths from a given node to the root. - *

- * Whilst walking up the hierarchy to the root, some nodes may have a root aspect. - * Everytime one of these is encountered, a new path is farmed off, but the method - * continues to walk up the hierarchy. - * - * @param currentNode the node to start from, i.e. the child node to work upwards from - * @param currentPath the path from the current node to the descendent that we started from - * @param completedPaths paths that have reached the root are added to this collection - * @param assocStack the parent-child relationships traversed whilst building the path. - * Used to detected cyclic relationships. - * @param primaryOnly true if only the primary parent association must be traversed. - * If this is true, then the only root is the top level node having no parents. - * @throws CyclicChildRelationshipException - */ - private void prependPaths( - final Node currentNode, - final Path currentPath, - Collection completedPaths, - Stack assocStack, - boolean primaryOnly) - throws CyclicChildRelationshipException - { - NodeRef currentNodeRef = currentNode.getNodeRef(); - // get the parent associations of the given node - Collection parentAssocs = currentNode.getParentAssocs(); - // does the node have parents - boolean hasParents = parentAssocs.size() > 0; - // does the current node have a root aspect? - boolean isRoot = hasAspect(currentNodeRef, ContentModel.ASPECT_ROOT); - boolean isStoreRoot = currentNode.getTypeQName().equals(ContentModel.TYPE_STOREROOT); - - // look for a root. If we only want the primary root, then ignore all but the top-level root. - if (isRoot && !(primaryOnly && hasParents)) // exclude primary search with parents present - { - // create a one-sided assoc ref for the root node and prepend to the stack - // this effectively spoofs the fact that the current node is not below the root - // - we put this assoc in as the first assoc in the path must be a one-sided - // reference pointing to the root node - ChildAssociationRef assocRef = new ChildAssociationRef( - null, - null, - null, - getRootNode(currentNode.getNodeRef().getStoreRef())); - // create a path to save and add the 'root' assoc - Path pathToSave = new Path(); - Path.ChildAssocElement first = null; - for (Path.Element element: currentPath) - { - if (first == null) - { - first = (Path.ChildAssocElement) element; - } - else - { - pathToSave.append(element); - } - } - if (first != null) - { - // mimic an association that would appear if the current node was below - // the root node - // or if first beneath the root node it will make the real thing - ChildAssociationRef updateAssocRef = new ChildAssociationRef( - isStoreRoot ? ContentModel.ASSOC_CHILDREN : first.getRef().getTypeQName(), - getRootNode(currentNode.getNodeRef().getStoreRef()), - first.getRef().getQName(), - first.getRef().getChildRef()); - Path.Element newFirst = new Path.ChildAssocElement(updateAssocRef); - pathToSave.prepend(newFirst); - } - - Path.Element element = new Path.ChildAssocElement(assocRef); - pathToSave.prepend(element); - - // store the path just built - completedPaths.add(pathToSave); - } - - if (parentAssocs.size() == 0 && !isRoot) - { - throw new RuntimeException("Node without parents does not have root aspect: " + - currentNodeRef); - } - // walk up each parent association - for (ChildAssoc assoc : parentAssocs) - { - // does the association already exist in the stack - if (assocStack.contains(assoc)) - { - // the association was present already - throw new CyclicChildRelationshipException( - "Cyclic parent-child relationship detected: \n" + - " current node: " + currentNode + "\n" + - " current path: " + currentPath + "\n" + - " next assoc: " + assoc, - assoc); - } - // do we consider only primary assocs? - if (primaryOnly && !assoc.getIsPrimary()) - { - continue; - } - // build a path element - NodeRef parentRef = assoc.getParent().getNodeRef(); - QName qname = assoc.getQname(); - NodeRef childRef = assoc.getChild().getNodeRef(); - boolean isPrimary = assoc.getIsPrimary(); - // build a real association reference - ChildAssociationRef assocRef = new ChildAssociationRef(assoc.getTypeQName(), parentRef, qname, childRef, isPrimary, -1); - // Ordering is not important here: We are building distinct paths upwards - Path.Element element = new Path.ChildAssocElement(assocRef); - // create a new path that builds on the current path - Path path = new Path(); - path.append(currentPath); - // prepend element - path.prepend(element); - // get parent node - Node parentNode = assoc.getParent(); - - // push the assoc stack, recurse and pop - assocStack.push(assoc); - prependPaths(parentNode, path, completedPaths, assocStack, primaryOnly); - assocStack.pop(); - } - // done - } - - /** - * @see #getPaths(NodeRef, boolean) - * @see #prependPaths(Node, Path, Collection, Stack, boolean) - */ - public Path getPath(NodeRef nodeRef) throws InvalidNodeRefException - { - List 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 primaryOnly == true, checks that there is exactly - * one path. - * @see #prependPaths(Node, Path, Collection, Stack, boolean) - */ - public List getPaths(NodeRef nodeRef, boolean primaryOnly) throws InvalidNodeRefException - { - // get the starting node - Node node = getNodeNotNull(nodeRef); - // create storage for the paths - only need 1 bucket if we are looking for the primary path - List paths = new ArrayList(primaryOnly ? 1 : 10); - // create an empty current path to start from - Path currentPath = new Path(); - // create storage for touched associations - Stack assocStack = new Stack(); - // call recursive method to sort it out - prependPaths(node, currentPath, paths, assocStack, primaryOnly); - - // check that for the primary only case we have exactly one path - if (primaryOnly && paths.size() != 1) - { - throw new RuntimeException("Node has " + paths.size() + " primary paths: " + nodeRef); - } - - // done - if (loggerPaths.isDebugEnabled()) - { - StringBuilder sb = new StringBuilder(256); - if (primaryOnly) - { - sb.append("Primary paths"); - } - else - { - sb.append("Paths"); - } - sb.append(" for node ").append(nodeRef); - for (Path path : paths) - { - sb.append("\n").append(" ").append(path); - } - loggerPaths.debug(sb); - } - return paths; - } - - private void archiveNode(NodeRef nodeRef, StoreRef archiveStoreRef) - { - Node node = getNodeNotNull(nodeRef); - ChildAssoc primaryParentAssoc = nodeDaoService.getPrimaryParentAssoc(node); - - // add the aspect - Set aspects = node.getAspects(); - aspects.add(ContentModel.ASPECT_ARCHIVED); - Map properties = node.getProperties(); - PropertyValue archivedByProperty = makePropertyValue( - dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_BY), - AuthenticationUtil.getCurrentUserName()); - properties.put(ContentModel.PROP_ARCHIVED_BY, archivedByProperty); - PropertyValue archivedDateProperty = makePropertyValue( - dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_DATE), - new Date()); - properties.put(ContentModel.PROP_ARCHIVED_DATE, archivedDateProperty); - PropertyValue archivedPrimaryParentNodeRefProperty = makePropertyValue( - dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC), - primaryParentAssoc.getChildAssocRef()); - properties.put(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC, archivedPrimaryParentNodeRefProperty); - PropertyValue originalOwnerProperty = properties.get(ContentModel.PROP_OWNER); - PropertyValue originalCreatorProperty = properties.get(ContentModel.PROP_CREATOR); - if (originalOwnerProperty != null || originalCreatorProperty != null) - { - properties.put( - ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER, - originalOwnerProperty != null ? originalOwnerProperty : originalCreatorProperty); - } - - // change the node ownership - aspects.add(ContentModel.ASPECT_OWNABLE); - PropertyValue newOwnerProperty = makePropertyValue( - dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER), - AuthenticationUtil.getCurrentUserName()); - properties.put(ContentModel.PROP_OWNER, newOwnerProperty); - - // move the node - NodeRef archiveStoreRootNodeRef = getRootNode(archiveStoreRef); - moveNode( - nodeRef, - archiveStoreRootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedItem")); - - // get the IDs of all the node's primary children, including its own - Map nodesById = getNodeHierarchy(node, null); - - // Archive all the associations between the archived nodes and non-archived nodes - for (Node nodeToArchive : nodesById.values()) - { - archiveAssocs(nodeToArchive, nodesById); - } - - // the node reference has changed due to the store move - nodeRef = node.getNodeRef(); - } - - /** - * Performs all the necessary housekeeping involved in changing a node's store. - * This method cascades down through all the primary children of the node as - * well. - * - * @param node the node whose store is changing - * @param store the new store for the node - */ - private void moveNodeToStore(Node node, Store store) - { - // get the IDs of all the node's primary children, including its own - Map nodesById = getNodeHierarchy(node, null); - - // move each node into the archive store - for (Node nodeToMove : nodesById.values()) - { - NodeRef oldNodeRef = nodeToMove.getNodeRef(); - nodeToMove.setStore(store); - NodeRef newNodeRef = nodeToMove.getNodeRef(); - - String txnId = AlfrescoTransactionSupport.getTransactionId(); - // update old status - NodeStatus oldNodeStatus = nodeDaoService.getNodeStatus(oldNodeRef, true); - oldNodeStatus.setNode(null); - oldNodeStatus.getTransaction().setChangeTxnId(txnId); - // create the new status - NodeStatus newNodeStatus = nodeDaoService.getNodeStatus(newNodeRef, true); - newNodeStatus.setNode(nodeToMove); - newNodeStatus.getTransaction().setChangeTxnId(txnId); - } - } - - /** - * Fill the map of all primary children below the given node. - * The given node will be added to the map and the method is recursive - * to all primary children. - * - * @param node the start of the hierarchy - * @param nodesById a map of nodes that will be reused as the return value - * @return Returns a map of nodes in the hierarchy keyed by their IDs - */ - private Map getNodeHierarchy(Node node, Map nodesById) - { - if (nodesById == null) - { - nodesById = new HashMap(23); - } - - Long id = node.getId(); - if (nodesById.containsKey(id)) - { - // this ID was already added - circular reference - logger.warn("Circular hierarchy found including node " + id); - return nodesById; - } - // add the node to the map - nodesById.put(id, node); - // recurse into the primary children - Collection childAssocs = nodeDaoService.getChildAssocs(node); - for (ChildAssoc childAssoc : childAssocs) - { - // cascade into primary associations - if (childAssoc.getIsPrimary()) - { - Node primaryChild = childAssoc.getChild(); - nodesById = getNodeHierarchy(primaryChild, nodesById); - } - } - return nodesById; - } - - /** - * Archive all associations to and from the given node, with the - * exception of associations to or from nodes in the given map. - *

- * Primary parent associations are also ignored. - * - * @param node the node whose associations must be archived - * @param nodesById a map of nodes partaking in the archival process - */ - private void archiveAssocs(Node node, Map nodesById) - { - List childAssocsToDelete = new ArrayList(5); - // child associations - ArrayList archivedChildAssocRefs = new ArrayList(5); - Collection childAssocs = nodeDaoService.getChildAssocs(node); - for (ChildAssoc assoc : childAssocs) - { - Long relatedNodeId = assoc.getChild().getId(); - if (nodesById.containsKey(relatedNodeId)) - { - // a sibling in the archive process - continue; - } - childAssocsToDelete.add(assoc); - archivedChildAssocRefs.add(assoc.getChildAssocRef()); - } - // parent associations - ArrayList archivedParentAssocRefs = new ArrayList(5); - for (ChildAssoc assoc : node.getParentAssocs()) - { - Long relatedNodeId = assoc.getParent().getId(); - if (nodesById.containsKey(relatedNodeId)) - { - // a sibling in the archive process - continue; - } - else if (assoc.getIsPrimary()) - { - // ignore the primary parent as this is handled more specifically - continue; - } - childAssocsToDelete.add(assoc); - archivedParentAssocRefs.add(assoc.getChildAssocRef()); - } - - List nodeAssocsToDelete = new ArrayList(5); - // source associations - ArrayList archivedSourceAssocRefs = new ArrayList(5); - for (NodeAssoc assoc : nodeDaoService.getSourceNodeAssocs(node)) - { - Long relatedNodeId = assoc.getSource().getId(); - if (nodesById.containsKey(relatedNodeId)) - { - // a sibling in the archive process - continue; - } - nodeAssocsToDelete.add(assoc); - archivedSourceAssocRefs.add(assoc.getNodeAssocRef()); - } - // target associations - ArrayList archivedTargetAssocRefs = new ArrayList(5); - for (NodeAssoc assoc : nodeDaoService.getTargetNodeAssocs(node)) - { - Long relatedNodeId = assoc.getTarget().getId(); - if (nodesById.containsKey(relatedNodeId)) - { - // a sibling in the archive process - continue; - } - nodeAssocsToDelete.add(assoc); - archivedTargetAssocRefs.add(assoc.getNodeAssocRef()); - } - // delete child assocs - for (ChildAssoc assoc : childAssocsToDelete) - { - nodeDaoService.deleteChildAssoc(assoc, false); - } - // delete node assocs - for (NodeAssoc assoc : nodeAssocsToDelete) - { - nodeDaoService.deleteNodeAssoc(assoc); - } - - // add archived aspect - node.getAspects().add(ContentModel.ASPECT_ARCHIVED_ASSOCS); - // set properties - Map properties = node.getProperties(); - - if (archivedParentAssocRefs.size() > 0) - { - PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS); - PropertyValue propertyValue = makePropertyValue(propertyDef, archivedParentAssocRefs); - properties.put(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS, propertyValue); - } - if (archivedChildAssocRefs.size() > 0) - { - PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS); - PropertyValue propertyValue = makePropertyValue(propertyDef, archivedChildAssocRefs); - properties.put(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS, propertyValue); - } - if (archivedSourceAssocRefs.size() > 0) - { - PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS); - PropertyValue propertyValue = makePropertyValue(propertyDef, archivedSourceAssocRefs); - properties.put(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS, propertyValue); - } - if (archivedTargetAssocRefs.size() > 0) - { - PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS); - PropertyValue propertyValue = makePropertyValue(propertyDef, archivedTargetAssocRefs); - properties.put(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS, propertyValue); - } - } - - public NodeRef getStoreArchiveNode(StoreRef storeRef) - { - StoreRef archiveStoreRef = storeArchiveMap.getArchiveMap().get(storeRef); - if (archiveStoreRef == null) - { - // no mapping for the given store - return null; - } - else - { - return getRootNode(archiveStoreRef); - } - } - - public NodeRef restoreNode(NodeRef archivedNodeRef, NodeRef destinationParentNodeRef, QName assocTypeQName, QName assocQName) - { - Node archivedNode = getNodeNotNull(archivedNodeRef); - Set aspects = archivedNode.getAspects(); - Map properties = archivedNode.getProperties(); - // the node must be a top-level archive node - if (!aspects.contains(ContentModel.ASPECT_ARCHIVED)) - { - throw new AlfrescoRuntimeException("The node to archive is not an archive node"); - } - ChildAssociationRef originalPrimaryParentAssocRef = (ChildAssociationRef) makeSerializableValue( - dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC), - properties.get(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC)); - PropertyValue originalOwnerProperty = properties.get(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER); - // remove the aspect archived aspect - aspects.remove(ContentModel.ASPECT_ARCHIVED); - properties.remove(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC); - properties.remove(ContentModel.PROP_ARCHIVED_BY); - properties.remove(ContentModel.PROP_ARCHIVED_DATE); - properties.remove(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER); - - // restore the original ownership - if (originalOwnerProperty != null) - { - aspects.add(ContentModel.ASPECT_OWNABLE); - properties.put(ContentModel.PROP_OWNER, originalOwnerProperty); - } - - 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 - moveNode( - archivedNodeRef, - destinationParentNodeRef, - assocTypeQName, - assocQName); - - // get the IDs of all the node's primary children, including its own - Map restoredNodesById = getNodeHierarchy(archivedNode, null); - // Restore the archived associations, if required - for (Node restoredNode : restoredNodesById.values()) - { - restoreAssocs(restoredNode); - } - - // the node reference has changed due to the store move - NodeRef restoredNodeRef = archivedNode.getNodeRef(); - - // done - if (logger.isDebugEnabled()) - { - logger.debug("Restored node: \n" + - " original noderef: " + archivedNodeRef + "\n" + - " restored noderef: " + restoredNodeRef + "\n" + - " new parent: " + destinationParentNodeRef); - } - return restoredNodeRef; - } - - private void restoreAssocs(Node node) - { - NodeRef nodeRef = node.getNodeRef(); - // set properties - Map properties = node.getProperties(); - - // restore parent associations - Collection parentAssocRefs = (Collection) getProperty( - nodeRef, - ContentModel.PROP_ARCHIVED_PARENT_ASSOCS); - if (parentAssocRefs != null) - { - for (ChildAssociationRef assocRef : parentAssocRefs) - { - NodeRef parentNodeRef = assocRef.getParentRef(); - if (!exists(parentNodeRef)) - { - continue; - } - Node parentNode = getNodeNotNull(parentNodeRef); - // get the name to use for the unique child check - QName assocTypeQName = assocRef.getTypeQName(); - nodeDaoService.newChildAssoc( - parentNode, - node, - assocRef.isPrimary(), - assocTypeQName, - assocRef.getQName()); - } - properties.remove(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS); - } - - // make sure that the node name uniqueness is enforced - setChildUniqueName(node); - - // restore child associations - Collection childAssocRefs = (Collection) getProperty( - nodeRef, - ContentModel.PROP_ARCHIVED_CHILD_ASSOCS); - if (childAssocRefs != null) - { - for (ChildAssociationRef assocRef : childAssocRefs) - { - NodeRef childNodeRef = assocRef.getChildRef(); - if (!exists(childNodeRef)) - { - continue; - } - Node childNode = getNodeNotNull(childNodeRef); - QName assocTypeQName = assocRef.getTypeQName(); - // get the name to use for the unique child check - nodeDaoService.newChildAssoc( - node, - childNode, - assocRef.isPrimary(), - assocTypeQName, - assocRef.getQName()); - // ensure that the name uniqueness is enforced for the child node - setChildUniqueName(childNode); - } - properties.remove(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS); - } - // restore source associations - Collection sourceAssocRefs = (Collection) getProperty( - nodeRef, - ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS); - if (sourceAssocRefs != null) - { - for (AssociationRef assocRef : sourceAssocRefs) - { - NodeRef sourceNodeRef = assocRef.getSourceRef(); - if (!exists(sourceNodeRef)) - { - continue; - } - Node sourceNode = getNodeNotNull(sourceNodeRef); - nodeDaoService.newNodeAssoc(sourceNode, node, assocRef.getTypeQName()); - } - properties.remove(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS); - } - // restore target associations - Collection targetAssocRefs = (Collection) getProperty( - nodeRef, - ContentModel.PROP_ARCHIVED_TARGET_ASSOCS); - if (targetAssocRefs != null) - { - for (AssociationRef assocRef : targetAssocRefs) - { - NodeRef targetNodeRef = assocRef.getTargetRef(); - if (!exists(targetNodeRef)) - { - continue; - } - Node targetNode = getNodeNotNull(targetNodeRef); - nodeDaoService.newNodeAssoc(node, targetNode, assocRef.getTypeQName()); - } - properties.remove(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS); - } - // remove the aspect - node.getAspects().remove(ContentModel.ASPECT_ARCHIVED_ASSOCS); - } - - /** - * Checks the dictionary's definition of the association to assign a unique name to the child node. - * - * @param assocTypeQName the type of the child association - * @param childNode the child node being added. The name will be extracted from it, if necessary. - */ - private void setChildUniqueName(Node childNode) - { - // get the name property - Map properties = childNode.getProperties(); - PropertyValue nameValue = properties.get(ContentModel.PROP_NAME); - String useName = null; - if (nameValue == null) - { - // no name has been assigned, so assign the ID of the child node - useName = childNode.getUuid(); - } - else - { - useName = (String) nameValue.getValue(DataTypeDefinition.TEXT); - } - // get all the parent assocs - Collection parentAssocs = childNode.getParentAssocs(); - for (ChildAssoc assoc : parentAssocs) - { - QName assocTypeQName = assoc.getTypeQName(); - AssociationDefinition assocDef = dictionaryService.getAssociation(assocTypeQName); - if (!assocDef.isChild()) - { - throw new DataIntegrityViolationException("Child association has non-child type: " + assoc.getId()); - } - ChildAssociationDefinition childAssocDef = (ChildAssociationDefinition) assocDef; - if (childAssocDef.getDuplicateChildNamesAllowed()) - { - // the name is irrelevant, so it doesn't need to be put into the unique key - nodeDaoService.setChildNameUnique(assoc, null); - } - else - { - nodeDaoService.setChildNameUnique(assoc, useName); - } - } - // done - if (logger.isDebugEnabled()) - { - logger.debug( - "Unique name set for all " + parentAssocs.size() + " parent associations: \n" + - " name: " + useName); - } - } -} +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +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.Map; +import java.util.Set; +import java.util.Stack; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.domain.ChildAssoc; +import org.alfresco.repo.domain.Node; +import org.alfresco.repo.domain.NodeAssoc; +import org.alfresco.repo.domain.NodeStatus; +import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.repo.domain.Store; +import org.alfresco.repo.node.AbstractNodeServiceImpl; +import org.alfresco.repo.node.StoreArchiveMap; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +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.CyclicChildRelationshipException; +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.NodeService; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.StoreExistsException; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.NodeRef.Status; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.QNamePattern; +import org.alfresco.util.ParameterCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.util.Assert; + +/** + * 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 static Log loggerPaths = LogFactory.getLog(DbNodeServiceImpl.class.getName() + ".paths"); + + private NodeDaoService nodeDaoService; + private StoreArchiveMap storeArchiveMap; + private NodeService avmNodeService; + + public DbNodeServiceImpl() + { + storeArchiveMap = new StoreArchiveMap(); // in case it is not set + } + + public void setNodeDaoService(NodeDaoService nodeDaoService) + { + this.nodeDaoService = nodeDaoService; + } + + public void setStoreArchiveMap(StoreArchiveMap storeArchiveMap) + { + this.storeArchiveMap = storeArchiveMap; + } + + public void setAvmNodeService(NodeService avmNodeService) + { + this.avmNodeService = avmNodeService; + } + + /** + * 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 Node getNodeNotNull(NodeRef nodeRef) throws InvalidNodeRefException + { + Node unchecked = nodeDaoService.getNode(nodeRef); + if (unchecked == null) + { + throw new InvalidNodeRefException("Node does not exist: " + nodeRef, nodeRef); + } + return unchecked; + } + + public boolean exists(StoreRef storeRef) + { + Store store = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier()); + boolean exists = (store != null); + // done + return exists; + } + + public boolean exists(NodeRef nodeRef) + { + Node node = nodeDaoService.getNode(nodeRef); + boolean exists = (node != null); + // done + return exists; + } + + public Status getNodeStatus(NodeRef nodeRef) + { + NodeStatus nodeStatus = nodeDaoService.getNodeStatus(nodeRef, false); + if (nodeStatus == null) // node never existed + { + return null; + } + else + { + return new NodeRef.Status( + nodeStatus.getTransaction().getChangeTxnId(), + nodeStatus.isDeleted()); + } + } + + /** + * @see NodeDaoService#getStores() + */ + public List getStores() + { + List stores = nodeDaoService.getStores(); + List storeRefs = new ArrayList(stores.size()); + for (Store store : stores) + { + storeRefs.add(store.getStoreRef()); + } + // Now get the AVMStores. + List 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); + // check that the store does not already exist + Store store = nodeDaoService.getStore(protocol, identifier); + if (store != null) + { + throw new StoreExistsException("Unable to create a store that already exists: " + storeRef, storeRef); + } + + // invoke policies + invokeBeforeCreateStore(ContentModel.TYPE_STOREROOT, storeRef); + + // create a new one + store = nodeDaoService.createStore(protocol, identifier); + // get the root node + Node rootNode = store.getRootNode(); + // assign the root aspect - this is expected of all roots, even store roots + addAspect(rootNode.getNodeRef(), + ContentModel.ASPECT_ROOT, + Collections.emptyMap()); + + // invoke policies + invokeOnCreateStore(rootNode.getNodeRef()); + + // done + if (!store.getStoreRef().equals(storeRef)) + { + throw new RuntimeException("Incorrect store reference"); + } + return storeRef; + } + + public NodeRef getRootNode(StoreRef storeRef) throws InvalidStoreRefException + { + Store store = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier()); + if (store == null) + { + throw new InvalidStoreRefException("Store does not exist", storeRef); + } + // get the root + Node node = store.getRootNode(); + if (node == null) + { + throw new InvalidStoreRefException("Store does not have a root node", storeRef); + } + NodeRef nodeRef = node.getNodeRef(); + // done + return nodeRef; + } + + /** + * @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); + } + + /** + * @see org.alfresco.service.cmr.repository.NodeService#createNode(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName, org.alfresco.service.namespace.QName, java.util.Map) + */ + public ChildAssociationRef createNode( + NodeRef parentRef, + QName assocTypeQName, + QName assocQName, + QName nodeTypeQName, + Map properties) + { + Assert.notNull(parentRef); + Assert.notNull(assocTypeQName); + Assert.notNull(assocQName); + + // null property map is allowed + if (properties == null) + { + properties = new HashMap(); + } + else + { + // Copy the incomming property map since we may need to modify it later + properties = new HashMap(properties); + } + + // Invoke policy behaviour + invokeBeforeUpdateNode(parentRef); + invokeBeforeCreateNode(parentRef, assocTypeQName, assocQName, nodeTypeQName); + + // get the store that the parent belongs to + StoreRef storeRef = parentRef.getStoreRef(); + Store store = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier()); + if (store == null) + { + throw new RuntimeException("No store found for parent node: " + parentRef); + } + + // check the node type + TypeDefinition nodeTypeDef = dictionaryService.getType(nodeTypeQName); + if (nodeTypeDef == null) + { + throw new InvalidTypeException(nodeTypeQName); + } + + // get/generate an ID for the node + String newId = generateGuid(properties); + + // create the node instance + Node childNode = nodeDaoService.newNode(store, newId, nodeTypeQName); + + // get the parent node + Node parentNode = getNodeNotNull(parentRef); + + // Set the default property values + addDefaultPropertyValues(nodeTypeDef, properties); + + // Add the default aspects to the node + addDefaultAspects(nodeTypeDef, childNode, properties); + + // set the properties - it is a new node so only set properties if there are any + Map propertiesBefore = getPropertiesImpl(childNode); + Map propertiesAfter = null; + if (properties.size() > 0) + { + propertiesAfter = setPropertiesImpl(childNode, properties); + } + + // create the association + ChildAssoc childAssoc = nodeDaoService.newChildAssoc( + parentNode, + childNode, + true, + assocTypeQName, + assocQName); + setChildUniqueName(childNode); // ensure uniqueness + ChildAssociationRef childAssocRef = childAssoc.getChildAssocRef(); + + // Invoke policy behaviour + invokeOnCreateNode(childAssocRef); + invokeOnUpdateNode(parentRef); + if (propertiesAfter != null) + { + invokeOnUpdateProperties(childAssocRef.getChildRef(), propertiesBefore, propertiesAfter); + } + + // done + return childAssocRef; + } + + /** + * Add the default aspects to a given node + * + * @param nodeTypeDef + */ + private void addDefaultAspects(ClassDefinition classDefinition, Node node, Map properties) + { + NodeRef nodeRef = node.getNodeRef(); + + // get the mandatory aspects for the node type + List defaultAspectDefs = classDefinition.getDefaultAspects(); + + // add all the aspects to the node + Set nodeAspects = node.getAspects(); + for (AspectDefinition defaultAspectDef : defaultAspectDefs) + { + invokeBeforeAddAspect(nodeRef, defaultAspectDef.getName()); + nodeAspects.add(defaultAspectDef.getName()); + addDefaultPropertyValues(defaultAspectDef, properties); + invokeOnAddAspect(nodeRef, defaultAspectDef.getName()); + + // Now add any default aspects for this aspect + addDefaultAspects(defaultAspectDef, node, properties); + } + } + + /** + * Drops the old primary association and creates a new one + */ + public ChildAssociationRef moveNode( + NodeRef nodeToMoveRef, + NodeRef newParentRef, + QName assocTypeQName, + QName assocQName) + throws InvalidNodeRefException + { + Assert.notNull(nodeToMoveRef); + Assert.notNull(newParentRef); + Assert.notNull(assocTypeQName); + Assert.notNull(assocQName); + + // check the node references + Node nodeToMove = getNodeNotNull(nodeToMoveRef); + Node newParentNode = getNodeNotNull(newParentRef); + // get the primary parent assoc + ChildAssoc oldAssoc = nodeDaoService.getPrimaryParentAssoc(nodeToMove); + ChildAssociationRef oldAssocRef = oldAssoc.getChildAssocRef(); + // get the old parent + Node oldParentNode = oldAssoc.getParent(); + + boolean movingStore = !nodeToMoveRef.getStoreRef().equals(newParentRef.getStoreRef()); + + // data needed for policy invocation + QName nodeToMoveTypeQName = nodeToMove.getTypeQName(); + Set nodeToMoveAspects = nodeToMove.getAspects(); + + // Invoke policy behaviour + if (movingStore) + { + invokeBeforeDeleteNode(nodeToMoveRef); + invokeBeforeCreateNode(newParentRef, assocTypeQName, assocQName, nodeToMoveTypeQName); + } + else + { + invokeBeforeDeleteChildAssociation(oldAssocRef); + invokeBeforeCreateChildAssociation(newParentRef, nodeToMoveRef, assocTypeQName, assocQName); + invokeBeforeUpdateNode(oldParentNode.getNodeRef()); // old parent will be updated + invokeBeforeUpdateNode(newParentRef); // new parent ditto + } + + // remove the child assoc from the old parent + // don't cascade as we will still need the node afterwards + nodeDaoService.deleteChildAssoc(oldAssoc, false); + + // create a new assoc + ChildAssoc newAssoc = nodeDaoService.newChildAssoc( + newParentNode, + nodeToMove, + true, + assocTypeQName, + assocQName); + setChildUniqueName(nodeToMove); // ensure uniqueness + ChildAssociationRef newAssocRef = newAssoc.getChildAssocRef(); + + // If the node is moving stores, then drag the node hierarchy with it + if (movingStore) + { + // do the move + Store newStore = newParentNode.getStore(); + moveNodeToStore(nodeToMove, newStore); + // the node reference will have changed too + nodeToMoveRef = nodeToMove.getNodeRef(); + } + + // check that no cyclic relationships have been created + getPaths(nodeToMoveRef, false); + + // invoke policy behaviour + if (movingStore) + { + // TODO for now indicate that the node has been archived to prevent the version history from being removed + // in the future a onMove policy could be added and remove the need for onDelete and onCreate to be fired here + invokeOnDeleteNode(oldAssocRef, nodeToMoveTypeQName, nodeToMoveAspects, true); + invokeOnCreateNode(newAssoc.getChildAssocRef()); + } + else + { + invokeOnCreateChildAssociation(newAssoc.getChildAssocRef()); + invokeOnDeleteChildAssociation(oldAssoc.getChildAssocRef()); + invokeOnUpdateNode(oldParentNode.getNodeRef()); + invokeOnUpdateNode(newParentRef); + } + invokeOnMoveNode(oldAssocRef, newAssocRef); + + // update the node status + nodeDaoService.recordChangeId(nodeToMoveRef); + + // done + return newAssoc.getChildAssocRef(); + } + + public void setChildAssociationIndex(ChildAssociationRef childAssocRef, int index) + { + // get nodes + Node parentNode = getNodeNotNull(childAssocRef.getParentRef()); + Node childNode = getNodeNotNull(childAssocRef.getChildRef()); + + ChildAssoc assoc = nodeDaoService.getChildAssoc( + parentNode, + childNode, + childAssocRef.getTypeQName(), + childAssocRef.getQName()); + if (assoc == null) + { + throw new InvalidChildAssociationRefException("Unable to set child association index: \n" + + " assoc: " + childAssocRef + "\n" + + " index: " + index, + childAssocRef); + } + // set the index + assoc.setIndex(index); + } + + public QName getType(NodeRef nodeRef) throws InvalidNodeRefException + { + Node node = getNodeNotNull(nodeRef); + return node.getTypeQName(); + } + + /** + * @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); + } + + // Invoke policies + invokeBeforeUpdateNode(nodeRef); + + // Get the node and set the new type + Node node = getNodeNotNull(nodeRef); + node.setTypeQName(typeQName); + + // Add the default aspects to the node (update the properties with any new default values) + Map properties = this.getPropertiesImpl(node); + addDefaultAspects(nodeTypeDef, node, properties); + this.setProperties(nodeRef, properties); + + // Invoke policies + invokeOnUpdateNode(nodeRef); + } + + /** + * @see Node#getAspects() + */ + public void addAspect( + NodeRef nodeRef, + QName aspectTypeQName, + Map 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); + } + + // Invoke policy behaviours + invokeBeforeUpdateNode(nodeRef); + invokeBeforeAddAspect(nodeRef, aspectTypeQName); + + Node node = getNodeNotNull(nodeRef); + + // attach the properties to the current node properties + Map nodeProperties = getPropertiesImpl(node); + + if (aspectProperties != null) + { + nodeProperties.putAll(aspectProperties); + } + + // Set any default property values that appear on the aspect + addDefaultPropertyValues(aspectDef, nodeProperties); + + // Add any dependant aspect + addDefaultAspects(aspectDef, node, nodeProperties); + + // Set the property values back on the node + setProperties(nodeRef, nodeProperties); + + // physically attach the aspect to the node + if (node.getAspects().add(aspectTypeQName) == true) + { + // Invoke policy behaviours + invokeOnUpdateNode(nodeRef); + invokeOnAddAspect(nodeRef, aspectTypeQName); + + // update the node status + nodeDaoService.recordChangeId(nodeRef); + } + } + + /** + * @see Node#getAspects() + */ + public void removeAspect(NodeRef nodeRef, QName aspectTypeQName) + throws InvalidNodeRefException, InvalidAspectException + { + // Invoke policy behaviours + invokeBeforeUpdateNode(nodeRef); + invokeBeforeRemoveAspect(nodeRef, aspectTypeQName); + + // get the aspect + AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName); + if (aspectDef == null) + { + throw new InvalidAspectException(aspectTypeQName); + } + // get the node + Node node = getNodeNotNull(nodeRef); + + // remove the aspect, if present + boolean removed = node.getAspects().remove(aspectTypeQName); + // if the aspect was present, remove the associated properties + if (removed) + { + Map nodeProperties = node.getProperties(); + Map propertyDefs = aspectDef.getProperties(); + for (QName propertyName : propertyDefs.keySet()) + { + nodeProperties.remove(propertyName); + } + + // Invoke policy behaviours + invokeOnUpdateNode(nodeRef); + invokeOnRemoveAspect(nodeRef, aspectTypeQName); + + // update the node status + nodeDaoService.recordChangeId(nodeRef); + } + } + + /** + * Performs a check on the set of node aspects + * + * @see Node#getAspects() + */ + public boolean hasAspect(NodeRef nodeRef, QName aspectRef) throws InvalidNodeRefException, InvalidAspectException + { + Node node = getNodeNotNull(nodeRef); + Set aspectQNames = node.getAspects(); + boolean hasAspect = aspectQNames.contains(aspectRef); + // done + return hasAspect; + } + + public Set getAspects(NodeRef nodeRef) throws InvalidNodeRefException + { + Node node = getNodeNotNull(nodeRef); + Set aspectQNames = node.getAspects(); + // copy the set to ensure initialization + Set ret = new HashSet(aspectQNames.size()); + ret.addAll(aspectQNames); + // done + return ret; + } + + public void deleteNode(NodeRef nodeRef) + { + boolean isArchivedNode = false; + boolean requiresDelete = false; + + // Invoke policy behaviours + invokeBeforeDeleteNode(nodeRef); + + // get the node + Node node = getNodeNotNull(nodeRef); + // get the primary parent-child relationship before it is gone + ChildAssociationRef childAssocRef = getPrimaryParent(nodeRef); + // get type and aspect QNames as they will be unavailable after the delete + QName nodeTypeQName = node.getTypeQName(); + Set nodeAspectQNames = node.getAspects(); + + // check if we need to archive the node + StoreRef archiveStoreRef = null; + if (nodeAspectQNames.contains(ContentModel.ASPECT_TEMPORARY)) + { + // the node has the temporary aspect meaning + // it can not be archived + requiresDelete = true; + isArchivedNode = false; + } + else + { + StoreRef storeRef = nodeRef.getStoreRef(); + archiveStoreRef = storeArchiveMap.getArchiveMap().get(storeRef); + // get the type and check if we need archiving + TypeDefinition typeDef = dictionaryService.getType(node.getTypeQName()); + if (typeDef == null || !typeDef.isArchive() || archiveStoreRef == null) + { + requiresDelete = true; + } + } + + if (requiresDelete) + { + // perform a normal deletion + nodeDaoService.deleteNode(node, true); + isArchivedNode = false; + } + else + { + // archive it + archiveNode(nodeRef, archiveStoreRef); + isArchivedNode = true; + } + + // Invoke policy behaviours + invokeOnDeleteNode(childAssocRef, nodeTypeQName, nodeAspectQNames, isArchivedNode); + } + + public ChildAssociationRef addChild(NodeRef parentRef, NodeRef childRef, QName assocTypeQName, QName assocQName) + { + // Invoke policy behaviours + invokeBeforeUpdateNode(parentRef); + invokeBeforeCreateChildAssociation(parentRef, childRef, assocTypeQName, assocQName); + + // get the parent node and ensure that it is a container node + Node parentNode = getNodeNotNull(parentRef); + // get the child node + Node childNode = getNodeNotNull(childRef); + // make the association + ChildAssoc assoc = nodeDaoService.newChildAssoc( + parentNode, + childNode, + false, + assocTypeQName, + assocQName); + // ensure name uniqueness + setChildUniqueName(childNode); + ChildAssociationRef assocRef = assoc.getChildAssocRef(); + NodeRef childNodeRef = assocRef.getChildRef(); + + // check that the child addition of the child has not created a cyclic relationship + // this functionality is provided for free in getPath + getPaths(childNodeRef, false); + + // Invoke policy behaviours + invokeOnCreateChildAssociation(assocRef); + invokeOnUpdateNode(parentRef); + + return assoc.getChildAssocRef(); + } + + public void removeChild(NodeRef parentRef, NodeRef childRef) throws InvalidNodeRefException + { + Node parentNode = getNodeNotNull(parentRef); + Node childNode = getNodeNotNull(childRef); + Long childNodeId = childNode.getId(); + + // get all the child assocs + ChildAssociationRef primaryAssocRef = null; + Collection assocs = nodeDaoService.getChildAssocs(parentNode); + assocs = new HashSet(assocs); // copy set as we will be modifying it + for (ChildAssoc assoc : assocs) + { + if (!assoc.getChild().getId().equals(childNodeId)) + { + continue; // not a matching association + } + ChildAssociationRef assocRef = assoc.getChildAssocRef(); + // Is this a primary association? + if (assoc.getIsPrimary()) + { + // keep the primary associaton for last + primaryAssocRef = assocRef; + } + else + { + // delete the association instance - it is not primary + invokeBeforeDeleteChildAssociation(assocRef); + nodeDaoService.deleteChildAssoc(assoc, true); // cascade + invokeOnDeleteChildAssociation(assocRef); + } + } + // remove the child if the primary association was a match + if (primaryAssocRef != null) + { + deleteNode(primaryAssocRef.getChildRef()); + } + + // Invoke policy behaviours + invokeOnUpdateNode(parentRef); + + // done + } + + public Map getProperties(NodeRef nodeRef) throws InvalidNodeRefException + { + Node node = getNodeNotNull(nodeRef); + return getPropertiesImpl(node); + } + + private Map getPropertiesImpl(Node node) throws InvalidNodeRefException + { + NodeRef nodeRef = node.getNodeRef(); + + Map nodeProperties = node.getProperties(); + Map ret = new HashMap(nodeProperties.size()); + // copy values + for (Map.Entry entry: nodeProperties.entrySet()) + { + QName propertyQName = entry.getKey(); + PropertyValue propertyValue = entry.getValue(); + // get the property definition + PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); + // convert to the correct type + Serializable value = makeSerializableValue(propertyDef, propertyValue); + // copy across + ret.put(propertyQName, value); + } + // spoof referencable properties + addReferencableProperties(nodeRef, node.getId(), ret); + // done + return ret; + } + + public Serializable getProperty(NodeRef nodeRef, QName qname) throws InvalidNodeRefException + { + // 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(); + } + + // get the property from the node + Node node = getNodeNotNull(nodeRef); + + if (qname.equals(ContentModel.PROP_NODE_DBID)) + { + return node.getId(); + } + + Map properties = node.getProperties(); + PropertyValue propertyValue = properties.get(qname); + + // get the property definition + PropertyDefinition propertyDef = dictionaryService.getProperty(qname); + // convert to the correct type + Serializable value = makeSerializableValue(propertyDef, propertyValue); + // done + return value; + } + + /** + * Ensures that all required properties are present on the node and copies the + * property values to the Node. + *

+ * To remove a property, remove it from the map before calling this method. + * Null-valued properties are allowed. + *

+ * 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 properties) throws InvalidNodeRefException + { + Node node = getNodeNotNull(nodeRef); + + // Invoke policy behaviours + invokeBeforeUpdateNode(nodeRef); + + // Do the set properties + Map propertiesBefore = getPropertiesImpl(node); + Map propertiesAfter = setPropertiesImpl(node, properties); + + setChildUniqueName(node); // ensure uniqueness + + // Invoke policy behaviours + invokeOnUpdateNode(nodeRef); + invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter); + } + + /** + * Does the work of setting the property values. Returns a map containing the state of the properties after the set + * operation is complete. + * + * @param node the node + * @param properties the map of property values + * @return the map of property values after the set operation is complete + * @throws InvalidNodeRefException + */ + private Map setPropertiesImpl(Node node, Map properties) throws InvalidNodeRefException + { + ParameterCheck.mandatory("properties", properties); + + // remove referencable properties + removeReferencableProperties(properties); + + // copy properties onto node + Map nodeProperties = node.getProperties(); + nodeProperties.clear(); + + // check the property type and copy the values across + for (QName propertyQName : properties.keySet()) + { + PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); + Serializable value = properties.get(propertyQName); + // get a persistable value + PropertyValue propertyValue = makePropertyValue(propertyDef, value); + nodeProperties.put(propertyQName, propertyValue); + } + + // update the node status + NodeRef nodeRef = node.getNodeRef(); + nodeDaoService.recordChangeId(nodeRef); + + // Return the properties after + return Collections.unmodifiableMap(properties); + } + + /** + * 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 + { + Assert.notNull(qname); + + // Invoke policy behaviours + invokeBeforeUpdateNode(nodeRef); + + // get the node + Node node = getNodeNotNull(nodeRef); + + // Do the set operation + Map propertiesBefore = getPropertiesImpl(node); + Map propertiesAfter = setPropertyImpl(node, qname, value); + + if (qname.equals(ContentModel.PROP_NAME)) + { + setChildUniqueName(node); // ensure uniqueness + } + + // Invoke policy behaviours + invokeOnUpdateNode(nodeRef); + invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter); + } + + /** + * Does the work of setting a property value. Returns the values of the properties after the set operation is + * complete. + * + * @param node the node + * @param qname the qname of the property + * @param value the value of the property + * @return the values of the properties after the set operation is complete + * @throws InvalidNodeRefException + */ + public Map setPropertyImpl(Node node, QName qname, Serializable value) throws InvalidNodeRefException + { + NodeRef nodeRef = node.getNodeRef(); + + Map properties = node.getProperties(); + PropertyDefinition propertyDef = dictionaryService.getProperty(qname); + // get a persistable value + PropertyValue propertyValue = makePropertyValue(propertyDef, value); + properties.put(qname, propertyValue); + + // update the node status + nodeDaoService.recordChangeId(nodeRef); + + return getPropertiesImpl(node); + } + + /** + * Transforms {@link Node#getParentAssocs()} to a new collection + */ + public Collection getParents(NodeRef nodeRef) throws InvalidNodeRefException + { + Node node = getNodeNotNull(nodeRef); + // get the assocs pointing to it + Collection parentAssocs = node.getParentAssocs(); + // list of results + Collection results = new ArrayList(parentAssocs.size()); + for (ChildAssoc assoc : parentAssocs) + { + // get the parent + Node parentNode = assoc.getParent(); + results.add(parentNode.getNodeRef()); + } + // done + return results; + } + + /** + * Filters out any associations if their qname is not a match to the given pattern. + */ + public List getParentAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern, QNamePattern qnamePattern) + { + Node node = getNodeNotNull(nodeRef); + // get the assocs pointing to it + Collection parentAssocs = node.getParentAssocs(); + // shortcut if there are no assocs + if (parentAssocs.size() == 0) + { + return Collections.emptyList(); + } + // list of results + List results = new ArrayList(parentAssocs.size()); + for (ChildAssoc assoc : parentAssocs) + { + // does the qname match the pattern? + if (!qnamePattern.isMatch(assoc.getQname()) || !typeQNamePattern.isMatch(assoc.getTypeQName())) + { + // no match - ignore + continue; + } + results.add(assoc.getChildAssocRef()); + } + // done + return results; + } + + /** + * Filters out any associations if their qname is not a match to the given pattern. + */ + public List getChildAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern, QNamePattern qnamePattern) + { + Node node = getNodeNotNull(nodeRef); + // get the assocs pointing from it + Collection childAssocRefs = nodeDaoService.getChildAssocRefs(node); + // shortcut if there are no assocs + if (childAssocRefs.size() == 0) + { + return Collections.emptyList(); + } + // sort results + ArrayList orderedList = new ArrayList(childAssocRefs); + Collections.sort(orderedList); + + // list of results + int nthSibling = 0; + Iterator iterator = orderedList.iterator(); + while(iterator.hasNext()) + { + ChildAssociationRef childAssocRef = iterator.next(); + // does the qname match the pattern? + if (!qnamePattern.isMatch(childAssocRef.getQName()) || !typeQNamePattern.isMatch(childAssocRef.getTypeQName())) + { + // no match - remove + iterator.remove(); + } + else + { + childAssocRef.setNthSibling(nthSibling); + nthSibling++; + } + } + // done + return orderedList; + } + + public NodeRef getChildByName(NodeRef nodeRef, QName assocTypeQName, String childName) + { + Node node = getNodeNotNull(nodeRef); + ChildAssoc childAssoc = nodeDaoService.getChildAssoc(node, assocTypeQName, childName); + if (childAssoc != null) + { + return childAssoc.getChild().getNodeRef(); + } + else + { + return null; + } + } + + public ChildAssociationRef getPrimaryParent(NodeRef nodeRef) throws InvalidNodeRefException + { + Node node = getNodeNotNull(nodeRef); + // get the primary parent assoc + ChildAssoc assoc = nodeDaoService.getPrimaryParentAssoc(node); + + // done - the assoc may be null for a root node + ChildAssociationRef assocRef = null; + if (assoc == null) + { + assocRef = new ChildAssociationRef(null, null, null, nodeRef); + } + else + { + assocRef = assoc.getChildAssocRef(); + } + return assocRef; + } + + public AssociationRef createAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName) + throws InvalidNodeRefException, AssociationExistsException + { + // Invoke policy behaviours + invokeBeforeUpdateNode(sourceRef); + + Node sourceNode = getNodeNotNull(sourceRef); + Node targetNode = getNodeNotNull(targetRef); + // see if it exists + NodeAssoc assoc = nodeDaoService.getNodeAssoc(sourceNode, targetNode, assocTypeQName); + if (assoc != null) + { + throw new AssociationExistsException(sourceRef, targetRef, assocTypeQName); + } + // we are sure that the association doesn't exist - make it + assoc = nodeDaoService.newNodeAssoc(sourceNode, targetNode, assocTypeQName); + AssociationRef assocRef = assoc.getNodeAssocRef(); + + // Invoke policy behaviours + invokeOnUpdateNode(sourceRef); + invokeOnCreateAssociation(assocRef); + + return assocRef; + } + + public void removeAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName) + throws InvalidNodeRefException + { + Node sourceNode = getNodeNotNull(sourceRef); + Node targetNode = getNodeNotNull(targetRef); + // get the association + NodeAssoc assoc = nodeDaoService.getNodeAssoc(sourceNode, targetNode, assocTypeQName); + if (assoc == null) + { + // nothing to remove + return; + } + AssociationRef assocRef = assoc.getNodeAssocRef(); + + // Invoke policy behaviours + invokeBeforeUpdateNode(sourceRef); + + // delete it + nodeDaoService.deleteNodeAssoc(assoc); + + // Invoke policy behaviours + invokeOnUpdateNode(sourceRef); + invokeOnDeleteAssociation(assocRef); + } + + public List getTargetAssocs(NodeRef sourceRef, QNamePattern qnamePattern) + { + Node sourceNode = getNodeNotNull(sourceRef); + // get all assocs to target + Collection assocs = nodeDaoService.getTargetNodeAssocs(sourceNode); + List nodeAssocRefs = new ArrayList(assocs.size()); + for (NodeAssoc assoc : assocs) + { + // check qname pattern + if (!qnamePattern.isMatch(assoc.getTypeQName())) + { + continue; // the assoc name doesn't match the pattern given + } + nodeAssocRefs.add(assoc.getNodeAssocRef()); + } + // done + return nodeAssocRefs; + } + + public List getSourceAssocs(NodeRef targetRef, QNamePattern qnamePattern) + { + Node targetNode = getNodeNotNull(targetRef); + // get all assocs to source + Collection assocs = nodeDaoService.getSourceNodeAssocs(targetNode); + List nodeAssocRefs = new ArrayList(assocs.size()); + for (NodeAssoc assoc : assocs) + { + // check qname pattern + if (!qnamePattern.isMatch(assoc.getTypeQName())) + { + continue; // the assoc name doesn't match the pattern given + } + nodeAssocRefs.add(assoc.getNodeAssocRef()); + } + // done + return nodeAssocRefs; + } + + /** + * Recursive method used to build up paths from a given node to the root. + *

+ * Whilst walking up the hierarchy to the root, some nodes may have a root aspect. + * Everytime one of these is encountered, a new path is farmed off, but the method + * continues to walk up the hierarchy. + * + * @param currentNode the node to start from, i.e. the child node to work upwards from + * @param currentPath the path from the current node to the descendent that we started from + * @param completedPaths paths that have reached the root are added to this collection + * @param assocStack the parent-child relationships traversed whilst building the path. + * Used to detected cyclic relationships. + * @param primaryOnly true if only the primary parent association must be traversed. + * If this is true, then the only root is the top level node having no parents. + * @throws CyclicChildRelationshipException + */ + private void prependPaths( + final Node currentNode, + final Path currentPath, + Collection completedPaths, + Stack assocStack, + boolean primaryOnly) + throws CyclicChildRelationshipException + { + NodeRef currentNodeRef = currentNode.getNodeRef(); + // get the parent associations of the given node + Collection parentAssocs = currentNode.getParentAssocs(); + // does the node have parents + boolean hasParents = parentAssocs.size() > 0; + // does the current node have a root aspect? + boolean isRoot = hasAspect(currentNodeRef, ContentModel.ASPECT_ROOT); + boolean isStoreRoot = currentNode.getTypeQName().equals(ContentModel.TYPE_STOREROOT); + + // look for a root. If we only want the primary root, then ignore all but the top-level root. + if (isRoot && !(primaryOnly && hasParents)) // exclude primary search with parents present + { + // create a one-sided assoc ref for the root node and prepend to the stack + // this effectively spoofs the fact that the current node is not below the root + // - we put this assoc in as the first assoc in the path must be a one-sided + // reference pointing to the root node + ChildAssociationRef assocRef = new ChildAssociationRef( + null, + null, + null, + getRootNode(currentNode.getNodeRef().getStoreRef())); + // create a path to save and add the 'root' assoc + Path pathToSave = new Path(); + Path.ChildAssocElement first = null; + for (Path.Element element: currentPath) + { + if (first == null) + { + first = (Path.ChildAssocElement) element; + } + else + { + pathToSave.append(element); + } + } + if (first != null) + { + // mimic an association that would appear if the current node was below + // the root node + // or if first beneath the root node it will make the real thing + ChildAssociationRef updateAssocRef = new ChildAssociationRef( + isStoreRoot ? ContentModel.ASSOC_CHILDREN : first.getRef().getTypeQName(), + getRootNode(currentNode.getNodeRef().getStoreRef()), + first.getRef().getQName(), + first.getRef().getChildRef()); + Path.Element newFirst = new Path.ChildAssocElement(updateAssocRef); + pathToSave.prepend(newFirst); + } + + Path.Element element = new Path.ChildAssocElement(assocRef); + pathToSave.prepend(element); + + // store the path just built + completedPaths.add(pathToSave); + } + + if (parentAssocs.size() == 0 && !isRoot) + { + throw new RuntimeException("Node without parents does not have root aspect: " + + currentNodeRef); + } + // walk up each parent association + for (ChildAssoc assoc : parentAssocs) + { + // does the association already exist in the stack + if (assocStack.contains(assoc)) + { + // the association was present already + throw new CyclicChildRelationshipException( + "Cyclic parent-child relationship detected: \n" + + " current node: " + currentNode + "\n" + + " current path: " + currentPath + "\n" + + " next assoc: " + assoc, + assoc); + } + // do we consider only primary assocs? + if (primaryOnly && !assoc.getIsPrimary()) + { + continue; + } + // build a path element + NodeRef parentRef = assoc.getParent().getNodeRef(); + QName qname = assoc.getQname(); + NodeRef childRef = assoc.getChild().getNodeRef(); + boolean isPrimary = assoc.getIsPrimary(); + // build a real association reference + ChildAssociationRef assocRef = new ChildAssociationRef(assoc.getTypeQName(), parentRef, qname, childRef, isPrimary, -1); + // Ordering is not important here: We are building distinct paths upwards + Path.Element element = new Path.ChildAssocElement(assocRef); + // create a new path that builds on the current path + Path path = new Path(); + path.append(currentPath); + // prepend element + path.prepend(element); + // get parent node + Node parentNode = assoc.getParent(); + + // push the assoc stack, recurse and pop + assocStack.push(assoc); + prependPaths(parentNode, path, completedPaths, assocStack, primaryOnly); + assocStack.pop(); + } + // done + } + + /** + * @see #getPaths(NodeRef, boolean) + * @see #prependPaths(Node, Path, Collection, Stack, boolean) + */ + public Path getPath(NodeRef nodeRef) throws InvalidNodeRefException + { + List 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 primaryOnly == true, checks that there is exactly + * one path. + * @see #prependPaths(Node, Path, Collection, Stack, boolean) + */ + public List getPaths(NodeRef nodeRef, boolean primaryOnly) throws InvalidNodeRefException + { + // get the starting node + Node node = getNodeNotNull(nodeRef); + // create storage for the paths - only need 1 bucket if we are looking for the primary path + List paths = new ArrayList(primaryOnly ? 1 : 10); + // create an empty current path to start from + Path currentPath = new Path(); + // create storage for touched associations + Stack assocStack = new Stack(); + // call recursive method to sort it out + prependPaths(node, currentPath, paths, assocStack, primaryOnly); + + // check that for the primary only case we have exactly one path + if (primaryOnly && paths.size() != 1) + { + throw new RuntimeException("Node has " + paths.size() + " primary paths: " + nodeRef); + } + + // done + if (loggerPaths.isDebugEnabled()) + { + StringBuilder sb = new StringBuilder(256); + if (primaryOnly) + { + sb.append("Primary paths"); + } + else + { + sb.append("Paths"); + } + sb.append(" for node ").append(nodeRef); + for (Path path : paths) + { + sb.append("\n").append(" ").append(path); + } + loggerPaths.debug(sb); + } + return paths; + } + + private void archiveNode(NodeRef nodeRef, StoreRef archiveStoreRef) + { + Node node = getNodeNotNull(nodeRef); + ChildAssoc primaryParentAssoc = nodeDaoService.getPrimaryParentAssoc(node); + + // add the aspect + Set aspects = node.getAspects(); + aspects.add(ContentModel.ASPECT_ARCHIVED); + Map properties = node.getProperties(); + PropertyValue archivedByProperty = makePropertyValue( + dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_BY), + AuthenticationUtil.getCurrentUserName()); + properties.put(ContentModel.PROP_ARCHIVED_BY, archivedByProperty); + PropertyValue archivedDateProperty = makePropertyValue( + dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_DATE), + new Date()); + properties.put(ContentModel.PROP_ARCHIVED_DATE, archivedDateProperty); + PropertyValue archivedPrimaryParentNodeRefProperty = makePropertyValue( + dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC), + primaryParentAssoc.getChildAssocRef()); + properties.put(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC, archivedPrimaryParentNodeRefProperty); + PropertyValue originalOwnerProperty = properties.get(ContentModel.PROP_OWNER); + PropertyValue originalCreatorProperty = properties.get(ContentModel.PROP_CREATOR); + if (originalOwnerProperty != null || originalCreatorProperty != null) + { + properties.put( + ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER, + originalOwnerProperty != null ? originalOwnerProperty : originalCreatorProperty); + } + + // change the node ownership + aspects.add(ContentModel.ASPECT_OWNABLE); + PropertyValue newOwnerProperty = makePropertyValue( + dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER), + AuthenticationUtil.getCurrentUserName()); + properties.put(ContentModel.PROP_OWNER, newOwnerProperty); + + // move the node + NodeRef archiveStoreRootNodeRef = getRootNode(archiveStoreRef); + moveNode( + nodeRef, + archiveStoreRootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedItem")); + + // get the IDs of all the node's primary children, including its own + Map nodesById = getNodeHierarchy(node, null); + + // Archive all the associations between the archived nodes and non-archived nodes + for (Node nodeToArchive : nodesById.values()) + { + archiveAssocs(nodeToArchive, nodesById); + } + + // the node reference has changed due to the store move + nodeRef = node.getNodeRef(); + } + + /** + * Performs all the necessary housekeeping involved in changing a node's store. + * This method cascades down through all the primary children of the node as + * well. + * + * @param node the node whose store is changing + * @param store the new store for the node + */ + private void moveNodeToStore(Node node, Store store) + { + // get the IDs of all the node's primary children, including its own + Map nodesById = getNodeHierarchy(node, null); + + // move each node into the archive store + for (Node nodeToMove : nodesById.values()) + { + NodeRef oldNodeRef = nodeToMove.getNodeRef(); + nodeToMove.setStore(store); + NodeRef newNodeRef = nodeToMove.getNodeRef(); + + String txnId = AlfrescoTransactionSupport.getTransactionId(); + // update old status + NodeStatus oldNodeStatus = nodeDaoService.getNodeStatus(oldNodeRef, true); + oldNodeStatus.setNode(null); + oldNodeStatus.getTransaction().setChangeTxnId(txnId); + // create the new status + NodeStatus newNodeStatus = nodeDaoService.getNodeStatus(newNodeRef, true); + newNodeStatus.setNode(nodeToMove); + newNodeStatus.getTransaction().setChangeTxnId(txnId); + } + } + + /** + * Fill the map of all primary children below the given node. + * The given node will be added to the map and the method is recursive + * to all primary children. + * + * @param node the start of the hierarchy + * @param nodesById a map of nodes that will be reused as the return value + * @return Returns a map of nodes in the hierarchy keyed by their IDs + */ + private Map getNodeHierarchy(Node node, Map nodesById) + { + if (nodesById == null) + { + nodesById = new HashMap(23); + } + + Long id = node.getId(); + if (nodesById.containsKey(id)) + { + // this ID was already added - circular reference + logger.warn("Circular hierarchy found including node " + id); + return nodesById; + } + // add the node to the map + nodesById.put(id, node); + // recurse into the primary children + Collection childAssocs = nodeDaoService.getChildAssocs(node); + for (ChildAssoc childAssoc : childAssocs) + { + // cascade into primary associations + if (childAssoc.getIsPrimary()) + { + Node primaryChild = childAssoc.getChild(); + nodesById = getNodeHierarchy(primaryChild, nodesById); + } + } + return nodesById; + } + + /** + * Archive all associations to and from the given node, with the + * exception of associations to or from nodes in the given map. + *

+ * Primary parent associations are also ignored. + * + * @param node the node whose associations must be archived + * @param nodesById a map of nodes partaking in the archival process + */ + private void archiveAssocs(Node node, Map nodesById) + { + List childAssocsToDelete = new ArrayList(5); + // child associations + ArrayList archivedChildAssocRefs = new ArrayList(5); + Collection childAssocs = nodeDaoService.getChildAssocs(node); + for (ChildAssoc assoc : childAssocs) + { + Long relatedNodeId = assoc.getChild().getId(); + if (nodesById.containsKey(relatedNodeId)) + { + // a sibling in the archive process + continue; + } + childAssocsToDelete.add(assoc); + archivedChildAssocRefs.add(assoc.getChildAssocRef()); + } + // parent associations + ArrayList archivedParentAssocRefs = new ArrayList(5); + for (ChildAssoc assoc : node.getParentAssocs()) + { + Long relatedNodeId = assoc.getParent().getId(); + if (nodesById.containsKey(relatedNodeId)) + { + // a sibling in the archive process + continue; + } + else if (assoc.getIsPrimary()) + { + // ignore the primary parent as this is handled more specifically + continue; + } + childAssocsToDelete.add(assoc); + archivedParentAssocRefs.add(assoc.getChildAssocRef()); + } + + List nodeAssocsToDelete = new ArrayList(5); + // source associations + ArrayList archivedSourceAssocRefs = new ArrayList(5); + for (NodeAssoc assoc : nodeDaoService.getSourceNodeAssocs(node)) + { + Long relatedNodeId = assoc.getSource().getId(); + if (nodesById.containsKey(relatedNodeId)) + { + // a sibling in the archive process + continue; + } + nodeAssocsToDelete.add(assoc); + archivedSourceAssocRefs.add(assoc.getNodeAssocRef()); + } + // target associations + ArrayList archivedTargetAssocRefs = new ArrayList(5); + for (NodeAssoc assoc : nodeDaoService.getTargetNodeAssocs(node)) + { + Long relatedNodeId = assoc.getTarget().getId(); + if (nodesById.containsKey(relatedNodeId)) + { + // a sibling in the archive process + continue; + } + nodeAssocsToDelete.add(assoc); + archivedTargetAssocRefs.add(assoc.getNodeAssocRef()); + } + // delete child assocs + for (ChildAssoc assoc : childAssocsToDelete) + { + nodeDaoService.deleteChildAssoc(assoc, false); + } + // delete node assocs + for (NodeAssoc assoc : nodeAssocsToDelete) + { + nodeDaoService.deleteNodeAssoc(assoc); + } + + // add archived aspect + node.getAspects().add(ContentModel.ASPECT_ARCHIVED_ASSOCS); + // set properties + Map properties = node.getProperties(); + + if (archivedParentAssocRefs.size() > 0) + { + PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS); + PropertyValue propertyValue = makePropertyValue(propertyDef, archivedParentAssocRefs); + properties.put(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS, propertyValue); + } + if (archivedChildAssocRefs.size() > 0) + { + PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS); + PropertyValue propertyValue = makePropertyValue(propertyDef, archivedChildAssocRefs); + properties.put(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS, propertyValue); + } + if (archivedSourceAssocRefs.size() > 0) + { + PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS); + PropertyValue propertyValue = makePropertyValue(propertyDef, archivedSourceAssocRefs); + properties.put(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS, propertyValue); + } + if (archivedTargetAssocRefs.size() > 0) + { + PropertyDefinition propertyDef = dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS); + PropertyValue propertyValue = makePropertyValue(propertyDef, archivedTargetAssocRefs); + properties.put(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS, propertyValue); + } + } + + public NodeRef getStoreArchiveNode(StoreRef storeRef) + { + StoreRef archiveStoreRef = storeArchiveMap.getArchiveMap().get(storeRef); + if (archiveStoreRef == null) + { + // no mapping for the given store + return null; + } + else + { + return getRootNode(archiveStoreRef); + } + } + + public NodeRef restoreNode(NodeRef archivedNodeRef, NodeRef destinationParentNodeRef, QName assocTypeQName, QName assocQName) + { + Node archivedNode = getNodeNotNull(archivedNodeRef); + Set aspects = archivedNode.getAspects(); + Map properties = archivedNode.getProperties(); + // the node must be a top-level archive node + if (!aspects.contains(ContentModel.ASPECT_ARCHIVED)) + { + throw new AlfrescoRuntimeException("The node to archive is not an archive node"); + } + ChildAssociationRef originalPrimaryParentAssocRef = (ChildAssociationRef) makeSerializableValue( + dictionaryService.getProperty(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC), + properties.get(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC)); + PropertyValue originalOwnerProperty = properties.get(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER); + // remove the aspect archived aspect + aspects.remove(ContentModel.ASPECT_ARCHIVED); + properties.remove(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC); + properties.remove(ContentModel.PROP_ARCHIVED_BY); + properties.remove(ContentModel.PROP_ARCHIVED_DATE); + properties.remove(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER); + + // restore the original ownership + if (originalOwnerProperty != null) + { + aspects.add(ContentModel.ASPECT_OWNABLE); + properties.put(ContentModel.PROP_OWNER, originalOwnerProperty); + } + + 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 + moveNode( + archivedNodeRef, + destinationParentNodeRef, + assocTypeQName, + assocQName); + + // get the IDs of all the node's primary children, including its own + Map restoredNodesById = getNodeHierarchy(archivedNode, null); + // Restore the archived associations, if required + for (Node restoredNode : restoredNodesById.values()) + { + restoreAssocs(restoredNode); + } + + // the node reference has changed due to the store move + NodeRef restoredNodeRef = archivedNode.getNodeRef(); + + // done + if (logger.isDebugEnabled()) + { + logger.debug("Restored node: \n" + + " original noderef: " + archivedNodeRef + "\n" + + " restored noderef: " + restoredNodeRef + "\n" + + " new parent: " + destinationParentNodeRef); + } + return restoredNodeRef; + } + + private void restoreAssocs(Node node) + { + NodeRef nodeRef = node.getNodeRef(); + // set properties + Map properties = node.getProperties(); + + // restore parent associations + Collection parentAssocRefs = (Collection) getProperty( + nodeRef, + ContentModel.PROP_ARCHIVED_PARENT_ASSOCS); + if (parentAssocRefs != null) + { + for (ChildAssociationRef assocRef : parentAssocRefs) + { + NodeRef parentNodeRef = assocRef.getParentRef(); + if (!exists(parentNodeRef)) + { + continue; + } + Node parentNode = getNodeNotNull(parentNodeRef); + // get the name to use for the unique child check + QName assocTypeQName = assocRef.getTypeQName(); + nodeDaoService.newChildAssoc( + parentNode, + node, + assocRef.isPrimary(), + assocTypeQName, + assocRef.getQName()); + } + properties.remove(ContentModel.PROP_ARCHIVED_PARENT_ASSOCS); + } + + // make sure that the node name uniqueness is enforced + setChildUniqueName(node); + + // restore child associations + Collection childAssocRefs = (Collection) getProperty( + nodeRef, + ContentModel.PROP_ARCHIVED_CHILD_ASSOCS); + if (childAssocRefs != null) + { + for (ChildAssociationRef assocRef : childAssocRefs) + { + NodeRef childNodeRef = assocRef.getChildRef(); + if (!exists(childNodeRef)) + { + continue; + } + Node childNode = getNodeNotNull(childNodeRef); + QName assocTypeQName = assocRef.getTypeQName(); + // get the name to use for the unique child check + nodeDaoService.newChildAssoc( + node, + childNode, + assocRef.isPrimary(), + assocTypeQName, + assocRef.getQName()); + // ensure that the name uniqueness is enforced for the child node + setChildUniqueName(childNode); + } + properties.remove(ContentModel.PROP_ARCHIVED_CHILD_ASSOCS); + } + // restore source associations + Collection sourceAssocRefs = (Collection) getProperty( + nodeRef, + ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS); + if (sourceAssocRefs != null) + { + for (AssociationRef assocRef : sourceAssocRefs) + { + NodeRef sourceNodeRef = assocRef.getSourceRef(); + if (!exists(sourceNodeRef)) + { + continue; + } + Node sourceNode = getNodeNotNull(sourceNodeRef); + nodeDaoService.newNodeAssoc(sourceNode, node, assocRef.getTypeQName()); + } + properties.remove(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS); + } + // restore target associations + Collection targetAssocRefs = (Collection) getProperty( + nodeRef, + ContentModel.PROP_ARCHIVED_TARGET_ASSOCS); + if (targetAssocRefs != null) + { + for (AssociationRef assocRef : targetAssocRefs) + { + NodeRef targetNodeRef = assocRef.getTargetRef(); + if (!exists(targetNodeRef)) + { + continue; + } + Node targetNode = getNodeNotNull(targetNodeRef); + nodeDaoService.newNodeAssoc(node, targetNode, assocRef.getTypeQName()); + } + properties.remove(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS); + } + // remove the aspect + node.getAspects().remove(ContentModel.ASPECT_ARCHIVED_ASSOCS); + } + + /** + * Checks the dictionary's definition of the association to assign a unique name to the child node. + * + * @param assocTypeQName the type of the child association + * @param childNode the child node being added. The name will be extracted from it, if necessary. + */ + private void setChildUniqueName(Node childNode) + { + // get the name property + Map properties = childNode.getProperties(); + PropertyValue nameValue = properties.get(ContentModel.PROP_NAME); + String useName = null; + if (nameValue == null) + { + // no name has been assigned, so assign the ID of the child node + useName = childNode.getUuid(); + } + else + { + useName = (String) nameValue.getValue(DataTypeDefinition.TEXT); + } + // get all the parent assocs + Collection parentAssocs = childNode.getParentAssocs(); + for (ChildAssoc assoc : parentAssocs) + { + QName assocTypeQName = assoc.getTypeQName(); + AssociationDefinition assocDef = dictionaryService.getAssociation(assocTypeQName); + if (!assocDef.isChild()) + { + throw new DataIntegrityViolationException("Child association has non-child type: " + assoc.getId()); + } + ChildAssociationDefinition childAssocDef = (ChildAssociationDefinition) assocDef; + if (childAssocDef.getDuplicateChildNamesAllowed()) + { + // the name is irrelevant, so it doesn't need to be put into the unique key + nodeDaoService.setChildNameUnique(assoc, null); + } + else + { + nodeDaoService.setChildNameUnique(assoc, useName); + } + } + // done + if (logger.isDebugEnabled()) + { + logger.debug( + "Unique name set for all " + parentAssocs.size() + " parent associations: \n" + + " name: " + useName); + } + } +}