This is an automated email from the ASF dual-hosted git repository. rcordier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 4cfab0f2b3db546c30911f749fcfedc4989dd269 Author: Tran Tien Duc <[email protected]> AuthorDate: Thu Nov 21 12:00:07 2019 +0700 JAMES-2987 Add MessageMetadataViewFactory MessageViewFactory becomes the generic class converting MessageResults into MessageView. By avoiding the intermediate MessageContentWithMetadata, we can really simplify new MessageView factories. --- .../manager/InMemoryIntegrationResources.java | 12 +++ .../model/message/view/MessageFullViewFactory.java | 29 +----- .../message/view/MessageMetadataViewFactory.java | 57 +++++++++++ .../model/message/view/MessageViewFactory.java | 67 +++++++++++++ .../view/MessageMetadataViewFactoryTest.java | 110 +++++++++++++++++++++ 5 files changed, 251 insertions(+), 24 deletions(-) diff --git a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/manager/InMemoryIntegrationResources.java b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/manager/InMemoryIntegrationResources.java index 29c4ae3..59e1a88 100644 --- a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/manager/InMemoryIntegrationResources.java +++ b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/manager/InMemoryIntegrationResources.java @@ -50,6 +50,8 @@ import org.apache.james.mailbox.store.FakeAuthorizator; import org.apache.james.mailbox.store.JVMMailboxPathLocker; import org.apache.james.mailbox.store.PreDeletionHooks; import org.apache.james.mailbox.store.SessionProvider; +import org.apache.james.mailbox.store.StoreAttachmentManager; +import org.apache.james.mailbox.store.StoreBlobManager; import org.apache.james.mailbox.store.StoreMailboxAnnotationManager; import org.apache.james.mailbox.store.StoreMailboxManager; import org.apache.james.mailbox.store.StoreMessageIdManager; @@ -384,6 +386,7 @@ public class InMemoryIntegrationResources implements IntegrationResources<StoreM private final StoreMessageIdManager storeMessageIdManager; private final MessageSearchIndex searchIndex; private final EventBus eventBus; + private final StoreBlobManager blobManager; InMemoryIntegrationResources(InMemoryMailboxManager mailboxManager, StoreRightManager storeRightManager, MessageId.Factory messageIdFactory, InMemoryCurrentQuotaManager currentQuotaManager, DefaultUserQuotaRootResolver defaultUserQuotaRootResolver, InMemoryPerUserMaxQuotaManager maxQuotaManager, QuotaManager quotaManager, MessageSearchIndex searchIndex, EventBus eventBus) { this.mailboxManager = mailboxManager; @@ -404,6 +407,11 @@ public class InMemoryIntegrationResources implements IntegrationResources<StoreM quotaManager, defaultUserQuotaRootResolver, mailboxManager.getPreDeletionHooks()); + + this.blobManager = new StoreBlobManager( + new StoreAttachmentManager((InMemoryMailboxSessionMapperFactory) mailboxManager.getMapperFactory(), storeMessageIdManager), + storeMessageIdManager, + messageIdFactory); } public DefaultUserQuotaRootResolver getDefaultUserQuotaRootResolver() { @@ -450,4 +458,8 @@ public class InMemoryIntegrationResources implements IntegrationResources<StoreM public EventBus getEventBus() { return eventBus; } + + public StoreBlobManager getBlobManager() { + return blobManager; + } } diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFullViewFactory.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFullViewFactory.java index 2f7d15b..ebef606 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFullViewFactory.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFullViewFactory.java @@ -40,7 +40,6 @@ import org.apache.james.jmap.draft.model.Emailer; import org.apache.james.jmap.draft.model.Keywords; import org.apache.james.jmap.draft.model.MessagePreviewGenerator; import org.apache.james.jmap.draft.utils.HtmlTextExtractor; -import org.apache.james.jmap.draft.utils.KeywordsCombiner; import org.apache.james.mailbox.BlobManager; import org.apache.james.mailbox.MessageUid; import org.apache.james.mailbox.exception.MailboxException; @@ -66,11 +65,9 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Multimaps; import com.google.common.collect.Sets; -public class MessageFullViewFactory { +public class MessageFullViewFactory implements MessageViewFactory<MessageFullView> { public static final String JMAP_MULTIVALUED_FIELD_DELIMITER = "\n"; - private static final KeywordsCombiner ACCUMULATOR = new KeywordsCombiner(); - private final BlobManager blobManager; private final MessagePreviewGenerator messagePreview; private final MessageContentExtractor messageContentExtractor; @@ -87,6 +84,7 @@ public class MessageFullViewFactory { this.keywordsFactory = Keywords.lenientFactory(); } + @Override public MessageFullView fromMessageResults(Collection<MessageResult> messageResults) throws MailboxException { return fromMetaDataWithContent(toMetaDataWithContent(messageResults)); } @@ -122,20 +120,11 @@ public class MessageFullViewFactory { } private MetaDataWithContent toMetaDataWithContent(Collection<MessageResult> messageResults) throws MailboxException { - Preconditions.checkArgument(!messageResults.isEmpty(), "MessageResults cannot be empty"); - Preconditions.checkArgument(hasOnlyOneMessageId(messageResults), "MessageResults need to share the same messageId"); + assertOneMessageId(messageResults); MessageResult firstMessageResult = messageResults.iterator().next(); - List<MailboxId> mailboxIds = messageResults.stream() - .map(MessageResult::getMailboxId) - .distinct() - .collect(Guavate.toImmutableList()); - - Keywords keywords = messageResults.stream() - .map(MessageResult::getFlags) - .map(keywordsFactory::fromFlags) - .reduce(ACCUMULATOR) - .get(); + List<MailboxId> mailboxIds = getMailboxIds(messageResults); + Keywords keywords = getKeywords(messageResults); return MetaDataWithContent.builderFromMessageResult(firstMessageResult) .messageId(firstMessageResult.getMessageId()) @@ -144,14 +133,6 @@ public class MessageFullViewFactory { .build(); } - private boolean hasOnlyOneMessageId(Collection<MessageResult> messageResults) { - return messageResults - .stream() - .map(MessageResult::getMessageId) - .distinct() - .count() == 1; - } - private Instant getDateFromHeaderOrInternalDateOtherwise(org.apache.james.mime4j.dom.Message mimeMessage, MetaDataWithContent message) { return Optional.ofNullable(mimeMessage.getDate()) .map(Date::toInstant) diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageMetadataViewFactory.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageMetadataViewFactory.java new file mode 100644 index 0000000..95a382f --- /dev/null +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageMetadataViewFactory.java @@ -0,0 +1,57 @@ +/**************************************************************** + * 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.draft.model.message.view; + +import java.util.Collection; +import java.util.List; + +import javax.inject.Inject; + +import org.apache.james.jmap.draft.model.BlobId; +import org.apache.james.mailbox.BlobManager; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxId; +import org.apache.james.mailbox.model.MessageResult; + +public class MessageMetadataViewFactory implements MessageViewFactory<MessageMetadataView> { + private final BlobManager blobManager; + + @Inject + MessageMetadataViewFactory(BlobManager blobManager) { + this.blobManager = blobManager; + } + + @Override + public MessageMetadataView fromMessageResults(Collection<MessageResult> messageResults) throws MailboxException { + assertOneMessageId(messageResults); + + MessageResult firstMessageResult = messageResults.iterator().next(); + List<MailboxId> mailboxIds = getMailboxIds(messageResults); + + return MessageMetadataView.messageMetadataBuilder() + .id(firstMessageResult.getMessageId()) + .mailboxIds(mailboxIds) + .blobId(BlobId.of(blobManager.toBlobId(firstMessageResult.getMessageId()))) + .threadId(firstMessageResult.getMessageId().serialize()) + .keywords(getKeywords(messageResults)) + .size(firstMessageResult.getSize()) + .build(); + } +} diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageViewFactory.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageViewFactory.java new file mode 100644 index 0000000..5339c13 --- /dev/null +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageViewFactory.java @@ -0,0 +1,67 @@ +/**************************************************************** + * 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.draft.model.message.view; + +import java.util.Collection; +import java.util.List; + +import org.apache.james.jmap.draft.model.Keywords; +import org.apache.james.jmap.draft.utils.KeywordsCombiner; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxId; +import org.apache.james.mailbox.model.MessageResult; + +import com.github.steveash.guavate.Guavate; +import com.google.common.base.Preconditions; + +public interface MessageViewFactory<T extends MessageView> { + KeywordsCombiner KEYWORDS_COMBINER = new KeywordsCombiner(); + Keywords.KeywordsFactory KEYWORDS_FACTORY = Keywords.lenientFactory(); + + T fromMessageResults(Collection<MessageResult> messageResults) throws MailboxException; + + default void assertOneMessageId(Collection<MessageResult> messageResults) { + Preconditions.checkArgument(!messageResults.isEmpty(), "MessageResults cannot be empty"); + Preconditions.checkArgument(hasOnlyOneMessageId(messageResults), "MessageResults need to share the same messageId"); + } + + default boolean hasOnlyOneMessageId(Collection<MessageResult> messageResults) { + return messageResults + .stream() + .map(MessageResult::getMessageId) + .distinct() + .count() == 1; + } + + default List<MailboxId> getMailboxIds(Collection<MessageResult> messageResults) { + return messageResults.stream() + .map(MessageResult::getMailboxId) + .distinct() + .collect(Guavate.toImmutableList()); + } + + default Keywords getKeywords(Collection<MessageResult> messageResults) { + return messageResults.stream() + .map(MessageResult::getFlags) + .map(KEYWORDS_FACTORY::fromFlags) + .reduce(KEYWORDS_COMBINER) + .get(); + } +} diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/model/message/view/MessageMetadataViewFactoryTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/model/message/view/MessageMetadataViewFactoryTest.java new file mode 100644 index 0000000..36cac92 --- /dev/null +++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/model/message/view/MessageMetadataViewFactoryTest.java @@ -0,0 +1,110 @@ +/**************************************************************** + * 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.draft.model.message.view; + +import java.util.List; + +import javax.mail.Flags; + +import org.apache.james.core.Username; +import org.apache.james.jmap.draft.model.BlobId; +import org.apache.james.jmap.draft.model.Keyword; +import org.apache.james.jmap.draft.model.Keywords; +import org.apache.james.jmap.draft.model.Number; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MessageIdManager; +import org.apache.james.mailbox.MessageManager; +import org.apache.james.mailbox.inmemory.InMemoryMailboxManager; +import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources; +import org.apache.james.mailbox.model.ComposedMessageId; +import org.apache.james.mailbox.model.FetchGroupImpl; +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.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.google.common.collect.ImmutableList; + +class MessageMetadataViewFactoryTest { + + public static final Username BOB = Username.of("bob"); + private MessageIdManager messageIdManager; + private MessageMetadataViewFactory testee; + private MailboxSession session; + private MessageManager bobInbox; + private MessageManager bobMailbox; + private ComposedMessageId message1; + + @BeforeEach + void setUp() throws Exception { + InMemoryIntegrationResources resources = InMemoryIntegrationResources.defaultResources(); + messageIdManager = resources.getMessageIdManager(); + InMemoryMailboxManager mailboxManager = resources.getMailboxManager(); + + session = mailboxManager.createSystemSession(BOB); + MailboxId bobInboxId = mailboxManager.createMailbox(MailboxPath.inbox(session), session).get(); + MailboxId bobMailboxId = mailboxManager.createMailbox(MailboxPath.forUser(BOB, "anotherMailbox"), session).get(); + + bobInbox = mailboxManager.getMailbox(bobInboxId, session); + bobMailbox = mailboxManager.getMailbox(bobMailboxId, session); + + message1 = bobInbox.appendMessage(MessageManager.AppendCommand.builder() + .withFlags(new Flags(Flags.Flag.SEEN)) + .build("header: value\r\n\r\nbody"), + session); + + testee = new MessageMetadataViewFactory(resources.getBlobManager()); + } + + @Test + void fromMessageResultsShouldReturnCorrectView() throws Exception { + List<MessageResult> messages = messageIdManager + .getMessages(ImmutableList.of(message1.getMessageId()), FetchGroupImpl.MINIMAL, session); + + MessageMetadataView actual = testee.fromMessageResults(messages); + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(actual.getId()).isEqualTo(message1.getMessageId()); + softly.assertThat(actual.getMailboxIds()).containsExactly(bobInbox.getId()); + softly.assertThat(actual.getThreadId()).isEqualTo(message1.getMessageId().serialize()); + softly.assertThat(actual.getSize()).isEqualTo(Number.fromLong(21)); + softly.assertThat(actual.getKeywords()).isEqualTo(Keywords.strictFactory().from(Keyword.SEEN).asMap()); + softly.assertThat(actual.getBlobId()).isEqualTo(BlobId.of(message1.getMessageId().serialize())); + }); + } + + @Test + void fromMessageResultsShouldCombineKeywords() throws Exception { + messageIdManager.setInMailboxes(message1.getMessageId(), ImmutableList.of(bobInbox.getId(), bobMailbox.getId()), session); + bobMailbox.setFlags(new Flags(Flags.Flag.FLAGGED), MessageManager.FlagsUpdateMode.REPLACE, MessageRange.all(), session); + + List<MessageResult> messages = messageIdManager + .getMessages(ImmutableList.of(message1.getMessageId()), FetchGroupImpl.MINIMAL, session); + + MessageMetadataView actual = testee.fromMessageResults(messages); + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(actual.getId()).isEqualTo(message1.getMessageId()); + softly.assertThat(actual.getKeywords()).isEqualTo(Keywords.strictFactory().from(Keyword.SEEN, Keyword.FLAGGED).asMap()); + }); + } + +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
