feature/APPS-1004 (#624)

* APPS-1004/APPS-1005: Retention steps are not updated when moving from another category with a different retention schedule

* APPS-1004/APPS-1005: [AGS] Retention steps are not updated when moving from another category with a different retention schedule
    - Added test for APPS-1004 and changed fix to solve another isuue
      RM-7106

* APPS-1004/APPS-1005: [AGS] Retention steps are not updated when moving from another category with a different retention schedule
    - Added common method to be used by both categoryType and folderType

* APPS-1004/APPS-1005: [AGS] Retention steps are not updated when moving from another category with a different retention schedule
    - Added change to reinitialize folders recursively, for the case
      where there are multiple subcategories

* APPS-1004/APPS-1005: [AGS] Retention steps are not updated when moving from another category with a different retention schedule
    - Added changes suggested at review
This commit is contained in:
ramunteanu
2021-08-03 18:05:15 +03:00
committed by GitHub
parent a1c0344828
commit 3cf1cfd133
6 changed files with 292 additions and 64 deletions

View File

@@ -102,9 +102,11 @@
<bean id="rma.unfiledRecordFolder" class="org.alfresco.module.org_alfresco_module_rm.model.rma.type.UnfiledRecordFolderType" parent="rm.baseBehaviour" />
<bean id="rma.recordCategory" class="org.alfresco.module.org_alfresco_module_rm.model.rma.type.RecordCategoryType" parent="rm.baseBehaviour">
<property name="recordService" ref="RecordService" />
<property name="vitalRecordService" ref="VitalRecordService" />
<property name="filePlanPermissionService" ref="FilePlanPermissionService" />
<property name="recordFolderService" ref="RecordFolderService" />
<property name="dispositionService" ref="DispositionService" />
</bean>
<bean id="rma.recordFolder" class="org.alfresco.module.org_alfresco_module_rm.model.rma.type.RecordFolderType" parent="rm.baseBehaviour">

View File

@@ -30,6 +30,10 @@ package org.alfresco.module.org_alfresco_module_rm.model.behaviour;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService;
import org.alfresco.module.org_alfresco_module_rm.model.BaseBehaviourBean;
import org.alfresco.module.org_alfresco_module_rm.record.RecordService;
import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderService;
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.QName;
@@ -52,7 +56,13 @@ public abstract class AbstractDisposableItem extends BaseBehaviourBean
/** disposition service */
protected DispositionService dispositionService;
/** record service */
protected RecordService recordService;
/** record folder service */
protected RecordFolderService recordFolderService;
/**
* @param dispositionService disposition service
*/
@@ -60,6 +70,22 @@ public abstract class AbstractDisposableItem extends BaseBehaviourBean
{
this.dispositionService = dispositionService;
}
/**
* @param recordService record service
*/
public void setRecordService(RecordService recordService)
{
this.recordService = recordService;
}
/**
* @param recordFolderService record folder service
*/
public void setRecordFolderService(RecordFolderService recordFolderService)
{
this.recordFolderService = recordFolderService;
}
/**
* Removes unwanted aspects
@@ -86,4 +112,35 @@ public abstract class AbstractDisposableItem extends BaseBehaviourBean
}
}
/**
* Cleans and re-initiates the containing records
*
* @param childAssociationRef
*/
protected void reinitializeRecordFolder(ChildAssociationRef childAssociationRef)
{
NodeRef newNodeRef = childAssociationRef.getChildRef();
AuthenticationUtil.runAs(() -> {
// clean record folder
cleanDisposableItem(nodeService, newNodeRef);
// re-initialise the record folder
recordFolderService.setupRecordFolder(newNodeRef);
// sort out the child records
for (NodeRef record : recordService.getRecords(newNodeRef))
{
// clean record
cleanDisposableItem(nodeService, record);
// Re-initiate the records in the new folder.
recordService.file(record);
}
return null;
}, AuthenticationUtil.getSystemUserName());
}
}

View File

