Package org.apache.james.imap.processor

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

/****************************************************************
* 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.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;

import javax.mail.Flags.Flag;

import org.apache.james.imap.api.ImapCommand;
import org.apache.james.imap.api.ImapConstants;
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.request.DayMonthYear;
import org.apache.james.imap.api.message.request.SearchKey;
import org.apache.james.imap.api.message.request.SearchOperation;
import org.apache.james.imap.api.message.request.SearchResultOption;
import org.apache.james.imap.api.message.response.ImapResponseMessage;
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.SearchRequest;
import org.apache.james.imap.message.response.ESearchResponse;
import org.apache.james.imap.message.response.SearchResponse;
import org.apache.james.mailbox.MailboxException;
import org.apache.james.mailbox.MailboxManager;
import org.apache.james.mailbox.MessageManager;
import org.apache.james.mailbox.MessageRange;
import org.apache.james.mailbox.MessageRangeException;
import org.apache.james.mailbox.SearchQuery;
import org.apache.james.mailbox.SearchQuery.AddressType;
import org.apache.james.mailbox.SearchQuery.Criterion;
import org.apache.james.mailbox.SearchQuery.DateResolution;

public class SearchProcessor extends AbstractMailboxProcessor<SearchRequest> implements CapabilityImplementingProcessor {

    public SearchProcessor(final ImapProcessor next, final MailboxManager mailboxManager, final StatusResponseFactory factory) {
        super(SearchRequest.class, next, mailboxManager, factory);
    }

    /*
     * (non-Javadoc)
     *
     * @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(SearchRequest request, ImapSession session, String tag, ImapCommand command, Responder responder) {
        final SearchOperation operation = request.getSearchOperation();
        final SearchKey searchKey = operation.getSearchKey();
        final boolean useUids = request.isUseUids();
        List<SearchResultOption> resultOptions = operation.getResultOptions();

        try {

            final MessageManager mailbox = getSelectedMailbox(session);

            final SearchQuery query = toQuery(searchKey, session);

            final Collection<Long> results = findIds(useUids, session, mailbox, query);
            final long[] ids = toArray(results);

            final ImapResponseMessage response;
            if (resultOptions == null || resultOptions.isEmpty()) {
                response = new SearchResponse(ids);
            } else {
                IdRange[] idRanges;
                List<Long> idList = new ArrayList<Long>(ids.length);
                for ( int i = 0; i < ids.length; i++) {
                    idList.add(ids[i]);
                }
                List<MessageRange> ranges = MessageRange.toRanges(idList);
                idRanges = new IdRange[ranges.size()];
                for (int i = 0 ; i <ranges.size(); i++) {
                    MessageRange range = ranges.get(i);
                    idRanges[i] = new IdRange(range.getUidFrom(), range.getUidTo());
                }
               
                boolean esearch = false;
                for (int i = 0; i < resultOptions.size(); i++) {
                    if (SearchResultOption.SAVE != resultOptions.get(i)) {
                        esearch = true;
                        break;
                    }
                }
               
                if (esearch) {
                    long min = -1;
                    long max = -1;
                    long count = ids.length;

                    if (ids.length > 0) {
                        min = ids[0];
                        max = ids[ids.length -1];
                    }
                  
                   
                    // Save the sequence-set for later usage. This is part of SEARCHRES
                    if (resultOptions.contains(SearchResultOption.SAVE)) {
                        if (resultOptions.contains(SearchResultOption.ALL) || resultOptions.contains(SearchResultOption.COUNT)) {
                            // if the options contain ALL or COUNT we need to save the complete sequence-set
                            SearchResUtil.saveSequenceSet(session, idRanges);
                        } else {
                            List<IdRange> savedRanges = new ArrayList<IdRange>();
                            if (resultOptions.contains(SearchResultOption.MIN)) {
                                // Store the MIN
                                savedRanges.add(new IdRange(min))
                            }
                            if (resultOptions.contains(SearchResultOption.MAX)) {
                                // Store the MAX
                                savedRanges.add(new IdRange(max));
                            }
                            SearchResUtil.saveSequenceSet(session, savedRanges.toArray(new IdRange[0]));
                        }
                    }
                    response = new ESearchResponse(min, max, count, idRanges, tag, useUids, resultOptions);
                } else {
                    // Just save the returned sequence-set as this is not SEARCHRES + ESEARCH
                    SearchResUtil.saveSequenceSet(session, idRanges);
                    response = new SearchResponse(ids);

                }
            }

            responder.respond(response);

            boolean omitExpunged = (!useUids);
            unsolicitedResponses(session, responder, omitExpunged, useUids);
            okComplete(command, tag, responder);
        } catch (MessageRangeException e) {
            session.getLog().debug("Search failed", e);
            taggedBad(command, tag, responder, HumanReadableText.INVALID_MESSAGESET);
        } catch (MailboxException e) {
            session.getLog().debug("Search failed", e);
            no(command, tag, responder, HumanReadableText.SEARCH_FAILED);
           
            if (resultOptions.contains(SearchResultOption.SAVE)) {
                // Reset the saved sequence-set on a BAD response if the SAVE option was used.
                //
                // See RFC5182 2.1.Normative Description of the SEARCHRES Extension
                SearchResUtil.resetSavedSequenceSet(session);
            }
        }
    }

    private long[] toArray(final Collection<Long> results) {
        final Iterator<Long> it = results.iterator();
        final int length = results.size();
        long[] ids = new long[length];
        for (int i = 0; i < length; i++) {
            ids[i] = ((Long) it.next()).longValue();
        }
        return ids;
    }

    private Collection<Long> findIds(final boolean useUids, final ImapSession session, MessageManager mailbox, final SearchQuery query) throws MailboxException, MessageRangeException {

        final Iterator<Long> it = mailbox.search(query, ImapSessionUtils.getMailboxSession(session));

        final Collection<Long> results = new TreeSet<Long>();

        while (it.hasNext()) {
            final long uid = it.next();
            final Long number;
            if (useUids) {
                number = uid;
            } else {
                final int msn = session.getSelected().msn(uid);
                number = (long) msn;
            }
            if (number == SelectedMailbox.NO_SUCH_MESSAGE == false)
                results.add(number);
        }
        return results;
    }

    private SearchQuery toQuery(final SearchKey key, final ImapSession session) throws MessageRangeException {
        final SearchQuery result = new SearchQuery();
        final SelectedMailbox selected = session.getSelected();
        if (selected != null) {
            result.addRecentMessageUids(selected.getRecent());
        }
        final SearchQuery.Criterion criterion = toCriterion(key, session);
        result.andCriteria(criterion);
        return result;
    }

    private SearchQuery.Criterion toCriterion(final SearchKey key, final ImapSession session) throws MessageRangeException {
        final int type = key.getType();
        final DayMonthYear date = key.getDate();
        switch (type) {
        case SearchKey.TYPE_ALL:
            return SearchQuery.all();
        case SearchKey.TYPE_AND:
            return and(key.getKeys(), session);
        case SearchKey.TYPE_ANSWERED:
            return SearchQuery.flagIsSet(Flag.ANSWERED);
        case SearchKey.TYPE_BCC:
            return SearchQuery.address(AddressType.Bcc, key.getValue());
        case SearchKey.TYPE_BEFORE:
            return SearchQuery.internalDateBefore(date.toDate(), DateResolution.Day);
        case SearchKey.TYPE_BODY:
            return SearchQuery.bodyContains(key.getValue());
        case SearchKey.TYPE_CC:
            return SearchQuery.address(AddressType.Cc, key.getValue());
        case SearchKey.TYPE_DELETED:
            return SearchQuery.flagIsSet(Flag.DELETED);
        case SearchKey.TYPE_DRAFT:
            return SearchQuery.flagIsSet(Flag.DRAFT);
        case SearchKey.TYPE_FLAGGED:
            return SearchQuery.flagIsSet(Flag.FLAGGED);
        case SearchKey.TYPE_FROM:
            return SearchQuery.address(AddressType.From, key.getValue());
        case SearchKey.TYPE_HEADER:
            String value = key.getValue();
            // Check if header exists if the value is empty. See IMAP-311
            if (value == null || value.length() == 0) {
                return SearchQuery.headerExists(key.getName());
            } else {
                return SearchQuery.headerContains(key.getName(), value);
            }
        case SearchKey.TYPE_KEYWORD:
            return SearchQuery.flagIsSet(key.getValue());
        case SearchKey.TYPE_LARGER:
            return SearchQuery.sizeGreaterThan(key.getSize());
        case SearchKey.TYPE_NEW:
            return SearchQuery.and(SearchQuery.flagIsSet(Flag.RECENT), SearchQuery.flagIsUnSet(Flag.SEEN));
        case SearchKey.TYPE_NOT:
            return not(key.getKeys(), session);
        case SearchKey.TYPE_OLD:
            return SearchQuery.flagIsUnSet(Flag.RECENT);
        case SearchKey.TYPE_ON:
            return SearchQuery.internalDateOn(date.toDate(), DateResolution.Day);
        case SearchKey.TYPE_OR:
            return or(key.getKeys(), session);
        case SearchKey.TYPE_RECENT:
            return SearchQuery.flagIsSet(Flag.RECENT);
        case SearchKey.TYPE_SEEN:
            return SearchQuery.flagIsSet(Flag.SEEN);
        case SearchKey.TYPE_SENTBEFORE:
            return SearchQuery.headerDateBefore(ImapConstants.RFC822_DATE, date.toDate(), DateResolution.Day);
        case SearchKey.TYPE_SENTON:
            return SearchQuery.headerDateOn(ImapConstants.RFC822_DATE, date.toDate(), DateResolution.Day);
        case SearchKey.TYPE_SENTSINCE:
            // Include the date which is used as search param. See IMAP-293
            Criterion onCrit = SearchQuery.headerDateOn(ImapConstants.RFC822_DATE, date.toDate(), DateResolution.Day);
            Criterion afterCrit = SearchQuery.headerDateAfter(ImapConstants.RFC822_DATE, date.toDate(), DateResolution.Day);
            return SearchQuery.or(onCrit, afterCrit);
        case SearchKey.TYPE_SEQUENCE_SET:
            return sequence(key.getSequenceNumbers(), session, true);
        case SearchKey.TYPE_SINCE:
            // Include the date which is used as search param. See IMAP-293
            return SearchQuery.or(SearchQuery.internalDateOn(date.toDate(), DateResolution.Day), SearchQuery.internalDateAfter(date.toDate(), DateResolution.Day));
        case SearchKey.TYPE_SMALLER:
            return SearchQuery.sizeLessThan(key.getSize());
        case SearchKey.TYPE_SUBJECT:
            return SearchQuery.headerContains(ImapConstants.RFC822_SUBJECT, key.getValue());
        case SearchKey.TYPE_TEXT:
            return SearchQuery.mailContains(key.getValue());
        case SearchKey.TYPE_TO:
            return SearchQuery.address(AddressType.To, key.getValue());
        case SearchKey.TYPE_UID:
            return sequence(key.getSequenceNumbers(), session, false);
        case SearchKey.TYPE_UNANSWERED:
            return SearchQuery.flagIsUnSet(Flag.ANSWERED);
        case SearchKey.TYPE_UNDELETED:
            return SearchQuery.flagIsUnSet(Flag.DELETED);
        case SearchKey.TYPE_UNDRAFT:
            return SearchQuery.flagIsUnSet(Flag.DRAFT);
        case SearchKey.TYPE_UNFLAGGED:
            return SearchQuery.flagIsUnSet(Flag.FLAGGED);
        case SearchKey.TYPE_UNKEYWORD:
            return SearchQuery.flagIsUnSet(key.getValue());
        case SearchKey.TYPE_UNSEEN:
            return SearchQuery.flagIsUnSet(Flag.SEEN);
        case SearchKey.TYPE_OLDER:
            Date withinDate = createWithinDate(key);
            return SearchQuery.or(SearchQuery.internalDateOn(withinDate, DateResolution.Second), SearchQuery.internalDateBefore(withinDate, DateResolution.Second));
        case SearchKey.TYPE_YOUNGER:
            Date withinDate2 = createWithinDate(key);
            return SearchQuery.or(SearchQuery.internalDateOn(withinDate2, DateResolution.Second), SearchQuery.internalDateAfter(withinDate2, DateResolution.Second));
        default:
            session.getLog().warn("Ignoring unknown search key.");
            return SearchQuery.all();
        }
    }
    private Date createWithinDate(SearchKey key) {
        long seconds = key.getSeconds();
        long res = System.currentTimeMillis() - seconds * 1000;
        return new Date(res);
    }

    private Criterion sequence(IdRange[] sequenceNumbers, final ImapSession session, boolean msn) throws MessageRangeException {
        final int length = sequenceNumbers.length;
        final List<SearchQuery.NumericRange> ranges = new ArrayList<SearchQuery.NumericRange>();
        for (int i = 0; i < length; i++) {
            final IdRange range = sequenceNumbers[i];

            // correctly calculate the ranges. See IMAP-292
            final SelectedMailbox selected = session.getSelected();
            MessageRange mRange = messageRange(selected, range, !msn);
            if (mRange != null) {
                ranges.add(new SearchQuery.NumericRange(mRange.getUidFrom(), mRange.getUidTo()));
            }
        }
        Criterion crit = SearchQuery.uid(ranges.toArray(new SearchQuery.NumericRange[0]));
        return crit;
    }

    private Criterion or(List<SearchKey> keys, final ImapSession session) throws MessageRangeException {
        final SearchKey keyOne = keys.get(0);
        final SearchKey keyTwo = keys.get(1);
        final Criterion criterionOne = toCriterion(keyOne, session);
        final Criterion criterionTwo = toCriterion(keyTwo, session);
        final Criterion result = SearchQuery.or(criterionOne, criterionTwo);
        return result;
    }

    private Criterion not(List<SearchKey> keys, final ImapSession session) throws MessageRangeException {
        final SearchKey key = keys.get(0);
        final Criterion criterion = toCriterion(key, session);
        final Criterion result = SearchQuery.not(criterion);
        return result;
    }

    private Criterion and(List<SearchKey> keys, final ImapSession session) throws MessageRangeException {
        final int size = keys.size();
        final List<Criterion> criteria = new ArrayList<Criterion>(size);
        for (final SearchKey key : keys) {
            final Criterion criterion = toCriterion(key, session);
            criteria.add(criterion);
        }
        final Criterion result = SearchQuery.and(criteria);
        return result;
    }

    /*
     * (non-Javadoc)
     * @see org.apache.james.imap.processor.CapabilityImplementingProcessor#getImplementedCapabilities(org.apache.james.imap.api.process.ImapSession)
     */
    public List<String> getImplementedCapabilities(ImapSession session) {
        return Arrays.asList("WITHIN", "ESEARCH", "SEARCHRES");
    }
}
TOP

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

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.