diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java b/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java
index 1aa8f6b162..50b3fdcf5e 100644
--- a/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java
+++ b/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2011 Alfresco Software Limited.
+ * Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -43,8 +43,12 @@ import org.alfresco.service.cmr.model.FileExistsException;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.model.FileNotFoundException;
+import org.alfresco.service.cmr.repository.InvalidNodeRefException;
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.security.AccessStatus;
+import org.alfresco.util.FileFilterMode;
import org.alfresco.util.GUID;
import org.alfresco.util.Utf7;
import org.apache.commons.logging.Log;
@@ -309,12 +313,116 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab
Date internalDate)
throws FileExistsException, FileNotFoundException, IOException, MessagingException
{
- long uid = createMimeMessageInFolder(this.folderInfo, message, flags);
+ long uid;
+ NodeRef sourceNodeRef = extractNodeRef(message);
+ if (isMoveOperation(sourceNodeRef))
+ {
+ uid = moveNode(this.folderInfo, message, flags, sourceNodeRef);
+ }
+ else
+ {
+ uid = createMimeMessageInFolder(this.folderInfo, message, flags);
+ }
// Invalidate current folder status
this.folderStatus = null;
return uid;
}
+ /**
+ * Moves the node sourceNodeRef
extracted from the message id.
+ * A part of a complex move operation.
+ *
+ * @param folderInfo
+ * @param message
+ * @param flags
+ * @param sourceNodeRef
+ * @return UUID of the moved node
+ * @throws FileExistsException
+ * @throws FileNotFoundException
+ */
+ @SuppressWarnings("deprecation")
+ private long moveNode(FileInfo folderInfo, MimeMessage message, Flags flags, NodeRef sourceNodeRef)
+ throws FileExistsException, FileNotFoundException
+ {
+ FileFolderService fileFolderService = serviceRegistry.getFileFolderService();
+ FileFilterMode.setClient(FileFilterMode.Client.imap);
+ fileFolderService.setHidden(sourceNodeRef, false);
+ FileInfo messageFile = fileFolderService.move(sourceNodeRef, folderInfo.getNodeRef(), null);
+ final long newMessageUid = (Long) messageFile.getProperties().get(ContentModel.PROP_NODE_DBID);
+ imapService.setFlag(messageFile, Flag.RECENT, true);
+ imapService.setFlag(messageFile, Flag.DELETED, false);
+ return newMessageUid;
+ }
+
+ /**
+ * Extract a NodeRef
from the message id.
+ *
Typical message id is "<74bad8aa-75a5-4063-8e46-9d1b5737f43b@alfresco.org>"
+ *
See {@link AbstractMimeMessage#updateMessageID()}
+ *
+ * @param message
+ * @return null if nothing is found
+ */
+ private NodeRef extractNodeRef(MimeMessage message)
+ {
+ String uuid = null;
+ String messageId = null;
+ NodeRef result = null;
+ try
+ {
+ messageId = message.getMessageID();
+ }
+ catch (MessagingException me)
+ {
+ // we cannot use message id to extract nodeRef
+ }
+
+ if (messageId != null)
+ {
+ if (messageId.startsWith("<"))
+ {
+ messageId = messageId.substring(1);
+ }
+ if (messageId.indexOf("@") != -1)
+ {
+ uuid = messageId.substring(0, messageId.indexOf("@"));
+ }
+ else
+ {
+ uuid = messageId;
+ }
+ result = new NodeRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore", uuid);
+ }
+ return result;
+ }
+
+ /**
+ * Determine if it is a complex move operation, which consists of a create superseded by a delete.
+ *
+ * @param sourceNodeRef
+ * @return
+ */
+ @SuppressWarnings("deprecation")
+ private boolean isMoveOperation(NodeRef sourceNodeRef)
+ {
+ if (sourceNodeRef != null)
+ {
+ NodeService nodeService = serviceRegistry.getNodeService();
+ if (nodeService.exists(sourceNodeRef))
+ {
+ FileFolderService fileFolderService = serviceRegistry.getFileFolderService();
+ FileInfo node = fileFolderService.getFileInfo(sourceNodeRef);
+ if (node != null)
+ {
+ if (fileFolderService.isHidden(sourceNodeRef))
+ {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
/**
* Copies message with the given UID to the specified {@link MailFolder}.
*
diff --git a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java
index 6fab3fe7a8..92acffa143 100644
--- a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java
+++ b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java
@@ -34,6 +34,8 @@ import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
import java.util.TreeMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -158,6 +160,8 @@ public class ImapServiceImpl implements ImapService, OnRestoreNodePolicy, OnCrea
private final static Map qNameToFlag;
private final static Map flagToQname;
+ private static final Timer deleteDelayTimer = new Timer();
+
private boolean imapServerEnabled = false;
static
@@ -538,11 +542,71 @@ public class ImapServiceImpl implements ImapService, OnRestoreNodePolicy, OnCrea
Flags flags = getFlags(fileInfo);
if (flags.contains(Flags.Flag.DELETED))
{
- fileFolderService.delete(fileInfo.getNodeRef());
+ // See MNT-12259
+ //fileFolderService.delete(fileInfo.getNodeRef());
+ hideAndDelete(fileInfo.getNodeRef());
messageCache.remove(fileInfo.getNodeRef());
}
}
+ /**
+ * Workaround for MNT-12259
+ * @param nodeRef
+ */
+ @SuppressWarnings("deprecation")
+ private void hideAndDelete(final NodeRef nodeRef)
+ {
+ FileFilterMode.setClient(FileFilterMode.Client.imap);
+ fileFolderService.setHidden(nodeRef, true);
+ {
+ // Get the current user
+ final String deleteDelayUser = AuthenticationUtil.getFullyAuthenticatedUser();
+ // Add a timed task to really delete the file
+ TimerTask deleteDelayTask = new TimerTask()
+ {
+ @Override
+ public void run()
+ {
+ RunAsWork deleteDelayRunAs = new RunAsWork()
+ {
+ @Override
+ public Void doWork() throws Exception
+ {
+ // Ignore if it is NOT hidden: the shuffle may have finished; the operation may have failed
+ if (!nodeService.exists(nodeRef) || !fileFolderService.isHidden(nodeRef))
+ {
+ return null;
+ }
+
+ // Since this will run in a different thread, the client thread-local must be set
+ // or else unhiding the node will not unhide it for IMAP.
+ FileFilterMode.setClient(FileFilterMode.Client.imap);
+
+ // Unhide the node, e.g. for archiving
+ fileFolderService.setHidden(nodeRef, false);
+
+ // This is the transaction-aware service
+ fileFolderService.delete(nodeRef);
+ return null;
+ }
+ };
+ try
+ {
+ AuthenticationUtil.runAs(deleteDelayRunAs, deleteDelayUser);
+ }
+ catch (Throwable e)
+ {
+ // consume exception to avoid it leaking from the TimerTask and causing the Timer to
+ // no longer accept tasks to be scheduled.
+ logger.info("Exception thrown during IMAP delete timer task.", e);
+ }
+ }
+ };
+ // Schedule a real delete 5 seconds after the current time
+ deleteDelayTimer.schedule(deleteDelayTask, 5000L);
+ }
+ }
+
public AlfrescoImapFolder getOrCreateMailbox(AlfrescoImapUser user, String mailboxName, boolean mayExist, boolean mayCreate)
{
if (mailboxName == null)
diff --git a/source/test-java/org/alfresco/repo/imap/ImapServiceImplTest.java b/source/test-java/org/alfresco/repo/imap/ImapServiceImplTest.java
index c1aa4b0402..b4a3387b37 100644
--- a/source/test-java/org/alfresco/repo/imap/ImapServiceImplTest.java
+++ b/source/test-java/org/alfresco/repo/imap/ImapServiceImplTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2013 Alfresco Software Limited.
+ * Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -54,6 +54,7 @@ import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.model.FileNotFoundException;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
+import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
@@ -69,12 +70,15 @@ import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.test_category.OwnJVMTestsCategory;
import org.alfresco.util.ApplicationContextHelper;
+import org.alfresco.util.GUID;
import org.alfresco.util.PropertyMap;
import org.alfresco.util.config.RepositoryFolderConfigBean;
import org.junit.experimental.categories.Category;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.ClassPathResource;
+import com.icegreen.greenmail.store.SimpleStoredMessage;
+
/**
* Unit test for ImapServiceImpl
*/
@@ -107,6 +111,7 @@ public class ImapServiceImplTest extends TestCase
private AlfrescoImapUser user;
private ImapService imapService;
private UserTransaction txn;
+ private ContentService contentService;
private NodeRef testImapFolderNodeRef;
private Flags flags;
@@ -128,7 +133,7 @@ public class ImapServiceImplTest extends TestCase
searchService = serviceRegistry.getSearchService();
namespaceService = serviceRegistry.getNamespaceService();
fileFolderService = serviceRegistry.getFileFolderService();
-
+ contentService = serviceRegistry.getContentService();
flags = new Flags();
flags.add(Flags.Flag.SEEN);
@@ -869,6 +874,53 @@ public class ImapServiceImplTest extends TestCase
assertPathHierarchy(fullPathList, pathAfterRenaming);
}
+ /**
+ * Test for MNT-12259
+ * There is a 5s gap to run the test, see {@link ImapServiceImpl#hideAndDelete}
+ *
+ * @throws Exception
+ */
+ public void testMoveViaDeleteAndAppend() throws Exception
+ {
+ AlfrescoImapUser poweredUser = new AlfrescoImapUser((USER_NAME + "@alfresco.com"), USER_NAME, USER_PASSWORD);
+ String fileName = "testfile" + GUID.generate();
+ String destinationName = "testFolder" + GUID.generate();
+ String destinationPath = IMAP_ROOT + AlfrescoImapConst.HIERARCHY_DELIMITER + destinationName;
+ String nodeContent = "test content";
+ NodeRef root = findCompanyHomeNodeRef();
+ AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
+
+ // Create node and destination folder
+ FileInfo origFile = fileFolderService.create(root, fileName, ContentModel.TYPE_CONTENT);
+ ContentWriter contentWriter = contentService.getWriter(origFile.getNodeRef(), ContentModel.PROP_CONTENT, true);
+ contentWriter.setMimetype("text/plain");
+ contentWriter.setEncoding("UTF-8");
+ contentWriter.putContent(nodeContent);
+
+ FileInfo destinationNode = fileFolderService.create(root, destinationName, ContentModel.TYPE_FOLDER);
+ nodeService.addAspect(origFile.getNodeRef(), ImapModel.ASPECT_IMAP_CONTENT, null);
+
+ // Save the message and ensure the message id is set
+ SimpleStoredMessage origMessage = imapService.getMessage(origFile);
+ origMessage.getMimeMessage().saveChanges();
+
+ imapService.setFlag(origFile, Flags.Flag.DELETED, true);
+ // Delete the node
+ imapService.expungeMessage(origFile);
+
+ // Append the message to destination
+ AlfrescoImapFolder destinationMailbox = imapService.getOrCreateMailbox(poweredUser, destinationPath, true, false);
+ destinationMailbox.appendMessage(origMessage.getMimeMessage(), flags, null);
+
+ // Check the destination has the original file and only this file
+ FileInfo movedNode = fileFolderService.getFileInfo(origFile.getNodeRef());
+ assertNotNull("The file should exist.", movedNode);
+ assertEquals("The file name should not change.", fileName, movedNode.getName());
+ NodeRef newParentNodeRef = nodeService.getPrimaryParent(origFile.getNodeRef()).getParentRef();
+ assertEquals("The parent should change to destination.", destinationNode.getNodeRef(), newParentNodeRef);
+ assertEquals("There should be only one node in the destination folder", 1, nodeService.getChildAssocs(destinationNode.getNodeRef()).size());
+ }
+
/**
* @param mailbox - {@link AlfrescoImapFolder} instance which should be checked
*/