diff --git a/config/alfresco/cache-context.xml b/config/alfresco/cache-context.xml index 6f914a2fbc..c1bcea9a32 100644 --- a/config/alfresco/cache-context.xml +++ b/config/alfresco/cache-context.xml @@ -205,7 +205,7 @@ org.alfresco.cache.node.nodesTransactionalCache - + @@ -238,7 +238,7 @@ org.alfresco.cache.node.aspectsTransactionalCache - + @@ -271,7 +271,7 @@ org.alfresco.cache.node.propertiesTransactionalCache - + @@ -304,7 +304,7 @@ org.alfresco.cache.node.parentAssocsTransactionalCache - + @@ -586,7 +586,7 @@ org.alfresco.nodeOwnerTransactionalCache - + @@ -859,7 +859,7 @@ org.alfresco.aclTransactionalCache - + @@ -1243,5 +1243,37 @@ + + + + + + + + + + + + + + org.alfresco.cache.imapMessageCache + + + + + + + + + + + + + org.alfresco.cache.imapMessageTransactionalCache + + + + + diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml index 429564cbf4..9890c62d23 100644 --- a/config/alfresco/ehcache-default.xml +++ b/config/alfresco/ehcache-default.xml @@ -51,33 +51,29 @@ /> @@ -206,7 +202,7 @@ - + @@ -144,10 +144,10 @@ @@ -163,10 +163,10 @@ @@ -182,10 +182,8 @@ @@ -474,7 +472,7 @@ - + d:boolean - - + + + UIDVALIDITY d:long - + + + MAXUID + d:long + + + CHANGETOKEN + d:text + - + diff --git a/config/alfresco/subsystems/imap/default/imap-server-context.xml b/config/alfresco/subsystems/imap/default/imap-server-context.xml index e1e16ad96a..f6ff15241f 100644 --- a/config/alfresco/subsystems/imap/default/imap-server-context.xml +++ b/config/alfresco/subsystems/imap/default/imap-server-context.xml @@ -19,8 +19,8 @@ ${imap.server.enabled} - - + + @@ -84,49 +84,11 @@ - ${server.transaction.mode.readOnly} - ${server.transaction.mode.default} - ${server.transaction.mode.default} - ${server.transaction.mode.readOnly} ${server.transaction.mode.default} - - - - - - - - - org.alfresco.cache.imapFoldersCache - - - - - - - - - - - - - - org.alfresco.repo.cache.SimpleCache - - - - - - - - - - - @@ -134,8 +96,8 @@ - - + + @@ -148,9 +110,8 @@ - - - + + @@ -174,6 +135,9 @@ + + ${imap.server.folder.cache.size} + ${imap.server.enabled} @@ -197,16 +161,6 @@ - - - - - - - - - - diff --git a/config/alfresco/subsystems/imap/default/imap-server.properties b/config/alfresco/subsystems/imap/default/imap-server.properties index 36d5aa6a2a..73f046d6a5 100644 --- a/config/alfresco/subsystems/imap/default/imap-server.properties +++ b/config/alfresco/subsystems/imap/default/imap-server.properties @@ -1,6 +1,7 @@ imap.server.enabled=false imap.server.port=143 imap.server.host=0.0.0.0 +imap.server.folder.cache.size=10000 imap.mail.from.default=alfresco@demo.alfresco.org imap.mail.to.default=alfresco@demo.alfresco.org diff --git a/source/java/org/alfresco/model/ImapModel.java b/source/java/org/alfresco/model/ImapModel.java index dcf0144a42..63da60ed04 100644 --- a/source/java/org/alfresco/model/ImapModel.java +++ b/source/java/org/alfresco/model/ImapModel.java @@ -57,5 +57,7 @@ public interface ImapModel static final QName PROP_ATTACH_ID = QName.createQName(IMAP_MODEL_1_0_URI, "attachID"); static final QName PROP_UIDVALIDITY = QName.createQName(IMAP_MODEL_1_0_URI, "uidValidity"); + static final QName PROP_MAXUID = QName.createQName(IMAP_MODEL_1_0_URI, "maxUid"); + static final QName PROP_CHANGE_TOKEN = QName.createQName(IMAP_MODEL_1_0_URI, "changeToken"); } diff --git a/source/java/org/alfresco/repo/cache/CacheTest.java b/source/java/org/alfresco/repo/cache/CacheTest.java index b41ff5a2bf..621dd0e64e 100644 --- a/source/java/org/alfresco/repo/cache/CacheTest.java +++ b/source/java/org/alfresco/repo/cache/CacheTest.java @@ -240,11 +240,11 @@ public class CacheTest extends TestCase // check that backing cache was updated with the in-transaction changes assertFalse("Item was not removed from backing cache", backingCache.contains(NEW_GLOBAL_ONE)); assertNull("Item could still be fetched from backing cache", backingCache.get(NEW_GLOBAL_ONE)); - assertEquals("Item not updated in backing cache", null, backingCache.get(UPDATE_TXN_THREE)); + assertEquals("Item not updated in backing cache", "XXX", backingCache.get(UPDATE_TXN_THREE)); // Check that the transactional cache serves get requests - assertEquals("Transactional cache must serve post-commit get requests", - null, transactionalCache.get(UPDATE_TXN_THREE)); + assertEquals("Transactional cache must serve post-commit get requests", "XXX", + transactionalCache.get(UPDATE_TXN_THREE)); } catch (Throwable e) { diff --git a/source/java/org/alfresco/repo/cache/TransactionalCache.java b/source/java/org/alfresco/repo/cache/TransactionalCache.java index 2486df6dfb..50834f1c03 100644 --- a/source/java/org/alfresco/repo/cache/TransactionalCache.java +++ b/source/java/org/alfresco/repo/cache/TransactionalCache.java @@ -856,7 +856,11 @@ public class TransactionalCache else { // Mutable, read-write - if (sharedObj != null) + if (sharedObj == null) + { + sharedCache.put(key, value); + } + else { // Remove new value in the cache sharedCache.remove(key); diff --git a/source/java/org/alfresco/repo/content/metadata/RFC822MetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/RFC822MetadataExtracter.java index 22ea98ea24..d9a482cdb8 100644 --- a/source/java/org/alfresco/repo/content/metadata/RFC822MetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/RFC822MetadataExtracter.java @@ -134,7 +134,7 @@ public class RFC822MetadataExtracter extends AbstractMappingMetadataExtracter { String lastReceived = rx[0]; lastReceived = MimeUtility.unfold(lastReceived); - int x = lastReceived.indexOf(';'); + int x = lastReceived.lastIndexOf(';'); if(x > 0) { String dateStr = lastReceived.substring(x + 1).trim(); diff --git a/source/java/org/alfresco/repo/imap/AbstractImapFolder.java b/source/java/org/alfresco/repo/imap/AbstractImapFolder.java index 558489374b..a823e10d0c 100644 --- a/source/java/org/alfresco/repo/imap/AbstractImapFolder.java +++ b/source/java/org/alfresco/repo/imap/AbstractImapFolder.java @@ -161,37 +161,6 @@ public abstract class AbstractImapFolder implements MailFolder } - /** - * Returns the number of the first unseen message. - * - * @return Number of the first unseen message. - */ - public int getFirstUnseen() - { - return getFirstUnseenInternal(); - } - - - /** - * Returns full name of the folder with namespace and full path delimited with the hierarchy delimiter - * (see {@link AlfrescoImapConst#HIERARCHY_DELIMITER}) - *

