org.apache.james.mailbox.store.StoreMessageManager.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.james.mailbox.store.StoreMessageManager.java

Source

/****************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one   *
 * or more contributor license agreements.  See the NOTICE file *
 * distributed with this work for additional information        *
 * regarding copyright ownership.  The ASF licenses this file   *
 * to you under the Apache License, Version 2.0 (the            *
 * "License"); you may not use this file except in compliance   *
 * with the License.  You may obtain a copy of the License at   *
 *                                                              *
 *   http://www.apache.org/licenses/LICENSE-2.0                 *
 *                                                              *
 * Unless required by applicable law or agreed to in writing,   *
 * software distributed under the License is distributed on an  *
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
 * KIND, either express or implied.  See the License for the    *
 * specific language governing permissions and limitations      *
 * under the License.                                           *
 ****************************************************************/

package org.apache.james.mailbox.store;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.mail.Flags;
import javax.mail.Flags.Flag;
import javax.mail.internet.SharedInputStream;
import javax.mail.util.SharedFileInputStream;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.TeeInputStream;
import org.apache.james.mailbox.MailboxListener;
import org.apache.james.mailbox.MailboxPathLocker;
import org.apache.james.mailbox.MailboxSession;
import org.apache.james.mailbox.MailboxSession.User;
import org.apache.james.mailbox.MessageManager;
import org.apache.james.mailbox.acl.GroupMembershipResolver;
import org.apache.james.mailbox.acl.MailboxACLResolver;
import org.apache.james.mailbox.acl.UnionMailboxACLResolver;
import org.apache.james.mailbox.exception.MailboxException;
import org.apache.james.mailbox.exception.ReadOnlyException;
import org.apache.james.mailbox.exception.UnsupportedRightException;
import org.apache.james.mailbox.model.Attachment;
import org.apache.james.mailbox.model.MailboxACL;
import org.apache.james.mailbox.model.MailboxACL.MailboxACLRights;
import org.apache.james.mailbox.model.MailboxId;
import org.apache.james.mailbox.model.MailboxPath;
import org.apache.james.mailbox.model.MessageAttachment;
import org.apache.james.mailbox.model.MessageMetaData;
import org.apache.james.mailbox.model.MessageRange;
import org.apache.james.mailbox.model.MessageResult.FetchGroup;
import org.apache.james.mailbox.model.MessageResultIterator;
import org.apache.james.mailbox.model.SearchQuery;
import org.apache.james.mailbox.model.SimpleMailboxACL;
import org.apache.james.mailbox.model.UpdatedFlags;
import org.apache.james.mailbox.quota.QuotaManager;
import org.apache.james.mailbox.quota.QuotaRootResolver;
import org.apache.james.mailbox.store.event.MailboxEventDispatcher;
import org.apache.james.mailbox.store.mail.AttachmentMapper;
import org.apache.james.mailbox.store.mail.MessageMapper;
import org.apache.james.mailbox.store.mail.MessageMapper.FetchType;
import org.apache.james.mailbox.store.mail.model.Mailbox;
import org.apache.james.mailbox.store.mail.model.MailboxMessage;
import org.apache.james.mailbox.store.mail.model.impl.MessageParser;
import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage;
import org.apache.james.mailbox.store.quota.QuotaChecker;
import org.apache.james.mailbox.store.search.MessageSearchIndex;
import org.apache.james.mailbox.store.streaming.BodyOffsetInputStream;
import org.apache.james.mailbox.store.streaming.CountingInputStream;
import org.apache.james.mailbox.store.transaction.Mapper;
import org.apache.james.mime4j.MimeException;
import org.apache.james.mime4j.message.DefaultBodyDescriptorBuilder;
import org.apache.james.mime4j.message.HeaderImpl;
import org.apache.james.mime4j.message.MaximalBodyDescriptor;
import org.apache.james.mime4j.stream.EntityState;
import org.apache.james.mime4j.stream.MimeConfig;
import org.apache.james.mime4j.stream.MimeTokenStream;
import org.apache.james.mime4j.stream.RecursionMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableList;

/**
 * Base class for {@link org.apache.james.mailbox.MessageManager}
 * implementations.
 * 
 * This base class take care of dispatching events to the registered
 * {@link MailboxListener} and so help with handling concurrent
 * {@link MailboxSession}'s.
 * 
 * 
 * 
 */
