org.apache.james.mailbox.store.search.SimpleMessageSearchIndex.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.james.mailbox.store.search.SimpleMessageSearchIndex.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.search;

import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.inject.Inject;

import org.apache.james.mailbox.MailboxManager.SearchCapabilities;
import org.apache.james.mailbox.MailboxSession;
import org.apache.james.mailbox.exception.MailboxException;
import org.apache.james.mailbox.model.MailboxId;
import org.apache.james.mailbox.model.MailboxPath;
import org.apache.james.mailbox.model.MessageRange;
import org.apache.james.mailbox.model.MultimailboxesSearchQuery;
import org.apache.james.mailbox.model.SearchQuery;
import org.apache.james.mailbox.model.SearchQuery.ConjunctionCriterion;
import org.apache.james.mailbox.model.SearchQuery.Criterion;
import org.apache.james.mailbox.model.SearchQuery.NumericRange;
import org.apache.james.mailbox.model.SearchQuery.UidCriterion;
import org.apache.james.mailbox.store.mail.MailboxMapperFactory;
import org.apache.james.mailbox.store.mail.MessageMapper;
import org.apache.james.mailbox.store.mail.MessageMapper.FetchType;
import org.apache.james.mailbox.store.mail.MessageMapperFactory;
import org.apache.james.mailbox.store.mail.model.Mailbox;
import org.apache.james.mailbox.store.mail.model.MailboxMessage;

import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableMultimap.Builder;
import com.google.common.collect.Multimap;

/**
 * {@link MessageSearchIndex} which just fetch {@link MailboxMessage}'s from the {@link MessageMapper} and use {@link MessageSearcher}
 * to match them against the {@link SearchQuery}.
 * 
 * This works with every implementation but is SLOW.
 * 
 *
 */
public class SimpleMessageSearchIndex implements MessageSearchIndex {
    private static final String WILDCARD = "%";

    private final MessageMapperFactory messageMapperFactory;
    private final MailboxMapperFactory mailboxMapperFactory;

    @Inject
    public SimpleMessageSearchIndex(MessageMapperFactory messageMapperFactory,
            MailboxMapperFactory mailboxMapperFactory) {
        this.messageMapperFactory = messageMapperFactory;
        this.mailboxMapperFactory = mailboxMapperFactory;
    }

    @Override
    public EnumSet<SearchCapabilities> getSupportedCapabilities() {
        return EnumSet.of(SearchCapabilities.MultimailboxSearch, SearchCapabilities.Text);
    }

    /**
     * Walks down the query tree's conjunctions to find a UidCriterion
     * @param crits - list of Criterion to search from
     * @return
     *      first UidCriterion found
     *      null - if not found
     */
    private static UidCriterion findConjugatedUidCriterion(List<Criterion> crits) {
        for (Criterion crit : crits) {
            if (crit instanceof UidCriterion) {
                return (UidCriterion) crit;
            } else if (crit instanceof ConjunctionCriterion) {
                return findConjugatedUidCriterion(((ConjunctionCriterion) crit).getCriteria());
            }
        }
        return null;
    }

    @Override
    public Iterator<Long> search(MailboxSession session, Mailbox mailbox, SearchQuery query)
            throws MailboxException {
        Preconditions.checkArgument(session != null, "'session' is mandatory");
        return searchMultimap(session, ImmutableList.of(mailbox), query).get(mailbox.getMailboxId()).iterator();
    }

    private Multimap<MailboxId, Long> searchMultimap(MailboxSession session, Iterable<Mailbox> mailboxes,
            SearchQuery query) throws MailboxException {
        Builder<MailboxId, Long> multimap = ImmutableMultimap.builder();
        for (Mailbox mailbox : mailboxes) {
            multimap.putAll(searchMultimap(session, mailbox, query));
        }
        return multimap.build();

    }

    private Multimap<MailboxId, Long> searchMultimap(MailboxSession session, Mailbox mailbox, SearchQuery query)
            throws MailboxException {
        if (!isMatchingUser(session, mailbox)) {
            return ImmutableMultimap.of();
        }
        MessageMapper mapper = messageMapperFactory.getMessageMapper(session);

        final SortedSet<MailboxMessage> hitSet = new TreeSet<MailboxMessage>();

        UidCriterion uidCrit = findConjugatedUidCriterion(query.getCriterias());
        if (uidCrit != null) {
            // if there is a conjugated uid range criterion in the query tree we can optimize by
            // only fetching this uid range
            NumericRange[] ranges = uidCrit.getOperator().getRange();
            for (NumericRange r : ranges) {
                Iterator<MailboxMessage> it = mapper.findInMailbox(mailbox,
                        MessageRange.range(r.getLowValue(), r.getHighValue()), FetchType.Metadata, -1);
                while (it.hasNext()) {
                    hitSet.add(it.next());
                }
            }
        } else {
            // we have to fetch all messages
            Iterator<MailboxMessage> messages = mapper.findInMailbox(mailbox, MessageRange.all(), FetchType.Full,
                    -1);
            while (messages.hasNext()) {
                MailboxMessage m = messages.next();
                hitSet.add(m);
            }
        }

        // MessageSearches does the filtering for us
        return ImmutableMultimap.<MailboxId, Long>builder()
                .putAll(mailbox.getMailboxId(),
                        ImmutableList.copyOf(new MessageSearches(hitSet.iterator(), query, session).iterator()))
                .build();
    }

    private boolean isMatchingUser(MailboxSession session, Mailbox mailbox) {
        return mailbox.getUser().equals(session.getUser().getUserName());
    }

    @Override
    public Map<MailboxId, Collection<Long>> search(MailboxSession session,
            final MultimailboxesSearchQuery searchQuery) throws MailboxException {
        List<Mailbox> allUserMailboxes = mailboxMapperFactory.getMailboxMapper(session).findMailboxWithPathLike(
                new MailboxPath(session.getPersonalSpace(), session.getUser().getUserName(), WILDCARD));
        FluentIterable<Mailbox> filteredMailboxes = FluentIterable.from(allUserMailboxes)
                .filter(new Predicate<Mailbox>() {
                    @Override
                    public boolean apply(Mailbox input) {
                        return !searchQuery.getNotInMailboxes().contains(input.getMailboxId());
                    }
                });
        if (searchQuery.getInMailboxes().isEmpty()) {
            return searchMultimap(session, filteredMailboxes, searchQuery.getSearchQuery()).asMap();
        }
        List<Mailbox> queriedMailboxes = new ArrayList<Mailbox>();
        for (Mailbox mailbox : filteredMailboxes) {
            if (searchQuery.getInMailboxes().contains(mailbox.getMailboxId())) {
                queriedMailboxes.add(mailbox);
            }
        }
        return searchMultimap(session, queriedMailboxes, searchQuery.getSearchQuery()).asMap();
    }

}