RM-1445m Implemented link-to rule. Also fixed RM-1466 and RM-1470 while refactoring common code

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/modules/recordsmanagement/HEAD@68076 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Mark Hibbins
2014-04-25 15:51:57 +00:00
parent 7583392341
commit 706dc33384
9 changed files with 205 additions and 49 deletions

View File

@@ -89,6 +89,11 @@ moveTo.title=Move to
moveTo.description=Moves a record to the specified record folder. moveTo.description=Moves a record to the specified record folder.
moveTo.path.display-label=Path to Record Folder moveTo.path.display-label=Path to Record Folder
moveTo.createRecordPath.display-label=Create Record Path 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
reject.title=Reject reject.title=Reject
reject.description=Rejects a record and moves the document to its original location reject.description=Rejects a record and moves the document to its original location

View File

@@ -911,6 +911,34 @@
<property name="allowParameterSubstitutions" value="true"/> <property name="allowParameterSubstitutions" value="true"/>
</bean> </bean>
<!-- Link To -->
<bean id="linkTo_proxy" parent="rmProxyAction">
<property name="target" ref="linkTo"/>
<property name="interceptorNames">
<list>
<idref bean="linkTo_security"/>
</list>
</property>
</bean>
<bean id="linkTo_security" class="org.alfresco.repo.security.permissions.impl.acegi.MethodSecurityInterceptor" parent="actionSecurity">
<property name="objectDefinitionSource">
<value>
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
</value>
</property>
</bean>
<bean id="linkTo" class="org.alfresco.module.org_alfresco_module_rm.action.impl.LinkToAction" parent="rmAction">
<property name="fileFolderService" ref="FileFolderService"/>
<property name="filePlanService" ref="FilePlanService" />
<property name="publicAction" value="true"/>
<property name="allowParameterSubstitutions" value="true"/>
</bean>
<!-- RequestInfo action --> <!-- RequestInfo action -->
<bean id="requestInfo_proxy" parent="rmProxyAction"> <bean id="requestInfo_proxy" parent="rmProxyAction">

View File

