mirror of
				https://github.com/Alfresco/alfresco-community-repo.git
				synced 2025-10-29 15:21:53 +00:00 
			
		
		
		
	107541: Merged 5.0.N (5.0.3) to HEAD-BUG-FIX (5.1/Cloud) (PARTIAL MERGE)
      107413: Merged DEV to 5.0.N (5.0.3)
         106858 : MNT-13545: JavaDoc : Inconsistencies between the Java doc and the actual code
            - Cleaning of Javadoc,
   107565: MNT-13545 Fix compilation after merge of Javadoc
git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@107633 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
		
	
		
			
				
	
	
		
			2283 lines
		
	
	
		
			92 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			2283 lines
		
	
	
		
			92 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| /*
 | |
|  * Copyright (C) 2005-2015 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 java.io.IOException;
 | |
| import java.io.Serializable;
 | |
| import java.util.ArrayList;
 | |
| import java.util.Arrays;
 | |
| import java.util.Collection;
 | |
| import java.util.Collections;
 | |
| import java.util.Date;
 | |
| import java.util.Enumeration;
 | |
| 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.NavigableMap;
 | |
| import java.util.Set;
 | |
| import java.util.Timer;
 | |
| import java.util.TimerTask;
 | |
| import java.util.TreeMap;
 | |
| import java.util.concurrent.locks.ReentrantReadWriteLock;
 | |
| 
 | |
| import javax.mail.Flags;
 | |
| import javax.mail.Header;
 | |
| import javax.mail.Flags.Flag;
 | |
| import javax.mail.MessagingException;
 | |
| import javax.mail.internet.AddressException;
 | |
| import javax.mail.internet.InternetAddress;
 | |
| import javax.mail.internet.MimeMessage;
 | |
| 
 | |
| 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.imap.exception.AlfrescoImapRuntimeException;
 | |
| 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.node.NodeServicePolicies.OnRestoreNodePolicy;
 | |
| import org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy;
 | |
| import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
 | |
| import org.alfresco.repo.policy.BehaviourFilter;
 | |
| import org.alfresco.repo.policy.JavaBehaviour;
 | |
| import org.alfresco.repo.policy.PolicyComponent;
 | |
| 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.SiteDoesNotExistException;
 | |
| import org.alfresco.repo.site.SiteModel;
 | |
| import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
 | |
| import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
 | |
| import org.alfresco.repo.transaction.TransactionListenerAdapter;
 | |
| import org.alfresco.service.ServiceRegistry;
 | |
| import org.alfresco.service.cmr.lock.NodeLockedException;
 | |
| import org.alfresco.service.cmr.model.FileExistsException;
 | |
| import org.alfresco.service.cmr.model.FileFolderService;
 | |
| import org.alfresco.service.cmr.model.FileFolderUtil;
 | |
| import org.alfresco.service.cmr.model.FileInfo;
 | |
| import org.alfresco.service.cmr.model.FileNotFoundException;
 | |
| import org.alfresco.service.cmr.model.SubFolderFilter;
 | |
| import org.alfresco.service.cmr.preference.PreferenceService;
 | |
| import org.alfresco.service.cmr.repository.AssociationRef;
 | |
| import org.alfresco.service.cmr.repository.ChildAssociationRef;
 | |
| import org.alfresco.service.cmr.repository.ContentData;
 | |
| 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.SearchService;
 | |
| import org.alfresco.service.cmr.security.AccessStatus;
 | |
| import org.alfresco.service.cmr.security.PermissionService;
 | |
| import org.alfresco.service.cmr.security.PersonService;
 | |
| import org.alfresco.service.cmr.site.SiteInfo;
 | |
| import org.alfresco.service.namespace.NamespaceService;
 | |
| import org.alfresco.service.namespace.QName;
 | |
| import org.alfresco.util.EqualsHelper;
 | |
| import org.alfresco.util.FileFilterMode;
 | |
| import org.alfresco.util.FileFilterMode.Client;
 | |
| import org.alfresco.util.GUID;
 | |
| import org.alfresco.util.MaxSizeMap;
 | |
| import org.alfresco.util.Pair;
 | |
| import org.alfresco.util.PropertyCheck;
 | |
| import org.alfresco.util.config.RepositoryFolderConfigBean;
 | |
| import org.apache.commons.logging.Log;
 | |
| import org.apache.commons.logging.LogFactory;
 | |
| import org.springframework.context.ApplicationEvent;
 | |
| import org.springframework.extensions.surf.util.AbstractLifecycleBean;
 | |
| import org.springframework.extensions.surf.util.I18NUtil;
 | |
| 
 | |
| import com.icegreen.greenmail.store.FolderException;
 | |
| import com.icegreen.greenmail.store.SimpleStoredMessage;
 | |
| 
 | |
| /**
 | |
|  * @author Dmitry Vaserin
 | |
|  * @author Arseny Kovalchuk
 | |
|  * @author David Ward
 | |
|  * @since 3.2
 | |
|  */
 | |
| public class ImapServiceImpl implements ImapService, OnRestoreNodePolicy, OnCreateChildAssociationPolicy, OnDeleteChildAssociationPolicy, OnUpdatePropertiesPolicy, BeforeDeleteNodePolicy
 | |
| {
 | |
|     private Log logger = LogFactory.getLog(ImapServiceImpl.class);
 | |
| 
 | |
|     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_TRANSACTION_LISTENER = "imap.uidvalidity.txn.listener";
 | |
|     
 | |
|     private SysAdminParams sysAdminParams;
 | |
|     private FileFolderService fileFolderService;
 | |
|     private NodeService nodeService;
 | |
|     private PermissionService permissionService;
 | |
|     private ServiceRegistry serviceRegistry;
 | |
|     private BehaviourFilter policyBehaviourFilter;
 | |
|     private NamespaceService namespaceService;
 | |
|     private SearchService searchService;
 | |
|     private AttachmentsExtractor attachmentsExtractor;
 | |
| 
 | |
|     // Note that this cache need not be cluster synchronized, as it is keyed by the cluster-safe 
 | |
|     // change token.  Key is username, changeToken
 | |
|     private Map<Pair<String, String>, FolderStatus> folderCache;
 | |
|     private int folderCacheSize = 1000;
 | |
|     private ReentrantReadWriteLock folderCacheLock = new ReentrantReadWriteLock();
 | |
|     private SimpleCache<NodeRef, CacheItem> messageCache;
 | |
|     private Map<String, ImapConfigMountPointsBean> imapConfigMountPoints;
 | |
|     private Map<String, Integer> mountPointIds;
 | |
|     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 long imapServerShuffleMoveDeleteDelay = 5000L;
 | |
|     private static final Timer deleteDelayTimer = new Timer();
 | |
| 
 | |
|     private boolean imapServerEnabled = false;
 | |
| 
 | |
|     private List<String> messageHeadersToPersist = Collections.<String>emptyList();
 | |
| 
 | |
|     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)
 | |
|         {
 | |
|             service.startupInTxn(false);
 | |
|         }
 | |
| 
 | |
|         @Override
 | |
|         protected void onShutdown(ApplicationEvent event)
 | |
|         {
 | |
|             AuthenticationUtil.runAs(new RunAsWork<Void>()
 | |
|             {
 | |
|                 @Override
 | |
|                 public Void doWork() throws Exception
 | |
|                 {
 | |
|                     if (service.getImapServerEnabled())
 | |
|                     {
 | |
|                         service.shutdown();
 | |
|                     }
 | |
|                     return null;
 | |
|                 }
 | |
|             }, AuthenticationUtil.getSystemUserName());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public void setSysAdminParams(SysAdminParams sysAdminParams)
 | |
|     {
 | |
|         this.sysAdminParams = sysAdminParams;
 | |
|     }
 | |
|     
 | |
|     public void setMessageCache(SimpleCache<NodeRef, CacheItem> messageCache)
 | |
|     {
 | |
|         this.messageCache = messageCache;
 | |
|     }
 | |
| 
 | |
|     public void setFileFolderService(FileFolderService fileFolderService)
 | |
|     {
 | |
|         this.fileFolderService = fileFolderService;
 | |
|     }
 | |
| 
 | |
|     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 setAttachmentsExtractor(AttachmentsExtractor attachmentsExtractor)
 | |
|     {
 | |
|         this.attachmentsExtractor = attachmentsExtractor;
 | |
|     }
 | |
| 
 | |
|     public void setImapHome(RepositoryFolderConfigBean imapHomeConfigBean)
 | |
|     {
 | |
|         this.imapHomeConfigBean = imapHomeConfigBean;
 | |
|     }
 | |
|     
 | |
|     public void setFolderCacheSize(int folderCacheSize)
 | |
|     {
 | |
|         this.folderCacheSize = folderCacheSize;
 | |
|     }
 | |
| 
 | |
|     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);
 | |
|         this.mountPointIds = new HashMap<String, Integer>(imapConfigMountPointsBeans.length * 2);
 | |
|         for (int i = 0; i < imapConfigMountPointsBeans.length; i++)
 | |
