diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/alfresco-global.properties b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/alfresco-global.properties index aa06ff15d3..8fdbf91cbd 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/alfresco-global.properties +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/alfresco-global.properties @@ -51,6 +51,11 @@ rm.autocompletesuggestion.nodeParameterSuggester.aspectsAndTypes=rma:record,cm:c # rm.dispositionlifecycletrigger.cronexpression=0 0/5 * * * ? +# +# Global RM notify of records due for review cron job expression +# +rm.notifyOfRecordsDueForReview.cronExpression=0 0/15 * * * ? + # # Records contributors group # diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/records-model.properties b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/records-model.properties index ab689a6057..a55d29cf8d 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/records-model.properties +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/records-model.properties @@ -174,8 +174,6 @@ rma_recordsmanagement.aspect.rma_vitalRecord.title=Vital Record rma_recordsmanagement.aspect.rma_vitalRecord.decription=Vital Record rma_recordsmanagement.property.rma_reviewAsOf.title=Next Review rma_recordsmanagement.property.rma_reviewAsOf.decription=Next Review -rma_recordsmanagement.property.rma_notificationIssued.title=Indicates that a due for review notification has been issued for this record -rma_recordsmanagement.property.rma_notificationIssued.decription=Indicates that a due for review notification has been issued for this record rma_recordsmanagement.aspect.rma_scheduled.title=Scheduled rma_recordsmanagement.aspect.rma_scheduled.decription=Scheduled diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml index d0103fb842..7d90248df9 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml @@ -885,13 +885,6 @@ d:date false - - Indicates whether a notification that this record is due for review has been issued - d:boolean - true - false - false - rma:filePlanComponent diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-job-context.xml b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-job-context.xml index 607fd1d9ec..c8cb9b668f 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-job-context.xml +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-job-context.xml @@ -47,7 +47,7 @@ - 0 0/15 * * * ? + ${rm.notifyOfRecordsDueForReview.cronExpression} diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/security/rm-method-security.properties b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/security/rm-method-security.properties index 4da32d8bef..1cc4f3e5c2 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/security/rm-method-security.properties +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/security/rm-method-security.properties @@ -121,14 +121,15 @@ rm.methodsecurity.org.alfresco.service.cmr.search.CategoryService.*=RM_DENY ## Lock Service -rm.methodsecurity.org.alfresco.service.cmr.lock.LockService.lock=RM_ABSTAIN -rm.methodsecurity.org.alfresco.service.cmr.lock.LockService.unlock=RM_ABSTAIN +rm.methodsecurity.org.alfresco.service.cmr.lock.LockService.lock=RM.Create.0 +rm.methodsecurity.org.alfresco.service.cmr.lock.LockService.unlock=RM.Create.0 rm.methodsecurity.org.alfresco.service.cmr.lock.LockService.getLockStatus=RM.Read.0 rm.methodsecurity.org.alfresco.service.cmr.lock.LockService.getLockType=RM.Read.0 rm.methodsecurity.org.alfresco.service.cmr.lock.LockService.checkForLock=RM.Read.0 rm.methodsecurity.org.alfresco.service.cmr.lock.LockService.getLocks=RM.Read.0 rm.methodsecurity.org.alfresco.service.cmr.lock.LockService.isLockedAndReadOnly=RM.Read.0 rm.methodsecurity.org.alfresco.service.cmr.lock.LockService.isLocked=RM.Read.0 +rm.methodsecurity.org.alfresco.service.cmr.lock.LockService.getLockState=RM.Read.0 rm.methodsecurity.org.alfresco.service.cmr.lock.LockService.*=RM_DENY ## Multilingual Content Service diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/job/NotifyOfRecordsDueForReviewJobExecuter.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/job/NotifyOfRecordsDueForReviewJobExecuter.java index aa659f6a84..61a04ca070 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/job/NotifyOfRecordsDueForReviewJobExecuter.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/job/NotifyOfRecordsDueForReviewJobExecuter.java @@ -91,12 +91,8 @@ public class NotifyOfRecordsDueForReviewJobExecuter extends RecordsManagementJob // Query is for all records that are due for review and for which // notification has not been sent. StringBuilder queryBuffer = new StringBuilder(); - queryBuffer.append("+ASPECT:\"rma:vitalRecord\" "); + queryBuffer.append("ASPECT:\"rma:vitalRecord\" "); queryBuffer.append("AND @rma\\:reviewAsOf:[MIN TO NOW] "); - queryBuffer.append("AND ( "); - queryBuffer.append("@rma\\:notificationIssued:false "); - queryBuffer.append("OR ISNULL:\"rma:notificationIssued\" "); - queryBuffer.append(") "); String query = queryBuffer.toString(); ResultSet results = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_FTS_ALFRESCO, query); @@ -124,27 +120,12 @@ public class NotifyOfRecordsDueForReviewJobExecuter extends RecordsManagementJob } }; - RetryingTransactionCallback txUpdateNodesCallback = new RetryingTransactionCallback() - { - // Set the notification issued property. - public Boolean execute() - { - for (NodeRef node : resultNodes) - { - nodeService.setProperty(node, RecordsManagementModel.PROP_NOTIFICATION_ISSUED, "true"); - } - return Boolean.TRUE; - } - }; - /** * Now do the work, one action in each transaction */ // don't retry the send email retryingTransactionHelper.setMaxRetries(0); retryingTransactionHelper.doInTransaction(txCallbackSendEmail); - retryingTransactionHelper.setMaxRetries(10); - retryingTransactionHelper.doInTransaction(txUpdateNodesCallback); } return null; } diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/RecordsManagementModel.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/RecordsManagementModel.java index a99f75cbab..d7c0308e9c 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/RecordsManagementModel.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/RecordsManagementModel.java @@ -136,7 +136,6 @@ public interface RecordsManagementModel extends RecordsManagementCustomModel // Vital record aspect QName ASPECT_VITAL_RECORD = QName.createQName(RM_URI, "vitalRecord"); QName PROP_REVIEW_AS_OF = QName.createQName(RM_URI, "reviewAsOf"); - QName PROP_NOTIFICATION_ISSUED = QName.createQName(RM_URI, "notificationIssued"); // Cut off aspect QName ASPECT_CUT_OFF = QName.createQName(RM_URI, "cutOff"); diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordServiceImpl.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordServiceImpl.java index a862d6f892..21b2393885 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordServiceImpl.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordServiceImpl.java @@ -92,6 +92,7 @@ import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileNotFoundException; import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; @@ -128,6 +129,7 @@ public class RecordServiceImpl extends BaseBehaviourBean RecordsManagementModel, RecordsManagementCustomModel, NodeServicePolicies.OnCreateChildAssociationPolicy, + NodeServicePolicies.OnRemoveAspectPolicy, NodeServicePolicies.OnUpdatePropertiesPolicy, ContentServicePolicies.OnContentUpdatePolicy { @@ -416,6 +418,29 @@ public class RecordServiceImpl extends BaseBehaviourBean */ @Override @Behaviour + ( + kind = BehaviourKind.CLASS, + type = "sys:noContent" + ) + public void onRemoveAspect(NodeRef nodeRef, QName aspect) + { + if (nodeService.hasAspect(nodeRef, ASPECT_RECORD)) + { + ContentData contentData = (ContentData) nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); + if (ContentData.hasContent(contentData) && contentData.getSize() > 0) + { + renameRecord(nodeRef); + } + } + } + + /** + * Behaviour executed when a new item is added to a record folder. + * + * @see org.alfresco.repo.node.NodeServicePolicies.OnCreateChildAssociationPolicy#onCreateChildAssociation(org.alfresco.service.cmr.repository.ChildAssociationRef, boolean) + */ + @Override + @Behaviour ( kind = ASSOCIATION, type = "rma:recordFolder", @@ -1765,7 +1790,7 @@ public class RecordServiceImpl extends BaseBehaviourBean ) public void onContentUpdate(NodeRef nodeRef, boolean newContent) { - if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_HIDDEN)) + if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_HIDDEN) && !nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE)) { renameRecord(nodeRef); } diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/vital/ReviewedAction.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/vital/ReviewedAction.java index 1cdd507c73..51c3fd8e33 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/vital/ReviewedAction.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/vital/ReviewedAction.java @@ -30,6 +30,7 @@ package org.alfresco.module.org_alfresco_module_rm.vital; import java.util.Date; import org.alfresco.module.org_alfresco_module_rm.action.RMActionExecuterAbstractBase; +import org.alfresco.repo.dictionary.types.period.Immediately; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.repository.NodeRef; import org.apache.commons.logging.Log; @@ -44,56 +45,72 @@ public class ReviewedAction extends RMActionExecuterAbstractBase { private static Log logger = LogFactory.getLog(ReviewedAction.class); - /** - * - * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, - * org.alfresco.service.cmr.repository.NodeRef) - */ - @Override - protected void executeImpl(Action action, NodeRef actionedUponNodeRef) - { - VitalRecordDefinition vrDef = getVitalRecordService().getVitalRecordDefinition(actionedUponNodeRef); + /** + * + * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, + * org.alfresco.service.cmr.repository.NodeRef) + */ + @Override + protected void executeImpl(Action action, NodeRef actionedUponNodeRef) + { + VitalRecordDefinition vrDef = getVitalRecordService().getVitalRecordDefinition(actionedUponNodeRef); if (vrDef != null && vrDef.isEnabled()) { - if (getRecordService().isRecord(actionedUponNodeRef)) - { - reviewRecord(actionedUponNodeRef, vrDef); - } - else if (getRecordFolderService().isRecordFolder(actionedUponNodeRef)) - { - for (NodeRef record : getRecordService().getRecords(actionedUponNodeRef)) + if (getRecordService().isRecord(actionedUponNodeRef)) + { + reviewRecord(actionedUponNodeRef, vrDef); + } + else if (getRecordFolderService().isRecordFolder(actionedUponNodeRef)) + { + for (NodeRef record : getRecordService().getRecords(actionedUponNodeRef)) { reviewRecord(record, vrDef); } - } - } - } + } + } + } - /** - * Make record as reviewed. - * - * @param nodeRef - * @param vrDef - */ - private void reviewRecord(NodeRef nodeRef, VitalRecordDefinition vrDef) - { + /** + * Make record as reviewed. + * + * @param nodeRef + * @param vrDef + */ + private void reviewRecord(NodeRef nodeRef, VitalRecordDefinition vrDef) + { // Calculate the next review date - Date reviewAsOf = vrDef.getNextReviewDate(); - if (reviewAsOf != null) + if (vrDef.getReviewPeriod().getPeriodType().equals(Immediately.PERIOD_TYPE)) { // Log if (logger.isDebugEnabled()) { StringBuilder msg = new StringBuilder(); + msg.append("Removing reviewAsOf property from") + .append(nodeRef); + logger.debug(msg.toString()); + } + + this.getNodeService().removeProperty(nodeRef, PROP_REVIEW_AS_OF); + } + else + { + Date reviewAsOf = vrDef.getNextReviewDate(); + if (reviewAsOf != null) + { + // Log + if (logger.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); msg.append("Setting new reviewAsOf property [") .append(reviewAsOf) .append("] on ") .append(nodeRef); - logger.debug(msg.toString()); + logger.debug(msg.toString()); + } + + this.getNodeService().setProperty(nodeRef, PROP_REVIEW_AS_OF, reviewAsOf); + // TODO And record previous review date, time, user } - - this.getNodeService().setProperty(nodeRef, PROP_REVIEW_AS_OF, reviewAsOf); - //TODO And record previous review date, time, user } - } + } } diff --git a/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/vital/ReviewedActionUnitTest.java b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/vital/ReviewedActionUnitTest.java new file mode 100644 index 0000000000..8b28cc049a --- /dev/null +++ b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/vital/ReviewedActionUnitTest.java @@ -0,0 +1,140 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2017 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * 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 . + * #L% + */ +package org.alfresco.module.org_alfresco_module_rm.vital; + +import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.module.org_alfresco_module_rm.record.RecordService; +import org.alfresco.repo.dictionary.types.period.Days; +import org.alfresco.repo.dictionary.types.period.Immediately; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Date; + +import static org.mockito.Mockito.verify; + +import org.alfresco.service.cmr.repository.Period; +import org.alfresco.service.cmr.repository.StoreRef; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.extensions.webscripts.GUID; + +/** + * Unit test for {@link ReviewedAction} class + * + * @author Ana Bozianu + * @sincev 2.6 + */ +public class ReviewedActionUnitTest implements RecordsManagementModel +{ + private @Mock VitalRecordService mockedVitalRecordService; + private @Mock RecordService mockedRecordService; + private @Mock NodeService mockedNodeService; + + private @InjectMocks ReviewedAction reviewedAction; + + @Before + public void testSetup() + { + MockitoAnnotations.initMocks(this); + } + + /** + * Given a record having the vital record definition of immediately + * When I mark the record as reviewed + * Then review as of date is removed from the record + */ + @Test + public void testReviewRecordWithAdHocReviewPeriod() + { + /* + * Given + */ + NodeRef mockedRecord = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, GUID.generate()); + when(mockedRecordService.isRecord(mockedRecord)).thenReturn(true); + + VitalRecordDefinition mockedVRDef = mock(VitalRecordDefinition.class); + when(mockedVRDef.isEnabled()).thenReturn(true); + when(mockedVitalRecordService.getVitalRecordDefinition(mockedRecord)).thenReturn(mockedVRDef); + + Period mockedReviewPeriod = mock(Period.class); + when(mockedReviewPeriod.getPeriodType()).thenReturn(Immediately.PERIOD_TYPE); + when(mockedVRDef.getReviewPeriod()).thenReturn(mockedReviewPeriod); + + /* + * When + */ + reviewedAction.executeImpl(null, mockedRecord); + + /* + * Then + */ + verify(mockedNodeService).removeProperty(mockedRecord, PROP_REVIEW_AS_OF); + } + + /** + * Given a record having a recurent vital record definition + * When I mark the record as reviewed + * Then the review as of date is updated according to the next review period computed by the vital record definition + */ + @Test + public void testReviewRecordWithRecurentReviewPeriod() + { + /* + * Given + */ + NodeRef mockedRecord = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, GUID.generate()); + when(mockedRecordService.isRecord(mockedRecord)).thenReturn(true); + + VitalRecordDefinition mockedVRDef = mock(VitalRecordDefinition.class); + when(mockedVRDef.isEnabled()).thenReturn(true); + when(mockedVitalRecordService.getVitalRecordDefinition(mockedRecord)).thenReturn(mockedVRDef); + + Date mockedNextReviewDate = mock(Date.class); + when(mockedVRDef.getNextReviewDate()).thenReturn(mockedNextReviewDate); + + Period mockedReviewPeriod = mock(Period.class); + when(mockedReviewPeriod.getPeriodType()).thenReturn(Days.PERIOD_TYPE); + when(mockedVRDef.getReviewPeriod()).thenReturn(mockedReviewPeriod); + + /* + * When + */ + reviewedAction.executeImpl(null, mockedRecord); + + /* + * Then + */ + verify(mockedNodeService).setProperty(mockedRecord, PROP_REVIEW_AS_OF, mockedNextReviewDate); + } +}