Merged master into feature/RM-6806_DeclareAndFileToUI

This commit is contained in:
rlucanu
2019-05-22 09:07:54 +03:00
10 changed files with 392 additions and 7 deletions

View File

@@ -18,6 +18,9 @@
<!-- TODO rename -->
<bean id="create-record" parent="action-executer" class="org.alfresco.module.org_alfresco_module_rm.action.dm.CreateRecordAction">
<property name="recordService" ref="RecordService" />
<property name="authenticationUtil" ref="rm.authenticationUtil" />
<property name="filePlanService" ref="FilePlanService" />
<property name="nodeService" ref="NodeService" />
<property name="applicableTypes">
<list>
<value>{http://www.alfresco.org/model/content/1.0}content</value>

View File

@@ -339,7 +339,6 @@
<entry key="capabilityCondition.cutoff" value="false"/>
<entry key="capabilityCondition.closed" value="false"/>
<entry key="capabilityCondition.declared" value="false"/>
<entry key="capabilityCondition.recordFiled" value="false"/>
<entry key="capabilityCondition.isRecordOriginatingLocationSet" value="true" />
</map>
</property>

View File

@@ -48,7 +48,7 @@ isRecordType.description=Records have a specified record type
#
# Declare As Record
create-record.title=Declare as Record
create-record.description=Declares file as a record
create-record.description=Declares file as a record and optionally files it
create-record.file-plan.display-label=File Plan
create-record.hide-record.display-label=Hide Record
create-record.path.display-label=Destination Record Folder Path

View File

@@ -27,16 +27,27 @@
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.action.AuditableActionExecuterAbstractBase;
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.repo.action.ParameterDefinitionImpl;
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.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.
@@ -48,16 +59,53 @@ import org.alfresco.service.cmr.repository.NodeRef;
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";
/** Parameter names */
public static final String PARAM_FILE_PLAN = "file-plan";
public static final String PARAM_HIDE_RECORD = "hide-record";
public static final String PARAM_PATH = "path";
/** Node service */
private NodeService nodeService;
/** File plan service */
private FilePlanService filePlanService;
/** Authentication util */
private AuthenticationUtil authenticationUtil;
/** Record service */
private RecordService recordService;
/**
* @param nodeService node service
*/
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
/**
* @param filePlanService file plan service
*/
public void setFilePlanService(FilePlanService filePlanService)
{
this.filePlanService = filePlanService;
}
/**
* @param authenticationUtil authentication util
*/
public void setAuthenticationUtil(AuthenticationUtil authenticationUtil)
{
this.authenticationUtil = authenticationUtil;
}
/**
* @param recordService record service
*/
@@ -74,6 +122,14 @@ public class CreateRecordAction extends AuditableActionExecuterAbstractBase
{
NodeRef filePlan = (NodeRef) action.getParameterValue(PARAM_FILE_PLAN);
// resolve destination record folder if path supplied
NodeRef destinationRecordFolder = null;
String pathParameter = (String) action.getParameterValue(PARAM_PATH);
if (pathParameter != null && !pathParameter.isEmpty())
{
destinationRecordFolder = resolvePath(filePlan, pathParameter);
}
// indicate whether the record should be hidden or not (default not)
boolean hideRecord = false;
Boolean hideRecordValue = ((Boolean) action.getParameterValue(PARAM_HIDE_RECORD));
@@ -85,7 +141,12 @@ public class CreateRecordAction extends AuditableActionExecuterAbstractBase
synchronized (this)
{
// create record from existing document
recordService.createRecord(filePlan, actionedUponNodeRef, !hideRecord);
recordService.createRecord(filePlan, actionedUponNodeRef, destinationRecordFolder, !hideRecord);
if (destinationRecordFolder != null)
{
recordService.file(actionedUponNodeRef);
}
}
}
@@ -97,6 +158,105 @@ public class CreateRecordAction extends AuditableActionExecuterAbstractBase
{
// 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)));
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<String> 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<NodeRef>()
{
@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;
}
}

View File

@@ -159,6 +159,31 @@ public interface RecordService
*/
boolean isDeclared(NodeRef nodeRef);
/**
* Creates a new record from an existing node and files it into the specified location.
* <p>
* Note that the node reference of the record will be the same as the original
* document.
*
* @param filePlan The filePlan in which the record should be placed. filePlan can be <code>null</code> in this case the default RM site will be used.
* @param nodeRef The node from which the record will be created
* @param locationNodeRef The container in which the record will be created
* @param isLinked indicates if the newly created record is linked to it's original location or not.
*/
void createRecord(final NodeRef filePlan, final NodeRef nodeRef, final NodeRef locationNodeRef, final boolean isLinked);
/**
* Creates a new record from an existing node and files it into the specified location.
* <p>
* Note that the node reference of the record will be the same as the original
* document.
*
* @param filePlan The filePlan in which the record should be placed. filePlan can be <code>null</code> in this case the default RM site will be used.
* @param nodeRef The node from which the record will be created
* @param locationNodeRef The container in which the record will be created
*/
void createRecord(final NodeRef filePlan, final NodeRef nodeRef, final NodeRef locationNodeRef);
/**
* Creates a new unfiled record from an existing node.
* <p>

View File

@@ -861,8 +861,27 @@ public class RecordServiceImpl extends BaseBehaviourBean
*/
@Override
public void createRecord(final NodeRef filePlan, final NodeRef nodeRef, final boolean isLinked)
{
createRecord(filePlan, nodeRef, null, isLinked);
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.record.RecordService#createRecord(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef)
*/
@Override
public void createRecord(final NodeRef filePlan, final NodeRef nodeRef, final NodeRef destinationNodeRef)
{
createRecord(filePlan, nodeRef, destinationNodeRef, true);
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.record.RecordService#createRecord(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, boolean)
*/
@Override
public void createRecord(final NodeRef filePlan, final NodeRef nodeRef, final NodeRef destinationNodeRef, final boolean isLinked)
{
// filePlan can be null. In this case the default RM site will be used.
// destinationNodeRef can be null. In this case the unfiled record container will be used
ParameterCheck.mandatory("nodeRef", nodeRef);
ParameterCheck.mandatory("isLinked", isLinked);
@@ -882,11 +901,33 @@ public class RecordServiceImpl extends BaseBehaviourBean
ruleService.disableRuleType("outbound");
try
{
// get the new record container for the file plan
NodeRef newRecordContainer = filePlanService.getUnfiledContainer(checkedFilePlan);
NodeRef newRecordContainer = destinationNodeRef;
// if optional location not specified, use the unfiledContainer
if (newRecordContainer == null)
{
throw new AlfrescoRuntimeException("Unable to create record, because new record container could not be found.");
// get the unfiled record container node for the file plan
newRecordContainer = filePlanService.getUnfiledContainer(checkedFilePlan);
if (newRecordContainer == null)
{
throw new AlfrescoRuntimeException("Unable to create record, because record container could not be found.");
}
}
// if optional location supplied, check that it is a valid record folder, unfiled record container or folder
else
{
final QName nodeType = nodeService.getType(newRecordContainer);
if(!(nodeType.equals(RecordsManagementModel.TYPE_RECORD_FOLDER) ||
nodeType.equals(RecordsManagementModel.TYPE_UNFILED_RECORD_FOLDER) ||
nodeType.equals(RecordsManagementModel.TYPE_UNFILED_RECORD_CONTAINER)))
{
throw new AlfrescoRuntimeException("Unable to create record, because container is not a valid type for new record.");
}
Boolean isClosed = (Boolean) nodeService.getProperty(newRecordContainer, PROP_IS_CLOSED);
if (isClosed != null && isClosed)
{
throw new AlfrescoRuntimeException("Unable to create record, because container is closed.");
}
}
// get the documents readers and writers

View File

@@ -31,6 +31,7 @@ import org.alfresco.module.org_alfresco_module_rm.action.dm.CreateRecordAction;
import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel;
import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.security.AccessStatus;
/**
@@ -52,6 +53,13 @@ public class CreateRecordActionTest extends BaseRMTestCase
return true;
}
/**
* Test create record action
*
* Given a collaboration site document
* When the create record action is executed for that document
* Then a record is created for it
*/
public void testCreateRecordAction()
{
doTestInTransaction(new Test<Void>()
@@ -75,8 +83,38 @@ public class CreateRecordActionTest extends BaseRMTestCase
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(dmDocument, RMPermissionModel.READ_RECORDS));
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(filePlan, RMPermissionModel.VIEW_RECORDS));
};
}
},
dmCollaborator);
}
public void testCreateRecordActionWithLocation()
{
doTestInTransaction(new Test<Void>()
{
public Void run()
{
assertFalse(recordService.isRecord(dmDocument1));
Action action = actionService.createAction(CreateRecordAction.NAME);
action.setParameterValue(CreateRecordAction.PARAM_HIDE_RECORD, false);
action.setParameterValue(CreateRecordAction.PARAM_FILE_PLAN, filePlan);
action.setParameterValue(CreateRecordAction.PARAM_PATH, "rmContainer/rmFolder");
actionService.executeAction(action, dmDocument1);
return null;
}
public void test(Void result) throws Exception
{
assertTrue(recordService.isRecord(dmDocument1));
assertTrue(recordService.isFiled(dmDocument1));
// is the record folder the primary parent of the filed record
NodeRef parent = nodeService.getPrimaryParent(dmDocument1).getParentRef();
assertEquals(rmFolder, parent);
}
},
ADMIN_USER);
}
}

