/* * Copyright (C) 2005-2010 Alfresco Software Limited. * * This file is part of Alfresco * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ 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 javax.mail.Flags; import javax.mail.MessagingException; import javax.mail.Multipart; import javax.mail.Part; import javax.mail.internet.ContentType; 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.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; import com.icegreen.greenmail.store.MessageFlags; 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 */ 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; /** * Name of the folder. */ private String folderName; /** * Defines view mode. */ private ImapViewMode viewMode; /** * Name of the mount point. */ private String mountPointName; /** * Reference to the {@link ImapService} object. */ private ImapService imapService; /** * Defines whether the folder is selectable or not. */ private boolean selectable; /** * The UIDValidity */ private long uidValidity = 0; 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 { PERMANENT_FLAGS.add(Flags.Flag.ANSWERED); PERMANENT_FLAGS.add(Flags.Flag.DELETED); PERMANENT_FLAGS.add(Flags.Flag.DRAFT); PERMANENT_FLAGS.add(Flags.Flag.FLAGGED); PERMANENT_FLAGS.add(Flags.Flag.SEEN); } public boolean isExtractAttachmentsEnabled() { return extractAttachmentsEnabled; } /*package*/ AlfrescoImapFolder(String qualifiedMailboxName, ServiceRegistry serviceRegistry) { this(qualifiedMailboxName, null, null, null, null, null, false, serviceRegistry); } /** * Constructs {@link AlfrescoImapFolder} object. * * @param qualifiedMailboxName - name of the mailbox (e.g. "admin" for admin user). * @param folderInfo - reference to the {@link FileInfo} object representing the folder. * @param folderName - name of the folder. * @param viewMode - defines view mode. Can be one of the following: {@link AlfrescoImapConst#MODE_ARCHIVE} or {@link AlfrescoImapConst#MODE_VIRTUAL}. * @param rootNodeRef - reference to the root node of the store where folder is placed. * @param mountPointName - name of the mount point. */ public AlfrescoImapFolder( String qualifiedMailboxName, FileInfo folderInfo, String folderName, ImapViewMode viewMode, NodeRef rootNodeRef, String mountPointName, boolean extractAttachmentsEnabled, ServiceRegistry serviceRegistry) { this(qualifiedMailboxName, folderInfo, folderName, viewMode, rootNodeRef, mountPointName, serviceRegistry, null, extractAttachmentsEnabled); } /** * Constructs {@link AlfrescoImapFolder} object. * * @param qualifiedMailboxName - name of the mailbox (e.g. "admin" for admin user). * @param folderInfo - reference to the {@link FileInfo} object representing the folder. * @param folderName - name of the folder. * @param viewMode - defines view mode. Can be one of the following: {@link AlfrescoImapConst#MODE_ARCHIVE} or {@link AlfrescoImapConst#MODE_VIRTUAL}. * @param rootNodeRef - reference to the root node of the store where folder is placed. * @param mountPointName - name of the mount point. * @param imapService - reference to the {@link ImapHelper} object. * @param selectable - defines whether the folder is selectable or not. */ public AlfrescoImapFolder( String qualifiedMailboxName, FileInfo folderInfo, String folderName, ImapViewMode viewMode, NodeRef rootNodeRef, String mountPointName, ServiceRegistry serviceRegistry, Boolean selectable, boolean extractAttachmentsEnabled) { super(serviceRegistry); this.qualifiedMailboxName = qualifiedMailboxName; this.folderInfo = folderInfo; this.rootNodeRef = rootNodeRef; this.folderName = folderName != null ? folderName : (folderInfo != null ? folderInfo.getName() : null); this.viewMode = viewMode != null ? viewMode : ImapViewMode.ARCHIVE; this.mountPointName = mountPointName; this.extractAttachmentsEnabled = extractAttachmentsEnabled; if (serviceRegistry != null) { 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 "" "" // S: * LIST () "." "" // S: 2 OK LIST completed. if (folderInfo != null) { if (selectable == null) { // isSelectable(); Boolean storedSelectable = !serviceRegistry.getNodeService().hasAspect(folderInfo.getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_NONSELECTABLE); if (storedSelectable == null) { setSelectable(true); } else { setSelectable(storedSelectable); } } else { setSelectable(selectable); } } else { setSelectable(true); } } /** * Appends message to the folder. * * @param message - message. * @param flags - message flags. * @param internalDate - not used. Current date used instead. */ @Override protected long appendMessageInternal( MimeMessage message, Flags flags, 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; } /** * Copies message with the given UID to the specified {@link MailFolder}. * * @param uid - UID of the message * @param toFolder - reference to the destination folder. * @throws MessagingException * @throws IOException * @throws FileNotFoundException * @throws FileExistsException */ @Override protected void copyMessageInternal( long uid, MailFolder toFolder) throws MessagingException, FileExistsException, FileNotFoundException, IOException { AlfrescoImapFolder toImapMailFolder = (AlfrescoImapFolder) toFolder; NodeRef destFolderNodeRef = toImapMailFolder.getFolderInfo().getNodeRef(); SimpleStoredMessage message = messages.get(uid); FileInfo sourceMessageFileInfo = ((AbstractMimeMessage) message.getMimeMessage()).getMessageInfo(); 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()); } else { serviceRegistry.getFileFolderService().copy(sourceMessageFileInfo.getNodeRef(), destFolderNodeRef, null); } removeMessageFromCache(uid); } /** * Marks all messages in the folder as deleted using {@link Flags.Flag#DELETED} flag. */ @Override public void deleteAllMessagesInternal() throws FolderException { if (isReadOnly()) { throw new FolderException("Can't delete all - Permission denied"); } for (SimpleStoredMessage mess : messages.values()) { AbstractMimeMessage message = (AbstractMimeMessage) mess.getMimeMessage(); FileInfo fileInfo = message.getMessageInfo(); imapService.setFlag(fileInfo, Flags.Flag.DELETED, true); // comment out to physically remove content. // fileFolderService.delete(fileInfo.getNodeRef()); removeMessageFromCache(mess.getUid()); } } /** * Deletes messages marked with {@link Flags.Flag#DELETED}. Note that this message deletes all messages with this flag. */ @Override protected void expungeInternal() throws FolderException { if (isReadOnly()) { throw new FolderException("Can't expunge - Permission denied"); } Collection listMess = messages.values(); for (SimpleStoredMessage mess : listMess) { Flags flags = getFlags(mess); if (flags.contains(Flags.Flag.DELETED)) { NodeRef nodeRef = ((AbstractMimeMessage) mess.getMimeMessage()).getMessageInfo().getNodeRef(); serviceRegistry.getFileFolderService().delete(nodeRef); removeMessageFromCache(mess.getUid()); } } } /** * Returns the number of the first unseen message. * * @return Number of the first unseen message. */ @Override protected int getFirstUnseenInternal() { 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); } /** * Returns message by its UID. * * @param uid - UID of the message. * @return message. * @throws MessagingException */ @Override protected SimpleStoredMessage getMessageInternal(long uid) throws MessagingException { if (logger.isDebugEnabled()) { logger.debug("[getMessageInternal] " + this); } AbstractMimeMessage mes = (AbstractMimeMessage) messages.get(uid).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; } } /** * Returns count of the messages in the folder. * * @return Count of the messages. */ @Override protected int getMessageCountInternal() { 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(); } /** * Returns UIDs of all messages in the folder. * * @return UIDS of the messages. */ @Override protected long[] getMessageUidsInternal() { 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(); int i = 0; for (Long key : keys) { uids[i++] = key; } return uids; } /** * Returns list of all messages in the folder. * * @return list of {@link SimpleStoredMessage} objects. */ @Override protected List getMessagesInternal() { if (logger.isDebugEnabled()) { logger.debug("[getMessagesInternal] " + this); } List fileInfos = imapService.searchMails(folderInfo.getNodeRef(), viewMode); return convertToMessages(fileInfos); } private List convertToMessages(List fileInfos) { if (logger.isDebugEnabled()) { logger.debug("[convertToMessages] " + this); } if (fileInfos == null || fileInfos.size() == 0) { logger.debug("[convertToMessages] - fileInfos is empty or null"); return Collections.emptyList(); } if (fileInfos.size() != messages.size()) { for (FileInfo fileInfo : fileInfos) { try { 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); } } } 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); } } /** * Returns list of messages by filter. * * @param msgRangeFilter - {@link MsgRangeFilter} object representing filter. * @return list of filtered messages. */ @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; } /** * 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. */ @Override protected int getMsnInternal(long uid) throws FolderException { Integer msn = msnCache.get(uid); if (msn != null) { return msn; } throw new FolderException("No such message."); } /** * Returns folder name. * * @return folder name. */ @Override protected String getNameInternal() { return folderName; } /** * Returns the list of messages that have no {@link Flags.Flag#DELETED} flag set for current user. * * @return the list of non-deleted messages. */ @Override protected List getNonDeletedMessagesInternal() { if (logger.isDebugEnabled()) { logger.debug("[getNonDeletedMessagesInternal] " + this); } List result = new ArrayList(); if (messages.size() == 0 && folderInfo != null) { List fileInfos = imapService.searchMails(folderInfo.getNodeRef(), viewMode); convertToMessages(fileInfos); } Collection values = messages.values(); for (SimpleStoredMessage message : values) { if (!getFlags(message).contains(Flags.Flag.DELETED)) { result.add(message); } } if (logger.isDebugEnabled() && folderInfo != null) { logger.debug(folderInfo.getName() + " - Non deleted messages count:" + result.size()); } return result; } /** * Returns permanent flags. * * @return {@link Flags} object containing flags. */ @Override protected Flags getPermanentFlagsInternal() { return PERMANENT_FLAGS; } /** * 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. */ @Override protected int getRecentCountInternal(boolean reset) { if (logger.isDebugEnabled()) { 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)) { count++; if (reset) { imapService.setFlag(((AbstractMimeMessage) message.getMimeMessage()).getMessageInfo(), Flags.Flag.RECENT, false); } } } if (logger.isDebugEnabled() && folderInfo != null) { logger.debug(folderInfo.getName() + " - Recent count: " + count + " reset: " + reset); } return count; } /** * Returns UIDNEXT value of the folder. * * @return UIDNEXT value. */ @Override protected long getUidNextInternal() { /** * 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; } 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() { /** * 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; } } /** * 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. */ @Override protected int getUnseenCountInternal() { 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; } /** * 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. * {@code silentListener} can be provided - this listener wouldn't be notified. * * @param flags - new flags. * @param uid - message UID. * @param silentListener - listener that shouldn't be notified. * @param addUid - defines whether or not listeners be notified. * @throws FolderException * @throws MessagingException */ @Override protected void replaceFlagsInternal( Flags flags, long uid, FolderListener silentListener, boolean addUid) throws FolderException, MessagingException { int msn = getMsn(uid); SimpleStoredMessage message = messages.get(uid); FileInfo fileInfo = ((AbstractMimeMessage) message.getMimeMessage()).getMessageInfo(); 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); } /** * Sets 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. * {@code silentListener} can be provided - this listener wouldn't be notified. * * @param flags - new flags. * @param value - flags value. * @param uid - message UID. * @param silentListener - listener that shouldn't be notified. * @param addUid - defines whether or not listeners be notified. * @throws MessagingException * @throws FolderException */ @Override protected void setFlagsInternal( Flags flags, boolean value, long uid, FolderListener silentListener, boolean addUid) 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); Long uidNotification = null; if (addUid) { uidNotification = new Long(uid); } notifyFlagUpdate(msn, message.getFlags(), 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(); } // ----------------------Getters and Setters---------------------------- 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}. */ @Override protected boolean isSelectableInternal() { 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); } /** * Whether the folder is read-only for user. * * @return {@code boolean} */ @Override protected boolean isReadOnly() { AccessStatus status = serviceRegistry.getPublicServiceAccessService().hasAccess(ServiceRegistry.NODE_SERVICE.getLocalName(), "createNode", folderInfo.getNodeRef(), null, null, null); //serviceRegistry.getPermissionService().hasPermission(folderInfo.getNodeRef(), PermissionService.WRITE); return status == AccessStatus.DENIED; } public ImapViewMode getViewMode() { return viewMode; } /** * Creates the EML message in the specified folder. * * @param folderFileInfo The folder to create message in. * @param message The original MimeMessage. * @return Wrapped AbstractMimeMessage which was created. * @throws FileNotFoundException * @throws FileExistsException * @throws MessagingException * @throws IOException */ private AbstractMimeMessage createMimeMessageInFolder( FileInfo folderFileInfo, MimeMessage message) throws FileExistsException, FileNotFoundException, IOException, MessagingException { String name = AlfrescoImapConst.MESSAGE_PREFIX + GUID.generate(); FileFolderService fileFolderService = serviceRegistry.getFileFolderService(); FileInfo messageFile = fileFolderService.create(folderFileInfo.getNodeRef(), name, ContentModel.TYPE_CONTENT); final long newMessageUid = (Long) messageFile.getProperties().get(ContentModel.PROP_NODE_DBID); name = AlfrescoImapConst.MESSAGE_PREFIX + newMessageUid + AlfrescoImapConst.EML_EXTENSION; fileFolderService.rename(messageFile.getNodeRef(), name); 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)); return (modifDate - YEAR_2005) / 1000; } } return new Long(0); } } }