Package org.apache.james.imap.processor

Source Code of org.apache.james.imap.processor.AbstractSelectionProcessor

/****************************************************************
* 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.imap.processor;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.apache.james.imap.api.ImapCommand;
import org.apache.james.imap.api.ImapConstants;
import org.apache.james.imap.api.ImapMessage;
import org.apache.james.imap.api.ImapSessionUtils;
import org.apache.james.imap.api.display.HumanReadableText;
import org.apache.james.imap.api.message.IdRange;
import org.apache.james.imap.api.message.response.StatusResponse;
import org.apache.james.imap.api.message.response.StatusResponse.ResponseCode;
import org.apache.james.imap.api.message.response.StatusResponseFactory;
import org.apache.james.imap.api.process.ImapProcessor;
import org.apache.james.imap.api.process.ImapSession;
import org.apache.james.imap.api.process.SearchResUtil;
import org.apache.james.imap.api.process.SelectedMailbox;
import org.apache.james.imap.message.request.AbstractMailboxSelectionRequest;
import org.apache.james.imap.message.response.ExistsResponse;
import org.apache.james.imap.message.response.RecentResponse;
import org.apache.james.imap.processor.base.SelectedMailboxImpl;
import org.apache.james.mailbox.MailboxManager;
import org.apache.james.mailbox.MailboxSession;
import org.apache.james.mailbox.MessageManager;
import org.apache.james.mailbox.MessageManager.MetaData;
import org.apache.james.mailbox.MessageManager.MetaData.FetchGroup;
import org.apache.james.mailbox.exception.MailboxException;
import org.apache.james.mailbox.exception.MailboxNotFoundException;
import org.apache.james.mailbox.exception.MessageRangeException;
import org.apache.james.mailbox.model.MailboxPath;
import org.apache.james.mailbox.model.MessageRange;
import org.apache.james.mailbox.model.SearchQuery;

abstract class AbstractSelectionProcessor<M extends AbstractMailboxSelectionRequest> extends AbstractMailboxProcessor<M> implements PermitEnableCapabilityProcessor {

    final StatusResponseFactory statusResponseFactory;

    private final boolean openReadOnly;
    private final static List<String> CAPS = Collections.unmodifiableList(Arrays.asList(ImapConstants.SUPPORTS_QRESYNC, ImapConstants.SUPPORTS_CONDSTORE));

   
    public AbstractSelectionProcessor(final Class<M> acceptableClass, final ImapProcessor next, final MailboxManager mailboxManager, final StatusResponseFactory statusResponseFactory, final boolean openReadOnly) {
        super(acceptableClass, next, mailboxManager, statusResponseFactory);
        this.statusResponseFactory = statusResponseFactory;
        this.openReadOnly = openReadOnly;

    }

    /**
     * @see
     * org.apache.james.imap.processor.AbstractMailboxProcessor#doProcess(org.apache.james.imap.api.message.request.ImapRequest,
     * org.apache.james.imap.api.process.ImapSession, java.lang.String,
     * org.apache.james.imap.api.ImapCommand,
     * org.apache.james.imap.api.process.ImapProcessor.Responder)
     */
    protected void doProcess(AbstractMailboxSelectionRequest request, ImapSession session, String tag, ImapCommand command, Responder responder) {
        final String mailboxName = request.getMailboxName();
        try {
            final MailboxPath fullMailboxPath = buildFullPath(session, mailboxName);

            respond(tag, command, session, fullMailboxPath, request, responder);
          
           
        } catch (MailboxNotFoundException e) {
            session.getLog().debug("Select failed as mailbox does not exist " + mailboxName, e);
            responder.respond(statusResponseFactory.taggedNo(tag, command, HumanReadableText.FAILURE_NO_SUCH_MAILBOX));
        } catch (MailboxException e) {
            session.getLog().info("Select failed for mailbox " + mailboxName , e);
            no(command, tag, responder, HumanReadableText.SELECT);
        }
    }

    private void respond(String tag, ImapCommand command, ImapSession session, MailboxPath fullMailboxPath, AbstractMailboxSelectionRequest request, Responder responder) throws MailboxException, MessageRangeException {
       
        Long lastKnownUidValidity = request.getLastKnownUidValidity();
        Long modSeq = request.getKnownModSeq();
        IdRange[] knownSequences = request.getKnownSequenceSet();
        IdRange[] knownUids = request.getKnownUidSet();

        // Check if a QRESYNC parameter was used and if so if QRESYNC was enabled before.
        // If it was not enabled before its needed to return a BAD response
        //
        // From RFC5162 3.1. QRESYNC Parameter to SELECT/EXAMINE
        //
        //    A server MUST respond with a tagged BAD response if the Quick
        //    Resynchronization parameter to SELECT/EXAMINE command is specified
        //    and the client hasn't issued "ENABLE QRESYNC" in the current
        //    connection.
        if (lastKnownUidValidity != null && !EnableProcessor.getEnabledCapabilities(session).contains(ImapConstants.SUPPORTS_QRESYNC)) {
            taggedBad(command, tag, responder, HumanReadableText.QRESYNC_NOT_ENABLED);
            return;
        }
       
       
       
        final MessageManager.MetaData metaData = selectMailbox(fullMailboxPath, session);
        final SelectedMailbox selected = session.getSelected();
        Long firstUnseen = metaData.getFirstUnseen();
       
        flags(responder, selected);
        exists(responder, metaData);
        recent(responder, selected);
        uidValidity(responder, metaData);
       
       
        // try to write the UNSEEN message to the client and retry if we fail because of concurrent sessions.
        //
        // See IMAP-345
        int retryCount = 0;
        while(unseen(responder, firstUnseen, selected, ImapSessionUtils.getMailboxSession(session)) == false) {
            // if we not was able to get find the unseen within 5 retries we should just not send it
            if (retryCount == 5) {
                if (session.getLog().isInfoEnabled()) {
                    session.getLog().info("Unable to uid for unseen message " + firstUnseen + " in mailbox " + selected.getPath());
                }
                break;
            }
            firstUnseen = selectMailbox(fullMailboxPath, session).getFirstUnseen();
            retryCount++;
           
        }
       
        permanentFlags(responder, metaData, selected);
        highestModSeq(responder, metaData, selected);
        uidNext(responder, metaData);
       
        if (request.getCondstore()) {
           condstoreEnablingCommand(session, responder, metaData, false);
        }
       
        // Now do the QRESYNC processing if necessary
        //
        // If the mailbox does not store the mod-sequence in a permanent way its needed to not process the QRESYNC paramters
        // The same is true if none are given ;)
        if (metaData.isModSeqPermanent() && lastKnownUidValidity != null) {
            if (lastKnownUidValidity == metaData.getUidValidity()) {
               
                final MailboxManager mailboxManager = getMailboxManager();
                final MailboxSession mailboxSession = ImapSessionUtils.getMailboxSession(session);
                final MessageManager mailbox = mailboxManager.getMailbox(fullMailboxPath, mailboxSession);
              
               
                //  If the provided UIDVALIDITY matches that of the selected mailbox, the
                //  server then checks the last known modification sequence.
                //
                //  The server sends the client any pending flag changes (using FETCH
                //  responses that MUST contain UIDs) and expunges those that have
                //  occurred in this mailbox since the provided modification sequence.
                SearchQuery sq = new SearchQuery();
                sq.andCriteria(SearchQuery.modSeqGreaterThan(request.getKnownModSeq()));
               
                IdRange[] uidSet = request.getUidSet();

                if (uidSet == null) {
                    // See mailbox had some messages stored before, if not we don't need to query at all
                    long uidNext = metaData.getUidNext();
                    if ( uidNext != 1) {
                        // Use UIDNEXT -1 as max uid as stated in the QRESYNC RFC
                        uidSet = new IdRange[] {new IdRange(1, uidNext -1)};
                    }
                }
               
                if (uidSet != null) {
                    // RFC5162 3.1. QRESYNC Parameter to SELECT/EXAMINE
                    //
                    // Message sequence match data:
                    //
                    //      A client MAY provide a parenthesized list of a message sequence set
                    //      and the corresponding UID sets.  Both MUST be provided in ascending
                    //      order.  The server uses this data to restrict the range for which it
                    //      provides expunged message information.
                    //
                    //
                    //      Conceptually, the client provides a small sample of sequence numbers
                    //      for which it knows the corresponding UIDs.  The server then compares
                    //      each sequence number and UID pair the client provides with the
                    //      current state of the mailbox.  If a pair matches, then the client
                    //      knows of any expunges up to, and including, the message, and thus
                    //      will not include that range in the VANISHED response, even if the
                    //      "mod-sequence-value" provided by the client is too old for the server
                    //      to have data of when those messages were expunged.
                    //
                    //      Thus, if the Nth message number in the first set in the list is 4,
                    //      and the Nth UID in the second set in the list is 8, and the mailbox's
                    //      fourth message has UID 8, then no UIDs equal to or less than 8 are
                    //      present in the VANISHED response.  If the (N+1)th message number is
                    //      12, and the (N+1)th UID is 24, and the (N+1)th message in the mailbox
                    //      has UID 25, then the lowest UID included in the VANISHED response
                    //      would be 9.
                    if (knownSequences != null && knownUids != null) {
                       
                        // Add all uids which are contained in the knownuidsset to a List so we can later access them via the index
                        List<Long> knownUidsList = new ArrayList<Long>();
                        for (int a = 0; a < knownUids.length; a++) {
                            Iterator<Long> it =  knownUids[a].iterator();
                           
                            while(it.hasNext()) {
                                knownUidsList.add(it.next());
                            }
                        }
                      
                       
                       
                        // loop over the known sequences and check the UID for MSN X again the known UID X
                        long firstUid = 1;
                        int index = 0;
                        for (int a = 0; a < knownSequences.length; a++) {
                            boolean done = false;
                            Iterator<Long> it =  knownSequences[a].iterator();
                            while(it.hasNext()) {
                               
                                // Check if we have uids left to check against
                                if (knownUidsList.size() > index++) {
                                    int msn = it.next().intValue();
                                    long knownUid = knownUidsList.get(index);
                                   
                                    // Check if the uid mathc if not we are done here
                                    if (selected.uid(msn) != knownUid) {
                                        done = true;
                                        break;
                                    } else {
                                        firstUid = knownUid;
                                    }
                                   
                                } else {
                                    done = true;
                                    break;
                                }

                            }
                           
                            // We found the first uid to start with
                            if (done) {
                                firstUid++;
                               
                                // Ok now its time to filter out the IdRanges which we are not interested in
                                List<IdRange> filteredUidSet = new ArrayList<IdRange>();
                                for ( int i = 0; i < uidSet.length; i++) {
                                    IdRange r = uidSet[i];
                                    if (r.getLowVal() < firstUid) {
                                        if (r.getHighVal() > firstUid) {
                                            r.setLowVal(firstUid);
                                            filteredUidSet.add(r);
                                        }
                                    } else {
                                        filteredUidSet.add(r);
                                    }
                                }
                                uidSet = filteredUidSet.toArray(new IdRange[0]);
                               
                                break;
                            }
                        }
                       
                       
                    }
                   
                    List<MessageRange> ranges = new ArrayList<MessageRange>();
                    for (int i = 0; i < uidSet.length; i++) {
                        MessageRange messageSet = messageRange(session.getSelected(), uidSet[i], true);
                        if (messageSet != null) {
                            MessageRange normalizedMessageSet = normalizeMessageRange(session.getSelected(), messageSet);
                            ranges.add(normalizedMessageSet);
                        }
                    }
                   
                   
                   
                    // TODO: Reconsider if we can do something to make the handling better. Maybe at least cache the triplets for the expunged
                    //       while have the server running. This could maybe allow us to not return every expunged message all the time
                    // 
                    //      As we don't store the <<MSN, UID>, <MODSEQ>> in a permanent way its the best to just ignore it here.
                    //
                    //      From RFC5162 4.1. Server Implementations That Don't Store Extra State
                    //
                    //
                    //          Strictly speaking, a server implementation that doesn't remember mod-
                    //          sequences associated with expunged messages can be considered
                    //          compliant with this specification.  Such implementations return all
                    //          expunged messages specified in the UID set of the UID FETCH
                    //          (VANISHED) command every time, without paying attention to the
                    //          specified CHANGEDSINCE mod-sequence.  Such implementations are
                    //          discouraged, as they can end up returning VANISHED responses that are
                    //          bigger than the result of a UID SEARCH command for the same UID set.
                    //
                    //          Clients that use the message sequence match data can reduce the scope
                    //          of this VANISHED response substantially in the typical case where
                    //          expunges have not happened, or happen only toward the end of the
                    //          mailbox.
                    //
                    respondVanished(mailboxSession, mailbox, ranges, modSeq, metaData , responder);
                }
                taggedOk(responder, tag, command, metaData, HumanReadableText.SELECT);
            } else {
               
                taggedOk(responder, tag, command, metaData, HumanReadableText.QRESYNC_UIDVALIDITY_MISMATCH);
            }
        } else {
            taggedOk(responder, tag, command, metaData, HumanReadableText.SELECT);
        }

        // Reset the saved sequence-set after successful SELECT / EXAMINE
        // See RFC 5812 2.1. Normative Description of the SEARCHRES Extension
        SearchResUtil.resetSavedSequenceSet(session);
    }



    private void highestModSeq(Responder responder, MetaData metaData, SelectedMailbox selected) {
        final StatusResponse untaggedOk;
        if (metaData.isModSeqPermanent()) {
            final long highestModSeq = metaData.getHighestModSeq();
            untaggedOk = statusResponseFactory.untaggedOk(HumanReadableText.HIGHEST_MOD_SEQ, ResponseCode.highestModSeq(highestModSeq));
        } else {
            untaggedOk = statusResponseFactory.untaggedOk(HumanReadableText.NO_MOD_SEQ, ResponseCode.noModSeq());
        }
        responder.respond(untaggedOk);       
    }

    private void uidNext(final Responder responder, final MessageManager.MetaData metaData) throws MailboxException {
        final long uid = metaData.getUidNext();
        final StatusResponse untaggedOk = statusResponseFactory.untaggedOk(HumanReadableText.UIDNEXT, ResponseCode.uidNext(uid));
        responder.respond(untaggedOk);
    }

    private void taggedOk(final Responder responder, final String tag, final ImapCommand command, final MetaData metaData, HumanReadableText text) {
        final boolean writeable = metaData.isWriteable() && !openReadOnly;
        final ResponseCode code;
        if (writeable) {
            code = ResponseCode.readWrite();
        } else {
            code = ResponseCode.readOnly();
        }
        final StatusResponse taggedOk = statusResponseFactory.taggedOk(tag, command, text, code);
        responder.respond(taggedOk);
    }

    private boolean unseen(Responder responder, Long firstUnseen, final SelectedMailbox selected, MailboxSession session) throws MailboxException {
        if (firstUnseen != null) {
            final long unseenUid = firstUnseen;
            int msn = selected.msn(unseenUid);

            if (msn == SelectedMailbox.NO_SUCH_MESSAGE) {
                if (session.getLog().isDebugEnabled()) {
                    session.getLog().debug("No message found with uid " + unseenUid + " in mailbox " + selected.getPath().getFullName(session.getPathDelimiter()));
                }
                return false;
            }

            final StatusResponse untaggedOk = statusResponseFactory.untaggedOk(HumanReadableText.unseen(msn), ResponseCode.unseen(msn));
            responder.respond(untaggedOk);
        }
        return true;


    }

    private void uidValidity(Responder responder, MessageManager.MetaData metaData) throws MailboxException {
        final long uidValidity = metaData.getUidValidity();
        final StatusResponse untaggedOk = statusResponseFactory.untaggedOk(HumanReadableText.UID_VALIDITY, ResponseCode.uidValidity(uidValidity));
        responder.respond(untaggedOk);
    }

    private void recent(Responder responder, final SelectedMailbox selected) {
        final int recentCount = selected.recentCount();
        final RecentResponse recentResponse = new RecentResponse(recentCount);
        responder.respond(recentResponse);
    }

    private void exists(Responder responder, MessageManager.MetaData metaData) throws MailboxException {
        final long messageCount = metaData.getMessageCount();
        final ExistsResponse existsResponse = new ExistsResponse(messageCount);
        responder.respond(existsResponse);
    }

    private MessageManager.MetaData selectMailbox(MailboxPath mailboxPath, ImapSession session) throws MailboxException {
        final MailboxManager mailboxManager = getMailboxManager();
        final MailboxSession mailboxSession = ImapSessionUtils.getMailboxSession(session);
        final MessageManager mailbox = mailboxManager.getMailbox(mailboxPath, mailboxSession);

        final SelectedMailbox sessionMailbox;
        final SelectedMailbox currentMailbox = session.getSelected();
        if (currentMailbox == null || !currentMailbox.getPath().equals(mailboxPath)) {
           
            // QRESYNC EXTENSION
            //
            // Response with the CLOSE return-code when the currently selected mailbox is closed implicitly using the SELECT/EXAMINE command on another mailbox
            //
            // See rfc5162 3.7. CLOSED Response Code
            if (currentMailbox != null) {
                getStatusResponseFactory().untaggedOk(HumanReadableText.QRESYNC_CLOSED, ResponseCode.closed());
            }
            session.selected(new SelectedMailboxImpl(getMailboxManager(),  session, mailboxPath));

            sessionMailbox = session.getSelected();
           
        } else {
            // TODO: Check if we need to handle CONDSTORE there too
            sessionMailbox = currentMailbox;
        }
        final MessageManager.MetaData metaData = mailbox.getMetaData(!openReadOnly, mailboxSession, MessageManager.MetaData.FetchGroup.FIRST_UNSEEN);
        addRecent(metaData, sessionMailbox);
        return metaData;
    }


    private void addRecent(final MessageManager.MetaData metaData, SelectedMailbox sessionMailbox) throws MailboxException {
        final List<Long> recentUids = metaData.getRecent();
        for (int i = 0; i < recentUids.size(); i++) {
            long uid = recentUids.get(i);
            sessionMailbox.addRecent(uid);
        }
    }

    /**
     * @see org.apache.james.imap.processor.CapabilityImplementingProcessor#getImplementedCapabilities(org.apache.james.imap.api.process.ImapSession)
     */
    public List<String> getImplementedCapabilities(ImapSession session) {       
        return CAPS;
    }

    /**
     * @see org.apache.james.imap.processor.PermitEnableCapabilityProcessor#getPermitEnableCapabilities(org.apache.james.imap.api.process.ImapSession)
     */
    public List<String> getPermitEnableCapabilities(ImapSession session) {
        return CAPS;
    }

    /**
     * @see org.apache.james.imap.processor.PermitEnableCapabilityProcessor#enable(org.apache.james.imap.api.ImapMessage, org.apache.james.imap.api.process.ImapProcessor.Responder, org.apache.james.imap.api.process.ImapSession, java.lang.String)
     */
    public void enable(ImapMessage message, Responder responder, ImapSession session, String capability) throws EnableException {

        if (EnableProcessor.getEnabledCapabilities(session).contains(capability) == false) {
            SelectedMailbox sm = session.getSelected();
            // Send a HIGHESTMODSEQ response if the there was a select mailbox before and the client just enabled
            // QRESYNC or CONDSTORE
            //
            // See http://www.dovecot.org/list/dovecot/2008-March/029561.html
            if (capability.equalsIgnoreCase(ImapConstants.SUPPORTS_CONDSTORE)|| capability.equalsIgnoreCase(ImapConstants.SUPPORTS_QRESYNC)) {
                try {
                    MetaData metaData  = null;
                    boolean send = false;
                    if (sm != null) {
                        MessageManager mailbox = getSelectedMailbox(session);
                        metaData = mailbox.getMetaData(false, ImapSessionUtils.getMailboxSession(session), FetchGroup.NO_COUNT);
                        send= true;
                    }
                    condstoreEnablingCommand(session, responder, metaData, send);
                } catch (MailboxException e) {
                    throw new EnableException("Unable to enable " + capability, e);
                }
            }
           
        }
    }
   
   
}
TOP

Related Classes of org.apache.james.imap.processor.AbstractSelectionProcessor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.