Merged 5.0.N (5.0.4) to 5.1.N (5.1.2)

128165 amorarasu: Merged V4.2-BUG-FIX (4.2.7) to 5.0.N (5.0.4)
      128151 rneamtu: MNT-15855 : Restoring a site or a folder with a locked for offline edit file does not work
         - restored associations between working copy and original node after a parent node is deleted
         - added unit test for case


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/DEV/5.1.N/root@128192 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Ancuta Morarasu
2016-06-17 17:23:42 +00:00
parent 65baa06360
commit 6a17c7a688
5 changed files with 296 additions and 35 deletions

View File

@@ -761,6 +761,7 @@
<property name="policyComponent" ref="policyComponent" />
<property name="nodeService" ref="NodeService" />
<property name="lockService" ref="LockService" />
<property name="nodeDAO" ref="nodeDAO" />
<property name="checkOutCheckInService" ref="CheckoutCheckinService" />
<property name="policyBehaviourFilter" ref="policyBehaviourFilter" />
</bean>

View File

@@ -343,6 +343,34 @@
</properties>
</aspect>
<!--archived properties used to restore locked nodes-->
<aspect name="sys:archivedLockable">
<title>Archived Lockable</title>
<properties>
<property name="sys:archivedLockOwner">
<type>d:text</type>
<protected>true</protected>
</property>
<property name="sys:archivedLockType">
<type>d:text</type>
<protected>true</protected>
</property>
<property name="sys:archivedLockLifetime">
<type>d:text</type>
<protected>true</protected>
</property>
<property name="sys:archivedExpiryDate">
<type>d:date</type>
<protected>true</protected>
<mandatory>false</mandatory>
</property>
<property name="sys:archivedLockAdditionalInfo">
<type>d:text</type>
<protected>true</protected>
</property>
</properties>
</aspect>
<!--
Localization:
If you add this aspect to a node, then the server will assume that all non-multilingual

View File

