| /**************************************************************** |
| * 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.jmap.methods; |
| |
| import java.util.Collection; |
| import java.util.Comparator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.function.Function; |
| import java.util.stream.Stream; |
| |
| import javax.inject.Inject; |
| import javax.inject.Named; |
| |
| import org.apache.james.jmap.model.ClientId; |
| import org.apache.james.jmap.model.Filter; |
| import org.apache.james.jmap.model.FilterCondition; |
| import org.apache.james.jmap.model.GetMessageListRequest; |
| import org.apache.james.jmap.model.GetMessageListResponse; |
| import org.apache.james.jmap.model.GetMessagesRequest; |
| import org.apache.james.jmap.model.MessageId; |
| import org.apache.james.jmap.utils.FilterToSearchQuery; |
| import org.apache.james.jmap.utils.MailboxUtils; |
| import org.apache.james.jmap.utils.SortToComparatorConvertor; |
| import org.apache.james.mailbox.MailboxManager; |
| import org.apache.james.mailbox.MailboxSession; |
| import org.apache.james.mailbox.MessageManager; |
| import org.apache.james.mailbox.exception.MailboxException; |
| import org.apache.james.mailbox.exception.MailboxNotFoundException; |
| import org.apache.james.mailbox.model.FetchGroupImpl; |
| import org.apache.james.mailbox.model.MailboxId; |
| import org.apache.james.mailbox.model.MailboxId.Factory; |
| import org.apache.james.mailbox.model.MailboxPath; |
| import org.apache.james.mailbox.model.MessageRange; |
| import org.apache.james.mailbox.model.MessageResult; |
| import org.apache.james.mailbox.model.MultimailboxesSearchQuery; |
| import org.apache.james.mailbox.model.SearchQuery; |
| import org.apache.james.mailbox.store.search.MessageSearchIndex; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.github.fge.lambdas.Throwing; |
| import com.github.steveash.guavate.Guavate; |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Preconditions; |
| import com.google.common.base.Throwables; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.LinkedHashMultimap; |
| import com.google.common.collect.Multimap; |
| |
| public class GetMessageListMethod implements Method { |
| |
| public static final String MAXIMUM_LIMIT = "maximumLimit"; |
| public static final int DEFAULT_MAXIMUM_LIMIT = 256; |
| |
| private static final Logger LOGGER = LoggerFactory.getLogger(GetMailboxesMethod.class); |
| private static final Method.Request.Name METHOD_NAME = Method.Request.name("getMessageList"); |
| private static final Method.Response.Name RESPONSE_NAME = Method.Response.name("messageList"); |
| |
| private final MailboxManager mailboxManager; |
| private final MessageSearchIndex messageSearchIndex; |
| private final int maximumLimit; |
| private final GetMessagesMethod getMessagesMethod; |
| private final MailboxUtils mailboxUtils; |
| private final Factory mailboxIdFactory; |
| |
| @Inject |
| @VisibleForTesting public GetMessageListMethod(MailboxManager mailboxManager, MessageSearchIndex messageSearchIndex, |
| @Named(MAXIMUM_LIMIT) int maximumLimit, GetMessagesMethod getMessagesMethod, MailboxUtils mailboxUtils, MailboxId.Factory mailboxIdFactory) { |
| |
| this.mailboxManager = mailboxManager; |
| this.messageSearchIndex = messageSearchIndex; |
| this.maximumLimit = maximumLimit; |
| this.getMessagesMethod = getMessagesMethod; |
| this.mailboxUtils = mailboxUtils; |
| this.mailboxIdFactory = mailboxIdFactory; |
| } |
| |
| @Override |
| public Method.Request.Name requestHandled() { |
| return METHOD_NAME; |
| } |
| |
| @Override |
| public Class<? extends JmapRequest> requestType() { |
| return GetMessageListRequest.class; |
| } |
| |
| @Override |
| public Stream<JmapResponse> process(JmapRequest request, ClientId clientId, MailboxSession mailboxSession) { |
| Preconditions.checkArgument(request instanceof GetMessageListRequest); |
| GetMessageListRequest messageListRequest = (GetMessageListRequest) request; |
| GetMessageListResponse messageListResponse = getMessageListResponse(messageListRequest, clientId, mailboxSession); |
| |
| Stream<JmapResponse> jmapResponse = Stream.of(JmapResponse.builder().clientId(clientId) |
| .response(messageListResponse) |
| .responseName(RESPONSE_NAME) |
| .build()); |
| return Stream.<JmapResponse> concat(jmapResponse, |
| processGetMessages(messageListRequest, messageListResponse, clientId, mailboxSession)); |
| } |
| |
| private GetMessageListResponse getMessageListResponse(GetMessageListRequest messageListRequest, ClientId clientId, MailboxSession mailboxSession) { |
| GetMessageListResponse.Builder builder = GetMessageListResponse.builder(); |
| try { |
| MultimailboxesSearchQuery searchQuery = convertToSearchQuery(messageListRequest); |
| Map<MailboxId, Collection<Long>> searchResults = messageSearchIndex.search(mailboxSession, searchQuery); |
| |
| aggregateResults(mailboxSession, searchResults).entries().stream() |
| .sorted(comparatorFor(messageListRequest)) |
| .map(entry -> new MessageId(mailboxSession.getUser(), entry.getKey(), entry.getValue().getUid())) |
| .skip(messageListRequest.getPosition()) |
| .limit(limit(messageListRequest.getLimit())) |
| .forEach(builder::messageId); |
| |
| return builder.build(); |
| } catch (MailboxException e) { |
| throw Throwables.propagate(e); |
| } |
| } |
| |
| private Multimap<MailboxPath, MessageResult> aggregateResults(MailboxSession mailboxSession, Map<MailboxId, Collection<Long>> searchResults) { |
| Multimap<MailboxPath, MessageResult> messages = LinkedHashMultimap.create(); |
| for (Map.Entry<MailboxId, Collection<Long>> mailboxResults: searchResults.entrySet()) { |
| try { |
| aggregate(mailboxSession, messages, mailboxResults); |
| } catch (MailboxNotFoundException e) { |
| LOGGER.error("Error retrieving mailbox", e); |
| throw Throwables.propagate(e); |
| } |
| } |
| return messages; |
| } |
| |
| private void aggregate(MailboxSession mailboxSession, Multimap<MailboxPath, MessageResult> aggregation, Map.Entry<MailboxId, Collection<Long>> mailboxResults) throws MailboxNotFoundException { |
| MailboxPath mailboxPath = mailboxUtils.mailboxPathFromMailboxId(mailboxResults.getKey().serialize(), mailboxSession) |
| .orElseThrow(() -> new MailboxNotFoundException(mailboxResults.getKey().serialize())); |
| MessageManager messageManager = getMessageManager(mailboxPath, mailboxSession) |
| .orElseThrow(() -> new MailboxNotFoundException(mailboxPath)); |
| List<MessageResult> mailboxMessages = MessageRange.toRanges(mailboxResults.getValue()).stream() |
| .map(Throwing.function(range -> messageManager.getMessages(range, FetchGroupImpl.MINIMAL, mailboxSession))) |
| .map(messageIterator -> ImmutableList.copyOf(messageIterator)) |
| .flatMap(List::stream) |
| .collect(Guavate.toImmutableList()); |
| aggregation.putAll(mailboxPath, mailboxMessages); |
| } |
| |
| private MultimailboxesSearchQuery convertToSearchQuery(GetMessageListRequest messageListRequest) { |
| SearchQuery searchQuery = messageListRequest.getFilter() |
| .map(filter -> new FilterToSearchQuery().convert(filter)) |
| .orElse(new SearchQuery()); |
| Set<MailboxId> inMailboxes = buildFilterMailboxesSet(messageListRequest.getFilter(), condition -> condition.getInMailboxes()); |
| Set<MailboxId> notInMailboxes = buildFilterMailboxesSet(messageListRequest.getFilter(), condition -> condition.getNotInMailboxes()); |
| return MultimailboxesSearchQuery |
| .from(searchQuery) |
| .inMailboxes(inMailboxes) |
| .notInMailboxes(notInMailboxes) |
| .build(); |
| } |
| |
| private Set<MailboxId> buildFilterMailboxesSet(Optional<Filter> maybeFilter, Function<FilterCondition, Optional<List<String>>> mailboxListExtractor) { |
| return filterToFilterCondition(maybeFilter) |
| .flatMap(condition -> Guavate.stream(mailboxListExtractor.apply(condition))) |
| .flatMap(List::stream) |
| .map(mailboxIdFactory::fromString) |
| .collect(Guavate.toImmutableSet()); |
| } |
| |
| private Stream<FilterCondition> filterToFilterCondition(Optional<Filter> maybeCondition) { |
| return Guavate.stream(maybeCondition) |
| .flatMap(c -> { |
| if (c instanceof FilterCondition) { |
| return Stream.of((FilterCondition)c); |
| } |
| return Stream.of(); |
| }); |
| } |
| |
| private Stream<JmapResponse> processGetMessages(GetMessageListRequest messageListRequest, GetMessageListResponse messageListResponse, ClientId clientId, MailboxSession mailboxSession) { |
| if (shouldChainToGetMessages(messageListRequest)) { |
| GetMessagesRequest getMessagesRequest = GetMessagesRequest.builder() |
| .ids(messageListResponse.getMessageIds()) |
| .properties(messageListRequest.getFetchMessageProperties()) |
| .build(); |
| return getMessagesMethod.process(getMessagesRequest, clientId, mailboxSession); |
| } |
| return Stream.empty(); |
| } |
| |
| private boolean shouldChainToGetMessages(GetMessageListRequest messageListRequest) { |
| return messageListRequest.isFetchMessages().orElse(false) |
| && !messageListRequest.isFetchThreads().orElse(false); |
| } |
| |
| private long limit(Optional<Integer> limit) { |
| return limit.orElse(maximumLimit); |
| } |
| |
| private Comparator<Map.Entry<MailboxPath, MessageResult>> comparatorFor(GetMessageListRequest messageListRequest) { |
| return SortToComparatorConvertor.comparatorFor(messageListRequest.getSort()); |
| } |
| |
| private Optional<MessageManager> getMessageManager(MailboxPath mailboxPath, MailboxSession mailboxSession) { |
| try { |
| return Optional.of(mailboxManager.getMailbox(mailboxPath, mailboxSession)); |
| } catch (MailboxException e) { |
| LOGGER.warn("Error retrieveing mailbox :" + mailboxPath, e); |
| return Optional.empty(); |
| } |
| } |
| |
| } |