/*
 * 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.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.mail.Flags;
import javax.mail.Flags.Flag;
import org.alfresco.error.AlfrescoRuntimeException;
import org.springframework.extensions.surf.util.I18NUtil;
import org.alfresco.model.ContentModel;
import org.alfresco.model.ImapModel;
import org.alfresco.repo.admin.SysAdminParams;
import org.alfresco.repo.imap.AlfrescoImapConst.ImapViewMode;
import org.alfresco.repo.imap.config.ImapConfigMountPointsBean;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.site.SiteServiceException;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.preference.PreferenceService;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.alfresco.util.PropertyCheck;
import org.alfresco.util.Utf7;
import org.alfresco.util.config.RepositoryFolderConfigBean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationEvent;
/**
 * @author Dmitry Vaserin
 * @author Arseny Kovalchuk
 * @since 3.2
 */
public class ImapServiceImpl implements ImapService
{
    private Log logger = LogFactory.getLog(ImapServiceImpl.class);
    private static final String ERROR_PERMISSION_DENIED = "imap.server.error.permission_denied";
    private static final String ERROR_FOLDER_ALREADY_EXISTS = "imap.server.error.folder_already_exist";
    private static final String ERROR_MAILBOX_NAME_IS_MANDATORY = "imap.server.error.mailbox_name_is_mandatory";
    private static final String ERROR_CANNOT_GET_A_FOLDER = "imap.server.error.cannot_get_a_folder";
    private SysAdminParams sysAdminParams;
    private FileFolderService fileFolderService;
    private NodeService nodeService;
    private ServiceRegistry serviceRegistry;
    private Map imapConfigMountPoints;
    private RepositoryFolderConfigBean[] ignoreExtractionFoldersBeans;
    private RepositoryFolderConfigBean imapHomeConfigBean;
    private NodeRef imapHomeNodeRef;
    private Set ignoreExtractionFolders;
    private String defaultFromAddress;
    private String repositoryTemplatePath;
    private boolean extractAttachmentsEnabled = true;
    private final static Map qNameToFlag;
    private final static Map flagToQname;
    static
    {
        qNameToFlag = new HashMap();
        qNameToFlag.put(ImapModel.PROP_FLAG_ANSWERED, Flags.Flag.ANSWERED);
        qNameToFlag.put(ImapModel.PROP_FLAG_DELETED, Flags.Flag.DELETED);
        qNameToFlag.put(ImapModel.PROP_FLAG_DRAFT, Flags.Flag.DRAFT);
        qNameToFlag.put(ImapModel.PROP_FLAG_SEEN, Flags.Flag.SEEN);
        qNameToFlag.put(ImapModel.PROP_FLAG_RECENT, Flags.Flag.RECENT);
        qNameToFlag.put(ImapModel.PROP_FLAG_FLAGGED, Flags.Flag.FLAGGED);
        flagToQname = new HashMap();
        flagToQname.put(Flags.Flag.ANSWERED, ImapModel.PROP_FLAG_ANSWERED);
        flagToQname.put(Flags.Flag.DELETED, ImapModel.PROP_FLAG_DELETED);
        flagToQname.put(Flags.Flag.DRAFT, ImapModel.PROP_FLAG_DRAFT);
        flagToQname.put(Flags.Flag.SEEN, ImapModel.PROP_FLAG_SEEN);
        flagToQname.put(Flags.Flag.RECENT, ImapModel.PROP_FLAG_RECENT);
        flagToQname.put(Flags.Flag.FLAGGED, ImapModel.PROP_FLAG_FLAGGED);
    }
    /**
     * Bootstrap initialization bean for the service implementation.
     * 
     * @author Derek Hulley
     * @since 3.2
     */
    public static class ImapServiceBootstrap extends AbstractLifecycleBean
    {
        private ImapServiceImpl service;
        private boolean imapServerEnabled;
        public void setService(ImapServiceImpl service)
        {
            this.service = service;
        }
        public void setImapServerEnabled(boolean imapServerEnabled)
        {
            this.imapServerEnabled = imapServerEnabled;
        }
        @Override
        protected void onBootstrap(ApplicationEvent event)
        {
            if (imapServerEnabled)
            {
                service.startup();
            }
        }
        @Override
        protected void onShutdown(ApplicationEvent event)
        {
            if (imapServerEnabled)
            {
                service.shutdown();
            }
        }
    }
    public void setSysAdminParams(SysAdminParams sysAdminParams)
    {
        this.sysAdminParams = sysAdminParams;
    }
    public FileFolderService getFileFolderService()
    {
        return fileFolderService;
    }
    public void setFileFolderService(FileFolderService fileFolderService)
    {
        this.fileFolderService = fileFolderService;
    }
    public NodeService getNodeService()
    {
        return nodeService;
    }
    public void setNodeService(NodeService nodeService)
    {
        this.nodeService = nodeService;
    }
    public ServiceRegistry getServiceRegistry()
    {
        return serviceRegistry;
    }
    public void setServiceRegistry(ServiceRegistry serviceRegistry)
    {
        this.serviceRegistry = serviceRegistry;
    }
    public void setImapHome(RepositoryFolderConfigBean imapHomeConfigBean)
    {
        this.imapHomeConfigBean = imapHomeConfigBean;
    }
    public String getDefaultFromAddress()
    {
        return defaultFromAddress;
    }
    public void setDefaultFromAddress(String defaultFromAddress)
    {
        this.defaultFromAddress = defaultFromAddress;
    }
    public String getWebApplicationContextUrl()
    {
        return sysAdminParams.getAlfrescoProtocol() + "://" + sysAdminParams.getAlfrescoHost() + ":" + sysAdminParams.getAlfrescoPort() + "/" + sysAdminParams.getAlfrescoContext();
    }
    public String getRepositoryTemplatePath()
    {
        return repositoryTemplatePath;
    }
    public void setRepositoryTemplatePath(String repositoryTemplatePath)
    {
        this.repositoryTemplatePath = repositoryTemplatePath;
    }
    public void setImapConfigMountPoints(ImapConfigMountPointsBean[] imapConfigMountPointsBeans)
    {
        this.imapConfigMountPoints = new LinkedHashMap(imapConfigMountPointsBeans.length * 2);
        for (ImapConfigMountPointsBean bean : imapConfigMountPointsBeans)
        {
            this.imapConfigMountPoints.put(bean.getMountPointName(), bean);
        }
    }
    public void setIgnoreExtractionFolders(final RepositoryFolderConfigBean[] ignoreExtractionFolders)
    {
        this.ignoreExtractionFoldersBeans = ignoreExtractionFolders;
    }
    public void setExtractAttachmentsEnabled(boolean extractAttachmentsEnabled)
    {
        this.extractAttachmentsEnabled = extractAttachmentsEnabled;
    }
    // ---------------------- Lifecycle Methods ------------------------------
    public void init()
    {
        PropertyCheck.mandatory(this, "imapConfigMountPoints", imapConfigMountPoints);
        PropertyCheck.mandatory(this, "ignoreExtractionFoldersBeans", ignoreExtractionFoldersBeans);
        PropertyCheck.mandatory(this, "imapHome", imapHomeConfigBean);
        
        PropertyCheck.mandatory(this, "fileFolderService", fileFolderService);
        PropertyCheck.mandatory(this, "nodeService", nodeService);
        PropertyCheck.mandatory(this, "serviceRegistry", serviceRegistry);
        PropertyCheck.mandatory(this, "defaultFromAddress", defaultFromAddress);
        PropertyCheck.mandatory(this, "repositoryTemplatePath", repositoryTemplatePath);
    }
    public void startup()
    {
        final NamespaceService namespaceService = serviceRegistry.getNamespaceService();
        final SearchService searchService = serviceRegistry.getSearchService();
        
        // Hit the mount points for early failure
        AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork()
        {
            public Void doWork() throws Exception
            {
                getMountPoints();
                return null;
            }
        }, AuthenticationUtil.getSystemUserName());
        