@@ -27,13 +27,14 @@
package org.alfresco.module.org_alfresco_module_rm.model.rma.type;
import static org.alfresco.model.ContentModel.TYPE_CONTENT;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.model.BaseBehaviourBean;
import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderService;
import org.alfresco.module.org_alfresco_module_rm.model.behaviour.AbstractDisposableItem;
import org.alfresco.module.org_alfresco_module_rm.security.FilePlanPermissionService;
import org.alfresco.module.org_alfresco_module_rm.vital.VitalRecordService;
import org.alfresco.repo.copy.CopyBehaviourCallback;
@@ -49,6 +50,7 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
/**
* rma:recordCategory behaviour bean
@@ -60,9 +62,10 @@ import org.alfresco.service.namespace.QName;
(
defaultType = "rma:recordCategory"
)
public class RecordCategoryType extends BaseBehaviourBean
public class RecordCategoryType extends AbstractDisposableItem
implements NodeServicePolicies.OnCreateChildAssociationPolicy,
NodeServicePolicies.OnCreateNodePolicy
NodeServicePolicies.OnCreateNodePolicy,
NodeServicePolicies.OnMoveNodePolicy
{
private final static List<QName> ACCEPTED_UNIQUE_CHILD_TYPES = new ArrayList<>();
private final static List<QName> ACCEPTED_NON_UNIQUE_CHILD_TYPES = Arrays.asList(TYPE_RECORD_CATEGORY, TYPE_RECORD_FOLDER);
@@ -73,9 +76,6 @@ public class RecordCategoryType extends BaseBehaviourBean
/** file plan permission service */
protected FilePlanPermissionService filePlanPermissionService;
/** record folder service */
private RecordFolderService recordFolderService;
/**
* @param vitalRecordService vital record service
*/
@@ -92,14 +92,6 @@ public class RecordCategoryType extends BaseBehaviourBean
this.filePlanPermissionService = filePlanPermissionService;
}
/**
* @param recordFolderService record folder service
*/
public void setRecordFolderService(RecordFolderService recordFolderService)
{
this.recordFolderService = recordFolderService;
}
/**
* On every event
*
@@ -204,6 +196,53 @@ public class RecordCategoryType extends BaseBehaviourBean
}
/**
* Record Category move behaviour
*
* @see org.alfresco.repo.node.NodeServicePolicies.OnMoveNodePolicy#onMoveNode(org.alfresco.service.cmr.repository.ChildAssociationRef, org.alfresco.service.cmr.repository.ChildAssociationRef)
*/
@Override
@Behaviour
(
kind = BehaviourKind.CLASS,
notificationFrequency = NotificationFrequency.FIRST_EVENT
)
public void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef)
{
// clean the child folders and records only if the old parent category has a disposition schedule set
// if it doesn't, then there are no old properties on the child nodes that have to be cleaned in order
// for new ones to be set
if (nodeService.getType(newChildAssocRef.getChildRef()).equals(TYPE_RECORD_CATEGORY)
&& dispositionService.getDispositionSchedule(oldChildAssocRef.getParentRef()) != null)
{
reinitializeRecordFolders(newChildAssocRef);
}
}
/**
* Recursively reinitialize each folder in a structure of categories
* Unwanted aspects will be removed from the child records and the records will be re-filed
* Disposition schedule aspects and properties will be inherited from the new parent category
*
* @param childAssociationRef
*/
private void reinitializeRecordFolders(ChildAssociationRef childAssociationRef)
{
for (ChildAssociationRef newChildRef : nodeService.getChildAssocs(childAssociationRef.getChildRef(),
ContentModel.ASSOC_CONTAINS,
RegexQNamePattern.MATCH_ALL))
{
if (nodeService.getType(newChildRef.getChildRef()).equals(TYPE_RECORD_CATEGORY))
{
reinitializeRecordFolders(newChildRef);
}
else if (!nodeService.getType(newChildRef.getChildRef()).equals(TYPE_CONTENT))
{
reinitializeRecordFolder(newChildRef);
}
}
}
/**
* Copy callback for record category
*/

View File

