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 0ee64536e6..8aeaa34a0a 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
@@ -759,6 +759,7 @@
+
diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/CreateRecordAction.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/CreateRecordAction.java
index f8174a1059..e7d091eea5 100644
--- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/CreateRecordAction.java
+++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/dm/CreateRecordAction.java
@@ -211,8 +211,11 @@ public class CreateRecordAction extends AuditableActionExecuterAbstractBase
hideRecord = hideRecordValue.booleanValue();
}
- // create record from existing document
- recordService.createRecord(filePlan, actionedUponNodeRef, !hideRecord);
+ synchronized (this)
+ {
+ // create record from existing document
+ recordService.createRecord(filePlan, actionedUponNodeRef, !hideRecord);
+ }
}
}
diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveLinkFileToBaseAction.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveLinkFileToBaseAction.java
index b879c733a0..9b8a11a0a9 100644
--- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveLinkFileToBaseAction.java
+++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveLinkFileToBaseAction.java
@@ -11,16 +11,18 @@ import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
import org.alfresco.repo.action.ParameterDefinitionImpl;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
+import org.alfresco.repo.transaction.RetryingTransactionHelper;
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.model.FileFolderService;
import org.alfresco.service.cmr.model.FileNotFoundException;
-import org.alfresco.service.cmr.repository.ChildAssociationRef;
+import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException;
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.dao.ConcurrencyFailureException;
import org.springframework.util.StringUtils;
/**
@@ -33,6 +35,9 @@ public abstract class CopyMoveLinkFileToBaseAction extends RMActionExecuterAbstr
{
private static Log logger = LogFactory.getLog(CopyMoveLinkFileToBaseAction.class);
+ /** Retrying transaction helper */
+ private RetryingTransactionHelper retryingTransactionHelper;
+
/** action parameters */
public static final String PARAM_DESTINATION_RECORD_FOLDER = "destinationRecordFolder";
public static final String PARAM_PATH = "path";
@@ -89,6 +94,14 @@ public abstract class CopyMoveLinkFileToBaseAction extends RMActionExecuterAbstr
this.filePlanService = filePlanService;
}
+ /**
+ * @param retryingTransactionHelper retrying transaction helper
+ */
+ public void setRetryingTransactionHelper(RetryingTransactionHelper retryingTransactionHelper)
+ {
+ this.retryingTransactionHelper = retryingTransactionHelper;
+ }
+
/**
* @see org.alfresco.module.org_alfresco_module_rm.action.RMActionExecuterAbstractBase#addParameterDefinitions(java.util.List)
*/
@@ -103,7 +116,7 @@ public abstract class CopyMoveLinkFileToBaseAction extends RMActionExecuterAbstr
* @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef)
*/
@Override
- protected void executeImpl(final Action action, final NodeRef actionedUponNodeRef)
+ protected synchronized void executeImpl(final Action action, final NodeRef actionedUponNodeRef)
{
String actionName = action.getActionDefinitionName();
if (isOkToProceedWithAction(actionedUponNodeRef, actionName))
@@ -125,8 +138,25 @@ public abstract class CopyMoveLinkFileToBaseAction extends RMActionExecuterAbstr
NodeRef recordFolder = (NodeRef)action.getParameterValue(PARAM_DESTINATION_RECORD_FOLDER);
if (recordFolder == null)
{
- // get the reference to the record folder based on the relative path
- recordFolder = createOrResolvePath(action, actionedUponNodeRef, targetIsUnfiledRecords);
+ final boolean finaltargetIsUnfiledRecords = targetIsUnfiledRecords;
+ recordFolder = retryingTransactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public NodeRef execute() throws Throwable
+ {
+ NodeRef result = null;
+ try
+ {
+ // get the reference to the record folder based on the relative path
+ result = createOrResolvePath(action, actionedUponNodeRef, finaltargetIsUnfiledRecords);
+ }
+ catch (DuplicateChildNodeNameException ex)
+ {
+ throw new ConcurrencyFailureException("Cannot create or resolve path.", ex);
+ }
+
+ return result;
+ }
+ }, false, true);
}
// now we have the reference to the target folder we can do some final checks to see if the action is valid
@@ -140,18 +170,21 @@ public abstract class CopyMoveLinkFileToBaseAction extends RMActionExecuterAbstr
{
try
{
- if(getMode() == CopyMoveLinkFileToActionMode.MOVE)
- {
- fileFolderService.move(actionedUponNodeRef, finalRecordFolder, null);
- }
- else if(getMode() == CopyMoveLinkFileToActionMode.COPY)
- {
- fileFolderService.copy(actionedUponNodeRef, finalRecordFolder, null);
- }
- else if(getMode() == CopyMoveLinkFileToActionMode.LINK)
- {
- getRecordService().link(actionedUponNodeRef, finalRecordFolder);
- }
+ synchronized (this)
+ {
+ if(getMode() == CopyMoveLinkFileToActionMode.MOVE)
+ {
+ fileFolderService.move(actionedUponNodeRef, finalRecordFolder, null);
+ }
+ else if(getMode() == CopyMoveLinkFileToActionMode.COPY)
+ {
+ fileFolderService.copy(actionedUponNodeRef, finalRecordFolder, null);
+ }
+ else if(getMode() == CopyMoveLinkFileToActionMode.LINK)
+ {
+ getRecordService().link(actionedUponNodeRef, finalRecordFolder);
+ }
+ }
}
catch (FileNotFoundException fileNotFound)
{
@@ -333,19 +366,7 @@ public abstract class CopyMoveLinkFileToBaseAction extends RMActionExecuterAbstr
*/
private NodeRef getChild(NodeRef parent, String childName)
{
- NodeRef child = null;
- List children = getNodeService().getChildAssocs(parent);
- for (ChildAssociationRef childAssoc : children)
- {
- NodeRef childNodeRef = childAssoc.getChildRef();
- String existingChildName = (String)getNodeService().getProperty(childNodeRef, ContentModel.PROP_NAME);
- if(existingChildName.equals(childName))
- {
- child = childNodeRef;
- break;
- }
- }
- return child;
+ return getNodeService().getChildByName(parent, ContentModel.ASSOC_CONTAINS, childName);
}
/**
@@ -365,22 +386,31 @@ public abstract class CopyMoveLinkFileToBaseAction extends RMActionExecuterAbstr
@Override
public NodeRef doWork()
{
- NodeRef child = null;
- if(targetisUnfiledRecords)
+ // double check that the child hasn't been created by another thread
+ NodeRef child = getChild(parent, childName);
+ if (child == null)
{
- child = fileFolderService.create(parent, childName, RecordsManagementModel.TYPE_UNFILED_RECORD_FOLDER).getNodeRef();
- }
- else if(lastAsFolder)
- {
- child = getRecordFolderService().createRecordFolder(parent, childName);
- }
- else
- {
- if(RecordsManagementModel.TYPE_RECORD_FOLDER.equals(getNodeService().getType(parent)))
+ if(targetisUnfiledRecords)
{
- throw new AlfrescoRuntimeException("Unable to execute " + action.getActionDefinitionName() + " action, because the destination path could not be created.");
+ // create unfiled folder
+ child = fileFolderService.create(parent, childName, RecordsManagementModel.TYPE_UNFILED_RECORD_FOLDER).getNodeRef();
+ }
+ else if(lastAsFolder)
+ {
+ // create record folder
+ child = getRecordFolderService().createRecordFolder(parent, childName);
+ }
+ else
+ {
+ // ensure we are not trying to create a record categtory in a record folder
+ if(RecordsManagementModel.TYPE_RECORD_FOLDER.equals(getNodeService().getType(parent)))
+ {
+ throw new AlfrescoRuntimeException("Unable to execute " + action.getActionDefinitionName() + " action, because the destination path has a record category within a record folder.");
+ }
+
+ // create record category
+ child = filePlanService.createRecordCategory(parent, childName);
}
- child = filePlanService.createRecordCategory(parent, childName);
}
return child;
}
diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/job/RecordsManagementJob.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/job/RecordsManagementJob.java
index 4f20ee4986..77bfa96e95 100644
--- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/job/RecordsManagementJob.java
+++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/job/RecordsManagementJob.java
@@ -25,6 +25,8 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
@@ -39,6 +41,8 @@ import org.quartz.JobExecutionException;
*/
public class RecordsManagementJob implements Job
{
+ private static Log logger = LogFactory.getLog(RecordsManagementJob.class);
+
private static final long DEFAULT_TIME = 30000L;
private JobLockService jobLockService;
@@ -108,7 +112,18 @@ public class RecordsManagementJob implements Job
}
finally
{
- jobLockService.releaseLock(lockToken, getLockQName());
+ try
+ {
+ jobLockService.releaseLock(lockToken, getLockQName());
+ }
+ catch (LockAcquisitionException e)
+ {
+ // Ignore
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Lock release failed: " + getLockQName() + ": " + lockToken + "(" + e.getMessage() + ")");
+ }
+ }
}
}
diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/jscript/app/JSONConversionComponent.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/jscript/app/JSONConversionComponent.java
index 4a246536b0..0923b56ea3 100644
--- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/jscript/app/JSONConversionComponent.java
+++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/jscript/app/JSONConversionComponent.java
@@ -18,6 +18,10 @@
*/
package org.alfresco.module.org_alfresco_module_rm.jscript.app;
+import static org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel.READ_RECORDS;
+import static org.alfresco.repo.security.authentication.AuthenticationUtil.runAsSystem;
+import static org.alfresco.service.cmr.security.AccessStatus.ALLOWED;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -375,7 +379,7 @@ public class JSONConversionComponent extends org.alfresco.repo.jscript.app.JS
@SuppressWarnings("unchecked")
private JSONObject setRmNodeValues(final NodeRef nodeRef, final boolean useShortQName)
{
- JSONObject rmNodeValues = new JSONObject();
+ JSONObject rmNodeValues = new JSONObject();
// UI convenience type
rmNodeValues.put("uiType", getUIType(nodeRef));
@@ -391,6 +395,23 @@ public class JSONConversionComponent extends org.alfresco.repo.jscript.app.JS
rmNodeValues.put("primaryParentNodeRef", assoc.getParentRef().toString());
}
+ // File plan node reference
+ NodeRef filePlan = getFilePlan(nodeRef);
+ if (permissionService.hasPermission(filePlan, READ_RECORDS).equals(ALLOWED))
+ {
+ rmNodeValues.put("filePlan", filePlan.toString());
+
+ // Unfiled container node reference
+ NodeRef unfiledRecordContainer = filePlanService.getUnfiledContainer(filePlan);
+ if (unfiledRecordContainer != null)
+ {
+ rmNodeValues.put("unfiledRecordContainer", unfiledRecordContainer.toString());
+ rmNodeValues.put("properties", propertiesToJSON(unfiledRecordContainer, nodeService.getProperties(unfiledRecordContainer), useShortQName));
+ QName type = fileFolderService.getFileInfo(unfiledRecordContainer).getType();
+ rmNodeValues.put("type", useShortQName ? type.toPrefixString(namespaceService) : type.toString());
+ }
+ }
+
Map values = AuthenticationUtil.runAsSystem(new RunAsWork