public class StoreMessageManager implements org.apache.james.mailbox.MessageManager {

    /**
     * The minimal Permanent flags the {@link MessageManager} must support. <br>
     * 
     * <strong>Be sure this static instance will never get modifed
     * later!</strong>
     */
    protected final static Flags MINIMAL_PERMANET_FLAGS;
    static {
        MINIMAL_PERMANET_FLAGS = new Flags();
        MINIMAL_PERMANET_FLAGS.add(Flags.Flag.ANSWERED);
        MINIMAL_PERMANET_FLAGS.add(Flags.Flag.DELETED);
        MINIMAL_PERMANET_FLAGS.add(Flags.Flag.DRAFT);
        MINIMAL_PERMANET_FLAGS.add(Flags.Flag.FLAGGED);
        MINIMAL_PERMANET_FLAGS.add(Flags.Flag.SEEN);
    }

    private static final Logger LOG = LoggerFactory.getLogger(StoreMessageManager.class);

    private final Mailbox mailbox;

    private final MailboxEventDispatcher dispatcher;

    private final MailboxSessionMapperFactory mapperFactory;

    private final MessageSearchIndex index;

    private final MailboxACLResolver aclResolver;

    private final GroupMembershipResolver groupMembershipResolver;

    private final QuotaManager quotaManager;

    private final QuotaRootResolver quotaRootResolver;

    private final MailboxPathLocker locker;

    private final MessageParser messageParser;

    private int fetchBatchSize;

    public StoreMessageManager(MailboxSessionMapperFactory mapperFactory, MessageSearchIndex index,
            MailboxEventDispatcher dispatcher, MailboxPathLocker locker, Mailbox mailbox,
            MailboxACLResolver aclResolver, final GroupMembershipResolver groupMembershipResolver,
            QuotaManager quotaManager, QuotaRootResolver quotaRootResolver, MessageParser messageParser)
            throws MailboxException {
        this.mailbox = mailbox;
        this.dispatcher = dispatcher;
        this.mapperFactory = mapperFactory;
        this.index = index;
        this.locker = locker;
        this.aclResolver = aclResolver;
        this.groupMembershipResolver = groupMembershipResolver;
        this.quotaManager = quotaManager;
        this.quotaRootResolver = quotaRootResolver;
        this.messageParser = messageParser;
    }

    public void setFetchBatchSize(int fetchBatchSize) {
        this.fetchBatchSize = fetchBatchSize;
    }

    /**
     * Return the {@link MailboxPathLocker}
     * 
     * @return locker
     */
    protected MailboxPathLocker getLocker() {
        return locker;
    }

    /**
     * Return the {@link MailboxEventDispatcher} for this Mailbox
     * 
     * @return dispatcher
     */
    protected MailboxEventDispatcher getDispatcher() {
        return dispatcher;
    }

    /**
     * Return the underlying {@link Mailbox}
     * 
     * @return mailbox
     * @throws MailboxException
     */

    public Mailbox getMailboxEntity() throws MailboxException {
        return mailbox;
    }

    /**
     * Return {@link Flags} which are permanent stored by the mailbox. By
     * default this are the following flags: <br>
     * {@link Flag#ANSWERED}, {@link Flag#DELETED}, {@link Flag#DRAFT},
     * {@link Flag#FLAGGED}, {@link Flag#RECENT}, {@link Flag#SEEN} <br>
     * 
     * Which in fact does not allow to permanent store user flags / keywords.
     * 
     * If the sub-class does allow to store "any" user flag / keyword it MUST
     * override this method and add {@link Flag#USER} to the list of returned
     * {@link Flags}. If only a special set of user flags / keywords should be
     * allowed just add them directly.
     * 
     * @param session
     * @return flags
     */
    protected Flags getPermanentFlags(MailboxSession session) {

        // Return a new flags instance to make sure the static declared flags
        // instance will not get modified later.
        //
        // See MAILBOX-109
        return new Flags(MINIMAL_PERMANET_FLAGS);
    }

