RM-1039: Can't move folder to Category with disposition schedule.

* improve general reliability of record folder move
  * fix up some issues with the way composite capabilities where being evaluated
  * use capabilities to enforce conditions of move .. not behavior .. this improves the visibility of the move action in the UI
  * unit test
  * reproduced and fixed up UI issue .. was showing No Items red banner in a very specific edge case



git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/modules/recordsmanagement/HEAD@56373 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Roy Wetherall
2013-10-08 04:22:34 +00:00
parent e84549afce
commit 48bcd03bd9
11 changed files with 309 additions and 75 deletions

View File

@@ -124,6 +124,7 @@
parent="declarativeCapability">
<property name="name" value="FileUnfiledRecords"/>
<property name="permission" value="FileUnfiledRecords"/>
<property name="kind" value="RECORD" />
<property name="conditions">
<map>
<entry key="capabilityCondition.filling" value="true"/> <!-- Checks if the user has the filling capability -->

View File

@@ -91,26 +91,36 @@ public class CompositeCapability extends DeclarativeCapability
{
int result = AccessDecisionVoter.ACCESS_ABSTAIN;
// Check we are dealing with a file plan component
if (filePlanService.isFilePlanComponent(source) == true &&
filePlanService.isFilePlanComponent(target) == true)
{
// Check the kind of the object, the permissions and the conditions
if (checkKinds(source) == true && checkPermissions(source) == true && checkConditions(source) == true)
{
if (targetCapability != null)
{
result = super.evaluate(source, target);
result = targetCapability.evaluate(target);
}
else
if (AccessDecisionVoter.ACCESS_DENIED != result)
{
// Check each capability using 'OR' logic
for (Capability capability : capabilities)
{
int capabilityResult = capability.evaluate(source, target);
if (capabilityResult != AccessDecisionVoter.ACCESS_DENIED)
result = capability.evaluate(source, target);
if (result == AccessDecisionVoter.ACCESS_GRANTED)
{
result = AccessDecisionVoter.ACCESS_ABSTAIN;
if (isUndetermined() == false && capabilityResult == AccessDecisionVoter.ACCESS_GRANTED)
{
result = AccessDecisionVoter.ACCESS_GRANTED;
}
break;
}
}
}
}
else
{
result = AccessDecisionVoter.ACCESS_DENIED;
}
}
return result;

View File

@@ -327,15 +327,11 @@ public class DeclarativeCapability extends AbstractCapability
@Override
public int evaluate(NodeRef source, NodeRef target)
{
int result = AccessDecisionVoter.ACCESS_ABSTAIN;
if (targetCapability != null)
{
result = evaluate(source);
if (result != AccessDecisionVoter.ACCESS_DENIED)
int result = evaluate(source);
if (targetCapability != null && result != AccessDecisionVoter.ACCESS_DENIED)
{
result = targetCapability.evaluate(target);
}
}
return result;
}

View File

@@ -208,6 +208,19 @@ public class DispositionServiceImpl implements
{
if (nodeService.exists(nodeRef) == true)
{
refreshDispositionAction(nodeRef);
}
}
/**
* Helper method used to refresh the dispostion action details of the given node.
*
* @param nodeRef node reference
*/
public void refreshDispositionAction(NodeRef nodeRef)
{
ParameterCheck.mandatory("nodeRef", nodeRef);
// get this disposition instructions for the node
DispositionSchedule di = getDispositionSchedule(nodeRef);
if (di != null)
@@ -222,7 +235,7 @@ public class DispositionServiceImpl implements
initialiseDispositionAction(nodeRef, nextDispositionActionDefinition);
}
}
}
}
/** ========= Disposition Property Methods ========= */
@@ -733,7 +746,10 @@ public class DispositionServiceImpl implements
if (result == false)
{
DispositionAction da = new DispositionActionImpl(serviceRegistry, nextDa);
boolean firstComplete = da.getDispositionActionDefinition().eligibleOnFirstCompleteEvent();
DispositionActionDefinition dad = da.getDispositionActionDefinition();
if (dad != null)
{
boolean firstComplete = dad.eligibleOnFirstCompleteEvent();
List<ChildAssociationRef> assocs = this.nodeService.getChildAssocs(nextDa, ASSOC_EVENT_EXECUTIONS, RegexQNamePattern.MATCH_ALL);
for (ChildAssociationRef assoc : assocs)
@@ -766,6 +782,7 @@ public class DispositionServiceImpl implements
}
}
}
}
return result;
}

View File