        // Get NodeRefs for folders to ignore
        this.ignoreExtractionFolders = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork>()
        {
            public Set doWork() throws Exception
            {
                Set result = new HashSet(ignoreExtractionFoldersBeans.length * 2);
                for (RepositoryFolderConfigBean ignoreExtractionFoldersBean : ignoreExtractionFoldersBeans)
                {
                    NodeRef nodeRef = ignoreExtractionFoldersBean.getFolderPath(
                            namespaceService, nodeService, searchService, fileFolderService);
                    if (!result.add(nodeRef))
                    {
                        // It was already in the set
                        throw new AlfrescoRuntimeException(
                                "The folder extraction path has been referenced already: \n" +
                                "   Folder: " + ignoreExtractionFoldersBean);
                    }
                }
                return result;
            }
        }, AuthenticationUtil.getSystemUserName());
        
        // Locate or create IMAP home
        imapHomeNodeRef = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork()
        {
            public NodeRef doWork() throws Exception
            {
                return imapHomeConfigBean.getOrCreateFolderPath(namespaceService, nodeService, searchService, fileFolderService);
            }
        }, AuthenticationUtil.getSystemUserName());
    }
    public void shutdown()
    {
    }
    // ---------------------- Service Methods --------------------------------
    public List listSubscribedMailboxes(AlfrescoImapUser user, String mailboxPattern)
    {
        mailboxPattern = Utf7.decode(mailboxPattern, Utf7.UTF7_MODIFIED);
        if (logger.isDebugEnabled())
        {
            logger.debug("Listing subscribed mailboxes: mailboxPattern=" + mailboxPattern);
        }
        mailboxPattern = getMailPathInRepo(mailboxPattern);
        if (logger.isDebugEnabled())
        {
            logger.debug("Listing subscribed mailboxes: mailboxPattern in alfresco=" + mailboxPattern);
        }
        return listMailboxes(user, mailboxPattern, true);
    }
    public List listMailboxes(AlfrescoImapUser user, String mailboxPattern)
    {
        mailboxPattern = Utf7.decode(mailboxPattern, Utf7.UTF7_MODIFIED);
        if (logger.isDebugEnabled())
        {
            logger.debug("Listing  mailboxes: mailboxPattern=" + mailboxPattern);
        }
        mailboxPattern = getMailPathInRepo(mailboxPattern);
        if (logger.isDebugEnabled())
        {
            logger.debug("Listing  mailboxes: mailboxPattern in alfresco=" + mailboxPattern);
        }
        return listMailboxes(user, mailboxPattern, false);
    }
    public AlfrescoImapFolder createMailbox(AlfrescoImapUser user, String mailboxName)
    {
        if (mailboxName == null)
        {
            throw new IllegalArgumentException(I18NUtil.getMessage(ERROR_MAILBOX_NAME_IS_MANDATORY));
        }
        mailboxName = Utf7.decode(mailboxName, Utf7.UTF7_MODIFIED);
        if (logger.isDebugEnabled())
        {
            logger.debug("Creating folder: " + mailboxName);
        }
        NodeRef root = getMailboxRootRef(mailboxName, user.getLogin());
        NodeRef parentNodeRef = root; // it is used for hierarhy deep search.
        for (String folderName : getMailPathInRepo(mailboxName).split(String.valueOf(AlfrescoImapConst.HIERARCHY_DELIMITER)))
        {
            List folders = searchFolders(parentNodeRef, folderName, false, ImapViewMode.MIXED);
            if (logger.isDebugEnabled())
            {
                logger.debug("Trying to create folder '" + folderName + "'");
            }
            if (folders.size() == 0)
            {
                // folder doesn't exist
                AccessStatus status = serviceRegistry.getPermissionService().hasPermission(parentNodeRef, PermissionService.CREATE_CHILDREN);
                if (status == AccessStatus.DENIED)
                {
                    throw new AlfrescoRuntimeException(ERROR_PERMISSION_DENIED);
                }
                FileInfo mailFolder = serviceRegistry.getFileFolderService().create(parentNodeRef, folderName, ContentModel.TYPE_FOLDER);
                return new AlfrescoImapFolder(
                        user.getQualifiedMailboxName(),
                        mailFolder,
                        folderName,
                        getViewMode(mailboxName),
                        root,
                        getMountPointName(mailboxName),
                        isExtractionEnabled(mailFolder.getNodeRef()),
                        serviceRegistry);
            }
            else
            {
                // folder already exists
                if (logger.isDebugEnabled())
                {
                    logger.debug("Folder '" + folderName + "' already exists");
                }
                // next search from new parent
                parentNodeRef = folders.get(0).getNodeRef();
            }
        }
        throw new AlfrescoRuntimeException(ERROR_FOLDER_ALREADY_EXISTS);
    }
    public void deleteMailbox(AlfrescoImapUser user, String mailboxName)
    {
        if (mailboxName == null)
        {
            throw new IllegalArgumentException(I18NUtil.getMessage(ERROR_MAILBOX_NAME_IS_MANDATORY));
        }
        if (logger.isDebugEnabled())
        {
            logger.debug("Deleting folder: mailboxName=" + mailboxName);
        }
        AlfrescoImapFolder folder = getFolder(user, mailboxName);
        NodeRef nodeRef = folder.getFolderInfo().getNodeRef();
        List childFolders = searchFolders(nodeRef, "*", false, folder.getViewMode());
        if (childFolders.isEmpty())
        {
            folder.signalDeletion();
            // Delete child folders and messages
            fileFolderService.delete(nodeRef);
        }
        else
        {
            if (folder.isSelectable())
            {
                // Delete all messages for this folder
                // Don't delete subfolders and their messages
                List messages = searchFiles(nodeRef, "*", false);
                for (FileInfo message : messages)
                {
                    fileFolderService.delete(message.getNodeRef());
                }
                nodeService.addAspect(nodeRef, ImapModel.ASPECT_IMAP_FOLDER_NONSELECTABLE, null);
            }
            else
            {
                throw new AlfrescoRuntimeException(mailboxName + " - Can't delete a non-selectable store with children.");
            }
        }
    }
    public void renameMailbox(AlfrescoImapUser user, String oldMailboxName, String newMailboxName)
    {
        if (oldMailboxName == null || newMailboxName == null)
        {
            throw new IllegalArgumentException(ERROR_MAILBOX_NAME_IS_MANDATORY);
        }
        oldMailboxName = Utf7.decode(oldMailboxName, Utf7.UTF7_MODIFIED);
        newMailboxName = Utf7.decode(newMailboxName, Utf7.UTF7_MODIFIED);
        if (logger.isDebugEnabled())
        {
            logger.debug("Renaming folder oldMailboxName=" + oldMailboxName + " newMailboxName=" + newMailboxName);
        }
        AlfrescoImapFolder sourceNode = getFolder(user, oldMailboxName);
        NodeRef root = getMailboxRootRef(oldMailboxName, user.getLogin());
        String[] folderNames = getMailPathInRepo(newMailboxName).split(String.valueOf(AlfrescoImapConst.HIERARCHY_DELIMITER));
        String folderName = null;
        NodeRef parentNodeRef = root; // initial root for search
        try
        {
            for (int i = 0; i < folderNames.length; i++)
            {
                folderName = folderNames[i];
                if (i == (folderNames.length - 1)) // is it the last element
                {
                    if (oldMailboxName.equalsIgnoreCase(AlfrescoImapConst.INBOX_NAME))
                    {
                        // If you trying to rename INBOX
                        // - just copy it to another folder with new name
                        // and leave INBOX (with children) intact.
                        fileFolderService.copy(sourceNode.getFolderInfo().getNodeRef(), parentNodeRef, folderName);
                    }
                    else
                    {
                        fileFolderService.move(sourceNode.getFolderInfo().getNodeRef(), parentNodeRef, folderName);
                    }
                }
                else
                // not last element than checks if it exists and creates if doesn't
                {
                    List folders = searchFolders(parentNodeRef, folderName, false, sourceNode.getViewMode());
                    if (folders.size() == 0)
                    {
                        // check creation permission
                        AccessStatus status = serviceRegistry.getPermissionService().hasPermission(parentNodeRef, PermissionService.CREATE_CHILDREN);
                        if (status == AccessStatus.DENIED)
                        {
                            throw new AlfrescoRuntimeException(ERROR_PERMISSION_DENIED);
                        }
                        if (logger.isDebugEnabled())
                        {
                            logger.debug("Creating folder '" + folderName + "'");
                        }
                        serviceRegistry.getFileFolderService().create(parentNodeRef, folderName, ContentModel.TYPE_FOLDER);
                    }
                    else
                    {
                        parentNodeRef = folders.get(0).getNodeRef();
                        if (logger.isDebugEnabled())
                        {
                            logger.debug("Folder '" + folderName + "' already exists");
                        }
                    }
                }
            }
        }
        catch (Exception e)
        {
            if (e instanceof AlfrescoRuntimeException)
            {
                throw (AlfrescoRuntimeException) e;
            }
            else
            {
                throw new AlfrescoRuntimeException(e.getMessage(), e);
            }
        }
    }
    public AlfrescoImapFolder getFolder(AlfrescoImapUser user, String mailboxName)
    {
        mailboxName = Utf7.decode(mailboxName, Utf7.UTF7_MODIFIED);
        if (logger.isDebugEnabled())
        {
            logger.debug("Getting folder '" + mailboxName + "'");
        }
        // If MailFolder object is used to obtain hierarchy delimiter by LIST command:
        // Example:
        // C: 2 list "" ""
        // S: * LIST () "." ""
        // S: 2 OK LIST completed.
        if ("".equals(mailboxName))
        {
            if (logger.isDebugEnabled())
            {
                logger.debug("Request for the hierarchy delimiter");
            }
            return new AlfrescoImapFolder(user.getQualifiedMailboxName(), serviceRegistry);
        }
        NodeRef root = getMailboxRootRef(mailboxName, user.getLogin());
        String mountPointName = getMountPointName(mailboxName);
        NodeRef nodeRef = root; // initial top folder
        ImapViewMode viewMode = getViewMode(mailboxName);
        String[] folderNames = getMailPathInRepo(mailboxName).split(String.valueOf(AlfrescoImapConst.HIERARCHY_DELIMITER));
        for (int i = 0; i < folderNames.length; i++)
        {
            if (logger.isDebugEnabled())
            {
                logger.debug("Processing of " + folderNames[i]);
            }
            List folderList = searchFolders(nodeRef, folderNames[i], false, viewMode);
            if (folderList.isEmpty())
            {
                return new AlfrescoImapFolder(user.getQualifiedMailboxName(), serviceRegistry);
            }
            FileInfo folderFileInfo = folderList.get(0); // we need the only one
            if (i == (folderNames.length - 1)) // is last
            {
                return new AlfrescoImapFolder(
                        user.getQualifiedMailboxName(),
                        folderFileInfo,
                        folderFileInfo.getName(),
                        viewMode,
                        root,
                        mountPointName,
                        isExtractionEnabled(folderFileInfo.getNodeRef()),
                        serviceRegistry);
            }
            else
            {
                nodeRef = folderFileInfo.getNodeRef(); // next parent
            }
        }
        throw new AlfrescoRuntimeException(ERROR_CANNOT_GET_A_FOLDER, new String[] { mailboxName });
    }
    /**
     * Search for mailboxes in specified context
     * 
     * @param contextNodeRef context folder for search
     * @param namePattern name pattern for search
     * @param includeSubFolders include SubFolders
     * @param isVirtualView is folder in "Virtual" View
     * @return list of mailboxes
     */
    public List searchFolders(NodeRef contextNodeRef, String namePattern, boolean includeSubFolders, ImapViewMode viewMode)
    {
        List result = fileFolderService.search(contextNodeRef, namePattern, false, true, includeSubFolders);
        if (viewMode == ImapViewMode.VIRTUAL || viewMode == ImapViewMode.MIXED)
        {
            List nonFavSites = getNonFavouriteSites(getCurrentUser());
            for (SiteInfo siteInfo : nonFavSites)
            {
                FileInfo nonFavSite = fileFolderService.getFileInfo(siteInfo.getNodeRef());
                List siteChilds = fileFolderService.search(nonFavSite.getNodeRef(), namePattern, false, true, true);
                result.removeAll(siteChilds);
                result.remove(nonFavSite);
            }
        }
        else
        {
            // Remove folders from Sites
            List sites = serviceRegistry.getTransactionService().getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback>()
            {
                public List execute() throws Exception
                {
                    List res = new ArrayList();
                    try
                    {
                        res = serviceRegistry.getSiteService().listSites(getCurrentUser());
                    }
                    catch (SiteServiceException e)
                    {
                        // Do nothing. Root sites folder was not created.
                        if (logger.isWarnEnabled())
                        {
                            logger.warn("Root sites folder was not created.");
                        }
                    }
                    catch (InvalidNodeRefException e)
                    {
                        // Do nothing. Root sites folder was deleted.
                        if (logger.isWarnEnabled())
                        {
                            logger.warn("Root sites folder was deleted.");
                        }
                    }
                    return res;
                }
            }, false, true);
            for (SiteInfo siteInfo : sites)
            {
                List siteChilds = fileFolderService.search(siteInfo.getNodeRef(), namePattern, false, true, true);
                result.removeAll(siteChilds);
                // remove site
                result.remove(fileFolderService.getFileInfo(siteInfo.getNodeRef()));
            }
        }
        return result;
    }
    /**
     * Search for files in specified context
     * 
     * @param contextNodeRef context folder for search
     * @param namePattern name pattern for search
     * @param searchType type for search
     * @param includeSubFolders include SubFolders
     * @return list of files with specifed type
     */
    public List searchFiles(NodeRef contextNodeRef, String namePattern, boolean includeSubFolders)
    {
        return fileFolderService.search(contextNodeRef, namePattern, true, false, includeSubFolders);
    }
    /**
     * Search for emails in specified folder depend on view mode.
     * 
     * @param contextNodeRef context folder for search
     * @param namePattern name pattern for search
     * @param viewMode context folder view mode
     * @param includeSubFolders includeSubFolders
     * @return list of emails that context folder contains.
     */
    public List searchMails(NodeRef contextNodeRef, String namePattern, ImapViewMode viewMode, boolean includeSubFolders)
    {
        List result = new LinkedList();
        List searchResult = fileFolderService.search(contextNodeRef, namePattern, true, false, includeSubFolders);
        switch (viewMode)
        {
        case MIXED:
            result = searchResult;
            break;
        case ARCHIVE:
            for (FileInfo fileInfo : searchResult)
            {
                if (nodeService.hasAspect(fileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_CONTENT))
                {
                    result.add(fileInfo);
                }
            }
            break;
        case VIRTUAL:
            for (FileInfo fileInfo : searchResult)
            {
                if (!nodeService.hasAspect(fileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_CONTENT))
                {
                    result.add(fileInfo);
                }
            }
            break;
        }
        return result;
    }
    public void subscribe(AlfrescoImapUser user, String mailbox)
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("Subscribing: " + mailbox);
        }
        AlfrescoImapFolder mailFolder = getFolder(user, mailbox);
        nodeService.removeAspect(mailFolder.getFolderInfo().getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_NONSUBSCRIBED);
    }
    public void unsubscribe(AlfrescoImapUser user, String mailbox)
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("Unsubscribing: " + mailbox);
        }
        AlfrescoImapFolder mailFolder = getFolder(user, mailbox);
        nodeService.addAspect(mailFolder.getFolderInfo().getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_NONSUBSCRIBED, null);
    }
    /**
     * Return flags that belong to the specified imap folder.
     * 
     * @param messageInfo imap folder info.
     * @return flags.
     */
    public synchronized Flags getFlags(FileInfo messageInfo)
    {
        Flags flags = new Flags();
        checkForFlaggableAspect(messageInfo.getNodeRef());
        Map props = nodeService.getProperties(messageInfo.getNodeRef());
        for (QName key : qNameToFlag.keySet())
        {
            Boolean value = (Boolean) props.get(key);
            if (value != null && value)
            {
                flags.add(qNameToFlag.get(key));
            }
        }
        return flags;
    }
    /**
     * Set flags to the specified imapFolder.
     * 
     * @param messageInfo FileInfo of imap Folder.
     * @param flags flags to set.
     * @param value value to set.
     */
    public synchronized void setFlags(FileInfo messageInfo, Flags flags, boolean value)
    {
        checkForFlaggableAspect(messageInfo.getNodeRef());
        for (Flags.Flag flag : flags.getSystemFlags())
        {
            setFlag(messageInfo, flag, value);
        }
    }
    /**
     * Set flags to the specified imapFolder.
     * 
     * @param messageInfo FileInfo of imap Folder
     * @param flag flag to set.
     * @param value value value to set.
     */
    public void setFlag(FileInfo messageInfo, Flag flag, boolean value)
    {
        checkForFlaggableAspect(messageInfo.getNodeRef());
        nodeService.setProperty(messageInfo.getNodeRef(), flagToQname.get(flag), value);
    }
    /**
     * Depend on listSubscribed param, list Mailboxes or list subscribed Mailboxes
     */
    private List listMailboxes(AlfrescoImapUser user, String mailboxPattern, boolean listSubscribed)
    {
        List result = new LinkedList();
        Map mountPoints = getMountPoints();
        NodeRef mountPoint;
        // List mailboxes that are in mount points
        for (String mountPointName : mountPoints.keySet())
        {
            mountPoint = mountPoints.get(mountPointName);
            FileInfo mountPointFileInfo = fileFolderService.getFileInfo(mountPoint);
            NodeRef mountParent = nodeService.getParentAssocs(mountPoint).get(0).getParentRef();
            ImapViewMode viewMode = imapConfigMountPoints.get(mountPointName).getMode();
            if (!mailboxPattern.equals("*"))
            {
                mountPoint = mountParent;
            }
            List folders = listFolder(mountPoint, mountPoint, user, mailboxPattern, listSubscribed, viewMode);
            if (folders != null)
            {
                for (AlfrescoImapFolder mailFolder : folders)
                {
                    AlfrescoImapFolder folder = (AlfrescoImapFolder) mailFolder;
                    folder.setMountPointName(mountPointName);
                    folder.setViewMode(viewMode);
                    folder.setMountParent(mountParent);
                }
                result.addAll(folders);
            }
            // Add mount point to the result list
            if (mailboxPattern.equals("*"))
            {
                if ((listSubscribed && isSubscribed(mountPointFileInfo, user.getLogin())) || (!listSubscribed))
                {
                    result.add(
                            new AlfrescoImapFolder(
                                    user.getQualifiedMailboxName(),
                                    mountPointFileInfo,
                                    mountPointName,
                                    viewMode,
                                    mountParent,
                                    mountPointName,
                                    isExtractionEnabled(mountPointFileInfo.getNodeRef()),
                                    serviceRegistry));
                }
                // \NoSelect
                else if (listSubscribed && hasSubscribedChild(mountPointFileInfo, user.getLogin(), viewMode))
                {
                    result.add(
                            new AlfrescoImapFolder(
                                    user.getQualifiedMailboxName(),
                                    mountPointFileInfo,
                                    mountPointName,
                                    viewMode,
                                    mountParent,
                                    mountPointName,
                                    serviceRegistry,
                                    false,
                                    isExtractionEnabled(mountPointFileInfo.getNodeRef())));
                }
            }
        }
        // List mailboxes that are in user IMAP Home
        NodeRef root = getUserImapHomeRef(user.getLogin());
        List imapFolders = listFolder(root, root, user, mailboxPattern, listSubscribed, ImapViewMode.ARCHIVE);
        if (imapFolders != null)
        {
            for (AlfrescoImapFolder mailFolder : imapFolders)
            {
                AlfrescoImapFolder folder = (AlfrescoImapFolder) mailFolder;
                folder.setViewMode(ImapViewMode.ARCHIVE);
                folder.setMountParent(root);
            }
            result.addAll(imapFolders);
        }
        return result;
    }
    private List listFolder(
            NodeRef mailboxRoot,
            NodeRef root,
            AlfrescoImapUser user,
            String mailboxPattern,
            boolean listSubscribed,
            ImapViewMode viewMode)
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("Listing mailboxes: mailboxPattern=" + mailboxPattern);
        }
        int index = mailboxPattern.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER);
        String name = null;
        String remainName = null;
        if (index < 0)
        {
            name = mailboxPattern;
        }
        else
        {
            name = mailboxPattern.substring(0, index);
            remainName = mailboxPattern.substring(index + 1);
        }
        if (logger.isDebugEnabled())
        {
            logger.debug("Listing mailboxes: name=" + name);
        }
        if (index < 0)
        {
            if ("*".equals(name))
            {
                Collection list = searchFolders(root, name, true, viewMode);
                if (listSubscribed)
                {
                    list = getSubscribed(list, user.getLogin());
                }
                if (list.size() > 0)
                {
                    return createMailFolderList(user, list, mailboxRoot);
                }
                return null;
            }
            else if (name.endsWith("*"))
            {
                List fullList = new LinkedList();
                List list = searchFolders(root, name.replace('%', '*'), false, viewMode);
                Collection subscribedList = list;
                if (listSubscribed)
                {
                    subscribedList = getSubscribed(list, user.getLogin());
                }
                if (list.size() > 0)
                {
                    fullList.addAll(subscribedList);
                    for (FileInfo fileInfo : list)
                    {
                        List childList = searchFolders(fileInfo.getNodeRef(), "*", true, viewMode);
                        if (listSubscribed)
                        {
                            fullList.addAll(getSubscribed(childList, user.getLogin()));
                        }
                        else
                        {
                            fullList.addAll(childList);
                        }
                    }
                    return createMailFolderList(user, fullList, mailboxRoot);
                }
                return null;
            }
            else if ("%".equals(name))
            {
                List list = searchFolders(root, "*", false, viewMode);
                LinkedList subscribedList = new LinkedList();
                if (listSubscribed)
                {
                    for (FileInfo fileInfo : list)
                    {
                        if (isSubscribed(fileInfo, user.getLogin()))
                        {
                            // folderName, viewMode, mountPointName will be setted in listMailboxes() method
                            subscribedList.add(
                                    new AlfrescoImapFolder(
                                            user.getQualifiedMailboxName(),
                                            fileInfo,
                                            null,
                                            null,
                                            mailboxRoot,
                                            null,
                                            isExtractionEnabled(fileInfo.getNodeRef()),
                                            serviceRegistry));
                        }
                        // \NoSelect
                        else if (hasSubscribedChild(fileInfo, user.getLogin(), viewMode))
                        {
                            // folderName, viewMode, mountPointName will be setted in listMailboxes() method
                            subscribedList.add(
                                    new AlfrescoImapFolder(
                                            user.getQualifiedMailboxName(),
                                            fileInfo,
                                            null,
                                            null,
                                            mailboxRoot,
                                            null,
                                            serviceRegistry,
                                            false,
                                            isExtractionEnabled(fileInfo.getNodeRef())));
                        }
                    }
                }
                else
                {
                    return createMailFolderList(user, list, mailboxRoot);
                }
                return subscribedList;
            }
            else if (name.contains("%") || name.contains("*"))
            {
                List list = searchFolders(root, name.replace('%', '*'), false, viewMode);
                Collection subscribedList = list;
                if (listSubscribed)
                {
                    subscribedList = getSubscribed(list, user.getLogin());
                }
                if (subscribedList.size() > 0)
                {
                    return createMailFolderList(user, subscribedList, mailboxRoot);
                }
                return null;
            }
            else
            {
                List list = searchFolders(root, name, false, viewMode);
                Collection subscribedList = list;
                if (listSubscribed)
                {
                    subscribedList = getSubscribed(list, user.getLogin());
                }
                if (subscribedList.size() > 0)
                {
                    return createMailFolderList(user, subscribedList, mailboxRoot);
                }
                return null;
            }
        }
        // If (index != -1) this is not the last level
        List result = new LinkedList();
        List list = searchFolders(root, name.replace('%', '*'), false, viewMode);
        for (FileInfo folder : list)
        {
            Collection childFolders = listFolder(mailboxRoot, folder.getNodeRef(), user, remainName, listSubscribed, viewMode);
            if (childFolders != null)
            {
                result.addAll(childFolders);
            }
        }
        if (result.isEmpty())
        {
            return null;
        }
        return result;
    }
    /**
     * Convert mailpath from IMAP client representation to the alfresco representation view 
     * (e.g. with default settings "getMailPathInRepo(Repository_virtual.Imap Home)" will return "Company Home.Imap Home")
     * 
     * @param mailPath mailbox path in IMAP client
     * @return mailbox path in alfresco
     */
    private String getMailPathInRepo(String mailPath)
    {
        String rootFolder;
        String remain = "";
        int index = mailPath.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER);
        if (index > 0)
        {
            rootFolder = mailPath.substring(0, index);
            remain = mailPath.substring(index);
        }
        else
        {
            rootFolder = mailPath;
        }
        if (imapConfigMountPoints.keySet().contains(rootFolder))
        {
            Map mountPoints = getMountPoints();
            NodeRef rootRef = mountPoints.get(rootFolder);
            String rootName = nodeService.getProperty(rootRef, ContentModel.PROP_NAME).toString();
            return rootName + remain;
        }
        else
        {
            return mailPath;
        }
    }
    /**
     * Return mount point name for the current mailbox.
     * 
     * @param mailboxName mailbox name in IMAP client.
     * @return mount point name or null.
     */
    private String getMountPointName(String mailboxName)
    {
        String rootFolder;
        int index = mailboxName.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER);
        if (index > 0)
        {
            rootFolder = mailboxName.substring(0, index);
        }
        else
        {
            rootFolder = mailboxName;
        }
        if (imapConfigMountPoints.keySet().contains(rootFolder))
        {
            return rootFolder;
        }
        else
        {
            return null;
        }
    }
    /**
     * Map of mount points. Name of mount point == key in the map.
     * 
     * @return Map of mount points.
     */
    private Map getMountPoints()
    {
        Set mountPointNodeRefs = new HashSet(5);
        
        Map mountPoints = new HashMap();
        NamespaceService namespaceService = serviceRegistry.getNamespaceService();
        SearchService searchService = serviceRegistry.getSearchService();
        for (ImapConfigMountPointsBean config : imapConfigMountPoints.values())
        {
            // Get node reference
            NodeRef nodeRef = config.getFolderPath(namespaceService, nodeService, searchService, fileFolderService);
            if (!mountPointNodeRefs.add(nodeRef))
            {
                throw new IllegalArgumentException(
                        "A mount point has been defined twice: \n" +
                        "   Mount point: " + config);
            }
            mountPoints.put(config.getMountPointName(), nodeRef);
        }
        return mountPoints;
    }
    /**
     * Get root reference for the specified mailbox
     * 
     * @param mailboxName mailbox name in IMAP client.
     * @param userName
     * @return root reference for the specified mailbox
     */
    public NodeRef getMailboxRootRef(String mailboxName, String userName)
    {
        String rootFolder;
        int index = mailboxName.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER);
        if (index > 0)
        {
            rootFolder = mailboxName.substring(0, index);
        }
        else
        {
            rootFolder = mailboxName;
        }
        Map imapConfigs = imapConfigMountPoints;
        if (imapConfigs.keySet().contains(rootFolder))
        {
            Map mountPoints = getMountPoints();
            NodeRef mountRef = mountPoints.get(rootFolder);
            return nodeService.getParentAssocs(mountRef).get(0).getParentRef();
        }
        else
        {
            return getUserImapHomeRef(userName);
        }
    }
    /**
     * @param userName user name
     * @return user IMAP home reference and create it if it doesn't exist.
     */
    private NodeRef getUserImapHomeRef(final String userName)
    {
        NodeRef userHome = fileFolderService.searchSimple(imapHomeNodeRef, userName);
        if (userHome == null)
        {
            // create user home
            userHome = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork()
            {
                public NodeRef doWork() throws Exception
                {
                    NodeRef result = fileFolderService.create(imapHomeNodeRef, userName, ContentModel.TYPE_FOLDER).getNodeRef();
                    nodeService.setProperty(result, ContentModel.PROP_DESCRIPTION, userName);
                    // create inbox
                    fileFolderService.create(result, AlfrescoImapConst.INBOX_NAME, ContentModel.TYPE_FOLDER);
                    return result;
                }
            }, AuthenticationUtil.getSystemUserName());
        }
        return userHome;
    }
    private boolean isSubscribed(FileInfo fileInfo, String userName)
    {
        return !nodeService.hasAspect(fileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_NONSUBSCRIBED);
        // This is a multiuser support. Commented due new requirements
        // Map properties = fileInfo.getProperties();
        // String subscribedList = (String) properties.get(ImapModel.PROP_IMAP_FOLDER_SUBSCRIBED);
        // if (subscribedList == null)
        // {
        // return false;
        // }
        // else
        // {
        // return subscribedList.contains(imapHelper.formatUserEntry(userName));
        // }
    }
    private Collection getSubscribed(Collection list, String userName)
    {
        Collection result = new LinkedList();
        for (FileInfo folderInfo : list)
        {
            if (isSubscribed(folderInfo, userName))
            {
                result.add(folderInfo);
            }
        }
        return result;
    }
    private boolean hasSubscribedChild(FileInfo parent, String userName, ImapViewMode viewMode)
    {
        List list = searchFolders(parent.getNodeRef(), "*", true, viewMode);
        for (FileInfo fileInfo : list)
        {
            if (isSubscribed(fileInfo, userName))
            {
                return true;
            }
        }
        return false;
    }
    private List createMailFolderList(AlfrescoImapUser user, Collection list, NodeRef imapUserHomeRef)
    {
        List result = new LinkedList();
        for (FileInfo folderInfo : list)
        {
            // folderName, viewMode, mountPointName will be setted in listSubscribedMailboxes() method
            result.add(
                    new AlfrescoImapFolder(
                            user.getQualifiedMailboxName(),
                            folderInfo,
                            null,
                            null,
                            imapUserHomeRef,
                            null,
                            isExtractionEnabled(folderInfo.getNodeRef()),
                            serviceRegistry));
        }
        return result;
    }
    /**
     * Return view mode ("virtual", "archive" or "mixed") for specified mailbox.
     * 
     * @param mailboxName name of the mailbox in IMAP client.
     * @return view mode of the specified mailbox.
     */
    private ImapViewMode getViewMode(String mailboxName)
    {
        String rootFolder;
        int index = mailboxName.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER);
        if (index > 0)
        {
            rootFolder = mailboxName.substring(0, index);
        }
        else
        {
            rootFolder = mailboxName;
        }
        if (imapConfigMountPoints.keySet().contains(rootFolder))
        {
            return imapConfigMountPoints.get(rootFolder).getMode();
        }
        else
        {
            return ImapViewMode.ARCHIVE;
        }
    }
    private String getCurrentUser()
    {
        return AuthenticationUtil.getFullyAuthenticatedUser();
    }
    /**
     * Return list of sites, that belong to the specified user and not marked as "Imap favourite"
     * 
     * @param userName name of user
     * @return List of nonFavourite sites.
     */
    private List getNonFavouriteSites(final String userName)
    {
        List nonFavSites = new LinkedList();
        PreferenceService preferenceService = (PreferenceService) serviceRegistry.getService(ServiceRegistry.PREFERENCE_SERVICE);
        Map prefs = preferenceService.getPreferences(userName, AlfrescoImapConst.PREF_IMAP_FAVOURITE_SITES);
        List sites = serviceRegistry.getTransactionService().getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback>()
        {
            public List execute() throws Exception
           {
                List res = new ArrayList();
                try
               {
                    res = serviceRegistry.getSiteService().listSites(userName);
        }
        catch (SiteServiceException e)
        {
            //Do nothing. Root sites folder was not created.
            if (logger.isDebugEnabled())
            {
                logger.warn("Root sites folder was not created.");
            }
        }
        catch (InvalidNodeRefException e)
        {
            //Do nothing. Root sites folder was deleted.
            if (logger.isDebugEnabled())
            {
                logger.warn("Root sites folder was deleted.");
            }
        }
                return res;
            }
        }, false, true);
        for (SiteInfo siteInfo : sites)
        {
            String key = AlfrescoImapConst.PREF_IMAP_FAVOURITE_SITES + "." + siteInfo.getShortName();
            Boolean isImapFavourite = (Boolean) prefs.get(key);
            if (isImapFavourite == null || !isImapFavourite)
            {
                nonFavSites.add(siteInfo);
            }
        }
        return nonFavSites;
    }
    private void checkForFlaggableAspect(NodeRef nodeRef)
    {
        if (!nodeService.hasAspect(nodeRef, ImapModel.ASPECT_FLAGGABLE))
        {
            Map aspectProperties = new HashMap();
            nodeService.addAspect(nodeRef, ImapModel.ASPECT_FLAGGABLE, aspectProperties);
        }
    }
    private boolean isExtractionEnabled(NodeRef nodeRef)
    {
        return extractAttachmentsEnabled && !ignoreExtractionFolders.contains(nodeRef);
    }
    /**
     * This method should returns a unique identifier of Alfresco server. The possible UID may be calculated based on IP address, Server port, MAC address, Web Application context.
     * This UID should be parseable into initial components. This necessary for the implementation of the following case: If the message being copied (e.g. drag-and-drop) between
     * two different Alfresco accounts in the IMAP client, we must unambiguously identify from which Alfresco server this message being copied. The message itself does not contain
     * content data, so we must download it from the initial server (e.g. using download content servlet) and save it into destination repository.
     * 
     * @return String representation of unique identifier of Alfresco server
     */
    public String getAlfrescoServerUID()
    {
        // TODO Implement as javadoc says.
        return "Not-Implemented";
    }
}