E.g.:
- * #mail.admin."Repository_archive.Data Dictionary.Space Templates.Software Engineering Project"
- * This is required by GreenMail implementation. - */ - public String getFullName() - { - CommandCallback command = new CommandCallback() - { - public String command() throws Throwable - { - return getFullNameInternal(); - } - }; - return command.run(); - } - - /** * Returns message by its UID. * @@ -210,23 +179,6 @@ public abstract class AbstractImapFolder implements MailFolder return command.run(); } - /** - * Returns count of the messages in the folder. - * - * @return Count of the messages. - */ - public int getMessageCount() - { - CommandCallback command = new CommandCallback() - { - public Integer command() throws Throwable - { - return getMessageCountInternal(); - } - }; - return command.run(); - } - /** * Returns list of all messages in the folder. * @@ -262,59 +214,6 @@ public abstract class AbstractImapFolder implements MailFolder return command.run(); } - /** - * Returns message sequence number in the folder by its UID. - * - * @param uid - message UID. - * @return message sequence number. - * @throws FolderException if no message with given UID. - */ - public int getMsn(final long uid) throws FolderException - { - CommandCallback command = new CommandCallback() - { - public Integer command() throws Throwable - { - return getMsnInternal(uid); - } - }; - return command.runFeedback(true); - } - - /** - * Returns folder name. - * - * @return folder name. - */ - public String getName() - { - CommandCallback command = new CommandCallback() - { - public String command() throws Throwable - { - return getNameInternal(); - } - }; - return command.run(); - } - - /** - * Returns UIDs of all messages in the folder. - * - * @return UIDS of the messages. - */ - public long[] getMessageUids() - { - CommandCallback command = new CommandCallback() - { - public Object command() throws Throwable - { - return getMessageUidsInternal(); - } - }; - return (long[])command.run(); - } - /** * Returns the list of messages that have no {@link Flags.Flag#DELETED} flag set for current user. * @@ -333,110 +232,6 @@ public abstract class AbstractImapFolder implements MailFolder return result; } - /** - * Returns permanent flags. - * - * @return {@link Flags} object containing flags. - */ - public Flags getPermanentFlags() - { - CommandCallback command = new CommandCallback() - { - public Flags command() throws Throwable - { - return getPermanentFlagsInternal(); - } - }; - return command.run(true); - } - - /** - * Returns count of messages with {@link Flags.Flag#RECENT} flag. If {@code reset} parameter is {@code true} - - * removes {@link Flags.Flag#RECENT} flag from the message for current user. - * - * @param reset - if true the {@link Flags.Flag#RECENT} will be deleted for current user if exists. - * @return returns count of recent messages. - */ - public int getRecentCount(final boolean reset) - { - CommandCallback command = new CommandCallback() - { - public Integer command() throws Throwable - { - return getRecentCountInternal(reset); - } - }; - return command.run(); - } - - /** - * Returns UIDNEXT value of the folder. - * - * @return UIDNEXT value. - */ - public long getUidNext() - { - CommandCallback command = new CommandCallback() - { - public Long command() throws Throwable - { - return getUidNextInternal(); - } - }; - return command.run(false); - } - - /** - * Returns UIDVALIDITY value of the folder. - * - * @return UIDVALIDITY value. - */ - public long getUidValidity() - { - CommandCallback command = new CommandCallback() - { - public Long command() throws Throwable - { - return getUidValidityInternal(); - } - }; - return command.run(false); - } - - /** - * Returns count of the messages with {@link Flags.Flag#SEEN} in the folder for the current user. - * - * @return Count of the unseen messages for current user. - */ - public int getUnseenCount() - { - CommandCallback command = new CommandCallback() - { - public Integer command() throws Throwable - { - return getUnseenCountInternal(); - } - }; - return command.run(); - } - - /** - * Whether the folder is selectable. - * - * @return {@code boolean}. - */ - public boolean isSelectable() - { - CommandCallback command = new CommandCallback() - { - public Boolean command() throws Throwable - { - return isSelectableInternal(); - } - }; - return command.run(true); - } - /** * Replaces flags for the message with the given UID. If {@code addUid} is set to * {@code true} {@link FolderListener} objects defined for this folder will be notified. @@ -587,38 +382,14 @@ public abstract class AbstractImapFolder implements MailFolder protected abstract void expungeInternal() throws Exception; - protected abstract int getFirstUnseenInternal(); - - protected abstract String getFullNameInternal() throws Exception; - protected abstract SimpleStoredMessage getMessageInternal(long uid) throws Exception; - protected abstract int getMessageCountInternal(); - protected abstract List getMessagesInternal(); protected abstract List getMessagesInternal(MsgRangeFilter msgRangeFilter); - protected abstract int getMsnInternal(long uid) throws Exception; - - protected abstract String getNameInternal(); - - protected abstract long[] getMessageUidsInternal(); - protected abstract List getNonDeletedMessagesInternal(); - protected abstract Flags getPermanentFlagsInternal(); - - protected abstract int getRecentCountInternal(boolean reset); - - protected abstract long getUidNextInternal(); - - protected abstract long getUidValidityInternal(); - - protected abstract int getUnseenCountInternal(); - - protected abstract boolean isSelectableInternal(); - protected abstract void replaceFlagsInternal(Flags flags, long uid, FolderListener silentListener, boolean addUid) throws Exception; protected abstract void setFlagsInternal(Flags flags, boolean value, long uid, FolderListener silentListener, boolean addUid) throws Exception; diff --git a/source/java/org/alfresco/repo/imap/AbstractMimeMessage.java b/source/java/org/alfresco/repo/imap/AbstractMimeMessage.java index 144a1fef9c..0061043aa1 100644 --- a/source/java/org/alfresco/repo/imap/AbstractMimeMessage.java +++ b/source/java/org/alfresco/repo/imap/AbstractMimeMessage.java @@ -21,24 +21,18 @@ package org.alfresco.repo.imap; import static org.alfresco.repo.imap.AlfrescoImapConst.MIME_VERSION; import static org.alfresco.repo.imap.AlfrescoImapConst.X_ALF_NODEREF_ID; -import java.io.Serializable; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Properties; -import javax.mail.Address; import javax.mail.Flags; import javax.mail.MessagingException; import javax.mail.Session; -import javax.mail.internet.AddressException; -import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; -import org.springframework.extensions.surf.util.I18NUtil; import org.alfresco.model.ContentModel; import org.alfresco.repo.imap.ImapService.EmailBodyFormat; -import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.site.SiteModel; import org.alfresco.repo.template.TemplateNode; import org.alfresco.repo.transaction.RetryingTransactionHelper; @@ -47,7 +41,6 @@ import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -56,6 +49,8 @@ import org.apache.commons.logging.LogFactory; */ public abstract class AbstractMimeMessage extends MimeMessage { + protected static final String DEFAULT_SUFFIX = "@alfresco.org"; + protected static int MAX_RETRIES = 1; private Log logger = LogFactory.getLog(AbstractMimeMessage.class); @@ -238,6 +233,6 @@ public abstract class AbstractMimeMessage extends MimeMessage protected void updateMessageID() throws MessagingException { - setHeader("Message-ID", this.messageFileInfo.getNodeRef().getId()); + setHeader("Message-ID", "<" + this.messageFileInfo.getNodeRef().getId() + DEFAULT_SUFFIX + ">"); } } diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java b/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java index 5355be1caa..68c48c1f36 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-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -19,52 +19,38 @@ package org.alfresco.repo.imap; import java.io.IOException; -import java.io.OutputStream; import java.io.Serializable; -import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.TreeMap; +import java.util.NavigableMap; import javax.mail.Flags; import javax.mail.MessagingException; -import javax.mail.Multipart; -import javax.mail.Part; -import javax.mail.internet.ContentType; +import javax.mail.Flags.Flag; import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeUtility; import org.alfresco.model.ContentModel; import org.alfresco.model.ImapModel; import org.alfresco.repo.imap.AlfrescoImapConst.ImapViewMode; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.imap.ImapService.FolderStatus; import org.alfresco.service.ServiceRegistry; 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.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.namespace.QName; import org.alfresco.util.GUID; import org.alfresco.util.Utf7; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.util.FileCopyUtils; import com.icegreen.greenmail.foedus.util.MsgRangeFilter; -import com.icegreen.greenmail.imap.ImapConstants; import com.icegreen.greenmail.store.FolderException; import com.icegreen.greenmail.store.FolderListener; import com.icegreen.greenmail.store.MailFolder; @@ -75,101 +61,51 @@ import com.icegreen.greenmail.store.SimpleStoredMessage; * Implementation of greenmail MailFolder. It represents an Alfresco content folder and handles * appendMessage, copyMessage, expunge (delete), getMessages, getMessage and so requests. * - * The folder is identified by a qualifiedMailboxName and versioned with a version number called UIDVALIDITY. - * * @author Mike Shavnev + * @author David Ward */ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializable { - /** - * - */ private static final long serialVersionUID = -7223111284066976111L; - private final static long YEAR_2005 = 1101765600000L; - private static Log logger = LogFactory.getLog(AlfrescoImapFolder.class); /** * Reference to the {@link FileInfo} object representing the folder. */ - private FileInfo folderInfo; - - /** - * Reference to the root node of the store where folder is placed. - */ - private NodeRef rootNodeRef; - - /** - * Name of the mailbox (e.g. "admin" for admin user). - */ - private String qualifiedMailboxName; + private final FileInfo folderInfo; /** * Name of the folder. */ - private String folderName; + private final String folderName; + + private final String folderPath; + + private final String userName; /** * Defines view mode. */ - private ImapViewMode viewMode; - - /** - * Name of the mount point. - */ - private String mountPointName; + private final ImapViewMode viewMode; /** * Reference to the {@link ImapService} object. */ - private ImapService imapService; - + private final ImapService imapService; + /** * Defines whether the folder is selectable or not. */ - private boolean selectable; + private final boolean selectable; + private final boolean extractAttachmentsEnabled; + /** - * The UIDValidity + * Cached Folder status information, validated against a change token. */ - private long uidValidity = 0; + private FolderStatus folderStatus; - private boolean extractAttachmentsEnabled; - - private Map messages = new TreeMap(); - private Map msnCache = new HashMap(); - private Map messagesCache = Collections.synchronizedMap(new MaxSizeMap(10, MESSAGE_CACHE_SIZE)); - - /** - * Map that ejects the last recently used element(s) to keep the size to a - * specified maximum - * - * @param Key - * @param Value - */ - private class MaxSizeMap extends LinkedHashMap - { - private static final long serialVersionUID = 1L; - - private int maxSize; - - public MaxSizeMap(int initialSize, int maxSize) - { - super(initialSize, 0.75f, true); - this.maxSize = maxSize; - } - - @Override - protected boolean removeEldestEntry(Map.Entry eldest) - { - boolean remove = super.size() > this.maxSize; - return remove; - } - } - - private final static int MESSAGE_CACHE_SIZE = 40; - private static final Flags PERMANENT_FLAGS = new Flags(); static @@ -186,11 +122,15 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab return extractAttachmentsEnabled; } - /*package*/ AlfrescoImapFolder(String qualifiedMailboxName, ServiceRegistry serviceRegistry) + /** + * Protected constructor for the hierarchy delimiter + */ + AlfrescoImapFolder(String userName, ServiceRegistry serviceRegistry) { - this(qualifiedMailboxName, null, null, null, null, null, false, serviceRegistry); + this(null, userName, "", "", null, serviceRegistry, false, false); } - + + /** * Constructs {@link AlfrescoImapFolder} object. * @@ -202,16 +142,15 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab * @param mountPointName - name of the mount point. */ public AlfrescoImapFolder( - String qualifiedMailboxName, FileInfo folderInfo, + String userName, String folderName, + String folderPath, ImapViewMode viewMode, - NodeRef rootNodeRef, - String mountPointName, boolean extractAttachmentsEnabled, ServiceRegistry serviceRegistry) { - this(qualifiedMailboxName, folderInfo, folderName, viewMode, rootNodeRef, mountPointName, serviceRegistry, null, extractAttachmentsEnabled); + this(folderInfo, userName, folderName, folderPath, viewMode, serviceRegistry, null, extractAttachmentsEnabled); } /** @@ -227,32 +166,24 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab * @param selectable - defines whether the folder is selectable or not. */ public AlfrescoImapFolder( - String qualifiedMailboxName, FileInfo folderInfo, + String userName, String folderName, + String folderPath, ImapViewMode viewMode, - NodeRef rootNodeRef, - String mountPointName, ServiceRegistry serviceRegistry, Boolean selectable, boolean extractAttachmentsEnabled) { super(serviceRegistry); - this.qualifiedMailboxName = qualifiedMailboxName; this.folderInfo = folderInfo; - this.rootNodeRef = rootNodeRef; + this.userName = userName; this.folderName = folderName != null ? folderName : (folderInfo != null ? folderInfo.getName() : null); + this.folderPath = folderPath; this.viewMode = viewMode != null ? viewMode : ImapViewMode.ARCHIVE; - this.mountPointName = mountPointName; this.extractAttachmentsEnabled = extractAttachmentsEnabled; - - if (serviceRegistry != null) - { - this.imapService = serviceRegistry.getImapService(); - } + this.imapService = serviceRegistry.getImapService(); - this.uidValidity = generateUidValidity(); - // MailFolder object can be null if it is used to obtain hierarchy delimiter by LIST command: // Example: // C: 2 list "" "" @@ -266,24 +197,96 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab Boolean storedSelectable = !serviceRegistry.getNodeService().hasAspect(folderInfo.getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_NONSELECTABLE); if (storedSelectable == null) { - setSelectable(true); + this.selectable = true; } else { - setSelectable(storedSelectable); + this.selectable = storedSelectable; } } else { - setSelectable(selectable); + this.selectable = selectable; } } else { - setSelectable(true); + this.selectable = false; } } + + /* + * (non-Javadoc) + * @see com.icegreen.greenmail.store.MailFolder#getFullName() + */ + @Override + public String getFullName() + { + return Utf7.encode(AlfrescoImapConst.USER_NAMESPACE + AlfrescoImapConst.HIERARCHY_DELIMITER + this.userName + + AlfrescoImapConst.HIERARCHY_DELIMITER + getFolderPath(), Utf7.UTF7_MODIFIED); + } + /* (non-Javadoc) + * @see com.icegreen.greenmail.store.MailFolder#getName() + */ + @Override + public String getName() + { + return this.folderName; + } + + /* (non-Javadoc) + * @see com.icegreen.greenmail.store.MailFolder#isSelectable() + */ + @Override + public boolean isSelectable() + { + return this.selectable; + } + + /** + * Returns the contents of this folder. + * + * @return A sorted map of UIDs to FileInfo objects. + */ + private NavigableMap searchMails() + { + return getFolderStatus().search; + } + + /** + * Invalidates the current cached state + * + * @return true if this instance is still valid for reuse + */ + public boolean reset() + { + this.folderStatus = null; + return new CommandCallback() + { + public Boolean command() throws Throwable + { + return serviceRegistry.getNodeService().exists(folderInfo.getNodeRef()); + } + }.run(true); + } + + protected FolderStatus getFolderStatus() + { + if (this.folderStatus == null) + { + CommandCallback command = new CommandCallback() + { + public FolderStatus command() throws Throwable + { + return imapService.getFolderStatus(userName, folderInfo.getNodeRef(), viewMode); + } + }; + this.folderStatus = command.run(); + } + return this.folderStatus; + } + /** * Appends message to the folder. * @@ -298,15 +301,10 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab Date internalDate) throws FileExistsException, FileNotFoundException, IOException, MessagingException { - AbstractMimeMessage internalMessage = createMimeMessageInFolder(this.folderInfo, message); - long newMessageUid = (Long) internalMessage.getMessageInfo().getProperties().get(ContentModel.PROP_NODE_DBID); - SimpleStoredMessage storedMessage = new SimpleStoredMessage(internalMessage, new Date(), newMessageUid); - messages.put(newMessageUid, storedMessage); - - // Saving message sequence number to cache - msnCache.put(newMessageUid, messages.size()); - - return newMessageUid; + long uid = createMimeMessageInFolder(this.folderInfo, message, flags); + // Invalidate current folder status + this.folderStatus = null; + return uid; } /** @@ -328,21 +326,18 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab NodeRef destFolderNodeRef = toImapMailFolder.getFolderInfo().getNodeRef(); - SimpleStoredMessage message = messages.get(uid); - FileInfo sourceMessageFileInfo = ((AbstractMimeMessage) message.getMimeMessage()).getMessageInfo(); + FileInfo sourceMessageFileInfo = searchMails().get(uid); if (serviceRegistry.getNodeService().hasAspect(sourceMessageFileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_CONTENT)) { //Generate body of message MimeMessage newMessage = new ImapModelMessage(sourceMessageFileInfo, serviceRegistry, true); - toImapMailFolder.appendMessageInternal(newMessage, message.getFlags(), new Date()); + toImapMailFolder.appendMessageInternal(newMessage, imapService.getFlags(sourceMessageFileInfo), new Date()); } else { serviceRegistry.getFileFolderService().copy(sourceMessageFileInfo.getNodeRef(), destFolderNodeRef, null); } - removeMessageFromCache(uid); - } /** @@ -356,14 +351,11 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab throw new FolderException("Can't delete all - Permission denied"); } - for (SimpleStoredMessage mess : messages.values()) + for (Map.Entry entry : searchMails().entrySet()) { - AbstractMimeMessage message = (AbstractMimeMessage) mess.getMimeMessage(); - FileInfo fileInfo = message.getMessageInfo(); - imapService.setFlag(fileInfo, Flags.Flag.DELETED, true); + imapService.setFlag(entry.getValue(), Flags.Flag.DELETED, true); // comment out to physically remove content. // fileFolderService.delete(fileInfo.getNodeRef()); - removeMessageFromCache(mess.getUid()); } } @@ -378,92 +370,21 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab throw new FolderException("Can't expunge - Permission denied"); } - Collection listMess = messages.values(); - for (SimpleStoredMessage mess : listMess) + for (Map.Entry entry : searchMails().entrySet()) { - - Flags flags = getFlags(mess); - if (flags.contains(Flags.Flag.DELETED)) - { - NodeRef nodeRef = ((AbstractMimeMessage) mess.getMimeMessage()).getMessageInfo().getNodeRef(); - serviceRegistry.getFileFolderService().delete(nodeRef); - removeMessageFromCache(mess.getUid()); - } + imapService.expungeMessage(entry.getValue()); } } /** - * Returns the number of the first unseen message. + * Returns the MSN number of the first unseen message. * - * @return Number of the first unseen message. + * @return MSN number of the first unseen message. */ @Override - protected int getFirstUnseenInternal() + public int getFirstUnseen() { - return 0; - } - - /** - * Returns full name of the folder with namespace and full path delimited with the hierarchy delimiter - * (see {@link AlfrescoImapConst#HIERARCHY_DELIMITER})

- * E.g.:
- * #mail.admin."Repository_archive.Data Dictionary.Space Templates.Software Engineering Project"
- * This is required by GreenMail implementation. - * - * @throws FileNotFoundException - */ - @Override - protected String getFullNameInternal() throws FileNotFoundException - { - // If MailFolder object is used to obtain hierarchy delimiter by LIST command: - // Example: - // C: 2 list "" "" - // S: * LIST () "." "" - // S: 2 OK LIST completed. - - if (rootNodeRef == null) - { - return ""; - } - - if (logger.isDebugEnabled()) - { - logger.debug("[getFullNameInternal] " + this); - } - - StringBuilder fullName = new StringBuilder(); - List pathList; - pathList = serviceRegistry.getFileFolderService().getNamePath(rootNodeRef, folderInfo.getNodeRef()); - fullName.append(ImapConstants.USER_NAMESPACE).append(AlfrescoImapConst.HIERARCHY_DELIMITER).append(qualifiedMailboxName); - - boolean isFirst = true; - for (FileInfo path : pathList) - { - fullName.append(AlfrescoImapConst.HIERARCHY_DELIMITER); - if (isFirst) - { - fullName.append("\""); - isFirst = false; - if (mountPointName != null) - { - fullName.append(mountPointName); - } - else - { - fullName.append(path.getName()); - } - } - else - { - fullName.append(path.getName()); - } - } - fullName.append("\""); - if (logger.isDebugEnabled()) - { - logger.debug("fullName: " + fullName); - } - return Utf7.encode(fullName.toString(), Utf7.UTF7_MODIFIED); + return getFolderStatus().firstUnseen; } /** @@ -480,46 +401,12 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab { logger.debug("[getMessageInternal] " + this); } - SimpleStoredMessage storedMessage = messages.get(uid); - if (storedMessage == null) + FileInfo mesInfo = searchMails().get(uid); + if (mesInfo == null) { - messagesCache.remove(uid); - msnCache.remove(uid); return null; } - AbstractMimeMessage mes = (AbstractMimeMessage) storedMessage.getMimeMessage(); - FileInfo mesInfo = mes.getMessageInfo(); - - Date modified = (Date) serviceRegistry.getNodeService().getProperty(mesInfo.getNodeRef(), ContentModel.PROP_MODIFIED); - if(modified != null) - { - CacheItem cached = messagesCache.get(uid); - if (cached != null) - { - if (logger.isDebugEnabled()) - { - logger.debug("retrieved message from cache uid: " + uid); - } - if (cached.getModified().equals(modified)) - { - return cached.getMessage(); - } - } - SimpleStoredMessage message = createImapMessage(mesInfo, uid, true); - messagesCache.put(uid, new CacheItem(modified, message)); - - if (logger.isDebugEnabled()) - { - logger.debug("caching message uid: " + uid + " cacheSize: " + messagesCache.size()); - } - - return message; - } - else - { - SimpleStoredMessage message = createImapMessage(mesInfo, uid, true); - return message; - } + return imapService.getMessage(mesInfo); } /** @@ -528,23 +415,9 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab * @return Count of the messages. */ @Override - protected int getMessageCountInternal() + public int getMessageCount() { - if (logger.isDebugEnabled()) - { - logger.debug("[getMessageCountInternal] " + this); - } - - if (messages.size() == 0 && folderInfo != null) - { - List fileInfos = imapService.searchMails(folderInfo.getNodeRef(), viewMode); - convertToMessages(fileInfos); - } - if (logger.isDebugEnabled() && folderInfo != null) - { - logger.debug(folderInfo.getName() + " - Messages count:" + messages.size()); - } - return messages.size(); + return getFolderStatus().messageCount; } /** @@ -553,25 +426,19 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab * @return UIDS of the messages. */ @Override - protected long[] getMessageUidsInternal() + public long[] getMessageUids() { if (logger.isDebugEnabled()) { logger.debug("[getMessageUidsInternal] " + this); } - if (messages == null || messages.size() == 0 && folderInfo != null) - { - List fileInfos = imapService.searchMails(folderInfo.getNodeRef(), viewMode); - convertToMessages(fileInfos); - } - int len = messages.size(); - long[] uids = new long[len]; - Set keys = messages.keySet(); + Collection uidSet = searchMails().keySet(); + long[] uids = new long[uidSet.size()]; int i = 0; - for (Long key : keys) + for (Long uid : uidSet) { - uids[i++] = key; + uids[i++] = uid; } return uids; } @@ -588,11 +455,10 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab { logger.debug("[getMessagesInternal] " + this); } - List fileInfos = imapService.searchMails(folderInfo.getNodeRef(), viewMode); - return convertToMessages(fileInfos); + return convertToMessages(searchMails().values()); } - private List convertToMessages(List fileInfos) + private List convertToMessages(Collection fileInfos) { if (logger.isDebugEnabled()) { @@ -603,44 +469,23 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab logger.debug("[convertToMessages] - fileInfos is empty or null"); return Collections.emptyList(); } - if (fileInfos.size() != messages.size()) + List result = new LinkedList(); + for (FileInfo fileInfo : fileInfos) { - for (FileInfo fileInfo : fileInfos) + try { - try + result.add(imapService.createImapMessage(fileInfo, false)); + if (logger.isDebugEnabled()) { - Long key = getMessageUid(fileInfo); - SimpleStoredMessage message = createImapMessage(fileInfo, key, false); - messages.put(key, message); - - // Saving message sequence number to cache - msnCache.put(key, messages.size()); - - if (logger.isDebugEnabled()) - { - logger.debug("[convertToMessages] Message added: " + fileInfo.getName()); - } - } - catch (MessagingException e) - { - logger.warn("[convertToMessages] Invalid message! File name:" + fileInfo.getName(), e); + logger.debug("[convertToMessages] Message added: " + fileInfo.getName()); } } + catch (MessagingException e) + { + logger.warn("[convertToMessages] Invalid message! File name:" + fileInfo.getName(), e); + } } - return new LinkedList(messages.values()); - } - - protected SimpleStoredMessage createImapMessage(FileInfo fileInfo, Long key, boolean generateBody) throws MessagingException - { - // TODO MER 26/11/2010- this test should really be that the content of the node is of type message/RFC822 - if (serviceRegistry.getNodeService().hasAspect(fileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_CONTENT)) - { - return new SimpleStoredMessage(new ImapModelMessage(fileInfo, serviceRegistry, generateBody), new Date(), key); - } - else - { - return new SimpleStoredMessage(new ContentModelMessage(fileInfo, serviceRegistry, generateBody), new Date(), key); - } + return result; } /** @@ -652,25 +497,7 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab @Override protected List getMessagesInternal(MsgRangeFilter msgRangeFilter) { - if (logger.isDebugEnabled()) - { - logger.debug("[getMessagesInternal] " + this); - } - if (messages == null || messages.size() == 0) - { - List fileInfos = imapService.searchMails(folderInfo.getNodeRef(), viewMode); - convertToMessages(fileInfos); - } - List ret = new ArrayList(); - for (int i = 0; i < messages.size(); i++) - { - if (msgRangeFilter.includes(i + 1)) - { - ret.add(messages.get(i)); - } - } - - return ret; + throw new UnsupportedOperationException("IMAP implementation doesn't support POP3 requests"); } /** @@ -681,25 +508,14 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab * @throws FolderException if no message with given UID. */ @Override - protected int getMsnInternal(long uid) throws FolderException + public int getMsn(long uid) throws FolderException { - Integer msn = msnCache.get(uid); - if (msn != null) + NavigableMap messages = searchMails(); + if (!messages.containsKey(uid)) { - return msn; + throw new FolderException("No such message."); } - throw new FolderException("No such message."); - } - - /** - * Returns folder name. - * - * @return folder name. - */ - @Override - protected String getNameInternal() - { - return folderName; + return messages.headMap(uid, true).size(); } /** @@ -716,13 +532,7 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab } List result = new ArrayList(); - if (messages.size() == 0 && folderInfo != null) - { - List fileInfos = imapService.searchMails(folderInfo.getNodeRef(), viewMode); - convertToMessages(fileInfos); - } - - Collection values = messages.values(); + Collection values = getMessagesInternal(); for (SimpleStoredMessage message : values) { if (!getFlags(message).contains(Flags.Flag.DELETED)) @@ -744,7 +554,7 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab * @return {@link Flags} object containing flags. */ @Override - protected Flags getPermanentFlagsInternal() + public Flags getPermanentFlags() { return PERMANENT_FLAGS; } @@ -758,38 +568,29 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab * @return returns count of recent messages. */ @Override - protected int getRecentCountInternal(boolean reset) + public int getRecentCount(boolean reset) { - if (logger.isDebugEnabled()) + int recent = getFolderStatus().recentCount; + if (reset && recent > 0) { - logger.debug("[getRecentCountInternal] " + this); - } - if (messages.size() == 0 && folderInfo != null) - { - List fileInfos = imapService.searchMails(folderInfo.getNodeRef(), viewMode); - convertToMessages(fileInfos); - } - - int count = 0; - Collection values = messages.values(); - for (SimpleStoredMessage message : values) - { - if (getFlags(message).contains(Flags.Flag.RECENT)) + CommandCallback command = new CommandCallback() { - count++; - if (reset) + public Void command() throws Throwable { - imapService.setFlag(((AbstractMimeMessage) message.getMimeMessage()).getMessageInfo(), Flags.Flag.RECENT, false); + for (FileInfo fileInfo : folderStatus.search.values()) + { + Flags flags = imapService.getFlags(fileInfo); + if (flags.contains(Flags.Flag.RECENT)) + { + imapService.setFlag(fileInfo, Flags.Flag.RECENT, false); + } + } + return null; } - } - + }; + command.run(); } - - if (logger.isDebugEnabled() && folderInfo != null) - { - logger.debug(folderInfo.getName() + " - Recent count: " + count + " reset: " + reset); - } - return count; + return recent; } /** @@ -798,58 +599,21 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab * @return UIDNEXT value. */ @Override - protected long getUidNextInternal() + public long getUidNext() { - /** - * Can't used cached value since it may be out of date. - */ - return (Long) AuthenticationUtil.runAs(new GetUidValidityWork(folderInfo.getNodeRef(), serviceRegistry.getNodeService()), AuthenticationUtil.getSystemUserName()) + 1; + NavigableMap search = getFolderStatus().search; + return search.isEmpty() ? 1 : search.lastKey() + 1; } - public boolean isStale() - { - if(uidValidity == generateUidValidity()) - { - logger.debug("folder is not stale"); - return false; - } - else - { - logger.debug("folder is stale"); - return true; - } - } - /** * Returns UIDVALIDITY value of the folder. * * @return UIDVALIDITY value. */ @Override - protected long getUidValidityInternal() + public long getUidValidity() { - /** - * Can't used cached value uidValidity since it may be out of date. - */ - //return uidValidity; - return (Long) AuthenticationUtil.runAs(new GetUidValidityWork(folderInfo.getNodeRef(), serviceRegistry.getNodeService()), AuthenticationUtil.getSystemUserName()); - } - - /** - * Generates UIDVALIDITY value of the folder. - * - * @return UIDVALIDITY value. - */ - private long generateUidValidity() - { - if(folderInfo != null) - { - return (Long) AuthenticationUtil.runAs(new GenerateUidValidityWork(folderInfo.getNodeRef(), serviceRegistry.getNodeService()), AuthenticationUtil.getSystemUserName()); - } - else - { - return 0; - } + return getFolderStatus().uidValidity; } /** @@ -858,33 +622,9 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab * @return Count of the unseen messages for current user. */ @Override - protected int getUnseenCountInternal() + public int getUnseenCount() { - if (logger.isDebugEnabled()) - { - logger.debug("[getUnseenCountInternal] " + this); - } - if (messages.size() == 0 && folderInfo != null) - { - List fileInfos = imapService.searchMails(folderInfo.getNodeRef(), viewMode); - convertToMessages(fileInfos); - } - - int count = 0; - Collection values = messages.values(); - for (SimpleStoredMessage message : values) - { - if (!getFlags(message).contains(Flags.Flag.SEEN)) - { - count++; - } - - } - if (logger.isDebugEnabled() && folderInfo != null) - { - logger.debug(folderInfo.getName() + " - Unseen count: " + count); - } - return count; + return getFolderStatus().unseenCount; } /** @@ -908,15 +648,12 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab throws FolderException, MessagingException { int msn = getMsn(uid); - SimpleStoredMessage message = messages.get(uid); - FileInfo fileInfo = ((AbstractMimeMessage) message.getMimeMessage()).getMessageInfo(); + FileInfo fileInfo = searchMails().get(uid); imapService.setFlags(fileInfo, MessageFlags.ALL_FLAGS, false); imapService.setFlags(fileInfo, flags, true); - message = new SimpleStoredMessage(message.getMimeMessage(), message.getInternalDate(), uid); - messages.put(uid, message); - + Long uidNotification = addUid ? uid : null; - notifyFlagUpdate(msn, message.getFlags(), uidNotification, silentListener); + notifyFlagUpdate(msn, flags, uidNotification, silentListener); } /** @@ -942,36 +679,18 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab throws MessagingException, FolderException { int msn = getMsn(uid); - SimpleStoredMessage message = (SimpleStoredMessage) messages.get(uid); - - imapService.setFlags(((AbstractMimeMessage) message.getMimeMessage()).getMessageInfo(), flags, value); - message = new SimpleStoredMessage(message.getMimeMessage(), message.getInternalDate(), uid); - messages.put(uid, message); - + FileInfo fileInfo = searchMails().get(uid); + imapService.setFlags(fileInfo, flags, value); + Long uidNotification = null; if (addUid) { uidNotification = new Long(uid); } - notifyFlagUpdate(msn, message.getFlags(), uidNotification, silentListener); + notifyFlagUpdate(msn, flags, uidNotification, silentListener); } - /** - * @param fileInfo - {@link FileInfo} representing message. - * @return UID of the message. - */ - private long getMessageUid(FileInfo fileInfo) - { - if (serviceRegistry.getNodeService().getType(fileInfo.getNodeRef()).equals(ContentModel.TYPE_FOLDER)) - { - long modifDate = ((Date) serviceRegistry.getNodeService().getProperty(fileInfo.getNodeRef(), ContentModel.PROP_MODIFIED)).getTime(); - return (modifDate - YEAR_2005)/1000; - } - - return (Long) serviceRegistry.getNodeService().getProperty(fileInfo.getNodeRef(), ContentModel.PROP_NODE_DBID); - } - private Flags getFlags(SimpleStoredMessage mess) { return ((AbstractMimeMessage) mess.getMimeMessage()).getFlags(); @@ -979,54 +698,24 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab // ----------------------Getters and Setters---------------------------- + public String getFolderPath() + { + return this.folderPath; + } + public FileInfo getFolderInfo() { return folderInfo; } - - public void setFolderName(String folderName) - { - this.folderName = folderName; - } - - public void setViewMode(ImapViewMode viewMode) - { - this.viewMode = viewMode; - } - - public void setMountPointName(String mountPointName) - { - this.mountPointName = mountPointName; - } - - public void setMountParent(NodeRef mountParent) - { - this.rootNodeRef = mountParent; - } - - /** - * Whether the folder is selectable. - * - * @return {@code boolean}. + + /* (non-Javadoc) + * @see org.alfresco.repo.imap.AbstractImapFolder#isMarkedInternal() */ @Override - protected boolean isSelectableInternal() + public boolean isMarked() { - - return this.selectable; - } - - /** - * Sets {@link #selectable} property. - * - * @param selectable - {@code boolean}. - */ - public void setSelectable(boolean selectable) - { - this.selectable = selectable; - // Map properties = folderInfo.getProperties(); - // properties.put(ImapModel.PROP_IMAP_FOLDER_SELECTABLE, this.selectable); - // imapHelper.setProperties(folderInfo, properties); + FolderStatus folderStatus = getFolderStatus(); + return folderStatus.recentCount > 0 || folderStatus.unseenCount > 0; } /** @@ -1052,15 +741,16 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab * * @param folderFileInfo The folder to create message in. * @param message The original MimeMessage. - * @return Wrapped AbstractMimeMessage which was created. + * @return ID of the new message created * @throws FileNotFoundException * @throws FileExistsException * @throws MessagingException * @throws IOException */ - private AbstractMimeMessage createMimeMessageInFolder( + private long createMimeMessageInFolder( FileInfo folderFileInfo, - MimeMessage message) + MimeMessage message, + Flags flags) throws FileExistsException, FileNotFoundException, IOException, MessagingException { String name = AlfrescoImapConst.MESSAGE_PREFIX + GUID.generate(); @@ -1069,133 +759,16 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab final long newMessageUid = (Long) messageFile.getProperties().get(ContentModel.PROP_NODE_DBID); name = AlfrescoImapConst.MESSAGE_PREFIX + newMessageUid + AlfrescoImapConst.EML_EXTENSION; fileFolderService.rename(messageFile.getNodeRef(), name); + Flags newFlags = new Flags(flags); + newFlags.add(Flag.RECENT); + imapService.setFlags(messageFile, newFlags, true); if (extractAttachmentsEnabled) { imapService.extractAttachments(folderFileInfo.getNodeRef(), messageFile.getNodeRef(), message); } - return new IncomingImapMessage(messageFile, serviceRegistry, message); - } - - private void removeMessageFromCache(long uid) - { - messages.remove(uid); - msnCache.remove(uid); - messagesCache.remove(uid); - } - - class CacheItem - { - private Date modified; - private SimpleStoredMessage message; - - public CacheItem(Date modified, SimpleStoredMessage message) - { - this.setMessage(message); - this.setModified(modified); - } - - public void setModified(Date modified) - { - this.modified = modified; - } - - public Date getModified() - { - return modified; - } - - public void setMessage(SimpleStoredMessage message) - { - this.message = message; - } - - public SimpleStoredMessage getMessage() - { - return message; - } - } - - /** - * Generate UID validity - * - * In general this class will return a long UID value but if there is no - * ASPECT_IMAP_FOLDER then running this method will add the aspect and add - * initial values. Needs to be run in a read/write transaction - */ - public class GenerateUidValidityWork implements RunAsWork - { - - private NodeService nodeService; - private NodeRef folderNodeRef; - - public GenerateUidValidityWork(NodeRef folderNodeRef, NodeService nodeService) - { - this.folderNodeRef = folderNodeRef; - this.nodeService = nodeService; - } - - @Override - public Long doWork() throws Exception - { - if(nodeService.exists(folderNodeRef)) - { - long modifDate = 0L; - if (nodeService.hasAspect(folderNodeRef, ImapModel.ASPECT_IMAP_FOLDER)) - { - modifDate = ((Long) nodeService.getProperty(folderNodeRef, ImapModel.PROP_UIDVALIDITY)); - } - else - { - Date currDate = new Date(); - modifDate = currDate.getTime(); - Map aspectProperties = new HashMap(1, 1); - aspectProperties.put(ImapModel.PROP_UIDVALIDITY, modifDate); - nodeService.addAspect(folderNodeRef, ImapModel.ASPECT_IMAP_FOLDER, aspectProperties); - } - return (modifDate - YEAR_2005) / 1000; - } - else - { - return new Long(0); - } - } - - } - - /** - * Read only transaction to get uidvalidity - * @author mrogers - */ - public class GetUidValidityWork implements RunAsWork - { - - private NodeService nodeService; - private NodeRef folderNodeRef; - - public GetUidValidityWork(NodeRef folderNodeRef, NodeService nodeService) - { - this.folderNodeRef = folderNodeRef; - this.nodeService = nodeService; - } - - @Override - public Long doWork() throws Exception - { - if(nodeService.exists(folderNodeRef)) - { - long modifDate = 0L; - if (nodeService.hasAspect(folderNodeRef, ImapModel.ASPECT_IMAP_FOLDER)) - { - modifDate = ((Long) nodeService.getProperty(folderNodeRef, ImapModel.PROP_UIDVALIDITY)); - // we need tens part of the second at least, because - // we should avoid issues when several changes were completed within a second. - // so, divide by 100 instead of 1000. see ImapServiceImplCacheTest#testRepoBehaviourWithFoldersCache() - return (modifDate - YEAR_2005) / 100; - } - } - return new Long(0); - } - + // Force persistence of the message to the repository + new IncomingImapMessage(messageFile, serviceRegistry, message); + return newMessageUid; } } diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapHostManager.java b/source/java/org/alfresco/repo/imap/AlfrescoImapHostManager.java index 7f19ad4430..859ad7b4f4 100644 --- a/source/java/org/alfresco/repo/imap/AlfrescoImapHostManager.java +++ b/source/java/org/alfresco/repo/imap/AlfrescoImapHostManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -20,10 +20,13 @@ package org.alfresco.repo.imap; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.alfresco.repo.imap.exception.AlfrescoImapFolderException; -import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.Utf7; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -34,14 +37,27 @@ import com.icegreen.greenmail.store.MailFolder; import com.icegreen.greenmail.user.GreenMailUser; /** + * This Host Manager is assumed to be session local, i.e. there is one instance per IMAP TCP connection. This means that + * it can locally cache items being interacted with during the session and its knowledge of which folders / messages do + * or do not exist will match that of the client. + * * @author Mike Shavnev + * @author David Ward */ public class AlfrescoImapHostManager implements ImapHostManager { private ImapService imapService; - private TransactionService transactionService; + private Map folderCache; private static Log logger = LogFactory.getLog(AlfrescoImapHostManager.class); + + /** + * @param imapService + */ + public AlfrescoImapHostManager(ImapService imapService) + { + this.imapService = imapService; + } /** * Returns the hierarchy delimiter for mailboxes on this host. @@ -67,13 +83,9 @@ public class AlfrescoImapHostManager implements ImapHostManager { try { - return new ArrayList( - imapService.listMailboxes( - new AlfrescoImapUser( - user.getEmail(), - user.getLogin(), - user.getPassword()), - mailboxPattern)); + AlfrescoImapUser alfrescoUser = new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()); + return registerMailboxes(imapService.listMailboxes(alfrescoUser, getUnqualifiedMailboxPattern( + alfrescoUser, mailboxPattern), false)); } catch (Throwable e) { @@ -93,18 +105,15 @@ public class AlfrescoImapHostManager implements ImapHostManager * @return Collection of mailboxes matching the pattern. * @throws com.icegreen.greenmail.store.FolderException */ - public Collection listSubscribedMailboxes(GreenMailUser user, String mailboxPattern) throws FolderException + public Collection listSubscribedMailboxes(GreenMailUser user, String mailboxPattern) + throws FolderException { try - { - return new ArrayList( - imapService.listSubscribedMailboxes( - new AlfrescoImapUser( - user.getEmail(), - user.getLogin(), - user.getPassword()), - mailboxPattern)); - } + { + AlfrescoImapUser alfrescoUser = new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()); + return registerMailboxes(imapService.listMailboxes(alfrescoUser, getUnqualifiedMailboxPattern( + alfrescoUser, mailboxPattern), true)); + } catch (Throwable e) { logger.debug(e.getMessage(), e); @@ -129,12 +138,21 @@ public class AlfrescoImapHostManager implements ImapHostManager */ public void renameMailbox(GreenMailUser user, String oldMailboxName, String newMailboxName) throws FolderException, AuthorizationException { - try - { - imapService.renameMailbox(new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()), oldMailboxName, newMailboxName); - } + try + { + AlfrescoImapUser alfrescoUser = new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()); + String oldFolderPath = getUnqualifiedMailboxPattern(alfrescoUser, + oldMailboxName); + String newFolderpath = getUnqualifiedMailboxPattern(alfrescoUser, newMailboxName); + imapService.renameMailbox(alfrescoUser, oldFolderPath, newFolderpath); + if (folderCache != null) + { + folderCache.remove(oldFolderPath); + folderCache.remove(newFolderpath); + } + } catch (Throwable e) - { + { logger.debug(e.getMessage(), e); throw new FolderException(e.getMessage()); } @@ -154,11 +172,13 @@ public class AlfrescoImapHostManager implements ImapHostManager public MailFolder createMailbox(GreenMailUser user, String mailboxName) throws AuthorizationException, FolderException { try - { - return imapService.createMailbox(new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()), mailboxName); - } + { + AlfrescoImapUser alfrescoUser = new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()); + return registerMailBox(imapService.getOrCreateMailbox(alfrescoUser, getUnqualifiedMailboxPattern( + alfrescoUser, mailboxName), false, true)); + } catch (Throwable e) - { + { logger.debug(e.getMessage(), e); throw new FolderException(e.getMessage()); } @@ -175,11 +195,17 @@ public class AlfrescoImapHostManager implements ImapHostManager public void deleteMailbox(GreenMailUser user, String mailboxName) throws FolderException, AuthorizationException { try - { - imapService.deleteMailbox(new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()), mailboxName); - } - catch (Throwable e) + { + AlfrescoImapUser alfrescoUser = new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()); + String folderPath = getUnqualifiedMailboxPattern(alfrescoUser, mailboxName); + imapService.deleteMailbox(alfrescoUser, folderPath); + if (folderCache != null) { + folderCache.remove(folderPath); + } + } + catch (Throwable e) + { logger.debug(e.getMessage(), e); throw new FolderException(e.getMessage()); } @@ -199,23 +225,32 @@ public class AlfrescoImapHostManager implements ImapHostManager */ public MailFolder getFolder(GreenMailUser user, String mailboxName) { - return imapService.getFolder(new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()), mailboxName); - } + AlfrescoImapUser alfrescoUser = new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()); + String folderPath = getUnqualifiedMailboxPattern( + alfrescoUser, mailboxName); + // Warm up the cache if necessary + if (folderCache == null) + { + registerMailboxes(imapService.listMailboxes(alfrescoUser, "*", true)); + } + // Try to retrieve from the cache + AlfrescoImapFolder result = folderCache.get(folderPath); + if (result != null && result.reset()) + { + return result; + } + + // Look up and cache as a last resort + return registerMailBox(imapService.getOrCreateMailbox(alfrescoUser, folderPath, true, false)); + } /** * Simply calls {@link #getFolder(GreenMailUser, String)}.

Added to implement {@link ImapHostManager}. */ - public MailFolder getFolder(final GreenMailUser user, final String mailboxName, boolean mustExist) throws FolderException - { - try - { - return getFolder(new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()), mailboxName); - } - catch (Throwable e) - { - logger.debug(e.getMessage(), e); - throw new FolderException(e.getMessage()); - } + public MailFolder getFolder(final GreenMailUser user, final String mailboxName, boolean mustExist) + throws FolderException + { + return getFolder(user, mailboxName); } /** @@ -226,15 +261,7 @@ public class AlfrescoImapHostManager implements ImapHostManager */ public MailFolder getInbox(GreenMailUser user) throws FolderException { - try - { - return getFolder(new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()), AlfrescoImapConst.INBOX_NAME); - } - catch (Throwable e) - { - logger.debug(e.getMessage(), e); - throw new FolderException(e.getMessage()); - } + return getFolder(new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()), AlfrescoImapConst.INBOX_NAME); } /** @@ -257,7 +284,8 @@ public class AlfrescoImapHostManager implements ImapHostManager { try { - imapService.subscribe(new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()), mailbox); + AlfrescoImapUser alfrescoUser = new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()); + imapService.subscribe(alfrescoUser, getUnqualifiedMailboxPattern(alfrescoUser, mailbox)); } catch (Throwable e) { @@ -276,7 +304,8 @@ public class AlfrescoImapHostManager implements ImapHostManager { try { - imapService.unsubscribe(new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()), mailbox); + AlfrescoImapUser alfrescoUser = new AlfrescoImapUser(user.getEmail(), user.getLogin(), user.getPassword()); + imapService.unsubscribe(alfrescoUser, getUnqualifiedMailboxPattern(alfrescoUser, mailbox)); } catch (Throwable e) { @@ -291,28 +320,70 @@ public class AlfrescoImapHostManager implements ImapHostManager public List getAllMessages() { throw new UnsupportedOperationException(); + } + + private String getUnqualifiedMailboxPattern(AlfrescoImapUser user, String mailboxPattern) + { + mailboxPattern = Utf7.decode(mailboxPattern, Utf7.UTF7_MODIFIED); + if (mailboxPattern.startsWith(AlfrescoImapConst.NAMESPACE_PREFIX)) + { + int sepIndex = mailboxPattern.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER); + return sepIndex == -1 ? mailboxPattern.endsWith("*") ? "*" : "" : mailboxPattern.substring(sepIndex + 1); + } + return mailboxPattern; + } + + + /** + * Caches returned mail folder collection and converts to a generic MailFolder collection. + * + * @param mailboxes + * a Collection of Alfresco mailboxes + * @return a generic MailFolder collection associated with the session + */ + private Collection registerMailboxes(Collection mailboxes) + { + int size = mailboxes.size(); + if (size == 0) + { + return Collections.emptyList(); + } + Collection result = new ArrayList(size); + if (folderCache == null) + { + folderCache = new HashMap(size * 2); + } + for (AlfrescoImapFolder mailbox : mailboxes) + { + result.add(registerMailBox(mailbox)); + } + return result; } - // ----------------------Getters and Setters---------------------------- - - public ImapService getImapService() + /** + * Caches a returned mail folder and converts to a generic MailFolder. + * + * @param mailbox + * an Alfresco mailbox + * @return a generic MailFolder associated with the session + */ + private MailFolder registerMailBox(AlfrescoImapFolder mailbox) { - return imapService; + String folderPath = mailbox.getFolderPath(); + if ((mailbox.isSelectable() || folderPath.isEmpty()) && folderCache != null) + { + AlfrescoImapFolder oldFolder = folderCache.get(folderPath); + if (oldFolder != null + && oldFolder.getFolderInfo().getNodeRef().equals(mailbox.getFolderInfo().getNodeRef()) + && oldFolder.reset()) + { + mailbox = oldFolder; + } + else + { + folderCache.put(folderPath, mailbox); + } + } + return mailbox; } - - public void setImapService(ImapService imapService) - { - this.imapService = imapService; - } - - public TransactionService getTransactionService() - { - return transactionService; - } - - public void setTransactionService(TransactionService transactionService) - { - this.transactionService = transactionService; - } - } diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapServer.java b/source/java/org/alfresco/repo/imap/AlfrescoImapServer.java index 193e486d6b..b2bea3a49e 100644 --- a/source/java/org/alfresco/repo/imap/AlfrescoImapServer.java +++ b/source/java/org/alfresco/repo/imap/AlfrescoImapServer.java @@ -18,14 +18,17 @@ */ package org.alfresco.repo.imap; -import org.springframework.extensions.surf.util.AbstractLifecycleBean; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; import com.icegreen.greenmail.Managers; import com.icegreen.greenmail.imap.ImapHostManager; import com.icegreen.greenmail.imap.ImapServer; +import com.icegreen.greenmail.store.FolderException; import com.icegreen.greenmail.user.UserManager; import com.icegreen.greenmail.util.ServerSetup; @@ -41,9 +44,9 @@ public class AlfrescoImapServer extends AbstractLifecycleBean private int port = 143; private String host = "0.0.0.0"; - private ImapHostManager imapHostManager; private UserManager imapUserManager; + private ImapService imapService; private boolean imapServerEnabled; @@ -78,9 +81,9 @@ public class AlfrescoImapServer extends AbstractLifecycleBean return host; } - public void setImapHostManager(ImapHostManager imapHostManager) + public void setImapService(ImapService imapService) { - this.imapHostManager = imapHostManager; + this.imapService = imapService; } public void setImapUserManager(UserManager imapUserManager) @@ -113,11 +116,12 @@ public class AlfrescoImapServer extends AbstractLifecycleBean { if(serverImpl == null) { - Managers imapManagers = new Managers() + final Managers imapManagers = new Managers() { + // We create a new Host Manager instance per session to allow for session state tracking public ImapHostManager getImapHostManager() { - return imapHostManager; + return new AlfrescoImapHostManager(AlfrescoImapServer.this.imapService); } public UserManager getUserManager() @@ -128,6 +132,7 @@ public class AlfrescoImapServer extends AbstractLifecycleBean serverImpl = new ImapServer(new ServerSetup(port, host, ServerSetup.PROTOCOL_IMAP), imapManagers); serverImpl.startService(null); + if (logger.isInfoEnabled()) { logger.info("IMAP service started on host:port " + host + ":" + this.port + "."); diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapUser.java b/source/java/org/alfresco/repo/imap/AlfrescoImapUser.java index 4d27467be2..3cf7e0eb2d 100644 --- a/source/java/org/alfresco/repo/imap/AlfrescoImapUser.java +++ b/source/java/org/alfresco/repo/imap/AlfrescoImapUser.java @@ -39,7 +39,7 @@ public class AlfrescoImapUser implements GreenMailUser { this.email = email; this.userName = login; - this.password = password.toCharArray(); + this.password = password == null ? null : password.toCharArray(); } public void authenticate(String password) throws UserException diff --git a/source/java/org/alfresco/repo/imap/ContentModelMessage.java b/source/java/org/alfresco/repo/imap/ContentModelMessage.java index b23564a0d3..1247670a0c 100644 --- a/source/java/org/alfresco/repo/imap/ContentModelMessage.java +++ b/source/java/org/alfresco/repo/imap/ContentModelMessage.java @@ -35,20 +35,18 @@ import javax.mail.internet.MimeUtility; import org.alfresco.model.ContentModel; import org.alfresco.repo.imap.ImapService.EmailBodyFormat; -import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.extensions.surf.util.I18NUtil; public class ContentModelMessage extends AbstractMimeMessage { private Log logger = LogFactory.getLog(ContentModelMessage.class); - protected static final String DEFAULT_EMAIL_FROM = "alfresco@alfresco.org"; - protected static final String DEFAULT_EMAIL_TO = "alfresco@alfresco.org"; + protected static final String DEFAULT_EMAIL_FROM = "alfresco" + DEFAULT_SUFFIX; + protected static final String DEFAULT_EMAIL_TO = DEFAULT_EMAIL_FROM; public ContentModelMessage(FileInfo fileInfo, ServiceRegistry serviceRegistry, boolean generateBody) throws MessagingException { diff --git a/source/java/org/alfresco/repo/imap/ImapModelMessage.java b/source/java/org/alfresco/repo/imap/ImapModelMessage.java index 73484f6a0d..64f175658b 100644 --- a/source/java/org/alfresco/repo/imap/ImapModelMessage.java +++ b/source/java/org/alfresco/repo/imap/ImapModelMessage.java @@ -21,7 +21,9 @@ package org.alfresco.repo.imap; import java.io.IOException; import java.io.InputStream; +import javax.activation.DataHandler; import javax.mail.MessagingException; +import javax.mail.internet.MimePartDataSource; import javax.mail.util.SharedByteArrayInputStream; import org.alfresco.model.ContentModel; @@ -80,22 +82,31 @@ public class ImapModelMessage extends AbstractMimeMessage { ContentService contentService = serviceRegistry.getContentService(); ContentReader reader = contentService.getReader(messageFileInfo.getNodeRef(), ContentModel.PROP_CONTENT); + InputStream is = null; try { - InputStream is = reader.getContentInputStream(); + is = reader.getContentInputStream(); this.parse(is); - is.close(); - is = null; } catch (ContentIOException e) { //logger.error(e); throw new MessagingException("The error occured during message creation from content stream.", e); } - catch (IOException e) + finally { - //logger.error(e); - throw new MessagingException("The error occured during message creation from content stream.", e); + if (is != null) + { + try + { + is.close(); + } + catch (IOException e) + { + throw new MessagingException("The error occured during message creation from content stream.", e); + } + is = null; + } } } @@ -122,7 +133,7 @@ public class ImapModelMessage extends AbstractMimeMessage throw new MessagingException(e.getMessage(),e); } } - + /* protected void parse(InputStream inputstream) throws MessagingException { @@ -130,5 +141,4 @@ public class ImapModelMessage extends AbstractMimeMessage contentStream = inputstream; } */ - } diff --git a/source/java/org/alfresco/repo/imap/ImapService.java b/source/java/org/alfresco/repo/imap/ImapService.java index 49c5d84b91..4a666bc795 100644 --- a/source/java/org/alfresco/repo/imap/ImapService.java +++ b/source/java/org/alfresco/repo/imap/ImapService.java @@ -20,6 +20,7 @@ package org.alfresco.repo.imap; import java.io.IOException; import java.util.List; +import java.util.NavigableMap; import javax.mail.Flags; import javax.mail.MessagingException; @@ -30,6 +31,8 @@ import org.alfresco.repo.imap.AlfrescoImapConst.ImapViewMode; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.repository.NodeRef; +import com.icegreen.greenmail.store.SimpleStoredMessage; + /** * @author Arseny Kovalchuk * @since 3.2 @@ -85,36 +88,18 @@ public interface ImapService * Returns an collection of mailboxes. This method serves LIST command of the IMAP protocol. * * @param user User making the request - * @param mailboxPattern String name of a mailbox encoded in MUTF-7, possible including a wildcard. + * @param mailboxPattern String name of a mailbox, possible including a wildcard. + * @param listSubscribed list only subscribed folders? * @return Collection of mailboxes matching the pattern. */ - public List listMailboxes(AlfrescoImapUser user, String mailboxPattern); - - /** - * Returns an collection of subscribed mailboxes. This method serves LSUB command of the IMAP protocol. - * - * @param user User making the request - * @param mailboxPattern String name of a mailbox encoded in MUTF-7, possible including a wildcard. - * @return Collection of mailboxes matching the pattern. - */ - public List listSubscribedMailboxes(AlfrescoImapUser user, String mailboxPattern); - - /** - * Returns a reference to a newly created mailbox. The request should specify a mailbox that does not already exist on this server, that could exist on this server and that the - * user has rights to create. This method serves CREATE command of the IMAP protocol. - * - * @param user User making the request. - * @param mailboxName String name of the target encoded in MUTF-7, - * @return an Mailbox reference. - */ - public AlfrescoImapFolder createMailbox(AlfrescoImapUser user, String mailboxName); + public List listMailboxes(AlfrescoImapUser user, String mailboxPattern, boolean listSubscribed); /** * Deletes an existing MailBox. Specified mailbox must already exist on this server, and the user must have rights to delete it. This method serves DELETE command of the IMAP * protocol. * * @param user User making the request. - * @param mailboxName String name of the target encoded in MUTF-7, + * @param mailboxName String name of the target, * @throws com.icegreen.greenmail.store.FolderException if mailbox has a non-selectable store with children */ public void deleteMailbox(AlfrescoImapUser user, String mailboxName); @@ -126,35 +111,40 @@ public interface ImapService * protocol. * * @param user User making the request. - * @param oldMailboxName String name of the existing folder encoded in MUTF-7, - * @param newMailboxName String target new name encoded in MUTF-7, + * @param oldMailboxName String name of the existing folder + * @param newMailboxName String target new name */ public void renameMailbox(AlfrescoImapUser user, String oldMailboxName, String newMailboxName); /** - * Returns a reference to an existing Mailbox. The requested mailbox must already exists on this server and the requesting user must have at least lookup rights.

