Dave Ward cb4a571fa1 Merged V3.4-BUG-FIX to HEAD
29870: ALF-9952: Compatibility fix to allow mounting of Alfresco WebDAV as a network drive on Windows XP (without WebDrive)
   29872: Fixed ALF-7698 "Defects in tags picker in SHARE."
   - now checks added items to avoid duplicates
   29873: Made sure the onActionDetails doclib action generates SimpleDialog instance ids each time, to avoid multiple js class instances with the same id getting called for every dialog created.
   29878: ALF-9378: Use caching plus a read write lock, in a similar manner to ALF-7064, to avoid contention in SubsystemProxyFactory under load
   29881: Added helper text for the translators to the property file.
   29910: ALF-7433: a file deleted using the web UI still appears in a NFS mount but with NULL stats
   - timestamp propagation now enabled by default so NFS always works
   - timestamp propagation less agressive - only has to propagate on adds / deletes and renames
   29911: Values for recurrence strings updated now that I've read RFC-2445
   29944: ALF-9988: Merged PATCHES/V3.3.4 to V3.4-BUG-FIX
      28581: ALF-8944: Corrected sort parameter handling for datetime fields, based on analyzer and tokenization mode
   29955: Fixed ALF-9965: Property names matching fails if the property contains non Alphanumeric characters
   29956: Fixed ALF-9424: Webform(XSD): xf:switch switch group does not work based on "complexContent with Base" element.
   29964: Fixed ALF-9089: TinyMCE convert_fonts_to_spans parameter is not being picked up in Web Forms
   29977: L10N Updates from Gloria (based on rev29838).
   29980: Ignore virtual tomcat directory
   29981: Merged BRANCHES/DEV/BELARUS/V3.4-BUG-FIX-2011_08_19 to BRANCHES/DEV/V3.4-BUG-FIX 
       ALF-6808 : Incorrect Search Language Conversion for wild card characters (%, _) for MS SQL 
   29984: ALF-4753: Fix search query escaping in Alfresco Explorer
   - AWC-1743 / CHK-2171 fix restored
   29993: Merged DEV/TEMPORARY to V3.4-BUG-FIX
      29989: ALF-9976: WebDAV Class 2 compliance issue with OPTIONS response.
         Class 2 incompliance connected with absence of 'PROPPATCH' method in the 'Allow' response header has been fixed
   30009: ALF-7239 : Documents from Records Manager cannot be viewed in Outlook   
   30012: Merged BRANCHES/DEV/BELARUS/V3.4-BUG-FIX-2011_08_19 to BRANCHES/DEV/V3.4-BUG-FIX:
      29986: ALF-7105: pdfbox returns errors in the logs but one cannot understand what file is affected (PDFBox)
   30014: ALF-6284: Fix for Share Kerberos SSO Websphere compatibility (by Pavel)
   30016: Merged DEV to V3.4-BUG-FIX
      29424: ALF-8715: NFS: Admin sometimes cannot edit content uploaded via JSF
             Fix for "Can't open file for writing" during saving file in VI-like editors connected with FileExpiry daemon:
             - 'getNodeOrNull()' method extracted to get and check on existence of the cached file object
                and then update cache timeout or remove it from the cache;
             - 'getNodeForPath()' was appropriately updated in accordance with logic extracted to 'getNodeOrNull()';
             - 'fileExists()' method was corrected to take into account existence of the cached file object 
   30017: Reverses an accidental check in made with the last L10N bundle update (r29977)
   30045: ALF-8664 - Custom 'mandatory' RM metadata causes editing issues   
   30053: ALF-9681: webScriptsRegistryCache size in ehcache-default.xml   
      webScriptsRegistryCache maxElementsInMemory size was incremented up to 1000
   30079: ALF-10027: Fix template alfresco-global.properties for custom tomcat ports
   - Obselete web.application.context.url removed
   - alfresco.host, alfresco.port, share.host and share.port added
   30080: Fixed parse error mentioned in ALF-9511 "RSS Feeds with HTTP Auth doesn't work with Feed dashlet"
    - In java regexps the . (dot) does NOT include the \r or \n by default, to change that a (?s) instruction was added in the beginning of the regexp
   30083: Fixed ALF-10048 "Multiple thumbnails in RSS feeds causes problem for RSS Feed dashlet"
   30088: ALF-7433: Fix unit test fallout from new timestamp propagation behaviour
   30089: ALF-7433: Further unit test fallout from new timestamp propagation behaviour
   - testPermissionsAndPolicies needs to get folder modification date in a distinct transaction after populating it
   30091: ALF-10050: CIFS: Coordinator is unable to delete content after IMAP has been enabled
   - Transactional cache null entry issue
   30096: ALF-9793: Allow auto-created NFS authenticated users to work
   - Also removed admin from default mappings to avoid security hole
   30102: ALF-8723 CIFS on Windows fails to start under certain conditions
      - Found another place >= was required rather than just >
   30104: ALF-9890 SiteService is not producing audit data.
      - Internal siteService rather than public SiteService was being used, so there was no audit advice.
   30119: ALF-9793: Fallout from previous fix. Prevent initialization error when there are no user mappings and NFS is not enabled.
   30120: ALF-9526: Work around the fact that Websphere ignores the response encoding unless you call response.setCharacterEncoding()
   - GlobalLocalizationFilter inserts a response wrapper that parses the charset parameter from the content type header, if it is present
   30121: ALF-9535: Alfresco ignores Accept-Language sent from Share
   - Ticket and webscript authentication (i.e. Share requests) will use the language set in request headers as the session language
   30130: ALF-10049: Fix by Andrey to enable Flash upload on Weblogic!
   30141: ALF-8732 Now if the adhoc workflow throws exceptions while sending a notification email then those exceptions are ignored.
   30226: ALF-9415: JSF - Copy/Paste vs Cut/Paste add aspect rule.
   30242: Added NodeService.getNodeRef(Long nodeId)
    - First step to remove Lucene search from inbound email server
    - Done while investigating ALF-9660
   30243: Fixed ALF-9660: Inbound emails are always stored with encoding=UTF-8 regardless the encoding the email client uses.
    - If the encoding is provided, then it is not guessed
    - Includes tweak to remove Lucene search and rather use direct node ID-NodeRef translation provided by NodeService
   30263: Incremented version revision
   30264: ALF-10187: Merged V3.3 to V3.4-BUG-FIX
      30003: ALF-9898: More defensive exception handling to avoid packet pool leaks and extra logging on packet pool exhaustion
   30265: Merged V3.4 to V3.4-BUG-FIX
      30259: ALF-6527 LangPack FR - [Search results' page] Incorrect label when search matches results
        - Added "(s)" to "trouvé(s) dans l'entrepôt."   (for a repository search - found in retest)
        - The original change:
          Added "(s)" to "trouvé(s) dans tous les sites."   (for an All Sites search)
          
          However this leaves the current site search. As a result I also:
          Added "(s)" to "trouvé(s) dans le site {0}."  (for a current Site search)
          and checked with a French speaker that this would sound correct.
          
          Note: all three labels are prefixed by a separate text label: "N résultat(s) "
      30261: (RECORD ONLY) Merged V3.4-BUG-FIX to V3.4 (3.4.5)
      30262: ALF-10186: Merged PATCHES/V3.4.1 to V3.4
         30126: ALF-10075: Concurrency during CMIS document update causes content stream to close prematurely.
            - Created BufferedRequest request wrapper in RepositoryContainer
            - If the content stream is accessed directly it is streamed to a temporary file so that requests can be retried
         30137: ALF-10075: Fixed NPE


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@30271 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2011-09-06 16:14:53 +00:00

2540 lines
100 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 static org.alfresco.repo.imap.AlfrescoImapConst.DICTIONARY_TEMPLATE_PREFIX;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
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 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.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.model.ImapModel;
import org.alfresco.repo.admin.SysAdminParams;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.imap.AlfrescoImapConst.ImapViewMode;
import org.alfresco.repo.imap.config.ImapConfigMountPointsBean;
import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy;
import org.alfresco.repo.node.NodeServicePolicies.OnCreateChildAssociationPolicy;
import org.alfresco.repo.node.NodeServicePolicies.OnDeleteChildAssociationPolicy;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.site.SiteModel;
import org.alfresco.repo.site.SiteServiceException;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.lock.NodeLockedException;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.model.SubFolderFilter;
import org.alfresco.service.cmr.preference.PreferenceService;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.MimetypeService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.ResultSet;
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.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.apache.poi.hmef.HMEFMessage;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.extensions.surf.util.I18NUtil;
import org.springframework.util.FileCopyUtils;
import com.icegreen.greenmail.imap.ImapConstants;
/**
* @author Dmitry Vaserin
* @author Arseny Kovalchuk
* @since 3.2
*/
public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPolicy, OnDeleteChildAssociationPolicy
{
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 static final String ERROR_CANNOT_PARSE_DEFAULT_EMAIL = "imap.server.error.cannot_parse_default_email";
private static final String CHECKED_NODES = "imap.flaggable.aspect.checked.list";
private static final String FAVORITE_SITES = "imap.favorite.sites.list";
private static final String UIDVALIDITY_LISTENER_ALREADY_BOUND = "imap.uidvalidity.already.bound";
private SysAdminParams sysAdminParams;
private FileFolderService fileFolderService;
private NodeService nodeService;
private PermissionService permissionService;
private ServiceRegistry serviceRegistry;
private BehaviourFilter policyBehaviourFilter;
private MimetypeService mimetypeService;
/**
* Folders cache
*
* Key : folder name, Object : AlfrescoImapFolder
*/
private SimpleCache<Serializable, Object> foldersCache;
private Map<String, ImapConfigMountPointsBean> imapConfigMountPoints;
private RepositoryFolderConfigBean[] ignoreExtractionFoldersBeans;
private RepositoryFolderConfigBean imapHomeConfigBean;
private NodeRef imapHomeNodeRef;
private Set<NodeRef> ignoreExtractionFolders;
private String defaultFromAddress;
private String defaultToAddress;
private String repositoryTemplatePath;
private boolean extractAttachmentsEnabled = true;
private Map<EmailBodyFormat, String> defaultBodyTemplates;
private final static Map<QName, Flags.Flag> qNameToFlag;
private final static Map<Flags.Flag, QName> flagToQname;
private boolean imapServerEnabled = false;
static
{
qNameToFlag = new HashMap<QName, Flags.Flag>();
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<Flags.Flag, QName>();
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;
public void setService(ImapServiceImpl service)
{
this.service = service;
}
@Override
protected void onBootstrap(ApplicationEvent event)
{
if (service.getImapServerEnabled())
{
service.startup();
}
}
@Override
protected void onShutdown(ApplicationEvent event)
{
if (service.getImapServerEnabled())
{
service.shutdown();
}
}
}
public void setSysAdminParams(SysAdminParams sysAdminParams)
{
this.sysAdminParams = sysAdminParams;
}
public void setFoldersCache(SimpleCache<Serializable, Object> foldersCache)
{
this.foldersCache = foldersCache;
}
public SimpleCache<Serializable, Object> getFoldersCache()
{
return foldersCache;
}
public FileFolderService getFileFolderService()
{
return fileFolderService;
}
public void setFileFolderService(FileFolderService fileFolderService)
{
this.fileFolderService = fileFolderService;
}
public void setMimetypeService(MimetypeService mimetypeService)
{
this.mimetypeService = mimetypeService;
}
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
public void setPermissionService(PermissionService permissionService)
{
this.permissionService = permissionService;
}
public void setServiceRegistry(ServiceRegistry serviceRegistry)
{
this.serviceRegistry = serviceRegistry;
}
public void setPolicyFilter(BehaviourFilter policyFilter)
{
this.policyBehaviourFilter = policyFilter;
}
public void setImapHome(RepositoryFolderConfigBean imapHomeConfigBean)
{
this.imapHomeConfigBean = imapHomeConfigBean;
}
public String getDefaultFromAddress()
{
return defaultFromAddress;
}
public void setDefaultFromAddress(String defaultFromAddress)
{
this.defaultFromAddress = defaultFromAddress;
}
public String getDefaultToAddress()
{
return defaultToAddress;
}
public void setDefaultToAddress(String defaultToAddress)
{
this.defaultToAddress = defaultToAddress;
}
public String getWebApplicationContextUrl()
{
return sysAdminParams.getAlfrescoProtocol() + "://" + sysAdminParams.getAlfrescoHost() + ":" + sysAdminParams.getAlfrescoPort() + "/" + sysAdminParams.getAlfrescoContext();
}
public String getShareApplicationContextUrl()
{
return sysAdminParams.getShareProtocol() + "://" + sysAdminParams.getShareHost() + ":" + sysAdminParams.getSharePort() + "/" + sysAdminParams.getShareContext();
}
public String getRepositoryTemplatePath()
{
return repositoryTemplatePath;
}
public void setRepositoryTemplatePath(String repositoryTemplatePath)
{
this.repositoryTemplatePath = repositoryTemplatePath;
}
public void setImapConfigMountPoints(ImapConfigMountPointsBean[] imapConfigMountPointsBeans)
{
this.imapConfigMountPoints = new LinkedHashMap<String, ImapConfigMountPointsBean>(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;
}
public void setImapServerEnabled(boolean enabled)
{
this.imapServerEnabled = enabled;
}
public boolean getImapServerEnabled()
{
return this.imapServerEnabled;
}
// ---------------------- 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, "permissionService", permissionService);
PropertyCheck.mandatory(this, "serviceRegistry", serviceRegistry);
PropertyCheck.mandatory(this, "defaultFromAddress", defaultFromAddress);
PropertyCheck.mandatory(this, "defaultToAddress", defaultToAddress);
PropertyCheck.mandatory(this, "repositoryTemplatePath", repositoryTemplatePath);
PropertyCheck.mandatory(this, "policyBehaviourFilter", policyBehaviourFilter);
PropertyCheck.mandatory(this, "mimetypeService", mimetypeService);
// be sure that a default e-mail is correct
try
{
InternetAddress.parse(defaultFromAddress);
}
catch (AddressException ex)
{
throw new AlfrescoRuntimeException(
ERROR_CANNOT_PARSE_DEFAULT_EMAIL,
new Object[] {defaultFromAddress});
}
try
{
InternetAddress.parse(defaultToAddress);
}
catch (AddressException ex)
{
throw new AlfrescoRuntimeException(
ERROR_CANNOT_PARSE_DEFAULT_EMAIL,
new Object[] {defaultToAddress});
}
}
public void startup()
{
bindBeahaviour();
final NamespaceService namespaceService = serviceRegistry.getNamespaceService();
final SearchService searchService = serviceRegistry.getSearchService();
// Hit the mount points for early failure
AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Void>()
{
public Void doWork() throws Exception
{
getMountPoints();
return null;
}
}, AuthenticationUtil.getSystemUserName());
// Get NodeRefs for folders to ignore
this.ignoreExtractionFolders = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Set<NodeRef>>()
{
public Set<NodeRef> doWork() throws Exception
{
Set<NodeRef> result = new HashSet<NodeRef>(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<NodeRef>()
{
public NodeRef doWork() throws Exception
{
return imapHomeConfigBean.getOrCreateFolderPath(namespaceService, nodeService, searchService, fileFolderService);
}
}, AuthenticationUtil.getSystemUserName());
}
public void shutdown()
{
}
protected void bindBeahaviour()
{
if (logger.isDebugEnabled())
{
logger.debug("[bindBeahaviour] Binding behaviours");
}
PolicyComponent policyComponent = (PolicyComponent) serviceRegistry.getService(QName.createQName(NamespaceService.ALFRESCO_URI, "policyComponent"));
policyComponent.bindAssociationBehaviour(
OnCreateChildAssociationPolicy.QNAME,
ContentModel.TYPE_FOLDER,
ContentModel.ASSOC_CONTAINS,
new JavaBehaviour(this, "onCreateChildAssociation", NotificationFrequency.TRANSACTION_COMMIT));
policyComponent.bindAssociationBehaviour(
OnCreateChildAssociationPolicy.QNAME,
ContentModel.TYPE_CONTENT,
ContentModel.ASSOC_CONTAINS,
new JavaBehaviour(this, "onCreateChildAssociation", NotificationFrequency.TRANSACTION_COMMIT));
policyComponent.bindAssociationBehaviour(
OnDeleteChildAssociationPolicy.QNAME,
ContentModel.TYPE_FOLDER,
ContentModel.ASSOC_CONTAINS,
new JavaBehaviour(this, "onDeleteChildAssociation", NotificationFrequency.TRANSACTION_COMMIT));
policyComponent.bindAssociationBehaviour(
OnDeleteChildAssociationPolicy.QNAME,
ContentModel.TYPE_CONTENT,
ContentModel.ASSOC_CONTAINS,
new JavaBehaviour(this, "onDeleteChildAssociation", NotificationFrequency.TRANSACTION_COMMIT));
policyComponent.bindClassBehaviour(
BeforeDeleteNodePolicy.QNAME,
ContentModel.TYPE_CONTENT,
new JavaBehaviour(this, "beforeDeleteNode", NotificationFrequency.EVERY_EVENT));
policyComponent.bindClassBehaviour(
BeforeDeleteNodePolicy.QNAME,
ContentModel.TYPE_FOLDER,
new JavaBehaviour(this, "beforeDeleteNode", NotificationFrequency.EVERY_EVENT));
}
// ---------------------- Service Methods --------------------------------
public List<AlfrescoImapFolder> 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: mailbox path in Alfresco=" + mailboxPattern);
}
return listMailboxes(user, mailboxPattern, true);
}
public List<AlfrescoImapFolder> 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: mailbox path in Alfresco Repository = " + 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 mailbox: " + 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)))
{
NodeRef child = fileFolderService.searchSimple(parentNodeRef, folderName);
if (logger.isDebugEnabled())
{
logger.debug("Trying to create folder '" + folderName + "'");
}
if (child == null)
{
// folder doesn't exist
AccessStatus status = permissionService.hasPermission(parentNodeRef, PermissionService.CREATE_CHILDREN);
if (status == AccessStatus.DENIED)
{
throw new AlfrescoRuntimeException(ERROR_PERMISSION_DENIED);
}
FileInfo mailFolder = serviceRegistry.getFileFolderService().create(parentNodeRef, folderName, ContentModel.TYPE_FOLDER);
AlfrescoImapFolder resultFolder = new AlfrescoImapFolder(
user.getQualifiedMailboxName(),
mailFolder,
folderName,
getViewMode(mailboxName),
root,
getMountPointName(mailboxName),
isExtractionEnabled(mailFolder.getNodeRef()),
serviceRegistry);
foldersCache.put(mailboxName, resultFolder);
return resultFolder;
}
else
{
// folder already exists
if (logger.isDebugEnabled())
{
logger.debug("Folder '" + folderName + "' already exists");
}
// next search from new parent
parentNodeRef = child;
}
}
throw new AlfrescoRuntimeException(ERROR_FOLDER_ALREADY_EXISTS);
}
public void deleteMailbox(AlfrescoImapUser user, String mailboxName)
{
if (logger.isDebugEnabled())
{
logger.debug("Deleting mailbox: mailboxName=" + mailboxName);
}
if (mailboxName == null)
{
throw new IllegalArgumentException(I18NUtil.getMessage(ERROR_MAILBOX_NAME_IS_MANDATORY));
}
AlfrescoImapFolder folder = getFolder(user, mailboxName);
NodeRef nodeRef = folder.getFolderInfo().getNodeRef();
List<FileInfo> childFolders = fileFolderService.listFolders(nodeRef);
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<FileInfo> messages = fileFolderService.listFiles(nodeRef);
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.");
}
}
foldersCache.remove(mailboxName);
}
public void renameMailbox(AlfrescoImapUser user, String oldMailboxName, String newMailboxName)
{
if (oldMailboxName == null || newMailboxName == null)
{
throw new IllegalArgumentException(ERROR_MAILBOX_NAME_IS_MANDATORY);
}
AlfrescoImapFolder sourceNode = getFolder(user, oldMailboxName);
oldMailboxName = Utf7.decode(oldMailboxName, Utf7.UTF7_MODIFIED);
newMailboxName = Utf7.decode(newMailboxName, Utf7.UTF7_MODIFIED);
if (logger.isDebugEnabled())
{
logger.debug("Renaming folder oldMailboxName=" + oldMailboxName + " newMailboxName=" + newMailboxName);
}
NodeRef root = getMailboxRootRef(oldMailboxName, user.getLogin());
String[] folderNames = getMailPathInRepo(newMailboxName).split(String.valueOf(AlfrescoImapConst.HIERARCHY_DELIMITER));
String folderName = null;
NodeRef parentNodeRef = root; // initial root for search
try
{
for (int i = 0; i < folderNames.length; i++)
{
folderName = folderNames[i];
if (i == (folderNames.length - 1)) // is it the last element
{
FileInfo newFileInfo = null;
if (oldMailboxName.equalsIgnoreCase(AlfrescoImapConst.INBOX_NAME))
{
// If you trying to rename INBOX
// - just copy it to another folder with new name
// and leave INBOX (with children) intact.
newFileInfo = fileFolderService.copy(sourceNode.getFolderInfo().getNodeRef(), parentNodeRef, folderName);
}
else
{
newFileInfo = fileFolderService.move(
sourceNode.getFolderInfo().getNodeRef(),
parentNodeRef, folderName);
}
foldersCache.remove(oldMailboxName);
AlfrescoImapFolder resultFolder = new AlfrescoImapFolder(
user.getQualifiedMailboxName(),
newFileInfo,
folderName,
getViewMode(newMailboxName),
root,
getMountPointName(newMailboxName),
isExtractionEnabled(newFileInfo.getNodeRef()),
serviceRegistry);
foldersCache.put(newMailboxName, resultFolder);
}
else
{
// not last element than checks if it exists and creates if doesn't
NodeRef child = fileFolderService.searchSimple(parentNodeRef, folderName);
if (child == null)
{
// check creation permission
AccessStatus status = permissionService.hasPermission(parentNodeRef, PermissionService.CREATE_CHILDREN);
if (status == AccessStatus.DENIED)
{
throw new AlfrescoRuntimeException(ERROR_PERMISSION_DENIED);
}
if (logger.isDebugEnabled())
{
logger.debug("Creating folder '" + folderName + "'");
}
serviceRegistry.getFileFolderService().create(parentNodeRef, folderName, ContentModel.TYPE_FOLDER);
}
else
{
parentNodeRef = child;
if (logger.isDebugEnabled())
{
logger.debug("Folder '" + folderName + "' already exists");
}
}
}
}
}
catch (Exception e)
{
if (e instanceof AlfrescoRuntimeException)
{
throw (AlfrescoRuntimeException) e;
}
else
{
throw new AlfrescoRuntimeException(e.getMessage(), e);
}
}
}
public AlfrescoImapFolder getFolder(AlfrescoImapUser user, String mailboxName)
{
if (logger.isDebugEnabled())
{
logger.debug("Folders cache size is " + foldersCache.getKeys().size());
}
mailboxName = Utf7.decode(mailboxName, Utf7.UTF7_MODIFIED);
if (logger.isDebugEnabled())
{
logger.debug("Get folder '" + mailboxName + "'");
}
// If MailFolder object is used to obtain hierarchy delimiter by LIST command:
// Example:
// C: 2 list "" ""
// S: * LIST () "." ""
// S: 2 OK LIST completed.
if ("".equals(mailboxName))
{
if (logger.isDebugEnabled())
{
logger.debug("Request for the hierarchy delimiter");
}
AlfrescoImapFolder hierarhyFolder = (AlfrescoImapFolder) foldersCache.get("hierarhy.delimeter");
if (logger.isDebugEnabled())
{
logger.debug("Got a hierarhy delimeter from cache: " + hierarhyFolder);
}
if (hierarhyFolder == null)
{
hierarhyFolder = new AlfrescoImapFolder(user.getQualifiedMailboxName(), serviceRegistry);
// TEMP Comment out putting this into the same cache as the "real folders" Causes NPE in
// Security Interceptor
//foldersCache.put("hierarhy.delimeter", hierarhyFolder);
}
return hierarhyFolder;
}
else if (AlfrescoImapConst.INBOX_NAME.equalsIgnoreCase(mailboxName) || AlfrescoImapConst.TRASH_NAME.equalsIgnoreCase(mailboxName))
{
String cacheKey = user.getLogin() + '.' + mailboxName;
AlfrescoImapFolder imapSystemFolder = (AlfrescoImapFolder) foldersCache.get(cacheKey);
if(imapSystemFolder != null)
{
if (logger.isDebugEnabled())
{
logger.debug("Got a system folder '" + mailboxName + "' from cache: " + imapSystemFolder);
}
/**
* Check whether resultFolder is stale
*/
if(imapSystemFolder.isStale())
{
logger.debug("system folder is stale");
imapSystemFolder = null;
}
}
if (imapSystemFolder == null)
{
NodeRef userImapRoot = getUserImapHomeRef(user.getLogin());
NodeRef mailBoxRef = nodeService.getChildByName(userImapRoot, ContentModel.ASSOC_CONTAINS, mailboxName);
if (mailBoxRef != null)
{
FileInfo mailBoxFileInfo = fileFolderService.getFileInfo(mailBoxRef);
imapSystemFolder = new AlfrescoImapFolder(
user.getQualifiedMailboxName(),
mailBoxFileInfo,
mailBoxFileInfo.getName(),
getViewMode(mailboxName),
userImapRoot,
getMountPointName(mailboxName),
isExtractionEnabled(mailBoxFileInfo.getNodeRef()),
serviceRegistry);
foldersCache.put(cacheKey, imapSystemFolder);
if (logger.isDebugEnabled())
{
logger.debug("Returning folder '" + mailboxName + "'");
}
}
else
{
if (logger.isDebugEnabled())
{
logger.debug("Cannot get a folder '" + mailboxName + "'");
}
throw new AlfrescoRuntimeException(ERROR_CANNOT_GET_A_FOLDER, new String[] { mailboxName });
}
}
return imapSystemFolder;
}
/**
* Folder is not hierarchy.delimiter or a "System" folder (INBOX or TRASH)
*/
AlfrescoImapFolder resultFolder = (AlfrescoImapFolder) foldersCache.get(mailboxName);
if(resultFolder != null)
{
if (logger.isDebugEnabled())
{
logger.debug("Got a folder '" + mailboxName + "' from cache: " + resultFolder);
}
/**
* Check whether resultFolder is stale
*/
/*
if(resultFolder.isStale())
{
logger.debug("folder is stale");
resultFolder = null;
}
*/
}
if (resultFolder == null)
{
ImapViewMode viewMode = getViewMode(mailboxName);
String mountPointName = getMountPointName(mailboxName);
NodeRef root = getMailboxRootRef(mailboxName, user.getLogin());
NodeRef nodeRef = root; // initial top folder
String[] folderNames = getMailPathInRepo(mailboxName).split(String.valueOf(AlfrescoImapConst.HIERARCHY_DELIMITER));
if(folderNames.length == 1 && folderNames[0].length() == 0)
{
// This is the root of the mount point e.g "Alfresco IMAP" which has a path from root of ""
FileInfo folderFileInfo = fileFolderService.getFileInfo(root);
resultFolder = new AlfrescoImapFolder(
user.getQualifiedMailboxName(),
folderFileInfo,
mountPointName,
viewMode,
root,
mountPointName,
isExtractionEnabled(folderFileInfo.getNodeRef()),
serviceRegistry);
if (logger.isDebugEnabled())
{
logger.debug("Returning root folder '" + mailboxName + "'");
}
}
else
{
for (int i = 0; i < folderNames.length; i++)
{
if (logger.isDebugEnabled())
{
logger.debug("Processing of '" + folderNames[i] + "'");
}
NodeRef targetNode = fileFolderService.searchSimple(nodeRef, folderNames[i]);
if (i == 0 && targetNode == null)
{
resultFolder = new AlfrescoImapFolder(user.getQualifiedMailboxName(), serviceRegistry);
if (logger.isDebugEnabled())
{
logger.debug("Returning empty folder '" + folderNames[i] + "'");
}
// skip cache for root
return resultFolder;
}
if (i == (folderNames.length - 1)) // is last
{
FileInfo folderFileInfo = fileFolderService.getFileInfo(targetNode);
if (logger.isDebugEnabled())
{
logger.debug("Found folder to list: " + folderFileInfo.getName());
}
resultFolder = new AlfrescoImapFolder(
user.getQualifiedMailboxName(),
folderFileInfo,
folderFileInfo.getName(),
viewMode,
root,
mountPointName,
isExtractionEnabled(folderFileInfo.getNodeRef()),
serviceRegistry);
if (logger.isDebugEnabled())
{
logger.debug("Returning folder '" + mailboxName + "'");
}
}
/**
* End of loop - next element in path
*/
nodeRef = targetNode;
}
}
if (resultFolder == null)
{
if (logger.isDebugEnabled())
{
logger.debug("Cannot get a folder '" + mailboxName + "'");
}
throw new AlfrescoRuntimeException(ERROR_CANNOT_GET_A_FOLDER, new String[] { mailboxName });
}
else
{
foldersCache.put(mailboxName, resultFolder);
if (logger.isDebugEnabled())
{
logger.debug("Put a folder '" + mailboxName + "' to cache: " + resultFolder);
}
}
}
return resultFolder;
}
/**
* Deep search for mailboxes/folders in the specified context
*
* Certain folders are excluded depending upon the view mode.
* - For ARCHIVE mode all Share Sites are excluded.
* - For MIXED and VIRTUAL non favourite sites are excluded.
*
* @param contextNodeRef context folder for search
* @param viewMode is folder in "Virtual" View
* @return list of mailboxes/folders
*/
private List<FileInfo> searchDeep(final NodeRef contextNodeRef, final ImapViewMode viewMode)
{
if (logger.isDebugEnabled())
{
logger.debug("[searchDeep] Start. nodeRef=" + contextNodeRef + ", viewMode=" + viewMode);
}
List<FileInfo> searchResult = fileFolderService.listDeepFolders(contextNodeRef, new ImapSubFolderFilter(viewMode));
if (logger.isDebugEnabled())
{
logger.debug("[searchDeep] End");
}
return new ArrayList<FileInfo>(searchResult);
}
/**
* Shallow search for mailboxes in specified context
*
* @param contextNodeRef context folder for search
* @param viewMode is folder in "Virtual" View
* @param namePattern name pattern for search
* @return list of mailboxes
*/
private List<FileInfo> searchByPattern(final NodeRef contextNodeRef, final ImapViewMode viewMode, final String namePattern)
{
if (logger.isDebugEnabled())
{
logger.debug("[searchByPattern] Start. nodeRef=" + contextNodeRef + ", viewMode=" + viewMode + " namePattern=" + namePattern);
}
List<FileInfo> searchResult;
/**
* Shallow search for all folders below contextNodeRef
*/
if("*".equals(namePattern))
{
/**
* This is a simple listing of all folders below contextNodeRef
*/
logger.debug("call file folder service to list folders");
searchResult = fileFolderService.listFolders(contextNodeRef);
}
else
{
logger.debug("call listDeepFolders");
searchResult = fileFolderService.listDeepFolders(contextNodeRef, new ImapSubFolderFilter(viewMode, namePattern));
}
// DO WE NEED TO WORRY ABOUT THE STUFF BELOW ?
// WOULD ONLY BE RELEVANT if ContextNodeRef is site root.
// IF contextNodeRef is the imap home or the contextNodeRef is in the depths of a non favourite site.
//
// Set<FileInfo> result = new HashSet<FileInfo>(searchResult);
// if (viewMode == ImapViewMode.VIRTUAL || viewMode == ImapViewMode.MIXED)
// {
// /**
// * In VIRTUAL and MIXED MODE WE SHOULD ONLY DISPLAY FAVOURITE SITES
// */
// List<SiteInfo> nonFavSites = getNonFavouriteSites(getCurrentUser());
// for (SiteInfo siteInfo : nonFavSites)
// {
// FileInfo nonFavSite = fileFolderService.getFileInfo(siteInfo.getNodeRef());
//
// // search deep for all folders in the site
// List<FileInfo> siteChilds = fileFolderService.search(nonFavSite.getNodeRef(), namePattern, false, true, true);
// result.removeAll(siteChilds);
// result.remove(nonFavSite);
// }
//
// }
// else
// {
// /**
// * IN ARCHIVE MODE we don't display folders any SITES
// */
// // Remove folders from Sites
// List<SiteInfo> sites = serviceRegistry.getTransactionService().getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<List<SiteInfo>>()
// {
// public List<SiteInfo> execute() throws Exception
// {
// List<SiteInfo> res = new ArrayList<SiteInfo>();
// try
// {
//
// res = serviceRegistry.getSiteService().listSites(getCurrentUser());
// }
// catch (SiteServiceException e)
// {
// // Do nothing. Root sites folder was not created.
// if (logger.isWarnEnabled())
// {
// logger.warn("Root sites folder was not created.");
// }
// }
// catch (InvalidNodeRefException e)
// {
// // Do nothing. Root sites folder was deleted.
// if (logger.isWarnEnabled())
// {
// logger.warn("Root sites folder was deleted.");
// }
// }
//
// if (logger.isDebugEnabled())
// {
// logger.debug("Search folders return ");
// }
//
// return res;
// }
// }, false, true);
//
// for (SiteInfo siteInfo : sites)
// {
// List<FileInfo> siteChilds = fileFolderService.search(siteInfo.getNodeRef(), namePattern, false, true, true);
// result.removeAll(siteChilds);
// // remove site
// result.remove(fileFolderService.getFileInfo(siteInfo.getNodeRef()));
// }
//
// }
if (logger.isDebugEnabled())
{
if (logger.isDebugEnabled())
{
logger.debug("[searchByPattern] End. nodeRef=" + contextNodeRef + ", viewMode=" + viewMode + ", namePattern=" + namePattern + ", searchResult=" +searchResult.size());
}
}
return searchResult;
}
/**
* Search for emails in specified folder depending on view mode.
*
* Shallow list of files
*
* @param contextNodeRef context folder for search
* @param viewMode context folder view mode
* @return list of emails that context folder contains.
*/
public List<FileInfo> searchMails(NodeRef contextNodeRef, ImapViewMode viewMode)
{
if (logger.isDebugEnabled())
{
logger.debug("Search mails contextNodeRef=" + contextNodeRef + ", viewMode=" + viewMode );
}
List<FileInfo> searchResult = fileFolderService.listFiles(contextNodeRef);
List<FileInfo> result = new LinkedList<FileInfo>();
//List<FileInfo> 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;
}
logger.debug("Found files:" + result.size());
return result;
}
public void subscribe(AlfrescoImapUser user, String mailbox)
{
if (logger.isDebugEnabled())
{
logger.debug("Subscribing: " + user + ", " + 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: " + user + ", " + mailbox);
}
AlfrescoImapFolder mailFolder = getFolder(user, mailbox);
if(mailFolder.getFolderInfo() != null)
{
logger.debug("Unsubscribing by ASPECT_IMAP_FOLDER_NONSUBSCRIBED");
nodeService.addAspect(mailFolder.getFolderInfo().getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_NONSUBSCRIBED, null);
}
else
{
// perhaps the folder has been deleted by another async process?
logger.debug("Unable to find folder to unsubscribe");
}
}
/**
* 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();
if (nodeService.exists(messageInfo.getNodeRef()))
{
checkForFlaggableAspect(messageInfo.getNodeRef());
Map<QName, Serializable> 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)
{
NodeRef nodeRef = messageInfo.getNodeRef();
checkForFlaggableAspect(nodeRef);
AccessStatus status = permissionService.hasPermission(nodeRef, PermissionService.WRITE_PROPERTIES);
if (status == AccessStatus.DENIED)
{
logger.debug("[setFlag] Access denied to add FLAG to " + nodeRef);
//TODO should we throw an exception here?
}
else
{
nodeService.setProperty(messageInfo.getNodeRef(), flagToQname.get(flag), value);
}
}
/**
* Depend on listSubscribed param, list Mailboxes or list subscribed Mailboxes
*/
private List<AlfrescoImapFolder> listMailboxes(AlfrescoImapUser user, String mailboxPattern, boolean listSubscribed)
{
if(logger.isDebugEnabled())
{
logger.debug("[listMailboxes] user:" + user.getLogin() + ", mailboxPattern:" + mailboxPattern + ", listSubscribed:" + listSubscribed);
}
List<AlfrescoImapFolder> result = new LinkedList<AlfrescoImapFolder>();
Map<String, NodeRef> 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();
/* FIX for ALF-2793 Reinstated
if (!mailboxPattern.equals("*"))
{
mountPoint = mountParent;
}
*/
List<AlfrescoImapFolder> folders = expandFolder(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<AlfrescoImapFolder> imapFolders = expandFolder(root, root, user, mailboxPattern, listSubscribed, ImapViewMode.ARCHIVE);
if (imapFolders != null)
{
for (AlfrescoImapFolder mailFolder : imapFolders)
{
//AlfrescoImapFolder folder = (AlfrescoImapFolder) mailFolder;
mailFolder.setViewMode(ImapViewMode.ARCHIVE);
mailFolder.setMountParent(root);
}
result.addAll(imapFolders);
}
logger.debug("listMailboxes returning size:" + result.size());
StringBuilder prefix = new StringBuilder(128);
prefix.append(ImapConstants.USER_NAMESPACE)
.append(AlfrescoImapConst.HIERARCHY_DELIMITER)
.append(user.getQualifiedMailboxName())
.append(AlfrescoImapConst.HIERARCHY_DELIMITER);
int prefixLength = prefix.length();
for(AlfrescoImapFolder folder : result)
{
String cacheKey = folder.getFullName().substring(prefixLength + 1, folder.getFullName().length() - 1);
logger.debug("[listMailboxes] Adding the cache entry : " + cacheKey);
foldersCache.put(cacheKey, folder);
}
return result;
}
/**
* Get the list of folders
*
* @param mailboxRoot
* @param root
* @param user
* @param mailboxPattern
* @param listSubscribed
* @param viewMode
* @return
*/
private List<AlfrescoImapFolder> expandFolder(
NodeRef mailboxRoot,
NodeRef root,
AlfrescoImapUser user,
String mailboxPattern,
boolean listSubscribed,
ImapViewMode viewMode)
{
if (logger.isDebugEnabled())
{
logger.debug("expand folder: root:" + root + " user: " + user + " :mailboxPattern=" + mailboxPattern);
}
if (mailboxPattern == null)
return null;
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)
{
// This is the last level
if ("*".equals(name))
{
// Deep listing of all folders
Collection<FileInfo> list = searchDeep(root, viewMode);
if (listSubscribed)
{
list = getSubscribed(list, user.getLogin());
}
if (list.size() > 0)
{
return createMailFolderList(user, list, mailboxRoot);
}
return null;
}
else if (name.endsWith("*"))
{
// Ends with wildcard
List<FileInfo> fullList = new LinkedList<FileInfo>();
List<FileInfo> list = searchByPattern(root, viewMode, name.replace('%', '*'));
Collection<FileInfo> subscribedList = list;
if (listSubscribed)
{
subscribedList = getSubscribed(list, user.getLogin());
}
if (list.size() > 0)
{
fullList.addAll(subscribedList);
for (FileInfo fileInfo : list)
{
List<FileInfo> childList = searchDeep(fileInfo.getNodeRef(), viewMode);
if (listSubscribed)
{
fullList.addAll(getSubscribed(childList, user.getLogin()));
}
else
{
fullList.addAll(childList);
}
}
return createMailFolderList(user, fullList, mailboxRoot);
}
return null;
}
else if ("%".equals(name))
{
// Non recursive listing
List<AlfrescoImapFolder> subscribedList = new LinkedList<AlfrescoImapFolder>();
List<FileInfo> list = searchByPattern(root, viewMode, "*");
if (listSubscribed)
{
for (FileInfo fileInfo : list)
{
if (isSubscribed(fileInfo, user.getLogin()))
{
// folderName, viewMode, mountPointName will be set in listMailboxes() method
subscribedList.add(
new AlfrescoImapFolder(
user.getQualifiedMailboxName(),
fileInfo,
null,
null,
mailboxRoot,
null,
isExtractionEnabled(fileInfo.getNodeRef()),
serviceRegistry));
}
// \NoSelect
else if (hasSubscribedChild(fileInfo, user.getLogin(), viewMode))
{
// folderName, viewMode, mountPointName will be set in listMailboxes() method
subscribedList.add(
new AlfrescoImapFolder(
user.getQualifiedMailboxName(),
fileInfo,
null,
null,
mailboxRoot,
null,
serviceRegistry,
false,
isExtractionEnabled(fileInfo.getNodeRef())));
}
}
}
else
{
return createMailFolderList(user, list, mailboxRoot);
}
return subscribedList;
}
else if (name.contains("%") || name.contains("*"))
{
// wild cards in the middle of the name
List<FileInfo> list = searchByPattern(root, viewMode, name.replace('%', '*'));
Collection<FileInfo> subscribedList = list;
if (listSubscribed)
{
subscribedList = getSubscribed(list, user.getLogin());
}
if (subscribedList.size() > 0)
{
return createMailFolderList(user, subscribedList, mailboxRoot);
}
return null;
}
else
{
// No wild cards
List<FileInfo> list = searchByPattern(root, viewMode, name);
Collection<FileInfo> 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<AlfrescoImapFolder> result = new LinkedList<AlfrescoImapFolder>();
List<FileInfo> list = searchByPattern(root, viewMode, name.replace('%', '*'));
for (FileInfo folder : list)
{
Collection<AlfrescoImapFolder> childFolders = expandFolder(mailboxRoot, folder.getNodeRef(), user, remainName, listSubscribed, viewMode);
if (childFolders != null)
{
result.addAll(childFolders);
}
}
return !result.isEmpty() ? result : null;
}
/**
* Convert mailpath from IMAP client representation to the alfresco representation view
* (e.g. with default settings
* "getMailPathInRepo(Repository_virtual/Imap Home)" will return "Company Home/Imap Home")
*
* @param mailPath mailbox path in IMAP client
* @return mailbox path in alfresco
*/
private String getMailPathInRepo(String mailPath)
{
if(logger.isDebugEnabled())
{
logger.debug("[getMailPathInRepo] Path: " + mailPath);
}
String rootFolder;
String remain = "";
int index = mailPath.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER);
if (index > 0)
{
// mail path contains a /
rootFolder = mailPath.substring(0, index);
remain = mailPath.substring(index + 1);
}
else
{
//mail path is a root folder
rootFolder = mailPath;
}
if (imapConfigMountPoints.keySet().contains(rootFolder))
{
//Map<String, NodeRef> mountPoints = getMountPoints();
//NodeRef rootRef = mountPoints.get(rootFolder);
String path = remain;
if(logger.isDebugEnabled())
{
logger.debug("[getMailPathInRepo] Mounted point returning: " + path);
}
return path;
}
else
{
if(logger.isDebugEnabled())
{
logger.debug("[getMailPathInRepo] Not mounted. Returning path as is: " + mailPath);
}
return mailPath;
}
}
/**
* Return mount point name for the current mailbox.
*
* @param mailboxName mailbox name in IMAP client.
* @return mount point name or null.
*/
private String getMountPointName(String mailboxName)
{
String rootFolder;
int index = mailboxName.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER);
if (index > 0)
{
rootFolder = mailboxName.substring(0, index);
}
else
{
rootFolder = mailboxName;
}
if (imapConfigMountPoints.keySet().contains(rootFolder))
{
return rootFolder;
}
else
{
return null;
}
}
/**
* Map of mount points. Name of mount point == key in the map.
*
* @return Map of mount points.
*/
private Map<String, NodeRef> getMountPoints()
{
Set<NodeRef> mountPointNodeRefs = new HashSet<NodeRef>(5);
Map<String, NodeRef> mountPoints = new HashMap<String, NodeRef>();
final NamespaceService namespaceService = serviceRegistry.getNamespaceService();
final SearchService searchService = serviceRegistry.getSearchService();
for (final ImapConfigMountPointsBean config : imapConfigMountPoints.values())
{
try
{
// Get node reference. Do it in new transaction to avoid RollBack in case when AccessDeniedException is thrown.
NodeRef nodeRef = serviceRegistry.getTransactionService().getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<NodeRef>()
{
public NodeRef execute() throws Exception
{
try
{
return config.getFolderPath(namespaceService, nodeService, searchService, fileFolderService);
}
catch (AccessDeniedException e)
{
if (logger.isDebugEnabled())
{
logger.debug("A mount point is skipped due to Access Dennied. \n" + " Mount point: " + config + "\n" + " User: "
+ AuthenticationUtil.getFullyAuthenticatedUser());
}
}
return null;
}
}, true, true);
if (nodeRef != null)
{
if (!mountPointNodeRefs.add(nodeRef))
{
throw new IllegalArgumentException("A mount point has been defined twice: \n" + " Mount point: " + config);
}
mountPoints.put(config.getMountPointName(), nodeRef);
}
}
catch (AccessDeniedException e)
{
if (logger.isDebugEnabled())
{
logger.debug("A mount point is skipped due to Access Dennied. \n" + " Mount point: " + config + "\n" + " User: "
+ AuthenticationUtil.getFullyAuthenticatedUser());
}
}
}
return 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<String, ImapConfigMountPointsBean> imapConfigs = imapConfigMountPoints;
if (imapConfigs.keySet().contains(rootFolder))
{
Map<String, NodeRef> mountPoints = getMountPoints();
NodeRef mountRef = mountPoints.get(rootFolder);
logger.debug("getMailboxRootRef mounted, " + mountRef);
return mountRef;
// MER EXPERIMENT
//return nodeService.getParentAssocs(mountRef).get(0).getParentRef();
}
else
{
NodeRef ret = getUserImapHomeRef(userName);
logger.debug("getMailboxRootRef using userImapHome, " + ret);
return ret;
}
}
/**
* Get the node ref of the user's imap home. Will create it on demand if it
* does not already exist.
*
* @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 = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<NodeRef>()
{
public NodeRef doWork() throws Exception
{
// Look for user imap home
NodeRef userHome = fileFolderService.searchSimple(imapHomeNodeRef, userName);
if (userHome == null)
{
// user imap home does not exist
NodeRef result = fileFolderService.create(imapHomeNodeRef, userName, ContentModel.TYPE_FOLDER).getNodeRef();
nodeService.setProperty(result, ContentModel.PROP_DESCRIPTION, userName);
// create user inbox
fileFolderService.create(result, AlfrescoImapConst.INBOX_NAME, ContentModel.TYPE_FOLDER);
// Set permissions on user's imap home
permissionService.setInheritParentPermissions(result, false);
permissionService.setPermission(result, PermissionService.OWNER_AUTHORITY, PermissionService.ALL_PERMISSIONS, true);
return result;
}
return userHome;
}
}, AuthenticationUtil.getSystemUserName());
return userHome;
}
private boolean isSubscribed(FileInfo fileInfo, String userName)
{
return !nodeService.hasAspect(fileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_FOLDER_NONSUBSCRIBED);
}
/**
* getSubscribed filters out folders which are not subscribed.
* @param list
* @param userName
* @return collection of subscribed folders.
*/
private Collection<FileInfo> getSubscribed(Collection<FileInfo> list, String userName)
{
Collection<FileInfo> result = new LinkedList<FileInfo>();
for (FileInfo folderInfo : list)
{
if (isSubscribed(folderInfo, userName))
{
result.add(folderInfo);
}
}
return result;
}
private boolean hasSubscribedChild(FileInfo parent, String userName, ImapViewMode viewMode)
{
List<FileInfo> list = searchDeep(parent.getNodeRef(), viewMode);
for (FileInfo fileInfo : list)
{
if (isSubscribed(fileInfo, userName))
{
return true;
}
}
return false;
}
private List<AlfrescoImapFolder> createMailFolderList(AlfrescoImapUser user, Collection<FileInfo> list, NodeRef imapUserHomeRef)
{
List<AlfrescoImapFolder> result = new LinkedList<AlfrescoImapFolder>();
// XXX : put folders into the cache and get them in next lookups.
// The question is to get a mailBoxName for the cache key... And what if a folders list was changed?
// So, for now keep it as is.
for (FileInfo folderInfo : list)
{
// folderName, viewMode, mountPointName will be set in listSubscribedMailboxes() method
result.add(
new AlfrescoImapFolder(
user.getQualifiedMailboxName(),
folderInfo,
null,
null,
imapUserHomeRef,
null,
isExtractionEnabled(folderInfo.getNodeRef()),
serviceRegistry));
}
return result;
}
/**
* Return view mode ("virtual", "archive" or "mixed") for specified mailbox.
*
* @param mailboxName name of the mailbox in IMAP client.
* @return view mode of the specified mailbox.
*/
private ImapViewMode getViewMode(String mailboxName)
{
String rootFolder;
int index = mailboxName.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER);
if (index > 0)
{
rootFolder = mailboxName.substring(0, index);
}
else
{
rootFolder = mailboxName;
}
if (imapConfigMountPoints.keySet().contains(rootFolder))
{
return imapConfigMountPoints.get(rootFolder).getMode();
}
else
{
return ImapViewMode.ARCHIVE;
}
}
private String getCurrentUser()
{
return AuthenticationUtil.getFullyAuthenticatedUser();
}
/**
* Return list of "favourite" sites, that belong to the specified user and are marked as "Imap favourite"
*
* @param userName name of user
* @return List of favourite sites.
*/
private List<NodeRef> getFavouriteSites(final String userName)
{
if (logger.isDebugEnabled())
{
logger.debug("[getFavouriteSites] entry for user: " + userName);
}
List<NodeRef> favSites = AlfrescoTransactionSupport.getResource(FAVORITE_SITES);
if (logger.isDebugEnabled())
{
if (favSites == null)
{
logger.debug("[getFavouriteSites] There is no Favorite sites' list bound to transaction " + AlfrescoTransactionSupport.getTransactionId());
}
else
{
logger.debug("[getFavouriteSites] Found Favorite sites' list bound to transaction " + AlfrescoTransactionSupport.getTransactionId());
}
}
if (favSites == null)
{
favSites = new LinkedList<NodeRef>();
PreferenceService preferenceService = (PreferenceService) serviceRegistry
.getService(ServiceRegistry.PREFERENCE_SERVICE);
Map<String, Serializable> prefs = preferenceService.getPreferences(
userName, AlfrescoImapConst.PREF_IMAP_FAVOURITE_SITES);
/**
* List the user's sites
*/
List<SiteInfo> sites = serviceRegistry.getTransactionService()
.getRetryingTransactionHelper().doInTransaction(
new RetryingTransactionCallback<List<SiteInfo>>()
{
public List<SiteInfo> execute() throws Exception
{
List<SiteInfo> res = new ArrayList<SiteInfo>();
try
{
res = serviceRegistry.getSiteService()
.listSites(userName);
}
catch (SiteServiceException e)
{
// Do nothing. Root sites folder was not
// created.
if (logger.isDebugEnabled())
{
logger.warn("[getFavouriteSites] Root sites folder was not created.");
}
}
catch (InvalidNodeRefException e)
{
// Do nothing. Root sites folder was
// deleted.
if (logger.isDebugEnabled())
{
logger.warn("[getFavouriteSites] 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)
{
if(logger.isDebugEnabled())
{
logger.debug("[getFavouriteSites] User: " + userName + " Favourite site: " + siteInfo.getShortName());
}
favSites.add(siteInfo.getNodeRef());
}
}
if (logger.isDebugEnabled())
{
logger.debug("[getFavouriteSites] Bind new Favorite sites' list to transaction " + AlfrescoTransactionSupport.getTransactionId());
}
AlfrescoTransactionSupport.bindResource(FAVORITE_SITES, favSites);
}
if (logger.isDebugEnabled())
{
logger.debug("[getFavouriteSites] end for user: " + userName);
}
return favSites;
}
/**
* Checks for the existence of the flaggable aspect and adds it if it is not already present on the folder.
* @param nodeRef
*/
private void checkForFlaggableAspect(NodeRef nodeRef)
{
Set<NodeRef> alreadyChecked = AlfrescoTransactionSupport.getResource(CHECKED_NODES);
if (alreadyChecked == null)
{
alreadyChecked = new HashSet<NodeRef>();
}
if (alreadyChecked.contains(nodeRef))
{
if (logger.isDebugEnabled())
{
logger.debug("[checkForFlaggableAspect] Flaggable aspect has been already checked for {" + nodeRef + "}");
}
return;
}
try
{
serviceRegistry.getLockService().checkForLock(nodeRef);
}
catch (NodeLockedException e)
{
if (logger.isDebugEnabled())
{
logger.debug("[checkForFlaggableAspect] Node {" + nodeRef + "} is locked");
}
alreadyChecked.add(nodeRef);
return;
}
if (!nodeService.hasAspect(nodeRef, ImapModel.ASPECT_FLAGGABLE))
{
AccessStatus status = permissionService.hasPermission(nodeRef, PermissionService.WRITE_PROPERTIES);
if (status == AccessStatus.DENIED)
{
logger.debug("[checkForFlaggableAspect] No permissions to add FLAGGABLE aspect" + nodeRef);
}
else
{
try
{
policyBehaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
logger.debug("[checkForFlaggableAspect] Adding flaggable aspect to nodeRef: " + nodeRef);
Map<QName, Serializable> aspectProperties = new HashMap<QName, Serializable>();
nodeService.addAspect(nodeRef, ImapModel.ASPECT_FLAGGABLE, aspectProperties);
}
finally
{
policyBehaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
}
}
}
alreadyChecked.add(nodeRef);
AlfrescoTransactionSupport.bindResource(CHECKED_NODES, alreadyChecked);
}
private boolean isExtractionEnabled(NodeRef nodeRef)
{
return extractAttachmentsEnabled && !ignoreExtractionFolders.contains(nodeRef);
}
public String getDefaultEmailBodyTemplate(EmailBodyFormat type)
{
if (defaultBodyTemplates == null)
{
defaultBodyTemplates = new HashMap<EmailBodyFormat, String>(4);
for (EmailBodyFormat onetype : EmailBodyFormat.values())
{
String result = onetype.getClasspathTemplatePath();
try
{
// This query uses cm:name to find the template node(s).
// For the case where the templates are renamed, it would be better to use a QName path-based query.
final StringBuilder templateName = new StringBuilder(DICTIONARY_TEMPLATE_PREFIX).append("_").append(onetype.getTypeSubtype()).append("_").append(onetype.getWebApp()).append(".ftl");
final String repositoryTemplatePath = getRepositoryTemplatePath();
int indexOfStoreDelim = repositoryTemplatePath.indexOf(StoreRef.URI_FILLER);
if (indexOfStoreDelim == -1)
{
throw new IllegalArgumentException("Bad path format, " + StoreRef.URI_FILLER + " not found");
}
indexOfStoreDelim += StoreRef.URI_FILLER.length();
int indexOfPathDelim = repositoryTemplatePath.indexOf("/", indexOfStoreDelim);
if (indexOfPathDelim == -1)
{
throw new IllegalArgumentException("Bad path format, '/' not found");
}
final String storePath = repositoryTemplatePath.substring(0, indexOfPathDelim);
final String rootPathInStore = repositoryTemplatePath.substring(indexOfPathDelim);
final String query = rootPathInStore + "/" + NamespaceService.CONTENT_MODEL_PREFIX + ":" + templateName;
if (logger.isDebugEnabled())
{
logger.debug("[getDefaultEmailBodyTemplate] Query: " + query);
}
StoreRef storeRef = new StoreRef(storePath);
ResultSet resultSet = serviceRegistry.getSearchService().query(storeRef, "xpath", query);
if (resultSet == null || resultSet.length() == 0)
{
if(logger.isDebugEnabled())
{
logger.debug("template not found:" + templateName);
}
throw new AlfrescoRuntimeException(String.format("[getDefaultEmailBodyTemplate] IMAP message template '%1$s' does not exist in the path '%2$s'.", templateName, repositoryTemplatePath));
}
final NodeRef defaultLocaleTemplate = resultSet.getNodeRef(0);
NodeRef localisedSibling = serviceRegistry.getFileFolderService().getLocalizedSibling(defaultLocaleTemplate);
result = localisedSibling.toString();
resultSet.close();
}
// We are catching all exceptions. E.g. search service can possibly throw an exceptions on malformed queries.
catch (Exception e)
{
logger.error("[getDefaultEmailBodyTemplate]", e);
}
defaultBodyTemplates.put(onetype, result);
}
}
return defaultBodyTemplates.get(type);
}
/**
* 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";
}
/**
* Share Site Exclusion Filter
*/
private class ImapSubFolderFilter implements SubFolderFilter
{
/**
* Exclude Share Sites of TYPE_SITE
*/
private Collection<QName> typesToExclude;
private List<NodeRef> favs;
private String mailboxPattern;
private ImapViewMode imapViewMode;
ImapSubFolderFilter(ImapViewMode imapViewMode)
{
this.imapViewMode = imapViewMode;
this.typesToExclude = serviceRegistry.getDictionaryService().getSubTypes(SiteModel.TYPE_SITE, true);
this.favs = getFavouriteSites(getCurrentUser());
}
ImapSubFolderFilter(ImapViewMode imapViewMode, String mailboxPattern)
{
this(imapViewMode);
this.mailboxPattern = mailboxPattern.replaceAll("\\*", "(.)*");;
}
@Override
public boolean isEnterSubfolder(ChildAssociationRef subfolderRef)
{
NodeRef folder = subfolderRef.getChildRef();
if (mailboxPattern != null)
{
logger.debug("Child QName: " + subfolderRef.getQName());
String name = (String) nodeService.getProperty(folder, ContentModel.PROP_NAME);
if (logger.isDebugEnabled())
{
logger.debug("Folder name: " + name + ". Pattern: " + mailboxPattern + ". Matches: " + name.matches(mailboxPattern));
}
if (!name.matches(mailboxPattern))
return false;
}
QName typeOfFolder = nodeService.getType(folder);
if (typesToExclude.contains(typeOfFolder))
{
if (imapViewMode == ImapViewMode.VIRTUAL || imapViewMode == ImapViewMode.MIXED)
{
/**
* In VIRTUAL and MIXED MODE WE SHOULD ONLY DISPLAY FOLDERS FROM FAVOURITE SITES
*/
if (favs.contains(folder))
{
logger.debug("[ImapSubFolderFilter] (VIRTUAL) including fav site folder :" + subfolderRef.getQName());
return true;
}
else
{
logger.debug("[ImapSubFolderFilter] (VIRTUAL) excluding non fav site folder :" + subfolderRef.getQName());
return false;
}
}
else
{
/**
* IN ARCHIVE MODE we don't display folders for any SITES, regardless of whether they are favourites.
*/
logger.debug("[ImapSubFolderFilter] (ARCHIVE) excluding site folder :" + subfolderRef.getQName());
return false;
}
}
return true;
}
}
@Override
public void onCreateChildAssociation(ChildAssociationRef childAssocRef, boolean isNewNode)
{
// Add a listener once, when a lots of messsages were created/moved into the folder
if (AlfrescoTransactionSupport.getResource(UIDVALIDITY_LISTENER_ALREADY_BOUND) == null)
{
AlfrescoTransactionSupport.bindListener(new UidValidityTransactionListener(childAssocRef.getParentRef(), nodeService));
AlfrescoTransactionSupport.bindResource(UIDVALIDITY_LISTENER_ALREADY_BOUND, true);
}
if (logger.isDebugEnabled())
{
logger.debug("[onCreateChildAssociation] Association " + childAssocRef + " created. UIDVALIDITY will be changed.");
}
}
public void onDeleteChildAssociation(ChildAssociationRef childAssocRef)
{
// Add a listener once, when a lots of messsages were created/moved into the folder
if (AlfrescoTransactionSupport.getResource(UIDVALIDITY_LISTENER_ALREADY_BOUND) == null)
{
AlfrescoTransactionSupport.bindListener(new UidValidityTransactionListener(childAssocRef.getParentRef(), nodeService));
AlfrescoTransactionSupport.bindResource(UIDVALIDITY_LISTENER_ALREADY_BOUND, true);
}
if (logger.isDebugEnabled())
{
logger.debug("[onDeleteChildAssociation] Association " + childAssocRef + " removed. UIDVALIDITY will be changed.");
}
}
public void beforeDeleteNode(NodeRef nodeRef)
{
NodeRef parentNodeRef = nodeService.getPrimaryParent(nodeRef).getParentRef();
if (ContentModel.TYPE_FOLDER.equals(nodeService.getType(nodeRef)))
{
// If a node is a folder, we need to remove its' cache with its' children cache as well
invalidateFolderCacheByNodeRef(nodeRef, true);
}
else if (ContentModel.TYPE_CONTENT.equals(nodeService.getType(nodeRef)))
{
// If a node is a content, it is simpler to remove its' parent cache
// to avoid of deal with folder messages cache
invalidateFolderCacheByNodeRef(parentNodeRef, false);
}
else
{
return;
}
// Add a listener once, when a lots of messsages were created/moved into the folder
if (AlfrescoTransactionSupport.getResource(UIDVALIDITY_LISTENER_ALREADY_BOUND) == null)
{
AlfrescoTransactionSupport.bindListener(new UidValidityTransactionListener(parentNodeRef, nodeService));
AlfrescoTransactionSupport.bindResource(UIDVALIDITY_LISTENER_ALREADY_BOUND, true);
}
if (logger.isDebugEnabled())
{
logger.debug("[beforeDeleteNode] Node " + nodeRef + " going to be removed. UIDVALIDITY will be changed for " + parentNodeRef);
}
}
private class UidValidityTransactionListener extends TransactionListenerAdapter
{
private RunAsWork<Long> work;
UidValidityTransactionListener(NodeRef folderNodeRef, NodeService nodeService)
{
this.work = new IncrementUidValidityWork(folderNodeRef, nodeService);
}
@Override
public void afterCommit()
{
AuthenticationUtil.runAs(this.work, AuthenticationUtil.getSystemUserName());
}
}
private class IncrementUidValidityWork implements RunAsWork<Long>
{
private NodeService nodeService;
private NodeRef folderNodeRef;
public IncrementUidValidityWork(NodeRef folderNodeRef, NodeService nodeService)
{
this.folderNodeRef = folderNodeRef;
this.nodeService = nodeService;
}
@Override
public Long doWork() throws Exception
{
RetryingTransactionHelper txnHelper = serviceRegistry.getRetryingTransactionHelper();
return txnHelper.doInTransaction(new RetryingTransactionCallback<Long>(){
@Override
public Long execute() throws Throwable
{
long modifDate = new Date().getTime();
if (!IncrementUidValidityWork.this.nodeService.hasAspect(folderNodeRef, ImapModel.ASPECT_IMAP_FOLDER))
{
Map<QName, Serializable> aspectProperties = new HashMap<QName, Serializable>(1, 1);
aspectProperties.put(ImapModel.PROP_UIDVALIDITY, modifDate);
IncrementUidValidityWork.this.nodeService.addAspect(folderNodeRef, ImapModel.ASPECT_IMAP_FOLDER, aspectProperties);
}
else
{
IncrementUidValidityWork.this.nodeService.setProperty(folderNodeRef, ImapModel.PROP_UIDVALIDITY, modifDate);
}
if (logger.isDebugEnabled())
{
logger.debug("UIDVALIDITY was modified for " + folderNodeRef);
}
return modifDate;
}
}, false, true);
}
}
private void invalidateFolderCacheByNodeRef(NodeRef folderNodeRef, boolean invalidateChildren)
{
if (logger.isDebugEnabled())
{
if (invalidateChildren)
{
logger.debug("[invalidateFolderCacheByNodeRef] Invalidate cache entries for " + folderNodeRef);
}
else
{
logger.debug("[invalidateFolderCacheByNodeRef] Invalidate cache entries for " + folderNodeRef + " and children");
}
}
if (invalidateChildren)
{
SimpleCache<Serializable, Object> foldersCache = getFoldersCache();
List<Serializable> toRemove = new LinkedList<Serializable>();
for(Serializable name : foldersCache.getKeys())
{
AlfrescoImapFolder folder = (AlfrescoImapFolder) foldersCache.get(name);
if (folder == null || folderNodeRef.equals(folder.getFolderInfo().getNodeRef()))
{
toRemove.add(name);
break;
}
}
if (toRemove.size() > 0)
{
String rootName = (String) toRemove.get(0);
for(Serializable name : foldersCache.getKeys())
{
if (((String) name).startsWith(rootName))
{
toRemove.add(name);
}
}
if (logger.isDebugEnabled())
{
logger.debug("Caches to invalidate: " + toRemove.toString());
}
for(Serializable name : toRemove)
{
foldersCache.remove(name);
}
}
}
else
{
SimpleCache<Serializable, Object> foldersCache = getFoldersCache();
for(Serializable name : foldersCache.getKeys())
{
AlfrescoImapFolder folder = (AlfrescoImapFolder) foldersCache.get(name);
if (folder == null || folderNodeRef.equals(folder.getFolderInfo().getNodeRef()))
{
foldersCache.remove(name);
break;
}
}
}
}
/**
* Return true if provided nodeRef is in Sites/.../documentlibrary
*/
public boolean isNodeInSitesLibrary(NodeRef nodeRef)
{
boolean isInDocLibrary = false;
NodeRef parent = nodeService.getPrimaryParent(nodeRef).getParentRef();
while (parent != null && !nodeService.getType(parent).equals(SiteModel.TYPE_SITE))
{
String parentName = (String) nodeService.getProperty(parent, ContentModel.PROP_NAME);
if (parentName.equalsIgnoreCase("documentlibrary"))
{
isInDocLibrary = true;
}
nodeRef = parent;
if (nodeService.getPrimaryParent(nodeRef) != null)
{
parent = nodeService.getPrimaryParent(nodeRef).getParentRef();
}
}
if (parent == null)
{
return false;
}
else
{
return nodeService.getType(parent).equals(SiteModel.TYPE_SITE) && isInDocLibrary;
}
}
/**
* Extract attachments from a MimeMessage
*
* Puts the attachments into a subfolder below the parent folder.
*
* @return the node ref of the folder containing the attachments or null if there are no
* attachments.
*/
public NodeRef extractAttachments(
NodeRef parentFolder,
NodeRef messageFile,
MimeMessage originalMessage)
throws IOException, MessagingException
{
String messageName = (String)nodeService.getProperty(messageFile, 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,
attachmentsFolderName,
ContentModel.TYPE_FOLDER);
nodeService.createAssociation(
messageFile,
attachmentsFolderFileInfo.getNodeRef(),
ImapModel.ASSOC_IMAP_ATTACHMENTS_FOLDER);
}
createAttachment(messageFile, attachmentsFolderFileInfo.getNodeRef(), part);
}
}
}
if(attachmentsFolderFileInfo != null)
{
return attachmentsFolderFileInfo.getNodeRef();
}
else
{
return null;
}
}
/**
* Create an attachment given a mime part
*
* @param messageFile the file containing the message
* @param destinationFolder where to put the attachment
* @param part the mime part
*
* @throws MessagingException
* @throws IOException
*/
private void createAttachment(NodeRef messageFile, NodeRef destinationFolder, 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());
if(contentType.getBaseType().equalsIgnoreCase("application/ms-tnef"))
{
// The content is TNEF
HMEFMessage hmef = new HMEFMessage(part.getInputStream());
//hmef.getBody();
List<org.apache.poi.hmef.Attachment> attachments = hmef.getAttachments();
for(org.apache.poi.hmef.Attachment attachment : attachments)
{
String subName = attachment.getLongFilename();
NodeRef attachmentNode = fileFolderService.searchSimple(destinationFolder, subName);
if (attachmentNode == null)
{
/*
* If the node with the given name does not already exist
* Create the content node to contain the attachment
*/
FileInfo createdFile = fileFolderService.create(
destinationFolder,
subName,
ContentModel.TYPE_CONTENT);
attachmentNode = createdFile.getNodeRef();
serviceRegistry.getNodeService().createAssociation(
messageFile,
attachmentNode,
ImapModel.ASSOC_IMAP_ATTACHMENT);
byte[] bytes = attachment.getContents();
ContentWriter writer = fileFolderService.getWriter(attachmentNode);
//TODO ENCODING - attachment.getAttribute(TNEFProperty.);
String extension = attachment.getExtension();
String mimetype = mimetypeService.getMimetype(extension);
if(mimetype != null)
{
writer.setMimetype(mimetype);
}
OutputStream os = writer.getContentOutputStream();
ByteArrayInputStream is = new ByteArrayInputStream(bytes);
FileCopyUtils.copy(is, os);
}
}
}
else
{
// not TNEF
NodeRef attachmentNode = fileFolderService.searchSimple(destinationFolder, fileName);
if (attachmentNode == null)
{
/*
* If the node with the given name does not already exist
* Create the content node to contain the attachment
*/
FileInfo createdFile = fileFolderService.create(
destinationFolder,
fileName,
ContentModel.TYPE_CONTENT);
attachmentNode = createdFile.getNodeRef();
serviceRegistry.getNodeService().createAssociation(
messageFile,
attachmentNode,
ImapModel.ASSOC_IMAP_ATTACHMENT);
// the part is a normal IMAP attachment
ContentWriter writer = fileFolderService.getWriter(attachmentNode);
writer.setMimetype(contentType.getBaseType());
String charset = contentType.getParameter("charset");
if(charset != null)
{
writer.setEncoding(charset);
}
OutputStream os = writer.getContentOutputStream();
FileCopyUtils.copy(part.getInputStream(), os);
}
}
}
}