|         {
 | |
|             String name = imapConfigMountPointsBeans[i].getMountPointName();
 | |
|             this.imapConfigMountPoints.put(name, imapConfigMountPointsBeans[i]);
 | |
|             this.mountPointIds.put(name, i + 1);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     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 void setMessageHeadersToPersist(List<String> headers)
 | |
|     {
 | |
|         this.messageHeadersToPersist  = headers;
 | |
|     }
 | |
| 
 | |
|     public void setImapServerShuffleMoveDeleteDelay(long imapServerShuffleMoveDeleteDelay)
 | |
|     {
 | |
|         this.imapServerShuffleMoveDeleteDelay = imapServerShuffleMoveDeleteDelay;
 | |
|     }
 | |
|     
 | |
|     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, "namespaceService", namespaceService);
 | |
|         PropertyCheck.mandatory(this, "searchService", getSearchService());
 | |
|         this.folderCache = new MaxSizeMap<Pair<String,String>, FolderStatus>(folderCacheSize, false);
 | |
|         
 | |
|         // 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});
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * This method is run as System within a single transaction on startup.
 | |
|      */
 | |
|     public void startup()
 | |
|     {
 | |
|         bindBehaviour();
 | |
|                         
 | |
|         // Get NodeRefs for folders to ignore
 | |
|         this.ignoreExtractionFolders = new HashSet<NodeRef>(ignoreExtractionFoldersBeans.length * 2);
 | |
| 
 | |
|         for (RepositoryFolderConfigBean ignoreExtractionFoldersBean : ignoreExtractionFoldersBeans)
 | |
|         {
 | |
|             NodeRef nodeRef = ignoreExtractionFoldersBean.getFolderPath(namespaceService, nodeService, searchService,
 | |
|                     fileFolderService);
 | |
| 
 | |
|             if (!ignoreExtractionFolders.add(nodeRef))
 | |
|             {
 | |
|                 // It was already in the set
 | |
|                 throw new AlfrescoRuntimeException("The folder extraction path has been referenced already: \n"
 | |
|                         + "   Folder: " + ignoreExtractionFoldersBean);
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         // Locate or create IMAP home
 | |
|         imapHomeNodeRef = imapHomeConfigBean.getOrCreateFolderPath(namespaceService, nodeService, searchService, fileFolderService);        
 | |
|     }
 | |
| 
 | |
|     public void shutdown()
 | |
|     {
 | |
|     }
 | |
|     
 | |
|     protected void startupInTxn(boolean force)
 | |
|     {
 | |
|         if (force || getImapServerEnabled())
 | |
|         {
 | |
|             AuthenticationUtil.runAs(new RunAsWork<Void>()
 | |
|             {
 | |
|                 @Override
 | |
|                 public Void doWork() throws Exception
 | |
|                 {
 | |
|                     List<AlfrescoImapFolder> mailboxes = serviceRegistry.getTransactionService().getRetryingTransactionHelper().doInTransaction(
 | |
|                             new RetryingTransactionCallback<List<AlfrescoImapFolder>>()
 | |
|                             {
 | |
|                                 @Override
 | |
|                                 public List<AlfrescoImapFolder> execute() throws Throwable
 | |
|                                 {
 | |
|                                     startup();
 | |
|                                     
 | |
|                                     List<AlfrescoImapFolder> result = new LinkedList<AlfrescoImapFolder>();
 | |
|                                     
 | |
|                                     // Hit the mount points and warm the caches for early failure
 | |
|                                     for (String mountPointName : imapConfigMountPoints.keySet())
 | |
|                                     {
 | |
|                                         result.addAll(listMailboxes(new AlfrescoImapUser(null, AuthenticationUtil
 | |
|                                                 .getSystemUserName(), null), mountPointName + "*", false));
 | |
|                                     }
 | |
|                                     
 | |
|                                     return result;
 | |
|                                 }
 | |
|                             });
 | |
|                     
 | |
|                     // Let each mailbox search trigger its own distinct transaction
 | |
|                     for (AlfrescoImapFolder mailbox : mailboxes)
 | |
|                     {
 | |
|                         mailbox.getUidNext();
 | |
|                     }
 | |
|                     
 | |
|                     return null;                    
 | |
|                 }
 | |
|             }, AuthenticationUtil.getSystemUserName());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     protected void bindBehaviour()
 | |
|     {
 | |
|         if (logger.isDebugEnabled())
 | |
|         {
 | |
|             logger.debug("[bindBeahaviour] Binding behaviours");
 | |
|         }
 | |
|         PolicyComponent policyComponent = (PolicyComponent) serviceRegistry.getService(QName.createQName(NamespaceService.ALFRESCO_URI, "policyComponent"));
 | |
| 
 | |
|         // Only listen to folders we've tagged with imap properties - not all folders or we'll really slow down the repository!
 | |
|         policyComponent.bindAssociationBehaviour(
 | |
|                 OnCreateChildAssociationPolicy.QNAME,
 | |
|                 ImapModel.ASPECT_IMAP_FOLDER,
 | |
|                 ContentModel.ASSOC_CONTAINS,
 | |
|                 new JavaBehaviour(this, "onCreateChildAssociation", NotificationFrequency.EVERY_EVENT));
 | |
|         policyComponent.bindAssociationBehaviour(
 | |
|                 OnDeleteChildAssociationPolicy.QNAME,
 | |
|                 ImapModel.ASPECT_IMAP_FOLDER,
 | |
|                 ContentModel.ASSOC_CONTAINS,
 | |
|                 new JavaBehaviour(this, "onDeleteChildAssociation", NotificationFrequency.EVERY_EVENT));
 | |
|         policyComponent.bindClassBehaviour(
 | |
|                 OnUpdatePropertiesPolicy.QNAME,
 | |
|                 ContentModel.TYPE_CONTENT,
 | |
|                 new JavaBehaviour(this, "onUpdateProperties", NotificationFrequency.EVERY_EVENT));
 | |
|         policyComponent.bindClassBehaviour(
 | |
|                 BeforeDeleteNodePolicy.QNAME,
 | |
|                 ContentModel.TYPE_CONTENT,
 | |
|                 new JavaBehaviour(this, "beforeDeleteNode", NotificationFrequency.EVERY_EVENT));
 | |
|         policyComponent.bindClassBehaviour(
 | |
|                 OnRestoreNodePolicy.QNAME,
 | |
|                 ContentModel.TYPE_CONTENT,
 | |
|                 new JavaBehaviour(this, "onRestoreNode", NotificationFrequency.EVERY_EVENT));
 | |
|     }
 | |
| 
 | |
|     // ---------------------- Service Methods --------------------------------
 | |
| 
 | |
|     public SimpleStoredMessage getMessage(FileInfo mesInfo) throws MessagingException
 | |
|     {
 | |
|         NodeRef nodeRef = mesInfo.getNodeRef();
 | |
|         Date modified = (Date) nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED);
 | |
|         if(modified != null)
 | |
|         {
 | |
|             CacheItem cached =  messageCache.get(nodeRef);
 | |
|             if (cached != null)
 | |
|             {
 | |
|                 if (cached.getModified().equals(modified))
 | |
|                 {
 | |
|                     return cached.getMessage();
 | |
|                 }
 | |
|             }
 | |
|             SimpleStoredMessage message = createImapMessage(mesInfo, true);
 | |
|             messageCache.put(nodeRef, new CacheItem(modified, message));            
 | |
|             return message;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             SimpleStoredMessage message = createImapMessage(mesInfo, true);
 | |
|             return message;
 | |
|         }
 | |
|     }
 | |
|         
 | |
|     public SimpleStoredMessage createImapMessage(FileInfo fileInfo, boolean generateBody) throws MessagingException
 | |
|     {
 | |
|         // TODO MER 26/11/2010- this test should really be that the content of the node is of type message/RFC822
 | |
|         Long key = (Long) fileInfo.getProperties().get(ContentModel.PROP_NODE_DBID);
 | |
|         if (nodeService.hasAspect(fileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_CONTENT))
 | |
|         {
 | |
|             return new SimpleStoredMessage(new ImapModelMessage(fileInfo, serviceRegistry, generateBody), new Date(), key);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             return new SimpleStoredMessage(new ContentModelMessage(fileInfo, serviceRegistry, generateBody), new Date(), key);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public void expungeMessage(FileInfo fileInfo)
 | |
|     {
 | |
|         Flags flags = getFlags(fileInfo);
 | |
|         if (flags.contains(Flags.Flag.DELETED))
 | |
|         {
 | |
|             // See MNT-12259
 | |
|             //fileFolderService.delete(fileInfo.getNodeRef());
 | |
|             hideAndDelete(fileInfo.getNodeRef());
 | |
|             messageCache.remove(fileInfo.getNodeRef());
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Workaround for MNT-12259
 | |
|      * @param nodeRef NodeRef
 | |
|      */
 | |
|     @SuppressWarnings("deprecation")
 | |
|     private void hideAndDelete(final NodeRef nodeRef)
 | |
|     {
 | |
|         FileFilterMode.setClient(FileFilterMode.Client.imap);
 | |
|         fileFolderService.setHidden(nodeRef, true);
 | |
|         {
 | |
|             // Get the current user
 | |
|             final String deleteDelayUser = AuthenticationUtil.getFullyAuthenticatedUser();
 | |
|             // Add a timed task to really delete the file
 | |
|             TimerTask deleteDelayTask = new TimerTask()
 | |
|             {
 | |
|                 @Override
 | |
|                 public void run()
 | |
|                 {
 | |
|                     RunAsWork<Void> deleteDelayRunAs = new RunAsWork<Void>()
 | |
|                     {
 | |
|                         @Override
 | |
|                         public Void doWork() throws Exception
 | |
|                         {
 | |
|                             return serviceRegistry.getTransactionService().getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Void>()
 | |
|                             {
 | |
|                                 @Override
 | |
|                                 public Void execute() throws Throwable
 | |
|                                 {
 | |
|                                     // Ignore if it is NOT hidden: the shuffle may have finished; the operation may have failed
 | |
|                                     if (!nodeService.exists(nodeRef) || !fileFolderService.isHidden(nodeRef))
 | |
|                                     {
 | |
|                                         return null;
 | |
|                                     }
 | |
| 
 | |
|                                     // Since this will run in a different thread, the client thread-local must be set
 | |
|                                     // or else unhiding the node will not unhide it for IMAP.
 | |
|                                     FileFilterMode.setClient(FileFilterMode.Client.imap);
 | |
| 
 | |
|                                     // Unhide the node, e.g. for archiving
 | |
|                                     fileFolderService.setHidden(nodeRef, false);
 | |
| 
 | |
|                                     // This is the transaction-aware service
 | |
|                                     fileFolderService.delete(nodeRef);
 | |
| 
 | |
|                                     if (logger.isDebugEnabled())
 | |
|                                     {
 | |
|                                         logger.debug("Node has been async deleted " + nodeRef);
 | |
|                                     }
 | |
| 
 | |
|                                     return null;
 | |
|                                 }
 | |
|                             });
 | |
|                         }
 | |
|                     };
 | |
|                     try
 | |
|                     {
 | |
|                         AuthenticationUtil.runAs(deleteDelayRunAs, deleteDelayUser);
 | |
|                     }
 | |
|                     catch (Throwable e)
 | |
|                     {
 | |
|                         // consume exception to avoid it leaking from the TimerTask and causing the Timer to
 | |
|                         // no longer accept tasks to be scheduled.
 | |
|                         logger.info("Exception thrown during IMAP delete timer task.", e);
 | |
|                     }
 | |
|                 }
 | |
|             };
 | |
|             // Schedule a real delete 5 seconds after the current time
 | |
|             if (logger.isDebugEnabled())
 | |
|             {
 | |
|                 logger.debug("Delete timer is scheduled for " + nodeRef);
 | |
|             }
 | |
|             deleteDelayTimer.schedule(deleteDelayTask, imapServerShuffleMoveDeleteDelay);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * @throws AlfrescoImapRuntimeException
 | |
|      */
 | |
|     public AlfrescoImapFolder getOrCreateMailbox(AlfrescoImapUser user, String mailboxName, boolean mayExist, boolean mayCreate)
 | |
|     {
 | |
|         if (mailboxName == null)
 | |
|         {
 | |
|             throw new IllegalArgumentException(I18NUtil.getMessage(ERROR_MAILBOX_NAME_IS_MANDATORY));
 | |
|         }
 | |
|         // A request for the hierarchy delimiter
 | |
|         if (mailboxName.length() == 0)
 | |
|         {
 | |
|             return new AlfrescoImapFolder(user.getLogin(), this, serviceRegistry);
 | |
|         }
 | |
|         final NodeRef root;
 | |
|         List<String> pathElements = null;
 | |
|         ImapViewMode viewMode = ImapViewMode.ARCHIVE;
 | |
|         int index = mailboxName.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER);
 | |
|         int mountPointId = 0;
 | |
| 
 | |
|         String rootPath = (index > 0) ? (mailboxName.substring(0, index)) : (mailboxName);
 | |
| 
 | |
|         ImapConfigMountPointsBean imapConfigMountPoint = this.imapConfigMountPoints.get(rootPath);
 | |
|         if (imapConfigMountPoint != null)
 | |
|         {
 | |
|             mountPointId = this.mountPointIds.get(rootPath);
 | |
|             root = imapConfigMountPoint.getFolderPath(serviceRegistry.getNamespaceService(), nodeService, searchService, fileFolderService);
 | |
| 
 | |
|             if (index > 0)
 | |
|             {
 | |
|                 pathElements = Arrays.asList(mailboxName.substring(index + 1).split(String.valueOf(AlfrescoImapConst.HIERARCHY_DELIMITER)));
 | |
|             }
 | |
| 
 | |
|             viewMode = imapConfigMountPoint.getMode();
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             root = getUserImapHomeRef(user.getLogin());
 | |
|             pathElements = Arrays.asList(mailboxName.split(String.valueOf(AlfrescoImapConst.HIERARCHY_DELIMITER)));
 | |
|         }
 | |
| 
 | |
|         FileInfo mailFolder;
 | |
|         try
 | |
|         {
 | |
|             if (null != pathElements)
 | |
|             {
 | |
|                 mailFolder = fileFolderService.resolveNamePath(root, pathElements, !mayCreate);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 mailFolder = fileFolderService.getFileInfo(root);
 | |
|             }
 | |
|         }
 | |
|         catch (FileNotFoundException e)
 | |
|         {
 | |
|             throw new AlfrescoImapRuntimeException(ERROR_CANNOT_GET_A_FOLDER, new String[] { mailboxName }, new FolderException(FolderException.NOT_LOCAL));
 | |
|         }
 | |
|         if (mailFolder == null)
 | |
|         {
 | |
|             if (!mayCreate)
 | |
|             {
 | |
|                 throw new AlfrescoImapRuntimeException(ERROR_CANNOT_GET_A_FOLDER, new String[] { mailboxName }, new FolderException(FolderException.NOT_LOCAL));
 | |
|             }
 | |
|             if (logger.isDebugEnabled())
 | |
|             {
 | |
|                 logger.debug("Creating mailbox: " + mailboxName);
 | |
|             }
 | |
|             mailFolder = FileFolderUtil.makeFolders(fileFolderService, root, pathElements, ContentModel.TYPE_FOLDER);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             if (!mayExist)
 | |
|             {
 | |
|                 throw new AlfrescoImapRuntimeException(ERROR_FOLDER_ALREADY_EXISTS, new FolderException(FolderException.ALREADY_EXISTS_LOCALLY));
 | |
|             }
 | |
|         }
 | |
|         String path = (null != pathElements) ? (pathElements.get(pathElements.size() - 1)) : (rootPath);
 | |
|         return new AlfrescoImapFolder(mailFolder, user.getLogin(), path, mailboxName, viewMode, this, serviceRegistry, true, isExtractionEnabled(mailFolder.getNodeRef()),
 | |
|                 mountPointId);
 | |
|     }
 | |
| 
 | |
|     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 = getOrCreateMailbox(user, mailboxName, true, false);
 | |
|         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.");
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public void renameMailbox(AlfrescoImapUser user, String oldMailboxName, String newMailboxName)
 | |
|     {
 | |
|         if (oldMailboxName == null || newMailboxName == null)
 | |
|         {
 | |
|             throw new IllegalArgumentException(ERROR_MAILBOX_NAME_IS_MANDATORY);
 | |
|         }
 | |
| 		
 | |
| 		AlfrescoImapFolder sourceNode = getOrCreateMailbox(user, oldMailboxName, true, false);
 | |
| 			  
 | |
|         if (logger.isDebugEnabled())
 | |
|         {
 | |
|             logger.debug("Renaming folder oldMailboxName=" + oldMailboxName + " newMailboxName=" + newMailboxName);
 | |
|         }
 | |
|         
 | |
|         NodeRef newMailParent;
 | |
|         String newMailName;
 | |
|         int index = newMailboxName.lastIndexOf(AlfrescoImapConst.HIERARCHY_DELIMITER);
 | |
|         if (index < 0)
 | |
|         {
 | |
|             newMailParent = getUserImapHomeRef(user.getLogin());
 | |
|             newMailName = newMailboxName;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             newMailParent = getOrCreateMailbox(user, newMailboxName.substring(0, index), true, true).getFolderInfo().getNodeRef();
 | |
|             newMailName = newMailboxName.substring(index + 1);
 | |
|         }
 | |
| 
 | |
|         try
 | |
|         {
 | |
|             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(), newMailParent,
 | |
|                         AlfrescoImapConst.INBOX_NAME);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 fileFolderService.move(sourceNode.getFolderInfo().getNodeRef(), newMailParent, newMailName);
 | |
|             }
 | |
|         }
 | |
|         catch (FileNotFoundException e)
 | |
|         {
 | |
|             throw new AlfrescoRuntimeException(e.getMessage(), e);
 | |
|         }
 | |
|         catch (FileExistsException e)
 | |
|         {
 | |
|             throw new AlfrescoRuntimeException(e.getMessage(), e);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * 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 FolderStatus getFolderStatus(final String userName, final NodeRef contextNodeRef, ImapViewMode viewMode)
 | |
|     {
 | |
|         if (logger.isDebugEnabled())
 | |
|         {
 | |
|             logger.debug("getFolderStatus contextNodeRef=" + contextNodeRef + ", viewMode=" + viewMode);
 | |
|         }
 | |
| 
 | |
|         // No need to ACL check the change token read
 | |
|         String changeToken = AuthenticationUtil.runAs(new RunAsWork<String>()
 | |
|         {
 | |
|             @Override
 | |
|             public String doWork() throws Exception
 | |
|             {
 | |
|                 return (String) nodeService.getProperty(contextNodeRef, ImapModel.PROP_CHANGE_TOKEN);
 | |
|             }
 | |
|         }, AuthenticationUtil.getSystemUserName());
 | |
| 
 | |
|         Pair<String, String> cacheKey = null;
 | |
|         if (changeToken != null)
 | |
|         {
 | |
|             cacheKey = new Pair<String, String>(userName, changeToken);
 | |
|             this.folderCacheLock.readLock().lock();
 | |
|             try
 | |
|             {
 | |
|                 FolderStatus result = this.folderCache.get(cacheKey);
 | |
|                 if (result != null)
 | |
|                 {
 | |
|                     return result;
 | |
|                 }
 | |
|             }
 | |
|             finally
 | |
|             {
 | |
|                 this.folderCacheLock.readLock().unlock();
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         List<FileInfo> fileInfos = null;
 | |
|         FileFilterMode.setClient(Client.imap);
 | |
|         try
 | |
|         {
 | |
|             fileInfos = fileFolderService.listFiles(contextNodeRef);
 | |
|         }
 | |
|         finally
 | |
|         {
 | |
|             FileFilterMode.clearClient();
 | |
|         }
 | |
|         
 | |
|         final NavigableMap<Long, FileInfo> currentSearch = new TreeMap<Long, FileInfo>();
 | |
| 
 | |
|         switch (viewMode)
 | |
|         {
 | |
|         case MIXED:
 | |
|             for (FileInfo fileInfo : fileInfos)
 | |
|             {
 | |
|                 currentSearch.put((Long) fileInfo.getProperties().get(ContentModel.PROP_NODE_DBID), fileInfo);
 | |
|             }
 | |
|             break;
 | |
|         case ARCHIVE:
 | |
|             for (FileInfo fileInfo : fileInfos)
 | |
|             {
 | |
|                 if (nodeService.hasAspect(fileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_CONTENT))
 | |
|                 {
 | |
|                     currentSearch.put((Long) fileInfo.getProperties().get(ContentModel.PROP_NODE_DBID), fileInfo);
 | |
|                 }
 | |
|             }
 | |
|             break;
 | |
|         case VIRTUAL:
 | |
|             for (FileInfo fileInfo : fileInfos)
 | |
|             {
 | |
|                 if (!nodeService.hasAspect(fileInfo.getNodeRef(), ImapModel.ASPECT_IMAP_CONTENT))
 | |
|                 {
 | |
|                     currentSearch.put((Long) fileInfo.getProperties().get(ContentModel.PROP_NODE_DBID), fileInfo);
 | |
|                 }
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         int messageCount = currentSearch.size(), recentCount = 0, unseenCount = 0, firstUnseen = 0;
 | |
|         int i = 1;
 | |
|         for (FileInfo fileInfo : currentSearch.values())
 | |
|         {
 | |
|             Flags flags = getFlags(fileInfo);
 | |
|             if (flags.contains(Flags.Flag.RECENT))
 | |
|             {
 | |
|                 recentCount++;
 | |
|             }
 | |
|             if (!flags.contains(Flags.Flag.SEEN))
 | |
|             {
 | |
|                 if (firstUnseen == 0)
 | |
|                 {
 | |
|                     firstUnseen = i;
 | |
|                 }
 | |
|                 unseenCount++;
 | |
|             }
 | |
|             i++;
 | |
|         }
 | |
|         // Add the IMAP folder aspect with appropriate initial values if it is not already there
 | |
|         if (changeToken == null)
 | |
|         {
 | |
|             changeToken = GUID.generate();
 | |
|             cacheKey = new Pair<String, String>(userName, changeToken);
 | |
|             final String finalToken = changeToken;
 | |
|             doAsSystem(new RunAsWork<Void>()
 | |
|             {
 | |
|                 @Override
 | |
|                 public Void doWork() throws Exception
 | |
|                 {
 | |
|                     nodeService.setProperty(contextNodeRef, ImapModel.PROP_CHANGE_TOKEN, finalToken);
 | |
|                     nodeService.setProperty(contextNodeRef, ImapModel.PROP_MAXUID, currentSearch.isEmpty() ? 0
 | |
|                             : currentSearch.lastKey());
 | |
|                     return null;
 | |
|                 }
 | |
|             });
 | |
|         }
 | |
|         Long uidValidity = (Long) nodeService.getProperty(contextNodeRef, ImapModel.PROP_UIDVALIDITY);
 | |
|         FolderStatus result = new FolderStatus(messageCount, recentCount, firstUnseen, unseenCount,
 | |
|                 uidValidity == null ? 0 : uidValidity, changeToken, currentSearch);
 | |
|         this.folderCacheLock.writeLock().lock();
 | |
|         try
 | |
|         {
 | |
|             FolderStatus oldResult = this.folderCache.get(cacheKey);
 | |
|             if (oldResult != null)
 | |
|             {
 | |
|                 if(logger.isDebugEnabled())
 | |
|                 {
 | |
|                     logger.debug("At end of getFolderStatus. Found info in cache, changeToken:" + changeToken);
 | |
|                 }
 | |
|          
 | |
|                 return oldResult;
 | |
|             }
 | |
|             this.folderCache.put(cacheKey, result);
 | |
| 
 | |
|             if(logger.isDebugEnabled())
 | |
|             {
 | |
|                 logger.debug("At end of getFolderStatus. Found files:" + currentSearch.size() + ", changeToken:" + changeToken);
 | |
|             }
 | |
|             return result;
 | |
|         }
 | |
|         finally
 | |
|         {
 | |
|             this.folderCacheLock.writeLock().unlock();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public void subscribe(AlfrescoImapUser user, String mailbox)
 | |
|     {
 | |
|         if (logger.isDebugEnabled())
 | |
|         {
 | |
|             logger.debug("Subscribing: " + user + ", " + mailbox);
 | |
|         }
 | |
|         AlfrescoImapFolder mailFolder = getOrCreateMailbox(user, mailbox, true, false);
 | |
|         PersonService personService = serviceRegistry.getPersonService();
 | |
|         NodeRef userRef = personService.getPerson(user.getLogin());
 | |
| 
 | |
|         nodeService.removeAssociation(userRef, mailFolder.getFolderInfo().getNodeRef(), ImapModel.ASSOC_IMAP_UNSUBSCRIBED);
 | |
|     }
 | |
| 
 | |
|     public void unsubscribe(AlfrescoImapUser user, String mailbox)
 | |
|     {
 | |
|         if (logger.isDebugEnabled())
 | |
|         {
 | |
|             logger.debug("Unsubscribing: " + user + ", " + mailbox);
 | |
|         }
 | |
|         AlfrescoImapFolder mailFolder = getOrCreateMailbox(user, mailbox, true, false);
 | |
|         if(mailFolder.getFolderInfo() != null)
 | |
|         {
 | |
|             PersonService personService = serviceRegistry.getPersonService();
 | |
|             NodeRef userRef = personService.getPerson(user.getLogin());
 | |
|             nodeService.createAssociation(userRef, mailFolder.getFolderInfo().getNodeRef(), ImapModel.ASSOC_IMAP_UNSUBSCRIBED);
 | |
|         }
 | |
|         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 Flags getFlags(FileInfo messageInfo)
 | |
|     {
 | |
|         Flags flags = new Flags();
 | |
|         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 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 message.
 | |
|      * 
 | |
|      * @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)
 | |
|     {
 | |
|         setFlag(messageInfo.getNodeRef(), flag, value);
 | |
|     }
 | |
| 
 | |
|     private void setFlag(NodeRef nodeRef, Flag flag, boolean value)
 | |
|     {
 | |
|         String permission = (flag == Flag.DELETED ? PermissionService.DELETE_NODE : PermissionService.WRITE_PROPERTIES);
 | |
|         
 | |
|         
 | |
|         AccessStatus status = permissionService.hasPermission(nodeRef, permission);
 | |
|         if (status == AccessStatus.DENIED)
 | |
|         {
 | |
|             if(flag == Flag.DELETED)
 | |
|             {
 | |
|                 logger.debug("[setFlag] Access denied to set DELETED FLAG:" + nodeRef);
 | |
|                 throw new AccessDeniedException("No permission to set DELETED flag");
 | |
|             }
 | |
|             if(flag == Flag.SEEN)
 | |
|             {
 | |
|                 logger.debug("[setFlag] Access denied to set SEEN FLAG:" + nodeRef);
 | |
|                 //TODO - should we throw an exception here?
 | |
|                 //throw new AccessDeniedException("No permission to set DELETED flag");
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                
 | |
|                 logger.debug("[setFlag] Access denied to set flag:" + nodeRef);
 | |
|                 throw new AccessDeniedException("No permission to set flag:" + flag.toString());
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             checkForFlaggableAspect(nodeRef);
 | |
|             policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE);
 | |
|             policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_VERSIONABLE);
 | |
|             try
 | |
|             {                    
 | |
|                 if(logger.isDebugEnabled())
 | |
|                 {
 | |
|                     logger.debug("set flag nodeRef:" + nodeRef + ",flag:" + flagToQname.get(flag) + ", value:" + value);
 | |
|                 }
 | |
|                 nodeService.setProperty(nodeRef, flagToQname.get(flag), value);
 | |
|                 messageCache.remove(nodeRef);
 | |
|             }
 | |
|             finally
 | |
|             {
 | |
|                 policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_AUDITABLE);
 | |
|                 policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_VERSIONABLE);                
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Depend on listSubscribed param, list Mailboxes or list subscribed Mailboxes
 | |
|      */
 | |
|     public 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>();
 | |
|         
 | |
|         // List mailboxes that are in mount points
 | |
|         int index = mailboxPattern.indexOf(AlfrescoImapConst.HIERARCHY_DELIMITER);
 | |
|         String rootPath = index == -1 ? mailboxPattern : mailboxPattern.substring(0, index);
 | |
|         boolean found = false;
 | |
|         
 | |
|         String userName = user.getLogin();
 | |
|         Set<NodeRef> unsubscribedFodlers = getUnsubscribedFolders(userName);
 | |
| 
 | |
|         for (String mountPointName : imapConfigMountPoints.keySet())
 | |
|         {
 | |
|             if (mountPointName.matches(rootPath.replaceAll("[%\\*]", ".*")))
 | |
|             {
 | |
|                 NodeRef mountPoint = getMountPoint(mountPointName);
 | |
|                 if (mountPoint != null)
 | |
|                 {
 | |
|                     int mountPointId = mountPointIds.get(mountPointName);
 | |
|                     FileInfo mountPointFileInfo = fileFolderService.getFileInfo(mountPoint);
 | |
|                     ImapViewMode viewMode = imapConfigMountPoints.get(mountPointName).getMode();
 | |
|                     if (index < 0)
 | |
|                     {
 | |
| 
 | |
|                         if (!listSubscribed || !unsubscribedFodlers.contains(mountPointFileInfo.getNodeRef()))
 | |
|                         {
 | |
|                             result.add(new AlfrescoImapFolder(mountPointFileInfo, userName, mountPointName, mountPointName, viewMode,
 | |
|                                     isExtractionEnabled(mountPointFileInfo.getNodeRef()), this, serviceRegistry, mountPointId));
 | |
|                         }
 | |
|                         else if (rootPath.endsWith("%") && !expandFolder(mountPoint, user, mountPointName, "%", true, viewMode, mountPointId).isEmpty()) // \NoSelect 
 | |
|                         {
 | |
|                             result.add(new AlfrescoImapFolder(mountPointFileInfo, userName, mountPointName, mountPointName, viewMode,
 | |
|                                     this, serviceRegistry, false, isExtractionEnabled(mountPointFileInfo.getNodeRef()), mountPointId));
 | |
|                         }
 | |
|                         if (rootPath.endsWith("*"))
 | |
|                         {
 | |
|                             result.addAll(expandFolder(mountPoint, user, mountPointName, "*", listSubscribed, viewMode, mountPointId));                            
 | |
|                         }                        
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         result.addAll(expandFolder(mountPoint, user, mountPointName,
 | |
|                                 mailboxPattern.substring(index + 1), listSubscribed, viewMode, mountPointId));
 | |
|                     }
 | |
|                 }
 | |
|                 // If we had an exact match, there is no point continuing to search
 | |
|                 if (mountPointName.equals(rootPath))
 | |
|                 {
 | |
|                     found = true;
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // List mailboxes that are in user IMAP Home
 | |
|         if (!found)
 | |
|         {
 | |
|             NodeRef root = getUserImapHomeRef(user.getLogin());
 | |
|             result.addAll(expandFolder(root, user, "", mailboxPattern, listSubscribed, ImapViewMode.ARCHIVE, 0));
 | |
|         }
 | |
|             
 | |
|         logger.debug("listMailboxes returning size:" + result.size());
 | |
|         
 | |
|         return result;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Recursively search the given root to get a list of folders
 | |
|      * 
 | |
|      * @return List<AlfrescoImapFolder>
 | |
|      */
 | |
|     private List<AlfrescoImapFolder> expandFolder(
 | |
|             NodeRef root,
 | |
|             AlfrescoImapUser user,
 | |
|             String rootPath,
 | |
|             String mailboxPattern,
 | |
|             boolean listSubscribed,
 | |
|             ImapViewMode viewMode,
 | |
|             int mountPointId)
 | |
|     {
 | |
|         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;
 | |
|         if (index < 0)
 | |
|         {
 | |
|             name = mailboxPattern;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             name = mailboxPattern.substring(0, index);
 | |
|         }
 | |
|         String rootPathPrefix = rootPath.length() == 0 ? "" : rootPath + AlfrescoImapConst.HIERARCHY_DELIMITER; 
 | |
| 
 | |
|         if (logger.isDebugEnabled())
 | |
|         {
 | |
|             logger.debug("Listing mailboxes: name=" + name);
 | |
|         }
 | |
| 
 | |
|         List<AlfrescoImapFolder> fullList = new LinkedList<AlfrescoImapFolder>();
 | |
|         ImapSubFolderFilter filter = new ImapSubFolderFilter(viewMode, name.replace('%', '*'));
 | |
|         List<FileInfo> list;
 | |
|         // Only list this folder if we have a wildcard name. Otherwise do a direct lookup by name.
 | |
|         if (name.contains("*") || name.contains("%"))
 | |
|         {
 | |
|             FileFilterMode.setClient(Client.imap);
 | |
|             try
 | |
|             {
 | |
|                 // Check if the mount point is site root
 | |
|                 // then shortcut to user's favorite sites
 | |
|                 // MNT-12055
 | |
|                 if (serviceRegistry.getSiteService().getSiteRoot().equals(root))
 | |
|                 {
 | |
|                     list = fileFolderService.toFileInfoList(filter.favs);
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     list = fileFolderService.listFolders(root);
 | |
|                 }
 | |
|             }
 | |
|             finally
 | |
|             {
 | |
|                 FileFilterMode.clearClient();
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             NodeRef nodeRef = fileFolderService.searchSimple(root, name);
 | |
|             FileInfo fileInfo;
 | |
|             list = nodeRef == null || !(fileInfo = fileFolderService.getFileInfo(nodeRef)).isFolder() ? Collections.<FileInfo>emptyList() : Collections.singletonList(fileInfo);
 | |
|         }
 | |
|         
 | |
|         if (index < 0)
 | |
|         {
 | |
|             String userName = user.getLogin();
 | |
|             Set<NodeRef> unsubscribedFodlers = getUnsubscribedFolders(userName);
 | |
| 
 | |
|             // This is the last level            
 | |
|             for (FileInfo fileInfo : list)
 | |
|             {
 | |
|                 if (!filter.isEnterSubfolder(fileInfo.getNodeRef()))
 | |
|                 {
 | |
|                     continue;
 | |
|                 }
 | |
|                 String folderPath = rootPathPrefix + fileInfo.getName();
 | |
|                 if (!listSubscribed || !unsubscribedFodlers.contains(fileInfo.getNodeRef()))
 | |
|                 {
 | |
|                     fullList.add(new AlfrescoImapFolder(fileInfo, userName, fileInfo.getName(), folderPath, viewMode,
 | |
|                             isExtractionEnabled(fileInfo.getNodeRef()), this, serviceRegistry, mountPointId));
 | |
|                 }
 | |
|                 else if (name.endsWith("%") && !expandFolder(fileInfo.getNodeRef(), user, folderPath, "%", true, viewMode, mountPointId).isEmpty()) // \NoSelect 
 | |
|                 {
 | |
|                     fullList.add(new AlfrescoImapFolder(fileInfo, userName, fileInfo.getName(), folderPath, viewMode,
 | |
|                             this, serviceRegistry, false, isExtractionEnabled(fileInfo.getNodeRef()), mountPointId));
 | |
|                 }
 | |
|                 if (name.endsWith("*"))
 | |
|                 {
 | |
|                     fullList.addAll(expandFolder(fileInfo.getNodeRef(), user, folderPath, "*", listSubscribed, viewMode, mountPointId));                            
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {    
 | |
|             // If (index != -1) this is not the last level
 | |
|             for (FileInfo folder : list)
 | |
|             {
 | |
|                 if (!filter.isEnterSubfolder(folder.getNodeRef()))
 | |
|                 {
 | |
|                     continue;
 | |
|                 }
 | |
|                 fullList.addAll(expandFolder(folder.getNodeRef(), user, rootPathPrefix + folder.getName(),
 | |
|                         mailboxPattern.substring(index + 1), listSubscribed, viewMode, mountPointId));
 | |
|             }
 | |
|         }
 | |
|         return fullList;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Map of mount points. Name of mount point == key in the map.
 | |
|      * 
 | |
|      * @return Map of mount points.
 | |
|      */
 | |
|     private NodeRef getMountPoint(String rootFolder)
 | |
|     {
 | |
|         final ImapConfigMountPointsBean config = imapConfigMountPoints.get(rootFolder);
 | |
|         try
 | |
|         {
 | |
|             // Get node reference. Do it in new transaction to avoid RollBack in case when AccessDeniedException is thrown.
 | |
|             return serviceRegistry.getTransactionService().getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<NodeRef>()
 | |
|             {
 | |
|                 public NodeRef execute() throws Exception
 | |
|                 {
 | |
|                     try
 | |
|                     {
 | |
|                         return config.getFolderPathOrNull(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);
 | |
|         }
 | |
|         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;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * 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.
 | |
|      */
 | |
|     public 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 Set<NodeRef> getUnsubscribedFolders(String userName)
 | |
|     {
 | |
|         Set<NodeRef> result = new HashSet<NodeRef>();
 | |
|         PersonService personService = serviceRegistry.getPersonService();
 | |
|         NodeRef userRef = personService.getPerson(userName);
 | |
|         List<AssociationRef> unsubscribedFodlers = nodeService.getTargetAssocs(userRef, ImapModel.ASSOC_IMAP_UNSUBSCRIBED);
 | |
|         for (AssociationRef asocRef : unsubscribedFodlers)
 | |
|         {
 | |
|             result.add(asocRef.getTargetRef());
 | |
|         }
 | |
|     
 | |
|         return result;
 | |
|     }
 | |
|     
 | |
|     
 | |
|     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);
 | |
|     
 | |
|             for (String key : prefs.keySet())
 | |
|             {
 | |
|                 Boolean isImapFavourite = (Boolean) prefs.get(key);
 | |
|                 if (isImapFavourite != null && isImapFavourite)
 | |
|                 {
 | |
|                     String siteName = key.substring(AlfrescoImapConst.PREF_IMAP_FAVOURITE_SITES.length() + 1); // count the dot
 | |
|                     boolean isMember = false;
 | |
|                     try
 | |
|                     {
 | |
|                         isMember = serviceRegistry.getSiteService().isMember(siteName, userName);
 | |
|                     }
 | |
|                     catch (SiteDoesNotExistException sdne)
 | |
|                     {
 | |
|                         // Ignore, see MNT-13579
 | |
|                         // The site might be archived. In this case it will still be in user's preferences.
 | |
|                     }
 | |
|                     
 | |
|                     if (isMember)
 | |
|                     {
 | |
|                         SiteInfo siteInfo = serviceRegistry.getSiteService().getSite(siteName);
 | |
|                         if (siteInfo != null)
 | |
|                         {
 | |
|                             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 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(ContentModel.ASPECT_AUDITABLE);
 | |
|                     policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_VERSIONABLE);
 | |
|                     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(ContentModel.ASPECT_AUDITABLE);
 | |
|                     policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_VERSIONABLE);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         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);
 | |
|                     
 | |
|                     NodeRef rootNode = nodeService.getRootNode(storeRef);
 | |
|                     
 | |
|                     List<NodeRef> templates = searchService.selectNodes(rootNode, query, null, namespaceService, true);
 | |
|                     if (templates == null || templates.size() == 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 = templates.get(0);
 | |
|       
 | |
|                     NodeRef localisedSibling = serviceRegistry.getFileFolderService().getLocalizedSibling(defaultLocaleTemplate);
 | |
|                     result = localisedSibling.toString();
 | |
|                 }
 | |
|                 // We are catching all exceptions. E.g. search service can possibly throw an exceptions on malformed queries.
 | |
|                 catch (Exception e)
 | |
|                 {
 | |
|                     logger.error("ImapServiceImpl [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 = ImapServiceImpl.this.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)
 | |
|         {
 | |
|             return isEnterSubfolder(subfolderRef.getChildRef());
 | |
|         }
 | |
|         
 | |
|         public boolean isEnterSubfolder(NodeRef folder)
 | |
|         {        
 | |
|             String name = (String) nodeService.getProperty(folder, ContentModel.PROP_NAME);
 | |
|             if (mailboxPattern != null)
 | |
|             {
 | |
|                 logger.debug("Child name: " + 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 :" + name);
 | |
|                         return true;
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         logger.debug("[ImapSubFolderFilter] (VIRTUAL) excluding non fav site folder :" + name);
 | |
|                         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 :" + name);
 | |
|                     return false;
 | |
|                 }
 | |
|             }
 | |
|             return true;
 | |
|         }
 | |
|         
 | |
|     }
 | |
| 
 | |
|     private UidValidityTransactionListener getUidValidityTransactionListener(NodeRef folderRef)
 | |
|     {
 | |
|         String key = UIDVALIDITY_TRANSACTION_LISTENER + folderRef.toString();
 | |
|         UidValidityTransactionListener txnListener = AlfrescoTransactionSupport.getResource(key);
 | |
|         if (txnListener == null)
 | |
|         {
 | |
|             txnListener = new UidValidityTransactionListener(folderRef);
 | |
|             AlfrescoTransactionSupport.bindListener(txnListener);
 | |
|             AlfrescoTransactionSupport.bindResource(key, txnListener);
 | |
|         }
 | |
|         return txnListener;        
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public void onCreateChildAssociation(final ChildAssociationRef childAssocRef, boolean isNewNode)
 | |
|     {
 | |
|         doAsSystem(new RunAsWork<Void>()
 | |
|         {
 | |
|             @Override
 | |
|             public Void doWork() throws Exception
 | |
|             {
 | |
|                 NodeRef childNodeRef = childAssocRef.getChildRef();
 | |
|                 
 | |
|                 if (serviceRegistry.getDictionaryService().isSubClass(nodeService.getType(childNodeRef), ContentModel.TYPE_CONTENT))
 | |
|                 {
 | |
|                     long newId = (Long) nodeService.getProperty(childNodeRef, ContentModel.PROP_NODE_DBID);
 | |
|                     // Keep a record of minimum and maximum node IDs in this folder in this transaction and add a listener that will
 | |
|                     // update the UIDVALIDITY and MAXUID properties appropriately. Also force generation of a new change token
 | |
|                     getUidValidityTransactionListener(childAssocRef.getParentRef()).recordNewUid(newId);
 | |
|                     // Flag new content as recent
 | |
|                     setFlag(childNodeRef, Flags.Flag.RECENT, true);
 | |
|                 }
 | |
|                 
 | |
|                 if (logger.isDebugEnabled())
 | |
|                 {
 | |
|                     logger.debug("[onCreateChildAssociation] Association " + childAssocRef + " created. CHANGETOKEN will be changed.");
 | |
|                 }
 | |
|                 return null;
 | |
|             }
 | |
|         });
 | |
|     }
 | |
|     
 | |
|     @Override
 | |
|     public void onDeleteChildAssociation(final ChildAssociationRef childAssocRef)
 | |
|     {
 | |
|         doAsSystem(new RunAsWork<Void>()
 | |
|         {
 | |
|             @Override
 | |
|             public Void doWork() throws Exception
 | |
|             {
 | |
|                 NodeRef childNodeRef = childAssocRef.getChildRef();
 | |
|                 if (serviceRegistry.getDictionaryService().isSubClass(nodeService.getType(childNodeRef),
 | |
|                         ContentModel.TYPE_CONTENT))
 | |
|                 {
 | |
|                     // Force generation of a new change token
 | |
|                     getUidValidityTransactionListener(childAssocRef.getParentRef());
 | |
| 
 | |
|                     // Remove the message from the cache
 | |
|                     messageCache.remove(childNodeRef);
 | |
|                 }
 | |
|                 if (logger.isDebugEnabled())
 | |
|                 {
 | |
|                     logger.debug("[onDeleteChildAssociation] Association " + childAssocRef
 | |
|                             + " created. CHANGETOKEN will be changed.");
 | |
|                 }
 | |
|                 return null;
 | |
|             }
 | |
|         });
 | |
|     }
 | |
|     
 | |
|     @Override
 | |
|     public void onUpdateProperties(final NodeRef nodeRef, final Map<QName, Serializable> before,
 | |
|             final Map<QName, Serializable> after)
 | |
|     {
 | |
|         doAsSystem(new RunAsWork<Void>()
 | |
|         {
 | |
|             @Override
 | |
|             public Void doWork() throws Exception
 | |
|             { 
 | |
|                 /**
 | |
|                  * Imap only cares about a few properties however if those properties 
 | |
|                  * change then the uidvalidity needs to be reset otherwise the new content
 | |
|                  * won't get re-loaded.   This is nonsense for an email server, but needed for 
 | |
|                  * modifiable repository.  Also we need to ignore certain properties.
 | |
|                  */
 | |
|                 boolean hasChanged = false;
 | |
|                 
 | |
|                 if(!hasChanged)
 | |
|                 {
 | |
|                     hasChanged = !EqualsHelper.nullSafeEquals(before.get(ContentModel.PROP_NAME), after.get(ContentModel.PROP_NAME));
 | |
|                 }
 | |
|                 if(!hasChanged)
 | |
|                 {
 | |
|                     hasChanged = !EqualsHelper.nullSafeEquals(before.get(ContentModel.PROP_AUTHOR), after.get(ContentModel.PROP_AUTHOR));
 | |
|                 }
 | |
|                 if(!hasChanged)
 | |
|                 {
 | |
|                     hasChanged = !EqualsHelper.nullSafeEquals(before.get(ContentModel.PROP_TITLE), after.get(ContentModel.PROP_TITLE));
 | |
|                 }
 | |
|                 if(!hasChanged)
 | |
|                 {
 | |
|                     hasChanged = !EqualsHelper.nullSafeEquals(before.get(ContentModel.PROP_DESCRIPTION), after.get(ContentModel.PROP_DESCRIPTION));
 | |
|                 }
 | |
|                 
 | |
|                 if(!hasChanged)
 | |
|                 {
 | |
|                     Serializable s1 = before.get(ContentModel.PROP_CONTENT);
 | |
|                     Serializable s2 = after.get(ContentModel.PROP_CONTENT);
 | |
|                 
 | |
|                     if(s1 != null && s2 != null)
 | |
|                     {
 | |
|                         ContentData c1 = (ContentData)s1;
 | |
|                         ContentData c2 = (ContentData)s2;
 | |
|                         
 | |
|                         hasChanged = !EqualsHelper.nullSafeEquals(c1.getContentUrl(), c2.getContentUrl());
 | |
|                     }
 | |
|                 }
 | |
|                 
 | |
|                 for (ChildAssociationRef parentAssoc : nodeService.getParentAssocs(nodeRef))
 | |
|                 {
 | |
|                     NodeRef folderRef = parentAssoc.getParentRef();
 | |
|                     if (nodeService.hasAspect(folderRef, ImapModel.ASPECT_IMAP_FOLDER))
 | |
|                     {
 | |
|                         messageCache.remove(nodeRef);
 | |
| 
 | |
|                         // Force generation of a new change token for the parent folders
 | |
|                         UidValidityTransactionListener listener = getUidValidityTransactionListener(folderRef);
 | |
|                 
 | |
|                         // if we have a significant change then we need to force a new uidvalidity.
 | |
|                         if(hasChanged)
 | |
|                         {
 | |
|                             logger.debug("message has changed - force new uidvalidity for the parent folder");
 | |
|                             listener.forceNewUidvalidity();
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|                 return null;
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public void onRestoreNode(final ChildAssociationRef childAssocRef)
 | |
|     {
 | |
|         doAsSystem(new RunAsWork<Void>()
 | |
|         {
 | |
|             @Override
 | |
|             public Void doWork() throws Exception
 | |
|             {
 | |
|                 NodeRef childNodeRef = childAssocRef.getChildRef();
 | |
|                 if (serviceRegistry.getDictionaryService().isSubClass(nodeService.getType(childNodeRef), ContentModel.TYPE_CONTENT))
 | |
|                 {
 | |
|                     setFlag(childNodeRef, Flags.Flag.DELETED, false);
 | |
|                     setFlag(childNodeRef, Flags.Flag.SEEN, false);
 | |
|                 }
 | |
| 
 | |
|                 NodeRef folderRef = childAssocRef.getParentRef();
 | |
|                 long newId = (Long) nodeService.getProperty(childNodeRef, ContentModel.PROP_NODE_DBID);
 | |
|                 if (nodeService.hasAspect(folderRef, ImapModel.ASPECT_IMAP_FOLDER))
 | |
|                 {
 | |
|                     // Force generation of a new change token and updating the UIDVALIDITY 
 | |
|                     getUidValidityTransactionListener(folderRef).recordNewUid(newId);
 | |
|                 }
 | |
|                 return null;
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public void beforeDeleteNode(final NodeRef nodeRef)
 | |
|     {
 | |
|         doAsSystem(new RunAsWork<Void>()
 | |
|         {
 | |
|             @Override
 | |
|             public Void doWork() throws Exception
 | |
|             {
 | |
|                 for (ChildAssociationRef parentAssoc : nodeService.getParentAssocs(nodeRef))
 | |
|                 {
 | |
|                     NodeRef folderRef = parentAssoc.getParentRef();
 | |
|                     if (nodeService.hasAspect(folderRef, ImapModel.ASPECT_IMAP_FOLDER))
 | |
|                     {
 | |
|                         messageCache.remove(nodeRef);
 | |
| 
 | |
|                         // Force generation of a new change token
 | |
|                         getUidValidityTransactionListener(folderRef);
 | |
|                     }
 | |
|                 }
 | |
|                 return null;
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     private <R> R doAsSystem(RunAsWork<R> work)
 | |
|     {
 | |
|         policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE);
 | |
|         policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_VERSIONABLE);
 | |
|         try
 | |
|         {
 | |
|             return AuthenticationUtil.runAs(work, AuthenticationUtil.getSystemUserName());
 | |
|         }
 | |
|         finally
 | |
|         {
 | |
|             policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_AUDITABLE);
 | |
|             policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_VERSIONABLE);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private class UidValidityTransactionListener extends TransactionListenerAdapter
 | |
|     {
 | |
|         // Generate a unique token for each folder change with which we can validate session caches
 | |
|         private String changeToken = GUID.generate();
 | |
|         private NodeRef folderNodeRef;
 | |
|         private Long minUid;
 | |
|         private Long maxUid;
 | |
|         private boolean forceNewUidValidity = false;
 | |
|         
 | |
|         public UidValidityTransactionListener(NodeRef folderNodeRef)
 | |
|         {
 | |
|             this.folderNodeRef = folderNodeRef;
 | |
|         }
 | |
|         
 | |
|         public void forceNewUidvalidity()
 | |
|         {
 | |
|             this.forceNewUidValidity = true;
 | |
|         }
 | |
|         
 | |
|         public void recordNewUid(long newUid)
 | |
|         {
 | |
|             if (this.minUid == null)
 | |
|             {
 | |
|                 this.minUid = this.maxUid = newUid;
 | |
|             }
 | |
|             else if (newUid < this.minUid)
 | |
|             {
 | |
|                 this.minUid = newUid;
 | |
|             }
 | |
|             else if (newUid > this.maxUid)
 | |
|             {
 | |
|                 this.maxUid = newUid;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         @Override
 | |
|         public void beforeCommit(boolean readOnly)
 | |
|         {
 | |
|             if (readOnly)
 | |
|             {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             doAsSystem(new RunAsWork<Void>()
 | |
|             {
 | |
|                 @Override
 | |
|                 public Void doWork() throws Exception
 | |
|                 {
 | |
|                     // This fires at the end of the transaction, so double-check that the node is still present
 | |
|                     if (!nodeService.exists(folderNodeRef))
 | |
|                     {
 | |
|                         return null;
 | |
|                     }
 | |
|                     
 | |
|                     if (UidValidityTransactionListener.this.forceNewUidValidity || UidValidityTransactionListener.this.minUid != null)
 | |
|                     {
 | |
|                         long modifDate = System.currentTimeMillis();
 | |
|                         Long oldMax = (Long) nodeService.getProperty(folderNodeRef, ImapModel.PROP_MAXUID);
 | |
|                         // Only update UIDVALIDITY if a new node has and ID that is smaller or equals the old maximum (as UIDs are always meant to increase)
 | |
|                         if (UidValidityTransactionListener.this.forceNewUidValidity || oldMax == null || UidValidityTransactionListener.this.minUid <= oldMax)
 | |
|                         {
 | |
|                             nodeService.setProperty(folderNodeRef, ImapModel.PROP_UIDVALIDITY, modifDate);                            
 | |
|                             if (logger.isDebugEnabled())
 | |
|                             {
 | |
|                                 logger.debug("UIDVALIDITY was modified for folder, nodeRef:" + folderNodeRef);
 | |
|                             }
 | |
|                         }
 | |
|                         if(UidValidityTransactionListener.this.maxUid != null)
 | |
|                         {
 | |
|                             nodeService.setProperty(folderNodeRef, ImapModel.PROP_MAXUID, UidValidityTransactionListener.this.maxUid);
 | |
|                             if (logger.isDebugEnabled())
 | |
|                             {
 | |
|                                 logger.debug("MAXUID was modified for folder, nodeRef:" + folderNodeRef);
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                     nodeService.setProperty(folderNodeRef, ImapModel.PROP_CHANGE_TOKEN, changeToken);                            
 | |
|                     return null;
 | |
|                 }                        
 | |
|             });
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public NodeRef getNodeSiteContainer(final NodeRef inputNodeRef)
 | |
|     {
 | |
|         return doAsSystem(new RunAsWork<NodeRef>()
 | |
|         {
 | |
|             @Override
 | |
|             public NodeRef doWork() throws Exception
 | |
|             {
 | |
|                 NodeRef nodeRef = inputNodeRef;
 | |
|                 while (true)
 | |
|                 {
 | |
|                     NodeRef parent = nodeService.getPrimaryParent(nodeRef).getParentRef();
 | |
|                     if (parent == null)
 | |
|                     {
 | |
|                         if (logger.isDebugEnabled())
 | |
|                         {
 | |
|                             logger.debug("The node with nodeRef:" + inputNodeRef + " is not in the site.");
 | |
|                         }
 | |
|                         nodeRef = null;
 | |
|                         break;
 | |
|                     }
 | |
|                     nodeRef = parent;
 | |
|                     if (nodeService.hasAspect(parent, SiteModel.ASPECT_SITE_CONTAINER))
 | |
|                     {
 | |
|                         if (logger.isDebugEnabled())
 | |
|                         {
 | |
|                             logger.debug("The node with nodeRef:" + inputNodeRef + " is in the site.");
 | |
|                         }
 | |
|                         // found the container
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
|                 return nodeRef;
 | |
|             }
 | |
|         });
 | |
| 
 | |
|     }
 | |
| 
 | |
|     public String getContentFolderUrl(NodeRef contentNodeRef)
 | |
|     {
 | |
|         String url = "";
 | |
|         String CONTAINER_URL_TEMPLATE = "%s/page/site/%s";
 | |
|         String REPOSITORY_URL_TEMPLATE = "%s/page/%s";
 | |
| 
 | |
|         NodeRef siteContainerNodeRef = getNodeSiteContainer(contentNodeRef);
 | |
| 
 | |
|         if (siteContainerNodeRef != null)
 | |
|         {
 | |
|             // the node is in site's container
 | |
|             // determine which one
 | |
| 
 | |
|             NodeRef siteNodeRef = nodeService.getPrimaryParent(siteContainerNodeRef).getParentRef();
 | |
|             String siteName = ((String) nodeService.getProperty(siteNodeRef, ContentModel.PROP_NAME)).toLowerCase();
 | |
|             String componentId = (String) nodeService.getProperty(siteContainerNodeRef, SiteModel.PROP_COMPONENT_ID);
 | |
|             switch (componentId.toLowerCase())
 | |
|             {
 | |
|                 case "datalists":
 | |
|                     url = String.format(CONTAINER_URL_TEMPLATE, getShareApplicationContextUrl(), siteName + "/data-lists");
 | |
|                     break;
 | |
|                 case "wiki":
 | |
|                     url = String.format(CONTAINER_URL_TEMPLATE, getShareApplicationContextUrl(), siteName + "/wiki");
 | |
|                     break;
 | |
|                 case "links":
 | |
|                     url = String.format(CONTAINER_URL_TEMPLATE, getShareApplicationContextUrl(), siteName + "/links");
 | |
|                     break;
 | |
|                 case "calendar":
 | |
|                     url = String.format(CONTAINER_URL_TEMPLATE, getShareApplicationContextUrl(), siteName + "/calendar");
 | |
|                     break;
 | |
|                 case "discussions":
 | |
|                     url = String.format(CONTAINER_URL_TEMPLATE, getShareApplicationContextUrl(), siteName + "/discussions-topiclist");
 | |
|                     break;
 | |
|                 case "blog":
 | |
|                     url = String.format(CONTAINER_URL_TEMPLATE, getShareApplicationContextUrl(), siteName + "/blog-postlist");
 | |
|                     break;
 | |
|                 case "documentlibrary":
 | |
|                     String pathFromSites = getPathFromSites(nodeService.getPrimaryParent(contentNodeRef).getParentRef());
 | |
|                     StringBuilder parsedPath = new StringBuilder();
 | |
|                     String[] pathParts = pathFromSites.split("/");
 | |
|                     if (pathParts.length > 2)
 | |
|                     {
 | |
|                         parsedPath.append(pathParts[0] + "/" + pathParts[1]);
 | |
|                         parsedPath.append("?filter=path|");
 | |
|                         for (int i = 2; i < pathParts.length; i++)
 | |
|                         {
 | |
|                             parsedPath.append("/").append(pathParts[i]);
 | |
|                         }
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         parsedPath.append(pathFromSites);
 | |
|                     }
 | |
|                     url = String.format(CONTAINER_URL_TEMPLATE, getShareApplicationContextUrl(), parsedPath.toString());
 | |
|                     break;
 | |
|                 default:
 | |
|                     break;
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             // the node is in repository
 | |
|             String pathFromRepo = getPathFromRepo(nodeService.getPrimaryParent(contentNodeRef));
 | |
|             StringBuilder parsedPath = new StringBuilder();
 | |
|             String[] pathParts = pathFromRepo.split("/");
 | |
|             if (pathParts.length > 1)
 | |
|             {
 | |
|                 parsedPath.append(pathParts[0]);
 | |
|                 parsedPath.append("?filter=path|");
 | |
|                 for (int i = 1; i < pathParts.length; i++)
 | |
|                 {
 | |
|                     parsedPath.append("/").append(pathParts[i]);
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 parsedPath.append(pathFromRepo);
 | |
|             }
 | |
|             url = String.format(REPOSITORY_URL_TEMPLATE, getShareApplicationContextUrl(), parsedPath.toString());
 | |
|         }
 | |
|         if (logger.isDebugEnabled())
 | |
|         {
 | |
|             logger.debug("Resolved content folder URL:" + url + " for node " + contentNodeRef);
 | |
|         }
 | |
|         return url;
 | |
|     }
 | |
| 
 | |
|     public void setNamespaceService(NamespaceService namespaceService)
 | |
|     {
 | |
|         this.namespaceService = namespaceService;
 | |
|     }
 | |
| 
 | |
|     public NamespaceService getNamespaceService()
 | |
|     {
 | |
|         return namespaceService;
 | |
|     }
 | |
| 
 | |
|     public void setSearchService(SearchService searchService)
 | |
|     {
 | |
|         this.searchService = searchService;
 | |
|     }
 | |
| 
 | |
|     public SearchService getSearchService()
 | |
|     {
 | |
|         return searchService;
 | |
|     }
 | |
| 
 | |
|     public void extractAttachments(NodeRef messageRef, MimeMessage originalMessage) throws IOException, MessagingException
 | |
|     {
 | |
|         attachmentsExtractor.extractAttachments(messageRef, originalMessage);
 | |
|     }
 | |
| 
 | |
|     public String generateUniqueFilename(NodeRef destFolderNodeRef, String fileName)
 | |
|     {
 | |
|         if(fileFolderService.searchSimple(destFolderNodeRef, fileName) != null)
 | |
|         {
 | |
|             String name = fileName;
 | |
|             String ext = "";
 | |
|             if (fileName.lastIndexOf(".") != -1)
 | |
|             {
 | |
|                 int index = fileName.lastIndexOf(".");
 | |
|                 name = fileName.substring(0, index);
 | |
|                 ext = fileName.substring(index);
 | |
|             }
 | |
|             int copyNum = 0;
 | |
|             do
 | |
|             {
 | |
|                 copyNum++;
 | |
|             } while (fileFolderService.searchSimple(destFolderNodeRef, name + " (" + copyNum + ")" + ext) != null);
 | |
|                 fileName =  name + " (" + copyNum + ")" + ext;
 | |
|         }
 | |
|      	
 | |
|         return fileName;
 | |
|     }
 | |
|     
 | |
|     @SuppressWarnings("unchecked")
 | |
|     public void persistMessageHeaders(NodeRef messageRef, MimeMessage message)
 | |
|     {
 | |
|         try
 | |
|         {
 | |
|             Enumeration<Header> headers = message.getAllHeaders();
 | |
|             List<String> messaheHeadersProperties = new ArrayList<String>();
 | |
|             while(headers.hasMoreElements())
 | |
|             {
 | |
|                 Header header = headers.nextElement();
 | |
|                 if (isPersistableHeader(header))
 | |
|                 {
 | |
|                     messaheHeadersProperties.add(header.getName() + ImapModel.MESSAGE_HEADER_TO_PERSIST_SPLITTER + header.getValue());
 | |
|                     
 | |
|                     if (logger.isDebugEnabled())
 | |
|                     {
 | |
|                         logger.debug("[persistHeaders] Persisting Header " + header.getName() + " : " + header.getValue());
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             
 | |
|             Map<QName, Serializable> props = new HashMap<QName, Serializable>();
 | |
|             props.put(ImapModel.PROP_MESSAGE_HEADERS, (Serializable)messaheHeadersProperties);
 | |
|             
 | |
|             serviceRegistry.getNodeService().addAspect(messageRef, ImapModel.ASPECT_IMAP_MESSAGE_HEADERS, props);
 | |
|         }
 | |
|         catch(MessagingException me)
 | |
|         {
 | |
|             
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     private boolean isPersistableHeader(Header header)
 | |
|     {
 | |
|         for (String headerToPersist : messageHeadersToPersist)
 | |
|         {
 | |
|             if (headerToPersist.equalsIgnoreCase(header.getName()))
 | |
|             {
 | |
|                 return true;
 | |
|             }
 | |
|         }
 | |
|         return false; 
 | |
|     }
 | |
|     
 | |
|     static class CacheItem
 | |
|     {
 | |
|         private Date modified;
 | |
|         private SimpleStoredMessage message;
 | |
|         
 | |
|         public CacheItem(Date modified, SimpleStoredMessage message)
 | |
|         {
 | |
|             this.setMessage(message);
 | |
|             this.setModified(modified);
 | |
|         }
 | |
| 
 | |
|         public void setModified(Date modified)
 | |
|         {
 | |
|             this.modified = modified;
 | |
|         }
 | |
| 
 | |
|         public Date getModified()
 | |
|         {
 | |
|             return modified;
 | |
|         }
 | |
| 
 | |
|         public void setMessage(SimpleStoredMessage message)
 | |
|         {
 | |
|             this.message = message;
 | |
|         }
 | |
| 
 | |
|         public SimpleStoredMessage getMessage()
 | |
|         {
 | |
|             return message;
 | |
|         }
 | |
|     }    
 | |
| 
 | |
|     @Override
 | |
|     public String getPathFromSites(final NodeRef ref)
 | |
|     {
 | |
|         return doAsSystem(new RunAsWork<String>()
 | |
|         {
 | |
|             @Override
 | |
|             public String doWork() throws Exception
 | |
|             {
 | |
|                 String name = ((String) nodeService.getProperty(ref, ContentModel.PROP_NAME)).toLowerCase();
 | |
|                 if (nodeService.getType(ref).equals(SiteModel.TYPE_SITE))
 | |
|                 {
 | |
|                     return name;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     NodeRef parent = nodeService.getPrimaryParent(ref).getParentRef();
 | |
|                     return getPathFromSites(parent) + "/" + name;
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     @Override
 | |
|     public String getPathFromRepo(final ChildAssociationRef assocRef)
 | |
|     {
 | |
|         return doAsSystem(new RunAsWork<String>()
 | |
|         {
 | |
|             @Override
 | |
|             public String doWork() throws Exception
 | |
|             {
 | |
|                 NodeRef ref = assocRef.getParentRef();
 | |
|                 String name = ((String) nodeService.getProperty(ref, ContentModel.PROP_NAME)).toLowerCase();
 | |
|                 ChildAssociationRef parent = nodeService.getPrimaryParent(ref);
 | |
|                 QName qname = parent.getQName();
 | |
|                 if (qname.equals(QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "company_home")))
 | |
|                 {
 | |
|                     return "repository";
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     return getPathFromRepo(parent) + "/" + name;
 | |
|                 }
 | |
|             }
 | |
|         });
 | |
|     }
 | |
| } |