It is - * also can be used by to obtain hierarchy delimiter by the LIST command:

C: 2 list "" ""

S: * LIST () "." ""

S: 2 OK LIST completed. + * Returns a reference to a mailbox, either creating a new one or retrieving an existing one. * - * @param user User making the request. - * @param mailboxName String name of the target encoded in MUTF-7,. - * @return an Mailbox reference. + * @param user + * User making the request. + * @param mailboxName + * String name of the target. + * @param mayExist + * Is the mailbox allowed to exist already? If false and the mailbox already exists, an error will be thrown + * @param mayCreate + * If the mailbox does not exist, can one be created? If false then an error is thrown if the folder does not exist + * @return a Mailbox reference */ - public AlfrescoImapFolder getFolder(AlfrescoImapUser user, String mailboxName); + public AlfrescoImapFolder getOrCreateMailbox(AlfrescoImapUser user, String mailboxName, boolean mayExist, boolean mayCreate); /** - * Get root reference for the specified mailbox + * Get the node ref of the user's imap home. Will create it on demand if it + * does not already exist. * - * @param mailboxName mailbox name in IMAP client. - * @param userName - * @return NodeRef of root reference for the specified mailbox + * @param userName user name + * @return user IMAP home reference and create it if it doesn't exist. */ - public NodeRef getMailboxRootRef(String mailboxName, String userName); + public NodeRef getUserImapHomeRef(final String userName); /** * Subscribes a user to a mailbox. The mailbox must exist locally and the user must have rights to modify it.

This method serves SUBSCRIBE command of the IMAP protocol. * * @param user User making the request - * @param mailbox String representation of a mailbox name encoded in MUTF-7,. + * @param mailbox String representation of a mailbox name. */ public void subscribe(AlfrescoImapUser user, String mailbox); @@ -162,7 +152,7 @@ public interface ImapService * Unsubscribes from a given mailbox.