@@ -27,7 +27,11 @@
package org.alfresco.repo.coci;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.alfresco.model.ContentModel;
@@ -35,22 +39,28 @@ import org.alfresco.repo.copy.CopyBehaviourCallback;
import org.alfresco.repo.copy.CopyDetails;
import org.alfresco.repo.copy.CopyServicePolicies;
import org.alfresco.repo.copy.DefaultCopyBehaviourCallback;
import org.alfresco.repo.domain.node.NodeDAO;
import org.alfresco.repo.lock.mem.Lifetime;
import org.alfresco.repo.node.NodeServicePolicies;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.service.cmr.coci.CheckOutCheckInService;
import org.alfresco.service.cmr.lock.LockService;
import org.alfresco.service.cmr.lock.LockType;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.GUID;
import org.alfresco.util.Pair;
public class WorkingCopyAspect implements CopyServicePolicies.OnCopyNodePolicy, NodeServicePolicies.OnRemoveAspectPolicy
public class WorkingCopyAspect implements CopyServicePolicies.OnCopyNodePolicy, NodeServicePolicies.OnRemoveAspectPolicy, NodeServicePolicies.BeforeArchiveNodePolicy, NodeServicePolicies.OnRestoreNodePolicy
{
private PolicyComponent policyComponent;
private NodeService nodeService;
private NodeDAO nodeDAO;
private LockService lockService;
private CheckOutCheckInService checkOutCheckInService;
private BehaviourFilter policyBehaviourFilter;
@@ -77,6 +87,14 @@ public class WorkingCopyAspect implements CopyServicePolicies.OnCopyNodePolicy,
this.nodeService = nodeService;
}
/**
* Set the node dao
*/
public void setNodeDAO(NodeDAO nodeDAO)
{
this.nodeDAO = nodeDAO;
}
/**
* Set the lock service
*/
@@ -120,12 +138,24 @@ public class WorkingCopyAspect implements CopyServicePolicies.OnCopyNodePolicy,
ContentModel.ASPECT_CHECKED_OUT,
new JavaBehaviour(this, "getCopyCallback"));
// register beforeArchiveNode class behaviour for the working copy aspect
this.policyComponent.bindClassBehaviour(
NodeServicePolicies.BeforeArchiveNodePolicy.QNAME,
ContentModel.ASPECT_WORKING_COPY,
new JavaBehaviour(this, "beforeArchiveNode"));
// register onBeforeDelete class behaviour for the working copy aspect
this.policyComponent.bindClassBehaviour(
NodeServicePolicies.BeforeDeleteNodePolicy.QNAME,
ContentModel.ASPECT_WORKING_COPY,
new JavaBehaviour(this, "beforeDeleteWorkingCopy"));
// register onRestoreNode class behaviour for archived Lockable aspect
this.policyComponent.bindClassBehaviour(
NodeServicePolicies.OnRestoreNodePolicy.QNAME,
ContentModel.ASPECT_ARCHIVE_LOCKABLE,
new JavaBehaviour(this, "onRestoreNode"));
// Watch for removal of the aspect and ensure that the cm:workingcopylink assoc is removed
this.policyComponent.bindClassBehaviour(
NodeServicePolicies.OnRemoveAspectPolicy.QNAME,
@@ -184,6 +214,163 @@ public class WorkingCopyAspect implements CopyServicePolicies.OnCopyNodePolicy,
}
/**
* beforeArchiveNode policy behaviour
*
* @param nodeRef
* the node reference about to be archived
*/
@Override
public void beforeArchiveNode(NodeRef workingCopyNodeRef)
{
NodeRef checkedOutNodeRef = checkOutCheckInService.getCheckedOut(workingCopyNodeRef);
if (checkedOutNodeRef != null)
{
try
{
policyBehaviourFilter.disableBehaviour(workingCopyNodeRef, ContentModel.ASPECT_AUDITABLE);
if (nodeService.hasAspect(checkedOutNodeRef, ContentModel.ASPECT_LOCKABLE))
{
Map<QName, Serializable> checkedOutNodeProperties = nodeService.getProperties(checkedOutNodeRef);
Map<QName, Serializable> workingCopyProperties = nodeService.getProperties(workingCopyNodeRef);
Long nodeId = nodeDAO.getNodePair(workingCopyNodeRef).getFirst();
//get lock properties from checked out node and set them on working copy node in order to be available for restore
String lockOwner = (String) checkedOutNodeProperties.get(ContentModel.PROP_LOCK_OWNER);
Date expiryDate = (Date) checkedOutNodeProperties.get(ContentModel.PROP_EXPIRY_DATE);
String lockTypeStr = (String) checkedOutNodeProperties.get(ContentModel.PROP_LOCK_TYPE);
LockType lockType = lockTypeStr != null ? LockType.valueOf(lockTypeStr) : null;
String lifetimeStr = (String) checkedOutNodeProperties.get(ContentModel.PROP_LOCK_LIFETIME);
Lifetime lifetime = lifetimeStr != null ? Lifetime.valueOf(lifetimeStr) : null;
String additionalInfo = (String) checkedOutNodeProperties.get(ContentModel.PROP_LOCK_ADDITIONAL_INFO);
nodeService.addAspect(workingCopyNodeRef, ContentModel.ASPECT_ARCHIVE_LOCKABLE, null);
workingCopyProperties.put(ContentModel.PROP_ARCHIVED_LOCK_OWNER, lockOwner);
workingCopyProperties.put(ContentModel.PROP_ARCHIVED_LOCK_TYPE, lockType);
workingCopyProperties.put(ContentModel.PROP_ARCHIVED_LOCK_LIFETIME, lifetime);
workingCopyProperties.put(ContentModel.PROP_ARCHIVED_EXPIRY_DATE, expiryDate);
workingCopyProperties.put(ContentModel.PROP_ARCHIVED_LOCK_ADDITIONAL_INFO, additionalInfo);
// Target associations
Collection<Pair<Long, AssociationRef>> targetAssocs = nodeDAO.getTargetNodeAssocs(nodeId, null);
for (Pair<Long, AssociationRef> targetAssocPair : targetAssocs)
{
if (ContentModel.ASSOC_ORIGINAL.equals(targetAssocPair.getSecond().getTypeQName()))
{
workingCopyProperties.put(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS, targetAssocPair.getSecond());
}
}
// Source associations
Collection<Pair<Long, AssociationRef>> sourceAssocs = nodeDAO.getSourceNodeAssocs(nodeId, null);
for (Pair<Long, AssociationRef> sourceAssocPair : sourceAssocs)
{
if (ContentModel.ASSOC_WORKING_COPY_LINK.equals(sourceAssocPair.getSecond().getTypeQName()))
{
workingCopyProperties.put(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS, sourceAssocPair.getSecond());
}
}
//update working copy node properties
nodeService.setProperties(workingCopyNodeRef, workingCopyProperties);
}
}
finally
{
policyBehaviourFilter.enableBehaviour(checkedOutNodeRef, ContentModel.ASPECT_AUDITABLE);
}
}
}
/**
* onRestoreNode policy behaviour
*
* @param nodeRef
* the node reference that was restored
*/
@SuppressWarnings("unchecked")
@Override
public void onRestoreNode(ChildAssociationRef childAssocRef)
{
NodeRef workingCopyNodeRef = childAssocRef.getChildRef();
//check that node is working copy
if (nodeService.hasAspect(workingCopyNodeRef, ContentModel.ASPECT_WORKING_COPY))
{
try
{
NodeRef checkedOutNodeRef = null;
policyBehaviourFilter.disableBehaviour(workingCopyNodeRef, ContentModel.ASPECT_AUDITABLE);
Map<QName, Serializable> workingCopyProperties = nodeService.getProperties(workingCopyNodeRef);
//get archived lock properties in order to be restored on the original node
String lockOwner = (String) workingCopyProperties.get(ContentModel.PROP_ARCHIVED_LOCK_OWNER);
workingCopyProperties.remove(ContentModel.PROP_ARCHIVED_LOCK_OWNER);
Date expiryDate = (Date) workingCopyProperties.get(ContentModel.PROP_ARCHIVED_EXPIRY_DATE);
workingCopyProperties.remove(ContentModel.PROP_ARCHIVED_EXPIRY_DATE);
String lockTypeStr = (String) workingCopyProperties.get(ContentModel.PROP_ARCHIVED_LOCK_TYPE);
workingCopyProperties.remove(ContentModel.PROP_ARCHIVED_LOCK_TYPE);
LockType lockType = lockTypeStr != null ? LockType.valueOf(lockTypeStr) : null;
String lifetimeStr = (String) workingCopyProperties.get(ContentModel.PROP_ARCHIVED_LOCK_LIFETIME);
workingCopyProperties.remove(ContentModel.PROP_ARCHIVED_LOCK_LIFETIME);
Lifetime lifetime = lifetimeStr != null ? Lifetime.valueOf(lifetimeStr) : null;
String additionalInfo = (String) workingCopyProperties.get(ContentModel.PROP_ARCHIVED_LOCK_ADDITIONAL_INFO);
workingCopyProperties.remove(ContentModel.PROP_ARCHIVED_LOCK_ADDITIONAL_INFO);
List<AssociationRef> targetAssocList = (ArrayList<AssociationRef>) workingCopyProperties
.get(ContentModel.PROP_ARCHIVED_TARGET_ASSOCS);
if (targetAssocList != null && targetAssocList.get(0) != null)
{
AssociationRef targetAssoc = (AssociationRef) targetAssocList.get(0);
checkedOutNodeRef = targetAssoc.getSourceRef();
nodeService.createAssociation( targetAssoc.getSourceRef(),targetAssoc.getTargetRef(), ContentModel.ASSOC_ORIGINAL);
}
workingCopyProperties.remove( ContentModel.PROP_ARCHIVED_TARGET_ASSOCS);
ArrayList<AssociationRef> sourceAssocList = (ArrayList<AssociationRef>) workingCopyProperties
.get(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS);
if (sourceAssocList != null && sourceAssocList.get(0) != null)
{
AssociationRef sourceAssoc = (AssociationRef) sourceAssocList.get(0);
checkedOutNodeRef = sourceAssoc.getSourceRef();
nodeService.createAssociation(sourceAssoc.getSourceRef(), sourceAssoc.getTargetRef(),
ContentModel.ASSOC_WORKING_COPY_LINK);
}
workingCopyProperties.remove(ContentModel.PROP_ARCHIVED_SOURCE_ASSOCS);
//clean up the archived aspect and properties for working copy node
nodeService.removeAspect(workingCopyNodeRef, ContentModel.ASPECT_ARCHIVE_LOCKABLE);
nodeService.setProperties(workingCopyNodeRef, workingCopyProperties);
//restore properties on original node
nodeService.addAspect(checkedOutNodeRef, ContentModel.ASPECT_LOCKABLE, null);
Map<QName, Serializable> checkedOutNodeProperties = nodeService.getProperties(checkedOutNodeRef);
checkedOutNodeProperties.put(ContentModel.PROP_LOCK_OWNER, lockOwner);
checkedOutNodeProperties.put(ContentModel.PROP_LOCK_TYPE, lockType);
checkedOutNodeProperties.put(ContentModel.PROP_LOCK_LIFETIME, lifetime);
checkedOutNodeProperties.put(ContentModel.PROP_EXPIRY_DATE, expiryDate);
checkedOutNodeProperties.put(ContentModel.PROP_LOCK_ADDITIONAL_INFO, additionalInfo);
nodeService.setProperties(checkedOutNodeRef, checkedOutNodeProperties);
}
finally
{
policyBehaviourFilter.enableBehaviour(workingCopyNodeRef, ContentModel.ASPECT_AUDITABLE);
}
}
}
@Override
public void onRemoveAspect(NodeRef nodeRef, QName aspectTypeQName)
{

View File

@@ -2777,6 +2777,8 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl implements Extens
StoreRef oldStoreRef = oldNodeToMoveRef.getStoreRef();
StoreRef newStoreRef = parentNodeRef.getStoreRef();
List<ChildAssociationRef> nodesToRestoreAssociationsFor = new ArrayList<ChildAssociationRef>();
// Get the primary parent association
Pair<Long, ChildAssociationRef> oldParentAssocPair = nodeDAO.getPrimaryParentAssoc(nodeToMoveId);
if (oldParentAssocPair == null)
@@ -2881,6 +2883,19 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl implements Extens
// Fire node policies. This ensures that each node in the hierarchy gets a notification fired.
invokeOnDeleteNode(oldChildAssoc, childNodeTypeQName, childNodeAspectQNames, true);
invokeOnCreateNode(newChildAssoc);
// collect working copy nodes that need to be updated; we need all nodes
// to be already moved when create association between nodes
if (hasAspect(newChildAssoc.getChildRef(), ContentModel.ASPECT_ARCHIVE_LOCKABLE))
{
nodesToRestoreAssociationsFor.add(newChildAssoc);
}
}
// invoke onRestoreNode for working copy nodes in order to restore original lock
for (ChildAssociationRef childAssoc : nodesToRestoreAssociationsFor)
{
invokeOnRestoreNode(childAssoc);
}
return newParentAssocRef;