    /**
     * Returns the flags which are shared for the current mailbox, i.e. the
     * flags set up so that changes to those flags are visible to another user.
     * See RFC 4314 section 5.2.
     * 
     * In this implementation, all permanent flags are shared, ergo we simply
     * return {@link #getPermanentFlags(MailboxSession)}
     * 
     * @see UnionMailboxACLResolver#isReadWrite(MailboxACLRights, Flags)
     * 
     * @param session
     * @return
     */
    protected Flags getSharedPermanentFlags(MailboxSession session) {
        return getPermanentFlags(session);
    }

    /**
     * Return true. If an subclass don't want to store mod-sequences in a
     * permanent way just override this and return false
     * 
     * @return true
     */
    public boolean isModSeqPermanent(MailboxSession session) {
        return true;
    }

    /**
     * @see org.apache.james.mailbox.MessageManager#expunge(org.apache.james.mailbox.model.MessageRange,
     *      org.apache.james.mailbox.MailboxSession)
     */
    public Iterator<Long> expunge(MessageRange set, MailboxSession mailboxSession) throws MailboxException {
        if (!isWriteable(mailboxSession)) {
            throw new ReadOnlyException(getMailboxPath(), mailboxSession.getPathDelimiter());
        }
        Map<Long, MessageMetaData> uids = deleteMarkedInMailbox(set, mailboxSession);

        dispatcher.expunged(mailboxSession, uids, getMailboxEntity());
        return uids.keySet().iterator();
    }

