/* * 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.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.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.service.namespace.RegexQNamePattern; 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; } /** * Gets the node status for a live node. * @param nodeRef the node reference * @return Returns the node status, which will not be null and will have a live node attached. * @throws InvalidNodeRefException if the node is deleted or never existed */ public NodeStatus getNodeStatusNotNull(NodeRef nodeRef) throws InvalidNodeRefException { NodeStatus nodeStatus = nodeDaoService.getNodeStatus(nodeRef, false); if (nodeStatus == null || nodeStatus.getNode() == null) { throw new InvalidNodeRefException("Node does not exist: " + nodeRef, nodeRef); } return nodeStatus; } 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, 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); invokeBeforeCreateNodeAssociation(parentRef, assocTypeQName, assocQName); // 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); invokeOnCreateNodeAssociation(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); // flush nodeDaoService.flush(); } 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 boolean removeChildAssociation(ChildAssociationRef childAssocRef) { Node parentNode = getNodeNotNull(childAssocRef.getParentRef()); Node childNode = getNodeNotNull(childAssocRef.getChildRef()); QName typeQName = childAssocRef.getTypeQName(); QName qname = childAssocRef.getQName(); // Delete the association invokeBeforeDeleteChildAssociation(childAssocRef); boolean deleted = nodeDaoService.deleteChildAssoc(parentNode, childNode, typeQName, qname); if (deleted) { invokeOnDeleteChildAssociation(childAssocRef); } // Done return deleted; } /** * Remove properties that should not be persisted as general properties. Where necessary, the * properties are set on the node. * * @param node the node to set properties on * @param properties properties to change */ private void extractIntrinsicProperties(Node node, Map properties) { properties.remove(ContentModel.PROP_STORE_PROTOCOL); properties.remove(ContentModel.PROP_STORE_IDENTIFIER); properties.remove(ContentModel.PROP_NODE_UUID); properties.remove(ContentModel.PROP_NODE_DBID); } /** * Adds all properties used by the * {@link ContentModel#ASPECT_REFERENCEABLE referencable aspect}. *

* This method can be used to ensure that the values used by the aspect * are present as node properties. *