View File

@@ -123,6 +123,7 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase
/** test data */
protected String NAME_DM_DOCUMENT = "collabDocument.txt";
protected String NAME_DM_DOCUMENT1 = "collabDocument1.txt";
/** admin user */
protected static final String ADMIN_USER = "admin";
@@ -273,7 +274,9 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase
protected SiteInfo collaborationSite;
protected NodeRef documentLibrary;
protected NodeRef dmFolder;
protected NodeRef dmFolder1;
protected NodeRef dmDocument;
protected NodeRef dmDocument1;
/** collaboration site users */
protected String dmConsumer;
@@ -779,6 +782,8 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase
// create a folder and documents
dmFolder = fileFolderService.create(documentLibrary, "collabFolder", ContentModel.TYPE_FOLDER).getNodeRef();
dmDocument = fileFolderService.create(dmFolder, NAME_DM_DOCUMENT, ContentModel.TYPE_CONTENT).getNodeRef();
dmFolder1 = fileFolderService.create(documentLibrary, "collabFolder1", ContentModel.TYPE_FOLDER).getNodeRef();
dmDocument1 = fileFolderService.create(dmFolder1, NAME_DM_DOCUMENT1, ContentModel.TYPE_CONTENT).getNodeRef();
dmConsumer = GUID.generate();
dmConsumerNodeRef = createPerson(dmConsumer);