View File

@@ -393,8 +393,8 @@ public class SiteServiceImplMoreTest
* This test also checks that after restore the locks are restored correctly for all the locked
* files;
*
* TODO in MNT-15855:
* It should also checks the case when there is a working copy (simulate lock for offline edit)
* MNT-15855:
* Checks the case when there is a working copy (simulate lock for offline edit)
*
* @throws Exception
*/
@@ -443,6 +443,7 @@ public class SiteServiceImplMoreTest
* |-- [folder] fileFolderPrefix + "subfolder"
* |
* |-- [cm:content] fileFolderPrefix + "fileInSubfolder.txt"
* --- [cm:content] fileFolderPrefix + "fileEditOfline.txt"
*/
String fileFolderPrefix = "TESTLOCK_";
String componentId = "doclib";
@@ -479,10 +480,10 @@ public class SiteServiceImplMoreTest
ContentWriter writer3 = FILE_FOLDER_SERVICE.getWriter(fileInfo3.getNodeRef());
writer3.putContent("Just some old content that doesn't mean anything");
// make sure there are no locks on the fileInfo yet
// Make sure there are no locks on the fileInfo yet
assertEquals(LockStatus.NO_LOCK, LOCK_SERVICE.getLockStatus(fileInfo.getNodeRef()));
// lock a file as userOwner
// Lock a file as userOwner
TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
@@ -492,15 +493,15 @@ public class SiteServiceImplMoreTest
}
});
// make sure we have a lock now
// Make sure we have a lock now
assertEquals(LockStatus.LOCK_OWNER, LOCK_SERVICE.getLockStatus(fileInfo.getNodeRef()));
checkThatNonMembersCanNotCreateFiles(userCollaborator, fileFolderPrefix, folder2Info);
// make sure we are running as userOwner
// Make sure we are running as userOwner
AUTHENTICATION_COMPONENT.setCurrentUser(userOwner);
// make userCollaborator a member of the site
// Make userCollaborator a member of the site
TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback<Object>()
{
public Object execute() throws Throwable
@@ -510,7 +511,7 @@ public class SiteServiceImplMoreTest
}
});
// now, as userCollaborator create a file and lock it
// Now, as userCollaborator create a file and lock it
AUTHENTICATION_COMPONENT.setCurrentUser(userCollaborator);
final FileInfo fileInfoForCollaboratorUser = FILE_FOLDER_SERVICE.create(
@@ -536,7 +537,32 @@ public class SiteServiceImplMoreTest
// Test valid lock
assertEquals(LockStatus.LOCK_OWNER, LOCK_SERVICE.getLockStatus(fileInfoForCollaboratorUser.getNodeRef()));
// switch back to userOwner so we can call delete the site
// Create a file to test Edit offline
final FileInfo fileEditOffline = FILE_FOLDER_SERVICE.create(
siteContainer,
fileFolderPrefix + "fileEditOfline.txt",
ContentModel.TYPE_CONTENT);
ContentWriter writerEO = FILE_FOLDER_SERVICE.getWriter(fileEditOffline.getNodeRef());
writerEO.putContent("Just some old content that doesn't mean anything");
//Make sure there are no locks on the fileEditOffline yet
assertEquals(LockStatus.NO_LOCK, LOCK_SERVICE.getLockStatus(fileEditOffline.getNodeRef()));
// Check out the document - simulate Edit offline
TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
NodeRef workingCopy = COCI_SERVICE.checkout(fileEditOffline.getNodeRef());
assertNotNull(workingCopy);
return null;
}
});
// Make sure we have a lock now on fileEditOffline
assertEquals(LockStatus.LOCK_OWNER, LOCK_SERVICE.getLockStatus(fileEditOffline.getNodeRef()));
// Switch back to userOwner so we can call delete the site
AUTHENTICATION_COMPONENT.setCurrentUser(userOwner);
// Delete the site
@@ -553,7 +579,7 @@ public class SiteServiceImplMoreTest
}
});
// restore the site
// Restore the site
TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
@@ -573,14 +599,18 @@ public class SiteServiceImplMoreTest
}
});
// check that the files have been restored and all the locks are present
// Check that the files have been restored and all the locks are present
AUTHENTICATION_COMPONENT.setCurrentUser(userCollaborator);
assertEquals(LockStatus.LOCK_OWNER, LOCK_SERVICE.getLockStatus(fileInfoForCollaboratorUser.getNodeRef()));
// Check that the file for edit offline has been restored and has the expected lock owner
assertEquals(LockStatus.LOCK_OWNER, LOCK_SERVICE.getLockStatus(fileEditOffline.getNodeRef()));
AUTHENTICATION_COMPONENT.setCurrentUser(userOwner);
assertEquals(LockStatus.LOCK_OWNER, LOCK_SERVICE.getLockStatus(fileInfo.getNodeRef()));
// Check that the file for edit offline has been restored and is locked
assertEquals(LockStatus.LOCKED, LOCK_SERVICE.getLockStatus(fileEditOffline.getNodeRef()));
// remove site completely
// Remove site completely
TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable