Fix for CIFS/CheckInOut.exe save of working copy breaks lock on original file. ALF-2028.

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@19784 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Gary Spencer
2010-04-09 08:47:35 +00:00
parent 56f6c38b22
commit 1261142950
3 changed files with 337 additions and 142 deletions

View File

@@ -65,6 +65,7 @@
<property name="stateReaper"><ref bean="fileStateReaper"/></property>
<property name="nodeMonitorFactory"><ref bean="nodeMonitorFactory"/></property>
<property name="nodeArchiveService"><ref bean="nodeArchiveService" /></property>
<property name="lockService"><ref bean="lockService" /></property>
</bean>
<bean id="nodeMonitorFactory" class="org.alfresco.filesys.repo.NodeMonitorFactory">

View File

@@ -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<QName, Serializable> workingCopyProperties = new HashMap<QName, Serializable>(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<QName, Serializable> copiedFromProperties = new HashMap<QName, Serializable>(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");
}
}
}

View File

@@ -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
*