    /**
     * @see org.apache.james.mailbox.MessageManager#appendMessage(java.io.InputStream,
     *      java.util.Date, org.apache.james.mailbox.MailboxSession, boolean,
     *      javax.mail.Flags)
     */
    public long appendMessage(InputStream msgIn, Date internalDate, final MailboxSession mailboxSession,
            boolean isRecent, Flags flagsToBeSet) throws MailboxException {

        File file = null;
        TeeInputStream tmpMsgIn = null;
        BodyOffsetInputStream bIn = null;
        FileOutputStream out = null;
        SharedFileInputStream contentIn = null;

        if (!isWriteable(mailboxSession)) {
            throw new ReadOnlyException(getMailboxPath(), mailboxSession.getPathDelimiter());
        }

        try {
            // Create a temporary file and copy the message to it. We will work
            // with the file as
            // source for the InputStream
            file = File.createTempFile("imap", ".msg");
            out = new FileOutputStream(file);

            tmpMsgIn = new TeeInputStream(msgIn, out);

            bIn = new BodyOffsetInputStream(tmpMsgIn);
            // Disable line length... This should be handled by the smtp server
            // component and not the parser itself
            // https://issues.apache.org/jira/browse/IMAP-122
            MimeConfig config = MimeConfig.custom().setMaxLineLen(-1).setMaxHeaderLen(-1).build();

            final MimeTokenStream parser = new MimeTokenStream(config, new DefaultBodyDescriptorBuilder());

            parser.setRecursionMode(RecursionMode.M_NO_RECURSE);
            parser.parse(bIn);
            final HeaderImpl header = new HeaderImpl();

            EntityState next = parser.next();
            while (next != EntityState.T_BODY && next != EntityState.T_END_OF_STREAM
                    && next != EntityState.T_START_MULTIPART) {
                if (next == EntityState.T_FIELD) {
                    header.addField(parser.getField());
                }
                next = parser.next();
            }
            final MaximalBodyDescriptor descriptor = (MaximalBodyDescriptor) parser.getBodyDescriptor();
            final PropertyBuilder propertyBuilder = new PropertyBuilder();
            final String mediaType;
            final String mediaTypeFromHeader = descriptor.getMediaType();
            final String subType;
            if (mediaTypeFromHeader == null) {
                mediaType = "text";
                subType = "plain";
            } else {
                mediaType = mediaTypeFromHeader;
                subType = descriptor.getSubType();
            }
            propertyBuilder.setMediaType(mediaType);
            propertyBuilder.setSubType(subType);
            propertyBuilder.setContentID(descriptor.getContentId());
            propertyBuilder.setContentDescription(descriptor.getContentDescription());
            propertyBuilder.setContentLocation(descriptor.getContentLocation());
            propertyBuilder.setContentMD5(descriptor.getContentMD5Raw());
            propertyBuilder.setContentTransferEncoding(descriptor.getTransferEncoding());
            propertyBuilder.setContentLanguage(descriptor.getContentLanguage());
            propertyBuilder.setContentDispositionType(descriptor.getContentDispositionType());
            propertyBuilder.setContentDispositionParameters(descriptor.getContentDispositionParameters());
            propertyBuilder.setContentTypeParameters(descriptor.getContentTypeParameters());
            // Add missing types
            final String codeset = descriptor.getCharset();
            if (codeset == null) {
                if ("TEXT".equalsIgnoreCase(mediaType)) {
                    propertyBuilder.setCharset("us-ascii");
                }
            } else {
                propertyBuilder.setCharset(codeset);
            }

            final String boundary = descriptor.getBoundary();
            if (boundary != null) {
                propertyBuilder.setBoundary(boundary);
            }
            if ("text".equalsIgnoreCase(mediaType)) {
                final CountingInputStream bodyStream = new CountingInputStream(parser.getInputStream());
                bodyStream.readAll();
                long lines = bodyStream.getLineCount();
                bodyStream.close();
                next = parser.next();
                if (next == EntityState.T_EPILOGUE) {
                    final CountingInputStream epilogueStream = new CountingInputStream(parser.getInputStream());
                    epilogueStream.readAll();
                    lines += epilogueStream.getLineCount();
                    epilogueStream.close();

                }
                propertyBuilder.setTextualLineCount(lines);
            }

            final Flags flags;
            if (flagsToBeSet == null) {
                flags = new Flags();
            } else {
                flags = flagsToBeSet;

                // Check if we need to trim the flags
                trimFlags(flags, mailboxSession);

            }
            if (isRecent) {
                flags.add(Flags.Flag.RECENT);
            }
            if (internalDate == null) {
                internalDate = new Date();
            }
            byte[] discard = new byte[4096];
            while (tmpMsgIn.read(discard) != -1) {
                // consume the rest of the stream so everything get copied to
                // the file now
                // via the TeeInputStream
            }
            int bodyStartOctet = (int) bIn.getBodyStartOffset();
            if (bodyStartOctet == -1) {
                bodyStartOctet = 0;
            }
            contentIn = new SharedFileInputStream(file);
            final int size = (int) file.length();

            final List<MessageAttachment> attachments = extractAttachments(contentIn);
            final MailboxMessage message = createMessage(internalDate, size, bodyStartOctet, contentIn, flags,
                    propertyBuilder, attachments);

            new QuotaChecker(quotaManager, quotaRootResolver, mailbox).tryAddition(1, size);

            return locker.executeWithLock(mailboxSession, getMailboxPath(),
                    new MailboxPathLocker.LockAwareExecution<Long>() {

                        @Override
                        public Long execute() throws MailboxException {
                            MessageMetaData data = appendMessageToStore(message, attachments, mailboxSession);

                            SortedMap<Long, MessageMetaData> uids = new TreeMap<Long, MessageMetaData>();
                            uids.put(data.getUid(), data);
                            dispatcher.added(mailboxSession, uids, getMailboxEntity());
                            return data.getUid();
                        }
                    }, true);

        } catch (IOException e) {
            throw new MailboxException("Unable to parse message", e);
        } catch (MimeException e) {
            throw new MailboxException("Unable to parse message", e);
        } finally {
            IOUtils.closeQuietly(bIn);
            IOUtils.closeQuietly(tmpMsgIn);
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(contentIn);

            // delete the temporary file if one was specified
            if (file != null) {
                if (!file.delete()) {
                    // Don't throw an IOException. The message could be appended
                    // and the temporary file
                    // will be deleted hopefully some day
                }
            }
        }

    }

    private List<MessageAttachment> extractAttachments(SharedFileInputStream contentIn) {
        try {
            return messageParser.retrieveAttachments(contentIn);
        } catch (Exception e) {
            LOG.warn("Error while parsing mail's attachments: " + e.getMessage(), e);
            return ImmutableList.of();
        }
    }

    /**
     * Create a new {@link MailboxMessage} for the given data
     * 
     * @param internalDate
     * @param size
     * @param bodyStartOctet
     * @param content
     * @param flags
     * @param attachments 
     * @return membership
     * @throws MailboxException
     */
    protected MailboxMessage createMessage(Date internalDate, int size, int bodyStartOctet,
            SharedInputStream content, Flags flags, PropertyBuilder propertyBuilder,
            List<MessageAttachment> attachments) throws MailboxException {
        return new SimpleMailboxMessage(internalDate, size, bodyStartOctet, content, flags, propertyBuilder,
                getMailboxEntity().getMailboxId(), attachments);
    }

