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}