@@ -25,31 +25,36 @@ import java.util.Map;
import org.alfresco.module.org_alfresco_module_rm.RecordsManagementService;
import org.alfresco.module.org_alfresco_module_rm.RecordsManagementServiceRegistry;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionServiceImpl;
import org.alfresco.module.org_alfresco_module_rm.identifier.IdentifierService;
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
import org.alfresco.module.org_alfresco_module_rm.record.RecordService;
import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderServiceImpl;
import org.alfresco.repo.copy.CopyBehaviourCallback;
import org.alfresco.repo.copy.CopyDetails;
import org.alfresco.repo.copy.DefaultCopyBehaviourCallback;
import org.alfresco.repo.copy.DoNothingCopyBehaviourCallback;
import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
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.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* Class containing behaviour for the vitalRecordDefinition aspect.
*
* @author neilm
*/
public class RecordCopyBehaviours implements RecordsManagementModel
public class RecordCopyBehaviours implements RecordsManagementModel,
ApplicationContextAware
{
/** The policy component */
private PolicyComponent policyComponent;
@@ -63,6 +68,18 @@ public class RecordCopyBehaviours implements RecordsManagementModel
/** List of aspects to remove during move and copy */
private List<QName> unwantedAspects = new ArrayList<QName>(5);
/** Application context */
private ApplicationContext applicationContext;
/**
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
this.applicationContext = applicationContext;
}
/**
* Set the policy component
*
@@ -193,7 +210,7 @@ public class RecordCopyBehaviours implements RecordsManagementModel
{
if (!oldChildAssocRef.getParentRef().equals(newChildAssocRef.getParentRef()))
{
final NodeRef oldNodeRef = oldChildAssocRef.getChildRef();
//final NodeRef oldNodeRef = oldChildAssocRef.getChildRef();
final NodeRef newNodeRef = newChildAssocRef.getChildRef();
AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Object>()
@@ -201,25 +218,36 @@ public class RecordCopyBehaviours implements RecordsManagementModel
public Object doWork() throws Exception
{
final RecordsManagementService rmService = rmServiceRegistry.getRecordsManagementService();
final DispositionService rmDispositionService = rmServiceRegistry.getDispositionService();
final RecordService rmRecordService = rmServiceRegistry.getRecordService();
final RecordFolderServiceImpl recordFolderService = (RecordFolderServiceImpl)applicationContext.getBean("recordFolderService");
final DispositionServiceImpl dispositionService = (DispositionServiceImpl)applicationContext.getBean("dispositionService");
if (rmDispositionService.getDispositionSchedule(oldNodeRef) != null || rmService.isCutoff(oldNodeRef))
{
throw new UnsupportedOperationException("Moving a record folder that has a disposition schedule or is cutoff is not suported.");
}
else
behaviourFilter.disableBehaviour();
try
{
// Remove unwanted aspects
removeUnwantedAspects(nodeService, newNodeRef);
// reinitialise the record folder
recordFolderService.initialiseRecordFolder(newNodeRef);
// reinitialise the record folder disposition action details
dispositionService.refreshDispositionAction(newNodeRef);
// Sort out the child records
for (NodeRef record : rmService.getRecords(newNodeRef))
{
// Remove unwanted aspects
removeUnwantedAspects(nodeService, record);
// Re-initiate the records in the new folder.
rmRecordService.file(record);
}
}
finally
{
behaviourFilter.enableBehaviour();
}
return null;
}

View File

@@ -891,7 +891,6 @@ public class RecordServiceImpl implements RecordService,
@Override
public void file(NodeRef record)
{
ParameterCheck.mandatory("item", record);
// we only support filling of content items

View File

@@ -28,5 +28,5 @@ package org.alfresco.module.org_alfresco_module_rm.recordfolder;
*/
public interface RecordFolderService
{
// TODO see RecordManagementService for Record Folder methods that need moving into this interface
}

View File

@@ -21,6 +21,7 @@ package org.alfresco.module.org_alfresco_module_rm.test;
import org.alfresco.module.org_alfresco_module_rm.test.issue.RM1008Test;
import org.alfresco.module.org_alfresco_module_rm.test.issue.RM1027Test;
import org.alfresco.module.org_alfresco_module_rm.test.issue.RM1030Test;
import org.alfresco.module.org_alfresco_module_rm.test.issue.RM1039Test;
import org.alfresco.module.org_alfresco_module_rm.test.issue.RM452Test;
import org.alfresco.module.org_alfresco_module_rm.test.issue.RM994Test;
import org.junit.runner.RunWith;
@@ -40,7 +41,8 @@ import org.junit.runners.Suite.SuiteClasses;
RM994Test.class,
RM1008Test.class,
RM1030Test.class,
RM1027Test.class
RM1027Test.class,
RM1039Test.class
})
public class IssueTestSuite
{

View File

@@ -25,7 +25,7 @@ import org.alfresco.service.cmr.repository.NodeRef;
/**
* Unit test for RM-1030 .. can't freeze a record folder that already has a frozen record containted within
* Unit test for RM-1030 .. can't freeze a record folder that already has a frozen record contained within
*
* @author Roy Wetherall
* @since 2.1

View File

@@ -0,0 +1,181 @@
/*
* Copyright (C) 2005-2013 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.module.org_alfresco_module_rm.test.issue;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import net.sf.acegisecurity.vote.AccessDecisionVoter;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.action.impl.CompleteEventAction;
import org.alfresco.module.org_alfresco_module_rm.action.impl.CutOffAction;
import org.alfresco.module.org_alfresco_module_rm.capability.Capability;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction;
import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase;
import org.alfresco.module.org_alfresco_module_rm.test.util.CommonRMTestUtils;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* Unit test for RM-1039 ... can't move a folder into a category with a disposition schedule
*
* @author Roy Wetherall
* @since 2.1
*/
public class RM1039Test extends BaseRMTestCase
{
@Override
protected boolean isRecordTest()
{
return true;
}
// try and move a folder from no disposition schedule to a disposition schedule
public void testMoveRecordFolderFromNoDisToDis() throws Exception
{
final NodeRef recordFolder = doTestInTransaction(new Test<NodeRef>()
{
@Override
public NodeRef run()
{
// create a record category (no disposition schedule)
NodeRef recordCategory = filePlanService.createRecordCategory(filePlan, "Caitlin Reed");
// create a record folder
return rmService.createRecordFolder(recordCategory, "Grace Wetherall");
}
@Override
public void test(NodeRef result) throws Exception
{
assertNotNull(result);
assertNull(dispositionService.getDispositionSchedule(result));
assertFalse(nodeService.hasAspect(result, ASPECT_DISPOSITION_LIFECYCLE));
}
});
final NodeRef record = doTestInTransaction(new Test<NodeRef>()
{
@Override
public NodeRef run()
{
// create a record
return fileFolderService.create(recordFolder, "mytest.txt", ContentModel.TYPE_CONTENT).getNodeRef();
}
@Override
public void test(NodeRef result) throws Exception
{
assertNotNull(result);
assertNull(dispositionService.getDispositionSchedule(result));
assertFalse(nodeService.hasAspect(result, ASPECT_DISPOSITION_LIFECYCLE));
}
});
doTestInTransaction(new Test<NodeRef>()
{
@Override
public NodeRef run() throws Exception
{
Capability capability = capabilityService.getCapability("CreateModifyDestroyFolders");
assertEquals(AccessDecisionVoter.ACCESS_GRANTED, capability.evaluate(recordFolder));
assertEquals(AccessDecisionVoter.ACCESS_GRANTED, capability.evaluate(recordFolder, rmContainer));
// take a look at the move capability
Capability moveCapability = capabilityService.getCapability("Move");
assertEquals(AccessDecisionVoter.ACCESS_GRANTED, moveCapability.evaluate(recordFolder, rmContainer));
// move the node
return fileFolderService.move(recordFolder, rmContainer, null).getNodeRef();
}
@Override
public void test(NodeRef result) throws Exception
{
assertNotNull(result);
assertNotNull(dispositionService.getDispositionSchedule(result));
assertTrue(nodeService.hasAspect(result, ASPECT_DISPOSITION_LIFECYCLE));
DispositionAction dispositionAction = dispositionService.getNextDispositionAction(result);
assertNotNull(dispositionAction);
assertNull(dispositionAction.getAsOfDate());
assertEquals("cutoff", dispositionAction.getName());
assertEquals(1, dispositionAction.getEventCompletionDetails().size());
// take a look at the record and check things are as we would expect
assertFalse(nodeService.hasAspect(record, ASPECT_DISPOSITION_LIFECYCLE));
}
});
}
// move from a disposition schedule to another .. both record folder level
// move from a disposition schedule to another .. from record to folder level
// try and move a cutoff folder
public void testMoveCutoffRecordFolder() throws Exception
{
final NodeRef destination = doTestInTransaction(new Test<NodeRef>()
{
@Override
public NodeRef run()
{
// create a record category (no disposition schedule)
return filePlanService.createRecordCategory(filePlan, "Caitlin Reed");
}
});
final NodeRef testFolder = doTestInTransaction(new Test<NodeRef>()
{
@Override
public NodeRef run()
{
// create folder
NodeRef testFolder = rmService.createRecordFolder(rmContainer, "Peter Edward Francis");
// complete event
Map<String, Serializable> params = new HashMap<String, Serializable>(1);
params.put(CompleteEventAction.PARAM_EVENT_NAME, CommonRMTestUtils.DEFAULT_EVENT_NAME);
actionService.executeRecordsManagementAction(testFolder, CompleteEventAction.NAME, params);
// cutoff folder
actionService.executeRecordsManagementAction(testFolder, CutOffAction.NAME);
// take a look at the move capability
Capability moveCapability = capabilityService.getCapability("Move");
assertEquals(AccessDecisionVoter.ACCESS_DENIED, moveCapability.evaluate(testFolder, destination));
return testFolder;
}
});
doTestInTransaction(new FailureTest()
{
@Override
public void run() throws Exception
{
fileFolderService.move(testFolder, destination, null).getNodeRef();
}
});
}
}