@@ -34,8 +34,6 @@ import org.alfresco.model.ContentModel;
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.model.behaviour.AbstractDisposableItem;
import org.alfresco.module.org_alfresco_module_rm.record.RecordService;
import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderService;
import org.alfresco.module.org_alfresco_module_rm.vital.VitalRecordService;
import org.alfresco.repo.copy.CopyBehaviourCallback;
import org.alfresco.repo.copy.CopyDetails;
@@ -68,11 +66,6 @@ public class RecordFolderType extends AbstractDisposableItem
implements NodeServicePolicies.OnMoveNodePolicy,
NodeServicePolicies.OnCreateChildAssociationPolicy
{
/** record service */
private RecordService recordService;
/** record folder service */
private RecordFolderService recordFolderService;
/** vital record service */
protected VitalRecordService vitalRecordService;
@@ -85,22 +78,6 @@ public class RecordFolderType extends AbstractDisposableItem
private static final String MSG_CANNOT_CREATE_CHILDREN_IN_CLOSED_RECORD_FOLDER = "rm.service.add-children-to-closed-record-folder";
/**
* @param recordService record service
*/
public void setRecordService(RecordService recordService)
{
this.recordService = recordService;
}
/**
* @param recordFolderService record folder service
*/
public void setRecordFolderService(RecordFolderService recordFolderService)
{
this.recordFolderService = recordFolderService;
}
/**
* @param vitalRecordService vital record service
*/
@@ -131,31 +108,7 @@ public class RecordFolderType extends AbstractDisposableItem
{
if (!oldChildAssocRef.getParentRef().equals(newChildAssocRef.getParentRef()))
{
final NodeRef newNodeRef = newChildAssocRef.getChildRef();
AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Object>()
{
public Object doWork()
{
// clean record folder
cleanDisposableItem(nodeService, newNodeRef);
// re-initialise the record folder
recordFolderService.setupRecordFolder(newNodeRef);
// sort out the child records
for (NodeRef record : recordService.getRecords(newNodeRef))
{
// clean record
cleanDisposableItem(nodeService, record);
// Re-initiate the records in the new folder.
recordService.file(record);
}
return null;
}
}, AuthenticationUtil.getSystemUserName());
reinitializeRecordFolder(newChildAssocRef);
}
}
else

View File

