mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
32598: ALF-11522 IMAP: Generic AlfrescoImapFolderException error is a bit misleading Text was duplicated - previously modified text was not being used 32690: Merged V3.4-BUG-FIX to V3.4 32689: ALF-11603: Use different UIDVALIDITY values for different mounts of the same folder in IMAP to force client to fetch in both locations 32691: Merged PATCHES/V3.4.6 to V3.4 32397: ALF-9878 / ALF-11727 CLONE - Pending Invite Search doesn't return anything if there's more than 1000 pending invites across all sites. Return first 200 invitations (similar to 4.0 paging) Transaction used by the invites.get is now read only so does not force a flush of caches. 32404: ALF-9878 / ALF-11727 CLONE - Pending Invite Search doesn't return anything if there's more than 1000 pending invites across all sites. Removed read only transaction from invites.get.desc.xml as it broke InviteServiceTest testRejectInvite 32501: ALF-9878 / ALF-11727: Reinstated read-only transaction around invites.get and prevented it from trying to lazily create persons from rejected invites that had previously been deleted by InviteHelper.cleanUpStaleInviteeResources! 32617: ALF-11977 / ALF-11879: IMAP performance - Fix node batch loading - batch load ContentData to avoid N+1 problem with content properties - During cache preloading, use distinct transactions for each folder search, thus avoiding blowing the transactional caches 32619: ALF-11977 / ALF-11879: Fixed typo 32652: ALF-11977 / ALF-11879: Deactivate auto-versioning and auditing (and run as system) whilst setting magic IMAP aspect properties 32683: ALF-11978 / ALF-11915: iBatis synchronizes on the ClassInfo class during high concurrency Similar change made to ibatis ClassInfo.java as was made to mybatis (in 4.0) for ALF-11894 src.diff is diff from new SVN location at http://mybatis.googlecode.com/svn/tags/java_release_2.3.4-726/ibatis-2-core hence the revision number changes modified the pom to use Java 1.5 (1.6 does not work due to changes in abstract sql packages) and removed a plugin that no longer works 32721: ALF-11913 Search in Explorer does not find documents found by Share Correct bug introduced by ALF-11435 'support for hidden files' 32735: ALF-12021: Merged PATCHES/V3.4.5 to V3.4 32513: ALF-11720: Merged HEAD to PATCHES/V3.4.5 32330: Merged DEV to HEAD 32207: Fix ALF-11644: AVM cleanup jobs run when WCM is not installed - Moved scheduled jobs to installable wcm-bootstrap-context.xml - Also got rid of orphan reaper warnings when running in a cluster (not ALF-11720, but mentioned in it) 32329: Fix ALF-11734: Concurrency conditions not handled correctly for ACL persistence - Struggled to get reproducible test in code - If this doesn't fix the problem fully, then at least it'll push it up a level, however the higher caches already have pessimistic removal of entries, so should be OK - In the same way, this could fix ALF-11720: WCM: OrphanReaper contention throws error (3.4.x) 32381: Minor follow up on 32329: ALF-11734: Concurrency conditions not handled correctly for ACL persistence - Pessimistically throw away the node cache entry referencing the ACL 32737: ALF-9878: Reverse merged following revisions from failed hotfix - deferred to 3.4.8 32397: ALF-11727 CLONE - Pending Invite Search doesn't return anything if there's more than 1000 pending invites across all sites. Return first 200 invitations (similar to 4.0 paging) Transaction used by the invites.get is now read only so does not force a flush of caches. 32404: ALF-11727 CLONE - Pending Invite Search doesn't return anything if there's more than 1000 pending invites across all sites. Removed read only transaction from invites.get.desc.xml as it broke InviteServiceTest testRejectInvite 32501: ALF-11727: Reinstated read-only transaction around invites.get and prevented it from trying to lazily create persons from rejected invites that had previously been deleted by InviteHelper.cleanUpStaleInviteeResources! git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@32743 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
462 lines
15 KiB
Java
462 lines
15 KiB
Java
/*
|
|
* Copyright (C) 2005-2010 Alfresco Software Limited.
|
|
*
|
|
* This file is part of Alfresco
|
|
*
|
|
* Alfresco is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Alfresco is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package org.alfresco.repo.imap;
|
|
|
|
import java.util.Date;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
|
|
import javax.mail.Flags;
|
|
import javax.mail.internet.MimeMessage;
|
|
import javax.mail.search.SearchTerm;
|
|
|
|
import org.alfresco.repo.imap.exception.AlfrescoImapFolderException;
|
|
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
|
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
|
import org.alfresco.service.ServiceRegistry;
|
|
|
|
import com.icegreen.greenmail.foedus.util.MsgRangeFilter;
|
|
import com.icegreen.greenmail.mail.MovingMessage;
|
|
import com.icegreen.greenmail.store.FolderException;
|
|
import com.icegreen.greenmail.store.FolderListener;
|
|
import com.icegreen.greenmail.store.MailFolder;
|
|
import com.icegreen.greenmail.store.SimpleStoredMessage;
|
|
|
|
/**
|
|
* Implementation of greenmail MailFolder. It represents an Alfresco content folder and handles
|
|
* appendMessage, copyMessage, expunge (delete), getMessages, getMessage and so requests.
|
|
*
|
|
* @author Ivan Rybnikov
|
|
*/
|
|
public abstract class AbstractImapFolder implements MailFolder
|
|
{
|
|
private List<FolderListener> listeners = new LinkedList<FolderListener>();
|
|
|
|
protected ServiceRegistry serviceRegistry;
|
|
protected static int MAX_RETRIES = 1;
|
|
|
|
|
|
public AbstractImapFolder(ServiceRegistry serviceRegistry)
|
|
{
|
|
this.serviceRegistry = serviceRegistry;
|
|
}
|
|
|
|
/**
|
|
* Method that checks mandatory parameter.
|
|
* @param The parameter instance to check.
|
|
* @param The name of the parameter.
|
|
*/
|
|
protected void checkParameter(Object parameter, String name)
|
|
{
|
|
if (parameter == null)
|
|
{
|
|
throw new IllegalArgumentException(name + " parameter is null.");
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Appends message to the folder.
|
|
*
|
|
* @param message - message.
|
|
* @param flags - message flags.
|
|
* @param internalDate - not used. Current date used instead.
|
|
* @return
|
|
*/
|
|
public long appendMessage(final MimeMessage message, final Flags flags, final Date internalDate) throws FolderException
|
|
{
|
|
if (isReadOnly())
|
|
{
|
|
throw new FolderException("Can't append message - Permission denied");
|
|
}
|
|
|
|
CommandCallback<Long> command = new CommandCallback<Long>()
|
|
{
|
|
public Long command() throws Throwable
|
|
{
|
|
return appendMessageInternal(message, flags, internalDate);
|
|
}
|
|
};
|
|
return command.runFeedback();
|
|
}
|
|
|
|
|
|
/**
|
|
* Copies message with the given UID to the specified {@link MailFolder}.
|
|
*
|
|
* @param uid - UID of the message
|
|
* @param toFolder - reference to the destination folder.
|
|
*/
|
|
public void copyMessage(final long uid, final MailFolder toFolder) throws FolderException
|
|
{
|
|
AbstractImapFolder toImapMailFolder = (AbstractImapFolder) toFolder;
|
|
|
|
if (toImapMailFolder.isReadOnly())
|
|
{
|
|
throw new FolderException(AlfrescoImapFolderException.PERMISSION_DENIED);
|
|
}
|
|
|
|
CommandCallback<Object> command = new CommandCallback<Object>()
|
|
{
|
|
public Object command() throws Throwable
|
|
{
|
|
copyMessageInternal(uid, toFolder);
|
|
return null;
|
|
}
|
|
};
|
|
command.runFeedback();
|
|
}
|
|
|
|
/**
|
|
* Marks all messages in the folder as deleted using {@link Flags.Flag#DELETED} flag.
|
|
*/
|
|
public void deleteAllMessages() throws FolderException
|
|
{
|
|
CommandCallback<Object> command = new CommandCallback<Object>()
|
|
{
|
|
public Object command() throws Throwable
|
|
{
|
|
deleteAllMessagesInternal();
|
|
return null;
|
|
}
|
|
};
|
|
command.runFeedback();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Deletes messages marked with {@link Flags.Flag#DELETED}. Note that this message deletes all messages with this flag.
|
|
*/
|
|
public void expunge() throws FolderException
|
|
{
|
|
if (isReadOnly())
|
|
{
|
|
throw new FolderException("Can't expunge - Permission denied");
|
|
}
|
|
CommandCallback<Object> command = new CommandCallback<Object>()
|
|
{
|
|
public Object command() throws Throwable
|
|
{
|
|
expungeInternal();
|
|
return null;
|
|
}
|
|
};
|
|
command.runFeedback();
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns message by its UID.
|
|
*
|
|
* @param uid - UID of the message.
|
|
* @return message.
|
|
*/
|
|
public SimpleStoredMessage getMessage(final long uid)
|
|
{
|
|
CommandCallback<SimpleStoredMessage> command = new CommandCallback<SimpleStoredMessage>()
|
|
{
|
|
public SimpleStoredMessage command() throws Throwable
|
|
{
|
|
return getMessageInternal(uid);
|
|
}
|
|
};
|
|
return command.run();
|
|
}
|
|
|
|
/**
|
|
* Returns list of all messages in the folder.
|
|
*
|
|
* @return list of {@link SimpleStoredMessage} objects.
|
|
*/
|
|
public List<SimpleStoredMessage> getMessages()
|
|
{
|
|
CommandCallback<List<SimpleStoredMessage>> command = new CommandCallback<List<SimpleStoredMessage>>()
|
|
{
|
|
public List<SimpleStoredMessage> command() throws Throwable
|
|
{
|
|
return getMessagesInternal();
|
|
}
|
|
};
|
|
return command.run();
|
|
}
|
|
|
|
/**
|
|
* Returns list of messages by filter.
|
|
*
|
|
* @param msgRangeFilter - {@link MsgRangeFilter} object representing filter.
|
|
* @return list of filtered messages.
|
|
*/
|
|
public List<SimpleStoredMessage> getMessages(final MsgRangeFilter msgRangeFilter)
|
|
{
|
|
CommandCallback <List<SimpleStoredMessage>> command = new CommandCallback <List<SimpleStoredMessage>>()
|
|
{
|
|
public List<SimpleStoredMessage> command() throws Throwable
|
|
{
|
|
return getMessagesInternal(msgRangeFilter);
|
|
}
|
|
};
|
|
return command.run();
|
|
}
|
|
|
|
/**
|
|
* Returns the list of messages that have no {@link Flags.Flag#DELETED} flag set for current user.
|
|
*
|
|
* @return the list of non-deleted messages.
|
|
*/
|
|
public List<SimpleStoredMessage> getNonDeletedMessages()
|
|
{
|
|
CommandCallback <List<SimpleStoredMessage>> command = new CommandCallback<List<SimpleStoredMessage>>()
|
|
{
|
|
public List<SimpleStoredMessage> command() throws Throwable
|
|
{
|
|
return getNonDeletedMessagesInternal();
|
|
}
|
|
};
|
|
List<SimpleStoredMessage> result = (List<SimpleStoredMessage>)command.run();
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Replaces flags for the message with the given UID. If {@code addUid} is set to
|
|
* {@code true} {@link FolderListener} objects defined for this folder will be notified.
|
|
* {@code silentListener} can be provided - this listener wouldn't be notified.
|
|
*
|
|
* @param flags - new flags.
|
|
* @param uid - message UID.
|
|
* @param silentListener - listener that shouldn't be notified.
|
|
* @param addUid - defines whether or not listeners be notified.
|
|
*/
|
|
public void replaceFlags(final Flags flags, final long uid, final FolderListener silentListener, final boolean addUid) throws FolderException
|
|
{
|
|
CommandCallback<Object> command = new CommandCallback<Object>()
|
|
{
|
|
public Object command() throws Throwable
|
|
{
|
|
replaceFlagsInternal(flags, uid, silentListener, addUid);
|
|
return null;
|
|
}
|
|
};
|
|
command.runFeedback();
|
|
}
|
|
|
|
|
|
/**
|
|
* Simply returns UIDs of all messages in the folder.
|
|
*
|
|
* @param searchTerm - not used
|
|
* @return UIDs of the messages
|
|
*/
|
|
public long[] search(SearchTerm searchTerm)
|
|
{
|
|
return getMessageUids();
|
|
}
|
|
|
|
/**
|
|
* Sets flags for the message with the given UID. If {@code addUid} is set to {@code true}
|
|
* {@link FolderListener} objects defined for this folder will be notified.
|
|
* {@code silentListener} can be provided - this listener wouldn't be notified.
|
|
*
|
|
* @param flags - new flags.
|
|
* @param value - flags value.
|
|
* @param uid - message UID.
|
|
* @param silentListener - listener that shouldn't be notified.
|
|
* @param addUid - defines whether or not listeners be notified.
|
|
*/
|
|
public void setFlags(
|
|
final Flags flags,
|
|
final boolean value,
|
|
final long uid,
|
|
final FolderListener silentListener,
|
|
final boolean addUid)
|
|
throws FolderException
|
|
{
|
|
CommandCallback<Object> command = new CommandCallback<Object>()
|
|
{
|
|
public Object command() throws Throwable
|
|
{
|
|
setFlagsInternal(flags, value, uid, silentListener, addUid);
|
|
return null;
|
|
}
|
|
};
|
|
command.runFeedback();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Not supported. Added to implement {@link MailFolder#store(MovingMessage)}.
|
|
*/
|
|
public void store(MovingMessage mail) throws Exception
|
|
{
|
|
throw new UnsupportedOperationException("Method store(MovingMessage) is not suppoted.");
|
|
}
|
|
|
|
/**
|
|
* Not supported. Added to implement {@link MailFolder#store(MimeMessage)}.
|
|
*/
|
|
public void store(MimeMessage message) throws Exception
|
|
{
|
|
throw new UnsupportedOperationException("Method store(MimeMessage) is not suppoted.");
|
|
}
|
|
|
|
|
|
/**
|
|
* Adds {@link FolderListener} to the folder.
|
|
*
|
|
* @param listener - new listener.
|
|
*/
|
|
public void addListener(FolderListener listener)
|
|
{
|
|
listeners.add(listener);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Removes {@link FolderListener} from the folder.
|
|
*
|
|
* @param listener - Listener to remove.
|
|
*/
|
|
public void removeListener(FolderListener listener)
|
|
{
|
|
listeners.remove(listener);
|
|
}
|
|
|
|
/**
|
|
* Method is called before the deletion of the folder. Notifies {@link FolderListener} objects with
|
|
* {@link FolderListener#mailboxDeleted()} method calls.
|
|
*/
|
|
public void signalDeletion()
|
|
{
|
|
synchronized (listeners)
|
|
{
|
|
for (int i = 0; i < listeners.size(); i++)
|
|
{
|
|
FolderListener listener = (FolderListener) listeners.get(i);
|
|
listener.mailboxDeleted();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
protected void notifyFlagUpdate(int msn, Flags flags, Long uidNotification, FolderListener silentListener)
|
|
{
|
|
synchronized (listeners)
|
|
{
|
|
for (FolderListener listener : listeners)
|
|
{
|
|
if (listener == silentListener)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
listener.flagsUpdated(msn, flags, uidNotification);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
protected abstract boolean isReadOnly();
|
|
|
|
protected abstract long appendMessageInternal(MimeMessage message, Flags flags, Date internalDate) throws Exception;
|
|
|
|
protected abstract void copyMessageInternal(long uid, MailFolder toFolder) throws Exception;
|
|
|
|
protected abstract void deleteAllMessagesInternal() throws Exception;
|
|
|
|
protected abstract void expungeInternal() throws Exception;
|
|
|
|
protected abstract SimpleStoredMessage getMessageInternal(long uid) throws Exception;
|
|
|
|
protected abstract List<SimpleStoredMessage> getMessagesInternal();
|
|
|
|
protected abstract List<SimpleStoredMessage> getMessagesInternal(MsgRangeFilter msgRangeFilter);
|
|
|
|
protected abstract List<SimpleStoredMessage> getNonDeletedMessagesInternal();
|
|
|
|
protected abstract void replaceFlagsInternal(Flags flags, long uid, FolderListener silentListener, boolean addUid) throws Exception;
|
|
|
|
protected abstract void setFlagsInternal(Flags flags, boolean value, long uid, FolderListener silentListener, boolean addUid) throws Exception;
|
|
|
|
protected abstract class CommandCallback<T>
|
|
{
|
|
public abstract T command() throws Throwable;
|
|
|
|
public T runFeedback() throws FolderException
|
|
{
|
|
return this.runFeedback(false);
|
|
}
|
|
|
|
public T runFeedback(boolean readOnly) throws FolderException
|
|
{
|
|
try
|
|
{
|
|
RetryingTransactionHelper txHelper = serviceRegistry.getTransactionService().getRetryingTransactionHelper();
|
|
txHelper.setMaxRetries(MAX_RETRIES);
|
|
txHelper.setReadOnly(readOnly);
|
|
T result = txHelper.doInTransaction(
|
|
new RetryingTransactionCallback<T>()
|
|
{
|
|
public T execute() throws Throwable
|
|
{
|
|
return command();
|
|
}
|
|
}, readOnly);
|
|
return result;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Throwable cause = e.getCause();
|
|
String message;
|
|
if (cause != null)
|
|
{
|
|
message = cause.getMessage();
|
|
}
|
|
else
|
|
{
|
|
message = e.getMessage();
|
|
}
|
|
throw new FolderException(message);
|
|
}
|
|
}
|
|
|
|
public T run()
|
|
{
|
|
return this.run(false);
|
|
}
|
|
|
|
public T run(boolean readOnly)
|
|
{
|
|
RetryingTransactionHelper txHelper = serviceRegistry.getTransactionService().getRetryingTransactionHelper();
|
|
txHelper.setMaxRetries(MAX_RETRIES);
|
|
txHelper.setReadOnly(readOnly);
|
|
T result = txHelper.doInTransaction(
|
|
new RetryingTransactionCallback<T>()
|
|
{
|
|
public T execute() throws Throwable
|
|
{
|
|
return command();
|
|
}
|
|
}, readOnly);
|
|
return result;
|
|
}
|
|
}
|
|
}
|