@@ -19,6 +19,8 @@ import org.alfresco.service.cmr.model.FileNotFoundException;
import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@@ -27,13 +29,16 @@ import org.springframework.util.StringUtils;
* @author Mark Hibbins * @author Mark Hibbins
* @since 2.2 * @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 */ /** action parameters */
public static final String PARAM_DESTINATION_RECORD_FOLDER = "destinationRecordFolder"; public static final String PARAM_DESTINATION_RECORD_FOLDER = "destinationRecordFolder";
public static final String PARAM_PATH = "path"; public static final String PARAM_PATH = "path";
public static final String PARAM_CREATE_RECORD_PATH = "createRecordPath"; public static final String PARAM_CREATE_RECORD_PATH = "createRecordPath";
public static final String ACTION_FILETO = "fileTo"; public static final String ACTION_FILETO = "fileTo";
public static final String ACTION_LINKTO = "linkTo";
/** file folder service */ /** file folder service */
private FileFolderService fileFolderService; private FileFolderService fileFolderService;
@@ -42,11 +47,11 @@ public abstract class CopyMoveFileToBaseAction extends RMActionExecuterAbstractB
private FilePlanService filePlanService; private FilePlanService filePlanService;
/** action modes */ /** action modes */
public enum CopyMoveFileToActionMode public enum CopyMoveLinkFileToActionMode
{ {
COPY, MOVE COPY, MOVE, LINK
}; };
protected CopyMoveFileToActionMode mode; protected CopyMoveLinkFileToActionMode mode;
/** /**
* @param fileFolderService file folder service * @param fileFolderService file folder service
@@ -80,22 +85,20 @@ public abstract class CopyMoveFileToBaseAction extends RMActionExecuterAbstractB
@Override @Override
protected void executeImpl(final Action action, final NodeRef actionedUponNodeRef) protected void executeImpl(final Action action, final NodeRef actionedUponNodeRef)
{ {
if (nodeService.exists(actionedUponNodeRef) && String actionName = action.getActionDefinitionName();
(freezeService.isFrozen(actionedUponNodeRef) == false) && if (isOkToProceedWithAction(actionedUponNodeRef, actionName))
(!ACTION_FILETO.equals(action.getActionDefinitionName()) || !recordService.isFiled(actionedUponNodeRef)) &&
(!(ACTION_FILETO.equals(action.getActionDefinitionName()) && RecordsManagementModel.TYPE_UNFILED_RECORD_CONTAINER.equals(nodeService.getType(actionedUponNodeRef)))))
{ {
boolean targetIsUnfiledRecord; QName actionedUponType = nodeService.getType(actionedUponNodeRef);;
boolean targetIsUnfiledRecords;
if (ACTION_FILETO.equals(action.getActionDefinitionName())) if (ACTION_FILETO.equals(action.getActionDefinitionName()))
{ {
targetIsUnfiledRecord = false; targetIsUnfiledRecords = false;
} }
else else
{ {
QName actionedUponType = nodeService.getType(actionedUponNodeRef); targetIsUnfiledRecords = (dictionaryService.isSubClass(actionedUponType, ContentModel.TYPE_CONTENT) && !recordService.isFiled(actionedUponNodeRef))
targetIsUnfiledRecord = (dictionaryService.isSubClass(actionedUponType, ContentModel.TYPE_CONTENT) && !recordService || TYPE_UNFILED_RECORD_FOLDER.equals(actionedUponType);
.isFiled(actionedUponNodeRef))
|| RecordsManagementModel.TYPE_UNFILED_RECORD_FOLDER.equals(actionedUponType);
} }
// first look to see if the destination record folder has been specified // 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) if (recordFolder == null)
{ {
// get the reference to the record folder based on the relative path // 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) // 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);
throw new AlfrescoRuntimeException("Unable to execute file to action, because the destination record folder could not be determined.");
}
final NodeRef finalRecordFolder = recordFolder; final NodeRef finalRecordFolder = recordFolder;
AuthenticationUtil.runAsSystem(new RunAsWork<Void>() AuthenticationUtil.runAsSystem(new RunAsWork<Void>()
@@ -119,19 +120,23 @@ public abstract class CopyMoveFileToBaseAction extends RMActionExecuterAbstractB
{ {
try try
{ {
if(mode == CopyMoveFileToActionMode.MOVE) if(mode == CopyMoveLinkFileToActionMode.MOVE)
{ {
fileFolderService.move(actionedUponNodeRef, finalRecordFolder, null); fileFolderService.move(actionedUponNodeRef, finalRecordFolder, null);
} }
else else if(mode == CopyMoveLinkFileToActionMode.COPY)
{ {
fileFolderService.copy(actionedUponNodeRef, finalRecordFolder, null); fileFolderService.copy(actionedUponNodeRef, finalRecordFolder, null);
} }
else if(mode == CopyMoveLinkFileToActionMode.LINK)
{
recordService.link(actionedUponNodeRef, finalRecordFolder);
}
} }
catch (FileNotFoundException fileNotFound) catch (FileNotFoundException fileNotFound)
{ {
throw new AlfrescoRuntimeException( 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 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 * Create or resolve the path specified in the action's path parameter
* *
@@ -197,12 +279,8 @@ public abstract class CopyMoveFileToBaseAction extends RMActionExecuterAbstractB
if(create) if(create)
{ {
creating = true; creating = true;
nodeRef = createChild( boolean lastAsFolder = lastPathElement && (ContentModel.TYPE_CONTENT.equals(nodeService.getType(actionedUponNodeRef)) || RecordsManagementModel.TYPE_NON_ELECTRONIC_DOCUMENT.equals(nodeService.getType(actionedUponNodeRef)));
action, nodeRef = createChild(action, parent, childName, targetisUnfiledRecords, lastAsFolder);
parent,
childName,
targetisUnfiledRecords,
lastPathElement && (ContentModel.TYPE_CONTENT.equals(nodeService.getType(actionedUponNodeRef)) || RecordsManagementModel.TYPE_NON_ELECTRONIC_DOCUMENT.equals(nodeService.getType(actionedUponNodeRef))));
} }
else else
{ {

View File

@@ -7,7 +7,7 @@ package org.alfresco.module.org_alfresco_module_rm.action.impl;
* @author Mark Hibbins * @author Mark Hibbins
* @since 2.2 * @since 2.2
*/ */
public class CopyToAction extends CopyMoveFileToBaseAction public class CopyToAction extends CopyMoveLinkFileToBaseAction
{ {
/** action name */ /** action name */
public static final String NAME = "copyTo"; public static final String NAME = "copyTo";
@@ -16,6 +16,6 @@ public class CopyToAction extends CopyMoveFileToBaseAction
public void init() public void init()
{ {
super.init(); super.init();
this.mode = CopyMoveFileToActionMode.COPY; this.mode = CopyMoveLinkFileToActionMode.COPY;
} }
} }

View File

@@ -7,7 +7,7 @@ package org.alfresco.module.org_alfresco_module_rm.action.impl;
* @author Roy Wetherall * @author Roy Wetherall
* @since 2.1 * @since 2.1
*/ */
public class FileToAction extends CopyMoveFileToBaseAction public class FileToAction extends CopyMoveLinkFileToBaseAction
{ {
/** action name */ /** action name */
public static final String NAME = "fileTo"; public static final String NAME = "fileTo";
@@ -16,6 +16,6 @@ public class FileToAction extends CopyMoveFileToBaseAction
public void init() public void init()
{ {
super.init(); super.init();
this.mode = CopyMoveFileToActionMode.MOVE; this.mode = CopyMoveLinkFileToActionMode.MOVE;
} }
} }

View File

@@ -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;
}
}

View File

