You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2020/11/16 12:09:29 UTC
[james-project] 06/08: JAMES-3440 Provide a mailbox listener to
populate EmailQueryView
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 4dd396e1579236bd79dd07872ea0688a3f8fdede
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Nov 13 11:57:33 2020 +0700
JAMES-3440 Provide a mailbox listener to populate EmailQueryView
---
.../jmap/event/PopulateEmailQueryViewListener.java | 128 ++++++++++++++++
.../event/PopulateEmailQueryViewListenerTest.java | 166 +++++++++++++++++++++
2 files changed, 294 insertions(+)
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/event/PopulateEmailQueryViewListener.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/event/PopulateEmailQueryViewListener.java
new file mode 100644
index 0000000..0251576
--- /dev/null
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/event/PopulateEmailQueryViewListener.java
@@ -0,0 +1,128 @@
+/****************************************************************
+ * 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.event;
+
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+import org.apache.james.jmap.api.projections.EmailQueryView;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MessageIdManager;
+import org.apache.james.mailbox.SessionProvider;
+import org.apache.james.mailbox.events.Event;
+import org.apache.james.mailbox.events.Group;
+import org.apache.james.mailbox.events.MailboxListener.ReactiveGroupMailboxListener;
+import org.apache.james.mailbox.model.FetchGroup;
+import org.apache.james.mailbox.model.MessageId;
+import org.apache.james.mailbox.model.MessageMetaData;
+import org.apache.james.mime4j.dom.Message;
+import org.apache.james.mime4j.stream.MimeConfig;
+import org.reactivestreams.Publisher;
+
+import com.github.fge.lambdas.Throwing;
+import com.google.common.collect.ImmutableList;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public class PopulateEmailQueryViewListener implements ReactiveGroupMailboxListener {
+ public static class PopulateEmailQueryViewListenerGroup extends Group {
+
+ }
+
+ static final Group GROUP = new PopulateEmailQueryViewListenerGroup();
+ private static final int CONCURRENCY = 5;
+
+ private final MessageIdManager messageIdManager;
+ private final EmailQueryView view;
+ private final SessionProvider sessionProvider;
+
+ @Inject
+ public PopulateEmailQueryViewListener(MessageIdManager messageIdManager, EmailQueryView view, SessionProvider sessionProvider) {
+ this.messageIdManager = messageIdManager;
+ this.view = view;
+ this.sessionProvider = sessionProvider;
+ }
+
+ @Override
+ public Group getDefaultGroup() {
+ return GROUP;
+ }
+
+ @Override
+ public boolean isHandling(Event event) {
+ return event instanceof Added
+ || event instanceof Expunged
+ || event instanceof MailboxDeletion;
+ }
+
+ @Override
+ public Publisher<Void> reactiveEvent(Event event) {
+ if (event instanceof Added) {
+ return handleAdded((Added) event);
+ }
+ if (event instanceof Expunged) {
+ return handleExpunged((Expunged) event);
+ }
+ if (event instanceof MailboxDeletion) {
+ return handleMailboxDeletion((MailboxDeletion) event);
+ }
+ return Mono.empty();
+ }
+
+ public Publisher<Void> handleMailboxDeletion(MailboxDeletion mailboxDeletion) {
+ return view.delete(mailboxDeletion.getMailboxId());
+ }
+
+ public Publisher<Void> handleExpunged(Expunged expunged) {
+ return Flux.fromStream(expunged.getUids().stream()
+ .map(uid -> expunged.getMetaData(uid).getMessageId()))
+ .concatMap(messageId -> view.delete(expunged.getMailboxId(), messageId))
+ .then();
+ }
+
+ public Mono<Void> handleAdded(Added added) {
+ MailboxSession session = sessionProvider.createSystemSession(added.getUsername());
+ return Flux.fromStream(added.getUids().stream()
+ .map(added::getMetaData))
+ .flatMap(messageMetaData -> handleAdded(added, messageMetaData, session), CONCURRENCY)
+ .then();
+ }
+
+ public Mono<Void> handleAdded(Added added, MessageMetaData messageMetaData, MailboxSession session) {
+ MessageId messageId = messageMetaData.getMessageId();
+ ZonedDateTime receivedAt = ZonedDateTime.ofInstant(messageMetaData.getInternalDate().toInstant(), ZoneOffset.UTC);
+
+ Mono<ZonedDateTime> sentAtMono = Flux.from(messageIdManager.getMessagesReactive(ImmutableList.of(messageId), FetchGroup.HEADERS, session))
+ .next()
+ .map(Throwing.function(messageResult -> Message.Builder
+ .of()
+ .use(MimeConfig.PERMISSIVE)
+ .parse(messageResult.getFullContent().getInputStream())
+ .build()))
+ .map(message -> Optional.ofNullable(message.getDate()).orElse(messageMetaData.getInternalDate()))
+ .map(date -> ZonedDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC));
+
+ return sentAtMono.flatMap(sentAt -> view.save(added.getMailboxId(), sentAt, receivedAt, messageId));
+ }
+}
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/event/PopulateEmailQueryViewListenerTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/event/PopulateEmailQueryViewListenerTest.java
new file mode 100644
index 0000000..a8a9293
--- /dev/null
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/event/PopulateEmailQueryViewListenerTest.java
@@ -0,0 +1,166 @@
+/****************************************************************
+ * 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.event;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.time.ZonedDateTime;
+import java.util.Date;
+
+import org.apache.james.core.Username;
+import org.apache.james.jmap.memory.projections.MemoryEmailQueryView;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MailboxSessionUtil;
+import org.apache.james.mailbox.MessageIdManager;
+import org.apache.james.mailbox.MessageManager;
+import org.apache.james.mailbox.events.Group;
+import org.apache.james.mailbox.events.InVMEventBus;
+import org.apache.james.mailbox.events.MemoryEventDeadLetters;
+import org.apache.james.mailbox.events.RetryBackoffConfiguration;
+import org.apache.james.mailbox.events.delivery.InVmEventDelivery;
+import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
+import org.apache.james.mailbox.model.ComposedMessageId;
+import org.apache.james.mailbox.model.MailboxId;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.store.FakeAuthenticator;
+import org.apache.james.mailbox.store.FakeAuthorizator;
+import org.apache.james.mailbox.store.SessionProviderImpl;
+import org.apache.james.mailbox.store.StoreMailboxManager;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.mime4j.dom.Message;
+import org.apache.james.util.streams.Limit;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class PopulateEmailQueryViewListenerTest {
+ private static final Username BOB = Username.of("bob");
+ private static final MailboxPath BOB_INBOX_PATH = MailboxPath.inbox(BOB);
+ private static final MailboxPath BOB_OTHER_BOX_PATH = MailboxPath.forUser(BOB, "otherBox");
+
+ MailboxSession mailboxSession;
+ StoreMailboxManager mailboxManager;
+
+ MessageManager inboxMessageManager;
+ MessageManager otherBoxMessageManager;
+ PopulateEmailQueryViewListener listener;
+ MessageIdManager messageIdManager;
+ private MemoryEmailQueryView view;
+ private MailboxId inboxId;
+
+ @BeforeEach
+ void setup() throws Exception {
+ // Default RetryBackoffConfiguration leads each events to be re-executed for 30s which is too long
+ // Reducing the wait time for the event bus allow a faster test suite execution without harming test correctness
+ RetryBackoffConfiguration backoffConfiguration = RetryBackoffConfiguration.builder()
+ .maxRetries(2)
+ .firstBackoff(Duration.ofMillis(1))
+ .jitterFactor(0.5)
+ .build();
+ InMemoryIntegrationResources resources = InMemoryIntegrationResources.builder()
+ .preProvisionnedFakeAuthenticator()
+ .fakeAuthorizator()
+ .eventBus(new InVMEventBus(new InVmEventDelivery(new RecordingMetricFactory()), backoffConfiguration, new MemoryEventDeadLetters()))
+ .defaultAnnotationLimits()
+ .defaultMessageParser()
+ .scanningSearchIndex()
+ .noPreDeletionHooks()
+ .storeQuotaManager()
+ .build();
+
+ mailboxManager = resources.getMailboxManager();
+ messageIdManager = resources.getMessageIdManager();
+
+ FakeAuthenticator authenticator = new FakeAuthenticator();
+ authenticator.addUser(BOB, "12345");
+ SessionProviderImpl sessionProvider = new SessionProviderImpl(authenticator, FakeAuthorizator.defaultReject());
+
+ view = new MemoryEmailQueryView();
+ listener = new PopulateEmailQueryViewListener(messageIdManager, view, sessionProvider);
+
+ resources.getEventBus().register(listener);
+
+ mailboxSession = MailboxSessionUtil.create(BOB);
+
+ inboxId = mailboxManager.createMailbox(BOB_INBOX_PATH, mailboxSession).get();
+ inboxMessageManager = mailboxManager.getMailbox(inboxId, mailboxSession);
+
+ MailboxId otherBoxId = mailboxManager.createMailbox(BOB_OTHER_BOX_PATH, mailboxSession).get();
+ otherBoxMessageManager = mailboxManager.getMailbox(otherBoxId, mailboxSession);
+ }
+
+
+ @Test
+ void deserializeMailboxAnnotationListenerGroup() throws Exception {
+ assertThat(Group.deserialize("org.apache.james.jmap.event.PopulateEmailQueryViewListener$PopulateEmailQueryViewListenerGroup"))
+ .isEqualTo(new PopulateEmailQueryViewListener.PopulateEmailQueryViewListenerGroup());
+ }
+
+ @Test
+ void appendingAMessageShouldAddItToTheView() throws Exception {
+ ComposedMessageId composedId = inboxMessageManager.appendMessage(
+ MessageManager.AppendCommand.builder()
+ .withInternalDate(Date.from(ZonedDateTime.parse("2014-10-30T15:12:00Z").toInstant()))
+ .build(emptyMessage(Date.from(ZonedDateTime.parse("2014-10-30T14:12:00Z").toInstant()))),
+ mailboxSession).getId();
+
+ assertThat(view.listMailboxContent(inboxId, Limit.limit(12)).collectList().block())
+ .containsOnly(composedId.getMessageId());
+ }
+
+ @Test
+ void deletingMailboxShouldClearTheView() throws Exception {
+ inboxMessageManager.appendMessage(
+ MessageManager.AppendCommand.builder()
+ .withInternalDate(Date.from(ZonedDateTime.parse("2014-10-30T15:12:00Z").toInstant()))
+ .build(emptyMessage(Date.from(ZonedDateTime.parse("2014-10-30T14:12:00Z").toInstant()))),
+ mailboxSession).getId();
+
+ mailboxManager.deleteMailbox(inboxId, mailboxSession);
+
+ assertThat(view.listMailboxContent(inboxId, Limit.limit(12)).collectList().block())
+ .isEmpty();
+ }
+
+ @Test
+ void deletingEmailShouldClearTheView() throws Exception {
+ ComposedMessageId composedMessageId = inboxMessageManager.appendMessage(
+ MessageManager.AppendCommand.builder()
+ .withInternalDate(Date.from(ZonedDateTime.parse("2014-10-30T15:12:00Z").toInstant()))
+ .build(emptyMessage(Date.from(ZonedDateTime.parse("2014-10-30T14:12:00Z").toInstant()))),
+ mailboxSession).getId();
+
+ inboxMessageManager.delete(ImmutableList.of(composedMessageId.getUid()), mailboxSession);
+
+ assertThat(view.listMailboxContent(inboxId, Limit.limit(12)).collectList().block())
+ .isEmpty();
+ }
+
+ private Message emptyMessage(Date sentAt) throws Exception {
+ return Message.Builder.of()
+ .setSubject("Empty message")
+ .setDate(sentAt)
+ .setBody("", StandardCharsets.UTF_8)
+ .build();
+ }
+}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org