diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/messages/actions.properties b/rm-server/config/alfresco/module/org_alfresco_module_rm/messages/actions.properties index f3dd8b81ba..f377f36af5 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/messages/actions.properties +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/messages/actions.properties @@ -89,6 +89,11 @@ moveTo.title=Move to moveTo.description=Moves a record to the specified record folder. moveTo.path.display-label=Path to Record Folder moveTo.createRecordPath.display-label=Create Record Path +# Link to +linkTo.title=Link to +linkTo.description=Links a record to the specified record folder. +linkTo.path.display-label=Path to Record Folder +linkTo.createRecordPath.display-label=Create Record Path # Reject reject.title=Reject reject.description=Rejects a record and moves the document to its original location diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-action-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-action-context.xml index f4ae65b80c..32b65eb8d8 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-action-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-action-context.xml @@ -911,6 +911,34 @@ + + + + + + + + + + + + + + + org.alfresco.module.org_alfresco_module_rm.action.RecordsManagementAction.execute=RM_CAP.0.rma:filePlanComponent.LinkToRecords + org.alfresco.module.org_alfresco_module_rm.action.RecordsManagementAction.*=RM_ALLOW + org.alfresco.repo.action.executer.ActionExecuter.*=RM_ALLOW + + + + + + + + + + + diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveFileToBaseAction.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveLinkFileToBaseAction.java similarity index 64% rename from rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveFileToBaseAction.java rename to rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveLinkFileToBaseAction.java index 3af05d184b..3a0d6102db 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveFileToBaseAction.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveLinkFileToBaseAction.java @@ -19,6 +19,8 @@ import org.alfresco.service.cmr.model.FileNotFoundException; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.util.StringUtils; /** @@ -27,13 +29,16 @@ import org.springframework.util.StringUtils; * @author Mark Hibbins * @since 2.2 */ -public abstract class CopyMoveFileToBaseAction extends RMActionExecuterAbstractBase +public abstract class CopyMoveLinkFileToBaseAction extends RMActionExecuterAbstractBase { + private static Log logger = LogFactory.getLog(CopyMoveLinkFileToBaseAction.class); + /** action parameters */ public static final String PARAM_DESTINATION_RECORD_FOLDER = "destinationRecordFolder"; public static final String PARAM_PATH = "path"; public static final String PARAM_CREATE_RECORD_PATH = "createRecordPath"; public static final String ACTION_FILETO = "fileTo"; + public static final String ACTION_LINKTO = "linkTo"; /** file folder service */ private FileFolderService fileFolderService; @@ -42,11 +47,11 @@ public abstract class CopyMoveFileToBaseAction extends RMActionExecuterAbstractB private FilePlanService filePlanService; /** action modes */ - public enum CopyMoveFileToActionMode + public enum CopyMoveLinkFileToActionMode { - COPY, MOVE + COPY, MOVE, LINK }; - protected CopyMoveFileToActionMode mode; + protected CopyMoveLinkFileToActionMode mode; /** * @param fileFolderService file folder service @@ -80,22 +85,20 @@ public abstract class CopyMoveFileToBaseAction extends RMActionExecuterAbstractB @Override protected void executeImpl(final Action action, final NodeRef actionedUponNodeRef) { - if (nodeService.exists(actionedUponNodeRef) && - (freezeService.isFrozen(actionedUponNodeRef) == false) && - (!ACTION_FILETO.equals(action.getActionDefinitionName()) || !recordService.isFiled(actionedUponNodeRef)) && - (!(ACTION_FILETO.equals(action.getActionDefinitionName()) && RecordsManagementModel.TYPE_UNFILED_RECORD_CONTAINER.equals(nodeService.getType(actionedUponNodeRef))))) + String actionName = action.getActionDefinitionName(); + if (isOkToProceedWithAction(actionedUponNodeRef, actionName)) { - boolean targetIsUnfiledRecord; + QName actionedUponType = nodeService.getType(actionedUponNodeRef);; + + boolean targetIsUnfiledRecords; if (ACTION_FILETO.equals(action.getActionDefinitionName())) { - targetIsUnfiledRecord = false; + targetIsUnfiledRecords = false; } else { - QName actionedUponType = nodeService.getType(actionedUponNodeRef); - targetIsUnfiledRecord = (dictionaryService.isSubClass(actionedUponType, ContentModel.TYPE_CONTENT) && !recordService - .isFiled(actionedUponNodeRef)) - || RecordsManagementModel.TYPE_UNFILED_RECORD_FOLDER.equals(actionedUponType); + targetIsUnfiledRecords = (dictionaryService.isSubClass(actionedUponType, ContentModel.TYPE_CONTENT) && !recordService.isFiled(actionedUponNodeRef)) + || TYPE_UNFILED_RECORD_FOLDER.equals(actionedUponType); } // first look to see if the destination record folder has been specified @@ -103,13 +106,11 @@ public abstract class CopyMoveFileToBaseAction extends RMActionExecuterAbstractB if (recordFolder == null) { // get the reference to the record folder based on the relative path - recordFolder = createOrResolvePath(action, actionedUponNodeRef, targetIsUnfiledRecord); + recordFolder = createOrResolvePath(action, actionedUponNodeRef, targetIsUnfiledRecords); } - if (recordFolder == null) - { - throw new AlfrescoRuntimeException("Unable to execute file to action, because the destination record folder could not be determined."); - } + // now we have the reference to the target folder we can do some final checks to see if the action is valid + validateActionPostPathResolution(actionedUponNodeRef, recordFolder, actionName, targetIsUnfiledRecords); final NodeRef finalRecordFolder = recordFolder; AuthenticationUtil.runAsSystem(new RunAsWork() @@ -119,19 +120,23 @@ public abstract class CopyMoveFileToBaseAction extends RMActionExecuterAbstractB { try { - if(mode == CopyMoveFileToActionMode.MOVE) + if(mode == CopyMoveLinkFileToActionMode.MOVE) { fileFolderService.move(actionedUponNodeRef, finalRecordFolder, null); } - else + else if(mode == CopyMoveLinkFileToActionMode.COPY) { fileFolderService.copy(actionedUponNodeRef, finalRecordFolder, null); } + else if(mode == CopyMoveLinkFileToActionMode.LINK) + { + recordService.link(actionedUponNodeRef, finalRecordFolder); + } } catch (FileNotFoundException fileNotFound) { throw new AlfrescoRuntimeException( - "Unable to execute file to action, because the " + (mode == CopyMoveFileToActionMode.MOVE ? "move" : "copy") + " operation failed.", + "Unable to execute file to action, because the " + (mode == CopyMoveLinkFileToActionMode.MOVE ? "move" : "copy") + " operation failed.", fileNotFound ); } @@ -142,6 +147,83 @@ public abstract class CopyMoveFileToBaseAction extends RMActionExecuterAbstractB } } + /** + * Return true if the passed parameters to the action are valid for the given action + * + * @param actionedUponNodeRef + * @param actionName + * @return + */ + private boolean isOkToProceedWithAction(NodeRef actionedUponNodeRef, String actionName) + { + // Check that the incoming parameters are valid prior to performing any action + boolean okToProceed = false; + if(nodeService.exists(actionedUponNodeRef) && !freezeService.isFrozen(actionedUponNodeRef)) + { + QName actionedUponType = nodeService.getType(actionedUponNodeRef); + if(ACTION_FILETO.equals(actionName)) + { + // file to action can only be performed on unfiled records + okToProceed = !recordService.isFiled(actionedUponNodeRef) && dictionaryService.isSubClass(actionedUponType, ContentModel.TYPE_CONTENT); + if(!okToProceed && logger.isDebugEnabled()) + { + logger.debug("Unable to run " + actionName + " action on a node that isn't unfiled and a sub-class of content type"); + } + } + else if(ACTION_LINKTO.equals(actionName)) + { + // link to action can only be performed on filed records + okToProceed = recordService.isFiled(actionedUponNodeRef) && dictionaryService.isSubClass(actionedUponType, ContentModel.TYPE_CONTENT); + if(!okToProceed && logger.isDebugEnabled()) + { + logger.debug("Unable to run " + actionName + " action on a node that isn't filed and a sub-class of content type"); + } + } + else + { + okToProceed = true; + } + } + return okToProceed; + } + + /** + * Do a final validation for the parameters and the resolve target path + * + * @param actionedUponNodeRef + * @param target + * @param actionName + * @param targetIsUnfiledRecords + */ + private void validateActionPostPathResolution(NodeRef actionedUponNodeRef, NodeRef target, String actionName, boolean targetIsUnfiledRecords) + { + QName actionedUponType = nodeService.getType(actionedUponNodeRef); + // now we have the reference to the target folder we can do some final checks to see if the action is valid + if (target == null) + { + throw new AlfrescoRuntimeException("Unable to run " + actionName + " action, because the destination record folder could not be determined."); + } + if(targetIsUnfiledRecords) + { + QName targetFolderType = nodeService.getType(target); + if(!TYPE_UNFILED_RECORD_CONTAINER.equals(targetFolderType) && !TYPE_UNFILED_RECORD_FOLDER.equals(targetFolderType)) + { + throw new AlfrescoRuntimeException("Unable to run " + actionName + " action, because the destination record folder is an inappropriate type."); + } + } + else + { + if(recordFolderService.isRecordFolder(target) && !dictionaryService.isSubClass(actionedUponType, ContentModel.TYPE_CONTENT) && (recordFolderService.isRecordFolder(actionedUponNodeRef) || filePlanService.isRecordCategory(actionedUponNodeRef))) + { + throw new AlfrescoRuntimeException("Unable to run " + actionName + " action, because the destination record folder is an inappropriate type. A record folder cannot contain another folder or a category"); + } + else if(filePlanService.isRecordCategory(target) && dictionaryService.isSubClass(actionedUponType, ContentModel.TYPE_CONTENT)) + { + throw new AlfrescoRuntimeException("Unable to run " + actionName + " action, because the destination record folder is an inappropriate type. A record category cannot contain a record"); + } + } + } + /** * Create or resolve the path specified in the action's path parameter * @@ -197,12 +279,8 @@ public abstract class CopyMoveFileToBaseAction extends RMActionExecuterAbstractB if(create) { creating = true; - nodeRef = createChild( - action, - parent, - childName, - targetisUnfiledRecords, - lastPathElement && (ContentModel.TYPE_CONTENT.equals(nodeService.getType(actionedUponNodeRef)) || RecordsManagementModel.TYPE_NON_ELECTRONIC_DOCUMENT.equals(nodeService.getType(actionedUponNodeRef)))); + boolean lastAsFolder = lastPathElement && (ContentModel.TYPE_CONTENT.equals(nodeService.getType(actionedUponNodeRef)) || RecordsManagementModel.TYPE_NON_ELECTRONIC_DOCUMENT.equals(nodeService.getType(actionedUponNodeRef))); + nodeRef = createChild(action, parent, childName, targetisUnfiledRecords, lastAsFolder); } else { diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyToAction.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyToAction.java index 23da848210..11d2a1653d 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyToAction.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyToAction.java @@ -7,7 +7,7 @@ package org.alfresco.module.org_alfresco_module_rm.action.impl; * @author Mark Hibbins * @since 2.2 */ -public class CopyToAction extends CopyMoveFileToBaseAction +public class CopyToAction extends CopyMoveLinkFileToBaseAction { /** action name */ public static final String NAME = "copyTo"; @@ -16,6 +16,6 @@ public class CopyToAction extends CopyMoveFileToBaseAction public void init() { super.init(); - this.mode = CopyMoveFileToActionMode.COPY; + this.mode = CopyMoveLinkFileToActionMode.COPY; } } \ No newline at end of file diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/FileToAction.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/FileToAction.java index fe365d48da..3f6e61ee15 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/FileToAction.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/FileToAction.java @@ -7,7 +7,7 @@ package org.alfresco.module.org_alfresco_module_rm.action.impl; * @author Roy Wetherall * @since 2.1 */ -public class FileToAction extends CopyMoveFileToBaseAction +public class FileToAction extends CopyMoveLinkFileToBaseAction { /** action name */ public static final String NAME = "fileTo"; @@ -16,6 +16,6 @@ public class FileToAction extends CopyMoveFileToBaseAction public void init() { super.init(); - this.mode = CopyMoveFileToActionMode.MOVE; + this.mode = CopyMoveLinkFileToActionMode.MOVE; } } diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/LinkToAction.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/LinkToAction.java new file mode 100644 index 0000000000..707e7295c6 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/LinkToAction.java @@ -0,0 +1,21 @@ +package org.alfresco.module.org_alfresco_module_rm.action.impl; + + +/** + * Link To action implementation. + * + * @author Mark Hibbins + * @since 2.2 + */ +public class LinkToAction extends CopyMoveLinkFileToBaseAction +{ + /** action name */ + public static final String NAME = "linkTo"; + + @Override + public void init() + { + super.init(); + this.mode = CopyMoveLinkFileToActionMode.LINK; + } +} \ No newline at end of file diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/MoveToAction.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/MoveToAction.java index 114cd099f0..429c6353d8 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/MoveToAction.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/MoveToAction.java @@ -7,7 +7,7 @@ package org.alfresco.module.org_alfresco_module_rm.action.impl; * @author Mark Hibbins * @since 2.2 */ -public class MoveToAction extends CopyMoveFileToBaseAction +public class MoveToAction extends CopyMoveLinkFileToBaseAction { /** action name */ public static final String NAME = "moveTo"; @@ -16,6 +16,6 @@ public class MoveToAction extends CopyMoveFileToBaseAction public void init() { super.init(); - this.mode = CopyMoveFileToActionMode.MOVE; + this.mode = CopyMoveLinkFileToActionMode.MOVE; } } \ No newline at end of file diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordService.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordService.java index 45a1d392a3..6603a813b4 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordService.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordService.java @@ -39,19 +39,19 @@ public interface RecordService /** * Register a record metadata aspect. *

