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(Pair nodePair, Map properties)
{
Long nodeId = nodePair.getFirst();
NodeRef nodeRef = nodePair.getSecond();
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, nodeId);
// add the ID as the name, if required
if (properties.get(ContentModel.PROP_NAME) == null)
{
properties.put(ContentModel.PROP_NAME, nodeRef.getId());
}
}
public Serializable getProperty(NodeRef nodeRef, QName qname) throws InvalidNodeRefException
{
Long nodeId = getNodePairNotNull(nodeRef).getFirst();
// Spoof referencable properties
if (qname.equals(ContentModel.PROP_STORE_PROTOCOL))
{
return nodeRef.getStoreRef().getProtocol();
}
else if (qname.equals(ContentModel.PROP_STORE_IDENTIFIER))
{
return nodeRef.getStoreRef().getIdentifier();
}
else if (qname.equals(ContentModel.PROP_NODE_UUID))
{
return nodeRef.getId();
}
else if (qname.equals(ContentModel.PROP_NODE_DBID))
{
return nodeId;
}
Serializable property = nodeDaoService.getNodeProperty(nodeId, qname);
// check if we need to provide a spoofed name
if (property == null && qname.equals(ContentModel.PROP_NAME))
{
return nodeRef.getId();
}
// done
return property;
}
public Map getProperties(NodeRef nodeRef) throws InvalidNodeRefException
{
Pair nodePair = getNodePairNotNull(nodeRef);
return getPropertiesImpl(nodePair);
}
/**
* Gets, converts and adds the intrinsic properties to the current node's properties
*/
private Map getPropertiesImpl(Pair nodePair) throws InvalidNodeRefException
{
Long nodeId = nodePair.getFirst();
Map nodeProperties = nodeDaoService.getNodeProperties(nodeId);
// spoof referencable properties
addIntrinsicProperties(nodePair, nodeProperties);
// done
return nodeProperties;
}
/**
* 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);
// get the node
Pair nodePair = getNodePairNotNull(nodeRef);
Long nodeId = nodePair.getFirst();
// Ensure that we are not setting intrinsic properties
Map properties = new HashMap(1, 1.0F);
properties.put(qname, value);
extractIntrinsicProperties(properties);
// Shortcut if nothing is left
if (properties.size() == 0)
{
return;
}
// Get the properties from before
Map propertiesBefore = getPropertiesImpl(nodePair);
invokeBeforeUpdateNode(nodeRef);
// Update the properties
setPropertyImpl(nodeId, qname, value);
// Policy callbacks
Map propertiesAfter = getPropertiesImpl(nodePair);
invokeOnUpdateNode(nodeRef);
invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter);
// Index
nodeIndexer.indexUpdateNode(nodeRef);
}
/**
* Sets the property, taking special care to handle intrinsic properties and cm:name properly
*/
private void setPropertyImpl(Long nodeId, QName qname, Serializable value)
{
if (qname.equals(ContentModel.PROP_NODE_UUID))
{
throw new IllegalArgumentException("The node UUID cannot be changed.");
}
else
{
// cm:name special handling
if (qname.equals(ContentModel.PROP_NAME))
{
Pair primaryParentAssocPair = nodeDaoService.getPrimaryParentAssoc(nodeId);
if (primaryParentAssocPair != null)
{
String oldName = extractNameProperty(nodeDaoService.getNodeProperties(nodeId));
String newName = DefaultTypeConverter.INSTANCE.convert(String.class, value);
setChildNameUnique(primaryParentAssocPair, newName, oldName);
}
}
// Set the property
nodeDaoService.addNodeProperty(nodeId, qname, 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
{
Pair nodePair = getNodePairNotNull(nodeRef);
Long nodeId = nodePair.getFirst();
extractIntrinsicProperties(properties);
// Invoke policy behaviours
Map propertiesBefore = getPropertiesImpl(nodePair);
invokeBeforeUpdateNode(nodeRef);
// Do the set properties
setPropertiesImpl(nodeId, properties);
// Invoke policy behaviours
Map propertiesAfter = getPropertiesImpl(nodePair);
invokeOnUpdateNode(nodeRef);
invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter);
// Index
nodeIndexer.indexUpdateNode(nodeRef);
}
private void setPropertiesImpl(Long nodeId, Map properties)
{
// Get the cm:name and uuid for special handling
if (properties.containsKey(ContentModel.PROP_NAME))
{
Serializable name = properties.get(ContentModel.PROP_NAME);
setPropertyImpl(nodeId, ContentModel.PROP_NAME, name);
}
if (properties.containsKey(ContentModel.PROP_NODE_UUID))
{
throw new IllegalArgumentException("The node UUID cannot be set");
}
// Now remove special properties
extractIntrinsicProperties(properties);
// Update the node
nodeDaoService.setNodeProperties(nodeId, properties);
}
public void removeProperty(NodeRef nodeRef, QName qname) throws InvalidNodeRefException
{
// Get the node
Pair nodePair = getNodePairNotNull(nodeRef);
Long nodeId = nodePair.getFirst();
// Invoke policy behaviours
invokeBeforeUpdateNode(nodeRef);
// Get the values before
Map propertiesBefore = getPropertiesImpl(nodePair);
// cm:name special handling
if (qname.equals(ContentModel.PROP_NAME))
{
Pair primaryParentAssocPair = nodeDaoService.getPrimaryParentAssoc(nodeId);
String oldName = extractNameProperty(nodeDaoService.getNodeProperties(nodeId));
String newName = null;
setChildNameUnique(primaryParentAssocPair, newName, oldName);
}
// Remove
nodeDaoService.removeNodeProperties(nodeId, Collections.singleton(qname));
// Invoke policy behaviours
Map propertiesAfter = getPropertiesImpl(nodePair);
invokeOnUpdateNode(nodeRef);
invokeOnUpdateProperties(nodeRef, propertiesBefore, propertiesAfter);
// Index
nodeIndexer.indexUpdateNode(nodeRef);
}
// private Map convertProperties(Map properties) throws InvalidNodeRefException
// {
// Map convertedProperties = new HashMap(17);
//
// // 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);
// convertedProperties.put(propertyQName, propertyValue);
// }
//
// // Return the converted properties
// return convertedProperties;
// }
//
// private Map convertPropertyValues(Map propertyValues) throws InvalidNodeRefException
// {
// Map convertedProperties = new HashMap(17);
//
// // check the property type and copy the values across
// for (Map.Entry entry : propertyValues.entrySet())
// {
// QName propertyQName = entry.getKey();
// PropertyValue propertyValue = entry.getValue();
// PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName);
// Serializable property = makeSerializableValue(propertyDef, propertyValue);
// convertedProperties.put(propertyQName, property);
// }
//
// // Return the converted properties
// return convertedProperties;
// }
//
public Collection getParents(NodeRef nodeRef) throws InvalidNodeRefException
{
// Get the node
Pair nodePair = getNodePairNotNull(nodeRef);
Long nodeId = nodePair.getFirst();
// Get the assocs pointing to it
Collection> parentAssocPairs = nodeDaoService.getParentAssocs(nodeId);
// list of results
Collection results = new ArrayList(parentAssocPairs.size());
for (Pair assocPair : parentAssocPairs)
{
NodeRef parentNodeRef = assocPair.getSecond().getParentRef();
results.add(parentNodeRef);
}
// 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)
{
// Get the node
Pair nodePair = getNodePairNotNull(nodeRef);
Long nodeId = nodePair.getFirst();
// Get the assocs pointing to it
Collection> parentAssocPairs = nodeDaoService.getParentAssocs(nodeId);
// list of results
List results = new ArrayList(parentAssocPairs.size());
for (Pair assocPair : parentAssocPairs)
{
ChildAssociationRef assocRef = assocPair.getSecond();
QName assocTypeQName = assocRef.getTypeQName();
QName assocQName = assocRef.getQName();
if (!qnamePattern.isMatch(assocQName) || !typeQNamePattern.isMatch(assocTypeQName))
{
// No match
continue;
}
results.add(assocRef);
}
// done
return results;
}
/**
* Filters out any associations if their qname is not a match to the given pattern.
*/
public List getChildAssocs(NodeRef nodeRef, final QNamePattern typeQNamePattern, final QNamePattern qnamePattern)
{
// Get the node
Pair nodePair = getNodePairNotNull(nodeRef);
Long nodeId = nodePair.getFirst();
final List results = new ArrayList(100);
// 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)
{
NodeDaoService.ChildAssocRefQueryCallback callback = new NodeDaoService.ChildAssocRefQueryCallback()
{
public boolean handle(
Pair childAssocPair,
Pair parentNodePair,
Pair childNodePair)
{
results.add(childAssocPair.getSecond());
return false;
}
};
// Get all child associations with the specific qualified name
nodeDaoService.getChildAssocs(nodeId, (QName)qnamePattern, callback);
}
else if (typeQNamePattern instanceof QName && qnamePattern instanceof QName)
{
NodeDaoService.ChildAssocRefQueryCallback callback = new NodeDaoService.ChildAssocRefQueryCallback()
{
public boolean handle(
Pair childAssocPair,
Pair parentNodePair,
Pair childNodePair)
{
results.add(childAssocPair.getSecond());
return false;
}
};
// Get all child associations with the specific qualified name
nodeDaoService.getChildAssocsByTypeQNameAndQName(
nodeId,
(QName)typeQNamePattern,
(QName)qnamePattern,
callback);
}
else
{
NodeDaoService.ChildAssocRefQueryCallback callback = new NodeDaoService.ChildAssocRefQueryCallback()
{
public boolean handle(Pair childAssocPair, Pair parentNodePair, Pair childNodePair)
{
ChildAssociationRef assocRef = childAssocPair.getSecond();
QName assocTypeQName = assocRef.getTypeQName();
QName assocQName = assocRef.getQName();
if (!qnamePattern.isMatch(assocQName) || !typeQNamePattern.isMatch(assocTypeQName))
{
// No match
return false;
}
results.add(assocRef);
return false;
}
};
// Get all child associations
nodeDaoService.getChildAssocs(nodeId, callback, false);
}
// sort the results
List orderedList = reorderChildAssocs(results);
// done
return orderedList;
}
public List getChildAssocs(NodeRef nodeRef, Set childNodeTypeQNames)
{
// Get the node
Pair nodePair = getNodePairNotNull(nodeRef);
Long nodeId = nodePair.getFirst();
final List results = new ArrayList(100);
NodeDaoService.ChildAssocRefQueryCallback callback = new NodeDaoService.ChildAssocRefQueryCallback()
{
public boolean handle(
Pair childAssocPair,
Pair parentNodePair,
Pair childNodePair)
{
results.add(childAssocPair.getSecond());
return false;
}
};
// Get all child associations with the specific qualified name
nodeDaoService.getChildAssocsByChildTypes(nodeId, childNodeTypeQNames, callback);
// Done
return results;
}
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)
{
// Get the node
Pair nodePair = getNodePairNotNull(nodeRef);
Long nodeId = nodePair.getFirst();
Pair childAssocPair = nodeDaoService.getChildAssoc(nodeId, assocTypeQName, childName);
if (childAssocPair != null)
{
return childAssocPair.getSecond().getChildRef();
}
else
{
return null;
}
}
public ChildAssociationRef getPrimaryParent(NodeRef nodeRef) throws InvalidNodeRefException
{
// Get the node
Pair nodePair = getNodePairNotNull(nodeRef);
Long nodeId = nodePair.getFirst();
// get the primary parent assoc
Pair assocPair = nodeDaoService.getPrimaryParentAssoc(nodeId);
// done - the assoc may be null for a root node
ChildAssociationRef assocRef = null;
if (assocPair == null)
{
assocRef = new ChildAssociationRef(null, null, null, nodeRef);
}
else
{
assocRef = assocPair.getSecond();
}
return assocRef;
}
public AssociationRef createAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName)
throws InvalidNodeRefException, AssociationExistsException
{
Pair sourceNodePair = getNodePairNotNull(sourceRef);
long sourceNodeId = sourceNodePair.getFirst();
Pair targetNodePair = getNodePairNotNull(targetRef);
long targetNodeId = targetNodePair.getFirst();
// we are sure that the association doesn't exist - make it
Pair assocPair = nodeDaoService.newNodeAssoc(sourceNodeId, targetNodeId, assocTypeQName);
AssociationRef assocRef = assocPair.getSecond();
// Invoke policy behaviours
invokeOnCreateAssociation(assocRef);
return assocRef;
}
public void removeAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName)
throws InvalidNodeRefException
{
Pair sourceNodePair = getNodePairNotNull(sourceRef);
long sourceNodeId = sourceNodePair.getFirst();
Pair targetNodePair = getNodePairNotNull(targetRef);
long targetNodeId = targetNodePair.getFirst();
// get the association
Pair assocPair = nodeDaoService.getNodeAssoc(sourceNodeId, targetNodeId, assocTypeQName);
if (assocPair == null)
{
// nothing to remove
return;
}
AssociationRef assocRef = assocPair.getSecond();
// delete it
nodeDaoService.deleteNodeAssoc(assocPair.getFirst());
// Invoke policy behaviours
invokeOnDeleteAssociation(assocRef);
}
public List getTargetAssocs(NodeRef sourceRef, QNamePattern qnamePattern)
{
Pair sourceNodePair = getNodePairNotNull(sourceRef);
long sourceNodeId = sourceNodePair.getFirst();
// get all assocs to target
Collection> assocPairs = nodeDaoService.getTargetNodeAssocs(sourceNodeId);
List nodeAssocRefs = new ArrayList(assocPairs.size());
for (Pair assocPair : assocPairs)
{
AssociationRef assocRef = assocPair.getSecond();
// check qname pattern
if (!qnamePattern.isMatch(assocRef.getTypeQName()))
{
continue; // the assoc name doesn't match the pattern given
}
nodeAssocRefs.add(assocRef);
}
// done
return nodeAssocRefs;
}
public List getSourceAssocs(NodeRef targetRef, QNamePattern qnamePattern)
{
Pair targetNodePair = getNodePairNotNull(targetRef);
long targetNodeId = targetNodePair.getFirst();
// get all assocs to target
Collection> assocPairs = nodeDaoService.getSourceNodeAssocs(targetNodeId);
List nodeAssocRefs = new ArrayList(assocPairs.size());
for (Pair assocPair : assocPairs)
{
AssociationRef assocRef = assocPair.getSecond();
// check qname pattern
if (!qnamePattern.isMatch(assocRef.getTypeQName()))
{
continue; // the assoc name doesn't match the pattern given
}
nodeAssocRefs.add(assocRef);
}
// 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(
Pair currentNodePair,
Pair currentRootNodePair,
Path currentPath,
Collection completedPaths,
Stack assocIdStack,
boolean primaryOnly)
throws CyclicChildRelationshipException
{
Long currentNodeId = currentNodePair.getFirst();
NodeRef currentNodeRef = currentNodePair.getSecond();
// Check if we have changed root nodes
StoreRef currentStoreRef = currentNodeRef.getStoreRef();
if (currentRootNodePair == null || !currentStoreRef.equals(currentRootNodePair.getFirst()))
{
// We've changed stores
Pair rootNodePair = nodeDaoService.getRootNode(currentStoreRef);
currentRootNodePair = new Pair(currentStoreRef, rootNodePair.getSecond());
}
// get the parent associations of the given node
Collection> parentAssocPairs = nodeDaoService.getParentAssocs(currentNodeId);
// does the node have parents
boolean hasParents = parentAssocPairs.size() > 0;
// does the current node have a root aspect?
boolean isRoot = nodeDaoService.hasNodeAspect(currentNodeId, ContentModel.ASPECT_ROOT);
boolean isStoreRoot = nodeDaoService.getNodeType(currentNodeId).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,
currentRootNodePair.getSecond());
// 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(),
currentRootNodePair.getSecond(),
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 (parentAssocPairs.size() == 0 && !isRoot)
{
throw new RuntimeException("Node without parents does not have root aspect: " +
currentNodeRef);
}
// walk up each parent association
for (Pair assocPair : parentAssocPairs)
{
Long assocId = assocPair.getFirst();
ChildAssociationRef assocRef = assocPair.getSecond();
// do we consider only primary assocs?
if (primaryOnly && !assocRef.isPrimary())
{
continue;
}
// Ordering is meaningless here as we are constructing a path upwards
// and have no idea where the node comes in the sibling order or even
// if there are like-pathed siblings.
assocRef.setNthSibling(-1);
// build a path element
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
NodeRef parentRef = assocRef.getParentRef();
Pair parentNodePair = getNodePairNotNull(parentRef);
// does the association already exist in the stack
if (assocIdStack.contains(assocId))
{
// the association was present already
throw new CyclicChildRelationshipException(
"Cyclic parent-child relationship detected: \n" +
" current node: " + currentNodeId + "\n" +
" current path: " + currentPath + "\n" +
" next assoc: " + assocId,
assocRef);
}
// push the assoc stack, recurse and pop
assocIdStack.push(assocId);
prependPaths(parentNodePair, currentRootNodePair, path, completedPaths, assocIdStack, primaryOnly);
assocIdStack.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
Pair nodePair = getNodePairNotNull(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 assocIdStack = new Stack();
// call recursive method to sort it out
prependPaths(nodePair, null, currentPath, paths, assocIdStack, 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)
{
Pair nodePair = getNodePairNotNull(nodeRef);
Long nodeId = nodePair.getFirst();
Pair primaryParentAssocPair = nodeDaoService.getPrimaryParentAssoc(nodeId);
Set newAspects = new HashSet(5);
Map existingProperties = nodeDaoService.getNodeProperties(nodeId);
Map newProperties = new HashMap(11);
// add the aspect
newAspects.add(ContentModel.ASPECT_ARCHIVED);
newProperties.put(ContentModel.PROP_ARCHIVED_BY, AuthenticationUtil.getCurrentUserName());
newProperties.put(ContentModel.PROP_ARCHIVED_DATE, new Date());
newProperties.put(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC, primaryParentAssocPair.getSecond());
Serializable originalOwner = existingProperties.get(ContentModel.PROP_OWNER);
Serializable originalCreator = existingProperties.get(ContentModel.PROP_CREATOR);
if (originalOwner != null || originalCreator != null)
{
newProperties.put(
ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER,
originalOwner != null ? originalOwner : originalCreator);
}
// change the node ownership
newAspects.add(ContentModel.ASPECT_OWNABLE);
newProperties.put(ContentModel.PROP_OWNER, AuthenticationUtil.getCurrentUserName());
// Set the aspects and properties
nodeDaoService.addNodeProperties(nodeId, newProperties);
nodeDaoService.addNodeAspects(nodeId, newAspects);
// move the node
Pair archiveStoreRootNodePair = nodeDaoService.getRootNode(archiveStoreRef);
moveNode(
nodeRef,
archiveStoreRootNodePair.getSecond(),
ContentModel.ASSOC_CHILDREN,
QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedItem"));
}
public NodeRef restoreNode(NodeRef archivedNodeRef, NodeRef destinationParentNodeRef, QName assocTypeQName, QName assocQName)
{
Pair archivedNodePair = getNodePairNotNull(archivedNodeRef);
Long archivedNodeId = archivedNodePair.getFirst();
Set existingAspects = nodeDaoService.getNodeAspects(archivedNodeId);
Set newAspects = new HashSet(5);
Map existingProperties = nodeDaoService.getNodeProperties(archivedNodeId);
Map newProperties = new HashMap(11);
// the node must be a top-level archive node
if (!existingAspects.contains(ContentModel.ASPECT_ARCHIVED))
{
throw new AlfrescoRuntimeException("The node to restore is not an archive node");
}
ChildAssociationRef originalPrimaryParentAssocRef = (ChildAssociationRef) existingProperties.get(
ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC);
Serializable originalOwner = existingProperties.get(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER);
// remove the archived aspect
Set removePropertyQNames = new HashSet(11);
removePropertyQNames.add(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC);
removePropertyQNames.add(ContentModel.PROP_ARCHIVED_BY);
removePropertyQNames.add(ContentModel.PROP_ARCHIVED_DATE);
removePropertyQNames.add(ContentModel.PROP_ARCHIVED_ORIGINAL_OWNER);
nodeDaoService.removeNodeProperties(archivedNodeId, removePropertyQNames);
nodeDaoService.removeNodeAspects(archivedNodeId, Collections.singleton(ContentModel.ASPECT_ARCHIVED));
// restore the original ownership
if (originalOwner != null)
{
newAspects.add(ContentModel.ASPECT_OWNABLE);
newProperties.put(ContentModel.PROP_OWNER, originalOwner);
}
if (destinationParentNodeRef == null)
{
// we must restore to the original location
destinationParentNodeRef = originalPrimaryParentAssocRef.getParentRef();
}
// check the associations
if (assocTypeQName == null)
{
assocTypeQName = originalPrimaryParentAssocRef.getTypeQName();
}
if (assocQName == null)
{
assocQName = originalPrimaryParentAssocRef.getQName();
}
// move the node to the target parent, which may or may not be the original parent
ChildAssociationRef newChildAssocRef = moveNode(
archivedNodeRef,
destinationParentNodeRef,
assocTypeQName,
assocQName);
// the node reference has changed due to the store move
NodeRef restoredNodeRef = newChildAssocRef.getChildRef();
// done
if (logger.isDebugEnabled())
{
logger.debug("Restored node: \n" +
" original noderef: " + archivedNodeRef + "\n" +
" restored noderef: " + restoredNodeRef + "\n" +
" new parent: " + destinationParentNodeRef);
}
return restoredNodeRef;
}
/**
* Drops the old primary association and creates a new one
*/
public ChildAssociationRef moveNode(
NodeRef nodeToMoveRef,
NodeRef newParentRef,
QName assocTypeQName,
QName assocQName)
{
Pair nodeToMovePair = getNodePairNotNull(nodeToMoveRef);
Pair parentNodePair = getNodePairNotNull(newParentRef);
Long nodeToMoveId = nodeToMovePair.getFirst();
QName nodeToMoveTypeQName = nodeDaoService.getNodeType(nodeToMoveId);
NodeRef oldNodeToMoveRef = nodeToMovePair.getSecond();
Long parentNodeId = parentNodePair.getFirst();
NodeRef parentNodeRef = parentNodePair.getSecond();
StoreRef oldStoreRef = oldNodeToMoveRef.getStoreRef();
StoreRef newStoreRef = parentNodeRef.getStoreRef();
NodeRef newNodeToMoveRef = new NodeRef(newStoreRef, oldNodeToMoveRef.getId());
Pair newNodeToMovePair = new Pair(nodeToMoveId, newNodeToMoveRef);
// Get the primary parent association
Pair oldParentAssocPair = nodeDaoService.getPrimaryParentAssoc(nodeToMoveId);
if (oldParentAssocPair == null)
{
// The node doesn't have parent. Moving it is not possible.
throw new IllegalArgumentException("Node " + nodeToMoveId + " doesn't have a parent. Use 'addChild' instead of move.");
}
Long oldParentAssocId = oldParentAssocPair.getFirst();
ChildAssociationRef oldParentAssocRef = oldParentAssocPair.getSecond();
// Shortcut this whole process if nothing has changed
if (EqualsHelper.nullSafeEquals(oldParentAssocRef.getParentRef(), newParentRef) &&
EqualsHelper.nullSafeEquals(oldParentAssocRef.getTypeQName(), assocTypeQName) &&
EqualsHelper.nullSafeEquals(oldParentAssocRef.getQName(), assocQName))
{
// It's all just the same
return oldParentAssocRef;
}
boolean movingStore = !oldStoreRef.equals(newStoreRef);
// Handle store conflicts
if (movingStore)
{
handleStoreMoveConflicts(nodeToMovePair, newStoreRef);
}
// Invoke policy behaviour
if (movingStore)
{
invokeBeforeDeleteNode(nodeToMoveRef);
invokeBeforeCreateNode(newParentRef, assocTypeQName, assocQName, nodeToMoveTypeQName);
}
else
{
invokeBeforeDeleteChildAssociation(oldParentAssocRef);
invokeBeforeCreateChildAssociation(newParentRef, nodeToMoveRef, assocTypeQName, assocQName, false);
}
// Handle store moves
if (movingStore)
{
Pair newNodePair = nodeDaoService.moveNodeToStore(nodeToMoveId, newStoreRef);
if (!newNodePair.equals(newNodeToMovePair))
{
throw new RuntimeException("Store-moved pair isn't expected: " + newNodePair + " != " + newNodeToMovePair);
}
}
// Get the new node's cm:name
Map newNodeProperties = nodeDaoService.getNodeProperties(nodeToMoveId);
String newNodeChildName = extractNameProperty(newNodeProperties);
// Modify the association directly. We do this AFTER the change of the node's store so that
// the association reference returned is correct.
Pair newParentAssocPair = nodeDaoService.updateChildAssoc(
oldParentAssocId,
parentNodeId,
nodeToMoveId,
assocTypeQName,
assocQName,
-1,
newNodeChildName);
ChildAssociationRef newParentAssocRef = newParentAssocPair.getSecond();
// Handle indexing differently if it is a store move
if (movingStore)
{
// The association existed before and the node is moving to a new store
nodeIndexer.indexDeleteNode(oldParentAssocRef);
nodeIndexer.indexCreateNode(newParentAssocRef);
}
else
{
// The node is in the same store and is just having it's child association modified
nodeIndexer.indexUpdateChildAssociation(oldParentAssocRef, newParentAssocRef);
}
// Ensure name uniqueness
setChildNameUnique(newParentAssocPair, newNodeToMovePair);
// Check that there is not a cyclic relationship
getPaths(newNodeToMoveRef, false);
// Call behaviours
if (movingStore)
{
Set nodeToMoveAspectQNames = nodeDaoService.getNodeAspects(nodeToMoveId);
// The Node changes NodeRefs, so this is really the deletion of the old node and creation
// of a node in a new store as far as the clients are concerned.
invokeOnDeleteNode(oldParentAssocRef, nodeToMoveTypeQName, nodeToMoveAspectQNames, true);
invokeOnCreateNode(newParentAssocRef);
}
else
{
invokeOnCreateChildAssociation(newParentAssocRef, false);
invokeOnDeleteChildAssociation(oldParentAssocRef);
invokeOnMoveNode(oldParentAssocRef, newParentAssocRef);
}
// If we have to cascade in the transaction, then pull the children over to the new store
if (cascadeInTransaction)
{
// Pull children to the new store
pullNodeChildrenToSameStore(newNodeToMovePair, true, true);
}
// Done
return newParentAssocRef;
}
/**
* Silently gives any clashing target nodes a new UUID
* @param nodeToMovePair the node that will be moved
* @param newStoreRef the store that the node will be moved to
*/
private void handleStoreMoveConflicts(Pair nodeToMovePair, StoreRef newStoreRef)
{
NodeRef oldNodeToMoveRef = nodeToMovePair.getSecond();
NodeRef newNodeToMoveRef = new NodeRef(newStoreRef, oldNodeToMoveRef.getId());
// If the new node reference is already taken, then give it a new uuid
Pair conflictingNodePair = nodeDaoService.getNodePair(newNodeToMoveRef);
if (conflictingNodePair != null)
{
// We are creating a new node. This noderef will be reused, so will be an update
nodeDaoService.updateNode(conflictingNodePair.getFirst(), null, GUID.generate(), null);
}
}
/**
* This process is less invasive than the move method as the child associations
* do not need to be remade. If the children are in the same store, only the indexChildren
* value is needed.
*/
private void pullNodeChildrenToSameStore(Pair nodePair, boolean cascade, boolean indexChildren)
{
Long nodeId = nodePair.getFirst();
NodeRef nodeRef = nodePair.getSecond();
StoreRef storeRef = nodeRef.getStoreRef();
// Get the node's children, but only one's that aren't in the same store
final List> childNodePairs = new ArrayList>(5);
NodeDaoService.ChildAssocRefQueryCallback callback = new NodeDaoService.ChildAssocRefQueryCallback()
{
public boolean handle(
Pair childAssocPair,
Pair parentNodePair,
Pair childNodePair
)
{
// Add it
childNodePairs.add(childNodePair);
return false;
}
};
// We only need to move child nodes that are not already in the same store
nodeDaoService.getPrimaryChildAssocsNotInSameStore(nodeId, callback);
// Each child must be moved to the same store as the parent
for (Pair oldChildNodePair : childNodePairs)
{
Long childNodeId = oldChildNodePair.getFirst();
NodeRef childNodeRef = oldChildNodePair.getSecond();
QName childNodeTypeQName = nodeDaoService.getNodeType(childNodeId);
Set childNodeAspectQNames = nodeDaoService.getNodeAspects(childNodeId);
Pair oldParentAssocPair = nodeDaoService.getPrimaryParentAssoc(childNodeId);
Pair newChildNodePair = oldChildNodePair;
Pair newParentAssocPair = oldParentAssocPair;
ChildAssociationRef newParentAssocRef = newParentAssocPair.getSecond();
// Fire node policies. This ensures that each node in the hierarchy gets a notification fired.
invokeBeforeDeleteNode(childNodeRef);
invokeBeforeCreateNode(
newParentAssocRef.getParentRef(),
newParentAssocRef.getTypeQName(),
newParentAssocRef.getQName(),
childNodeTypeQName);
// Move the node
handleStoreMoveConflicts(oldChildNodePair, storeRef);
// Change the store
newChildNodePair = nodeDaoService.moveNodeToStore(oldChildNodePair.getFirst(), storeRef);
// Get the new parent assoc
newParentAssocPair = nodeDaoService.getPrimaryParentAssoc(childNodeId);
// Index
if (indexChildren)
{
nodeIndexer.indexDeleteNode(oldParentAssocPair.getSecond());
nodeIndexer.indexCreateNode(newParentAssocPair.getSecond());
}
else
{
// The node we have just moved doesn't have it's children indexed, so tag it
nodeDaoService.addNodeAspects(childNodeId, Collections.singleton(ContentModel.ASPECT_INDEX_CHILDREN));
}
// Fire node policies. This ensures that each node in the hierarchy gets a notification fired.
invokeOnDeleteNode(oldParentAssocPair.getSecond(), childNodeTypeQName, childNodeAspectQNames, true);
invokeOnCreateNode(newParentAssocPair.getSecond());
// Cascade, if required
if (cascade)
{
pullNodeChildrenToSameStore(newChildNodePair, cascade, indexChildren);
}
}
}
public void indexChildren(Pair nodePair, boolean cascade)
{
Long nodeId = nodePair.getFirst();
// Get the node's children, but only one's that aren't in the same store
final List> childNodePairs = new ArrayList>(5);
NodeDaoService.ChildAssocRefQueryCallback callback = new NodeDaoService.ChildAssocRefQueryCallback()
{
public boolean handle(
Pair childAssocPair,
Pair parentNodePair,
Pair childNodePair
)
{
// Add it
childNodePairs.add(childNodePair);
return false;
}
};
nodeDaoService.getPrimaryChildAssocs(nodeId, callback);
// Each child must be moved to the same store as the parent
for (Pair oldChildNodePair : childNodePairs)
{
Long childNodeId = oldChildNodePair.getFirst();
NodeRef oldChildNodeRef = oldChildNodePair.getSecond();
Pair newChildNodePair = oldChildNodePair;
// Touch the node child node so that index tracking will work
nodeDaoService.setNodeStatus(childNodeId);
// Index
nodeIndexer.indexUpdateNode(oldChildNodeRef);
// Cascade, if required
if (cascade)
{
indexChildren(newChildNodePair, cascade);
}
else
{
// We didn't cascade to the children, so tag the node to index the children later
nodeDaoService.addNodeAspects(childNodeId, Collections.singleton(ContentModel.ASPECT_INDEX_CHILDREN));
}
}
// We have indexed the children, so remove the tagging aspect
nodeDaoService.removeNodeAspects(nodeId, Collections.singleton(ContentModel.ASPECT_INDEX_CHILDREN));
}
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);
}
}
private String extractNameProperty(Map properties)
{
Serializable nameValue = properties.get(ContentModel.PROP_NAME);
String name = (String) DefaultTypeConverter.INSTANCE.convert(String.class, nameValue);
return name;
}
private void setChildNameUnique(Pair childAssocPair, Pair childNodePair)
{
// Get the node's existing name
Serializable nameValue = nodeDaoService.getNodeProperty(childNodePair.getFirst(), ContentModel.PROP_NAME);
String name = (String) DefaultTypeConverter.INSTANCE.convert(String.class, nameValue);
setChildNameUnique(childAssocPair, name, null);
}
/**
* Ensures name uniqueness for the child and the child association. Note that nothing is done if the
* association type doesn't enforce name uniqueness.
*/
private void setChildNameUnique(Pair childAssocPair, String newName, String oldName)
{
if (childAssocPair == null)
{
// This happens if the node is a root node
return;
}
else if (EqualsHelper.nullSafeEquals(newName, oldName))
{
// The name has not changed
return;
}
Long assocId = childAssocPair.getFirst();
QName assocTypeQName = childAssocPair.getSecond().getTypeQName();
AssociationDefinition assocDef = dictionaryService.getAssociation(assocTypeQName);
if (!assocDef.isChild())
{
throw new IllegalArgumentException("Child association has non-child type: " + assocId);
}
ChildAssociationDefinition childAssocDef = (ChildAssociationDefinition) assocDef;
if (!childAssocDef.getDuplicateChildNamesAllowed())
{
nodeDaoService.setChildNameUnique(assocId, newName);
}
}
public static class MoveChildrenToCorrectStore extends AbstractNodeCleanupWorker
{
@Override
protected List doCleanInternal() throws Throwable
{
return dbNodeService.moveChildrenToCorrectStore();
}
};
private List moveChildrenToCorrectStore()
{
List results = new ArrayList(1000);
// Repeat the process for each store
List> storePairs = nodeDaoService.getStores();
for (Pair storePair : storePairs)
{
List storeResults = moveChildrenToCorrectStore(storePair.getFirst());
results.addAll(storeResults);
}
return results;
}
private List moveChildrenToCorrectStore(final Long storeId)
{
final List> parentNodePairs = new ArrayList>(100);
final NodeRefQueryCallback callback = new NodeRefQueryCallback()
{
public boolean handle(Pair nodePair)
{
parentNodePairs.add(nodePair);
return true;
}
};
RetryingTransactionCallback