You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by bt...@apache.org on 2019/12/06 02:34:24 UTC

[james-project] 14/21: JAMES-2991 Add a compute message preview listener for JMAP

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 433061a246cf52dc1eb669f8b989f24bc7618d89
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Tue Dec 3 14:12:35 2019 +0700

    JAMES-2991 Add a compute message preview listener for JMAP
---
 .../ComputeMessageFastViewProjectionListener.java  | 111 +++++++++++
 ...mputeMessageFastViewProjectionListenerTest.java | 203 +++++++++++++++++++++
 2 files changed, 314 insertions(+)

diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/event/ComputeMessageFastViewProjectionListener.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/event/ComputeMessageFastViewProjectionListener.java
new file mode 100644
index 0000000..5548448
--- /dev/null
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/event/ComputeMessageFastViewProjectionListener.java
@@ -0,0 +1,111 @@
+/****************************************************************
+ * 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.io.IOException;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.james.jmap.api.model.Preview;
+import org.apache.james.jmap.api.projections.MessageFastViewPrecomputedProperties;
+import org.apache.james.jmap.api.projections.MessageFastViewProjection;
+import org.apache.james.jmap.draft.model.message.view.MessageFullView;
+import org.apache.james.jmap.draft.model.message.view.MessageFullViewFactory;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MessageIdManager;
+import org.apache.james.mailbox.events.Event;
+import org.apache.james.mailbox.events.Group;
+import org.apache.james.mailbox.events.MailboxListener;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.model.FetchGroup;
+import org.apache.james.mailbox.model.MessageId;
+import org.apache.james.mailbox.model.MessageResult;
+import org.apache.james.mailbox.store.SessionProvider;
+import org.parboiled.common.ImmutableList;
+
+import com.github.fge.lambdas.Throwing;
+import com.github.steveash.guavate.Guavate;
+
+import reactor.core.publisher.Flux;
+import reactor.core.scheduler.Schedulers;
+
+public class ComputeMessageFastViewProjectionListener implements MailboxListener.GroupMailboxListener {
+    public static class ComputeMessageFastViewProjectionListenerGroup extends Group {
+
+    }
+
+    static final Group GROUP = new ComputeMessageFastViewProjectionListenerGroup();
+
+    private final MessageIdManager messageIdManager;
+    private final MessageFastViewProjection messageFastViewProjection;
+    private final SessionProvider sessionProvider;
+    private final MessageFullViewFactory messageFullViewFactory;
+
+    @Inject
+    public ComputeMessageFastViewProjectionListener(SessionProvider sessionProvider, MessageIdManager messageIdManager,
+                                                    MessageFastViewProjection messageFastViewProjection,
+                                                    MessageFullViewFactory messageFullViewFactory) {
+        this.sessionProvider = sessionProvider;
+        this.messageIdManager = messageIdManager;
+        this.messageFastViewProjection = messageFastViewProjection;
+        this.messageFullViewFactory = messageFullViewFactory;
+    }
+
+    @Override
+    public Group getDefaultGroup() {
+        return GROUP;
+    }
+
+    @Override
+    public void event(Event event) throws MailboxException {
+        if (event instanceof Added) {
+            MailboxSession session = sessionProvider.createSystemSession(event.getUsername());
+            handleAddedEvent((Added) event, session);
+        }
+    }
+
+    private void handleAddedEvent(Added addedEvent, MailboxSession session) throws MailboxException {
+        Flux.fromIterable(messageIdManager.getMessages(getEventMessageIds(addedEvent), FetchGroup.BODY_CONTENT, session))
+            .publishOn(Schedulers.boundedElastic())
+            .map(Throwing.function(messageResult -> Pair.of(messageResult.getMessageId(), computeFastViewPrecomputedProperties(messageResult))))
+            .flatMap(message -> messageFastViewProjection.store(message.getKey(), message.getValue()))
+            .then()
+            .block();
+    }
+
+    private List<MessageId> getEventMessageIds(Added addedEvent) {
+        return addedEvent.getUids()
+            .stream()
+            .map(uid -> addedEvent.getMetaData(uid).getMessageId())
+            .collect(Guavate.toImmutableSet())
+            .asList();
+    }
+
+    private MessageFastViewPrecomputedProperties computeFastViewPrecomputedProperties(MessageResult messageResult) throws MailboxException, IOException {
+        MessageFullView message = messageFullViewFactory.fromMessageResults(ImmutableList.of(messageResult));
+
+        return MessageFastViewPrecomputedProperties.builder()
+            .preview(Preview.from(message.getPreview().getValue()))
+            .hasAttachment(message.isHasAttachment())
+            .build();
+    }
+}
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/event/ComputeMessageFastViewProjectionListenerTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/event/ComputeMessageFastViewProjectionListenerTest.java
new file mode 100644
index 0000000..2e58340
--- /dev/null
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/event/ComputeMessageFastViewProjectionListenerTest.java
@@ -0,0 +1,203 @@
+/****************************************************************
+ * 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.util.Optional;
+
+import org.apache.james.core.Username;
+import org.apache.james.jmap.api.model.Preview;
+import org.apache.james.jmap.api.projections.MessageFastViewPrecomputedProperties;
+import org.apache.james.jmap.api.projections.MessageFastViewProjection;
+import org.apache.james.jmap.draft.model.PreviewDTO;
+import org.apache.james.jmap.draft.model.message.view.MessageFullViewFactory;
+import org.apache.james.jmap.draft.utils.JsoupHtmlTextExtractor;
+import org.apache.james.jmap.memory.projections.MemoryMessageFastViewProjection;
+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.inmemory.manager.InMemoryIntegrationResources;
+import org.apache.james.mailbox.model.ComposedMessageId;
+import org.apache.james.mailbox.model.FetchGroup;
+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.MessageResult;
+import org.apache.james.mailbox.store.FakeAuthenticator;
+import org.apache.james.mailbox.store.FakeAuthorizator;
+import org.apache.james.mailbox.store.SessionProvider;
+import org.apache.james.mailbox.store.StoreMailboxManager;
+import org.apache.james.mime4j.dom.Message;
+import org.apache.james.util.html.HtmlTextExtractor;
+import org.apache.james.util.mime.MessageContentExtractor;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import reactor.core.publisher.Mono;
+
+class ComputeMessageFastViewProjectionListenerTest {
+    private static final Username BOB = Username.of("bob");
+    private static final Preview PREVIEW = Preview.from("This should be the preview of the message...");
+    private static final MailboxPath BOB_INBOX_PATH = MailboxPath.inbox(BOB);
+    private static final MailboxPath BOB_OTHER_BOX_PATH = MailboxPath.forUser(BOB, "otherBox");
+    private static final MessageFastViewPrecomputedProperties MESSAGE_FAST_VIEW_PRECOMPUTED_PROPERTIES = MessageFastViewPrecomputedProperties.builder()
+        .preview(PREVIEW)
+        .noAttachments()
+        .build();
+    private static final MessageFastViewPrecomputedProperties MESSAGE_FAST_VIEW_PRECOMPUTED_PROPERTIES_EMPTY = MessageFastViewPrecomputedProperties.builder()
+        .preview(Preview.from(PreviewDTO.from(Optional.empty()).getValue()))
+        .noAttachments()
+        .build();
+
+    MessageFastViewProjection messageFastViewProjection;
+    MessageFullViewFactory messageFullViewFactory;
+    MailboxSession mailboxSession;
+    StoreMailboxManager mailboxManager;
+
+    private MessageManager inboxMessageManager;
+    private MessageManager otherBoxMessageManager;
+
+    @BeforeEach
+    void setup() throws Exception {
+        InMemoryIntegrationResources resources = InMemoryIntegrationResources.defaultResources();
+        mailboxManager = resources.getMailboxManager();
+        MessageIdManager messageIdManager = resources.getMessageIdManager();
+
+        messageFastViewProjection = new MemoryMessageFastViewProjection();
+
+        MessageContentExtractor messageContentExtractor = new MessageContentExtractor();
+        HtmlTextExtractor htmlTextExtractor = new JsoupHtmlTextExtractor();
+
+        messageFullViewFactory = new MessageFullViewFactory(resources.getBlobManager(), messageContentExtractor, htmlTextExtractor);
+
+        FakeAuthenticator authenticator = new FakeAuthenticator();
+        authenticator.addUser(BOB, "12345");
+
+        SessionProvider sessionProvider = new SessionProvider(authenticator, FakeAuthorizator.defaultReject());
+
+        ComputeMessageFastViewProjectionListener listener = new ComputeMessageFastViewProjectionListener(sessionProvider, messageIdManager,
+            messageFastViewProjection, messageFullViewFactory);
+
+        resources.getEventBus().register(listener);
+
+        mailboxSession = MailboxSessionUtil.create(BOB);
+
+        MailboxId 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.ComputeMessageFastViewProjectionListener$ComputeMessageFastViewProjectionListenerGroup"))
+            .isEqualTo(new ComputeMessageFastViewProjectionListener.ComputeMessageFastViewProjectionListenerGroup());
+    }
+
+    @Test
+    void shouldStorePreviewWhenBodyMessageNotEmpty() throws Exception {
+        ComposedMessageId composedId = inboxMessageManager.appendMessage(
+            MessageManager.AppendCommand.builder()
+                .build(previewMessage()),
+            mailboxSession);
+
+        assertThat(Mono.from(messageFastViewProjection.retrieve(composedId.getMessageId())).block())
+            .isEqualTo(MESSAGE_FAST_VIEW_PRECOMPUTED_PROPERTIES);
+    }
+
+    @Test
+    void shouldStoreEmptyPreviewWhenEmptyBodyMessage() throws Exception {
+        ComposedMessageId composedId = inboxMessageManager.appendMessage(
+            MessageManager.AppendCommand.builder()
+                .build(emptyMessage()),
+            mailboxSession);
+
+        assertThat(Mono.from(messageFastViewProjection.retrieve(composedId.getMessageId())).block())
+            .isEqualTo(MESSAGE_FAST_VIEW_PRECOMPUTED_PROPERTIES_EMPTY);
+    }
+
+    @Test
+    void shouldStoreMultiplePreviewsWhenMultipleMessagesAdded() throws Exception {
+        ComposedMessageId composedId1 = inboxMessageManager.appendMessage(
+            MessageManager.AppendCommand.builder()
+                .build(previewMessage()),
+            mailboxSession);
+
+        ComposedMessageId composedId2 = inboxMessageManager.appendMessage(
+            MessageManager.AppendCommand.builder()
+                .build(emptyMessage()),
+            mailboxSession);
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(Mono.from(messageFastViewProjection.retrieve(composedId1.getMessageId())).block())
+                .isEqualTo(MESSAGE_FAST_VIEW_PRECOMPUTED_PROPERTIES);
+            softly.assertThat(Mono.from(messageFastViewProjection.retrieve(composedId2.getMessageId())).block())
+                .isEqualTo(MESSAGE_FAST_VIEW_PRECOMPUTED_PROPERTIES_EMPTY);
+        });
+    }
+
+    @Test
+    void shouldKeepPreviewWhenMovingMessage() throws Exception {
+        inboxMessageManager.appendMessage(
+            MessageManager.AppendCommand.builder()
+                .build(previewMessage()),
+            mailboxSession);
+
+        mailboxManager.moveMessages(MessageRange.all(), BOB_INBOX_PATH, BOB_OTHER_BOX_PATH, mailboxSession);
+
+        MessageResult result = otherBoxMessageManager.getMessages(MessageRange.all(), FetchGroup.MINIMAL, mailboxSession).next();
+        assertThat(Mono.from(messageFastViewProjection.retrieve(result.getMessageId())).block())
+            .isEqualTo(MESSAGE_FAST_VIEW_PRECOMPUTED_PROPERTIES);
+    }
+
+    @Test
+    void shouldKeepPreviewWhenCopyingMessage() throws Exception {
+        inboxMessageManager.appendMessage(
+            MessageManager.AppendCommand.builder()
+                .build(previewMessage()),
+            mailboxSession);
+
+        mailboxManager.copyMessages(MessageRange.all(), BOB_INBOX_PATH, BOB_OTHER_BOX_PATH, mailboxSession);
+
+        MessageResult result = otherBoxMessageManager.getMessages(MessageRange.all(), FetchGroup.MINIMAL, mailboxSession).next();
+        assertThat(Mono.from(messageFastViewProjection.retrieve(result.getMessageId())).block())
+            .isEqualTo(MESSAGE_FAST_VIEW_PRECOMPUTED_PROPERTIES);
+    }
+
+    private Message previewMessage() throws Exception {
+        return Message.Builder.of()
+            .setSubject("Preview message")
+            .setBody(PREVIEW.getValue(), StandardCharsets.UTF_8)
+            .build();
+    }
+
+    private Message emptyMessage() throws Exception {
+        return Message.Builder.of()
+            .setSubject("Empty message")
+            .setBody("", StandardCharsets.UTF_8)
+            .build();
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org