001/****************************************************************
002 * Licensed to the Apache Software Foundation (ASF) under one   *
003 * or more contributor license agreements.  See the NOTICE file *
004 * distributed with this work for additional information        *
005 * regarding copyright ownership.  The ASF licenses this file   *
006 * to you under the Apache License, Version 2.0 (the            *
007 * "License"); you may not use this file except in compliance   *
008 * with the License.  You may obtain a copy of the License at   *
009 *                                                              *
010 *   http://www.apache.org/licenses/LICENSE-2.0                 *
011 *                                                              *
012 * Unless required by applicable law or agreed to in writing,   *
013 * software distributed under the License is distributed on an  *
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
015 * KIND, either express or implied.  See the License for the    *
016 * specific language governing permissions and limitations      *
017 * under the License.                                           *
018 ****************************************************************/
019
020package org.apache.james.mailbox.indexer;
021
022import java.util.Collection;
023import java.util.Iterator;
024import java.util.List;
025
026import javax.inject.Inject;
027
028import org.apache.james.mailbox.MailboxManager;
029import org.apache.james.mailbox.MailboxSession;
030import org.apache.james.mailbox.exception.MailboxException;
031import org.apache.james.mailbox.indexer.events.FlagsMessageEvent;
032import org.apache.james.mailbox.indexer.events.ImpactingEventType;
033import org.apache.james.mailbox.indexer.events.ImpactingMessageEvent;
034import org.apache.james.mailbox.indexer.registrations.GlobalRegistration;
035import org.apache.james.mailbox.indexer.registrations.MailboxRegistration;
036import org.apache.james.mailbox.model.MailboxPath;
037import org.apache.james.mailbox.model.MessageRange;
038import org.apache.james.mailbox.store.MailboxSessionMapperFactory;
039import org.apache.james.mailbox.store.mail.MessageMapper;
040import org.apache.james.mailbox.store.mail.model.Mailbox;
041import org.apache.james.mailbox.store.mail.model.MailboxMessage;
042import org.apache.james.mailbox.store.search.ListeningMessageSearchIndex;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045
046import com.google.common.base.Optional;
047import com.google.common.collect.Iterables;
048
049/**
050 * Note about live re-indexation handling :
051 *
052 *  - Data races may arise... If you modify the stored value between the received event check and the index operation,
053 *  you have an inconsistent behavior.
054 *
055 *  This class is more about supporting changes in real time for future indexed values. If you change a flags / delete
056 *  mails for instance, you will see it in the indexed value !
057 *
058 *  Why only care about updates and deletions ? Additions are already handled by the indexer that behaves normaly. We
059 *  should just "adapt" our indexed value to the latest value, if any. The normal indexer will take care of new stuff.
060 */
061public class ReIndexerImpl implements ReIndexer {
062
063    private static final Logger LOGGER = LoggerFactory.getLogger(ReIndexerImpl.class);
064    public static final int NO_LIMIT = 0;
065
066    private final MailboxManager mailboxManager;
067    private final ListeningMessageSearchIndex messageSearchIndex;
068    private final MailboxSessionMapperFactory mailboxSessionMapperFactory;
069
070    @Inject
071    public ReIndexerImpl(MailboxManager mailboxManager,
072                         ListeningMessageSearchIndex messageSearchIndex,
073                         MailboxSessionMapperFactory mailboxSessionMapperFactory) {
074        this.mailboxManager = mailboxManager;
075        this.messageSearchIndex = messageSearchIndex;
076        this.mailboxSessionMapperFactory = mailboxSessionMapperFactory;
077    }
078
079    public void reIndex(MailboxPath path) throws MailboxException {
080        MailboxSession mailboxSession = mailboxManager.createSystemSession(path.getUser(), LOGGER);
081        reIndex(path, mailboxSession);
082    }
083
084
085    public void reIndex() throws MailboxException {
086        MailboxSession mailboxSession = mailboxManager.createSystemSession("re-indexing", LOGGER);
087        LOGGER.info("Starting a full reindex");
088        List<MailboxPath> mailboxPaths = mailboxManager.list(mailboxSession);
089        GlobalRegistration globalRegistration = new GlobalRegistration();
090        mailboxManager.addGlobalListener(globalRegistration, mailboxSession);
091        try {
092            handleFullReindexingIterations(mailboxPaths, globalRegistration);
093        } finally {
094            mailboxManager.removeGlobalListener(globalRegistration, mailboxSession);
095        }
096        LOGGER.info("Full reindex finished");
097    }
098
099    private void reIndex(MailboxPath path, MailboxSession mailboxSession) throws MailboxException {
100        MailboxRegistration mailboxRegistration = new MailboxRegistration(path);
101        LOGGER.info("Intend to reindex {}",path);
102        Mailbox mailbox = mailboxSessionMapperFactory.getMailboxMapper(mailboxSession).findMailboxByPath(path);
103        messageSearchIndex.deleteAll(mailboxSession, mailbox);
104        mailboxManager.addListener(path, mailboxRegistration, mailboxSession);
105        try {
106            handleMailboxIndexingIterations(mailboxSession,
107                mailboxRegistration,
108                mailbox,
109                mailboxSessionMapperFactory.getMessageMapper(mailboxSession)
110                    .findInMailbox(mailbox,
111                        MessageRange.all(),
112                        MessageMapper.FetchType.Full,
113                        NO_LIMIT));
114            LOGGER.info("Finish to reindex " + path);
115        } finally {
116            mailboxManager.removeListener(path, mailboxRegistration, mailboxSession);
117        }
118    }
119
120    private void handleFullReindexingIterations(List<MailboxPath> mailboxPaths, GlobalRegistration globalRegistration) throws MailboxException {
121        for (MailboxPath mailboxPath : mailboxPaths) {
122            Optional<MailboxPath> pathToIndex = globalRegistration.getPathToIndex(mailboxPath);
123            if (pathToIndex.isPresent()) {
124                try {
125                    reIndex(pathToIndex.get());
126                } catch(Throwable e) {
127                    LOGGER.error("Error while proceeding to full reindexing on {}", pathToIndex.get(), e);
128                }
129            }
130        }
131    }
132
133    private void handleMailboxIndexingIterations(MailboxSession mailboxSession, MailboxRegistration mailboxRegistration, Mailbox mailbox, Iterator<MailboxMessage> iterator) throws MailboxException {
134        while (iterator.hasNext()) {
135            MailboxMessage message = iterator.next();
136            ImpactingMessageEvent impactingMessageEvent = findMostRelevant(mailboxRegistration.getImpactingEvents(message.getUid()));
137            if (impactingMessageEvent == null) {
138                messageSearchIndex.add(mailboxSession, mailbox, message);
139            } else if (impactingMessageEvent instanceof FlagsMessageEvent) {
140                message.setFlags(((FlagsMessageEvent) impactingMessageEvent).getFlags());
141                messageSearchIndex.add(mailboxSession, mailbox, message);
142            }
143        }
144    }
145
146    private ImpactingMessageEvent findMostRelevant(Collection<ImpactingMessageEvent> messageEvents) {
147        for (ImpactingMessageEvent impactingMessageEvent : messageEvents) {
148            if (impactingMessageEvent.getType().equals(ImpactingEventType.Deletion)) {
149                return impactingMessageEvent;
150            }
151        }
152        return Iterables.getLast(messageEvents, null);
153    }
154
155}