- * The file plan type indicates which file plan type the aspect applied to. Null indicates that + * The file plan type indicates which file plan type the aspect applied to. Null indicates that * the aspect applies to rma:filePlan. *

- * A record metadata aspect can be registered more than once if it applies to more than one + * A record metadata aspect can be registered more than once if it applies to more than one * file plan type. - * + * * @param recordMetadataAspect record metadata aspect qualified name * @param filePlanType file plan type - * + * * @since 2.2 */ void registerRecordMetadataAspect(QName recordMetadataAspect, QName filePlanType); - + /** * Disables the property editable check. */ @@ -66,35 +66,35 @@ public interface RecordService * Gets a list of all the record meta-data aspects * * @return {@link Set}<{@link QName}> list of record meta-data aspects - * + * * @deprecated since 2.2, file plan component required to provide context */ @Deprecated Set getRecordMetaDataAspects(); - + /** * Gets a list of all the record metadata aspects relevant to the file plan type of the * file plan component provided. *

- * If a null context is provided all record meta-data aspects are returned, but this is not + * If a null context is provided all record meta-data aspects are returned, but this is not * recommended. * * @param nodeRef node reference to file plan component providing context * @return {@link Set}<{@link QName}> list of record meta-data aspects - * + * * @since 2.2 */ Set getRecordMetadataAspects(NodeRef nodeRef); - + /** * Gets a list of all the record metadata aspect that relate to the provided file plan type. *

- * If null is provided for the file plan type then record metadata aspects for the default + * If null is provided for the file plan type then record metadata aspects for the default * file plan type (rma:filePlan) are returned. - * + * * @param filePlanType file plan type * @return{@link Set}<{@link QName}> list of record meta-data aspects - * + * * @since 2.2 */ Set getRecordMetadataAspects(QName filePlanType); @@ -214,4 +214,12 @@ public interface RecordService * @param nodeRef The document node reference from which a record will be created */ void makeRecord(NodeRef nodeRef); + + /** + * Creates a link for the specified document in target + * + * @param nodeRef The document node reference for which a link will be created + * @param folder The folder in which the link will be created + */ + void link(NodeRef nodeRef, NodeRef folder); } 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 46bb53f4ca..48f0f987ae 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 @@ -635,7 +635,7 @@ public class RecordServiceImpl extends BaseBehaviourBean if (getRecordMetadataAspectsMap().containsKey(recordMetadataAspect)) { // get the current set of file plan types for this aspect - filePlanTypes = (Set)getRecordMetadataAspectsMap().get(recordMetadataAspect); + filePlanTypes = getRecordMetadataAspectsMap().get(recordMetadataAspect); } else { @@ -717,6 +717,7 @@ public class RecordServiceImpl extends BaseBehaviourBean /** * @see org.alfresco.module.org_alfresco_module_rm.record.RecordService#createRecord(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, boolean) */ + @Override public void createRecord(final NodeRef filePlan, final NodeRef nodeRef, final boolean isLinked) { ParameterCheck.mandatory("filePlan", filePlan); @@ -773,7 +774,7 @@ public class RecordServiceImpl extends BaseBehaviourBean // save the information about the originating details Map aspectProperties = new HashMap(3); - aspectProperties.put(PROP_RECORD_ORIGINATING_LOCATION, (Serializable) parentAssoc.getParentRef()); + aspectProperties.put(PROP_RECORD_ORIGINATING_LOCATION, parentAssoc.getParentRef()); aspectProperties.put(PROP_RECORD_ORIGINATING_USER_ID, userId); aspectProperties.put(PROP_RECORD_ORIGINATING_CREATION_DATE, new Date()); nodeService.addAspect(nodeRef, ASPECT_RECORD_ORIGINATING_DETAILS, aspectProperties); @@ -1391,4 +1392,19 @@ public class RecordServiceImpl extends BaseBehaviourBean logger.info(I18NUtil.getMessage(MSG_NODE_HAS_ASPECT, nodeRef.toString(), typeQName.toString())); } } + + /** + * @see org.alfresco.module.org_alfresco_module_rm.record.RecordService#link(NodeRef, NodeRef) + */ + @Override + public void link(NodeRef nodeRef, NodeRef folder) + { + ParameterCheck.mandatory("nodeRef", nodeRef); + ParameterCheck.mandatory("folder", folder); + + if(isRecord(nodeRef) && isRecordFolder(folder)) + { + nodeService.addChild(folder, nodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, nodeService.getProperty(nodeRef, ContentModel.PROP_NAME).toString())); + } + } }