Britt Park be6a222554 Extended the content model to include relevant avm specific types.
cm:avmcontent and cm:avmfolder are 'abstract'.  cm:avmplaincontent is derived
from cm:avmcontent and is just a plain file. cm:avmlayeredcontent is derived
from cm:avmcontent and is (surprise) a layered file and has a d:noderef mandatory
property, cm:avmfileindirection, that is the (possibly non-existent) file that
the layered file is transparent to.  cm:avmplainfolder is derived from 
cm:avmfolder and is just a plain directory.  cm:avmlayeredfolder is a layered
directory and has a property, cm:avmdirinderection, that is the (possibly
non-existent) directory that the layered directory is transparent to.
The ContentModel QName constants are.
TYPE_AVM_PLAIN_FOLDER
TYPE_AVM_LAYERED_FOLDER
TYPE_AVM_PLAIN_CONTENT
TYPE_AVM_LAYERED_CONTENT
PROP_AVM_DIR_INDIRECTION
PROP_AVM_FILE_INDIRECTION
One can now create all four flavors of avm nodes through 
AVMNodeService.createNode().  The advantage of using these over the
corresponding AVMService methods is that since (for now) AVMService, is
permission and indexing unaware.
Backed out cm:mounted aspect and dispatching code in DbNodeServiceImpl.  
(Dave and Derek, you may now relax) as we are implementing the UI with
AVM dedicated screens.
Finally, beginning interface for AVM node tree synchronization and comparison.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/WCM-DEV2/root@3764 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2006-09-12 03:16:10 +00:00

1806 lines
70 KiB
Java