    /**
     * This mailbox is writable
     * 
     * @throws MailboxException
     */
    public boolean isWriteable(MailboxSession session) throws MailboxException {
        return aclResolver.isReadWrite(myRights(session), getSharedPermanentFlags(session));
    }

    /**
     * @see MessageManager#getMetaData(boolean, MailboxSession,
     *      org.apache.james.mailbox.MessageManager.MetaData.FetchGroup)
     */
    public MetaData getMetaData(boolean resetRecent, MailboxSession mailboxSession,
            org.apache.james.mailbox.MessageManager.MetaData.FetchGroup fetchGroup) throws MailboxException {

        final List<Long> recent;
        final Flags permanentFlags = getPermanentFlags(mailboxSession);
        final long uidValidity = getMailboxEntity().getUidValidity();
        final long uidNext = mapperFactory.getMessageMapper(mailboxSession).getLastUid(mailbox) + 1;
        final long highestModSeq = mapperFactory.getMessageMapper(mailboxSession).getHighestModSeq(mailbox);
        final long messageCount;
        final long unseenCount;
        final Long firstUnseen;
        switch (fetchGroup) {
        case UNSEEN_COUNT:
            unseenCount = countUnseenMessagesInMailbox(mailboxSession);
            messageCount = getMessageCount(mailboxSession);
            firstUnseen = null;
            recent = recent(resetRecent, mailboxSession);

            break;
        case FIRST_UNSEEN:
            firstUnseen = findFirstUnseenMessageUid(mailboxSession);
            messageCount = getMessageCount(mailboxSession);
            unseenCount = 0;
            recent = recent(resetRecent, mailboxSession);

            break;
        case NO_UNSEEN:
            firstUnseen = null;
            unseenCount = 0;
            messageCount = getMessageCount(mailboxSession);
            recent = recent(resetRecent, mailboxSession);

            break;
        default:
            firstUnseen = null;
            unseenCount = 0;
            messageCount = -1;
            // just reset the recent but not include them in the metadata
            if (resetRecent) {
                recent(resetRecent, mailboxSession);
            }
            recent = new ArrayList<Long>();
            break;
        }
        MailboxACL resolvedAcl = getResolvedMailboxACL(mailboxSession);
        return new MailboxMetaData(recent, permanentFlags, uidValidity, uidNext, highestModSeq, messageCount,
                unseenCount, firstUnseen, isWriteable(mailboxSession), isModSeqPermanent(mailboxSession),
                resolvedAcl);
    }

    /**
     * Check if the given {@link Flags} contains {@link Flags} which are not
     * included in the returned {@link Flags} of
     * {@link #getPermanentFlags(MailboxSession)}. If any are found, these are
     * removed from the given {@link Flags} instance. The only exception is the
     * {@link Flag#RECENT} flag.
     * 
     * This flag is never removed!
     * 
     * @param flags
     * @param session
     */
    private void trimFlags(Flags flags, MailboxSession session) {

        Flags permFlags = getPermanentFlags(session);

        Flag[] systemFlags = flags.getSystemFlags();
        for (Flag f : systemFlags) {
            if (f != Flag.RECENT && permFlags.contains(f) == false) {
                flags.remove(f);
            }
        }
        // if the permFlags contains the special USER flag we can skip this as
        // all user flags are allowed
        if (permFlags.contains(Flags.Flag.USER) == false) {
            String[] uFlags = flags.getUserFlags();
            for (String uFlag : uFlags) {
                if (permFlags.contains(uFlag) == false) {
                    flags.remove(uFlag);
                }
            }
        }

    }