* This method also ensures that the {@link ContentModel#PROP_NAME name property} * is always present as a property on a node. * * @param node the node with the values * @param nodeRef the node reference containing the values required * @param properties the node properties */ private void addIntrinsicProperties(Node node, Map properties) { NodeRef nodeRef = node.getNodeRef(); properties.put(ContentModel.PROP_STORE_PROTOCOL, nodeRef.getStoreRef().getProtocol()); properties.put(ContentModel.PROP_STORE_IDENTIFIER, nodeRef.getStoreRef().getIdentifier()); properties.put(ContentModel.PROP_NODE_UUID, nodeRef.getId()); properties.put(ContentModel.PROP_NODE_DBID, node.getId()); // add the ID as the name, if required if (properties.get(ContentModel.PROP_NAME) == null) { properties.put(ContentModel.PROP_NAME, nodeRef.getId()); } } public Map getProperties(NodeRef nodeRef) throws InvalidNodeRefException { Node node = getNodeNotNull(nodeRef); return getPropertiesImpl(node); } private Map getPropertiesImpl(Node node) throws InvalidNodeRefException { 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 addIntrinsicProperties(node, 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); // check if we need to provide a spoofed name if (propertyValue == null && qname.equals(ContentModel.PROP_NAME)) { return nodeRef.getId(); } // 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 extractIntrinsicProperties(node, 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); Collection childAssocRefs = null; // if the type is the wildcard type, and the qname is not a search, then use a shortcut query if (typeQNamePattern.equals(RegexQNamePattern.MATCH_ALL) && qnamePattern instanceof QName) { // get all child associations with the specific qualified name childAssocRefs = nodeDaoService.getChildAssocRefs(node, (QName)qnamePattern); } else { // get all child associations childAssocRefs = nodeDaoService.getChildAssocRefs(node); // remove non-matching assocs Iterator iterator = childAssocRefs.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(); } } } // sort the results List orderedList = reorderChildAssocs(childAssocRefs); // done return orderedList; } private List reorderChildAssocs(Collection childAssocRefs) { // 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(); 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) { NodeStatus nodeStatus = nodeDaoService.getNodeStatus(nodeRef, false); Node node = nodeStatus.getNode(); 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")); // the node reference has changed due to the store move nodeRef = node.getNodeRef(); // as has the node status nodeStatus = nodeDaoService.getNodeStatus(nodeRef, true); // get the IDs of all the node's primary children, including its own Map nodeStatusesById = getNodeHierarchy(nodeStatus, null); // Archive all the associations between the archived nodes and non-archived nodes for (NodeStatus nodeStatusToArchive : nodeStatusesById.values()) { Node nodeToArchive = nodeStatusToArchive.getNode(); if (nodeToArchive == null) { continue; } archiveAssocs(nodeToArchive, nodeStatusesById); } } /** * 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) { NodeRef nodeRef = node.getNodeRef(); NodeStatus nodeStatus = nodeDaoService.getNodeStatus(nodeRef, true); // get the IDs of all the node's primary children, including its own Map nodeStatusesById = getNodeHierarchy(nodeStatus, null); // move each node into the archive store for (NodeStatus oldNodeStatus : nodeStatusesById.values()) { Node nodeToMove = oldNodeStatus.getNode(); nodeToMove.setStore(store); NodeRef newNodeRef = nodeToMove.getNodeRef(); // update old status oldNodeStatus.setNode(null); // create the new status NodeStatus newNodeStatus = nodeDaoService.getNodeStatus(newNodeRef, true); newNodeStatus.setNode(nodeToMove); invokeOnUpdateNode(newNodeRef); } } /** * 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 nodeStatus the status of the node at the top of the hierarchy * @param nodeStatusesById a map of node statuses that will be reused as the return value * @return Returns a map of nodes in the hierarchy keyed by their IDs */ private Map getNodeHierarchy(NodeStatus nodeStatus, Map nodeStatusesById) { if (nodeStatusesById == null) { nodeStatusesById = new HashMap(23); // this is the entry into the hierarchy - flush to ensure we are not stale nodeDaoService.flush(); } Node node = nodeStatus.getNode(); if (node == null) { // the node has already been deleted return nodeStatusesById; } Long nodeId = node.getId(); if (nodeStatusesById.containsKey(nodeId)) { // this ID was already added - circular reference logger.warn("Circular hierarchy found including node " + nodeId); return nodeStatusesById; } // add the node to the map nodeStatusesById.put(nodeId, nodeStatus); // recurse into the primary children Collection primaryChildNodeStatuses = nodeDaoService.getPrimaryChildNodeStatuses(node); for (NodeStatus primaryChildNodeStatus : primaryChildNodeStatuses) { // cascade into primary associations nodeStatusesById = getNodeHierarchy(primaryChildNodeStatus, nodeStatusesById); } return nodeStatusesById; } /** * 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 nodeStatusesById) { 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 (nodeStatusesById.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 (nodeStatusesById.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 (nodeStatusesById.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 (nodeStatusesById.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) { NodeStatus archivedNodeStatus = getNodeStatusNotNull(archivedNodeRef); Node archivedNode = archivedNodeStatus.getNode(); 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 ChildAssociationRef newChildAssocRef = moveNode( archivedNodeRef, destinationParentNodeRef, assocTypeQName, assocQName); archivedNodeRef = newChildAssocRef.getChildRef(); archivedNodeStatus = nodeDaoService.getNodeStatus(archivedNodeRef, false); // get the IDs of all the node's primary children, including its own Map restoreNodeStatusesById = getNodeHierarchy(archivedNodeStatus, null); // Restore the archived associations, if required for (NodeStatus restoreNodeStatus : restoreNodeStatusesById.values()) { Node restoreNode = restoreNodeStatus.getNode(); restoreAssocs(restoreNode); } // 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. * @return Returns the value to be put on the child association for uniqueness, or null if */ 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); } } }