View File

@@ -45,12 +45,15 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule;
import org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest;
import org.alfresco.repo.policy.Behaviour;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.apache.commons.collections.CollectionUtils;
@@ -69,6 +72,10 @@ public class RecordServiceImplUnitTest extends BaseUnitTest
{
private NodeRef nonStandardFilePlanComponent;
private NodeRef nonStandardFilePlan;
private NodeRef dmNodeRef;
private NodeRef unfiledRecordContainer;
private NodeRef unfiledRecordFolder;
private ChildAssociationRef parentAssoc;
private static QName TYPE_MY_FILE_PLAN = generateQName();
private static QName ASPECT_FOR_FILE_PLAN = generateQName();
@@ -84,6 +91,10 @@ public class RecordServiceImplUnitTest extends BaseUnitTest
nonStandardFilePlanComponent = generateNodeRef(TYPE_RECORD_CATEGORY);
nonStandardFilePlan = generateNodeRef(TYPE_MY_FILE_PLAN);
dmNodeRef = generateNodeRef(TYPE_CONTENT);
unfiledRecordContainer = generateNodeRef(TYPE_UNFILED_RECORD_CONTAINER);
unfiledRecordFolder = generateNodeRef(TYPE_UNFILED_RECORD_FOLDER);
parentAssoc = mock(ChildAssociationRef.class);
// set-up node service
when(mockedNodeService.getProperty(nonStandardFilePlanComponent, PROP_ROOT_NODEREF)).thenReturn(nonStandardFilePlan);
@@ -462,4 +473,103 @@ public class RecordServiceImplUnitTest extends BaseUnitTest
// verify
verify(values, never()).add(nodeRef);
}
/**
* Given a file that is not yet a record
* When I create the record without specifying a location
* Then the record is created in the unfiled record container
*/
@Test
public void createRecordIntoUnfiledRecordContainer()
{
mocksForRecordCreation();
// create the record
recordService.createRecord(filePlan, dmNodeRef);
// verify record was created in unfiled record container
verify(mockedNodeService, times(1)).moveNode(
dmNodeRef,
unfiledRecordContainer,
ContentModel.ASSOC_CONTAINS,
parentAssoc.getQName());
}
/**
* Given a file that is not yet a record
* When I create the record specifying the unfiled record container
* Then the record is created in the unfiled record container
*/
@Test
public void createRecordIntoSpecifiedUnfiledRecordContainer()
{
mocksForRecordCreation();
// create the record
recordService.createRecord(filePlan, dmNodeRef, unfiledRecordContainer);
// verify record was created in specified unfiled record container
verify(mockedNodeService, times(1)).moveNode(
dmNodeRef,
unfiledRecordContainer,
ContentModel.ASSOC_CONTAINS,
parentAssoc.getQName());
}
/**
* Given a file that is not yet a record
* When I create the record specifying a location
* Then the record is created in the specified record folder
*/
@Test
public void createRecordIntoSpecifiedRecordFolder()
{
mocksForRecordCreation();
// create the record
recordService.createRecord(filePlan, dmNodeRef, recordFolder);
// verify record was created in specified record folder
verify(mockedNodeService, times(1)).moveNode(
dmNodeRef,
recordFolder,
ContentModel.ASSOC_CONTAINS,
parentAssoc.getQName());
}
/**
* Given a file that is not yet a record
* When I create the record specifying an invalid location
* Then an exception is thrown
*/
@Test(expected=AlfrescoRuntimeException.class)
public void createRecordIntoInvalidRecordFolder()
{
mocksForRecordCreation();
NodeRef recordCategory = generateNodeRef(TYPE_RECORD_CATEGORY);
// create the record
recordService.createRecord(filePlan, dmNodeRef, recordCategory);
}
/* Helper method to set up the mocks for record creation */
private void mocksForRecordCreation()
{
when(mockedNodeService.getPrimaryParent(dmNodeRef))
.thenReturn(parentAssoc);
when(parentAssoc.getQName()).thenReturn(generateQName());
// mocks for sanity checks on node and fileplan
when(mockedExtendedPermissionService.hasPermission(dmNodeRef, PermissionService.WRITE)).thenReturn(AccessStatus.ALLOWED);
when(mockedDictionaryService.isSubClass(mockedNodeService.getType(dmNodeRef), ContentModel.TYPE_CONTENT)).thenReturn(true);
when(mockedFilePlanService.isFilePlan(nonStandardFilePlan)).thenReturn(true);
// mocks for policies
doNothing().when(recordService).invokeBeforeRecordDeclaration(dmNodeRef);
doNothing().when(recordService).invokeOnRecordDeclaration(dmNodeRef);
when(mockedFilePlanService.getUnfiledContainer(filePlan)).thenReturn(unfiledRecordContainer);
when(mockedVersionService.getVersionHistory(dmNodeRef)).thenReturn(null);
}
}

View File

@@ -72,10 +72,12 @@ import org.alfresco.service.cmr.repository.CopyService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.rule.RuleService;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.OwnableService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.version.VersionService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.QNamePattern;
@@ -122,6 +124,8 @@ public class BaseUnitTest implements RecordsManagementModel, ContentModel
@Mock(name="copyService") protected CopyService mockedCopyService;
@Mock(name="fileFolderService") protected FileFolderService mockedFileFolderService;
@Mock(name="modelSecurityService") protected ModelSecurityService mockedModelSecurityService;
@Mock(name="ruleService") protected RuleService mockedRuleService;
@Mock(name="versionService") protected VersionService mockedVersionService;
/** rm service mocks */
@Mock(name="filePlanService") protected FilePlanService mockedFilePlanService;