Files
alfresco-community-repo/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java
Dave Ward 2163f99539 Merged V3.3 to HEAD
20025: Created Enterprise branch V3.3
   20026: ALF-2597 : IMAP : permissions on home space.
      - now, by default, people can't read other's mail.
   20030: Merged BRANCHES/V3.2 to BRANCHES/V3.3:
      19919: Merged BRANCHES/V3.1 to BRANCHES/V3.2:
         19766: Fixed ALF-2351: Oracle upgrade scripts need enhancements from 2.2SP7
      20027: Merged BRANCHES/V3.1 to BRANCHES/V3.2:
         19983: Changes for ALF-2545: Cannot upgrade from 2.1.2a (b 209) to the 3.1.2 (.a3 458) on Oracle
         20008: ALF-2351: Oracle upgrade scripts need enhancements from 2.2SP7
   20032: Merged HEAD to BRANCHES/V3.3 (RECORD ONLY)
      20031: Fix ALF-2626 - Share Repository browser broken
   20035: Enterprise branding for Share & Explorer - DO NOT MERGE (RECORD ONLY)
      Also: SAIL-282: Update the Help URLs for 3.3 Enterprise
   20039: Fix ALF-2393 - Alfresco Comunity 3.3 deployment error on JBoss v6
   20044: Fix ALF-750 (versioning does not persist node associations)
      - TODO: review version migrator (if upgrading directly from Ent 2.x to Ent 3.3)
   20049: Merged PATCHES/V3.2.r to BRANCHES/V3.3
      20047: Fix for ALF-2640: Share: Edit Offline and Upload New Version fails with HTML uploader on FF3.5, works on IE
   20054: Fix ALF-750 (versioning does not persist node associations)
      - update version migrator (only applies if not already run, ie. upgrading directly from Ent 2.x to Ent 3.3)
   20057: Merged HEAD to BRANCHES/V3.3: (RECORD ONLY)
      20033: Accordion example was broken when FDK is deployed as a JAR
   20064: Fix for ALF-2623: Alfresco 3.3G's Share site is prone to cross site scripting attacks
      - Bug is actually in the wiki components
   20065: Fix unreported issue (auto-versioning for metadata-only updates stops working after checkin) & additional improvements to LockService
      - explicitly remove lockable aspect (rather than nullifying properties) for unlock / checkin
      - use txn resource to track ignorable nodes (for lockable aspect behaviours)
      - note: currently affects Alfresco Explorer only (since Alfresco Share explicitly disables autoVersionOnUpdateProps)
   20066: Increased PermGen space for tests to 256M from 128M
   20071: AVM - check for circular layered directories (ALF-813 / ALF-910)
   20073: Fix LockService tests
      - fix typo (introduced in r20065)
      - TODO: review LockOwnerDynamicAuthorityTest.testCheckOutCheckInAuthorities
   20076: Fix LockOwnerDynamicAuthorityTest.testCheckOutCheckInAuthorities
   20078: Fixed ALF-2464 "Missing i18n labels when rules fail to run"
   20081: Fixed ALF-1626 "The position:absolute behaviour of the Flash preview container needs a re-think"
      - Now handles long file names (resize was already fixed)
   20083: Fix for ALF-2708: Unmodifiable exception thrown when Web Script f/w attempts to report error (latest Spring Surf webscripts libraries)
   20084: Fixed ALF-253 "Unfriendly message appears when trying to login with username which contains symbol '\'"
      - also fixed bug whereerror messages for illegal characters was displayed as undefined for FF on Mac
   20085: Merging HEAD into BRANCHES/V3.3:
      20074: ALF-959 The invitation email 'subject' can now be set as a localizable property in invitation-services.properties:
      20080: Fixing failing test InvitationServiceImplTest.
   20087: ALF-1498: RM web script puts Alfresco in endless loop
      This was a general issue with the onUpdateProperties behaviour in the versionable aspect.  This code now disables the behaviour whilst it is executing to prevent the endless loop occuring.
   20088: Fixed an issue when uploading 2 or more documents for a new site. 
      - A failure occured since it asynchronously tried to create the documentLibrary container twice and the second attempt failed since it already existed.
   20089: SAIL-356: Action label changes
   20090: Dynamic models: minor improvements to DictionaryModelType
      - add (optional) concurrency locking
      - remove duplicate bean def
      - bind remaining class behaviours (onCreateNode, onRemoveAspect) based on type
   20091: Fixed ALF-1046 "Leave button is displayed for admin on Site Finder page near private site where admin is not invited"
   20092: Merged DEV/BELARUS/V3.2-2010_03_17 to V3.3
      20043: ALF-928: Upgrade from 2.1.7 to 3.2 with lots of content items - GC overhead limit exceeded exception
         Call getChildAssocs(NodeRef, QNamePattern, QNamePattern, boolean) with a value of 'false' for the preload argument to avoid preloading all the child nodes
   20093: Fix for ALF-2721: Upgrade clean 2.2.current + 20k users to 3.3.current fails in CalendarModelUriPatch updating URI that does not exist
   20094: ALF-2630: LDAP differential sync was failing to sync group memberships of users who themselves hadn't changed
      - New post process deals with group associations of unprocessed users
      - Modified unit test to properly simulate differential sync
   20095: Fix for ALF-2715: Rule creation in Alfresco Share 3.3G leads to an "Internal Server Error" failure message
   20096: Fix webview and wiki dashlet titles in yellow and gdocs themes.
   20097: Follow-up fix to cross-browser WebView dashlet (iframe) resizing
   20098: Workaround for ALF-2211: Share - Accessing User homes from Share/JSF integration freezes the browser.
      - The tree control has been given a configurable maximum folder count setting for both Site and Repository working modes. By default these are "unlimited" in Site mode and 500 in Repository mode. These values can be overridden in share-config-custom.xml - see the sample configuration file for details.
      - The workaround is to display a "Too many sub-folders to display" message when the maximum number of folders has been reached.
      - To aid users to select their User Home space (or sub-folder thereof) for Copy and Move actions, a new "My User Home" button is provided on the folder picker control.
   20099: Fix for ALF-2606: Manage Permissions on multiple nodes.
      - Toolbar action removed when in Repository Browser, as the fine-grained permissions page does not support multiple nodes.
   20100: Merged Outlook Meeting Workspace integration from BRANCHES/DEV/BELARUS/V3.2-2010_01_11
   20102: Fix for ALF-478: Authority CRC calculations must use UTF-8
   20103: Follow up from ALF-253 (Unfriendly message appears when trying to login with username which contains symbol '\')
      - Making lastName mandatory in Share ui since service otherwise complains
   20106: Fixed ALF-1041 "Revert action is available for SiteContributor and SiteConsumer" (and added a missing msg key for blogs)
   20108: ALF-2235: Permission exception when creating non-electronic records by Power User with Read and File permssions
   20109: Fix for ALF-2706 "ConcurrentModificationException in AsynchronousActionExecutionQueueImpl"
   20110: Merge Dev to V3.3
      ALF-1980 - Huge UIDVALIDITY giving IMAP client problems
   20111: Latest webeditor JAR containing change to orientation strings in WEF
   20113: Fix Share DocLib copy/move actions from recent refactor. Picker now appears with correct Site/Repository mode set upon opening.
   20114: Fix for ALF-2726: 'Transform and Copy content' action causes error.
   20115: Fix for ALF-2697 - File encoding is hard-coded for upload.post.js (Webscript API)
   20116: Fix for ALF-1090
   20119: ALF-2734 - Incorrect behaviour on creating google docs in Repository Browser
   20120: Enterprise build fix for Index check tests 
      - disable user usage updates
      - this should not be required
   20121: ALF-959 The site name/title should now correctly appear in the invite email subject, replacing '{0}'.
   20123: Merged HEAD to V3.3 (RECORD ONLY)
      20122: First part of fix for ALF-2718:  DOD5015 module breaks CMIS Atom DiscoveryService webscripts
   20126: Fix rule rest api json so numbers are not incorrectly formatted.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@20565 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2010-06-09 12:47:50 +00:00

1035 lines
35 KiB
Java

/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.imap;
import java.io.IOException;
import java.io.OutputStream;
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.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.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.cmr.security.PermissionService;
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.
*
* @author Mike Shavnev
*/
public class AlfrescoImapFolder extends AbstractImapFolder
{
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;
/**
* Defines whether the folder is read-only for user or not.
*/
private Boolean readOnly;
private boolean extractAttachmentsEnabled;
private Map<Long, SimpleStoredMessage> messages = new TreeMap<Long, SimpleStoredMessage>();
private Map<Long, Integer> msnCache = new HashMap<Long, Integer>();
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();
}
// 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);
}
AccessStatus status = serviceRegistry.getPublicServiceAccessService().hasAccess(ServiceRegistry.NODE_SERVICE.getLocalName(), "createNode", folderInfo.getNodeRef(), null, null, null);
//serviceRegistry.getPermissionService().hasPermission(folderInfo.getNodeRef(), PermissionService.WRITE);
if (status == AccessStatus.DENIED)
{
readOnly = true;
}
else
{
readOnly = false;
}
}
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);
}
}
/**
* Marks all messages in the folder as deleted using {@link Flags.Flag#DELETED} flag.
*/
@Override
public void deleteAllMessagesInternal() throws FolderException
{
if (this.readOnly)
{
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());
messages.remove(mess.getUid());
msnCache.remove(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 (this.readOnly)
{
throw new FolderException("Can't expunge - Permission denied");
}
Collection<SimpleStoredMessage> 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);
}
}
}
/**
* 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}) <p/>
* E.g.: <br/>
* #mail.admin."Repository_archive.Data Dictionary.Space Templates.Software Engineering Project"<br/>
* 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 entry");
}
StringBuilder fullName = new StringBuilder();
List<FileInfo> 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
{
AbstractMimeMessage mes = (AbstractMimeMessage) messages.get(uid).getMimeMessage();
FileInfo mesInfo = mes.getMessageInfo();
return createImapMessage(mesInfo, uid, true);
}
/**
* Returns count of the messages in the folder.
*
* @return Count of the messages.
*/
@Override
protected int getMessageCountInternal()
{
if (logger.isDebugEnabled())
{
logger.debug("getMessageCountInternal entry");
}
if (messages.size() == 0)
{
List<FileInfo> fileInfos = imapService.searchMails(folderInfo.getNodeRef(), "*", viewMode, false);
getMessages(fileInfos);
}
if (logger.isDebugEnabled())
{
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 (messages == null || messages.size() == 0)
{
List<FileInfo> fileInfos = imapService.searchMails(folderInfo.getNodeRef(), "*", viewMode, false);
getMessages(fileInfos);
}
int len = messages.size();
long[] uids = new long[len];
Set<Long> 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<SimpleStoredMessage> getMessagesInternal()
{
List<FileInfo> fileInfos = imapService.searchMails(folderInfo.getNodeRef(), "*", viewMode, false);
return getMessages(fileInfos);
}
private List<SimpleStoredMessage> getMessages(List<FileInfo> fileInfos)
{
if (fileInfos == null || fileInfos.size() == 0)
{
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("Message added: " + fileInfo.getName());
}
}
catch (MessagingException e)
{
logger.warn("Invalid message! File name:" + fileInfo.getName(), e);
}
}
}
return new LinkedList<SimpleStoredMessage>(messages.values());
}
protected SimpleStoredMessage createImapMessage(FileInfo fileInfo, Long key, boolean generateBody) throws MessagingException
{
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<SimpleStoredMessage> getMessagesInternal(MsgRangeFilter msgRangeFilter)
{
if (logger.isDebugEnabled())
{
logger.debug("getMessagesInternal entry");
}
if (messages == null || messages.size() == 0)
{
List<FileInfo> fileInfos = imapService.searchMails(folderInfo.getNodeRef(), "*", viewMode, false);
getMessages(fileInfos);
}
List<SimpleStoredMessage> ret = new ArrayList<SimpleStoredMessage>();
for (int i = 0; i < messages.size(); i++)
{
if (msgRangeFilter.includes(i + 1))
{
ret.add(messages.get(i));
}
}
if (logger.isDebugEnabled())
{
logger.debug("getMessagesInternal exit");
}
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<SimpleStoredMessage> getNonDeletedMessagesInternal()
{
List<SimpleStoredMessage> result = new ArrayList<SimpleStoredMessage>();
if (messages.size() == 0)
{
List<FileInfo> fileInfos = imapService.searchMails(folderInfo.getNodeRef(), "*", viewMode, false);
getMessages(fileInfos);
}
Collection<SimpleStoredMessage> values = messages.values();
for (SimpleStoredMessage message : values)
{
if (!getFlags(message).contains(Flags.Flag.DELETED))
{
result.add(message);
}
}
if (logger.isDebugEnabled())
{
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 (messages.size() == 0)
{
List<FileInfo> fileInfos = imapService.searchMails(folderInfo.getNodeRef(), "*", viewMode, false);
getMessages(fileInfos);
}
int count = 0;
Collection<SimpleStoredMessage> 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())
{
logger.debug(folderInfo.getName() + " - Recent count: " + count + " reset: " + reset);
}
return count;
}
/**
* Returns UIDNEXT value of the folder.
*
* @return UIDNEXT value.
*/
@Override
protected long getUidNextInternal()
{
return getUidValidity();
}
/**
* Returns UIDVALIDITY value of the folder.
*
* @return UIDVALIDITY value.
*/
@Override
protected long getUidValidityInternal()
{
long modifDate = ((Date) serviceRegistry.getNodeService().getProperty(folderInfo.getNodeRef(), ContentModel.PROP_MODIFIED)).getTime();
return (modifDate - YEAR_2005)/1000;
}
/**
* 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 (messages.size() == 0)
{
List<FileInfo> fileInfos = imapService.searchMails(folderInfo.getNodeRef(), "*", viewMode, false);
getMessages(fileInfos);
}
int count = 0;
Collection<SimpleStoredMessage> values = messages.values();
for (SimpleStoredMessage message : values)
{
if (!getFlags(message).contains(Flags.Flag.SEEN))
{
count++;
}
}
if (logger.isDebugEnabled())
{
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<QName, Serializable> 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()
{
return readOnly;
}
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)
{
extractAttachments(folderFileInfo, messageFile, message);
}
return new IncomingImapMessage(messageFile, serviceRegistry, message);
}
private void extractAttachments(
FileInfo parentFolder,
FileInfo messageFile,
MimeMessage originalMessage)
throws IOException, MessagingException
{
NodeService nodeService = serviceRegistry.getNodeService();
FileFolderService fileFolderService = serviceRegistry.getFileFolderService();
String messageName = (String)nodeService.getProperty(messageFile.getNodeRef(), ContentModel.PROP_NAME);
String attachmentsFolderName = messageName + "-attachments";
FileInfo attachmentsFolderFileInfo = null;
Object content = originalMessage.getContent();
if (content instanceof Multipart)
{
Multipart multipart = (Multipart) content;
for (int i = 0, n = multipart.getCount(); i < n; i++)
{
Part part = multipart.getBodyPart(i);
if ("attachment".equalsIgnoreCase(part.getDisposition()))
{
if (attachmentsFolderFileInfo == null)
{
attachmentsFolderFileInfo = fileFolderService.create(
parentFolder.getNodeRef(),
attachmentsFolderName,
ContentModel.TYPE_FOLDER);
serviceRegistry.getNodeService().createAssociation(
messageFile.getNodeRef(),
attachmentsFolderFileInfo.getNodeRef(),
ImapModel.ASSOC_IMAP_ATTACHMENTS_FOLDER);
}
createAttachment(messageFile, attachmentsFolderFileInfo, part);
}
}
}
}
private void createAttachment(FileInfo messageFile, FileInfo attachmentsFolderFileInfo, Part part) throws MessagingException, IOException
{
String fileName = part.getFileName();
try
{
fileName = MimeUtility.decodeText(fileName);
}
catch (UnsupportedEncodingException e)
{
if (logger.isWarnEnabled())
{
logger.warn("Cannot decode file name '" + fileName + "'", e);
}
}
ContentType contentType = new ContentType(part.getContentType());
FileFolderService fileFolderService = serviceRegistry.getFileFolderService();
List<FileInfo> result = fileFolderService.search(attachmentsFolderFileInfo.getNodeRef(), fileName, false);
// The one possible behaviour
/*
if (result.size() > 0)
{
for (FileInfo fi : result)
{
fileFolderService.delete(fi.getNodeRef());
}
}
*/
// And another one behaviour which will overwrite the content of the existing file. It is performance preferable.
FileInfo attachmentFile = null;
if (result.size() == 0)
{
FileInfo createdFile = fileFolderService.create(
attachmentsFolderFileInfo.getNodeRef(),
fileName,
ContentModel.TYPE_CONTENT);
serviceRegistry.getNodeService().createAssociation(
messageFile.getNodeRef(),
createdFile.getNodeRef(),
ImapModel.ASSOC_IMAP_ATTACHMENT);
result.add(createdFile);
}
attachmentFile = result.get(0);
ContentWriter writer = fileFolderService.getWriter(attachmentFile.getNodeRef());
writer.setMimetype(contentType.getBaseType());
OutputStream os = writer.getContentOutputStream();
FileCopyUtils.copy(part.getInputStream(), os);
}
}