@@ -7,7 +7,7 @@ package org.alfresco.module.org_alfresco_module_rm.action.impl;
* @author Mark Hibbins * @author Mark Hibbins
* @since 2.2 * @since 2.2
*/ */
public class MoveToAction extends CopyMoveFileToBaseAction public class MoveToAction extends CopyMoveLinkFileToBaseAction
{ {
/** action name */ /** action name */
public static final String NAME = "moveTo"; public static final String NAME = "moveTo";
@@ -16,6 +16,6 @@ public class MoveToAction extends CopyMoveFileToBaseAction
public void init() public void init()
{ {
super.init(); super.init();
this.mode = CopyMoveFileToActionMode.MOVE; this.mode = CopyMoveLinkFileToActionMode.MOVE;
} }
} }

View File

@@ -39,19 +39,19 @@ public interface RecordService
/** /**
* Register a record metadata aspect. * Register a record metadata aspect.
* <p> * <p>
* 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. * the aspect applies to rma:filePlan.
* <p> * <p>
* 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. * file plan type.
* *
* @param recordMetadataAspect record metadata aspect qualified name * @param recordMetadataAspect record metadata aspect qualified name
* @param filePlanType file plan type * @param filePlanType file plan type
* *
* @since 2.2 * @since 2.2
*/ */
void registerRecordMetadataAspect(QName recordMetadataAspect, QName filePlanType); void registerRecordMetadataAspect(QName recordMetadataAspect, QName filePlanType);
/** /**
* Disables the property editable check. * Disables the property editable check.
*/ */
@@ -66,35 +66,35 @@ public interface RecordService
* Gets a list of all the record meta-data aspects * Gets a list of all the record meta-data aspects
* *
* @return {@link Set}<{@link QName}> list of 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 since 2.2, file plan component required to provide context
*/ */
@Deprecated @Deprecated
Set<QName> getRecordMetaDataAspects(); Set<QName> getRecordMetaDataAspects();
/** /**
* Gets a list of all the record metadata aspects relevant to the file plan type of the * Gets a list of all the record metadata aspects relevant to the file plan type of the
* file plan component provided. * file plan component provided.
* <p> * <p>
* 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. * recommended.
* *
* @param nodeRef node reference to file plan component providing context * @param nodeRef node reference to file plan component providing context
* @return {@link Set}<{@link QName}> list of record meta-data aspects * @return {@link Set}<{@link QName}> list of record meta-data aspects
* *
* @since 2.2 * @since 2.2
*/ */
Set<QName> getRecordMetadataAspects(NodeRef nodeRef); Set<QName> getRecordMetadataAspects(NodeRef nodeRef);
/** /**
* Gets a list of all the record metadata aspect that relate to the provided file plan type. * Gets a list of all the record metadata aspect that relate to the provided file plan type.
* <p> * <p>
* 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. * file plan type (rma:filePlan) are returned.
* *
* @param filePlanType file plan type * @param filePlanType file plan type
* @return{@link Set}<{@link QName}> list of record meta-data aspects * @return{@link Set}<{@link QName}> list of record meta-data aspects
* *
* @since 2.2 * @since 2.2
*/ */
Set<QName> getRecordMetadataAspects(QName filePlanType); Set<QName> getRecordMetadataAspects(QName filePlanType);
@@ -214,4 +214,12 @@ public interface RecordService
* @param nodeRef The document node reference from which a record will be created * @param nodeRef The document node reference from which a record will be created
*/ */
void makeRecord(NodeRef nodeRef); 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);
} }

View File

@@ -635,7 +635,7 @@ public class RecordServiceImpl extends BaseBehaviourBean
if (getRecordMetadataAspectsMap().containsKey(recordMetadataAspect)) if (getRecordMetadataAspectsMap().containsKey(recordMetadataAspect))
{ {
// get the current set of file plan types for this aspect // get the current set of file plan types for this aspect
filePlanTypes = (Set<QName>)getRecordMetadataAspectsMap().get(recordMetadataAspect); filePlanTypes = getRecordMetadataAspectsMap().get(recordMetadataAspect);
} }
else 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) * @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) public void createRecord(final NodeRef filePlan, final NodeRef nodeRef, final boolean isLinked)
{ {
ParameterCheck.mandatory("filePlan", filePlan); ParameterCheck.mandatory("filePlan", filePlan);
@@ -773,7 +774,7 @@ public class RecordServiceImpl extends BaseBehaviourBean
// save the information about the originating details // save the information about the originating details
Map<QName, Serializable> aspectProperties = new HashMap<QName, Serializable>(3); Map<QName, Serializable> aspectProperties = new HashMap<QName, Serializable>(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_USER_ID, userId);
aspectProperties.put(PROP_RECORD_ORIGINATING_CREATION_DATE, new Date()); aspectProperties.put(PROP_RECORD_ORIGINATING_CREATION_DATE, new Date());
nodeService.addAspect(nodeRef, ASPECT_RECORD_ORIGINATING_DETAILS, aspectProperties); 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())); 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()));
}
}
} }