This method serves UNSUBSCRIBE command of the IMAP protocol. * * @param user User making the request - * @param mailbox String representation of a mailbox name encoded in MUTF-7,. + * @param mailbox String representation of a mailbox name. */ public void unsubscribe(AlfrescoImapUser user, String mailbox); @@ -196,7 +186,30 @@ public interface ImapService * @param includeSubFolders includeSubFolders * @return list of emails that context folder contains. */ - public List searchMails(NodeRef contextNodeRef, ImapViewMode viewMode); + public FolderStatus getFolderStatus(final String userName, final NodeRef contextNodeRef, ImapViewMode viewMode); + + /** + * Gets a cached MIME message for the given file, complete with message body. + * + * @param messageFileInfo imap file info. + * @return a message. + */ + public SimpleStoredMessage getMessage(FileInfo messageFileInfo) throws MessagingException; + + /** + * Creates a MIME message for the given file + * + * @param messageFileInfo imap file info. + * @param generateBody Should the message body be generated? + * @return a message. + */ + public SimpleStoredMessage createImapMessage(FileInfo messageFileInfo, boolean generateBody) throws MessagingException; + + /** + * Expunges (deletes) an IMAP message if its flags indicates + * @param messageFileInfo imap file info. + */ + public void expungeMessage(FileInfo messageFileInfo); /** * Return flags that belong to the specified imap folder. @@ -291,4 +304,27 @@ public interface ImapService * @return true if enabled */ public boolean getImapServerEnabled(); + + static class FolderStatus + { + public final int messageCount; + public final int recentCount; + public final int firstUnseen; + public final int unseenCount; + public final long uidValidity; + public final String changeToken; + public final NavigableMap search; + + public FolderStatus(int messageCount, int recentCount, int firstUnseen, int unseenCount, long uidValidity, + String changeToken, NavigableMap search) + { + this.messageCount = messageCount; + this.recentCount = recentCount; + this.firstUnseen = firstUnseen; + this.unseenCount = unseenCount; + this.uidValidity = uidValidity; + this.changeToken = changeToken; + this.search = search; + } + } } diff --git a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java index 8290567677..2cf79f3e6a 100644 --- a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java +++ b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -28,7 +28,9 @@ import java.io.OutputStream; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -36,9 +38,13 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.NavigableMap; import java.util.Set; +import java.util.TreeMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.mail.Flags; +import javax.mail.MessagingException; import javax.mail.Flags.Flag; import javax.mail.MessagingException; import javax.mail.Multipart; @@ -57,6 +63,7 @@ import org.alfresco.repo.imap.config.ImapConfigMountPointsBean; import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy; import org.alfresco.repo.node.NodeServicePolicies.OnCreateChildAssociationPolicy; import org.alfresco.repo.node.NodeServicePolicies.OnDeleteChildAssociationPolicy; +import org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy; import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; @@ -67,13 +74,15 @@ import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.site.SiteModel; import org.alfresco.repo.site.SiteServiceException; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.lock.NodeLockedException; +import org.alfresco.service.cmr.model.FileExistsException; import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileFolderUtil; import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.model.FileNotFoundException; import org.alfresco.service.cmr.model.SubFolderFilter; import org.alfresco.service.cmr.preference.PreferenceService; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -90,8 +99,10 @@ import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.site.SiteInfo; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.GUID; +import org.alfresco.util.MaxSizeMap; +import org.alfresco.util.Pair; import org.alfresco.util.PropertyCheck; -import org.alfresco.util.Utf7; import org.alfresco.util.config.RepositoryFolderConfigBean; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -101,18 +112,18 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean; import org.springframework.extensions.surf.util.I18NUtil; import org.springframework.util.FileCopyUtils; -import com.icegreen.greenmail.imap.ImapConstants; +import com.icegreen.greenmail.store.SimpleStoredMessage; /** * @author Dmitry Vaserin * @author Arseny Kovalchuk + * @author David Ward * @since 3.2 */ -public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPolicy, OnDeleteChildAssociationPolicy +public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPolicy, OnDeleteChildAssociationPolicy, OnUpdatePropertiesPolicy, BeforeDeleteNodePolicy { private Log logger = LogFactory.getLog(ImapServiceImpl.class); - private static final String ERROR_PERMISSION_DENIED = "imap.server.error.permission_denied"; private static final String ERROR_FOLDER_ALREADY_EXISTS = "imap.server.error.folder_already_exist"; private static final String ERROR_MAILBOX_NAME_IS_MANDATORY = "imap.server.error.mailbox_name_is_mandatory"; private static final String ERROR_CANNOT_GET_A_FOLDER = "imap.server.error.cannot_get_a_folder"; @@ -120,7 +131,7 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol private static final String CHECKED_NODES = "imap.flaggable.aspect.checked.list"; private static final String FAVORITE_SITES = "imap.favorite.sites.list"; - private static final String UIDVALIDITY_LISTENER_ALREADY_BOUND = "imap.uidvalidity.already.bound"; + private static final String UIDVALIDITY_TRANSACTION_LISTENER = "imap.uidvalidity.txn.listener"; private SysAdminParams sysAdminParams; private FileFolderService fileFolderService; @@ -130,16 +141,16 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol private BehaviourFilter policyBehaviourFilter; private MimetypeService mimetypeService; - /** - * Folders cache - * - * Key : folder name, Object : AlfrescoImapFolder - */ - private SimpleCache foldersCache; - + // Note that this cache need not be cluster synchronized, as it is keyed by the cluster-safe change token + private Map, FolderStatus> folderCache; + private int folderCacheSize = 1000; + private ReentrantReadWriteLock folderCacheLock = new ReentrantReadWriteLock(); + private SimpleCache messageCache; private Map imapConfigMountPoints; private RepositoryFolderConfigBean[] ignoreExtractionFoldersBeans; private RepositoryFolderConfigBean imapHomeConfigBean; + + private NodeRef imapHomeNodeRef; private Set ignoreExtractionFolders; @@ -193,10 +204,18 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol @Override protected void onBootstrap(ApplicationEvent event) { - if (service.getImapServerEnabled()) + service.serviceRegistry.getTransactionService().getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - service.startup(); - } + @Override + public Void execute() throws Throwable + { + if (service.getImapServerEnabled()) + { + service.startup(); + } + return null; + } + }); } @Override @@ -213,20 +232,10 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol { this.sysAdminParams = sysAdminParams; } - - public void setFoldersCache(SimpleCache foldersCache) + + public void setMessageCache(SimpleCache messageCache) { - this.foldersCache = foldersCache; - } - - public SimpleCache getFoldersCache() - { - return foldersCache; - } - - public FileFolderService getFileFolderService() - { - return fileFolderService; + this.messageCache = messageCache; } public void setFileFolderService(FileFolderService fileFolderService) @@ -263,6 +272,11 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol { this.imapHomeConfigBean = imapHomeConfigBean; } + + public void setFolderCacheSize(int folderCacheSize) + { + this.folderCacheSize = folderCacheSize; + } public String getDefaultFromAddress() { @@ -350,6 +364,7 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol PropertyCheck.mandatory(this, "repositoryTemplatePath", repositoryTemplatePath); PropertyCheck.mandatory(this, "policyBehaviourFilter", policyBehaviourFilter); PropertyCheck.mandatory(this, "mimetypeService", mimetypeService); + this.folderCache = new MaxSizeMap, FolderStatus>(folderCacheSize, false); // be sure that a default e-mail is correct try @@ -377,21 +392,11 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol public void startup() { - bindBeahaviour(); + bindBehaviour(); final NamespaceService namespaceService = serviceRegistry.getNamespaceService(); final SearchService searchService = serviceRegistry.getSearchService(); - - // Hit the mount points for early failure - AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { - public Void doWork() throws Exception - { - getMountPoints(); - return null; - } - }, AuthenticationUtil.getSystemUserName()); - + // Get NodeRefs for folders to ignore this.ignoreExtractionFolders = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork>() { @@ -425,138 +430,183 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol return imapHomeConfigBean.getOrCreateFolderPath(namespaceService, nodeService, searchService, fileFolderService); } }, AuthenticationUtil.getSystemUserName()); + + // Hit the mount points and warm the caches for early failure + AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Void doWork() throws Exception + { + for (String mountPointName: imapConfigMountPoints.keySet()) + { + for (AlfrescoImapFolder mailbox : listMailboxes(new AlfrescoImapUser(null, AuthenticationUtil + .getSystemUserName(), null), mountPointName + "*", false)) + { + mailbox.getUidNext(); + } + } + return null; + } + }, AuthenticationUtil.getSystemUserName()); + } public void shutdown() { } - protected void bindBeahaviour() + protected void bindBehaviour() { if (logger.isDebugEnabled()) { logger.debug("[bindBeahaviour] Binding behaviours"); } PolicyComponent policyComponent = (PolicyComponent) serviceRegistry.getService(QName.createQName(NamespaceService.ALFRESCO_URI, "policyComponent")); + + // Only listen to folders we've tagged with imap properties - not all folders or we'll really slow down the repository! policyComponent.bindAssociationBehaviour( OnCreateChildAssociationPolicy.QNAME, - ContentModel.TYPE_FOLDER, + ImapModel.ASPECT_IMAP_FOLDER, ContentModel.ASSOC_CONTAINS, - new JavaBehaviour(this, "onCreateChildAssociation", NotificationFrequency.TRANSACTION_COMMIT)); - policyComponent.bindAssociationBehaviour( - OnCreateChildAssociationPolicy.QNAME, - ContentModel.TYPE_CONTENT, - ContentModel.ASSOC_CONTAINS, - new JavaBehaviour(this, "onCreateChildAssociation", NotificationFrequency.TRANSACTION_COMMIT)); + new JavaBehaviour(this, "onCreateChildAssociation", NotificationFrequency.EVERY_EVENT)); policyComponent.bindAssociationBehaviour( OnDeleteChildAssociationPolicy.QNAME, - ContentModel.TYPE_FOLDER, + ImapModel.ASPECT_IMAP_FOLDER, ContentModel.ASSOC_CONTAINS, - new JavaBehaviour(this, "onDeleteChildAssociation", NotificationFrequency.TRANSACTION_COMMIT)); - policyComponent.bindAssociationBehaviour( - OnDeleteChildAssociationPolicy.QNAME, + new JavaBehaviour(this, "onDeleteChildAssociation", NotificationFrequency.EVERY_EVENT)); + policyComponent.bindClassBehaviour( + OnUpdatePropertiesPolicy.QNAME, ContentModel.TYPE_CONTENT, - ContentModel.ASSOC_CONTAINS, - new JavaBehaviour(this, "onDeleteChildAssociation", NotificationFrequency.TRANSACTION_COMMIT)); + new JavaBehaviour(this, "onUpdateProperties", NotificationFrequency.EVERY_EVENT)); policyComponent.bindClassBehaviour( BeforeDeleteNodePolicy.QNAME, ContentModel.TYPE_CONTENT, new JavaBehaviour(this, "beforeDeleteNode", NotificationFrequency.EVERY_EVENT)); - policyComponent.bindClassBehaviour( - BeforeDeleteNodePolicy.QNAME, - ContentModel.TYPE_FOLDER, - new JavaBehaviour(this, "beforeDeleteNode", NotificationFrequency.EVERY_EVENT)); } // ---------------------- Service Methods -------------------------------- - public List listSubscribedMailboxes(AlfrescoImapUser user, String mailboxPattern) + public SimpleStoredMessage getMessage(FileInfo mesInfo) throws MessagingException { - mailboxPattern = Utf7.decode(mailboxPattern, Utf7.UTF7_MODIFIED); - - if (logger.isDebugEnabled()) + NodeRef nodeRef = mesInfo.getNodeRef(); + Date modified = (Date) nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED); + if(modified != null) { - logger.debug("Listing subscribed mailboxes: mailboxPattern=" + mailboxPattern); + CacheItem cached = messageCache.get(nodeRef); + if (cached != null) + { + if (cached.getModified().equals(modified)) + { + return cached.getMessage(); + } + } + SimpleStoredMessage message = createImapMessage(mesInfo, true); + messageCache.put(nodeRef, new CacheItem(modified, message)); + return message; } - mailboxPattern = getMailPathInRepo(mailboxPattern); - if (logger.isDebugEnabled()) + else { - logger.debug("Listing subscribed mailboxes: mailbox path in Alfresco=" + mailboxPattern); + SimpleStoredMessage message = createImapMessage(mesInfo, true); + return message; + } + } + + public SimpleStoredMessage createImapMessage(FileInfo fileInfo, boolean generateBody) throws MessagingException + { + // TODO MER 26/11/2010- this test should really be that the content of the node is of type message/RFC822 + Long key = (Long) fileInfo.getProperties().get(ContentModel.PROP_NODE_DBID); + if (nodeService.hasAspect(fileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_CONTENT)) + { + return new SimpleStoredMessage(new ImapModelMessage(fileInfo, serviceRegistry, generateBody), new Date(), key); + } + else + { + return new SimpleStoredMessage(new ContentModelMessage(fileInfo, serviceRegistry, generateBody), new Date(), key); } - return listMailboxes(user, mailboxPattern, true); } - public List listMailboxes(AlfrescoImapUser user, String mailboxPattern) + public void expungeMessage(FileInfo fileInfo) { - mailboxPattern = Utf7.decode(mailboxPattern, Utf7.UTF7_MODIFIED); - - if (logger.isDebugEnabled()) + Flags flags = getFlags(fileInfo); + if (flags.contains(Flags.Flag.DELETED)) { - logger.debug("Listing mailboxes: mailboxPattern=" + mailboxPattern); + fileFolderService.delete(fileInfo.getNodeRef()); + messageCache.remove(fileInfo.getNodeRef()); } - mailboxPattern = getMailPathInRepo(mailboxPattern); - if (logger.isDebugEnabled()) - { - logger.debug("Listing mailboxes: mailbox path in Alfresco Repository = " + mailboxPattern); - } - - return listMailboxes(user, mailboxPattern, false); } - - public AlfrescoImapFolder createMailbox(AlfrescoImapUser user, String mailboxName) + + public AlfrescoImapFolder getOrCreateMailbox(AlfrescoImapUser user, String mailboxName, boolean mayExist, boolean mayCreate) { if (mailboxName == null) { throw new IllegalArgumentException(I18NUtil.getMessage(ERROR_MAILBOX_NAME_IS_MANDATORY)); } - mailboxName = Utf7.decode(mailboxName, Utf7.UTF7_MODIFIED); - if (logger.isDebugEnabled()) + // A request for the hierarchy delimiter + if (mailboxName.length() == 0) { - logger.debug("Creating mailbox: " + mailboxName); + return new AlfrescoImapFolder(user.getLogin(), serviceRegistry); } - NodeRef root = getMailboxRootRef(mailboxName, user.getLogin()); - NodeRef parentNodeRef = root; // it is used for hierarhy deep search. - for (String folderName : getMailPathInRepo(mailboxName).split(String.valueOf(AlfrescoImapConst.HIERARCHY_DELIMITER))) + final NodeRef root; + final List pathElements; + ImapViewMode viewMode = ImapViewMode.ARCHIVE; + int index = mailboxName.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER); + if (index < 0) { - NodeRef child = fileFolderService.searchSimple(parentNodeRef, folderName); - - if (logger.isDebugEnabled()) + root = getUserImapHomeRef(user.getLogin()); + pathElements = Collections.singletonList(mailboxName); + } + else + { + String rootPath = mailboxName.substring(0, index); + ImapConfigMountPointsBean imapConfigMountPoint = this.imapConfigMountPoints.get(rootPath); + if (imapConfigMountPoint != null) { - logger.debug("Trying to create folder '" + folderName + "'"); - } - if (child == null) - { - // folder doesn't exist - AccessStatus status = permissionService.hasPermission(parentNodeRef, PermissionService.CREATE_CHILDREN); - if (status == AccessStatus.DENIED) - { - throw new AlfrescoRuntimeException(ERROR_PERMISSION_DENIED); - } - FileInfo mailFolder = serviceRegistry.getFileFolderService().create(parentNodeRef, folderName, ContentModel.TYPE_FOLDER); - AlfrescoImapFolder resultFolder = new AlfrescoImapFolder( - user.getQualifiedMailboxName(), - mailFolder, - folderName, - getViewMode(mailboxName), - root, - getMountPointName(mailboxName), - isExtractionEnabled(mailFolder.getNodeRef()), - serviceRegistry); - foldersCache.put(mailboxName, resultFolder); - return resultFolder; + root = imapConfigMountPoint.getFolderPath(serviceRegistry.getNamespaceService(), nodeService, serviceRegistry.getSearchService(), fileFolderService); + pathElements = Arrays.asList(mailboxName.substring(index + 1).split( + String.valueOf(AlfrescoImapConst.HIERARCHY_DELIMITER))); + viewMode = imapConfigMountPoint.getMode(); } else - { - // folder already exists - if (logger.isDebugEnabled()) - { - logger.debug("Folder '" + folderName + "' already exists"); - } - // next search from new parent - parentNodeRef = child; + { + root = getUserImapHomeRef(user.getLogin()); + pathElements = Arrays.asList(mailboxName.split(String.valueOf(AlfrescoImapConst.HIERARCHY_DELIMITER))); } } - throw new AlfrescoRuntimeException(ERROR_FOLDER_ALREADY_EXISTS); + FileInfo mailFolder; + try + { + mailFolder = fileFolderService.resolveNamePath(root, pathElements, !mayCreate); + } + catch (FileNotFoundException e) + { + throw new AlfrescoRuntimeException(ERROR_CANNOT_GET_A_FOLDER, new String[] + { + mailboxName + }); + } + if (mailFolder == null) + { + if (!mayCreate) + { + throw new AlfrescoRuntimeException(ERROR_CANNOT_GET_A_FOLDER, new String[] + { + mailboxName + }); + } + if (logger.isDebugEnabled()) + { + logger.debug("Creating mailbox: " + mailboxName); + } + mailFolder = FileFolderUtil.makeFolders(fileFolderService, root, pathElements, ContentModel.TYPE_FOLDER); + } + else + { + if (!mayExist) + { + throw new AlfrescoRuntimeException(ERROR_FOLDER_ALREADY_EXISTS); + } + } + return new AlfrescoImapFolder(mailFolder, user.getLogin(), pathElements.get(pathElements.size() - 1), mailboxName, viewMode, + serviceRegistry, true, isExtractionEnabled(mailFolder.getNodeRef())); } public void deleteMailbox(AlfrescoImapUser user, String mailboxName) @@ -570,7 +620,7 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol throw new IllegalArgumentException(I18NUtil.getMessage(ERROR_MAILBOX_NAME_IS_MANDATORY)); } - AlfrescoImapFolder folder = getFolder(user, mailboxName); + AlfrescoImapFolder folder = getOrCreateMailbox(user, mailboxName, true, false); NodeRef nodeRef = folder.getFolderInfo().getNodeRef(); List childFolders = fileFolderService.listFolders(nodeRef); @@ -599,7 +649,6 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol throw new AlfrescoRuntimeException(mailboxName + " - Can't delete a non-selectable store with children."); } } - foldersCache.remove(mailboxName); } public void renameMailbox(AlfrescoImapUser user, String oldMailboxName, String newMailboxName) @@ -609,467 +658,50 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol throw new IllegalArgumentException(ERROR_MAILBOX_NAME_IS_MANDATORY); } - AlfrescoImapFolder sourceNode = getFolder(user, oldMailboxName); + AlfrescoImapFolder sourceNode = getOrCreateMailbox(user, oldMailboxName, true, false); - oldMailboxName = Utf7.decode(oldMailboxName, Utf7.UTF7_MODIFIED); - newMailboxName = Utf7.decode(newMailboxName, Utf7.UTF7_MODIFIED); if (logger.isDebugEnabled()) { logger.debug("Renaming folder oldMailboxName=" + oldMailboxName + " newMailboxName=" + newMailboxName); } - - NodeRef root = getMailboxRootRef(oldMailboxName, user.getLogin()); - String[] folderNames = getMailPathInRepo(newMailboxName).split(String.valueOf(AlfrescoImapConst.HIERARCHY_DELIMITER)); - String folderName = null; - NodeRef parentNodeRef = root; // initial root for search - try - { - for (int i = 0; i < folderNames.length; i++) - { - folderName = folderNames[i]; - if (i == (folderNames.length - 1)) // is it the last element - { - FileInfo newFileInfo = null; - if (oldMailboxName.equalsIgnoreCase(AlfrescoImapConst.INBOX_NAME)) - { - // If you trying to rename INBOX - // - just copy it to another folder with new name - // and leave INBOX (with children) intact. - newFileInfo = fileFolderService.copy(sourceNode.getFolderInfo().getNodeRef(), parentNodeRef, folderName); - } - else - { - newFileInfo = fileFolderService.move( - sourceNode.getFolderInfo().getNodeRef(), - parentNodeRef, folderName); - } - - foldersCache.remove(oldMailboxName); - AlfrescoImapFolder resultFolder = new AlfrescoImapFolder( - user.getQualifiedMailboxName(), - newFileInfo, - folderName, - getViewMode(newMailboxName), - root, - getMountPointName(newMailboxName), - isExtractionEnabled(newFileInfo.getNodeRef()), - serviceRegistry); - foldersCache.put(newMailboxName, resultFolder); - } - else - { - // not last element than checks if it exists and creates if doesn't - NodeRef child = fileFolderService.searchSimple(parentNodeRef, folderName); - - if (child == null) - { - // check creation permission - AccessStatus status = permissionService.hasPermission(parentNodeRef, PermissionService.CREATE_CHILDREN); - if (status == AccessStatus.DENIED) - { - throw new AlfrescoRuntimeException(ERROR_PERMISSION_DENIED); - } - - if (logger.isDebugEnabled()) - { - logger.debug("Creating folder '" + folderName + "'"); - } - serviceRegistry.getFileFolderService().create(parentNodeRef, folderName, ContentModel.TYPE_FOLDER); - } - else - { - parentNodeRef = child; - if (logger.isDebugEnabled()) - { - logger.debug("Folder '" + folderName + "' already exists"); - } - } - } - } - } - catch (Exception e) - { - if (e instanceof AlfrescoRuntimeException) - { - throw (AlfrescoRuntimeException) e; - } - else - { - throw new AlfrescoRuntimeException(e.getMessage(), e); - } - } - } - - public AlfrescoImapFolder getFolder(AlfrescoImapUser user, String mailboxName) - { - if (logger.isDebugEnabled()) - { - logger.debug("Folders cache size is " + foldersCache.getKeys().size()); - } - mailboxName = Utf7.decode(mailboxName, Utf7.UTF7_MODIFIED); - if (logger.isDebugEnabled()) - { - logger.debug("Get folder '" + mailboxName + "'"); - } - // If MailFolder object is used to obtain hierarchy delimiter by LIST command: - // Example: - // C: 2 list "" "" - // S: * LIST () "." "" - // S: 2 OK LIST completed. - if ("".equals(mailboxName)) - { - if (logger.isDebugEnabled()) - { - logger.debug("Request for the hierarchy delimiter"); - } - AlfrescoImapFolder hierarhyFolder = (AlfrescoImapFolder) foldersCache.get("hierarhy.delimeter"); - if (logger.isDebugEnabled()) - { - logger.debug("Got a hierarhy delimeter from cache: " + hierarhyFolder); - } - if (hierarhyFolder == null) - { - hierarhyFolder = new AlfrescoImapFolder(user.getQualifiedMailboxName(), serviceRegistry); - // TEMP Comment out putting this into the same cache as the "real folders" Causes NPE in - // Security Interceptor - //foldersCache.put("hierarhy.delimeter", hierarhyFolder); - } - return hierarhyFolder; - } - else if (AlfrescoImapConst.INBOX_NAME.equalsIgnoreCase(mailboxName) || AlfrescoImapConst.TRASH_NAME.equalsIgnoreCase(mailboxName)) - { - String cacheKey = user.getLogin() + '.' + mailboxName; - AlfrescoImapFolder imapSystemFolder = (AlfrescoImapFolder) foldersCache.get(cacheKey); - - if(imapSystemFolder != null) - { - if (logger.isDebugEnabled()) - { - logger.debug("Got a system folder '" + mailboxName + "' from cache: " + imapSystemFolder); - } - - /** - * Check whether resultFolder is stale - */ - if(imapSystemFolder.isStale()) - { - logger.debug("system folder is stale"); - imapSystemFolder = null; - } - } - - if (imapSystemFolder == null) - { - NodeRef userImapRoot = getUserImapHomeRef(user.getLogin()); - NodeRef mailBoxRef = nodeService.getChildByName(userImapRoot, ContentModel.ASSOC_CONTAINS, mailboxName); - if (mailBoxRef != null) - { - FileInfo mailBoxFileInfo = fileFolderService.getFileInfo(mailBoxRef); - imapSystemFolder = new AlfrescoImapFolder( - user.getQualifiedMailboxName(), - mailBoxFileInfo, - mailBoxFileInfo.getName(), - getViewMode(mailboxName), - userImapRoot, - getMountPointName(mailboxName), - isExtractionEnabled(mailBoxFileInfo.getNodeRef()), - serviceRegistry); - foldersCache.put(cacheKey, imapSystemFolder); - if (logger.isDebugEnabled()) - { - logger.debug("Returning folder '" + mailboxName + "'"); - } - } - else - { - if (logger.isDebugEnabled()) - { - logger.debug("Cannot get a folder '" + mailboxName + "'"); - } - throw new AlfrescoRuntimeException(ERROR_CANNOT_GET_A_FOLDER, new String[] { mailboxName }); - } - } - return imapSystemFolder; - - } - - /** - * Folder is not hierarchy.delimiter or a "System" folder (INBOX or TRASH) - */ - AlfrescoImapFolder resultFolder = (AlfrescoImapFolder) foldersCache.get(mailboxName); - if(resultFolder != null) - { - if (logger.isDebugEnabled()) - { - logger.debug("Got a folder '" + mailboxName + "' from cache: " + resultFolder); - } - - /** - * Check whether resultFolder is stale - */ - /* - if(resultFolder.isStale()) - { - logger.debug("folder is stale"); - resultFolder = null; - } - */ - } - - if (resultFolder == null) + NodeRef newMailParent; + String newMailName; + int index = newMailboxName.lastIndexOf(AlfrescoImapConst.HIERARCHY_DELIMITER); + if (index < 0) { - ImapViewMode viewMode = getViewMode(mailboxName); - String mountPointName = getMountPointName(mailboxName); - - NodeRef root = getMailboxRootRef(mailboxName, user.getLogin()); - NodeRef nodeRef = root; // initial top folder - - String[] folderNames = getMailPathInRepo(mailboxName).split(String.valueOf(AlfrescoImapConst.HIERARCHY_DELIMITER)); - - if(folderNames.length == 1 && folderNames[0].length() == 0) - { - // This is the root of the mount point e.g "Alfresco IMAP" which has a path from root of "" - FileInfo folderFileInfo = fileFolderService.getFileInfo(root); - - resultFolder = new AlfrescoImapFolder( - user.getQualifiedMailboxName(), - folderFileInfo, - mountPointName, - viewMode, - root, - mountPointName, - isExtractionEnabled(folderFileInfo.getNodeRef()), - serviceRegistry); - - if (logger.isDebugEnabled()) - { - logger.debug("Returning root folder '" + mailboxName + "'"); - } - } - else - { - - for (int i = 0; i < folderNames.length; i++) - { - if (logger.isDebugEnabled()) - { - logger.debug("Processing of '" + folderNames[i] + "'"); - } - - NodeRef targetNode = fileFolderService.searchSimple(nodeRef, folderNames[i]); - - if (i == 0 && targetNode == null) - { - resultFolder = new AlfrescoImapFolder(user.getQualifiedMailboxName(), serviceRegistry); - if (logger.isDebugEnabled()) - { - logger.debug("Returning empty folder '" + folderNames[i] + "'"); - } - // skip cache for root - return resultFolder; - } - - if (i == (folderNames.length - 1)) // is last - { - FileInfo folderFileInfo = fileFolderService.getFileInfo(targetNode); - - if (logger.isDebugEnabled()) - { - logger.debug("Found folder to list: " + folderFileInfo.getName()); - } - - resultFolder = new AlfrescoImapFolder( - user.getQualifiedMailboxName(), - folderFileInfo, - folderFileInfo.getName(), - viewMode, - root, - mountPointName, - isExtractionEnabled(folderFileInfo.getNodeRef()), - serviceRegistry); - - if (logger.isDebugEnabled()) - { - logger.debug("Returning folder '" + mailboxName + "'"); - } - } - - /** - * End of loop - next element in path - */ - nodeRef = targetNode; - } - } - - if (resultFolder == null) - { - if (logger.isDebugEnabled()) - { - logger.debug("Cannot get a folder '" + mailboxName + "'"); - } - throw new AlfrescoRuntimeException(ERROR_CANNOT_GET_A_FOLDER, new String[] { mailboxName }); - } - else - { - foldersCache.put(mailboxName, resultFolder); - if (logger.isDebugEnabled()) - { - logger.debug("Put a folder '" + mailboxName + "' to cache: " + resultFolder); - } - } - } - return resultFolder; - - } - - /** - * Deep search for mailboxes/folders in the specified context - * - * Certain folders are excluded depending upon the view mode. - * - For ARCHIVE mode all Share Sites are excluded. - * - For MIXED and VIRTUAL non favourite sites are excluded. - * - * @param contextNodeRef context folder for search - * @param viewMode is folder in "Virtual" View - * @return list of mailboxes/folders - */ - private List searchDeep(final NodeRef contextNodeRef, final ImapViewMode viewMode) - { - if (logger.isDebugEnabled()) - { - logger.debug("[searchDeep] Start. nodeRef=" + contextNodeRef + ", viewMode=" + viewMode); - } - - List searchResult = fileFolderService.listDeepFolders(contextNodeRef, new ImapSubFolderFilter(viewMode)); - - if (logger.isDebugEnabled()) - { - logger.debug("[searchDeep] End"); - } - return new ArrayList(searchResult); - } - - - /** - * Shallow search for mailboxes in specified context - * - * @param contextNodeRef context folder for search - * @param viewMode is folder in "Virtual" View - * @param namePattern name pattern for search - * @return list of mailboxes - */ - private List searchByPattern(final NodeRef contextNodeRef, final ImapViewMode viewMode, final String namePattern) - { - if (logger.isDebugEnabled()) - { - logger.debug("[searchByPattern] Start. nodeRef=" + contextNodeRef + ", viewMode=" + viewMode + " namePattern=" + namePattern); - } - - List searchResult; - - /** - * Shallow search for all folders below contextNodeRef - */ - if("*".equals(namePattern)) - { - /** - * This is a simple listing of all folders below contextNodeRef - */ - logger.debug("call file folder service to list folders"); - - searchResult = fileFolderService.listFolders(contextNodeRef); + newMailParent = getUserImapHomeRef(user.getLogin()); + newMailName = newMailboxName; } else { - logger.debug("call listDeepFolders"); - searchResult = fileFolderService.listDeepFolders(contextNodeRef, new ImapSubFolderFilter(viewMode, namePattern)); + newMailParent = getOrCreateMailbox(user, newMailboxName.substring(0, index), true, true).getFolderInfo().getNodeRef(); + newMailName = newMailboxName.substring(index + 1); } - - -// DO WE NEED TO WORRY ABOUT THE STUFF BELOW ? -// WOULD ONLY BE RELEVANT if ContextNodeRef is site root. -// IF contextNodeRef is the imap home or the contextNodeRef is in the depths of a non favourite site. -// -// Set result = new HashSet(searchResult); -// if (viewMode == ImapViewMode.VIRTUAL || viewMode == ImapViewMode.MIXED) -// { -// /** -// * In VIRTUAL and MIXED MODE WE SHOULD ONLY DISPLAY FAVOURITE SITES -// */ -// List nonFavSites = getNonFavouriteSites(getCurrentUser()); -// for (SiteInfo siteInfo : nonFavSites) -// { -// FileInfo nonFavSite = fileFolderService.getFileInfo(siteInfo.getNodeRef()); -// -// // search deep for all folders in the site -// List siteChilds = fileFolderService.search(nonFavSite.getNodeRef(), namePattern, false, true, true); -// result.removeAll(siteChilds); -// result.remove(nonFavSite); -// } -// -// } -// else -// { -// /** -// * IN ARCHIVE MODE we don't display folders any SITES -// */ -// // Remove folders from Sites -// List sites = serviceRegistry.getTransactionService().getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback>() -// { -// public List execute() throws Exception -// { -// List res = new ArrayList(); -// try -// { -// -// res = serviceRegistry.getSiteService().listSites(getCurrentUser()); -// } -// catch (SiteServiceException e) -// { -// // Do nothing. Root sites folder was not created. -// if (logger.isWarnEnabled()) -// { -// logger.warn("Root sites folder was not created."); -// } -// } -// catch (InvalidNodeRefException e) -// { -// // Do nothing. Root sites folder was deleted. -// if (logger.isWarnEnabled()) -// { -// logger.warn("Root sites folder was deleted."); -// } -// } -// -// if (logger.isDebugEnabled()) -// { -// logger.debug("Search folders return "); -// } -// -// return res; -// } -// }, false, true); -// -// for (SiteInfo siteInfo : sites) -// { -// List siteChilds = fileFolderService.search(siteInfo.getNodeRef(), namePattern, false, true, true); -// result.removeAll(siteChilds); -// // remove site -// result.remove(fileFolderService.getFileInfo(siteInfo.getNodeRef())); -// } -// -// } - - if (logger.isDebugEnabled()) + + try { - if (logger.isDebugEnabled()) + if (oldMailboxName.equalsIgnoreCase(AlfrescoImapConst.INBOX_NAME)) { - logger.debug("[searchByPattern] End. nodeRef=" + contextNodeRef + ", viewMode=" + viewMode + ", namePattern=" + namePattern + ", searchResult=" +searchResult.size()); + // If you trying to rename INBOX + // - just copy it to another folder with new name + // and leave INBOX (with children) intact. + fileFolderService.copy(sourceNode.getFolderInfo().getNodeRef(), newMailParent, + AlfrescoImapConst.INBOX_NAME); + } + else + { + fileFolderService.move(sourceNode.getFolderInfo().getNodeRef(), newMailParent, newMailName); } } - - return searchResult; + catch (FileNotFoundException e) + { + throw new AlfrescoRuntimeException(e.getMessage(), e); + } + catch (FileExistsException e) + { + throw new AlfrescoRuntimeException(e.getMessage(), e); + } } /** @@ -1081,44 +713,129 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol * @param viewMode context folder view mode * @return list of emails that context folder contains. */ - public List searchMails(NodeRef contextNodeRef, ImapViewMode viewMode) + public FolderStatus getFolderStatus(final String userName, final NodeRef contextNodeRef, ImapViewMode viewMode) { if (logger.isDebugEnabled()) { - logger.debug("Search mails contextNodeRef=" + contextNodeRef + ", viewMode=" + viewMode ); + logger.debug("Search mails contextNodeRef=" + contextNodeRef + ", viewMode=" + viewMode); } - - List searchResult = fileFolderService.listFiles(contextNodeRef); - - List result = new LinkedList(); - //List searchResult = fileFolderService.search(contextNodeRef, namePattern, true, false, includeSubFolders); + + // No need to ACL check the change token read + String changeToken = AuthenticationUtil.runAs(new RunAsWork() + { + @Override + public String doWork() throws Exception + { + return (String) nodeService.getProperty(contextNodeRef, ImapModel.PROP_CHANGE_TOKEN); + } + }, AuthenticationUtil.getSystemUserName()); + + Pair cacheKey = null; + if (changeToken != null) + { + cacheKey = new Pair(userName, changeToken); + this.folderCacheLock.readLock().lock(); + try + { + FolderStatus result = this.folderCache.get(cacheKey); + if (result != null) + { + return result; + } + } + finally + { + this.folderCacheLock.readLock().unlock(); + } + } + List fileInfos = fileFolderService.listFiles(contextNodeRef); + final NavigableMap currentSearch = new TreeMap(); + switch (viewMode) { case MIXED: - result = searchResult; + for (FileInfo fileInfo : fileInfos) + { + currentSearch.put((Long) fileInfo.getProperties().get(ContentModel.PROP_NODE_DBID), fileInfo); + } break; case ARCHIVE: - for (FileInfo fileInfo : searchResult) + for (FileInfo fileInfo : fileInfos) { if (nodeService.hasAspect(fileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_CONTENT)) { - result.add(fileInfo); + currentSearch.put((Long) fileInfo.getProperties().get(ContentModel.PROP_NODE_DBID), fileInfo); } } break; case VIRTUAL: - for (FileInfo fileInfo : searchResult) + for (FileInfo fileInfo : fileInfos) { if (!nodeService.hasAspect(fileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_CONTENT)) { - result.add(fileInfo); + currentSearch.put((Long) fileInfo.getProperties().get(ContentModel.PROP_NODE_DBID), fileInfo); } } break; } - logger.debug("Found files:" + result.size()); - return result; + int messageCount = currentSearch.size(), recentCount = 0, unseenCount = 0, firstUnseen = 0; + int i = 1; + for (FileInfo fileInfo : currentSearch.values()) + { + Flags flags = getFlags(fileInfo); + if (flags.contains(Flags.Flag.RECENT)) + { + recentCount++; + } + if (!flags.contains(Flags.Flag.SEEN)) + { + if (firstUnseen == 0) + { + firstUnseen = i; + } + unseenCount++; + } + i++; + } + // Add the IMAP folder aspect with appropriate initial values if it is not already there + if (changeToken == null) + { + changeToken = GUID.generate(); + cacheKey = new Pair(userName, changeToken); + final String finalToken = changeToken; + AuthenticationUtil.runAs(new RunAsWork() + { + @Override + public Void doWork() throws Exception + { + nodeService.setProperty(contextNodeRef, ImapModel.PROP_CHANGE_TOKEN, finalToken); + nodeService.setProperty(contextNodeRef, ImapModel.PROP_MAXUID, currentSearch.isEmpty() ? 0 + : currentSearch.lastKey()); + return null; + } + }, AuthenticationUtil.getSystemUserName()); + } + Long uidValidity = (Long) nodeService.getProperty(contextNodeRef, ImapModel.PROP_UIDVALIDITY); + FolderStatus result = new FolderStatus(messageCount, recentCount, firstUnseen, unseenCount, + uidValidity == null ? 0 : uidValidity, changeToken, currentSearch); + this.folderCacheLock.writeLock().lock(); + try + { + FolderStatus oldResult = this.folderCache.get(cacheKey); + if (oldResult != null) + { + return oldResult; + } + this.folderCache.put(cacheKey, result); + + logger.debug("Found files:" + currentSearch.size()); + return result; + } + finally + { + this.folderCacheLock.writeLock().unlock(); + } } public void subscribe(AlfrescoImapUser user, String mailbox) @@ -1127,7 +844,7 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol { logger.debug("Subscribing: " + user + ", " + mailbox); } - AlfrescoImapFolder mailFolder = getFolder(user, mailbox); + AlfrescoImapFolder mailFolder = getOrCreateMailbox(user, mailbox, true, false); nodeService.removeAspect(mailFolder.getFolderInfo().getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_NONSUBSCRIBED); } @@ -1137,7 +854,7 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol { logger.debug("Unsubscribing: " + user + ", " + mailbox); } - AlfrescoImapFolder mailFolder = getFolder(user, mailbox); + AlfrescoImapFolder mailFolder = getOrCreateMailbox(user, mailbox, true, false); if(mailFolder.getFolderInfo() != null) { logger.debug("Unsubscribing by ASPECT_IMAP_FOLDER_NONSUBSCRIBED"); @@ -1156,12 +873,9 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol * @param messageInfo imap folder info. * @return flags. */ - public synchronized Flags getFlags(FileInfo messageInfo) + public Flags getFlags(FileInfo messageInfo) { Flags flags = new Flags(); - if (nodeService.exists(messageInfo.getNodeRef())) - { - checkForFlaggableAspect(messageInfo.getNodeRef()); Map props = nodeService.getProperties(messageInfo.getNodeRef()); for (QName key : qNameToFlag.keySet()) @@ -1172,7 +886,7 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol flags.add(qNameToFlag.get(key)); } } - } + return flags; } @@ -1183,7 +897,7 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol * @param flags flags to set. * @param value value to set. */ - public synchronized void setFlags(FileInfo messageInfo, Flags flags, boolean value) + public void setFlags(FileInfo messageInfo, Flags flags, boolean value) { checkForFlaggableAspect(messageInfo.getNodeRef()); @@ -1203,7 +917,11 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol */ public void setFlag(FileInfo messageInfo, Flag flag, boolean value) { - NodeRef nodeRef = messageInfo.getNodeRef(); + setFlag(messageInfo.getNodeRef(), flag, value); + } + + private void setFlag(NodeRef nodeRef, Flag flag, boolean value) + { checkForFlaggableAspect(nodeRef); AccessStatus status = permissionService.hasPermission(nodeRef, PermissionService.WRITE_PROPERTIES); if (status == AccessStatus.DENIED) @@ -1213,138 +931,90 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol } else { - nodeService.setProperty(messageInfo.getNodeRef(), flagToQname.get(flag), value); + nodeService.setProperty(nodeRef, flagToQname.get(flag), value); } + messageCache.remove(nodeRef); } /** * Depend on listSubscribed param, list Mailboxes or list subscribed Mailboxes */ - private List listMailboxes(AlfrescoImapUser user, String mailboxPattern, boolean listSubscribed) + public List listMailboxes(AlfrescoImapUser user, String mailboxPattern, boolean listSubscribed) { if(logger.isDebugEnabled()) { logger.debug("[listMailboxes] user:" + user.getLogin() + ", mailboxPattern:" + mailboxPattern + ", listSubscribed:" + listSubscribed); } List result = new LinkedList(); - - Map mountPoints = getMountPoints(); - - NodeRef mountPoint; - + // List mailboxes that are in mount points - for (String mountPointName : mountPoints.keySet()) + int index = mailboxPattern.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER); + String rootPath = index == -1 ? mailboxPattern : mailboxPattern.substring(0, index); + boolean found = false; + + for (String mountPointName : imapConfigMountPoints.keySet()) { - - mountPoint = mountPoints.get(mountPointName); - FileInfo mountPointFileInfo = fileFolderService.getFileInfo(mountPoint); - NodeRef mountParent = nodeService.getParentAssocs(mountPoint).get(0).getParentRef(); - ImapViewMode viewMode = imapConfigMountPoints.get(mountPointName).getMode(); - /* FIX for ALF-2793 Reinstated - if (!mailboxPattern.equals("*")) + if (mountPointName.matches(rootPath.replaceAll("[%\\*]", ".*"))) { - mountPoint = mountParent; - } - */ - List folders = expandFolder(mountPoint, mountPoint, user, mailboxPattern, listSubscribed, viewMode); - if (folders != null) - { - for (AlfrescoImapFolder mailFolder : folders) + NodeRef mountPoint = getMountPoint(mountPointName); + if (mountPoint != null) { - AlfrescoImapFolder folder = (AlfrescoImapFolder) mailFolder; - folder.setMountPointName(mountPointName); - folder.setViewMode(viewMode); - folder.setMountParent(mountParent); + FileInfo mountPointFileInfo = fileFolderService.getFileInfo(mountPoint); + ImapViewMode viewMode = imapConfigMountPoints.get(mountPointName).getMode(); + if (index < 0) + { + String userName = user.getLogin(); + if (!listSubscribed || isSubscribed(mountPointFileInfo, userName)) + { + result.add(new AlfrescoImapFolder(mountPointFileInfo, userName, mountPointName, mountPointName, viewMode, + isExtractionEnabled(mountPointFileInfo.getNodeRef()), serviceRegistry)); + } + else if (rootPath.endsWith("%") && !expandFolder(mountPoint, user, mountPointName, "%", true, viewMode).isEmpty()) // \NoSelect + { + result.add(new AlfrescoImapFolder(mountPointFileInfo, userName, mountPointName, mountPointName, viewMode, + serviceRegistry, false, isExtractionEnabled(mountPointFileInfo.getNodeRef()))); + } + if (rootPath.endsWith("*")) + { + result.addAll(expandFolder(mountPoint, user, mountPointName, "*", listSubscribed, viewMode)); + } + } + else + { + result.addAll(expandFolder(mountPoint, user, mountPointName, + mailboxPattern.substring(index + 1), listSubscribed, viewMode)); + } } - result.addAll(folders); - } - - // Add mount point to the result list - if (mailboxPattern.equals("*")) - { - if ((listSubscribed && isSubscribed(mountPointFileInfo, user.getLogin())) || (!listSubscribed)) + // If we had an exact match, there is no point continuing to search + if (mountPointName.equals(rootPath)) { - result.add( - new AlfrescoImapFolder( - user.getQualifiedMailboxName(), - mountPointFileInfo, - mountPointName, - viewMode, - mountParent, - mountPointName, - isExtractionEnabled(mountPointFileInfo.getNodeRef()), - serviceRegistry)); - } - // \NoSelect - else if (listSubscribed && hasSubscribedChild(mountPointFileInfo, user.getLogin(), viewMode)) - { - result.add( - new AlfrescoImapFolder( - user.getQualifiedMailboxName(), - mountPointFileInfo, - mountPointName, - viewMode, - mountParent, - mountPointName, - serviceRegistry, - false, - isExtractionEnabled(mountPointFileInfo.getNodeRef()))); + found = true; + break; } } - - } // List mailboxes that are in user IMAP Home - NodeRef root = getUserImapHomeRef(user.getLogin()); - List imapFolders = expandFolder(root, root, user, mailboxPattern, listSubscribed, ImapViewMode.ARCHIVE); - - if (imapFolders != null) + if (!found) { - for (AlfrescoImapFolder mailFolder : imapFolders) - { - //AlfrescoImapFolder folder = (AlfrescoImapFolder) mailFolder; - mailFolder.setViewMode(ImapViewMode.ARCHIVE); - mailFolder.setMountParent(root); - } - result.addAll(imapFolders); + NodeRef root = getUserImapHomeRef(user.getLogin()); + result.addAll(expandFolder(root, user, "", mailboxPattern, listSubscribed, ImapViewMode.ARCHIVE)); } - + logger.debug("listMailboxes returning size:" + result.size()); - - StringBuilder prefix = new StringBuilder(128); - prefix.append(ImapConstants.USER_NAMESPACE) - .append(AlfrescoImapConst.HIERARCHY_DELIMITER) - .append(user.getQualifiedMailboxName()) - .append(AlfrescoImapConst.HIERARCHY_DELIMITER); - int prefixLength = prefix.length(); - - for(AlfrescoImapFolder folder : result) - { - String cacheKey = folder.getFullName().substring(prefixLength + 1, folder.getFullName().length() - 1); - logger.debug("[listMailboxes] Adding the cache entry : " + cacheKey); - foldersCache.put(cacheKey, folder); - } return result; - } - + /** - * Get the list of folders + * Recursively search the given root to get a list of folders * - * @param mailboxRoot - * @param root - * @param user - * @param mailboxPattern - * @param listSubscribed - * @param viewMode * @return */ private List expandFolder( - NodeRef mailboxRoot, NodeRef root, AlfrescoImapUser user, + String rootPath, String mailboxPattern, boolean listSubscribed, ImapViewMode viewMode) @@ -1358,8 +1028,6 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol int index = mailboxPattern.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER); String name = null; - String remainName = null; - if (index < 0) { name = mailboxPattern; @@ -1367,238 +1035,70 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol else { name = mailboxPattern.substring(0, index); - remainName = mailboxPattern.substring(index + 1); } + String rootPathPrefix = rootPath.length() == 0 ? "" : rootPath + AlfrescoImapConst.HIERARCHY_DELIMITER; if (logger.isDebugEnabled()) { logger.debug("Listing mailboxes: name=" + name); } + List fullList = new LinkedList(); + ImapSubFolderFilter filter = new ImapSubFolderFilter(viewMode, name.replace('%', '*')); + List list; + // Only list this folder if we have a wildcard name. Otherwise do a direct lookup by name. + if (name.contains("*") || name.contains("%")) + { + list = fileFolderService.listFolders(root); + } + else + { + NodeRef nodeRef = fileFolderService.searchSimple(root, name); + FileInfo fileInfo; + list = nodeRef == null || !(fileInfo = fileFolderService.getFileInfo(nodeRef)).isFolder() ? Collections.emptyList() : Collections.singletonList(fileInfo); + } + if (index < 0) { - // This is the last level - - if ("*".equals(name)) + // This is the last level + for (FileInfo fileInfo : list) { - // Deep listing of all folders - Collection list = searchDeep(root, viewMode); - if (listSubscribed) + if (!filter.isEnterSubfolder(fileInfo.getNodeRef())) { - list = getSubscribed(list, user.getLogin()); + continue; } - - if (list.size() > 0) + String folderPath = rootPathPrefix + fileInfo.getName(); + String userName = user.getLogin(); + if (!listSubscribed || isSubscribed(fileInfo, userName)) { - return createMailFolderList(user, list, mailboxRoot); + fullList.add(new AlfrescoImapFolder(fileInfo, userName, fileInfo.getName(), folderPath, viewMode, + isExtractionEnabled(fileInfo.getNodeRef()), serviceRegistry)); + } + else if (name.endsWith("%") && !expandFolder(fileInfo.getNodeRef(), user, folderPath, "%", true, viewMode).isEmpty()) // \NoSelect + { + fullList.add(new AlfrescoImapFolder(fileInfo, userName, fileInfo.getName(), folderPath, viewMode, + serviceRegistry, false, isExtractionEnabled(fileInfo.getNodeRef()))); + } + if (name.endsWith("*")) + { + fullList.addAll(expandFolder(fileInfo.getNodeRef(), user, folderPath, "*", listSubscribed, viewMode)); } - return null; } - else if (name.endsWith("*")) - { - // Ends with wildcard - List fullList = new LinkedList(); - List list = searchByPattern(root, viewMode, name.replace('%', '*')); - Collection subscribedList = list; - if (listSubscribed) - { - subscribedList = getSubscribed(list, user.getLogin()); - } - - if (list.size() > 0) - { - fullList.addAll(subscribedList); - for (FileInfo fileInfo : list) - { - List childList = searchDeep(fileInfo.getNodeRef(), viewMode); - if (listSubscribed) - { - fullList.addAll(getSubscribed(childList, user.getLogin())); - } - else - { - fullList.addAll(childList); - } - } - return createMailFolderList(user, fullList, mailboxRoot); - } - return null; - } - else if ("%".equals(name)) - { - // Non recursive listing - List subscribedList = new LinkedList(); - List list = searchByPattern(root, viewMode, "*"); - if (listSubscribed) - { - for (FileInfo fileInfo : list) - { - if (isSubscribed(fileInfo, user.getLogin())) - { - // folderName, viewMode, mountPointName will be set in listMailboxes() method - subscribedList.add( - new AlfrescoImapFolder( - user.getQualifiedMailboxName(), - fileInfo, - null, - null, - mailboxRoot, - null, - isExtractionEnabled(fileInfo.getNodeRef()), - serviceRegistry)); - } - // \NoSelect - else if (hasSubscribedChild(fileInfo, user.getLogin(), viewMode)) - { - // folderName, viewMode, mountPointName will be set in listMailboxes() method - subscribedList.add( - new AlfrescoImapFolder( - user.getQualifiedMailboxName(), - fileInfo, - null, - null, - mailboxRoot, - null, - serviceRegistry, - false, - isExtractionEnabled(fileInfo.getNodeRef()))); - } - } - } - else - { - return createMailFolderList(user, list, mailboxRoot); - } - - return subscribedList; - } - else if (name.contains("%") || name.contains("*")) - { - // wild cards in the middle of the name - List list = searchByPattern(root, viewMode, name.replace('%', '*')); - Collection subscribedList = list; - if (listSubscribed) - { - subscribedList = getSubscribed(list, user.getLogin()); - } - - if (subscribedList.size() > 0) - { - return createMailFolderList(user, subscribedList, mailboxRoot); - } - return null; - } - else - { - // No wild cards - List list = searchByPattern(root, viewMode, name); - Collection subscribedList = list; - if (listSubscribed) - { - subscribedList = getSubscribed(list, user.getLogin()); - } - - if (subscribedList.size() > 0) - { - return createMailFolderList(user, subscribedList, mailboxRoot); - } - return null; - } - } - - // If (index != -1) this is not the last level - List result = new LinkedList(); - List list = searchByPattern(root, viewMode, name.replace('%', '*')); - for (FileInfo folder : list) - { - Collection childFolders = expandFolder(mailboxRoot, folder.getNodeRef(), user, remainName, listSubscribed, viewMode); - - if (childFolders != null) - { - result.addAll(childFolders); - } - } - return !result.isEmpty() ? result : null; - } - - /** - * Convert mailpath from IMAP client representation to the alfresco representation view - * (e.g. with default settings - * "getMailPathInRepo(Repository_virtual/Imap Home)" will return "Company Home/Imap Home") - * - * @param mailPath mailbox path in IMAP client - * @return mailbox path in alfresco - */ - private String getMailPathInRepo(String mailPath) - { - if(logger.isDebugEnabled()) - { - logger.debug("[getMailPathInRepo] Path: " + mailPath); - } - String rootFolder; - String remain = ""; - int index = mailPath.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER); - if (index > 0) - { - // mail path contains a / - rootFolder = mailPath.substring(0, index); - remain = mailPath.substring(index + 1); } else - { - //mail path is a root folder - rootFolder = mailPath; - } - if (imapConfigMountPoints.keySet().contains(rootFolder)) - { - //Map mountPoints = getMountPoints(); - //NodeRef rootRef = mountPoints.get(rootFolder); - String path = remain; - - if(logger.isDebugEnabled()) + { + // If (index != -1) this is not the last level + for (FileInfo folder : list) { - logger.debug("[getMailPathInRepo] Mounted point returning: " + path); + if (!filter.isEnterSubfolder(folder.getNodeRef())) + { + continue; + } + fullList.addAll(expandFolder(folder.getNodeRef(), user, rootPathPrefix + folder.getName(), + mailboxPattern.substring(index + 1), listSubscribed, viewMode)); } - - return path; } - else - { - if(logger.isDebugEnabled()) - { - logger.debug("[getMailPathInRepo] Not mounted. Returning path as is: " + mailPath); - } - return mailPath; - } - } - - /** - * Return mount point name for the current mailbox. - * - * @param mailboxName mailbox name in IMAP client. - * @return mount point name or null. - */ - private String getMountPointName(String mailboxName) - { - String rootFolder; - int index = mailboxName.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER); - if (index > 0) - { - rootFolder = mailboxName.substring(0, index); - } - else - { - rootFolder = mailboxName; - } - if (imapConfigMountPoints.keySet().contains(rootFolder)) - { - return rootFolder; - } - else - { - return null; - } - + return fullList; } /** @@ -1606,96 +1106,44 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol * * @return Map of mount points. */ - private Map getMountPoints() + private NodeRef getMountPoint(String rootFolder) { - Set mountPointNodeRefs = new HashSet(5); - - Map mountPoints = new HashMap(); final NamespaceService namespaceService = serviceRegistry.getNamespaceService(); final SearchService searchService = serviceRegistry.getSearchService(); - for (final ImapConfigMountPointsBean config : imapConfigMountPoints.values()) + final ImapConfigMountPointsBean config = imapConfigMountPoints.get(rootFolder); + try { - try + // Get node reference. Do it in new transaction to avoid RollBack in case when AccessDeniedException is thrown. + return serviceRegistry.getTransactionService().getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - // Get node reference. Do it in new transaction to avoid RollBack in case when AccessDeniedException is thrown. - NodeRef nodeRef = serviceRegistry.getTransactionService().getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + public NodeRef execute() throws Exception { - public NodeRef execute() throws Exception + try { - try - { - return config.getFolderPath(namespaceService, nodeService, searchService, fileFolderService); - } - catch (AccessDeniedException e) - { - if (logger.isDebugEnabled()) - { - logger.debug("A mount point is skipped due to Access Dennied. \n" + " Mount point: " + config + "\n" + " User: " - + AuthenticationUtil.getFullyAuthenticatedUser()); - } - } - - return null; + return config.getFolderPath(namespaceService, nodeService, searchService, fileFolderService); } - }, true, true); - - if (nodeRef != null) - { - if (!mountPointNodeRefs.add(nodeRef)) + catch (AccessDeniedException e) { - throw new IllegalArgumentException("A mount point has been defined twice: \n" + " Mount point: " + config); + if (logger.isDebugEnabled()) + { + logger.debug("A mount point is skipped due to Access Dennied. \n" + " Mount point: " + config + "\n" + " User: " + + AuthenticationUtil.getFullyAuthenticatedUser()); + } } - mountPoints.put(config.getMountPointName(), nodeRef); + + return null; } - } - catch (AccessDeniedException e) + }, true, true); + } + catch (AccessDeniedException e) + { + if (logger.isDebugEnabled()) { - if (logger.isDebugEnabled()) - { - logger.debug("A mount point is skipped due to Access Dennied. \n" + " Mount point: " + config + "\n" + " User: " - + AuthenticationUtil.getFullyAuthenticatedUser()); - } + logger.debug("A mount point is skipped due to Access Dennied. \n" + " Mount point: " + config + "\n" + " User: " + + AuthenticationUtil.getFullyAuthenticatedUser()); } } - return mountPoints; - } - - /** - * Get root reference for the specified mailbox - * - * @param mailboxName mailbox name in IMAP client. - * @param userName - * @return root reference for the specified mailbox - */ - public NodeRef getMailboxRootRef(String mailboxName, String userName) - { - String rootFolder; - int index = mailboxName.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER); - if (index > 0) - { - rootFolder = mailboxName.substring(0, index); - } - else - { - rootFolder = mailboxName; - } - - Map imapConfigs = imapConfigMountPoints; - if (imapConfigs.keySet().contains(rootFolder)) - { - Map mountPoints = getMountPoints(); - NodeRef mountRef = mountPoints.get(rootFolder); - logger.debug("getMailboxRootRef mounted, " + mountRef); - return mountRef; - // MER EXPERIMENT - //return nodeService.getParentAssocs(mountRef).get(0).getParentRef(); - } - else - { - NodeRef ret = getUserImapHomeRef(userName); - logger.debug("getMailboxRootRef using userImapHome, " + ret); - return ret; - } + return null; } /** @@ -1705,9 +1153,8 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol * @param userName user name * @return user IMAP home reference and create it if it doesn't exist. */ - private NodeRef getUserImapHomeRef(final String userName) + public NodeRef getUserImapHomeRef(final String userName) { - NodeRef userHome = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { public NodeRef doWork() throws Exception @@ -1741,97 +1188,7 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol { return !nodeService.hasAspect(fileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_NONSUBSCRIBED); } - - /** - * getSubscribed filters out folders which are not subscribed. - * @param list - * @param userName - * @return collection of subscribed folders. - */ - private Collection getSubscribed(Collection list, String userName) - { - Collection result = new LinkedList(); - - for (FileInfo folderInfo : list) - { - if (isSubscribed(folderInfo, userName)) - { - result.add(folderInfo); - } - } - - return result; - } - - private boolean hasSubscribedChild(FileInfo parent, String userName, ImapViewMode viewMode) - { - List list = searchDeep(parent.getNodeRef(), viewMode); - - for (FileInfo fileInfo : list) - { - if (isSubscribed(fileInfo, userName)) - { - return true; - } - } - - return false; - } - - private List createMailFolderList(AlfrescoImapUser user, Collection list, NodeRef imapUserHomeRef) - { - List result = new LinkedList(); - // XXX : put folders into the cache and get them in next lookups. - // The question is to get a mailBoxName for the cache key... And what if a folders list was changed? - // So, for now keep it as is. - for (FileInfo folderInfo : list) - { - // folderName, viewMode, mountPointName will be set in listSubscribedMailboxes() method - result.add( - new AlfrescoImapFolder( - user.getQualifiedMailboxName(), - folderInfo, - null, - null, - imapUserHomeRef, - null, - isExtractionEnabled(folderInfo.getNodeRef()), - serviceRegistry)); - } - - return result; - - } - - /** - * Return view mode ("virtual", "archive" or "mixed") for specified mailbox. - * - * @param mailboxName name of the mailbox in IMAP client. - * @return view mode of the specified mailbox. - */ - private ImapViewMode getViewMode(String mailboxName) - { - String rootFolder; - int index = mailboxName.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER); - if (index > 0) - { - rootFolder = mailboxName.substring(0, index); - } - else - { - rootFolder = mailboxName; - } - if (imapConfigMountPoints.keySet().contains(rootFolder)) - { - return imapConfigMountPoints.get(rootFolder).getMode(); - } - else - { - return ImapViewMode.ARCHIVE; - } - } - private String getCurrentUser() { return AuthenticationUtil.getFullyAuthenticatedUser(); @@ -2095,7 +1452,7 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol ImapSubFolderFilter(ImapViewMode imapViewMode) { this.imapViewMode = imapViewMode; - this.typesToExclude = serviceRegistry.getDictionaryService().getSubTypes(SiteModel.TYPE_SITE, true); + this.typesToExclude = ImapServiceImpl.this.serviceRegistry.getDictionaryService().getSubTypes(SiteModel.TYPE_SITE, true); this.favs = getFavouriteSites(getCurrentUser()); } @@ -2108,11 +1465,15 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol @Override public boolean isEnterSubfolder(ChildAssociationRef subfolderRef) { - NodeRef folder = subfolderRef.getChildRef(); + return isEnterSubfolder(subfolderRef.getChildRef()); + } + + public boolean isEnterSubfolder(NodeRef folder) + { + String name = (String) nodeService.getProperty(folder, ContentModel.PROP_NAME); if (mailboxPattern != null) { - logger.debug("Child QName: " + subfolderRef.getQName()); - String name = (String) nodeService.getProperty(folder, ContentModel.PROP_NAME); + logger.debug("Child name: " + name); if (logger.isDebugEnabled()) { logger.debug("Folder name: " + name + ". Pattern: " + mailboxPattern + ". Matches: " + name.matches(mailboxPattern)); @@ -2130,12 +1491,12 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol */ if (favs.contains(folder)) { - logger.debug("[ImapSubFolderFilter] (VIRTUAL) including fav site folder :" + subfolderRef.getQName()); + logger.debug("[ImapSubFolderFilter] (VIRTUAL) including fav site folder :" + name); return true; } else { - logger.debug("[ImapSubFolderFilter] (VIRTUAL) excluding non fav site folder :" + subfolderRef.getQName()); + logger.debug("[ImapSubFolderFilter] (VIRTUAL) excluding non fav site folder :" + name); return false; } } @@ -2144,7 +1505,7 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol /** * IN ARCHIVE MODE we don't display folders for any SITES, regardless of whether they are favourites. */ - logger.debug("[ImapSubFolderFilter] (ARCHIVE) excluding site folder :" + subfolderRef.getQName()); + logger.debug("[ImapSubFolderFilter] (ARCHIVE) excluding site folder :" + name); return false; } } @@ -2153,192 +1514,160 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol } + private UidValidityTransactionListener getUidValidityTransactionListener(NodeRef folderRef) + { + String key = UIDVALIDITY_TRANSACTION_LISTENER + folderRef.toString(); + UidValidityTransactionListener txnListener = AlfrescoTransactionSupport.getResource(key); + if (txnListener == null) + { + txnListener = new UidValidityTransactionListener(folderRef, nodeService); + AlfrescoTransactionSupport.bindListener(txnListener); + AlfrescoTransactionSupport.bindResource(key, txnListener); + } + return txnListener; + } + @Override public void onCreateChildAssociation(ChildAssociationRef childAssocRef, boolean isNewNode) { - // Add a listener once, when a lots of messsages were created/moved into the folder - if (AlfrescoTransactionSupport.getResource(UIDVALIDITY_LISTENER_ALREADY_BOUND) == null) + NodeRef childNodeRef = childAssocRef.getChildRef(); + + if (this.serviceRegistry.getDictionaryService().isSubClass(this.nodeService.getType(childNodeRef), ContentModel.TYPE_CONTENT)) { - AlfrescoTransactionSupport.bindListener(new UidValidityTransactionListener(childAssocRef.getParentRef(), nodeService)); - AlfrescoTransactionSupport.bindResource(UIDVALIDITY_LISTENER_ALREADY_BOUND, true); + long newId = (Long) nodeService.getProperty(childNodeRef, ContentModel.PROP_NODE_DBID); + // Keep a record of minimum and maximum node IDs in this folder in this transaction and add a listener that will + // update the UIDVALIDITY and MAXUID properties appropriately. Also force generation of a new change token + getUidValidityTransactionListener(childAssocRef.getParentRef()).recordNewUid(newId); + // Flag new content as recent + setFlag(childNodeRef, Flags.Flag.RECENT, true); } + if (logger.isDebugEnabled()) { - logger.debug("[onCreateChildAssociation] Association " + childAssocRef + " created. UIDVALIDITY will be changed."); + logger.debug("[onCreateChildAssociation] Association " + childAssocRef + " created. CHANGETOKEN will be changed."); } } + @Override public void onDeleteChildAssociation(ChildAssociationRef childAssocRef) { - // Add a listener once, when a lots of messsages were created/moved into the folder - if (AlfrescoTransactionSupport.getResource(UIDVALIDITY_LISTENER_ALREADY_BOUND) == null) + NodeRef childNodeRef = childAssocRef.getChildRef(); + if (this.serviceRegistry.getDictionaryService().isSubClass(this.nodeService.getType(childNodeRef), ContentModel.TYPE_CONTENT)) { - AlfrescoTransactionSupport.bindListener(new UidValidityTransactionListener(childAssocRef.getParentRef(), nodeService)); - AlfrescoTransactionSupport.bindResource(UIDVALIDITY_LISTENER_ALREADY_BOUND, true); + // Force generation of a new change token + getUidValidityTransactionListener(childAssocRef.getParentRef()); + + // Remove the message from the cache + this.messageCache.remove(childNodeRef); } if (logger.isDebugEnabled()) { - logger.debug("[onDeleteChildAssociation] Association " + childAssocRef + " removed. UIDVALIDITY will be changed."); + logger.debug("[onDeleteChildAssociation] Association " + childAssocRef + " created. CHANGETOKEN will be changed."); } } + @Override + public void onUpdateProperties(NodeRef nodeRef, Map before, Map after) + { + for (ChildAssociationRef parentAssoc : nodeService.getParentAssocs(nodeRef)) + { + NodeRef folderRef = parentAssoc.getParentRef(); + if (this.nodeService.hasAspect(folderRef, ImapModel.ASPECT_IMAP_FOLDER)) + { + this.messageCache.remove(nodeRef); + // Force generation of a new change token + getUidValidityTransactionListener(folderRef); + } + } + } + + @Override public void beforeDeleteNode(NodeRef nodeRef) { - - NodeRef parentNodeRef = nodeService.getPrimaryParent(nodeRef).getParentRef(); - if (ContentModel.TYPE_FOLDER.equals(nodeService.getType(nodeRef))) + for (ChildAssociationRef parentAssoc : nodeService.getParentAssocs(nodeRef)) { - // If a node is a folder, we need to remove its' cache with its' children cache as well - invalidateFolderCacheByNodeRef(nodeRef, true); - } - else if (ContentModel.TYPE_CONTENT.equals(nodeService.getType(nodeRef))) - { - // If a node is a content, it is simpler to remove its' parent cache - // to avoid of deal with folder messages cache - invalidateFolderCacheByNodeRef(parentNodeRef, false); - } - else - { - return; - } - // Add a listener once, when a lots of messsages were created/moved into the folder - if (AlfrescoTransactionSupport.getResource(UIDVALIDITY_LISTENER_ALREADY_BOUND) == null) - { - AlfrescoTransactionSupport.bindListener(new UidValidityTransactionListener(parentNodeRef, nodeService)); - AlfrescoTransactionSupport.bindResource(UIDVALIDITY_LISTENER_ALREADY_BOUND, true); - } - if (logger.isDebugEnabled()) - { - logger.debug("[beforeDeleteNode] Node " + nodeRef + " going to be removed. UIDVALIDITY will be changed for " + parentNodeRef); + NodeRef folderRef = parentAssoc.getParentRef(); + if (this.nodeService.hasAspect(folderRef, ImapModel.ASPECT_IMAP_FOLDER)) + { + this.messageCache.remove(nodeRef); + + // Force generation of a new change token + getUidValidityTransactionListener(folderRef); + } } } - + private class UidValidityTransactionListener extends TransactionListenerAdapter { - - private RunAsWork work; - - UidValidityTransactionListener(NodeRef folderNodeRef, NodeService nodeService) - { - this.work = new IncrementUidValidityWork(folderNodeRef, nodeService); - } - - @Override - public void afterCommit() - { - AuthenticationUtil.runAs(this.work, AuthenticationUtil.getSystemUserName()); - } - - } - - private class IncrementUidValidityWork implements RunAsWork - { + // Generate a unique token for each folder change with which we can validate session caches + private String changeToken = GUID.generate(); private NodeService nodeService; private NodeRef folderNodeRef; + private Long minUid; + private Long maxUid; - public IncrementUidValidityWork(NodeRef folderNodeRef, NodeService nodeService) + public UidValidityTransactionListener(NodeRef folderNodeRef, NodeService nodeService) { this.folderNodeRef = folderNodeRef; this.nodeService = nodeService; } + + public void recordNewUid(long newUid) + { + if (this.minUid == null) + { + this.minUid = this.maxUid = newUid; + } + else if (newUid < this.minUid) + { + this.minUid = newUid; + } + else if (newUid > this.maxUid) + { + this.maxUid = newUid; + } + } @Override - public Long doWork() throws Exception + public void beforeCommit(boolean readOnly) { - RetryingTransactionHelper txnHelper = serviceRegistry.getRetryingTransactionHelper(); - return txnHelper.doInTransaction(new RetryingTransactionCallback(){ + if (readOnly) + { + return; + } + AuthenticationUtil.runAs(new RunAsWork() + { @Override - public Long execute() throws Throwable + public Void doWork() throws Exception { - long modifDate = new Date().getTime(); - - if (!IncrementUidValidityWork.this.nodeService.hasAspect(folderNodeRef, ImapModel.ASPECT_IMAP_FOLDER)) + if (UidValidityTransactionListener.this.minUid != null) { - Map aspectProperties = new HashMap(1, 1); - aspectProperties.put(ImapModel.PROP_UIDVALIDITY, modifDate); - IncrementUidValidityWork.this.nodeService.addAspect(folderNodeRef, ImapModel.ASPECT_IMAP_FOLDER, aspectProperties); + long modifDate = System.currentTimeMillis(); + Long oldMax = (Long)UidValidityTransactionListener.this.nodeService.getProperty(folderNodeRef, ImapModel.PROP_MAXUID); + // Only update UIDVALIDITY if a new node has and ID that is smaller than the old maximum (as UIDs are always meant to increase) + if (oldMax == null || UidValidityTransactionListener.this.minUid < oldMax) + { + UidValidityTransactionListener.this.nodeService.setProperty(folderNodeRef, ImapModel.PROP_UIDVALIDITY, modifDate); + if (logger.isDebugEnabled()) + { + logger.debug("UIDVALIDITY was modified for " + folderNodeRef); + } + } + UidValidityTransactionListener.this.nodeService.setProperty(folderNodeRef, ImapModel.PROP_MAXUID, UidValidityTransactionListener.this.maxUid); + if (logger.isDebugEnabled()) + { + logger.debug("MAXUID was modified for " + folderNodeRef); + } } - else - { - IncrementUidValidityWork.this.nodeService.setProperty(folderNodeRef, ImapModel.PROP_UIDVALIDITY, modifDate); - } - if (logger.isDebugEnabled()) - { - logger.debug("UIDVALIDITY was modified for " + folderNodeRef); - } - return modifDate; - } - - }, false, true); + UidValidityTransactionListener.this.nodeService.setProperty(folderNodeRef, ImapModel.PROP_CHANGE_TOKEN, changeToken); + return null; + } + }, AuthenticationUtil.getSystemUserName()); } + } - } - - - private void invalidateFolderCacheByNodeRef(NodeRef folderNodeRef, boolean invalidateChildren) - { - if (logger.isDebugEnabled()) - { - if (invalidateChildren) - { - logger.debug("[invalidateFolderCacheByNodeRef] Invalidate cache entries for " + folderNodeRef); - } - else - { - logger.debug("[invalidateFolderCacheByNodeRef] Invalidate cache entries for " + folderNodeRef + " and children"); - } - } - - if (invalidateChildren) - { - SimpleCache foldersCache = getFoldersCache(); - List toRemove = new LinkedList(); - for(Serializable name : foldersCache.getKeys()) - { - AlfrescoImapFolder folder = (AlfrescoImapFolder) foldersCache.get(name); - if (folder == null || folderNodeRef.equals(folder.getFolderInfo().getNodeRef())) - { - toRemove.add(name); - break; - } - } - if (toRemove.size() > 0) - { - String rootName = (String) toRemove.get(0); - for(Serializable name : foldersCache.getKeys()) - { - if (((String) name).startsWith(rootName)) - { - toRemove.add(name); - } - } - if (logger.isDebugEnabled()) - { - logger.debug("Caches to invalidate: " + toRemove.toString()); - } - for(Serializable name : toRemove) - { - foldersCache.remove(name); - } - } - } - else - { - SimpleCache foldersCache = getFoldersCache(); - for(Serializable name : foldersCache.getKeys()) - { - AlfrescoImapFolder folder = (AlfrescoImapFolder) foldersCache.get(name); - if (folder == null || folderNodeRef.equals(folder.getFolderInfo().getNodeRef())) - { - foldersCache.remove(name); - break; - } - } - } - } - /** * Return true if provided nodeRef is in Sites/.../documentlibrary */ @@ -2536,4 +1865,36 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol } } } + + static class CacheItem + { + private Date modified; + private SimpleStoredMessage message; + + public CacheItem(Date modified, SimpleStoredMessage message) + { + this.setMessage(message); + this.setModified(modified); + } + + public void setModified(Date modified) + { + this.modified = modified; + } + + public Date getModified() + { + return modified; + } + + public void setMessage(SimpleStoredMessage message) + { + this.message = message; + } + + public SimpleStoredMessage getMessage() + { + return message; + } + } } diff --git a/source/java/org/alfresco/repo/imap/ImapServiceImplCacheTest.java b/source/java/org/alfresco/repo/imap/ImapServiceImplCacheTest.java index c82b21c60b..5bcf42cbbb 100644 --- a/source/java/org/alfresco/repo/imap/ImapServiceImplCacheTest.java +++ b/source/java/org/alfresco/repo/imap/ImapServiceImplCacheTest.java @@ -10,6 +10,7 @@ import junit.framework.TestCase; import org.alfresco.model.ContentModel; import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory; import org.alfresco.repo.model.filefolder.FileFolderServiceImpl; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; @@ -21,6 +22,7 @@ import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.MutableAuthenticationService; import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.config.RepositoryFolderConfigBean; import org.springframework.context.ApplicationContext; @@ -47,6 +49,8 @@ public class ImapServiceImplCacheTest extends TestCase private NamespaceService namespaceService; private FileFolderService fileFolderService; private ContentService contentService; + private TransactionService transactionService; + private FileInfo oldFile; private ImapService imapService; @@ -63,6 +67,7 @@ public class ImapServiceImplCacheTest extends TestCase namespaceService = serviceRegistry.getNamespaceService(); fileFolderService = serviceRegistry.getFileFolderService(); contentService = serviceRegistry.getContentService(); + transactionService = serviceRegistry.getTransactionService(); authenticationService.authenticate(USER_NAME, USER_PASSWORD.toCharArray()); @@ -78,12 +83,13 @@ public class ImapServiceImplCacheTest extends TestCase ChildApplicationContextFactory imap = (ChildApplicationContextFactory) ctx.getBean("imap"); ApplicationContext imapCtx = imap.getApplicationContext(); - ImapServiceImpl imapServiceImpl = (ImapServiceImpl)imapCtx.getBean("imapService"); + final ImapServiceImpl imapServiceImpl = (ImapServiceImpl)imapCtx.getBean("imapService"); // Creating IMAP test folder for IMAP root LinkedList folders = new LinkedList(); folders.add(TEST_IMAP_FOLDER_NAME); - FileFolderServiceImpl.makeFolders(fileFolderService, companyHomeNodeRef, folders, ContentModel.TYPE_FOLDER); + FileInfo folder = FileFolderServiceImpl.makeFolders(fileFolderService, companyHomeNodeRef, folders, ContentModel.TYPE_FOLDER); + oldFile = fileFolderService.create(folder.getNodeRef(), "oldFile", ContentModel.TYPE_CONTENT); // Setting IMAP root RepositoryFolderConfigBean imapHome = new RepositoryFolderConfigBean(); @@ -93,7 +99,15 @@ public class ImapServiceImplCacheTest extends TestCase imapServiceImpl.setImapHome(imapHome); // Starting IMAP - imapServiceImpl.startup(); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + imapServiceImpl.startup(); + return null; + } + }); nodeRefs = searchService.selectNodes(storeRootNodeRef, companyHomePathInStore + "/" + NamespaceService.CONTENT_MODEL_PREFIX + ":" + TEST_IMAP_FOLDER_NAME, @@ -136,31 +150,33 @@ public class ImapServiceImplCacheTest extends TestCase // Create content within 'Alfresco IMAP/aaa/ALF9361' createTestContent(localRootFolder, contentItemsCount); // Load the cache - imapService.listMailboxes(localUser, "*"); - imapService.listSubscribedMailboxes(localUser, "*"); + imapService.listMailboxes(localUser, "*", false); + imapService.listMailboxes(localUser, "*", true); // Get the folder to examine - AlfrescoImapFolder folder = imapService.getFolder(localUser, mailbox); + AlfrescoImapFolder folder = imapService.getOrCreateMailbox(localUser, mailbox, true, false); // Check the folder exist via IMAP assertNotNull("Folder wasn't successfully gotten from IMAP", folder); assertEquals(contentItemsCount, folder.getMessageCount()); // Check UIDVALIDITY long uidValidityBefore = folder.getUidValidity(); + // Move in an old file with a smaller UID + fileFolderService.move(oldFile.getNodeRef(), folder.getFolderInfo().getNodeRef(), folder.getName()); + // Get the folder once more and check it was changed since an old child was moved in + folder = imapService.getOrCreateMailbox(localUser, mailbox, true, false); + // Content count should be increased + assertEquals(++contentItemsCount, folder.getMessageCount()); + long uidValidity = folder.getUidValidity(); + assertTrue("UIDVALIDITY wasn't incremented", (uidValidity - uidValidityBefore) > 0); // Delete first childMailbox 'ALF9361/ALF9361_0' //System.out.println(" --------------------- DELETE FOLDER --------------------"); //System.out.println(" Parent " + localRootFolder.getNodeRef()); fileFolderService.delete(subFolders.get(0).getNodeRef()); - // Get the folder once more and check it was changed since child was removed - folder = imapService.getFolder(localUser, mailbox); - // Content count should be the same since we havn't deleted a content yet - assertEquals(contentItemsCount, folder.getMessageCount()); - long uidValidity = folder.getUidValidity(); - assertTrue("UIDVALIDITY wasn't incremented", (uidValidity - uidValidityBefore) > 0); uidValidityBefore = uidValidity; // Try to get deleted child try { String subFolderName = mailbox + AlfrescoImapConst.HIERARCHY_DELIMITER + folderName + "_0"; - folder = imapService.getFolder(localUser, subFolderName); + folder = imapService.getOrCreateMailbox(localUser, subFolderName, true, false); fail("The folder still in the cache"); } catch (RuntimeException e) @@ -173,7 +189,7 @@ public class ImapServiceImplCacheTest extends TestCase try { String subSubFolderName = mailbox + AlfrescoImapConst.HIERARCHY_DELIMITER + mailbox + "_0" + AlfrescoImapConst.HIERARCHY_DELIMITER + "sub_0"; - folder = imapService.getFolder(localUser, subSubFolderName); + folder = imapService.getOrCreateMailbox(localUser, subSubFolderName, true, false); fail("The folder still in the cache"); } catch (RuntimeException e) @@ -181,7 +197,7 @@ public class ImapServiceImplCacheTest extends TestCase // expected } // Do manipulations with a content in the folder to check the cache behaviour - folder = imapService.getFolder(localUser, mailbox); + folder = imapService.getOrCreateMailbox(localUser, mailbox, true, false); SimpleStoredMessage message = folder.getMessages().get(0); AbstractMimeMessage alfrescoMessage = (AbstractMimeMessage) message.getMimeMessage(); long uid = message.getUid(); @@ -191,10 +207,7 @@ public class ImapServiceImplCacheTest extends TestCase fileFolderService.delete(alfrescoMessage.getMessageInfo().getNodeRef()); // Get a folder once again. We expect that the folder would be retrieved from the repo, // since its' cache should be invalidated - folder = imapService.getFolder(localUser, mailbox); - // Get UIDVALIDITY. It should be changed, since we removed a message form the mailbox. - uidValidity = folder.getUidValidity(); - assertTrue("UIDVALIDITY wasn't incremented", (uidValidity - uidValidityBefore) > 0); + folder = imapService.getOrCreateMailbox(localUser, mailbox, true, false); // Additional check whether messages cache is valid. Messages cache should be recreated //with the new inctance of AlfrescoImapMessage assertTrue("Messages cache is stale", contentItemsCount > folder.getMessageCount()); diff --git a/source/java/org/alfresco/repo/imap/ImapServiceImplTest.java b/source/java/org/alfresco/repo/imap/ImapServiceImplTest.java index 32e47ba5cc..e0c4369141 100644 --- a/source/java/org/alfresco/repo/imap/ImapServiceImplTest.java +++ b/source/java/org/alfresco/repo/imap/ImapServiceImplTest.java @@ -25,6 +25,7 @@ import java.io.Serializable; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.NavigableMap; import java.util.Properties; import javax.mail.Flags; @@ -229,9 +230,11 @@ public class ImapServiceImplTest extends TestCase private boolean checkMailbox(AlfrescoImapUser user, String mailboxName) { - AlfrescoImapFolder mailFolder = (AlfrescoImapFolder)imapService.getFolder(user, mailboxName); - - if (mailFolder.getFolderInfo() == null) + try + { + imapService.getOrCreateMailbox(user, mailboxName, true, false); + } + catch (AlfrescoRuntimeException e) { return false; } @@ -240,7 +243,7 @@ public class ImapServiceImplTest extends TestCase private boolean checkSubscribedMailbox(AlfrescoImapUser user, String mailboxName) { - List aifs = imapService.listSubscribedMailboxes(user, mailboxName); + List aifs = imapService.listMailboxes(user, mailboxName, true); boolean present = false; for (AlfrescoImapFolder aif : aifs) { @@ -262,15 +265,15 @@ public class ImapServiceImplTest extends TestCase public void testGetFolder() throws Exception { - imapService.createMailbox(user, MAILBOX_NAME_A); + imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true); assertTrue(checkMailbox(user, MAILBOX_NAME_A)); } public void testListMailbox() throws Exception { - imapService.createMailbox(user, MAILBOX_NAME_A); - imapService.createMailbox(user, MAILBOX_NAME_B); - List mf = imapService.listMailboxes(user, MAILBOX_PATTERN); + imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true); + imapService.getOrCreateMailbox(user, MAILBOX_NAME_B, false, true); + List mf = imapService.listMailboxes(user, MAILBOX_PATTERN, false); assertEquals(2, mf.size()); boolean foundA = false; @@ -291,33 +294,33 @@ public class ImapServiceImplTest extends TestCase assertTrue("folder A found", foundA); assertTrue("folder B found", foundB); - mf = imapService.listMailboxes(user, MAILBOX_PATTERN); + mf = imapService.listMailboxes(user, MAILBOX_PATTERN, false); assertEquals("can't repeat the listing of folders", 2, mf.size()); - mf = imapService.listMailboxes(user, MAILBOX_PATTERN); + mf = imapService.listMailboxes(user, MAILBOX_PATTERN, false); assertEquals("can't repeat the listing of folders", 2, mf.size()); /** * The new mailboxes should be subscribed? */ - List aif = imapService.listSubscribedMailboxes(user, MAILBOX_PATTERN); + List aif = imapService.listMailboxes(user, MAILBOX_PATTERN, true); assertEquals("not subscribed to two mailboxes", 2, aif.size()); /** * Unsubscribe to one of the mailboxes. */ imapService.unsubscribe(user, MAILBOX_NAME_B); - List aif2 = imapService.listSubscribedMailboxes(user, MAILBOX_PATTERN); + List aif2 = imapService.listMailboxes(user, MAILBOX_PATTERN, true); assertEquals("not subscribed to one mailbox", 1, aif2.size()); } public void testListSubscribedMailbox() throws Exception { - imapService.createMailbox(user, MAILBOX_NAME_A); - imapService.createMailbox(user, MAILBOX_NAME_B); + imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true); + imapService.getOrCreateMailbox(user, MAILBOX_NAME_B, false, true); imapService.subscribe(user, MAILBOX_NAME_A); imapService.subscribe(user, MAILBOX_NAME_B); - List aif = imapService.listSubscribedMailboxes(user, MAILBOX_PATTERN); + List aif = imapService.listMailboxes(user, MAILBOX_PATTERN, true); assertEquals(aif.size(), 2); assertTrue("Can't subscribe mailbox A", checkSubscribedMailbox(user, MAILBOX_NAME_A)); @@ -326,16 +329,16 @@ public class ImapServiceImplTest extends TestCase public void testCreateMailbox() throws Exception { - imapService.createMailbox(user, MAILBOX_NAME_A); + imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true); assertTrue("Mailbox isn't created", checkMailbox(user, MAILBOX_NAME_A)); } public void testDuplicateMailboxes() throws Exception { - imapService.createMailbox(user, MAILBOX_NAME_A); + imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true); try { - imapService.createMailbox(user, MAILBOX_NAME_A); + imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true); fail("Duplicate Mailbox was created"); } catch (AlfrescoRuntimeException e) @@ -347,7 +350,7 @@ public class ImapServiceImplTest extends TestCase public void testRenameMailbox() throws Exception { - imapService.createMailbox(user, MAILBOX_NAME_A); + imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true); imapService.renameMailbox(user, MAILBOX_NAME_A, MAILBOX_NAME_B); assertFalse("Can't rename mailbox", checkMailbox(user, MAILBOX_NAME_A)); assertTrue("Can't rename mailbox", checkMailbox(user, MAILBOX_NAME_B)); @@ -355,8 +358,8 @@ public class ImapServiceImplTest extends TestCase public void testRenameMailboxDuplicate() throws Exception { - imapService.createMailbox(user, MAILBOX_NAME_A); - imapService.createMailbox(user, MAILBOX_NAME_B); + imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true); + imapService.getOrCreateMailbox(user, MAILBOX_NAME_B, false, true); try { imapService.renameMailbox(user, MAILBOX_NAME_A, MAILBOX_NAME_B); @@ -370,7 +373,7 @@ public class ImapServiceImplTest extends TestCase public void testDeleteMailbox() throws Exception { - imapService.createMailbox(user, MAILBOX_NAME_B); + imapService.getOrCreateMailbox(user, MAILBOX_NAME_B, false, true); imapService.deleteMailbox(user, MAILBOX_NAME_B); assertFalse("Can't delete mailbox", checkMailbox(user, MAILBOX_NAME_B)); } @@ -424,7 +427,7 @@ public class ImapServiceImplTest extends TestCase public void testSubscribe() throws Exception { - imapService.createMailbox(user, MAILBOX_NAME_A); + imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true); imapService.subscribe(user, MAILBOX_NAME_A); assertTrue("Can't subscribe mailbox", checkSubscribedMailbox(user, MAILBOX_NAME_A)); @@ -432,7 +435,7 @@ public class ImapServiceImplTest extends TestCase public void testUnsubscribe() throws Exception { - imapService.createMailbox(user, MAILBOX_NAME_A); + imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true); imapService.subscribe(user, MAILBOX_NAME_A); imapService.unsubscribe(user, MAILBOX_NAME_A); // TODO MER 21/05/2010 : line below looks like a bug to me. @@ -453,10 +456,10 @@ public class ImapServiceImplTest extends TestCase public void testSetFlags() throws Exception { - List fis = imapService.searchMails(testImapFolderNodeRef, ImapViewMode.ARCHIVE); + NavigableMap fis = imapService.getFolderStatus(authenticationService.getCurrentUserName(), testImapFolderNodeRef, ImapViewMode.ARCHIVE).search; if (fis != null && fis.size() > 0) { - FileInfo messageFileInfo = fis.get(0); + FileInfo messageFileInfo = fis.firstEntry().getValue(); try { setFlags(messageFileInfo); @@ -486,10 +489,10 @@ public class ImapServiceImplTest extends TestCase public void testSetFlag() throws Exception { - List fis = imapService.searchMails(testImapFolderNodeRef, ImapViewMode.ARCHIVE); + NavigableMap fis = imapService.getFolderStatus(authenticationService.getCurrentUserName(), testImapFolderNodeRef, ImapViewMode.ARCHIVE).search; if (fis != null && fis.size() > 0) { - FileInfo messageFileInfo = fis.get(0); + FileInfo messageFileInfo = fis.firstEntry().getValue(); reauthenticate(USER_NAME, USER_PASSWORD); @@ -506,10 +509,10 @@ public class ImapServiceImplTest extends TestCase public void testGetFlags() throws Exception { - List fis = imapService.searchMails(testImapFolderNodeRef, ImapViewMode.ARCHIVE); + NavigableMap fis = imapService.getFolderStatus(authenticationService.getCurrentUserName(), testImapFolderNodeRef, ImapViewMode.ARCHIVE).search; if (fis != null && fis.size() > 0) { - FileInfo messageFileInfo = fis.get(0); + FileInfo messageFileInfo = fis.firstEntry().getValue(); reauthenticate(USER_NAME, USER_PASSWORD); @@ -526,13 +529,13 @@ public class ImapServiceImplTest extends TestCase public void testRenameAccentedMailbox() throws Exception { - String MAILBOX_ACCENTED_NAME_A = "H�tel"; - String MAILBOX_ACCENTED_NAME_B = "H�telXX"; + String MAILBOX_ACCENTED_NAME_A = "Hôtel"; + String MAILBOX_ACCENTED_NAME_B = "HôtelXX"; - imapService.createMailbox(user, MAILBOX_ACCENTED_NAME_A); + imapService.getOrCreateMailbox(user, MAILBOX_ACCENTED_NAME_A, false, true); imapService.deleteMailbox(user, MAILBOX_ACCENTED_NAME_A); - imapService.createMailbox(user, MAILBOX_ACCENTED_NAME_A); + imapService.getOrCreateMailbox(user, MAILBOX_ACCENTED_NAME_A, false, true); imapService.renameMailbox(user, MAILBOX_ACCENTED_NAME_A, MAILBOX_ACCENTED_NAME_B); assertFalse("Can't rename mailbox", checkMailbox(user, MAILBOX_ACCENTED_NAME_A)); assertTrue("Can't rename mailbox", checkMailbox(user, MAILBOX_ACCENTED_NAME_B)); diff --git a/source/java/org/alfresco/repo/imap/LoadTester.java b/source/java/org/alfresco/repo/imap/LoadTester.java index 75e295b0c2..eda81042d5 100644 --- a/source/java/org/alfresco/repo/imap/LoadTester.java +++ b/source/java/org/alfresco/repo/imap/LoadTester.java @@ -172,19 +172,19 @@ public class LoadTester extends TestCase false); // Used to create User's folder - NodeRef userFolderRef = imapService.getMailboxRootRef(TEST_DATA_FOLDER_NAME, anotherUserName); + NodeRef userFolderRef = imapService.getUserImapHomeRef(anotherUserName); permissionService.setPermission(userFolderRef, anotherUserName, PermissionService.ALL_PERMISSIONS, true); importTestData("imap/load_test_data.acp", userFolderRef); reauthenticate(anotherUserName, anotherUserName); - AlfrescoImapFolder testDataFolder = imapService.getFolder(user, TEST_DATA_FOLDER_NAME); + AlfrescoImapFolder testDataFolder = imapService.getOrCreateMailbox(user, TEST_DATA_FOLDER_NAME, true, false); SimpleStoredMessage m = testDataFolder.getMessages().get(0); m = testDataFolder.getMessage(m.getUid()); - AlfrescoImapFolder folder = imapService.createMailbox(user, TEST_FOLDER_NAME); + AlfrescoImapFolder folder = imapService.getOrCreateMailbox(user, TEST_FOLDER_NAME, false, true); logger.info("Creating folders..."); long t = System.currentTimeMillis(); @@ -227,7 +227,7 @@ public class LoadTester extends TestCase logger.info("Listing folders..."); long t = System.currentTimeMillis(); - List list = imapService.listMailboxes(user, TEST_FOLDER_NAME + "*"); + List list = imapService.listMailboxes(user, TEST_FOLDER_NAME + "*", false); t = System.currentTimeMillis() - t; logger.info("List time: " + t + " ms (" + t/1000 + " s)"); diff --git a/source/java/org/alfresco/repo/imap/RemoteLoadTester.java b/source/java/org/alfresco/repo/imap/RemoteLoadTester.java index eff32e3c8b..c4ae236d1a 100644 --- a/source/java/org/alfresco/repo/imap/RemoteLoadTester.java +++ b/source/java/org/alfresco/repo/imap/RemoteLoadTester.java @@ -27,6 +27,7 @@ import javax.mail.BodyPart; import javax.mail.Folder; import javax.mail.Message; import javax.mail.MessagingException; +import javax.mail.NoSuchProviderException; import javax.mail.Part; import javax.mail.Session; import javax.mail.Store; @@ -46,6 +47,16 @@ public class RemoteLoadTester extends TestCase private static final String USER_NAME = "test_imap_user"; private static final String USER_PASSWORD = "test_imap_user"; private static final String TEST_FOLDER_NAME = "test_imap1000"; + + private static final String ADMIN_USER_NAME = "admin"; + private static String REMOTE_HOST = "127.0.0.1"; + + public static void main(String[] args) + { + if (args.length > 0) + REMOTE_HOST = args[0]; + new RemoteLoadTester().testListSequence(); + } @Override public void setUp() throws Exception @@ -54,17 +65,75 @@ public class RemoteLoadTester extends TestCase public void tearDown() throws Exception { - } + public void testListSequence() + { + System.out.println(String.format("Connecting to remote server '%s'", REMOTE_HOST)); + Properties props = System.getProperties(); + props.setProperty("mail.imap.partialfetch", "false"); + Session session = Session.getDefaultInstance(props, null); + + Store store = null; + long startTime = 0; + long endTime = 0; + try + { + store = session.getStore("imap"); + store.connect(REMOTE_HOST, ADMIN_USER_NAME, ADMIN_USER_NAME); + Folder[] folders = null; + + startTime = System.currentTimeMillis(); + folders = store.getDefaultFolder().list(""); + endTime = System.currentTimeMillis(); + System.out.println(String.format("LIST '', folders.length = %d, execTime = %d sec", folders.length, (endTime - startTime)/1000)); + + startTime = System.currentTimeMillis(); + folders = store.getDefaultFolder().list("*"); + endTime = System.currentTimeMillis(); + System.out.println(String.format("LIST *, folders.length = %d, execTime = %d sec", folders.length, (endTime - startTime)/1000)); + + startTime = System.currentTimeMillis(); + folders = store.getDefaultFolder().listSubscribed("*"); + endTime = System.currentTimeMillis(); + System.out.println(String.format("LSUB *, folders.length = %d, execTime = %d sec", folders.length, (endTime - startTime)/1000)); + + startTime = System.currentTimeMillis(); + for (Folder folder : folders) + { + folder.getMessageCount(); + //Folder f = store.getFolder(folder.getFullName()); + } + endTime = System.currentTimeMillis(); + System.out.println(String.format("Folders Loop, folders.length = %d, execTime = %d sec", folders.length, (endTime - startTime)/1000)); + + } + catch (NoSuchProviderException e) + { + e.printStackTrace(); + } + catch (MessagingException e) + { + e.printStackTrace(); + } + finally + { + try + { + store.close(); + } + catch (MessagingException e) + { + System.err.println(e.getMessage()); + } + } + } public void testMailbox() { logger.info("Getting folder..."); long t = System.currentTimeMillis(); - String host = "localhost"; - // Create empty properties Properties props = new Properties(); props.setProperty("mail.imap.partialfetch", "false"); @@ -78,7 +147,7 @@ public class RemoteLoadTester extends TestCase { // Get the store store = session.getStore("imap"); - store.connect(host, USER_NAME, USER_PASSWORD); + store.connect(REMOTE_HOST, USER_NAME, USER_PASSWORD); // Get folder folder = store.getFolder(TEST_FOLDER_NAME); diff --git a/source/java/org/alfresco/repo/jscript/Imap.java b/source/java/org/alfresco/repo/jscript/Imap.java index fbafa8bb2b..a425d7cd07 100644 --- a/source/java/org/alfresco/repo/jscript/Imap.java +++ b/source/java/org/alfresco/repo/jscript/Imap.java @@ -22,7 +22,6 @@ import org.alfresco.repo.model.Repository; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; -import org.springframework.extensions.surf.util.ParameterCheck; public final class Imap extends BaseScopableProcessorExtension { @@ -74,13 +73,12 @@ public final class Imap extends BaseScopableProcessorExtension /** * Searches NodeRef to the IMAP home for specified user * - * @param mailboxName the name of the mailbox * @param userName the name of the user */ - public ScriptNode getImapHomeRef(String mailboxName, String userName) + public ScriptNode getImapHomeRef(String userName) { ScriptNode result = null; - NodeRef nodeRef = services.getImapService().getMailboxRootRef(mailboxName, userName); + NodeRef nodeRef = services.getImapService().getUserImapHomeRef(userName); if (nodeRef != null) { result = new ScriptNode(nodeRef, this.services, getScope()); diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java index 484edc3ee5..6067eb2a3a 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java @@ -1321,6 +1321,11 @@ public class FileFolderServiceImpl implements FileFolderService } public FileInfo resolveNamePath(NodeRef rootNodeRef, List pathElements) throws FileNotFoundException + { + return resolveNamePath(rootNodeRef, pathElements, true); + } + + public FileInfo resolveNamePath(NodeRef rootNodeRef, List pathElements, boolean mustExist) throws FileNotFoundException { if (pathElements.size() == 0) { @@ -1347,9 +1352,14 @@ public class FileFolderServiceImpl implements FileFolderService NodeRef fileNodeRef = searchSimple(parentNodeRef, pathElement); if (fileNodeRef == null) { - StringBuilder sb = new StringBuilder(128); - sb.append("File not found: " + currentPath); - throw new FileNotFoundException(sb.toString()); + if (mustExist) + { + throw new FileNotFoundException("File not found: " + currentPath); + } + else + { + return null; + } } FileInfo result = getFileInfo(fileNodeRef); // found it diff --git a/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java b/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java index 1bf5c35bc1..3dcd619dd2 100644 --- a/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java +++ b/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java @@ -440,17 +440,22 @@ public abstract class AbstractReindexComponent implements IndexRecovery // Check if the txn ID is present in every applicable store's index for (Map.Entry> entry : storeStatusMap.entrySet()) { - StoreRef storeRef = entry.getKey(); List storeStatuses = entry.getValue(); + if (storeStatuses.isEmpty()) + { + // Nothing to check + continue; + } + StoreRef storeRef = entry.getKey(); // Establish the number of deletes and updates for this storeRef - int deleteCount = 0; + List deletedNodes = new LinkedList(); int updateCount = 0; for (NodeRef.Status nodeStatus : storeStatuses) { if (nodeStatus.isDeleted()) { - deleteCount++; + deletedNodes.add(nodeStatus.getNodeRef()); } else { @@ -458,6 +463,15 @@ public abstract class AbstractReindexComponent implements IndexRecovery } } + // There were deleted nodes. Check that all the deleted nodes (including containers) were removed from the + // index - otherwise it is out of date. If all nodes have been removed from the index then the result is that the index is OK + // ETWOTWO-1387 + // ALF-1989 - even if the nodes have not been found it is no good to use for AUTO index checking + if (!deletedNodes.isEmpty() && !haveNodesBeenRemovedFromIndex(storeRef, deletedNodes, txn)) + { + result = InIndex.NO; + break; + } if (updateCount > 0) { // Check the index @@ -472,15 +486,6 @@ public abstract class AbstractReindexComponent implements IndexRecovery break; } } - // There were deleted nodes only. Check that all the deleted nodes were removed from the index otherwise it - // is out of date. If all nodes have been removed from the index then the result is that the index is OK - // ETWOTWO-1387 - // ALF-1989 - even if the nodes have not been found it is no good to use for AUTO index checking - else if (deleteCount > 0 && !haveNodesBeenRemovedFromIndex(storeRef, storeStatuses, txn)) - { - result = InIndex.NO; - break; - } } // done @@ -631,14 +636,13 @@ public abstract class AbstractReindexComponent implements IndexRecovery } } - private boolean haveNodesBeenRemovedFromIndex(final StoreRef storeRef, List nodeStatuses, final Transaction txn) + private boolean haveNodesBeenRemovedFromIndex(final StoreRef storeRef, List nodeRefs, final Transaction txn) { final Long txnId = txn.getId(); // there have been deletes, so we have to ensure that none of the nodes deleted are present in the index boolean foundNodeRef = false; - for (NodeRef.Status nodeStatus : nodeStatuses) + for (NodeRef nodeRef : nodeRefs) { - NodeRef nodeRef = nodeStatus.getNodeRef(); if (logger.isTraceEnabled()) { logger.trace( diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java index 87e426be9a..6f9be1abc6 100644 --- a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java +++ b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java @@ -20,13 +20,16 @@ package org.alfresco.repo.security.authority; import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; @@ -122,8 +125,15 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor /** The number of authorities in a zone to pre-cache, allowing quick generation of 'first n' results. */ private int zoneAuthoritySampleSize = 10000; - + private NamedObjectRegistry> cannedQueryRegistry; + + private static final Collection SEARCHABLE_AUTHORITY_TYPES = new LinkedList(); + static + { + SEARCHABLE_AUTHORITY_TYPES.add(AuthorityType.ROLE); + SEARCHABLE_AUTHORITY_TYPES.add(AuthorityType.GROUP); + } public AuthorityDAOImpl() { @@ -541,19 +551,38 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor query.append("TYPE:\"").append(ContentModel.TYPE_AUTHORITY_CONTAINER).append("\""); if (displayNamePattern != null) { - query.append(" AND (@").append( - AbstractLuceneQueryParser.escape("{" + ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI() + "}" - + ISO9075.encode(ContentModel.PROP_AUTHORITY_NAME.getLocalName()))).append(":\""); - // Allow for the appropriate type prefix in the authority name - if (type == null && !displayNamePattern.startsWith("*")) + query.append(" AND ("); + if (!displayNamePattern.startsWith("*")) { - query.append("*").append(AbstractLuceneQueryParser.escape(displayNamePattern)); + // Allow for the appropriate type prefix in the authority name + Collection authorityTypes = type == null ? SEARCHABLE_AUTHORITY_TYPES + : Collections.singleton(type); + boolean first = true; + for (AuthorityType subType: authorityTypes) + { + if (first) + { + first = false; + } + else + { + query.append(" OR "); + } + query.append("@").append( + AbstractLuceneQueryParser.escape("{" + ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI() + "}" + + ISO9075.encode(ContentModel.PROP_AUTHORITY_NAME.getLocalName()))).append(":\""); + query.append(getName(subType, AbstractLuceneQueryParser.escape(displayNamePattern))).append("\""); + + } } else { + query.append("@").append( + AbstractLuceneQueryParser.escape("{" + ContentModel.PROP_AUTHORITY_NAME.getNamespaceURI() + "}" + + ISO9075.encode(ContentModel.PROP_AUTHORITY_NAME.getLocalName()))).append(":\""); query.append(getName(type, AbstractLuceneQueryParser.escape(displayNamePattern))); } - query.append("\" OR @").append( + query.append(" OR @").append( AbstractLuceneQueryParser.escape("{" + ContentModel.PROP_AUTHORITY_DISPLAY_NAME.getNamespaceURI() + "}" + ISO9075.encode(ContentModel.PROP_AUTHORITY_DISPLAY_NAME.getLocalName()))).append( ":\"").append(AbstractLuceneQueryParser.escape(displayNamePattern)).append("\")"); @@ -608,9 +637,8 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor for (ResultSetRow row : rs) { NodeRef nodeRef = row.getNodeRef(); - QName idProp = type != AuthorityType.USER - || dictionaryService.isSubClass(nodeService.getType(nodeRef), - ContentModel.TYPE_AUTHORITY_CONTAINER) ? ContentModel.PROP_AUTHORITY_NAME + QName idProp = dictionaryService.isSubClass(nodeService.getType(nodeRef), + ContentModel.TYPE_AUTHORITY_CONTAINER) ? ContentModel.PROP_AUTHORITY_NAME : ContentModel.PROP_USERNAME; addAuthorityNameIfMatches(authorities, DefaultTypeConverter.INSTANCE.convert(String.class, nodeService .getProperty(nodeRef, idProp)), type, pattern); diff --git a/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService.java b/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService.java index 57fd9442e1..b8c271cdf3 100644 --- a/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService.java +++ b/source/java/org/alfresco/repo/security/authority/script/ScriptAuthorityService.java @@ -415,12 +415,9 @@ public class ScriptAuthorityService extends BaseScopableProcessorExtension { String filter = shortNameFilter; - /** - * Modify shortNameFilter to be "shortName*" - */ if (shortNameFilter.length() != 0) { - filter = filter.replace("\"", "") + "*"; + filter = filter.replace("\"", ""); } Set authorities; diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java index 2340e84bb3..9c6023bde8 100644 --- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java +++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java @@ -415,7 +415,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per /** * {@inheritDoc} */ - public NodeRef getPerson(final String userName, final boolean autoCreate) + public NodeRef getPerson(final String userName, final boolean autoCreateHomeFolderAndMissingPersonIfAllowed) { // MT share - for activity service system callback if (tenantService.isEnabled() && (AuthenticationUtil.SYSTEM_USER_NAME.equals(AuthenticationUtil.getRunAsUser())) && tenantService.isTenantUser(userName)) @@ -426,17 +426,17 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per { public NodeRef doWork() throws Exception { - return getPersonImpl(userName, autoCreate); + return getPersonImpl(userName, autoCreateHomeFolderAndMissingPersonIfAllowed); } }, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain)); } else { - return getPersonImpl(userName, autoCreate); + return getPersonImpl(userName, autoCreateHomeFolderAndMissingPersonIfAllowed); } } - private NodeRef getPersonImpl(String userName, boolean autoCreate) + private NodeRef getPersonImpl(String userName, boolean autoCreateHomeFolderAndMissingPersonIfAllowed) { if(userName == null) { @@ -450,17 +450,18 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per if (personNode == null) { TxnReadState txnReadState = AlfrescoTransactionSupport.getTransactionReadState(); - if (autoCreate && createMissingPeople() && txnReadState == TxnReadState.TXN_READ_WRITE) + if (autoCreateHomeFolderAndMissingPersonIfAllowed && createMissingPeople() && + txnReadState == TxnReadState.TXN_READ_WRITE) { // We create missing people AND are in a read-write txn - return createMissingPerson(userName); + return createMissingPerson(userName, true); } else { throw new NoSuchPersonException(userName); } } - else if (autoCreate) + else if (autoCreateHomeFolderAndMissingPersonIfAllowed) { makeHomeFolderIfRequired(personNode); } @@ -749,14 +750,14 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per /** * {@inheritDoc} */ - public void setPersonProperties(String userName, Map properties, boolean autoCreate) + public void setPersonProperties(String userName, Map properties, boolean autoCreateHomeFolder) { NodeRef personNode = getPersonOrNull(userName); if (personNode == null) { if (createMissingPeople()) { - personNode = createMissingPerson(userName); + personNode = createMissingPerson(userName, autoCreateHomeFolder); } else { @@ -765,7 +766,8 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per } else { - if (autoCreate) + // Must create the home folder first as a property holds its location. + if (autoCreateHomeFolder) { makeHomeFolderIfRequired(personNode); } @@ -794,10 +796,18 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per return true; } - private NodeRef createMissingPerson(String userName) + private NodeRef createMissingPerson(String userName, boolean autoCreateHomeFolder) { HashMap properties = getDefaultProperties(userName); NodeRef person = createPerson(properties); + + // The home folder will ONLY exist after the the person is created if + // homeFolderCreationEager == true + if (autoCreateHomeFolder && homeFolderCreationEager == false) + { + makeHomeFolderIfRequired(person); + } + return person; } diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java index 8c4f7fffcb..5752903fbe 100644 --- a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java +++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java @@ -1420,7 +1420,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl zones); ChainingUserRegistrySynchronizer.this.authorityService.addAuthorityToZones(personName, zoneSet); ChainingUserRegistrySynchronizer.this.personService.setPersonProperties(personName, - personProperties); + personProperties, false); } else { diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java index 0b521f7803..3da10f46d9 100644 --- a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java +++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java @@ -39,10 +39,12 @@ import junit.framework.TestCase; import org.alfresco.model.ContentModel; import org.alfresco.repo.management.subsystems.ChildApplicationContextManager; import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.person.PersonServiceImpl; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.cmr.security.PersonService; @@ -91,6 +93,9 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase /** The retrying transaction helper. */ private RetryingTransactionHelper retryingTransactionHelper; + + /** The value given to the person service. */ + private boolean homeFolderCreationEager; /* * (non-Javadoc) @@ -114,6 +119,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase this.retryingTransactionHelper = (RetryingTransactionHelper) ChainingUserRegistrySynchronizerTest.context .getBean("retryingTransactionHelper"); + setHomeFolderCreationEager(false); // the normal default if using LDAP } /* @@ -124,6 +130,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase protected void tearDown() throws Exception { this.authenticationContext.clearCurrentSecurityContext(); + setHomeFolderCreationEager(true); // the normal default if not using LDAP } /** @@ -231,6 +238,12 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase }, false, true); } + public void setHomeFolderCreationEager(boolean homeFolderCreationEager) + { + this.homeFolderCreationEager = homeFolderCreationEager; + ((PersonServiceImpl)personService).setHomeFolderCreationEager(homeFolderCreationEager); + } + /** * Tests a differential update of the test users and groups. The layout is as follows * @@ -438,6 +451,24 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase tearDownTestUsersAndGroups(); } + + public void testDifferentialUpdateWithHomeFolderCreation() throws Exception + { + setHomeFolderCreationEager(!homeFolderCreationEager); + testDifferentialUpdate(); + } + + public void testForcedUpdateWithHomeFolderCreation() throws Exception + { + setHomeFolderCreationEager(!homeFolderCreationEager); + testDifferentialUpdate(); + } + + public void testCaseChangeWithHomeFolderCreation() throws Exception + { + setHomeFolderCreationEager(!homeFolderCreationEager); + testDifferentialUpdate(); + } /** * Tests synchronization with a zone with a larger volume of authorities. @@ -592,6 +623,21 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase // Check case matches assertEquals(this.personService.getUserIdentifier(name), name); + + // Check the person exist. + NodeRef person = personService.getPerson(name, false); + assertNotNull("Person for "+name+" should exist", person); + + // Check the home folder exists or not. + NodeRef homeFolder = DefaultTypeConverter.INSTANCE.convert(NodeRef.class, nodeService.getProperty(person, ContentModel.PROP_HOMEFOLDER)); + if (homeFolderCreationEager) + { + assertNotNull("Home folder for "+name+" should exist", homeFolder); + } + else + { + assertNull("Home folder for "+name+" should not exist", homeFolder); + } } } @@ -626,7 +672,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase */ private void assertEmailEquals(String personName, String email) { - NodeRef personRef = this.personService.getPerson(personName); + NodeRef personRef = this.personService.getPerson(personName, false); assertEquals(email, this.nodeService.getProperty(personRef, ContentModel.PROP_EMAIL)); } diff --git a/source/java/org/alfresco/service/cmr/model/FileFolderService.java b/source/java/org/alfresco/service/cmr/model/FileFolderService.java index 32c518adfe..cf0173e8e9 100644 --- a/source/java/org/alfresco/service/cmr/model/FileFolderService.java +++ b/source/java/org/alfresco/service/cmr/model/FileFolderService.java @@ -339,6 +339,17 @@ public interface FileFolderService @Auditable(parameters = {"rootNodeRef", "pathElements"}) public FileInfo resolveNamePath(NodeRef rootNodeRef, List pathElements) throws FileNotFoundException; + /** + * Resolve a file or folder name path from a given root node down to the final node. + * + * @param rootNodeRef the start point node - a cm:folder type or subtype, e.g. the Company Home's nodeRef + * @param pathElements a list of names in the path. Do not include the referenced rootNodeRef's path element. + * @return Returns the info of the file or folder or null if mustExist is false and the file does not exist + * @throws FileNotFoundException if no file or folder exists along the path and mustExist is true + */ + @Auditable(parameters = {"rootNodeRef", "pathElements", "mustExist"}) + public FileInfo resolveNamePath(NodeRef rootNodeRef, List pathElements, boolean mustExist) throws FileNotFoundException; + /** * Get the file info (name, folder, etc) for the given node * diff --git a/source/java/org/alfresco/service/cmr/security/PersonService.java b/source/java/org/alfresco/service/cmr/security/PersonService.java index f49e5ea91b..17a4485154 100644 --- a/source/java/org/alfresco/service/cmr/security/PersonService.java +++ b/source/java/org/alfresco/service/cmr/security/PersonService.java @@ -49,8 +49,9 @@ public interface PersonService /** * Get a person by userName. The person is store in the repository. The * person may be created as a side effect of this call, depending on the - * setting to - * {@link #setCreateMissingPeople(boolean) create missing people or not}. + * setting of + * {@link #setCreateMissingPeople(boolean) to create missing people or not}. + * The home folder will also be created as a side effect if it does not exist. * * @param userName - * the userName key to find the person @@ -66,20 +67,27 @@ public interface PersonService public NodeRef getPerson(String userName); /** - * Retrieve the person NodeRef for a username key. Depending on the autoCreate parameter and - * configuration missing people will be created if not found, else a NoSuchPersonException exception will be thrown. + * Retrieve the person NodeRef for a {@code username}, optionally creating + * the home folder if it does not exist and optionally creating the person + * if they don't exist AND the PersonService is configured to allow the + * creation of missing persons {@see #setCreateMissingPeople(boolean)}. + * + * If not allowed to create missing persons and the person does not exist + * a {@code NoSuchPersonException} exception will be thrown. * * @param userName * of the person NodeRef to retrieve - * @param autoCreate - * should we auto-create the person node and home folder if they don't exist? (and configuration allows - * us to) + * @param autoCreateHomeFolderAndMissingPersonIfAllowed + * If the person exits: + * should we create the home folder if it does not exist? + * If the person exists AND the creation of missing persons is allowed + * should we create both the person and home folder. * @return NodeRef of the person as specified by the username * @throws NoSuchPersonException * if the person doesn't exist and can't be created */ @Auditable(parameters = {"userName", "autoCreate"}) - public NodeRef getPerson(final String userName, final boolean autoCreate); + public NodeRef getPerson(final String userName, final boolean autoCreateHomeFolderAndMissingPersonIfAllowed); /** * Check if a person exists. @@ -124,7 +132,7 @@ public interface PersonService /** * Set the properties on a person - some of these may be persisted in - * different locations. + * different locations - the home folder is created if it doesn't exist * * @param userName - * the user for which the properties should be set. @@ -142,11 +150,11 @@ public interface PersonService * - the user for which the properties should be set. * @param properties * - the map of properties to set (as the NodeService) - * @param autoCreate - * should we auto-create the home folder if it doesn't exist? (and configuration allows us to) + * @param autoCreateHomeFolder + * should we auto-create the home folder if it doesn't exist. */ @Auditable(parameters = {"userName", "properties", "autoCreate"}) - public void setPersonProperties(String userName, Map properties, boolean autoCreate); + public void setPersonProperties(String userName, Map properties, boolean autoCreateHomeFolder); /** * Can this service create, delete and update person information?