    /**
     * @see org.apache.james.mailbox.MessageManager#setFlags(javax.mail.Flags,
     *      boolean, boolean, org.apache.james.mailbox.model.MessageRange,
     *      org.apache.james.mailbox.MailboxSession)
     */
    public Map<Long, Flags> setFlags(final Flags flags, final FlagsUpdateMode flagsUpdateMode,
            final MessageRange set, MailboxSession mailboxSession) throws MailboxException {

        if (!isWriteable(mailboxSession)) {
            throw new ReadOnlyException(getMailboxPath(), mailboxSession.getPathDelimiter());
        }
        final SortedMap<Long, Flags> newFlagsByUid = new TreeMap<Long, Flags>();

        trimFlags(flags, mailboxSession);

        final MessageMapper messageMapper = mapperFactory.getMessageMapper(mailboxSession);

        Iterator<UpdatedFlags> it = messageMapper.execute(new Mapper.Transaction<Iterator<UpdatedFlags>>() {

            public Iterator<UpdatedFlags> run() throws MailboxException {
                return messageMapper.updateFlags(getMailboxEntity(),
                        new FlagsUpdateCalculator(flags, flagsUpdateMode), set);
            }
        });

        final SortedMap<Long, UpdatedFlags> uFlags = new TreeMap<Long, UpdatedFlags>();

        while (it.hasNext()) {
            UpdatedFlags flag = it.next();
            newFlagsByUid.put(flag.getUid(), flag.getNewFlags());
            uFlags.put(flag.getUid(), flag);
        }

        dispatcher.flagsUpdated(mailboxSession, new ArrayList<Long>(uFlags.keySet()), getMailboxEntity(),
                new ArrayList<UpdatedFlags>(uFlags.values()));

        return newFlagsByUid;
    }

    /**
     * Copy the {@link MessageRange} to the {@link StoreMessageManager}
     * 
     * @param set
     * @param toMailbox
     * @param session
     * @throws MailboxException
     */
    public List<MessageRange> copyTo(final MessageRange set, final StoreMessageManager toMailbox,
            final MailboxSession session) throws MailboxException {
        if (!toMailbox.isWriteable(session)) {
            throw new ReadOnlyException(new StoreMailboxPath(toMailbox.getMailboxEntity()),
                    session.getPathDelimiter());
        }

        return locker.executeWithLock(session, new StoreMailboxPath(toMailbox.getMailboxEntity()),
                new MailboxPathLocker.LockAwareExecution<List<MessageRange>>() {

                    @Override
                    public List<MessageRange> execute() throws MailboxException {
                        SortedMap<Long, MessageMetaData> copiedUids = copy(set, toMailbox, session);
                        dispatcher.added(session, copiedUids, toMailbox.getMailboxEntity());
                        return MessageRange.toRanges(new ArrayList<Long>(copiedUids.keySet()));
                    }
                }, true);
    }

    /**
     * Move the {@link MessageRange} to the {@link StoreMessageManager}
     * 
     * @param set
     * @param toMailbox
     * @param session
     * @throws MailboxException
     */
    public List<MessageRange> moveTo(final MessageRange set, final StoreMessageManager toMailbox,
            final MailboxSession session) throws MailboxException {
        if (!isWriteable(session)) {
            throw new ReadOnlyException(getMailboxPath(), session.getPathDelimiter());
        }
        if (!toMailbox.isWriteable(session)) {
            throw new ReadOnlyException(new StoreMailboxPath(toMailbox.getMailboxEntity()),
                    session.getPathDelimiter());
        }

        //TODO lock the from mailbox too, in a non-deadlocking manner - how?
        return locker.executeWithLock(session, new StoreMailboxPath(toMailbox.getMailboxEntity()),
                new MailboxPathLocker.LockAwareExecution<List<MessageRange>>() {

                    @Override
                    public List<MessageRange> execute() throws MailboxException {
                        SortedMap<Long, MessageMetaData> movedUids = move(set, toMailbox, session);
                        dispatcher.added(session, movedUids, toMailbox.getMailboxEntity());
                        return MessageRange.toRanges(new ArrayList<Long>(movedUids.keySet()));
                    }
                }, true);
    }

    protected MessageMetaData appendMessageToStore(final MailboxMessage message,
            final List<MessageAttachment> messageAttachments, MailboxSession session) throws MailboxException {
        final MessageMapper messageMapper = mapperFactory.getMessageMapper(session);
        final AttachmentMapper attachmentMapper = mapperFactory.getAttachmentMapper(session);
        return mapperFactory.getMessageMapper(session).execute(new Mapper.Transaction<MessageMetaData>() {

            public MessageMetaData run() throws MailboxException {
                ImmutableList.Builder<Attachment> attachments = ImmutableList.builder();
                for (MessageAttachment attachment : messageAttachments) {
                    attachments.add(attachment.getAttachment());
                }
                attachmentMapper.storeAttachments(attachments.build());
                return messageMapper.add(getMailboxEntity(), message);
            }

        });
    }

