diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/action-context.xml b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/action-context.xml index 477d319ef4..e7358d2770 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/action-context.xml +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/action-context.xml @@ -7,11 +7,12 @@ - + - - - + + + {http://www.alfresco.org/model/content/1.0}content @@ -26,6 +27,8 @@ + + {http://www.alfresco.org/model/content/1.0}content diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/capability/rm-capabilities-recordfolder-context.xml b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/capability/rm-capabilities-recordfolder-context.xml index 2420858b34..97f9aee406 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/capability/rm-capabilities-recordfolder-context.xml +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/capability/rm-capabilities-recordfolder-context.xml @@ -62,6 +62,27 @@ + + + + + + RECORD_FOLDER + + + + + + + + + + + + + + + @@ -114,7 +115,8 @@ - + + @@ -232,6 +234,10 @@ + + + + diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/security/rm-default-roles-bootstrap.json b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/security/rm-default-roles-bootstrap.json index 5771b8d991..257f495b84 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/security/rm-default-roles-bootstrap.json +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/security/rm-default-roles-bootstrap.json @@ -49,7 +49,8 @@ "RequestRecordInformation", "FileUnfiledRecords", "RejectRecords", - "LinkToRecords" + "LinkToRecords", + "FileVersionRecords" ] }, { @@ -74,7 +75,8 @@ "FileUnfiledRecords", "RejectRecords", "LinkToRecords", - "ManageAccessControls" + "ManageAccessControls", + "FileVersionRecords" ] }, { @@ -145,8 +147,8 @@ "DeleteHold", "EndRetention", "EditHold", - "ManageAccessControls" - + "ManageAccessControls", + "FileVersionRecords" ] }, { @@ -219,7 +221,8 @@ "FileHoldReport", "DeleteHold", "EditHold", - "EndRetention" + "EndRetention", + "FileVersionRecords" ] } ] \ No newline at end of file diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/CreateRecordAction.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/CreateRecordAction.java index 569bcc9121..2a6ab6942f 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/CreateRecordAction.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/CreateRecordAction.java @@ -27,12 +27,12 @@ package org.alfresco.module.org_alfresco_module_rm.action.dm; -import java.util.Arrays; +import static org.alfresco.module.org_alfresco_module_rm.action.dm.RecordActionUtils.resolvePath; + import java.util.List; -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.action.AuditableActionExecuterAbstractBase; +import org.alfresco.module.org_alfresco_module_rm.action.dm.RecordActionUtils.Services; import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; import org.alfresco.module.org_alfresco_module_rm.record.RecordService; @@ -43,11 +43,6 @@ import org.alfresco.service.cmr.action.ParameterDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.namespace.QName; - -import org.springframework.util.StringUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; /** * Creates a new record from an existing content object. @@ -59,9 +54,6 @@ import org.apache.commons.logging.LogFactory; public class CreateRecordAction extends AuditableActionExecuterAbstractBase implements RecordsManagementModel { - /** Logger */ - private static final Log LOGGER = LogFactory.getLog(CreateRecordAction.class); - /** Action name */ public static final String NAME = "create-record"; @@ -70,20 +62,26 @@ public class CreateRecordAction extends AuditableActionExecuterAbstractBase public static final String PARAM_HIDE_RECORD = "hide-record"; public static final String PARAM_PATH = "path"; - /** Node service */ + /** + * Node service + */ private NodeService nodeService; - /** File plan service */ + /** + * File plan service + */ private FilePlanService filePlanService; - /** Authentication util */ + /** + * Authentication util + */ private AuthenticationUtil authenticationUtil; /** Record service */ private RecordService recordService; /** - * @param nodeService node service + * @param nodeService node service */ public void setNodeService(NodeService nodeService) { @@ -91,7 +89,7 @@ public class CreateRecordAction extends AuditableActionExecuterAbstractBase } /** - * @param filePlanService file plan service + * @param filePlanService file plan service */ public void setFilePlanService(FilePlanService filePlanService) { @@ -99,13 +97,14 @@ public class CreateRecordAction extends AuditableActionExecuterAbstractBase } /** - * @param authenticationUtil authentication util + * @param authenticationUtil authentication util */ public void setAuthenticationUtil(AuthenticationUtil authenticationUtil) { this.authenticationUtil = authenticationUtil; } + /** * @param recordService record service */ @@ -131,12 +130,13 @@ public class CreateRecordAction extends AuditableActionExecuterAbstractBase Boolean hideRecordValue = ((Boolean) action.getParameterValue(PARAM_HIDE_RECORD)); if (hideRecordValue != null) { - hideRecord = hideRecordValue.booleanValue(); + hideRecord = hideRecordValue; } if (pathParameter != null && !pathParameter.isEmpty()) { - destinationRecordFolder = resolvePath(filePlan, pathParameter); + RecordActionUtils.Services services = new Services(nodeService, filePlanService, authenticationUtil); + destinationRecordFolder = resolvePath(services, filePlan, pathParameter, NAME); } synchronized (this) @@ -162,102 +162,4 @@ public class CreateRecordAction extends AuditableActionExecuterAbstractBase params.add(new ParameterDefinitionImpl(PARAM_PATH, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_PATH))); params.add(new ParameterDefinitionImpl(PARAM_HIDE_RECORD, DataTypeDefinition.BOOLEAN, false, getParamDisplayLabel(PARAM_HIDE_RECORD))); } - - /** - * Helper method to get the target record folder node reference from the action path parameter - * - * @param filePlan The filePlan containing the path - * @param pathParameter The path - * @return The NodeRef of the resolved path - */ - private NodeRef resolvePath(NodeRef filePlan, final String pathParameter) - { - NodeRef destinationFolder; - - if (filePlan == null) - { - filePlan = getDefaultFilePlan(); - } - - final String[] pathElementsArray = StringUtils.tokenizeToStringArray(pathParameter, "/", false, true); - if ((pathElementsArray != null) && (pathElementsArray.length > 0)) - { - destinationFolder = resolvePath(filePlan, Arrays.asList(pathElementsArray)); - - // destination must be a record folder - QName nodeType = nodeService.getType(destinationFolder); - if (!nodeType.equals(RecordsManagementModel.TYPE_RECORD_FOLDER)) - { - throw new AlfrescoRuntimeException("Unable to execute " + NAME + " action, because the destination path is not a record folder."); - } - } - else - { - throw new AlfrescoRuntimeException("Unable to execute " + NAME + " action, because the destination path could not be found."); - } - return destinationFolder; - } - - /** - * Helper method to recursively get the next path element node reference from the action path parameter - * - * @param parent The parent of the path elements - * @param pathElements The path elements still to be resolved - * @return The NodeRef of the resolved path element - */ - private NodeRef resolvePath(NodeRef parent, List pathElements) - { - NodeRef nodeRef; - String childName = pathElements.get(0); - - nodeRef = nodeService.getChildByName(parent, ContentModel.ASSOC_CONTAINS, childName); - - if (nodeRef == null) - { - throw new AlfrescoRuntimeException("Unable to execute " + NAME + " action, because the destination path could not be found."); - } - else - { - QName nodeType = nodeService.getType(nodeRef); - if (nodeType.equals(RecordsManagementModel.TYPE_HOLD_CONTAINER) || - nodeType.equals(RecordsManagementModel.TYPE_TRANSFER_CONTAINER) || - nodeType.equals(RecordsManagementModel.TYPE_UNFILED_RECORD_CONTAINER)) - { - throw new AlfrescoRuntimeException("Unable to execute " + NAME + " action, because the destination path is invalid."); - } - } - if (pathElements.size() > 1) - { - nodeRef = resolvePath(nodeRef, pathElements.subList(1, pathElements.size())); - } - return nodeRef; - } - - /** - * Helper method to get the default RM filePlan - * - * @return The NodeRef of the default RM filePlan - */ - private NodeRef getDefaultFilePlan() - { - NodeRef filePlan = authenticationUtil.runAsSystem(new org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork() - { - @Override - public NodeRef doWork() - { - return filePlanService.getFilePlanBySiteId(FilePlanService.DEFAULT_RM_SITE_ID); - } - }); - - // if the file plan is still null, raise an exception - if (filePlan == null) - { - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("Unable to execute " + NAME + " action, because the fileplan path could not be determined. Make sure at least one file plan has been created."); - throw new AlfrescoRuntimeException("Unable to execute " + NAME + " action, because the fileplan path could not be determined."); - } - } - return filePlan; - } } diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/DeclareAsVersionRecordAction.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/DeclareAsVersionRecordAction.java index 82b1fd52c8..3ad3c4cf00 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/DeclareAsVersionRecordAction.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/DeclareAsVersionRecordAction.java @@ -27,23 +27,32 @@ package org.alfresco.module.org_alfresco_module_rm.action.dm; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.action.AuditableActionExecuterAbstractBase; +import org.alfresco.module.org_alfresco_module_rm.action.dm.RecordActionUtils.Services; +import org.alfresco.module.org_alfresco_module_rm.capability.CapabilityService; import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService; 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.util.AuthenticationUtil; import org.alfresco.module.org_alfresco_module_rm.version.RecordableVersionService; +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; /** * Creates a new record from the 'current' document version. @@ -63,6 +72,9 @@ public class DeclareAsVersionRecordAction extends AuditableActionExecuterAbstrac /** Parameter names */ public static final String PARAM_FILE_PLAN = "file-plan"; + public static final String PARAM_PATH = "path"; + + private static final String FILE_VERSION_RECORDS_CAPABILITY = "FileVersionRecords"; /** Sync Model URI */ private static final String SYNC_MODEL_1_0_URI = "http://www.alfresco.org/model/sync/1.0"; @@ -79,12 +91,18 @@ public class DeclareAsVersionRecordAction extends AuditableActionExecuterAbstrac /** Dictionary service */ private DictionaryService dictionaryService; - /** recordable version service */ + /** Recordable version service */ private RecordableVersionService recordableVersionService; - /** authentication util */ + /** Authentication util */ private AuthenticationUtil authenticationUtil; + /** Record service */ + private RecordService recordService; + + /** Capability service */ + private CapabilityService capabilityService; + /** * @param nodeService node service */ @@ -118,13 +136,29 @@ public class DeclareAsVersionRecordAction extends AuditableActionExecuterAbstrac } /** - * @param authenticationUtil authentication util + * @param authenticationUtil authentication util */ public void setAuthenticationUtil(AuthenticationUtil authenticationUtil) { this.authenticationUtil = authenticationUtil; } + /** + * @param recordService record service + */ + public void setRecordService(RecordService recordService) + { + this.recordService = recordService; + } + + /** + * @param capabilityService capability service + */ + public void setCapabilityService(CapabilityService capabilityService) + { + this.capabilityService = capabilityService; + } + /** * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) */ @@ -147,87 +181,51 @@ public class DeclareAsVersionRecordAction extends AuditableActionExecuterAbstrac logger.debug("Can not declare version as record, because " + actionedUponNodeRef.toString() + " is not a supported type."); } } - else if (!nodeService.hasAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE)) - { - if (logger.isDebugEnabled()) - { - logger.debug("Can not declare version record, because " + actionedUponNodeRef.toString() + " does not have the versionable aspect applied."); - } - } - else if (nodeService.hasAspect(actionedUponNodeRef, ASPECT_RECORD)) - { - // Do not declare version record if the actioned upon node is already a record! - if (logger.isDebugEnabled()) - { - logger.debug("Can not declare version record, because " + actionedUponNodeRef.toString() + " is already a record."); - } - } - else if (nodeService.hasAspect(actionedUponNodeRef, ContentModel.ASPECT_WORKING_COPY)) - { - // We can not create records from working copies - if (logger.isDebugEnabled()) - { - logger.debug("Can not declare version record, because " + actionedUponNodeRef.toString() + " is a working copy."); - } - - } - else if (nodeService.hasAspect(actionedUponNodeRef, ASPECT_RECORD_REJECTION_DETAILS)) - { - // can not create a record from a previously rejected one - if (logger.isDebugEnabled()) - { - logger.debug("Can not declare version record, because " + actionedUponNodeRef.toString() + " has previously been rejected."); - } - } - else if (nodeService.hasAspect(actionedUponNodeRef, ASPECT_SYNCED)) - { - // can't declare the record if the node is sync'ed - if (logger.isDebugEnabled()) - { - logger.debug("Can't declare version record, because " + actionedUponNodeRef.toString() + " is synched content."); - } - } - else + else if (isActionEligible(actionedUponNodeRef)) { NodeRef filePlan = (NodeRef)action.getParameterValue(PARAM_FILE_PLAN); if (filePlan == null) { - // TODO .. eventually make the file plan parameter required - - filePlan = authenticationUtil.runAs(new org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork() + filePlan = RecordActionUtils.getDefaultFilePlan(authenticationUtil, filePlanService, NAME); + } + // verify that the provided file plan is actually a file plan + else if (!filePlanService.isFilePlan(filePlan)) + { + if (logger.isDebugEnabled()) { - @Override - public NodeRef doWork() - { - return filePlanService.getFilePlanBySiteId(FilePlanService.DEFAULT_RM_SITE_ID); - } - }, authenticationUtil.getAdminUserName()); + logger.debug("Can not declare version record, because the provided file plan node reference is not a file plan."); + } + throw new AlfrescoRuntimeException("Can not declare version record, because the provided file plan node reference is not a file plan."); + } - // if the file plan is still null, raise an exception - if (filePlan == null) + // resolve destination record folder if path supplied + NodeRef destinationRecordFolder = null; + String pathParameter = (String) action.getParameterValue(PARAM_PATH); + if (pathParameter != null && !pathParameter.isEmpty()) + { + RecordActionUtils.Services services = new Services(nodeService, filePlanService, authenticationUtil); + destinationRecordFolder = RecordActionUtils.resolvePath(services, filePlan, pathParameter, NAME); + } + + // create record from latest version + if (destinationRecordFolder != null) + { + boolean hasCapability = capabilityService.hasCapability(destinationRecordFolder, FILE_VERSION_RECORDS_CAPABILITY); + // validate destination record folder + if (hasCapability) { - if (logger.isDebugEnabled()) - { - logger.debug("Can not declare version record, because the default file plan can not be determined. Make sure at least one file plan has been created."); - } - throw new AlfrescoRuntimeException("Can not declare version record, because the default file plan can not be determined."); + NodeRef recordedVersion = recordableVersionService.createRecordFromLatestVersion(destinationRecordFolder, actionedUponNodeRef); + recordService.file(recordedVersion); + } + else + { + throw new AccessDeniedException(I18NUtil.getMessage("permissions.err_access_denied")); } } else { - // verify that the provided file plan is actually a file plan - if (!filePlanService.isFilePlan(filePlan)) - { - if (logger.isDebugEnabled()) - { - logger.debug("Can not declare version record, because the provided file plan node reference is not a file plan."); - } - throw new AlfrescoRuntimeException("Can not declare version record, because the provided file plan node reference is not a file plan."); - } + recordableVersionService.createRecordFromLatestVersion(filePlan, actionedUponNodeRef); } - - // create record from latest version - recordableVersionService.createRecordFromLatestVersion(filePlan, actionedUponNodeRef); } } @@ -239,6 +237,38 @@ public class DeclareAsVersionRecordAction extends AuditableActionExecuterAbstrac { // NOTE: commented out for now so that it doesn't appear in the UI ... enable later when multi-file plan support is added //params.add(new ParameterDefinitionImpl(PARAM_FILE_PLAN, DataTypeDefinition.NODE_REF, false, getParamDisplayLabel(PARAM_FILE_PLAN))); + params.add(new ParameterDefinitionImpl(PARAM_PATH, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_PATH))); } + /* Check aspects that stop declaring the version as record.*/ + private boolean isActionEligible(NodeRef actionedUponNodeRef) + { + Map mappedAspects = new HashMap<>(); + + mappedAspects.put(ASPECT_RECORD, " is already a record."); + mappedAspects.put(ContentModel.ASPECT_WORKING_COPY, " is a working copy."); + mappedAspects.put(ASPECT_RECORD_REJECTION_DETAILS, " has previously been rejected."); + mappedAspects.put(ASPECT_SYNCED, " is synched content."); + + for (Map.Entry aspect : mappedAspects.entrySet()) + { + if (nodeService.hasAspect(actionedUponNodeRef, aspect.getKey())) + { + if (logger.isDebugEnabled()) + { + logger.debug("Can not declare version record, because " + actionedUponNodeRef.toString() + aspect.getValue()); + } + return false; + } + } + if (!nodeService.hasAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE)) + { + if (logger.isDebugEnabled()) + { + logger.debug("Can not declare version record, because " + actionedUponNodeRef.toString() + " does not have the versionable aspect applied."); + } + return false; + } + return true; + } } diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/RecordActionUtils.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/RecordActionUtils.java new file mode 100644 index 0000000000..de087d4edd --- /dev/null +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/RecordActionUtils.java @@ -0,0 +1,175 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2020 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.action.dm; + +import java.util.Arrays; +import java.util.List; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService; +import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.module.org_alfresco_module_rm.util.AuthenticationUtil; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +/** + * Utility class containing helper methods for record + */ + +public class RecordActionUtils +{ + /** + * Logger + */ + private static final Logger LOGGER = LoggerFactory.getLogger(RecordActionUtils.class); + + /** Private constructor to prevent instantiation. */ + private RecordActionUtils() + { + } + + static class Services + { + private NodeService nodeService; + private FilePlanService filePlanService; + private AuthenticationUtil authenticationUtil; + + Services(NodeService nodeService, FilePlanService filePlanService, AuthenticationUtil authenticationUtil) + { + this.nodeService = nodeService; + this.filePlanService = filePlanService; + this.authenticationUtil = authenticationUtil; + } + + public NodeService getNodeService() + { + return nodeService; + } + + public FilePlanService getFilePlanService() + { + return filePlanService; + } + + public AuthenticationUtil getAuthenticationUtil() + { + return authenticationUtil; + } + } + + /** + * Helper method to get the target record folder node reference from the action path parameter + * + * @param filePlan The filePlan containing the path + * @param pathParameter The path + * @return The NodeRef of the resolved path + */ + static NodeRef resolvePath(Services services, NodeRef filePlan, final String pathParameter, String actionName) + { + NodeRef destinationFolder; + + if (filePlan == null) + { + filePlan = getDefaultFilePlan(services.getAuthenticationUtil(), services.getFilePlanService(), actionName); + } + + final String[] pathElementsArray = StringUtils.tokenizeToStringArray(pathParameter, "/", false, true); + if (pathElementsArray.length > 0) + { + destinationFolder = resolvePath(services.getNodeService(), filePlan, Arrays.asList(pathElementsArray), actionName); + + // destination must be a record folder + QName nodeType = services.getNodeService().getType(destinationFolder); + if (!nodeType.equals(RecordsManagementModel.TYPE_RECORD_FOLDER)) + { + throw new AlfrescoRuntimeException("Unable to execute " + actionName + " action, because the destination path is not a record folder."); + } + } + else + { + throw new AlfrescoRuntimeException("Unable to execute " + actionName + " action, because the destination path could not be found."); + } + return destinationFolder; + } + + /** + * Helper method to recursively get the next path element node reference from the action path parameter + * + * @param parent The parent of the path elements + * @param pathElements The path elements still to be resolved + * @return The NodeRef of the resolved path element + */ + static NodeRef resolvePath(NodeService nodeService, NodeRef parent, List pathElements, String actionName) + { + NodeRef nodeRef; + String childName = pathElements.get(0); + + nodeRef = nodeService.getChildByName(parent, ContentModel.ASSOC_CONTAINS, childName); + + if (nodeRef == null) + { + throw new AlfrescoRuntimeException("Unable to execute " + actionName + " action, because the destination path could not be found."); + } + QName nodeType = nodeService.getType(nodeRef); + if (nodeType.equals(RecordsManagementModel.TYPE_HOLD_CONTAINER) || + nodeType.equals(RecordsManagementModel.TYPE_TRANSFER_CONTAINER) || + nodeType.equals(RecordsManagementModel.TYPE_UNFILED_RECORD_CONTAINER)) + { + throw new AlfrescoRuntimeException("Unable to execute " + actionName + " action, because the destination path is invalid."); + } + if (pathElements.size() > 1) + { + nodeRef = resolvePath(nodeService, nodeRef, pathElements.subList(1, pathElements.size()), actionName); + } + return nodeRef; + } + + /** + * Helper method to get the default RM filePlan + * + * @return The NodeRef of the default RM filePlan + */ + static NodeRef getDefaultFilePlan(AuthenticationUtil authenticationUtil, FilePlanService filePlanService, String actionName) + { + NodeRef filePlan = authenticationUtil.runAsSystem(() -> filePlanService.getFilePlanBySiteId(FilePlanService.DEFAULT_RM_SITE_ID)); + + // if the file plan is still null, raise an exception + if (filePlan == null) + { + final String logMessage = + String.format("Unable to execute %s action, because the fileplan path could not be determined. Make sure at least one file plan has been created.", actionName); + LOGGER.debug(logMessage); + throw new AlfrescoRuntimeException(logMessage); + } + return filePlan; + } +} diff --git a/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/action/dm/DeclareAsVersionRecordActionUnitTest.java b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/action/dm/DeclareAsVersionRecordActionUnitTest.java index bee22e3a7d..86a9f49bf0 100644 --- a/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/action/dm/DeclareAsVersionRecordActionUnitTest.java +++ b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/action/dm/DeclareAsVersionRecordActionUnitTest.java @@ -38,12 +38,15 @@ import static org.mockito.Mockito.verify; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.action.BaseActionUnitTest; +import org.alfresco.module.org_alfresco_module_rm.capability.CapabilityService; import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; import org.junit.Test; import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.extensions.webscripts.GUID; /** * Declare as version record action unit test. @@ -59,10 +62,19 @@ public class DeclareAsVersionRecordActionUnitTest extends BaseActionUnitTest /** actioned upon node reference */ private NodeRef actionedUponNodeRef; + + /** destination record folder node reference */ + private NodeRef destinationRecordFolderNodeRef; + + /** parent destination node reference */ + private NodeRef parentDestinationNodeRef; /** declare as version record action */ private @InjectMocks DeclareAsVersionRecordAction declareAsVersionRecordAction; - + + @Mock + private CapabilityService mockedCapabilityService; + /** * @see org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest#before() */ @@ -76,6 +88,12 @@ public class DeclareAsVersionRecordActionUnitTest extends BaseActionUnitTest // mocked actioned upon noderef actionedUponNodeRef = generateNodeRef(); + + // mocked destination record folder nodeRef + destinationRecordFolderNodeRef = generateNodeRef(); + + // mocked parent destination nodeRef + parentDestinationNodeRef = generateNodeRef(); } /** @@ -200,27 +218,21 @@ public class DeclareAsVersionRecordActionUnitTest extends BaseActionUnitTest public void noFilePlanParameterNoDefaultFilePlan() { // setup - doReturn(true).when(mockedNodeService).exists(actionedUponNodeRef); - doReturn(true).when(mockedDictionaryService).isSubClass(any(QName.class), eq(ContentModel.TYPE_CONTENT)); - doReturn(true).when(mockedNodeService).hasAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE); - doReturn(false).when(mockedNodeService).hasAspect(actionedUponNodeRef, ASPECT_RECORD); - doReturn(false).when(mockedNodeService).hasAspect(actionedUponNodeRef, ContentModel.ASPECT_WORKING_COPY); - doReturn(false).when(mockedNodeService).hasAspect(actionedUponNodeRef, ASPECT_RECORD_REJECTION_DETAILS); - doReturn(false).when(mockedNodeService).hasAspect(actionedUponNodeRef, ASPECT_SYNCED); - + setupMockedAspects(); + // no default file plan doReturn(null).when(mockedFilePlanService).getFilePlanBySiteId(FilePlanService.DEFAULT_RM_SITE_ID); // expect exception exception.expect(AlfrescoRuntimeException.class); - // exceute action + // execute action declareAsVersionRecordAction.executeImpl(mock(Action.class), actionedUponNodeRef); } - - /** + + /** * Given that no file plan is provided - * And adefault file plan exists + * And a default file plan exists * When I execute the action * Then a version record is declared */ @@ -228,22 +240,16 @@ public class DeclareAsVersionRecordActionUnitTest extends BaseActionUnitTest public void noFilePlanParameterDefaultFilePlan() { // setup - doReturn(true).when(mockedNodeService).exists(actionedUponNodeRef); - doReturn(true).when(mockedDictionaryService).isSubClass(any(QName.class), eq(ContentModel.TYPE_CONTENT)); - doReturn(true).when(mockedNodeService).hasAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE); - doReturn(false).when(mockedNodeService).hasAspect(actionedUponNodeRef, ASPECT_RECORD); - doReturn(false).when(mockedNodeService).hasAspect(actionedUponNodeRef, ContentModel.ASPECT_WORKING_COPY); - doReturn(false).when(mockedNodeService).hasAspect(actionedUponNodeRef, ASPECT_RECORD_REJECTION_DETAILS); - doReturn(false).when(mockedNodeService).hasAspect(actionedUponNodeRef, ASPECT_SYNCED); - + setupMockedAspects(); + // no default file plan doReturn(filePlan).when(mockedFilePlanService).getFilePlanBySiteId(FilePlanService.DEFAULT_RM_SITE_ID); - - // exceute action - declareAsVersionRecordAction.executeImpl(mock(Action.class), actionedUponNodeRef); - verify(mockedRecordableVersionService, times(1)).createRecordFromLatestVersion(filePlan, actionedUponNodeRef); + + // execute action + declareAsVersionRecordAction.executeImpl(mock(Action.class), actionedUponNodeRef); + verify(mockedRecordableVersionService, times(1)).createRecordFromLatestVersion(filePlan, actionedUponNodeRef); } - + /** * Given that a file plan is provided * And it isn't a file plan @@ -254,21 +260,15 @@ public class DeclareAsVersionRecordActionUnitTest extends BaseActionUnitTest public void invalidFilePlanParameter() { // setup - doReturn(true).when(mockedNodeService).exists(actionedUponNodeRef); - doReturn(true).when(mockedDictionaryService).isSubClass(any(QName.class), eq(ContentModel.TYPE_CONTENT)); - doReturn(true).when(mockedNodeService).hasAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE); - doReturn(false).when(mockedNodeService).hasAspect(actionedUponNodeRef, ASPECT_RECORD); - doReturn(false).when(mockedNodeService).hasAspect(actionedUponNodeRef, ContentModel.ASPECT_WORKING_COPY); - doReturn(false).when(mockedNodeService).hasAspect(actionedUponNodeRef, ASPECT_RECORD_REJECTION_DETAILS); - doReturn(false).when(mockedNodeService).hasAspect(actionedUponNodeRef, ASPECT_SYNCED); - + setupMockedAspects(); + // not a file plan is provided in the parameters mockActionParameterValue(DeclareAsVersionRecordAction.PARAM_FILE_PLAN, generateNodeRef()); // expect exception exception.expect(AlfrescoRuntimeException.class); - // exceute action + // execute action declareAsVersionRecordAction.executeImpl(getMockedAction(), actionedUponNodeRef); } @@ -282,21 +282,81 @@ public class DeclareAsVersionRecordActionUnitTest extends BaseActionUnitTest public void validFilePlanParameter() { // setup - doReturn(true).when(mockedNodeService).exists(actionedUponNodeRef); - doReturn(true).when(mockedDictionaryService).isSubClass(any(QName.class), eq(ContentModel.TYPE_CONTENT)); - doReturn(true).when(mockedNodeService).hasAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE); - doReturn(false).when(mockedNodeService).hasAspect(actionedUponNodeRef, ASPECT_RECORD); - doReturn(false).when(mockedNodeService).hasAspect(actionedUponNodeRef, ContentModel.ASPECT_WORKING_COPY); - doReturn(false).when(mockedNodeService).hasAspect(actionedUponNodeRef, ASPECT_RECORD_REJECTION_DETAILS); - doReturn(false).when(mockedNodeService).hasAspect(actionedUponNodeRef, ASPECT_SYNCED); - + setupMockedAspects(); + // not a file plan is provided in the parameters NodeRef myFilePlan = generateNodeRef(TYPE_FILE_PLAN); doReturn(true).when(mockedFilePlanService).isFilePlan(myFilePlan); mockActionParameterValue(DeclareAsVersionRecordAction.PARAM_FILE_PLAN, myFilePlan); - // exceute action + // execute action declareAsVersionRecordAction.executeImpl(getMockedAction(), actionedUponNodeRef); verify(mockedRecordableVersionService, times(1)).createRecordFromLatestVersion(myFilePlan, actionedUponNodeRef); } + + /** + * Given that a valid location is provided + * When I execute the action + * Then a version record is declared in the provided location + */ + @Test + public void validDestinationRecordFolderProvided() + { + String pathParameter = GUID.generate(); + // setup + setupMockedAspects(); + + mockActionParameterValue(DeclareAsVersionRecordAction.PARAM_PATH, pathParameter); + + // provided location + doReturn(destinationRecordFolderNodeRef).when(mockedNodeService).getChildByName(filePlan, ContentModel.ASSOC_CONTAINS, pathParameter); + doReturn(TYPE_RECORD_FOLDER).when(mockedNodeService).getType(destinationRecordFolderNodeRef); + + // capability check + doReturn(true).when(mockedCapabilityService).hasCapability(destinationRecordFolderNodeRef, "FileVersionRecords"); + + // file plan service + doReturn(filePlan).when(mockedFilePlanService).getFilePlanBySiteId(FilePlanService.DEFAULT_RM_SITE_ID); + + // execute action + declareAsVersionRecordAction.executeImpl(getMockedAction(), actionedUponNodeRef); + verify(mockedRecordableVersionService, times(1)).createRecordFromLatestVersion(destinationRecordFolderNodeRef, actionedUponNodeRef); + } + + /** + * Given that an invalid location is provided + * When I execute the action + * Then an exception is thrown + */ + @Test + public void invalidDestinationRecordFolderProvided() + { + String childName = GUID.generate(); + // setup + setupMockedAspects(); + + // provided location + doReturn(destinationRecordFolderNodeRef).when(mockedNodeService).getChildByName(parentDestinationNodeRef, ContentModel.ASSOC_CONTAINS, childName); + doReturn(TYPE_RECORD_FOLDER).when(mockedNodeService).getType(destinationRecordFolderNodeRef); + + // capability check + doReturn(false).when(mockedCapabilityService).hasCapability(destinationRecordFolderNodeRef, "EditRecordMetadata"); + + // expect exception + exception.expect(AlfrescoRuntimeException.class); + + // execute action + declareAsVersionRecordAction.executeImpl(getMockedAction(), actionedUponNodeRef); + } + + private void setupMockedAspects() + { + doReturn(true).when(mockedNodeService).exists(actionedUponNodeRef); + doReturn(true).when(mockedDictionaryService).isSubClass(any(QName.class), eq(ContentModel.TYPE_CONTENT)); + doReturn(true).when(mockedNodeService).hasAspect(actionedUponNodeRef, ContentModel.ASPECT_VERSIONABLE); + doReturn(false).when(mockedNodeService).hasAspect(actionedUponNodeRef, ASPECT_RECORD); + doReturn(false).when(mockedNodeService).hasAspect(actionedUponNodeRef, ContentModel.ASPECT_WORKING_COPY); + doReturn(false).when(mockedNodeService).hasAspect(actionedUponNodeRef, ASPECT_RECORD_REJECTION_DETAILS); + doReturn(false).when(mockedNodeService).hasAspect(actionedUponNodeRef, ASPECT_SYNCED); + } }