@@ -28,6 +28,8 @@ package org.alfresco.module.org_alfresco_module_rm.test.integration.disposition;
import static org.alfresco.module.org_alfresco_module_rm.test.util.CommonRMTestUtils.DEFAULT_DISPOSITION_DESCRIPTION;
import static org.alfresco.module.org_alfresco_module_rm.test.util.CommonRMTestUtils.DEFAULT_DISPOSITION_INSTRUCTIONS;
import static org.alfresco.module.org_alfresco_module_rm.test.util.CommonRMTestUtils.DEFAULT_EVENT_NAME;
import static org.alfresco.module.org_alfresco_module_rm.test.util.CommonRMTestUtils.SEPARATION_EVENT_NAME;
import static org.alfresco.util.GUID.generate;
import java.io.Serializable;
@@ -37,6 +39,7 @@ import java.util.List;
import java.util.Map;
import org.alfresco.module.org_alfresco_module_rm.action.impl.CutOffAction;
import org.alfresco.module.org_alfresco_module_rm.action.impl.DestroyAction;
import org.alfresco.module.org_alfresco_module_rm.action.impl.RetainAction;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionAction;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule;
@@ -190,6 +193,162 @@ public class DispositionScheduleInheritanceTest extends BaseRMTestCase
});
}
/**
* Given a root record category A with a retention schedule set to retain and destroy after 1 day
* and another root record category B with a retention schedule set to cut off and destroy after 1 day containing a
* subcategory
* When moving the subcategory into the first root category
* Then records under the subcategory inherit the retention schedule of the parent record category
* The events list contain the retain event step inherited from the new parent category
* <p>
* Please see https://alfresco.atlassian.net/browse/APPS-1004
*/
public void testRetentionScheduleInheritance_APPS_1004()
{
doBehaviourDrivenTest(new BehaviourDrivenTest()
{
NodeRef category1;
NodeRef subcategory2;
NodeRef record;
Date asOfDateBeforeMove;
@Override
public void given()
{
// create root category1
category1 = filePlanService.createRecordCategory(filePlan, generate());
// create record level disposition schedule for category1
createDispositionScheduleRetainAndCutOffOneDay(category1);
// create root category2
NodeRef category2 = filePlanService.createRecordCategory(filePlan, generate());
// create record level disposition schedule for category2
createDispositionScheduleCutOffAndDestroyOneDay(category2);
// create subcategory2 under category2
subcategory2 = filePlanService.createRecordCategory(category2, generate());
// create folder under subcategory2
folder = recordFolderService.createRecordFolder(subcategory2, generate());
// file record in folder and complete it
record = utils.createRecord(folder, generate(), generate());
utils.completeRecord(record);
//store the date to check if it was updated
asOfDateBeforeMove = dispositionService.getNextDispositionAction(record).getAsOfDate();
}
@Override
public void when() throws Exception
{
// move subcategory2 under category1
fileFolderService.move(subcategory2, category1, null);
}
@Override
public void then() throws Exception
{
dispositionService.getDispositionSchedule(record);
// check the next disposition action
DispositionAction dispositionActionAfterMove = dispositionService.getNextDispositionAction(record);
assertNotNull(dispositionActionAfterMove);
assertEquals(RetainAction.NAME, dispositionActionAfterMove.getName());
assertNotNull(dispositionActionAfterMove.getAsOfDate());
assertTrue(dispositionActionAfterMove.getAsOfDate().after(asOfDateBeforeMove));
// check the search aspect details
assertTrue(nodeService.hasAspect(record, ASPECT_RM_SEARCH));
assertEquals(RetainAction.NAME, nodeService.getProperty(record, PROP_RS_DISPOSITION_ACTION_NAME));
assertNotNull(nodeService.getProperty(record, PROP_RS_DISPOSITION_ACTION_AS_OF));
assertNull((List<String>) nodeService.getProperty(record, PROP_RS_DISPOSITION_EVENTS));
assertNotNull(nodeService.getProperty(record, PROP_RS_DISPOITION_INSTRUCTIONS));
assertNotNull(nodeService.getProperty(record, PROP_RS_DISPOITION_AUTHORITY));
assertTrue((Boolean) nodeService.getProperty(record, PROP_RS_HAS_DISPOITION_SCHEDULE));
}
});
}
/**
* Given a root record category A with a retention schedule set to cut off on event 'case closed'
* and another root record category B with a retention schedule set to cut off on event 'separation'
* When moving the subcategory into the first root category
* Then records under the subcategory inherit the retention schedule of the parent record category
* The events list contain the case closed event step inherited from the new parent category
* <p>
* Please see https://alfresco.atlassian.net/browse/APPS-1005
*/
public void testRetentionScheduleInheritance_APPS_1005()
{
doBehaviourDrivenTest(new BehaviourDrivenTest()
{
NodeRef category1;
NodeRef subcategory2;
NodeRef record;
Date asOfDateBeforeMove;
@Override
public void given()
{
// create root category1
category1 = filePlanService.createRecordCategory(filePlan, generate());
utils.createDispositionSchedule(category1, DEFAULT_DISPOSITION_INSTRUCTIONS,
DEFAULT_DISPOSITION_DESCRIPTION, true, true, false, DEFAULT_EVENT_NAME);
// create root category2
NodeRef category2 = filePlanService.createRecordCategory(filePlan, generate());
// create record level disposition schedule for category2
utils.createDispositionSchedule(category2, DEFAULT_DISPOSITION_INSTRUCTIONS,
DEFAULT_DISPOSITION_DESCRIPTION, true, true, false, SEPARATION_EVENT_NAME);
// create subcategory2 under category2
subcategory2 = filePlanService.createRecordCategory(category2, generate());
// create folder under subcategory2
folder = recordFolderService.createRecordFolder(subcategory2, generate());
// file record in folder and complete it
record = utils.createRecord(folder, generate(), generate());
utils.completeRecord(record);
//store the date to check if it was updated
asOfDateBeforeMove = dispositionService.getNextDispositionAction(record).getAsOfDate();
}
@Override
public void when() throws Exception
{
// move subcategory2 under category1
fileFolderService.move(subcategory2, category1, null);
}
@Override
public void then() throws Exception
{
// check the next disposition action
DispositionAction dispositionActionAfterMove = dispositionService.getNextDispositionAction(record);
assertNotNull(dispositionActionAfterMove);
assertEquals(CutOffAction.NAME, dispositionActionAfterMove.getName());
// check the search aspect details
assertTrue(nodeService.hasAspect(record, ASPECT_RM_SEARCH));
assertEquals(CutOffAction.NAME, nodeService.getProperty(record, PROP_RS_DISPOSITION_ACTION_NAME));
assertNotNull((List<String>) nodeService.getProperty(record, PROP_RS_DISPOSITION_EVENTS));
assertEquals(((List<String>) ((List<String>) nodeService.getProperty(record,
PROP_RS_DISPOSITION_EVENTS))).size(), 1);
assertEquals(DEFAULT_EVENT_NAME, ((List<String>) ((List<String>) nodeService.getProperty(record,
PROP_RS_DISPOSITION_EVENTS))).get(0));
assertNotNull(nodeService.getProperty(record, PROP_RS_DISPOITION_INSTRUCTIONS));
assertNotNull(nodeService.getProperty(record, PROP_RS_DISPOITION_AUTHORITY));
assertTrue((Boolean) nodeService.getProperty(record, PROP_RS_HAS_DISPOITION_SCHEDULE));
}
});
}
private void createDispositionScheduleCutOff(NodeRef category, String action, String period)
{
DispositionSchedule ds = utils.createDispositionSchedule(category, DEFAULT_DISPOSITION_INSTRUCTIONS, DEFAULT_DISPOSITION_DESCRIPTION, true, false, false);
@@ -205,6 +364,22 @@ public class DispositionScheduleInheritanceTest extends BaseRMTestCase
createDispositionScheduleStep(ds, RetainAction.NAME, CommonRMTestUtils.PERIOD_IMMEDIATELY);
}
private void createDispositionScheduleRetainAndCutOffOneDay(NodeRef category)
{
DispositionSchedule ds = utils.createDispositionSchedule(category, DEFAULT_DISPOSITION_INSTRUCTIONS, DEFAULT_DISPOSITION_DESCRIPTION, true, false, false);
createDispositionScheduleStep(ds, RetainAction.NAME, CommonRMTestUtils.PERIOD_ONE_DAY);
createDispositionScheduleStep(ds, DestroyAction.NAME, CommonRMTestUtils.PERIOD_ONE_DAY);
}
private void createDispositionScheduleCutOffAndDestroyOneDay(NodeRef category)
{
DispositionSchedule ds = utils.createDispositionSchedule(category, DEFAULT_DISPOSITION_INSTRUCTIONS, DEFAULT_DISPOSITION_DESCRIPTION, true, false, false);
createDispositionScheduleStep(ds, CutOffAction.NAME, CommonRMTestUtils.PERIOD_ONE_DAY);
createDispositionScheduleStep(ds, DestroyAction.NAME, CommonRMTestUtils.PERIOD_ONE_DAY);
}
private void createDispositionScheduleStep(DispositionSchedule ds, String action, String period)
{
Map<QName, Serializable> step = new HashMap<QName, Serializable>(3);

View File

@@ -82,8 +82,10 @@ public class CommonRMTestUtils implements RecordsManagementModel
public static final String DEFAULT_DISPOSITION_INSTRUCTIONS = "disposition instructions";
public static final String DEFAULT_DISPOSITION_DESCRIPTION = "disposition action description";
public static final String DEFAULT_EVENT_NAME = "case_closed";
public static final String SEPARATION_EVENT_NAME = "separation";
public static final String PERIOD_NONE = "none|0";
public static final String PERIOD_IMMEDIATELY = "immediately|0";
public static final String PERIOD_ONE_DAY = "day|1";
public static final String PERIOD_FIVE_DAYS = "day|5";
public static final String PERIOD_TEN_DAYS = "day|10";
public static final String PERIOD_ONE_WEEK = "week|1";