From 48bcd03bd9d33bbe36762eb1b44f2f436dd84319 Mon Sep 17 00:00:00 2001 From: Roy Wetherall Date: Tue, 8 Oct 2013 04:22:34 +0000 Subject: [PATCH] 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 --- .../rm-capabilities-record-context.xml | 1 + .../rm-capabilities-recordfolder-context.xml | 2 +- .../declarative/CompositeCapability.java | 40 ++-- .../declarative/DeclarativeCapability.java | 10 +- .../disposition/DispositionServiceImpl.java | 85 ++++---- .../model/behaviour/RecordCopyBehaviours.java | 56 ++++-- .../record/RecordServiceImpl.java | 1 - .../recordfolder/RecordFolderService.java | 2 +- .../test/IssueTestSuite.java | 4 +- .../test/issue/RM1030Test.java | 2 +- .../test/issue/RM1039Test.java | 181 ++++++++++++++++++ 11 files changed, 309 insertions(+), 75 deletions(-) create mode 100644 rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/issue/RM1039Test.java diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/capability/rm-capabilities-record-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/capability/rm-capabilities-record-context.xml index 15b1537226..943a12cc2c 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/capability/rm-capabilities-record-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/capability/rm-capabilities-record-context.xml @@ -124,6 +124,7 @@ parent="declarativeCapability"> + diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/capability/rm-capabilities-recordfolder-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/capability/rm-capabilities-recordfolder-context.xml index afc80e15bb..501c4d11a8 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/capability/rm-capabilities-recordfolder-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/capability/rm-capabilities-recordfolder-context.xml @@ -73,7 +73,7 @@ parent="compositeCapability"> - + diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/capability/declarative/CompositeCapability.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/capability/declarative/CompositeCapability.java index f82ecaa57f..eacdc6d369 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/capability/declarative/CompositeCapability.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/capability/declarative/CompositeCapability.java @@ -91,25 +91,35 @@ public class CompositeCapability extends DeclarativeCapability { int result = AccessDecisionVoter.ACCESS_ABSTAIN; - if (targetCapability != null) + // Check we are dealing with a file plan component + if (filePlanService.isFilePlanComponent(source) == true && + filePlanService.isFilePlanComponent(target) == true) { - result = super.evaluate(source, target); - } - else - { - // Check each capability using 'OR' logic - for (Capability capability : capabilities) + // Check the kind of the object, the permissions and the conditions + if (checkKinds(source) == true && checkPermissions(source) == true && checkConditions(source) == true) { - int capabilityResult = capability.evaluate(source, target); - if (capabilityResult != AccessDecisionVoter.ACCESS_DENIED) + if (targetCapability != null) { - result = AccessDecisionVoter.ACCESS_ABSTAIN; - if (isUndetermined() == false && capabilityResult == AccessDecisionVoter.ACCESS_GRANTED) - { - result = AccessDecisionVoter.ACCESS_GRANTED; - } - break; + result = targetCapability.evaluate(target); } + + if (AccessDecisionVoter.ACCESS_DENIED != result) + { + // Check each capability using 'OR' logic + for (Capability capability : capabilities) + { + result = capability.evaluate(source, target); + if (result == AccessDecisionVoter.ACCESS_GRANTED) + { + break; + } + } + + } + } + else + { + result = AccessDecisionVoter.ACCESS_DENIED; } } diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/capability/declarative/DeclarativeCapability.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/capability/declarative/DeclarativeCapability.java index 9c86057535..cfb35470e2 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/capability/declarative/DeclarativeCapability.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/capability/declarative/DeclarativeCapability.java @@ -327,14 +327,10 @@ public class DeclarativeCapability extends AbstractCapability @Override public int evaluate(NodeRef source, NodeRef target) { - int result = AccessDecisionVoter.ACCESS_ABSTAIN; - if (targetCapability != null) + int result = evaluate(source); + if (targetCapability != null && result != AccessDecisionVoter.ACCESS_DENIED) { - result = evaluate(source); - if (result != AccessDecisionVoter.ACCESS_DENIED) - { - result = targetCapability.evaluate(target); - } + result = targetCapability.evaluate(target); } return result; } diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/disposition/DispositionServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/disposition/DispositionServiceImpl.java index 224ca3dd78..a3e8882f82 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/disposition/DispositionServiceImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/disposition/DispositionServiceImpl.java @@ -208,21 +208,34 @@ public class DispositionServiceImpl implements { if (nodeService.exists(nodeRef) == true) { - // get this disposition instructions for the node - DispositionSchedule di = getDispositionSchedule(nodeRef); - if (di != null) - { - List dispositionActionDefinitions = di.getDispositionActionDefinitions(); - if (dispositionActionDefinitions.isEmpty() == false) - { - // get the first disposition action definition - DispositionActionDefinition nextDispositionActionDefinition = dispositionActionDefinitions.get(0); - - // initialise the details of the next disposition action - initialiseDispositionAction(nodeRef, nextDispositionActionDefinition); - } + 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) + { + List dispositionActionDefinitions = di.getDispositionActionDefinitions(); + if (dispositionActionDefinitions.isEmpty() == false) + { + // get the first disposition action definition + DispositionActionDefinition nextDispositionActionDefinition = dispositionActionDefinitions.get(0); + + // initialise the details of the next disposition action + initialiseDispositionAction(nodeRef, nextDispositionActionDefinition); } } + } /** ========= Disposition Property Methods ========= */ @@ -733,33 +746,37 @@ public class DispositionServiceImpl implements if (result == false) { DispositionAction da = new DispositionActionImpl(serviceRegistry, nextDa); - boolean firstComplete = da.getDispositionActionDefinition().eligibleOnFirstCompleteEvent(); - - List assocs = this.nodeService.getChildAssocs(nextDa, ASSOC_EVENT_EXECUTIONS, RegexQNamePattern.MATCH_ALL); - for (ChildAssociationRef assoc : assocs) + DispositionActionDefinition dad = da.getDispositionActionDefinition(); + if (dad != null) { - NodeRef eventExecution = assoc.getChildRef(); - Boolean isCompleteValue = (Boolean)this.nodeService.getProperty(eventExecution, PROP_EVENT_EXECUTION_COMPLETE); - boolean isComplete = false; - if (isCompleteValue != null) + boolean firstComplete = dad.eligibleOnFirstCompleteEvent(); + + List assocs = this.nodeService.getChildAssocs(nextDa, ASSOC_EVENT_EXECUTIONS, RegexQNamePattern.MATCH_ALL); + for (ChildAssociationRef assoc : assocs) { - isComplete = isCompleteValue.booleanValue(); - - // implement AND and OR combination of event completions - if (isComplete == true) + NodeRef eventExecution = assoc.getChildRef(); + Boolean isCompleteValue = (Boolean)this.nodeService.getProperty(eventExecution, PROP_EVENT_EXECUTION_COMPLETE); + boolean isComplete = false; + if (isCompleteValue != null) { - result = true; - if (firstComplete == true) + isComplete = isCompleteValue.booleanValue(); + + // implement AND and OR combination of event completions + if (isComplete == true) { - break; + result = true; + if (firstComplete == true) + { + break; + } } - } - else - { - result = false; - if (firstComplete == false) + else { - break; + result = false; + if (firstComplete == false) + { + break; + } } } } diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/behaviour/RecordCopyBehaviours.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/behaviour/RecordCopyBehaviours.java index 81877c5bee..6b4202992f 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/behaviour/RecordCopyBehaviours.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/behaviour/RecordCopyBehaviours.java @@ -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 unwantedAspects = new ArrayList(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() @@ -201,26 +218,37 @@ 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(); - - 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 + final RecordFolderServiceImpl recordFolderService = (RecordFolderServiceImpl)applicationContext.getBean("recordFolderService"); + final DispositionServiceImpl dispositionService = (DispositionServiceImpl)applicationContext.getBean("dispositionService"); + + 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; } }, AuthenticationUtil.getSystemUserName()); @@ -428,5 +456,5 @@ public class RecordCopyBehaviours implements RecordsManagementModel result = "0" + result; } return result; - } + } } diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordServiceImpl.java index 6391ff8aca..491c3248a7 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordServiceImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordServiceImpl.java @@ -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 diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/recordfolder/RecordFolderService.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/recordfolder/RecordFolderService.java index 6c14447246..caf5a4ec0d 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/recordfolder/RecordFolderService.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/recordfolder/RecordFolderService.java @@ -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 } diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/IssueTestSuite.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/IssueTestSuite.java index 19e18f6217..30daf78cff 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/IssueTestSuite.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/IssueTestSuite.java @@ -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 { diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/issue/RM1030Test.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/issue/RM1030Test.java index 6861077541..45675916bb 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/issue/RM1030Test.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/issue/RM1030Test.java @@ -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 diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/issue/RM1039Test.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/issue/RM1039Test.java new file mode 100644 index 0000000000..d3d411ba9d --- /dev/null +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/issue/RM1039Test.java @@ -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 . + */ +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() + { + @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() + { + @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() + { + @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() + { + @Override + public NodeRef run() + { + // create a record category (no disposition schedule) + return filePlanService.createRecordCategory(filePlan, "Caitlin Reed"); + } + }); + + final NodeRef testFolder = doTestInTransaction(new Test() + { + @Override + public NodeRef run() + { + // create folder + NodeRef testFolder = rmService.createRecordFolder(rmContainer, "Peter Edward Francis"); + + // complete event + Map params = new HashMap(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(); + } + }); + } +}