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);
}
}
}