2010-03-09 00:39:21 +00:00

1013 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 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 "";
}
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 (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 (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));
}
}
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()
{
return ((Date) serviceRegistry.getNodeService().getProperty(folderInfo.getNodeRef(), ContentModel.PROP_MODIFIED)).getTime();
}
/**
* 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))
{
return ((Date) serviceRegistry.getNodeService().getProperty(fileInfo.getNodeRef(), ContentModel.PROP_MODIFIED)).getTime();
}
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);
}
}