/*
* 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.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 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<StoreRef> getStores()
{
List<Store> stores = nodeDaoService.getStores();
List<StoreRef> storeRefs = new ArrayList<StoreRef>(stores.size());
for (Store store : stores)
{
storeRefs.add(store.getStoreRef());
}
// Now get the AVMStores.
List<StoreRef> avmStores = avmNodeService.getStores();
storeRefs.addAll(avmStores);
// Return them all.
return storeRefs;
}
/**
* Defers to the typed service
* @see StoreDaoService#createWorkspace(String)
*/
public StoreRef createStore(String protocol, String identifier)
{
StoreRef storeRef = new StoreRef(protocol, identifier);
// 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.<QName, Serializable>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<QName, Serializable> properties)
{
Assert.notNull(parentRef);
Assert.notNull(assocTypeQName);
Assert.notNull(assocQName);
// null property map is allowed
if (properties == null)
{
properties = new HashMap<QName, Serializable>();
}
else
{
// Copy the incomming property map since we may need to modify it later
properties = new HashMap<QName, Serializable>(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);
// 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<QName, Serializable> propertiesBefore = getPropertiesImpl(childNode);
Map<QName, Serializable> propertiesAfter = null;
if (properties.size() > 0)
{
propertiesAfter = setPropertiesImpl(childNode, properties);
}
// get the parent node
Node parentNode = getNodeNotNull(parentRef);
// 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<QName, Serializable> properties)
{
NodeRef nodeRef = node.getNodeRef();
// get the mandatory aspects for the node type
List<AspectDefinition> defaultAspectDefs = classDefinition.getDefaultAspects();
// add all the aspects to the node
Set<QName> 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<QName> 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<QName, Serializable> 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<QName, Serializable> aspectProperties)
throws InvalidNodeRefException, InvalidAspectException
{
// check that the aspect is legal
AspectDefinition aspectDef = dictionaryService.getAspect(aspectTypeQName);
if (aspectDef == null)
{
throw new InvalidAspectException("The aspect is invalid: " + aspectTypeQName, aspectTypeQName);
}
// Invoke policy behaviours
invokeBeforeUpdateNode(nodeRef);
invokeBeforeAddAspect(nodeRef, aspectTypeQName);
Node node = getNodeNotNull(nodeRef);
// attach the properties to the current node properties
Map<QName, Serializable> 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<QName, PropertyValue> nodeProperties = node.getProperties();
Map<QName,PropertyDefinition> 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<QName> aspectQNames = node.getAspects();
boolean hasAspect = aspectQNames.contains(aspectRef);
// done
return hasAspect;
}
public Set<QName> getAspects(NodeRef nodeRef) throws InvalidNodeRefException
{
Node node = getNodeNotNull(nodeRef);
Set<QName> aspectQNames = node.getAspects();
// copy the set to ensure initialization
Set<QName> ret = new HashSet<QName>(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<QName> 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<ChildAssoc> assocs = nodeDaoService.getChildAssocs(parentNode);
assocs = new HashSet<ChildAssoc>(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<QName, Serializable> getProperties(NodeRef nodeRef) throws InvalidNodeRefException
{
Node node = getNodeNotNull(nodeRef);
return getPropertiesImpl(node);
}
private Map<QName, Serializable> getPropertiesImpl(Node node) throws InvalidNodeRefException
{
NodeRef nodeRef = node.getNodeRef();
Map<QName, PropertyValue> nodeProperties = node.getProperties();
Map<QName, Serializable> ret = new HashMap<QName, Serializable>(nodeProperties.size());
// copy values
for (Map.Entry<QName, PropertyValue> 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<QName, PropertyValue> 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 <code>Node</code>.
* <p>
* To remove a property, <b>remove it from the map</b> before calling this method.
* Null-valued properties are allowed.
* <p>
* If any of the values are null, a marker object is put in to mimic nulls. They will be turned back into
* a real nulls when the properties are requested again.
*
* @see Node#getProperties()
*/
public void setProperties(NodeRef nodeRef, Map<QName, Serializable> properties) throws InvalidNodeRefException
{
Node node = getNodeNotNull(nodeRef);
// Invoke policy behaviours
invokeBeforeUpdateNode(nodeRef);
// Do the set properties
Map<QName, Serializable> propertiesBefore = getPropertiesImpl(node);
Map<QName, Serializable> 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<QName, Serializable> setPropertiesImpl(Node node, Map<QName, Serializable> properties) throws InvalidNodeRefException
{
ParameterCheck.mandatory("properties", properties);
// remove referencable properties
removeReferencableProperties(properties);
// copy properties onto node
Map<QName, PropertyValue> 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<QName, Serializable> propertiesBefore = getPropertiesImpl(node);
Map<QName, Serializable> 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<QName, Serializable> setPropertyImpl(Node node, QName qname, Serializable value) throws InvalidNodeRefException
{
NodeRef nodeRef = node.getNodeRef();
Map<QName, PropertyValue> 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<NodeRef> getParents(NodeRef nodeRef) throws InvalidNodeRefException
{
Node node = getNodeNotNull(nodeRef);
// get the assocs pointing to it
Collection<ChildAssoc> parentAssocs = node.getParentAssocs();
// list of results
Collection<NodeRef> results = new ArrayList<NodeRef>(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<ChildAssociationRef> getParentAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern, QNamePattern qnamePattern)
{
Node node = getNodeNotNull(nodeRef);
// get the assocs pointing to it
Collection<ChildAssoc> parentAssocs = node.getParentAssocs();
// shortcut if there are no assocs
if (parentAssocs.size() == 0)
{
return Collections.emptyList();
}
// list of results
List<ChildAssociationRef> results = new ArrayList<ChildAssociationRef>(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<ChildAssociationRef> getChildAssocs(NodeRef nodeRef, QNamePattern typeQNamePattern, QNamePattern qnamePattern)
{
Node node = getNodeNotNull(nodeRef);
// get the assocs pointing from it
Collection<ChildAssociationRef> childAssocRefs = nodeDaoService.getChildAssocRefs(node);
// shortcut if there are no assocs
if (childAssocRefs.size() == 0)
{
return Collections.emptyList();
}
// sort results
ArrayList<ChildAssociationRef> orderedList = new ArrayList<ChildAssociationRef>(childAssocRefs);
Collections.sort(orderedList);
// list of results
int nthSibling = 0;
Iterator<ChildAssociationRef> iterator = orderedList.iterator();
while(iterator.hasNext())
{
ChildAssociationRef childAssocRef = iterator.next();
// 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<AssociationRef> getTargetAssocs(NodeRef sourceRef, QNamePattern qnamePattern)
{
Node sourceNode = getNodeNotNull(sourceRef);
// get all assocs to target
Collection<NodeAssoc> assocs = nodeDaoService.getTargetNodeAssocs(sourceNode);
List<AssociationRef> nodeAssocRefs = new ArrayList<AssociationRef>(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<AssociationRef> getSourceAssocs(NodeRef targetRef, QNamePattern qnamePattern)
{
Node targetNode = getNodeNotNull(targetRef);
// get all assocs to source
Collection<NodeAssoc> assocs = nodeDaoService.getSourceNodeAssocs(targetNode);
List<AssociationRef> nodeAssocRefs = new ArrayList<AssociationRef>(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.
* <p>
* Whilst walking up the hierarchy to the root, some nodes may have a <b>root</b> 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<Path> completedPaths,
Stack<ChildAssoc> assocStack,
boolean primaryOnly)
throws CyclicChildRelationshipException
{
NodeRef currentNodeRef = currentNode.getNodeRef();
// get the parent associations of the given node
Collection<ChildAssoc> 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<Path> paths = getPaths(nodeRef, true); // checks primary path count
if (paths.size() == 1)
{
return paths.get(0); // we know there is only one
}
throw new RuntimeException("Primary path count not checked"); // checked by getPaths()
}
/**
* When searching for <code>primaryOnly == true</code>, checks that there is exactly
* one path.
* @see #prependPaths(Node, Path, Collection, Stack, boolean)
*/
public List<Path> getPaths(NodeRef nodeRef, boolean primaryOnly) throws InvalidNodeRefException
{
// get the starting node
Node node = getNodeNotNull(nodeRef);
// create storage for the paths - only need 1 bucket if we are looking for the primary path
List<Path> paths = new ArrayList<Path>(primaryOnly ? 1 : 10);
// create an empty current path to start from
Path currentPath = new Path();
// create storage for touched associations
Stack<ChildAssoc> assocStack = new Stack<ChildAssoc>();
// 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
return paths;
}
private void archiveNode(NodeRef nodeRef, StoreRef archiveStoreRef)
{
Node node = getNodeNotNull(nodeRef);
ChildAssoc primaryParentAssoc = nodeDaoService.getPrimaryParentAssoc(node);
// add the aspect
Set<QName> aspects = node.getAspects();
aspects.add(ContentModel.ASPECT_ARCHIVED);
Map<QName, PropertyValue> 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<Long, Node> 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<Long, Node> 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<Long, Node> getNodeHierarchy(Node node, Map<Long, Node> nodesById)
{
if (nodesById == null)
{
nodesById = new HashMap<Long, Node>(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<ChildAssoc> 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.
* <p>
* 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<Long, Node> nodesById)
{
List<ChildAssoc> childAssocsToDelete = new ArrayList<ChildAssoc>(5);
// child associations
ArrayList<ChildAssociationRef> archivedChildAssocRefs = new ArrayList<ChildAssociationRef>(5);
Collection<ChildAssoc> 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<ChildAssociationRef> archivedParentAssocRefs = new ArrayList<ChildAssociationRef>(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<NodeAssoc> nodeAssocsToDelete = new ArrayList<NodeAssoc>(5);
// source associations
ArrayList<AssociationRef> archivedSourceAssocRefs = new ArrayList<AssociationRef>(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<AssociationRef> archivedTargetAssocRefs = new ArrayList<AssociationRef>(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<QName, PropertyValue> 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<QName> aspects = archivedNode.getAspects();
Map<QName, PropertyValue> 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<Long, Node> 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<QName, PropertyValue> properties = node.getProperties();
// restore parent associations
Collection<ChildAssociationRef> parentAssocRefs = (Collection<ChildAssociationRef>) 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<ChildAssociationRef> childAssocRefs = (Collection<ChildAssociationRef>) 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<AssociationRef> sourceAssocRefs = (Collection<AssociationRef>) 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<AssociationRef> targetAssocRefs = (Collection<AssociationRef>) 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<QName, PropertyValue> 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<ChildAssoc> 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);
}
}
}