    /**
     * @see org.apache.james.mailbox.MessageManager#getMessageCount(org.apache.james.mailbox.MailboxSession)
     */
    public long getMessageCount(MailboxSession mailboxSession) throws MailboxException {
        return mapperFactory.getMessageMapper(mailboxSession).countMessagesInMailbox(getMailboxEntity());
    }

    /**
     * @see org.apache.james.mailbox.MessageManager#getMessages(org.apache.james.mailbox.model.MessageRange,
     *      org.apache.james.mailbox.model.MessageResult.FetchGroup,
     *      org.apache.james.mailbox.MailboxSession)
     */
    public MessageResultIterator getMessages(MessageRange set, FetchGroup fetchGroup, MailboxSession mailboxSession)
            throws MailboxException {
        final MessageMapper messageMapper = mapperFactory.getMessageMapper(mailboxSession);
        return new StoreMessageResultIterator(messageMapper, mailbox, set, fetchBatchSize, fetchGroup);
    }

    /**
     * Return a List which holds all uids of recent messages and optional reset
     * the recent flag on the messages for the uids
     * 
     * @param reset
     * @param mailboxSession
     * @return list
     * @throws MailboxException
     */
    protected List<Long> recent(final boolean reset, MailboxSession mailboxSession) throws MailboxException {
        if (reset) {
            if (!isWriteable(mailboxSession)) {
                throw new ReadOnlyException(getMailboxPath(), mailboxSession.getPathDelimiter());
            }
        }
        final MessageMapper messageMapper = mapperFactory.getMessageMapper(mailboxSession);

        return messageMapper.execute(new Mapper.Transaction<List<Long>>() {

            public List<Long> run() throws MailboxException {
                final List<Long> members = messageMapper.findRecentMessageUidsInMailbox(getMailboxEntity());

                // Convert to MessageRanges so we may be able to optimize the
                // flag update
                List<MessageRange> ranges = MessageRange.toRanges(members);
                for (MessageRange range : ranges) {
                    if (reset) {
                        // only call save if we need to
                        messageMapper.updateFlags(getMailboxEntity(),
                                new FlagsUpdateCalculator(new Flags(Flag.RECENT), FlagsUpdateMode.REMOVE), range);
                    }
                }
                return members;
            }

        });

    }

    protected Map<Long, MessageMetaData> deleteMarkedInMailbox(final MessageRange range, MailboxSession session)
            throws MailboxException {

        final MessageMapper messageMapper = mapperFactory.getMessageMapper(session);

        return messageMapper.execute(new Mapper.Transaction<Map<Long, MessageMetaData>>() {

            public Map<Long, MessageMetaData> run() throws MailboxException {
                return messageMapper.expungeMarkedForDeletionInMailbox(getMailboxEntity(), range);
            }

        });
    }

    /**
     * @see org.apache.james.mailbox.MessageManager#search(org.apache.james.mailbox.model.SearchQuery,
     *      org.apache.james.mailbox.MailboxSession)
     */
    public Iterator<Long> search(SearchQuery query, MailboxSession mailboxSession) throws MailboxException {
        return index.search(mailboxSession, getMailboxEntity(), query);
    }

    private Iterator<MessageMetaData> copy(Iterator<MailboxMessage> originalRows, MailboxSession session)
            throws MailboxException {
        final List<MessageMetaData> copiedRows = new ArrayList<MessageMetaData>();
        final MessageMapper messageMapper = mapperFactory.getMessageMapper(session);
        QuotaChecker quotaChecker = new QuotaChecker(quotaManager, quotaRootResolver, mailbox);

        while (originalRows.hasNext()) {
            final MailboxMessage originalMessage = originalRows.next();
            quotaChecker.tryAddition(1, originalMessage.getFullContentOctets());
            MessageMetaData data = messageMapper.execute(new Mapper.Transaction<MessageMetaData>() {
                public MessageMetaData run() throws MailboxException {
                    return messageMapper.copy(getMailboxEntity(), originalMessage);

                }

            });
            copiedRows.add(data);
        }
        return copiedRows.iterator();
    }

