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 <[email protected]> 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: [email protected] For additional commands, e-mail: [email protected]
