mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
Content and folder node archival
git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2767 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -113,13 +113,15 @@ public abstract class AbstractNodeServiceImpl implements NodeService
|
||||
private AssociationPolicyDelegate<OnDeleteAssociationPolicy> onDeleteAssociationDelegate;
|
||||
|
||||
/**
|
||||
* @param policyComponent the component with which to register class policies and behaviour
|
||||
* @param dictionaryService
|
||||
* used to check that node operations conform to the model
|
||||
*
|
||||
*/
|
||||
protected AbstractNodeServiceImpl(PolicyComponent policyComponent)
|
||||
protected AbstractNodeServiceImpl()
|
||||
{
|
||||
this.uuid = GUID.generate();
|
||||
}
|
||||
|
||||
public void setPolicyComponent(PolicyComponent policyComponent)
|
||||
{
|
||||
this.policyComponent = policyComponent;
|
||||
}
|
||||
|
||||
|
@@ -373,16 +373,21 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest
|
||||
return ret;
|
||||
}
|
||||
|
||||
private int countNodesById(NodeRef nodeRef)
|
||||
private int countNodesByReference(NodeRef nodeRef)
|
||||
{
|
||||
String query =
|
||||
"select count(node.uuid)" +
|
||||
" from " +
|
||||
NodeImpl.class.getName() + " node" +
|
||||
" where node.uuid = ?";
|
||||
" where" +
|
||||
" node.uuid = ? and" +
|
||||
" node.store.key.protocol = ? and" +
|
||||
" node.store.key.identifier = ?";
|
||||
Session session = getSession();
|
||||
List results = session.createQuery(query)
|
||||
.setString(0, nodeRef.getId())
|
||||
.setString(1, nodeRef.getStoreRef().getProtocol())
|
||||
.setString(2, nodeRef.getStoreRef().getIdentifier())
|
||||
.list();
|
||||
Integer count = (Integer) results.get(0);
|
||||
return count.intValue();
|
||||
@@ -591,7 +596,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest
|
||||
ContentModel.TYPE_CONTAINER);
|
||||
NodeRef nodeRef = assocRef.getChildRef();
|
||||
// count the nodes with the given id
|
||||
int count = countNodesById(nodeRef);
|
||||
int count = countNodesByReference(nodeRef);
|
||||
assertEquals("Unexpected number of nodes present", 1, count);
|
||||
}
|
||||
|
||||
@@ -693,11 +698,11 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest
|
||||
|
||||
// delete n1
|
||||
nodeService.deleteNode(n1Ref);
|
||||
assertEquals("Node not directly deleted", 0, countNodesById(n1Ref));
|
||||
assertEquals("Node not cascade deleted", 0, countNodesById(n3Ref));
|
||||
assertEquals("Node incorrectly cascade deleted", 1, countNodesById(n4Ref));
|
||||
assertEquals("Node not cascade deleted", 0, countNodesById(n6Ref));
|
||||
assertEquals("Node not cascade deleted", 0, countNodesById(n8Ref));
|
||||
assertEquals("Node not directly deleted", 0, countNodesByReference(n1Ref));
|
||||
assertEquals("Node not cascade deleted", 0, countNodesByReference(n3Ref));
|
||||
assertEquals("Node incorrectly cascade deleted", 1, countNodesByReference(n4Ref));
|
||||
assertEquals("Node not cascade deleted", 0, countNodesByReference(n6Ref));
|
||||
assertEquals("Node not cascade deleted", 0, countNodesByReference(n8Ref));
|
||||
|
||||
// commit to check
|
||||
setComplete();
|
||||
|
65
source/java/org/alfresco/repo/node/StoreArchiveMap.java
Normal file
65
source/java/org/alfresco/repo/node/StoreArchiveMap.java
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
|
||||
/**
|
||||
* A map component that maps <b>node stores</b> to their archive <b>stores</b>.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class StoreArchiveMap
|
||||
{
|
||||
private Map<StoreRef, StoreRef> storeArchiveMap;
|
||||
|
||||
public StoreArchiveMap()
|
||||
{
|
||||
storeArchiveMap = new HashMap<StoreRef, StoreRef>(0);
|
||||
}
|
||||
|
||||
public Map<StoreRef, StoreRef> getArchiveMap()
|
||||
{
|
||||
return storeArchiveMap;
|
||||
}
|
||||
|
||||
public void setArchiveMap(Map<String, String> archiveMap)
|
||||
{
|
||||
// translate all the entries to references
|
||||
for (Map.Entry<String, String> entry : archiveMap.entrySet())
|
||||
{
|
||||
String storeRefKeyStr = entry.getKey();
|
||||
String storeRefValueStr = entry.getValue();
|
||||
StoreRef storeRefKey = null;
|
||||
StoreRef storeRefValue = null;
|
||||
try
|
||||
{
|
||||
storeRefKey = new StoreRef(storeRefKeyStr);
|
||||
storeRefValue = new StoreRef(storeRefValueStr);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Unable create store references from map entry: " + entry);
|
||||
}
|
||||
storeArchiveMap.put(storeRefKey, storeRefValue);
|
||||
}
|
||||
}
|
||||
}
|
@@ -20,6 +20,7 @@ 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.List;
|
||||
@@ -35,7 +36,9 @@ 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.policy.PolicyComponent;
|
||||
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.ClassDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
|
||||
@@ -56,8 +59,11 @@ 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.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -67,20 +73,31 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class DbNodeServiceImpl extends AbstractNodeServiceImpl
|
||||
{
|
||||
private final DictionaryService dictionaryService;
|
||||
private final NodeDaoService nodeDaoService;
|
||||
private static Log logger = LogFactory.getLog(DbNodeServiceImpl.class);
|
||||
|
||||
public DbNodeServiceImpl(
|
||||
PolicyComponent policyComponent,
|
||||
DictionaryService dictionaryService,
|
||||
NodeDaoService nodeDaoService)
|
||||
private DictionaryService dictionaryService;
|
||||
private NodeDaoService nodeDaoService;
|
||||
private StoreArchiveMap storeArchiveMap;
|
||||
|
||||
public DbNodeServiceImpl()
|
||||
{
|
||||
}
|
||||
|
||||
public void setDictionaryService(DictionaryService dictionaryService)
|
||||
{
|
||||
super(policyComponent);
|
||||
|
||||
this.dictionaryService = dictionaryService;
|
||||
}
|
||||
|
||||
public void setNodeDaoService(NodeDaoService nodeDaoService)
|
||||
{
|
||||
this.nodeDaoService = nodeDaoService;
|
||||
}
|
||||
|
||||
public void setStoreArchiveMap(StoreArchiveMap storeArchiveMap)
|
||||
{
|
||||
this.storeArchiveMap = storeArchiveMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a null-safe get of the node
|
||||
*
|
||||
@@ -97,6 +114,22 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
|
||||
}
|
||||
return unchecked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a null-safe get of the store
|
||||
* @param storeRef the store to retrieve
|
||||
* @return Returns the store entity (never null)
|
||||
* @throws InvalidStoreRefException if the referenced store could not be found
|
||||
*/
|
||||
private Store getStoreNotNull(StoreRef storeRef) throws InvalidStoreRefException
|
||||
{
|
||||
Store unchecked = nodeDaoService.getStore(storeRef.getProtocol(), storeRef.getIdentifier());
|
||||
if (unchecked == null)
|
||||
{
|
||||
throw new InvalidStoreRefException("Store does not exist: " + storeRef, storeRef);
|
||||
}
|
||||
return unchecked;
|
||||
}
|
||||
|
||||
public boolean exists(StoreRef storeRef)
|
||||
{
|
||||
@@ -116,7 +149,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
|
||||
|
||||
public Status getNodeStatus(NodeRef nodeRef)
|
||||
{
|
||||
NodeStatus nodeStatus = nodeDaoService.getNodeStatus(nodeRef);
|
||||
NodeStatus nodeStatus = nodeDaoService.getNodeStatus(nodeRef, false);
|
||||
if (nodeStatus == null) // node never existed
|
||||
{
|
||||
return null;
|
||||
@@ -306,7 +339,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
|
||||
|
||||
// add all the aspects to the node
|
||||
Set<QName> nodeAspects = node.getAspects();
|
||||
for (AspectDefinition defaultAspectDef : defaultAspectDefs)
|
||||
for (AspectDefinition defaultAspectDef : defaultAspectDefs)
|
||||
{
|
||||
invokeBeforeAddAspect(nodeRef, defaultAspectDef.getName());
|
||||
nodeAspects.add(defaultAspectDef.getName());
|
||||
@@ -613,104 +646,232 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
|
||||
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();
|
||||
// get type and aspect QNames as they will be unavailable after the delete
|
||||
QName nodeTypeQName = node.getTypeQName();
|
||||
Set<QName> nodeAspectQNames = node.getAspects();
|
||||
// delete it
|
||||
nodeDaoService.deleteNode(node, true);
|
||||
|
||||
// check if we need to archive the node
|
||||
StoreRef storeRef = nodeRef.getStoreRef();
|
||||
StoreRef 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)
|
||||
{
|
||||
// perform a normal deletion
|
||||
nodeDaoService.deleteNode(node, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// archive it
|
||||
archiveNode(nodeRef, archiveStoreRef);
|
||||
}
|
||||
|
||||
// Invoke policy behaviours
|
||||
invokeOnDeleteNode(childAssocRef, nodeTypeQName, nodeAspectQNames);
|
||||
}
|
||||
// /**
|
||||
// * Recursive method to ensure cascade-deletion works with full invocation of policy behaviours.
|
||||
// * <p>
|
||||
// * The recursion will first cascade down primary associations before deleting all regular and
|
||||
// * child associations to and from it. After this, the node itself is deleted. This bottom-up
|
||||
// * behaviour ensures that the policy invocation behaviour, which currently relies on being able
|
||||
// * to inspect association source types, gets fired correctly.
|
||||
// */
|
||||
// public void deleteNode(NodeRef nodeRef)
|
||||
// {
|
||||
// // Invoke policy behaviours
|
||||
// invokeBeforeDeleteNode(nodeRef);
|
||||
//
|
||||
// // get the node
|
||||
// Node node = getNodeNotNull(nodeRef);
|
||||
//
|
||||
// // get node info (for invocation purposes) before any deletions occur
|
||||
// // get the primary parent-child relationship before it is gone
|
||||
// ChildAssociationRef primaryParentAssocRef = getPrimaryParent(nodeRef);
|
||||
// // get type and aspect QNames as they will be unavailable after the delete
|
||||
// QName nodeTypeQName = node.getTypeQName();
|
||||
// Set<QName> nodeAspectQNames = node.getAspects();
|
||||
//
|
||||
// // get all associations, forcing a load of the collections
|
||||
// Collection<ChildAssoc> childAssocs = new ArrayList<ChildAssoc>(node.getChildAssocs());
|
||||
// Collection<ChildAssoc> parentAssocs = new ArrayList<ChildAssoc>(node.getParentAssocs());
|
||||
// Collection<NodeAssoc> sourceAssocs = new ArrayList<NodeAssoc>(node.getSourceNodeAssocs());
|
||||
// Collection<NodeAssoc> targetAssocs = new ArrayList<NodeAssoc>(node.getTargetNodeAssocs());
|
||||
//
|
||||
// // remove all child associations, including the primary one
|
||||
// for (ChildAssoc childAssoc : childAssocs)
|
||||
// {
|
||||
// ChildAssociationRef childAssocRef = childAssoc.getChildAssocRef();
|
||||
// // cascade into primary associations
|
||||
// if (childAssoc.getIsPrimary())
|
||||
// {
|
||||
// NodeRef childNodeRef = childAssocRef.getChildRef();
|
||||
// this.deleteNode(childNodeRef);
|
||||
// }
|
||||
//
|
||||
// // one or more of these associations may have been dealt with when deleting the
|
||||
// // child, so check that the association is valid
|
||||
//
|
||||
// // invoke pre-deletion behaviour
|
||||
// invokeBeforeDeleteChildAssociation(childAssocRef);
|
||||
// // remove it - cascade just to be sure
|
||||
// nodeDaoService.deleteChildAssoc(childAssoc, true);
|
||||
// // invoke post-deletion behaviour
|
||||
// invokeOnDeleteChildAssociation(childAssocRef);
|
||||
// }
|
||||
// // remove all parent associations, including the primary one
|
||||
// for (ChildAssoc parentAssoc : parentAssocs)
|
||||
// {
|
||||
// ChildAssociationRef parentAssocRef = parentAssoc.getChildAssocRef();
|
||||
// // invoke pre-deletion behaviour
|
||||
// invokeBeforeDeleteChildAssociation(parentAssocRef);
|
||||
// // remove it - don't cascade as this is a parent assoc
|
||||
// nodeDaoService.deleteChildAssoc(parentAssoc, false);
|
||||
// // invoke post-deletion behaviour
|
||||
// invokeOnDeleteChildAssociation(parentAssocRef);
|
||||
// }
|
||||
// // remove all source node associations
|
||||
// for (NodeAssoc sourceAssoc : sourceAssocs)
|
||||
// {
|
||||
// AssociationRef sourceAssocRef = sourceAssoc.getNodeAssocRef();
|
||||
// // remove it
|
||||
// nodeDaoService.deleteNodeAssoc(sourceAssoc);
|
||||
// // invoke post-deletion behaviour
|
||||
// invokeOnDeleteAssociation(sourceAssocRef);
|
||||
// }
|
||||
// // remove all target node associations
|
||||
// for (NodeAssoc targetAssoc : targetAssocs)
|
||||
// {
|
||||
// AssociationRef targetAssocRef = targetAssoc.getNodeAssocRef();
|
||||
// // remove it
|
||||
// nodeDaoService.deleteNodeAssoc(targetAssoc);
|
||||
// // invoke post-deletion behaviour
|
||||
// invokeOnDeleteAssociation(targetAssocRef);
|
||||
// }
|
||||
//
|
||||
// // delete it
|
||||
// // We cascade so that we are sure that any new children created by policy implementations are
|
||||
// // removed. There won't be any noticiations for these, but it prevents the cascade and
|
||||
// // notifications from chasing each other
|
||||
// nodeDaoService.deleteNode(node, true);
|
||||
//
|
||||
// // Invoke policy behaviours
|
||||
// invokeOnDeleteNode(primaryParentAssocRef, nodeTypeQName, nodeAspectQNames);
|
||||
// }
|
||||
|
||||
private void archiveNode(NodeRef nodeRef, StoreRef archiveStoreRef)
|
||||
{
|
||||
Node node = getNodeNotNull(nodeRef);
|
||||
Store archiveStore = getStoreNotNull(archiveStoreRef);
|
||||
ChildAssoc primaryParentAssoc = nodeDaoService.getPrimaryParentAssoc(node);
|
||||
|
||||
// add the aspect
|
||||
node.getAspects().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),
|
||||
primaryParentAssoc.getParent().getNodeRef());
|
||||
properties.put(ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT, archivedPrimaryParentNodeRefProperty);
|
||||
|
||||
// get the IDs of all the node's primary children, including its own
|
||||
Map<Long, Node> nodesById = new HashMap<Long, Node>(29);
|
||||
getPrimaryChildren(node, nodesById);
|
||||
|
||||
// move each node into the archive store
|
||||
for (Node nodeToMove : nodesById.values())
|
||||
{
|
||||
NodeRef oldNodeRef = nodeToMove.getNodeRef();
|
||||
nodeToMove.setStore(archiveStore);
|
||||
NodeRef newNodeRef = nodeToMove.getNodeRef();
|
||||
|
||||
// update change statuses
|
||||
String txnId = AlfrescoTransactionSupport.getTransactionId();
|
||||
NodeStatus oldNodeStatus = nodeDaoService.getNodeStatus(oldNodeRef, true);
|
||||
oldNodeStatus.setNode(null);
|
||||
oldNodeStatus.setChangeTxnId(txnId);
|
||||
NodeStatus newNodeStatus = nodeDaoService.getNodeStatus(newNodeRef, true);
|
||||
newNodeStatus.setNode(nodeToMove);
|
||||
newNodeStatus.setChangeTxnId(txnId);
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
// now associate the top-level node with the root of the new store
|
||||
NodeRef archiveStoreRootNodeRef = getRootNode(archiveStoreRef);
|
||||
Node archiveStoreRootNode = getNodeNotNull(archiveStoreRootNodeRef);
|
||||
QName assocTypeQName = ContentModel.ASSOC_CHILDREN;
|
||||
QName assocQName = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "archivedItem");
|
||||
|
||||
invokeBeforeCreateChildAssociation(archiveStoreRootNodeRef, nodeRef, assocTypeQName, assocQName);
|
||||
invokeBeforeUpdateNode(archiveStoreRootNodeRef);
|
||||
|
||||
// create a new assoc
|
||||
ChildAssoc newAssoc = nodeDaoService.newChildAssoc(archiveStoreRootNode, node, true, assocTypeQName, assocQName);
|
||||
|
||||
// invoke policy behaviour
|
||||
invokeOnCreateChildAssociation(newAssoc.getChildAssocRef());
|
||||
invokeOnUpdateNode(archiveStoreRootNodeRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private void getPrimaryChildren(Node node, Map<Long, Node> nodesById)
|
||||
{
|
||||
Long id = node.getId();
|
||||
if (nodesById.containsKey(id))
|
||||
{
|
||||
// this ID was already added - circular reference
|
||||
logger.warn("Circular hierarchy found including node " + id);
|
||||
return;
|
||||
}
|
||||
// add the node to the map
|
||||
nodesById.put(id, node);
|
||||
// recurse into the primary children
|
||||
Collection<ChildAssoc> childAssocs = node.getChildAssocs();
|
||||
for (ChildAssoc childAssoc : childAssocs)
|
||||
{
|
||||
// cascade into primary associations
|
||||
if (childAssoc.getIsPrimary())
|
||||
{
|
||||
Node primaryChild = childAssoc.getChild();
|
||||
getPrimaryChildren(primaryChild, nodesById);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Archive all associations to and from the given node, with the
|
||||
* exception of associations to or from nodes in the given map.
|
||||
* @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);
|
||||
for (ChildAssoc assoc : node.getChildAssocs())
|
||||
{
|
||||
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;
|
||||
}
|
||||
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 : node.getSourceNodeAssocs())
|
||||
{
|
||||
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 : node.getTargetNodeAssocs())
|
||||
{
|
||||
Long relatedNodeId = assoc.getSource().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 ChildAssociationRef addChild(NodeRef parentRef, NodeRef childRef, QName assocTypeQName, QName assocQName)
|
||||
{
|
||||
|
@@ -112,7 +112,7 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
|
||||
* <li>deletion</li>
|
||||
* </ul>
|
||||
*/
|
||||
public void testNodeStatus() throws Exception
|
||||
public void testNodeStatus() throws Throwable
|
||||
{
|
||||
Map<QName, ChildAssociationRef> assocRefs = buildNodeGraph();
|
||||
// get the node to play with
|
||||
@@ -190,13 +190,13 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
|
||||
public Object doWork()
|
||||
{
|
||||
// check n6
|
||||
NodeStatus n6Status = nodeDaoService.getNodeStatus(n6Ref);
|
||||
NodeStatus n6Status = nodeDaoService.getNodeStatus(n6Ref, false);
|
||||
if (!n6Status.isDeleted())
|
||||
{
|
||||
throw new RuntimeException("Deleted node does not have deleted status");
|
||||
}
|
||||
// n8 is a primary child - it should be deleted too
|
||||
NodeStatus n8Status = nodeDaoService.getNodeStatus(n8Ref);
|
||||
NodeStatus n8Status = nodeDaoService.getNodeStatus(n8Ref, false);
|
||||
if (!n8Status.isDeleted())
|
||||
{
|
||||
throw new RuntimeException("Cascade-deleted node does not have deleted status");
|
||||
@@ -228,7 +228,7 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
|
||||
TransactionUtil.executeInUserTransaction(txnService, checkRecreateWork);
|
||||
}
|
||||
|
||||
private void executeAndCheck(NodeRef nodeRef, TransactionWork<Object> work) throws Exception
|
||||
private void executeAndCheck(NodeRef nodeRef, TransactionWork<Object> work) throws Throwable
|
||||
{
|
||||
UserTransaction txn = txnService.getUserTransaction();
|
||||
txn.begin();
|
||||
@@ -248,7 +248,7 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
|
||||
assertEquals("Change didn't update status", currentTxnId, newStatus.getChangeTxnId());
|
||||
txn.commit();
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Throwable e)
|
||||
{
|
||||
try { txn.rollback(); } catch (Throwable ee) {}
|
||||
throw e;
|
||||
|
@@ -70,10 +70,11 @@ public interface NodeDaoService
|
||||
* <code>null</code> is returned.
|
||||
*
|
||||
* @param nodeRef the node reference
|
||||
* @param create true to create the entity if it doesn't exist
|
||||
* @return Returns the node status if the node exists or once existed, otherwise
|
||||
* returns <code>null</code>.
|
||||
* returns <code>null</code> if <code>create == false</code>
|
||||
*/
|
||||
public NodeStatus getNodeStatus(NodeRef nodeRef);
|
||||
public NodeStatus getNodeStatus(NodeRef nodeRef, boolean create);
|
||||
|
||||
/**
|
||||
* Sets the current transaction ID on the node status. Note that the node
|
||||
|
@@ -177,14 +177,13 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
|
||||
/**
|
||||
* Fetch the node status, if it exists
|
||||
*/
|
||||
public NodeStatus getNodeStatus(NodeRef nodeRef)
|
||||
public NodeStatus getNodeStatus(NodeRef nodeRef, boolean create)
|
||||
{
|
||||
NodeKey nodeKey = new NodeKey(nodeRef);
|
||||
NodeStatus status = null;
|
||||
try
|
||||
{
|
||||
NodeKey nodeKey = new NodeKey(nodeRef);
|
||||
Object obj = getHibernateTemplate().get(NodeStatusImpl.class, nodeKey);
|
||||
// done
|
||||
return (NodeStatus) obj;
|
||||
status = (NodeStatus) getHibernateTemplate().get(NodeStatusImpl.class, nodeKey);
|
||||
}
|
||||
catch (DataAccessException e)
|
||||
{
|
||||
@@ -195,6 +194,16 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
// create if necessary
|
||||
if (status == null && create)
|
||||
{
|
||||
status = new NodeStatusImpl();
|
||||
status.setKey(nodeKey);
|
||||
status.setChangeTxnId(AlfrescoTransactionSupport.getTransactionId());
|
||||
getHibernateTemplate().save(status);
|
||||
}
|
||||
// done
|
||||
return status;
|
||||
}
|
||||
|
||||
public void recordChangeId(NodeRef nodeRef)
|
||||
@@ -260,7 +269,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
|
||||
public Node getNode(NodeRef nodeRef)
|
||||
{
|
||||
// get it via the node status
|
||||
NodeStatus status = getNodeStatus(nodeRef);
|
||||
NodeStatus status = getNodeStatus(nodeRef, false);
|
||||
if (status == null)
|
||||
{
|
||||
// no status implies no node
|
||||
@@ -309,17 +318,9 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
|
||||
}
|
||||
// update the node status
|
||||
NodeRef nodeRef = node.getNodeRef();
|
||||
NodeStatus nodeStatus = getNodeStatus(nodeRef);
|
||||
if (nodeStatus == null)
|
||||
{
|
||||
logger.warn("Node to be deleted does not have a status: \n" +
|
||||
" node: " + node.getId());
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeStatus.setNode(null);
|
||||
nodeStatus.setChangeTxnId(AlfrescoTransactionSupport.getTransactionId());
|
||||
}
|
||||
NodeStatus nodeStatus = getNodeStatus(nodeRef, true);
|
||||
nodeStatus.setNode(null);
|
||||
nodeStatus.setChangeTxnId(AlfrescoTransactionSupport.getTransactionId());
|
||||
// finally delete the node
|
||||
getHibernateTemplate().delete(node);
|
||||
// done
|
||||
|
Reference in New Issue
Block a user