    private MoveResult move(Iterator<MailboxMessage> originalRows, MailboxSession session) throws MailboxException {
        final List<MessageMetaData> movedRows = new ArrayList<MessageMetaData>();
        final List<MessageMetaData> originalRowsCopy = new ArrayList<MessageMetaData>();
        final MessageMapper messageMapper = mapperFactory.getMessageMapper(session);

        while (originalRows.hasNext()) {
            final MailboxMessage originalMessage = originalRows.next();
            originalRowsCopy.add(new SimpleMessageMetaData(originalMessage));
            MessageMetaData data = messageMapper.execute(new Mapper.Transaction<MessageMetaData>() {
                public MessageMetaData run() throws MailboxException {
                    return messageMapper.move(getMailboxEntity(), originalMessage);
                }

            });
            movedRows.add(data);
        }
        return new MoveResult(movedRows.iterator(), originalRowsCopy.iterator());
    }

    private SortedMap<Long, MessageMetaData> copy(MessageRange set, StoreMessageManager to, MailboxSession session)
            throws MailboxException {
        Iterator<MailboxMessage> originalRows = retrieveOriginalRows(set, session);
        return collectMetadata(to.copy(originalRows, session));
    }

    private SortedMap<Long, MessageMetaData> move(MessageRange set, StoreMessageManager to, MailboxSession session)
            throws MailboxException {
        Iterator<MailboxMessage> originalRows = retrieveOriginalRows(set, session);
        MoveResult moveResult = to.move(originalRows, session);
        dispatcher.expunged(session, collectMetadata(moveResult.getOriginalMessages()), getMailboxEntity());
        return collectMetadata(moveResult.getMovedMessages());
    }

    private Iterator<MailboxMessage> retrieveOriginalRows(MessageRange set, MailboxSession session)
            throws MailboxException {
        MessageMapper messageMapper = mapperFactory.getMessageMapper(session);
        return messageMapper.findInMailbox(mailbox, set, FetchType.Full, -1);
    }

    private SortedMap<Long, MessageMetaData> collectMetadata(Iterator<MessageMetaData> ids) {
        final SortedMap<Long, MessageMetaData> copiedMessages = new TreeMap<Long, MessageMetaData>();
        while (ids.hasNext()) {
            MessageMetaData data = ids.next();
            copiedMessages.put(data.getUid(), data);
        }
        return copiedMessages;
    }

    /**
     * Return the count of unseen messages
     * 
     * @param session
     * @return count of unseen messages
     * @throws MailboxException
     */
    protected long countUnseenMessagesInMailbox(MailboxSession session) throws MailboxException {
        MessageMapper messageMapper = mapperFactory.getMessageMapper(session);
        return messageMapper.countUnseenMessagesInMailbox(getMailboxEntity());
    }

    /**
     * Return the uid of the first unseen message or null of none is found
     * 
     * @param session
     * @return uid
     * @throws MailboxException
     */
    protected Long findFirstUnseenMessageUid(MailboxSession session) throws MailboxException {
        MessageMapper messageMapper = mapperFactory.getMessageMapper(session);
        return messageMapper.findFirstUnseenMessageUid(getMailboxEntity());
    }

    private MailboxACLRights myRights(MailboxSession session) throws MailboxException {
        User user = session.getUser();
        if (user != null) {
            return aclResolver.resolveRights(user.getUserName(), groupMembershipResolver, mailbox.getACL(),
                    mailbox.getUser(), new GroupFolderResolver(session).isGroupFolder(mailbox));
        } else {
            return SimpleMailboxACL.NO_RIGHTS;
        }
    }

    /**
     * Applies the global ACL (if there are any) to the mailbox ACL.
     * 
     * @param mailboxSession
     * @return the ACL of the present mailbox merged with the global ACL (if
     *         there are any).
     * @throws UnsupportedRightException
     */
    protected MailboxACL getResolvedMailboxACL(MailboxSession mailboxSession) throws UnsupportedRightException {
        return aclResolver.applyGlobalACL(mailbox.getACL(),
                new GroupFolderResolver(mailboxSession).isGroupFolder(mailbox));
    }

    @Override
    public MailboxId getId() {
        return mailbox.getMailboxId();
    }

    @Override
    public MailboxPath getMailboxPath() throws MailboxException {
        return new StoreMailboxPath(getMailboxEntity());
    }
}