diff --git a/config/alfresco/network-protocol-context.xml b/config/alfresco/network-protocol-context.xml
index 2eedd142d5..42d4894c8d 100644
--- a/config/alfresco/network-protocol-context.xml
+++ b/config/alfresco/network-protocol-context.xml
@@ -65,6 +65,7 @@
+
diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java
index f33c57b753..1a790c0b72 100644
--- a/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java
+++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java
@@ -20,9 +20,12 @@ package org.alfresco.filesys.repo;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.Serializable;
import java.net.InetAddress;
import java.util.Date;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import javax.transaction.UserTransaction;
@@ -69,6 +72,8 @@ import org.alfresco.repo.admin.SysAdminParams;
import org.alfresco.repo.node.archive.NodeArchiveService;
import org.alfresco.repo.security.authentication.AuthenticationContext;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.service.cmr.lock.LockService;
+import org.alfresco.service.cmr.lock.LockType;
import org.alfresco.service.cmr.lock.NodeLockedException;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.repository.ContentIOException;
@@ -84,6 +89,7 @@ import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.PermissionService;
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.springframework.extensions.config.ConfigElement;
@@ -118,6 +124,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa
private PermissionService permissionService;
private FileFolderService fileFolderService;
private NodeArchiveService nodeArchiveService;
+ private LockService lockService;
private AuthenticationContext authContext;
private AuthenticationService authService;
@@ -236,6 +243,15 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa
return nodeArchiveService;
}
+ /**
+ * Return the lock service
+ *
+ * @return LockService
+ */
+ public final LockService getLockService() {
+ return lockService;
+ }
+
/**
* @param contentService the content service
*/
@@ -345,6 +361,15 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa
this.nodeArchiveService = nodeArchiveService;
}
+ /**
+ * Set the lock service
+ *
+ * @param lockService LockService
+ */
+ public void setLockService(LockService lockService) {
+ this.lockService = lockService;
+ }
+
/**
* Parse and validate the parameter string and create a device context object for this instance
* of the shared device. The same DeviceInterface implementation may be used for multiple
@@ -2327,7 +2352,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa
// DEBUG
- if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
+ if (logger.isDebugEnabled() && (ctx.hasDebug(AlfrescoContext.DBG_FILE) || ctx.hasDebug(AlfrescoContext.DBG_RENAME)))
logger.debug("Closed file: network file=" + file + " delete on close=" + file.hasDeleteOnClose());
}
@@ -2406,7 +2431,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa
// Debug
- if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
+ if (logger.isDebugEnabled() && (ctx.hasDebug(AlfrescoContext.DBG_FILE) || ctx.hasDebug(AlfrescoContext.DBG_RENAME)))
logger.debug("Deleted file: " + name + ", node=" + nodeRef);
}
catch (NodeLockedException ex)
@@ -2457,7 +2482,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa
{
// Create the transaction
- beginWriteTransaction( sess);
+ beginWriteTransaction( sess);
// Get the device context
@@ -2466,7 +2491,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
- logger.debug("Rename oldName=" + oldName + ", newName=" + newName);
+ logger.debug("Rename oldName=" + oldName + ", newName=" + newName);
try
{
@@ -2477,7 +2502,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa
// Check if the node is a link node
if ( nodeToMoveRef != null && nodeService.getProperty(nodeToMoveRef, ContentModel.PROP_LINK_DESTINATION) != null)
- throw new AccessDeniedException("Cannot rename link nodes");
+ throw new AccessDeniedException("Cannot rename link nodes");
// Get the new target folder - it must be a folder
@@ -2491,7 +2516,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa
boolean sameFolder = false;
if ( splitPaths[0].equalsIgnoreCase( oldPaths[0]))
- sameFolder = true;
+ sameFolder = true;
// Get the file state for the old file, if available
@@ -2502,9 +2527,9 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa
boolean isFolder = cifsHelper.isDirectory( nodeToMoveRef);
if ( isFolder == true || sameFolder == false) {
-
- // Update the old file state
-
+
+ // Update the old file state
+
if ( oldState != null)
{
// Update the file state index to use the new name
@@ -2515,51 +2540,51 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa
// Rename or move the file/folder
if ( sameFolder == true)
- cifsHelper.rename(nodeToMoveRef, name);
+ cifsHelper.rename(nodeToMoveRef, name);
else
- cifsHelper.move(nodeToMoveRef, targetFolderRef, name);
+ cifsHelper.move(nodeToMoveRef, targetFolderRef, name);
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
- logger.debug(" Renamed " + (isFolder ? "folder" : "file") + " using " + (sameFolder ? "rename" : "move"));
+ logger.debug(" Renamed " + (isFolder ? "folder" : "file") + " using " + (sameFolder ? "rename" : "move"));
}
else {
-
- // Rename a file within the same folder
- //
- // Check if the target file already exists
-
- int newExists = fileExists( sess, tree, newName);
- FileState newState = ctx.getStateTable().findFileState( newName, false, true);
-
- NodeRef targetNodeRef = null;
-
- boolean isFromVersionable = nodeService.hasAspect( nodeToMoveRef, ContentModel.ASPECT_VERSIONABLE);
-
- if ( newExists == FileStatus.FileExists) {
-
- // Use the existing file as the target node
-
- targetNodeRef = getNodeForPath( tree, newName);
- }
- else {
-
- // Check if the target has a renamed or delete-on-close state
-
- if ( newState.getFileStatus() == FileStateStatus.Renamed) {
-
+
+ // Rename a file within the same folder
+ //
+ // Check if the target file already exists
+
+ int newExists = fileExists( sess, tree, newName);
+ FileState newState = ctx.getStateTable().findFileState( newName, false, true);
+
+ NodeRef targetNodeRef = null;
+
+ boolean isFromVersionable = nodeService.hasAspect( nodeToMoveRef, ContentModel.ASPECT_VERSIONABLE);
+
+ if ( newExists == FileStatus.FileExists) {
+
+ // Use the existing file as the target node
+
+ targetNodeRef = getNodeForPath( tree, newName);
+ }
+ else {
+
+ // Check if the target has a renamed or delete-on-close state
+
+ if ( newState.getFileStatus() == FileStateStatus.Renamed) {
+
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
logger.debug(" Using renamed node, " + newState);
- // Use the renamed node as the target
+ // Use the renamed node to clone aspects/state
+
+ cloneNodeAspects( name, newState.getNodeRef(), nodeToMoveRef, ctx);
+ }
+ else if ( newState.getFileStatus() == FileStateStatus.DeleteOnClose) {
- targetNodeRef = newState.getNodeRef();
- }
- else if ( newState.getFileStatus() == FileStateStatus.DeleteOnClose) {
-
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
@@ -2572,114 +2597,145 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
- logger.debug(" Found archived node + " + archivedNode);
+ logger.debug(" Found archived node " + archivedNode);
if ( archivedNode != null && getNodeService().exists( archivedNode))
{
- // Restore the node
-
- targetNodeRef = getNodeService().restoreNode( archivedNode, null, null, null);
-
- // DEBUG
-
- if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
- logger.debug(" Restored node " + targetNodeRef);
- }
- }
-
- // Check if the node being renamed is versionable
-
- else if ( isFromVersionable == true) {
-
- // Create a new node for the target
-
- targetNodeRef = cifsHelper.createNode(ctx.getRootNode(), newName, true);
-
- // DEBUG
-
- if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
- logger.debug(" Created new node for " + newName);
-
- // Check if the new file name is a temporary file name
-
- String newNameNorm = newName.toLowerCase();
-
- if ( newNameNorm.endsWith(".tmp") || newNameNorm.endsWith(".temp")) {
-
- // Add the temporary aspect, also prevents versioning
-
- nodeService.addAspect(targetNodeRef, ContentModel.ASPECT_TEMPORARY, null);
-
+ // Restore the node
+
+ targetNodeRef = getNodeService().restoreNode( archivedNode, null, null, null);
+
// DEBUG
-
- if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
- logger.debug(" Added Temporary aspect to renamed file " + newName);
+
+ if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
+ logger.debug(" Restored node " + targetNodeRef);
+
+ // Check if the deleted file had a linked node, due to a rename
+
+ if ( newState.hasLinkNode() && nodeService.exists( newState.getLinkNode())) {
+
+ // Clone aspects from the linked node onto the restored node
+
+ cloneNodeAspects( name, newState.getLinkNode(), targetNodeRef, ctx);
+
+ // DEBUG
+
+ if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME)) {
+ logger.debug(" Moved aspects from linked node " + newState.getLinkNode());
+
+ // Check if the node is a working copy
+
+ if ( nodeService.hasAspect( targetNodeRef, ContentModel.ASPECT_WORKING_COPY)) {
+
+ // Check if the main document is still locked
+
+ NodeRef mainNodeRef = (NodeRef) nodeService.getProperty( targetNodeRef, ContentModel.PROP_COPY_REFERENCE);
+ if ( mainNodeRef != null) {
+ LockType lockTyp = lockService.getLockType( mainNodeRef);
+ logger.debug(" Main node ref lock type = " + lockTyp);
+ }
+ }
+ }
+ }
}
- }
- }
+ }
+
+ // Check if the node being renamed is versionable
+
+ else if ( isFromVersionable == true) {
+
+ // Create a new node for the target
+
+ targetNodeRef = cifsHelper.createNode(ctx.getRootNode(), newName, true);
+
+ // DEBUG
+
+ if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
+ logger.debug(" Created new node for " + newName);
- // If the original or target nodes are not versionable then just use a standard rename of the node
-
- if ( isFromVersionable == false &&
- ( targetNodeRef == null || nodeService.hasAspect( targetNodeRef, ContentModel.ASPECT_VERSIONABLE) == false)) {
+ // Copy aspects from the original file
+
+ cloneNodeAspects( name, nodeToMoveRef, targetNodeRef, ctx);
+ }
+ }
- // Rename the file/folder
-
- cifsHelper.rename(nodeToMoveRef, name);
-
- // Remove the file state for the old file name
-
- ctx.getStateTable().renameFileState(newName, oldState);
-
- // DEBUG
-
- if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
- logger.debug(" User standard rename for " + name + "(versionable=" + isFromVersionable + ", targetNodeRef=" + targetNodeRef + ")");
- }
- else {
-
- // Make sure we have a valid target node
-
- if ( targetNodeRef == null) {
-
- // DEBUG
-
- if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
- logger.debug(" No target node for rename");
-
- // Throw an error
-
- throw new AccessDeniedException( "No target node for file rename");
- }
-
- // Copy content data from the old file to the new file
-
- copyContentData( sess, tree, nodeToMoveRef, targetNodeRef);
-
- // Mark the new file as existing
-
- newState.setFileStatus( FileStatus.FileExists);
- newState.setNodeRef( targetNodeRef);
-
- // Delete the old file
-
- nodeService.deleteNode( nodeToMoveRef);
-
- // Make sure the old file state is cached for a short while, the file may not be open so the
- // file state could be expired
-
- oldState.setExpiryTime(System.currentTimeMillis() + FileState.DeleteTimeout);
-
- // Indicate that this is a deleted file state, set the node ref of the file that was renamed
-
- oldState.setFileStatus(FileStateStatus.DeleteOnClose);
- oldState.setNodeRef(nodeToMoveRef);
-
- // DEBUG
-
- if ( logger.isDebugEnabled() && ctx.hasDebug( AlfrescoContext.DBG_RENAME))
- logger.debug(" Cached delete state for " + oldName);
- }
+ // If the original or target nodes are not versionable then just use a standard rename of the node
+
+ if ( isFromVersionable == false &&
+ ( targetNodeRef == null || nodeService.hasAspect( targetNodeRef, ContentModel.ASPECT_VERSIONABLE) == false)) {
+
+ // Rename the file/folder
+
+ cifsHelper.rename(nodeToMoveRef, name);
+
+ // Mark the new file as existing
+
+ newState.setFileStatus( FileStatus.FileExists);
+ newState.setNodeRef( nodeToMoveRef);
+
+ // Make sure the old file state is cached for a short while, the file may not be open so the
+ // file state could be expired
+
+ oldState.setExpiryTime(System.currentTimeMillis() + FileState.DeleteTimeout);
+
+ // Indicate that this is a renamed file state, set the node ref of the file that was renamed
+
+ oldState.setFileStatus(FileStateStatus.Renamed);
+ oldState.setNodeRef(nodeToMoveRef);
+
+ // DEBUG
+
+ if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
+ logger.debug(" Use standard rename for " + name + "(versionable=" + isFromVersionable + ", targetNodeRef=" + targetNodeRef + ")");
+ }
+ else {
+
+ // Make sure we have a valid target node
+
+ if ( targetNodeRef == null) {
+
+ // DEBUG
+
+ if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
+ logger.debug(" No target node for rename");
+
+ // Throw an error
+
+ throw new AccessDeniedException( "No target node for file rename");
+ }
+
+ // Copy content data from the old file to the new file
+
+ copyContentData( sess, tree, nodeToMoveRef, targetNodeRef);
+
+ // Mark the new file as existing
+
+ newState.setFileStatus( FileStatus.FileExists);
+ newState.setNodeRef( targetNodeRef);
+
+ // Delete the old file
+
+ nodeService.deleteNode( nodeToMoveRef);
+
+ // Make sure the old file state is cached for a short while, the file may not be open so the
+ // file state could be expired
+
+ oldState.setExpiryTime(System.currentTimeMillis() + FileState.DeleteTimeout);
+
+ // Indicate that this is a deleted file state, set the node ref of the file that was renamed
+
+ oldState.setFileStatus(FileStateStatus.DeleteOnClose);
+ oldState.setNodeRef(nodeToMoveRef);
+
+ // Link to the new node, a new file may be renamed into place, we need to transfer aspect/locks
+
+ oldState.setLinkNode( targetNodeRef);
+
+ // DEBUG
+
+ if ( logger.isDebugEnabled() && ctx.hasDebug( AlfrescoContext.DBG_RENAME))
+ logger.debug(" Cached delete state for " + oldName);
+ }
}
}
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
@@ -3228,4 +3284,111 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa
throw new IOException("Failed to copy content");
}
}
+
+
+ /**
+ * Clone/move aspects/properties between nodes
+ *
+ * @param newName String
+ * @param fromNode NodeRef
+ * @param toNode NodeRef
+ * @param ctx ContentContext
+ */
+ private void cloneNodeAspects( String newName, NodeRef fromNode, NodeRef toNode, ContentContext ctx)
+ {
+ // We need to remove various aspects/properties from the original file, and move them to the new file
+ //
+ // Check for the lockable aspect
+
+ if ( nodeService.hasAspect( fromNode, ContentModel.ASPECT_LOCKABLE)) {
+
+ // Remove the lockable aspect from the old working copy, add it to the new file
+
+ nodeService.removeAspect( fromNode, ContentModel.ASPECT_LOCKABLE);
+ nodeService.addAspect( toNode, ContentModel.ASPECT_LOCKABLE, null);
+
+ // DEBUG
+
+ if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
+ logger.debug(" Moved aspect " + ContentModel.ASPECT_LOCKABLE + " to new document");
+ }
+
+ // Check for the working copy aspect
+
+ if ( nodeService.hasAspect( fromNode, ContentModel.ASPECT_WORKING_COPY)) {
+
+ // Add the working copy aspect to the new file
+
+ Map workingCopyProperties = new HashMap(1);
+ workingCopyProperties.put(ContentModel.PROP_WORKING_COPY_OWNER, nodeService.getProperty( fromNode, ContentModel.PROP_WORKING_COPY_OWNER));
+
+ nodeService.addAspect( toNode, ContentModel.ASPECT_WORKING_COPY, workingCopyProperties);
+
+ // Remove the working copy aspect from old working copy file
+
+ nodeService.removeAspect( fromNode, ContentModel.ASPECT_WORKING_COPY);
+
+ // DEBUG
+
+ if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
+ logger.debug(" Moved aspect " + ContentModel.ASPECT_WORKING_COPY + " to new document");
+ }
+
+ // Check for the copied from aspect
+
+ if ( nodeService.hasAspect( fromNode, ContentModel.ASPECT_COPIEDFROM)) {
+
+ // Add the copied from aspect to the new file
+
+ NodeRef copiedFromNode = (NodeRef) nodeService.getProperty( fromNode, ContentModel.PROP_COPY_REFERENCE);
+ Map copiedFromProperties = new HashMap(1);
+ copiedFromProperties.put(ContentModel.PROP_COPY_REFERENCE, copiedFromNode);
+
+ nodeService.addAspect( toNode, ContentModel.ASPECT_COPIEDFROM, copiedFromProperties);
+
+ // Remove the copied from aspect from old working copy file
+
+ nodeService.removeAspect( fromNode, ContentModel.ASPECT_COPIEDFROM);
+
+ // DEBUG
+
+ if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
+ logger.debug(" Moved aspect " + ContentModel.ASPECT_COPIEDFROM + " to new document");
+
+ // Check if the original node is locked
+
+ if ( lockService.getLockType( copiedFromNode) == null) {
+
+ // Add the lock back onto the original file
+
+ lockService.lock( copiedFromNode, LockType.READ_ONLY_LOCK);
+
+ // DEBUG
+
+ if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
+ logger.debug(" Re-locked copied from node " + copiedFromNode);
+ }
+ }
+
+ // Check if the new file name is a temporary file, remove any versionable aspect from it
+
+ String newNameNorm = newName.toLowerCase();
+
+ if ( newNameNorm.endsWith( ".tmp") || newNameNorm.endsWith( ".temp")) {
+
+ // Remove the versionable aspect
+
+ if ( nodeService.hasAspect( toNode, ContentModel.ASPECT_VERSIONABLE))
+ nodeService.removeAspect( toNode, ContentModel.ASPECT_VERSIONABLE);
+
+ // Add the temporary aspect, also prevents versioning
+
+ nodeService.addAspect( toNode, ContentModel.ASPECT_TEMPORARY, null);
+
+ // DEBUG
+
+ if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
+ logger.debug(" Removed versionable aspect from temp file");
+ }
+ }
}
diff --git a/source/java/org/alfresco/filesys/state/FileState.java b/source/java/org/alfresco/filesys/state/FileState.java
index cecfcab8e2..7901120240 100644
--- a/source/java/org/alfresco/filesys/state/FileState.java
+++ b/source/java/org/alfresco/filesys/state/FileState.java
@@ -103,6 +103,10 @@ public class FileState
private long m_modifyDate;
private long m_changeDate;
+ // Keep track of the node we are linked to, when deleted
+
+ private NodeRef m_linkNode;
+
/**
* Class constructor
*
@@ -639,6 +643,33 @@ public class FileState
m_modifyDate = modTime;
}
+ /**
+ * Check if the file is linked to another node
+ *
+ * @return boolean
+ */
+ public final boolean hasLinkNode() {
+ return m_linkNode != null ? true : false;
+ }
+
+ /**
+ * Return the node that the file is linked to
+ *
+ * @return NodeRef
+ */
+ public final NodeRef getLinkNode() {
+ return m_linkNode;
+ }
+
+ /**
+ * Set the node that this file is linked to
+ *
+ * @param node NodeRef
+ */
+ public final void setLinkNode( NodeRef node) {
+ m_linkNode = node;
+ }
+
/**
* Check if the file is readable for the specified section of the file and process id
*