JAMES-1818 Remove store usage in message creation by using managers
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/f1116e2d Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/f1116e2d Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/f1116e2d Branch: refs/heads/master Commit: f1116e2d5f406c893a08deec274e0797e498e903 Parents: 7bfb0cd Author: Raphael Ouazana <[email protected]> Authored: Thu Aug 25 09:41:54 2016 +0200 Committer: Raphael Ouazana <[email protected]> Committed: Mon Aug 29 15:17:16 2016 +0200 ---------------------------------------------------------------------- .../apache/james/mailbox/MessageManager.java | 6 + .../mailbox/store/StoreMessageManager.java | 19 +- .../base/MailboxEventAnalyserTest.java | 4 + .../integration/SetMessagesMethodTest.java | 4 +- .../methods/SetMessagesCreationProcessor.java | 93 +++--- .../james/jmap/model/CreationMessage.java | 6 +- .../apache/james/jmap/model/MessageFactory.java | 123 ++------ .../model/message/DateResolutionFormater.java | 66 ----- .../james/jmap/model/message/EMailer.java | 69 ----- .../jmap/model/message/HeaderCollection.java | 247 ---------------- .../jmap/model/message/IndexableMessage.java | 250 ---------------- .../model/message/JsonMessageConstants.java | 79 ----- .../jmap/model/message/MessageUpdateJson.java | 75 ----- .../james/jmap/model/message/MimePart.java | 292 ------------------- .../model/message/MimePartContainerBuilder.java | 47 --- .../jmap/model/message/MimePartParser.java | 121 -------- .../message/RootMimePartContainerBuilder.java | 89 ------ .../org/apache/james/jmap/send/MailFactory.java | 6 +- .../jmap/utils/SystemMailboxesProvider.java | 5 +- .../jmap/utils/SystemMailboxesProviderImpl.java | 30 +- .../SetMessagesCreationProcessorTest.java | 98 +++---- .../james/jmap/model/message/MimePartTest.java | 249 ---------------- .../apache/james/jmap/send/MailFactoryTest.java | 38 ++- 23 files changed, 149 insertions(+), 1867 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/f1116e2d/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java ---------------------------------------------------------------------- diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java b/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java index 2c6d8f0..92f3cae 100644 --- a/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java +++ b/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java @@ -31,6 +31,7 @@ import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.exception.UnsupportedCriteriaException; import org.apache.james.mailbox.model.MailboxACL; 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.model.MessageResult.FetchGroup; @@ -171,6 +172,11 @@ public interface MessageManager { MailboxId getId(); /** + * Gets the path of the referenced mailbox + */ + MailboxPath getMailboxPath() throws MailboxException; + + /** * Gets current meta data for the mailbox.<br> * Consolidates common calls together to allow improved performance.<br> * The meta-data returned should be immutable and represent the current http://git-wip-us.apache.org/repos/asf/james-project/blob/f1116e2d/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java ---------------------------------------------------------------------- diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java index 1e6fb3c..f84daa9 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java @@ -53,6 +53,7 @@ import org.apache.james.mailbox.model.Attachment; import org.apache.james.mailbox.model.MailboxACL; import org.apache.james.mailbox.model.MailboxACL.MailboxACLRights; import org.apache.james.mailbox.model.MailboxId; +import org.apache.james.mailbox.model.MailboxPath; import org.apache.james.mailbox.model.MessageAttachment; import org.apache.james.mailbox.model.MessageMetaData; import org.apache.james.mailbox.model.MessageRange; @@ -249,7 +250,7 @@ public class StoreMessageManager implements org.apache.james.mailbox.MessageMana */ public Iterator<Long> expunge(MessageRange set, MailboxSession mailboxSession) throws MailboxException { if (!isWriteable(mailboxSession)) { - throw new ReadOnlyException(new StoreMailboxPath(getMailboxEntity()), mailboxSession.getPathDelimiter()); + throw new ReadOnlyException(getMailboxPath(), mailboxSession.getPathDelimiter()); } Map<Long, MessageMetaData> uids = deleteMarkedInMailbox(set, mailboxSession); @@ -271,7 +272,7 @@ public class StoreMessageManager implements org.apache.james.mailbox.MessageMana SharedFileInputStream contentIn = null; if (!isWriteable(mailboxSession)) { - throw new ReadOnlyException(new StoreMailboxPath(getMailboxEntity()), mailboxSession.getPathDelimiter()); + throw new ReadOnlyException(getMailboxPath(), mailboxSession.getPathDelimiter()); } try { @@ -389,7 +390,7 @@ public class StoreMessageManager implements org.apache.james.mailbox.MessageMana new QuotaChecker(quotaManager, quotaRootResolver, mailbox).tryAddition(1, size); - return locker.executeWithLock(mailboxSession, new StoreMailboxPath(getMailboxEntity()), new MailboxPathLocker.LockAwareExecution<Long>() { + return locker.executeWithLock(mailboxSession, getMailboxPath(), new MailboxPathLocker.LockAwareExecution<Long>() { @Override public Long execute() throws MailboxException { @@ -552,7 +553,7 @@ public class StoreMessageManager implements org.apache.james.mailbox.MessageMana public Map<Long, Flags> setFlags(final Flags flags, final FlagsUpdateMode flagsUpdateMode, final MessageRange set, MailboxSession mailboxSession) throws MailboxException { if (!isWriteable(mailboxSession)) { - throw new ReadOnlyException(new StoreMailboxPath(getMailboxEntity()), mailboxSession.getPathDelimiter()); + throw new ReadOnlyException(getMailboxPath(), mailboxSession.getPathDelimiter()); } final SortedMap<Long, Flags> newFlagsByUid = new TreeMap<Long, Flags>(); @@ -614,7 +615,7 @@ public class StoreMessageManager implements org.apache.james.mailbox.MessageMana */ public List<MessageRange> moveTo(final MessageRange set, final StoreMessageManager toMailbox, final MailboxSession session) throws MailboxException { if (!isWriteable(session)) { - throw new ReadOnlyException(new StoreMailboxPath(getMailboxEntity()), session.getPathDelimiter()); + throw new ReadOnlyException(getMailboxPath(), session.getPathDelimiter()); } if (!toMailbox.isWriteable(session)) { throw new ReadOnlyException(new StoreMailboxPath(toMailbox.getMailboxEntity()), session.getPathDelimiter()); @@ -678,7 +679,7 @@ public class StoreMessageManager implements org.apache.james.mailbox.MessageMana protected List<Long> recent(final boolean reset, MailboxSession mailboxSession) throws MailboxException { if (reset) { if (!isWriteable(mailboxSession)) { - throw new ReadOnlyException(new StoreMailboxPath(getMailboxEntity()), mailboxSession.getPathDelimiter()); + throw new ReadOnlyException(getMailboxPath(), mailboxSession.getPathDelimiter()); } } final MessageMapper messageMapper = mapperFactory.getMessageMapper(mailboxSession); @@ -835,7 +836,13 @@ public class StoreMessageManager implements org.apache.james.mailbox.MessageMana return aclResolver.applyGlobalACL(mailbox.getACL(), new GroupFolderResolver(mailboxSession).isGroupFolder(mailbox)); } + @Override public MailboxId getId() { return mailbox.getMailboxId(); } + + @Override + public MailboxPath getMailboxPath() throws MailboxException { + return new StoreMailboxPath(getMailboxEntity()); + } } http://git-wip-us.apache.org/repos/asf/james-project/blob/f1116e2d/protocols/imap/src/test/java/org/apache/james/imap/processor/base/MailboxEventAnalyserTest.java ---------------------------------------------------------------------- diff --git a/protocols/imap/src/test/java/org/apache/james/imap/processor/base/MailboxEventAnalyserTest.java b/protocols/imap/src/test/java/org/apache/james/imap/processor/base/MailboxEventAnalyserTest.java index 6d1eaac..1e8dfa2 100644 --- a/protocols/imap/src/test/java/org/apache/james/imap/processor/base/MailboxEventAnalyserTest.java +++ b/protocols/imap/src/test/java/org/apache/james/imap/processor/base/MailboxEventAnalyserTest.java @@ -330,6 +330,10 @@ public class MailboxEventAnalyserTest { public MailboxId getId() { return null; } + + public MailboxPath getMailboxPath() { + return null; + } }; } http://git-wip-us.apache.org/repos/asf/james-project/blob/f1116e2d/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java index 7fa3e83..d85c1bc 100644 --- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java +++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java @@ -1024,7 +1024,7 @@ public abstract class SetMessagesMethodTest { .body(NAME, equalTo("messagesSet")) .body(ARGUMENTS + ".created", aMapWithSize(1)) .body(ARGUMENTS + ".created", hasKey(messageCreationId)) - .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].headers.from", equalTo(fromAddress)) + .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].headers.From", equalTo(fromAddress)) .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].from.name", equalTo(fromAddress)) .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].from.email", equalTo(fromAddress)); } @@ -1061,7 +1061,7 @@ public abstract class SetMessagesMethodTest { .body(NAME, equalTo("messagesSet")) .body(ARGUMENTS + ".created", aMapWithSize(1)) .body(ARGUMENTS + ".created", hasKey(messageCreationId)) - .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].headers.from", equalTo(fromAddress)) + .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].headers.From", equalTo(fromAddress)) .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].from.name", equalTo(fromAddress)) .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].from.email", equalTo(fromAddress)); } http://git-wip-us.apache.org/repos/asf/james-project/blob/f1116e2d/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java index ebd299d..f270673 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java @@ -24,13 +24,11 @@ import java.util.Date; import java.util.List; import java.util.Optional; import java.util.Set; -import java.util.function.Function; import java.util.stream.Collectors; import javax.inject.Inject; import javax.mail.Flags; import javax.mail.MessagingException; -import javax.mail.internet.SharedInputStream; import javax.mail.util.SharedByteArrayInputStream; import org.apache.james.jmap.exceptions.AttachmentsNotFoundException; @@ -42,6 +40,7 @@ import org.apache.james.jmap.model.CreationMessage; import org.apache.james.jmap.model.CreationMessageId; import org.apache.james.jmap.model.Message; import org.apache.james.jmap.model.MessageFactory; +import org.apache.james.jmap.model.MessageFactory.MetaDataWithContent; import org.apache.james.jmap.model.MessageId; import org.apache.james.jmap.model.MessageProperties; import org.apache.james.jmap.model.MessageProperties.MessageProperty; @@ -58,20 +57,13 @@ import org.apache.james.jmap.utils.SystemMailboxesProvider; import org.apache.james.lifecycle.api.LifecycleUtil; import org.apache.james.mailbox.AttachmentManager; import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MessageManager; import org.apache.james.mailbox.exception.AttachmentNotFoundException; import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.exception.MailboxNotFoundException; import org.apache.james.mailbox.model.AttachmentId; import org.apache.james.mailbox.model.Cid; -import org.apache.james.mailbox.model.MailboxId; -import org.apache.james.mailbox.model.MailboxPath; import org.apache.james.mailbox.model.MessageAttachment; -import org.apache.james.mailbox.store.MailboxSessionMapperFactory; -import org.apache.james.mailbox.store.mail.MessageMapper; -import org.apache.james.mailbox.store.mail.model.Mailbox; -import org.apache.james.mailbox.store.mail.model.MailboxMessage; -import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; -import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage; import org.apache.mailet.Mail; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -88,7 +80,6 @@ import com.google.common.collect.ImmutableList; public class SetMessagesCreationProcessor implements SetMessagesProcessor { private static final Logger LOG = LoggerFactory.getLogger(SetMailboxesCreationProcessor.class); - private final MailboxSessionMapperFactory mailboxSessionMapperFactory; private final MIMEMessageConverter mimeMessageConverter; private final MailSpool mailSpool; private final MailFactory mailFactory; @@ -98,14 +89,12 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor { @VisibleForTesting @Inject - SetMessagesCreationProcessor(MailboxSessionMapperFactory mailboxSessionMapperFactory, - MIMEMessageConverter mimeMessageConverter, + SetMessagesCreationProcessor(MIMEMessageConverter mimeMessageConverter, MailSpool mailSpool, MailFactory mailFactory, MessageFactory messageFactory, SystemMailboxesProvider systemMailboxesProvider, AttachmentManager attachmentManager) { - this.mailboxSessionMapperFactory = mailboxSessionMapperFactory; this.mimeMessageConverter = mimeMessageConverter; this.mailSpool = mailSpool; this.mailFactory = mailFactory; @@ -178,7 +167,7 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor { } } - private void validateImplementedFeature(CreationMessageEntry entry, MailboxSession session) throws MailboxNotImplementedException { + private void validateImplementedFeature(CreationMessageEntry entry, MailboxSession session) throws MailboxException, MailboxNotImplementedException { if (isAppendToMailboxWithRole(Role.DRAFTS, entry.getValue(), session)) { throw new MailboxNotImplementedException("Drafts saving is not implemented"); } @@ -241,35 +230,21 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor { private MessageWithId handleOutboxMessages(CreationMessageEntry entry, MailboxSession session) throws MailboxException, MessagingException { - Mailbox outbox = getMailboxWithRole(session, Role.OUTBOX).orElseThrow(() -> new MailboxNotFoundException(Role.OUTBOX.serialize())); + MessageManager outbox = getMailboxWithRole(session, Role.OUTBOX).orElseThrow(() -> new MailboxNotFoundException(Role.OUTBOX.serialize())); if (!isRequestForSending(entry.getValue(), session)) { throw new IllegalStateException("Messages for everything but outbox should have been filtered earlier"); } - Function<Long, MessageId> idGenerator = uid -> generateMessageId(session, outbox, uid); - return createMessageInOutboxAndSend(entry, session, outbox, idGenerator); + MetaDataWithContent newMessage = createMessageInOutbox(entry, outbox, session); + return sendMessage(entry.getCreationId(), newMessage, session); } - @VisibleForTesting - protected MessageWithId createMessageInOutboxAndSend(CreationMessageEntry createdEntry, - MailboxSession session, - Mailbox outbox, Function<Long, MessageId> buildMessageIdFromUid) throws MailboxException, MessagingException { - - CreationMessageId creationId = createdEntry.getCreationId(); - MessageMapper messageMapper = mailboxSessionMapperFactory.createMessageMapper(session); - MailboxMessage newMailboxMessage = buildMailboxMessage(session, createdEntry, outbox); - messageMapper.add(outbox, newMailboxMessage); - Message jmapMessage = messageFactory.fromMailboxMessage(newMailboxMessage, newMailboxMessage.getAttachments(), buildMessageIdFromUid); - sendMessage(newMailboxMessage, jmapMessage, session); - return new MessageWithId(creationId, jmapMessage); - } - - private boolean isAppendToMailboxWithRole(Role role, CreationMessage entry, MailboxSession mailboxSession) { + private boolean isAppendToMailboxWithRole(Role role, CreationMessage entry, MailboxSession mailboxSession) throws MailboxException { return getMailboxWithRole(mailboxSession, role) .map(box -> entry.isIn(box)) .orElse(false); } - private Optional<Mailbox> getMailboxWithRole(MailboxSession mailboxSession, Role role) { + private Optional<MessageManager> getMailboxWithRole(MailboxSession mailboxSession, Role role) throws MailboxException { return systemMailboxesProvider.listMailboxes(role, mailboxSession).findFirst(); } @@ -295,29 +270,29 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor { .collect(Collectors.toSet()); } - private boolean isRequestForSending(CreationMessage creationMessage, MailboxSession session) { + private boolean isRequestForSending(CreationMessage creationMessage, MailboxSession session) throws MailboxException { return isAppendToMailboxWithRole(Role.OUTBOX, creationMessage, session); } - private MessageId generateMessageId(MailboxSession session, Mailbox outbox, long uid) { - MailboxPath outboxPath = new MailboxPath(session.getPersonalSpace(), session.getUser().getUserName(), outbox.getName()); - return new MessageId(session.getUser(), outboxPath, uid); - } - - private MailboxMessage buildMailboxMessage(MailboxSession session, MessageWithId.CreationMessageEntry createdEntry, Mailbox outbox) throws MailboxException { + private MetaDataWithContent createMessageInOutbox(MessageWithId.CreationMessageEntry createdEntry, MessageManager outbox, MailboxSession session) throws MailboxException { ImmutableList<MessageAttachment> messageAttachments = getMessageAttachments(session, createdEntry.getValue().getAttachments()); byte[] messageContent = mimeMessageConverter.convert(createdEntry, messageAttachments); - SharedInputStream content = new SharedByteArrayInputStream(messageContent); - long size = messageContent.length; - int bodyStartOctet = 0; - - Flags flags = getMessageFlags(createdEntry.getValue()); - PropertyBuilder propertyBuilder = buildPropertyBuilder(); - MailboxId mailboxId = outbox.getMailboxId(); + SharedByteArrayInputStream content = new SharedByteArrayInputStream(messageContent); Date internalDate = Date.from(createdEntry.getValue().getDate().toInstant()); + Flags flags = getMessageFlags(createdEntry.getValue()); - return new SimpleMailboxMessage(internalDate, size, - bodyStartOctet, content, flags, propertyBuilder, mailboxId, messageAttachments); + long uid = outbox.appendMessage(content, internalDate, session, flags.contains(Flags.Flag.RECENT), flags); + + return MetaDataWithContent.builder() + .uid(uid) + .flags(flags) + .size(messageContent.length) + .internalDate(internalDate) + .sharedContent(content) + .attachments(messageAttachments) + .mailboxId(outbox.getId()) + .messageId(new MessageId(session.getUser(), outbox.getMailboxPath(), uid)) + .build(); } private ImmutableList<MessageAttachment> getMessageAttachments(MailboxSession session, ImmutableList<Attachment> attachments) throws MailboxException { @@ -342,10 +317,6 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor { } } - private PropertyBuilder buildPropertyBuilder() { - return new PropertyBuilder(); - } - private Flags getMessageFlags(CreationMessage message) { Flags result = new Flags(); if (!message.isIsUnread()) { @@ -363,8 +334,14 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor { return result; } - private void sendMessage(MailboxMessage mailboxMessage, Message jmapMessage, MailboxSession session) throws MessagingException { - Mail mail = buildMessage(mailboxMessage, jmapMessage); + private MessageWithId sendMessage(CreationMessageId creationId, MetaDataWithContent message, MailboxSession session) throws MailboxException, MessagingException { + Message jmapMessage = messageFactory.fromMetaDataWithContent(message); + sendMessage(message, jmapMessage, session); + return new MessageWithId(creationId, jmapMessage); + } + + private void sendMessage(MetaDataWithContent message, Message jmapMessage, MailboxSession session) throws MessagingException { + Mail mail = buildMessage(message, jmapMessage); try { MailMetadata metadata = new MailMetadata(jmapMessage.getId(), session.getUser().getUserName()); mailSpool.send(mail, metadata); @@ -373,9 +350,9 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor { } } - private Mail buildMessage(MailboxMessage mailboxMessage, Message jmapMessage) throws MessagingException { + private Mail buildMessage(MetaDataWithContent message, Message jmapMessage) throws MessagingException { try { - return mailFactory.build(mailboxMessage, jmapMessage); + return mailFactory.build(message, jmapMessage); } catch (IOException e) { throw new MessagingException("error building message to send", e); } http://git-wip-us.apache.org/repos/asf/james-project/blob/f1116e2d/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java index b925cc5..a113f83 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java @@ -33,7 +33,7 @@ import javax.mail.internet.InternetAddress; import org.apache.james.jmap.methods.ValidationResult; import org.apache.james.jmap.model.MessageProperties.MessageProperty; -import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mailbox.MessageManager; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; @@ -361,8 +361,8 @@ public class CreationMessage { from.filter(f -> !f.hasValidEmail()).ifPresent(f -> errors.add(invalidPropertyFrom)); } - public boolean isIn(Mailbox mailbox) { - return mailboxIds.contains(mailbox.getMailboxId().serialize()); + public boolean isIn(MessageManager mailbox) { + return mailboxIds.contains(mailbox.getId().serialize()); } @JsonDeserialize(builder = DraftEmailer.Builder.class) http://git-wip-us.apache.org/repos/asf/james-project/blob/f1116e2d/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageFactory.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageFactory.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageFactory.java index c92293c..c1597c2 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageFactory.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageFactory.java @@ -27,26 +27,21 @@ import java.util.Date; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Optional; -import java.util.Set; import java.util.TimeZone; import java.util.function.Function; import java.util.stream.Collectors; import javax.inject.Inject; import javax.mail.Flags; +import javax.mail.internet.SharedInputStream; import org.apache.james.jmap.model.MessageContentExtractor.MessageContent; -import org.apache.james.jmap.model.message.EMailer; -import org.apache.james.jmap.model.message.IndexableMessage; import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.model.Cid; import org.apache.james.mailbox.model.MailboxId; import org.apache.james.mailbox.model.MessageAttachment; import org.apache.james.mailbox.model.MessageMetaData; import org.apache.james.mailbox.model.MessageResult; -import org.apache.james.mailbox.store.extractor.DefaultTextExtractor; -import org.apache.james.mailbox.store.mail.model.MailboxMessage; import org.apache.james.mime4j.dom.address.AddressList; import org.apache.james.mime4j.dom.address.Mailbox; import org.apache.james.mime4j.dom.address.MailboxList; @@ -58,12 +53,10 @@ import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; public class MessageFactory { - public static final String MULTIVALUED_HEADERS_SEPARATOR = ", "; public static final ZoneId UTC_ZONE_ID = ZoneId.of("Z"); private final MessagePreviewGenerator messagePreview; @@ -123,38 +116,6 @@ public class MessageFactory { } } - public Message fromMailboxMessage(MailboxMessage mailboxMessage, - List<MessageAttachment> attachments, - Function<Long, MessageId> uidToMessageId) { - - IndexableMessage im = IndexableMessage.from(mailboxMessage, new DefaultTextExtractor(), UTC_ZONE_ID); - MessageId messageId = uidToMessageId.apply(im.getId()); - return Message.builder() - .id(messageId) - .blobId(BlobId.of(String.valueOf(im.getId()))) - .threadId(messageId.serialize()) - .mailboxIds(ImmutableList.of(im.getMailboxId())) - .inReplyToMessageId(getHeaderAsSingleValue(im, "in-reply-to")) - .isUnread(im.isUnRead()) - .isFlagged(im.isFlagged()) - .isAnswered(im.isAnswered()) - .isDraft(im.isDraft()) - .subject(getSubject(im)) - .headers(toMap(im.getHeaders())) - .from(firstElasticSearchEmailers(im.getFrom())) - .to(fromElasticSearchEmailers(im.getTo())) - .cc(fromElasticSearchEmailers(im.getCc())) - .bcc(fromElasticSearchEmailers(im.getBcc())) - .replyTo(fromElasticSearchEmailers(im.getReplyTo())) - .size(im.getSize()) - .date(getInternalDate(mailboxMessage, im)) - .preview(getPreview(im)) - .textBody(getTextBody(im)) - .htmlBody(getHtmlBody(im)) - .attachments(getAttachments(attachments)) - .build(); - } - private String getPreview(MessageContent messageContent) { if (messageContent.getHtmlBody().isPresent()) { return messagePreview.forHTMLBody(messageContent.getHtmlBody()); @@ -162,21 +123,6 @@ public class MessageFactory { return messagePreview.forTextBody(messageContent.getTextBody()); } - private String getPreview(IndexableMessage im) { - Optional<String> bodyHtml = im.getBodyHtml(); - if (bodyHtml.isPresent()) { - return messagePreview.forHTMLBody(bodyHtml); - } - return messagePreview.forTextBody(im.getBodyText()); - } - - private String getSubject(IndexableMessage im) { - return im.getSubjects() - .stream() - .map(String::trim) - .collect(Collectors.joining(MULTIVALUED_HEADERS_SEPARATOR)); - } - private Emailer firstFromMailboxList(MailboxList list) { if (list == null) { return null; @@ -211,34 +157,6 @@ public class MessageFactory { return mailbox.getAddress(); } - private Emailer firstElasticSearchEmailers(Set<EMailer> emailers) { - return emailers.stream() - .findFirst() - .map(this::fromElasticSearchEmailer) - .orElse(null); - } - - private ImmutableList<Emailer> fromElasticSearchEmailers(Set<EMailer> emailers) { - return emailers.stream() - .map(this::fromElasticSearchEmailer) - .collect(Guavate.toImmutableList()); - } - - private Emailer fromElasticSearchEmailer(EMailer emailer) { - return Emailer.builder() - .name(emailer.getName()) - .email(emailer.getAddress()) - .build(); - } - - private ImmutableMap<String, String> toMap(Multimap<String, String> multimap) { - return multimap - .asMap() - .entrySet() - .stream() - .collect(Guavate.toImmutableMap(Map.Entry::getKey, x -> joinOnComma(x.getValue()))); - } - private ImmutableMap<String, String> toMap(List<Field> fields) { Function<Entry<String, Collection<Field>>, String> bodyConcatenator = fieldListEntry -> fieldListEntry.getValue() .stream() @@ -261,26 +179,6 @@ public class MessageFactory { return field.getBody(); } - private String getHeaderAsSingleValue(IndexableMessage im, String header) { - return Strings.emptyToNull(joinOnComma(im.getHeaders().get(header))); - } - - private String joinOnComma(Iterable<String> iterable) { - return String.join(MULTIVALUED_HEADERS_SEPARATOR, iterable); - } - - private ZonedDateTime getInternalDate(MailboxMessage mailboxMessage, IndexableMessage im) { - return ZonedDateTime.ofInstant(mailboxMessage.getInternalDate().toInstant(), UTC_ZONE_ID); - } - - private String getTextBody(IndexableMessage im) { - return im.getBodyText().map(Strings::emptyToNull).orElse(null); - } - - private String getHtmlBody(IndexableMessage im) { - return im.getBodyHtml().map(Strings::emptyToNull).orElse(null); - } - private List<Attachment> getAttachments(List<MessageAttachment> attachments) { return attachments.stream() .map(this::fromMailboxAttachment) @@ -325,6 +223,7 @@ public class MessageFactory { private Long size; private Date internalDate; private InputStream content; + private SharedInputStream sharedContent; private List<MessageAttachment> attachments; private MailboxId mailboxId; private MessageId messageId; @@ -359,6 +258,11 @@ public class MessageFactory { return this; } + public Builder sharedContent(SharedInputStream sharedContent) { + this.sharedContent = sharedContent; + return this; + } + public Builder attachments(List<MessageAttachment> attachments) { this.attachments = attachments; return this; @@ -382,11 +286,11 @@ public class MessageFactory { Preconditions.checkArgument(flags != null); Preconditions.checkArgument(size != null); Preconditions.checkArgument(internalDate != null); - Preconditions.checkArgument(content != null); + Preconditions.checkArgument(content != null ^ sharedContent != null); Preconditions.checkArgument(attachments != null); Preconditions.checkArgument(mailboxId != null); Preconditions.checkArgument(messageId != null); - return new MetaDataWithContent(uid, modSeq, flags, size, internalDate, content, attachments, mailboxId, messageId); + return new MetaDataWithContent(uid, modSeq, flags, size, internalDate, content, sharedContent, attachments, mailboxId, messageId); } } @@ -396,17 +300,19 @@ public class MessageFactory { private final long size; private final Date internalDate; private final InputStream content; + private final SharedInputStream sharedContent; private final List<MessageAttachment> attachments; private final MailboxId mailboxId; private final MessageId messageId; - private MetaDataWithContent(long uid, long modSeq, Flags flags, long size, Date internalDate, InputStream content, List<MessageAttachment> attachments, MailboxId mailboxId, MessageId messageId) { + private MetaDataWithContent(long uid, long modSeq, Flags flags, long size, Date internalDate, InputStream content, SharedInputStream sharedContent, List<MessageAttachment> attachments, MailboxId mailboxId, MessageId messageId) { this.uid = uid; this.modSeq = modSeq; this.flags = flags; this.size = size; this.internalDate = internalDate; this.content = content; + this.sharedContent = sharedContent; this.attachments = attachments; this.mailboxId = mailboxId; this.messageId = messageId; @@ -442,6 +348,11 @@ public class MessageFactory { } public InputStream getContent() { + if (sharedContent != null) { + long begin = 0; + long allContent = -1; + return sharedContent.newStream(begin, allContent); + } return content; } http://git-wip-us.apache.org/repos/asf/james-project/blob/f1116e2d/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/DateResolutionFormater.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/DateResolutionFormater.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/DateResolutionFormater.java deleted file mode 100644 index e8baf74..0000000 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/DateResolutionFormater.java +++ /dev/null @@ -1,66 +0,0 @@ -/**************************************************************** - * 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.model.message; - -import org.apache.james.mailbox.model.SearchQuery; - -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; -import java.time.temporal.TemporalUnit; -import java.time.format.DateTimeFormatter; -import java.util.Date; - -public class DateResolutionFormater { - - public static DateTimeFormatter DATE_TIME_FOMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"); - - public static ZonedDateTime computeUpperDate(ZonedDateTime date, SearchQuery.DateResolution resolution) { - return date.truncatedTo(convertDateResolutionField(resolution)).plus(1,convertDateResolutionField(resolution)); - } - - public static ZonedDateTime computeLowerDate(ZonedDateTime date, SearchQuery.DateResolution resolution) { - return date.truncatedTo(convertDateResolutionField(resolution)); - } - - private static TemporalUnit convertDateResolutionField(SearchQuery.DateResolution resolution) { - switch(resolution) { - case Year: - return ChronoUnit.YEARS; - case Month: - return ChronoUnit.MONTHS; - case Day: - return ChronoUnit.DAYS; - case Hour: - return ChronoUnit.HOURS; - case Minute: - return ChronoUnit.MINUTES; - case Second: - return ChronoUnit.SECONDS; - default: - throw new RuntimeException("Unknown Date resolution used"); - } - } - - public static ZonedDateTime convertDateToZonedDateTime(Date date) { - return ZonedDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault()); - } -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/f1116e2d/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/EMailer.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/EMailer.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/EMailer.java deleted file mode 100644 index 0126c73..0000000 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/EMailer.java +++ /dev/null @@ -1,69 +0,0 @@ -/**************************************************************** - * 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.model.message; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.base.MoreObjects; - -import java.util.Objects; - -public class EMailer { - - private final String name; - private final String address; - - public EMailer(String name, String address) { - this.name = name; - this.address = address; - } - - @JsonProperty(JsonMessageConstants.EMailer.NAME) - public String getName() { - return name; - } - - @JsonProperty(JsonMessageConstants.EMailer.ADDRESS) - public String getAddress() { - return address; - } - - @Override - public boolean equals(Object o) { - if (o instanceof EMailer) { - EMailer otherEMailer = (EMailer) o; - return Objects.equals(name, otherEMailer.name) - && Objects.equals(address, otherEMailer.address); - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(name, address); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("name", name) - .add("address", address) - .toString(); - } -} http://git-wip-us.apache.org/repos/asf/james-project/blob/f1116e2d/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/HeaderCollection.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/HeaderCollection.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/HeaderCollection.java deleted file mode 100644 index 03a99f2..0000000 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/HeaderCollection.java +++ /dev/null @@ -1,247 +0,0 @@ -/**************************************************************** - * 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.model.message; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Charsets; -import com.google.common.base.Preconditions; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ImmutableMultimap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; -import org.apache.james.mailbox.store.search.SearchUtil; -import org.apache.james.mime4j.codec.DecoderUtil; -import org.apache.james.mime4j.dom.address.Address; -import org.apache.james.mime4j.dom.address.Group; -import org.apache.james.mime4j.dom.address.Mailbox; -import org.apache.james.mime4j.field.address.LenientAddressParser; -import org.apache.james.mime4j.stream.Field; -import org.apache.james.mime4j.util.MimeUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class HeaderCollection { - - public static class Builder { - - // Some sent e-mail have this form : Wed, 3 Jun 2015 09:05:46 +0000 (UTC) - // Java 8 Time library RFC_1123_DATE_TIME corresponds to Wed, 3 Jun 2015 09:05:46 +0000 only - // This REGEXP is here to match ( in order to remove ) the possible invalid end of a header date - // Example of matching patterns : - // (UTC) - // (CEST) - private static final Pattern DATE_SANITIZING_PATTERN = Pattern.compile(" *\\(.*\\) *"); - - private final Set<EMailer> toAddressSet; - private final Set<EMailer> fromAddressSet; - private final Set<EMailer> ccAddressSet; - private final Set<EMailer> bccAddressSet; - private final Set<EMailer> replyToAddressSet; - private final Set<String> subjectSet; - private final Multimap<String, String> headers; - private Optional<ZonedDateTime> sentDate; - - private Builder() { - toAddressSet = new HashSet<>(); - fromAddressSet = new HashSet<>(); - ccAddressSet = new HashSet<>(); - bccAddressSet = new HashSet<>(); - replyToAddressSet = new HashSet<>(); - subjectSet = new HashSet<>(); - headers = ArrayListMultimap.create(); - sentDate = Optional.empty(); - } - - public Builder add(Field field) { - Preconditions.checkNotNull(field); - String headerName = field.getName().toLowerCase(); - String headerValue = DecoderUtil.decodeEncodedWords(field.getBody(), Charsets.ISO_8859_1); - headers.put(headerName, headerValue); - handleSpecificHeader(headerName, headerValue); - return this; - } - - public HeaderCollection build() { - return new HeaderCollection( - ImmutableSet.copyOf(toAddressSet), - ImmutableSet.copyOf(fromAddressSet), - ImmutableSet.copyOf(ccAddressSet), - ImmutableSet.copyOf(bccAddressSet), - ImmutableSet.copyOf(replyToAddressSet), - ImmutableSet.copyOf(subjectSet), - ImmutableMultimap.copyOf(headers), - sentDate); - } - - private void handleSpecificHeader(String headerName, String headerValue) { - switch (headerName) { - case TO: - case FROM: - case CC: - case BCC: - case REPLY_TO: - manageAddressField(headerName, headerValue); - break; - case SUBJECT: - subjectSet.add(headerValue); - break; - case DATE: - sentDate = toISODate(headerValue); - break; - } - } - - private void manageAddressField(String headerName, String headerValue) { - LenientAddressParser.DEFAULT - .parseAddressList(MimeUtil.unfold(headerValue)) - .stream() - .flatMap(this::convertAddressToMailboxStream) - .map((mailbox) -> new EMailer(SearchUtil.getDisplayAddress(mailbox) , mailbox.getAddress())) - .collect(Collectors.toCollection(() -> getAddressSet(headerName))); - } - - private Stream<Mailbox> convertAddressToMailboxStream(Address address) { - if (address instanceof Mailbox) { - return Stream.of((Mailbox) address); - } else if (address instanceof Group) { - return ((Group) address).getMailboxes().stream(); - } - return Stream.empty(); - } - - private Set<EMailer> getAddressSet(String headerName) { - switch (headerName) { - case TO: - return toAddressSet; - case FROM: - return fromAddressSet; - case CC: - return ccAddressSet; - case BCC: - return bccAddressSet; - case REPLY_TO: - return replyToAddressSet; - } - throw new RuntimeException(headerName + " is not a address header name"); - } - - private Optional<ZonedDateTime> toISODate(String value) { - try { - return Optional.of(ZonedDateTime.parse( - sanitizeDateStringHeaderValue(value), - DateTimeFormatter.RFC_1123_DATE_TIME)); - } catch (Exception e) { - LOGGER.info("Can not parse receive date " + value); - return Optional.empty(); - } - } - - @VisibleForTesting String sanitizeDateStringHeaderValue(String value) { - // Some sent e-mail have this form : Wed, 3 Jun 2015 09:05:46 +0000 (UTC) - // Java 8 Time library RFC_1123_DATE_TIME corresponds to Wed, 3 Jun 2015 09:05:46 +0000 only - // This method is here to convert the first date into something parsable by RFC_1123_DATE_TIME DateTimeFormatter - Matcher sanitizerMatcher = DATE_SANITIZING_PATTERN.matcher(value); - if (sanitizerMatcher.find()) { - return value.substring(0 , sanitizerMatcher.start()); - } - return value; - } - - } - - public static final String TO = "to"; - public static final String FROM = "from"; - public static final String CC = "cc"; - public static final String BCC = "bcc"; - public static final String REPLY_TO = "reply-to"; - public static final String SUBJECT = "subject"; - public static final String DATE = "date"; - - private static final Logger LOGGER = LoggerFactory.getLogger(HeaderCollection.class); - - public static Builder builder() { - return new Builder(); - } - - private final ImmutableSet<EMailer> toAddressSet; - private final ImmutableSet<EMailer> fromAddressSet; - private final ImmutableSet<EMailer> ccAddressSet; - private final ImmutableSet<EMailer> bccAddressSet; - private final ImmutableSet<EMailer> replyToAddressSet; - private final ImmutableSet<String> subjectSet; - private final ImmutableMultimap<String, String> headers; - private final Optional<ZonedDateTime> sentDate; - - private HeaderCollection(ImmutableSet<EMailer> toAddressSet, ImmutableSet<EMailer> fromAddressSet, - ImmutableSet<EMailer> ccAddressSet, ImmutableSet<EMailer> bccAddressSet, ImmutableSet<EMailer> replyToAddressSet, ImmutableSet<String> subjectSet, - ImmutableMultimap<String, String> headers, Optional<ZonedDateTime> sentDate) { - this.toAddressSet = toAddressSet; - this.fromAddressSet = fromAddressSet; - this.ccAddressSet = ccAddressSet; - this.bccAddressSet = bccAddressSet; - this.replyToAddressSet = replyToAddressSet; - this.subjectSet = subjectSet; - this.headers = headers; - this.sentDate = sentDate; - } - - public Set<EMailer> getToAddressSet() { - return toAddressSet; - } - - public Set<EMailer> getFromAddressSet() { - return fromAddressSet; - } - - public Set<EMailer> getCcAddressSet() { - return ccAddressSet; - } - - public Set<EMailer> getBccAddressSet() { - return bccAddressSet; - } - - public Set<EMailer> getReplyToAddressSet() { - return replyToAddressSet; - } - - public Set<String> getSubjectSet() { - return subjectSet; - } - - public Optional<ZonedDateTime> getSentDate() { - return sentDate; - } - - public Multimap<String, String> getHeaders() { - return headers; - } - -} http://git-wip-us.apache.org/repos/asf/james-project/blob/f1116e2d/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/IndexableMessage.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/IndexableMessage.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/IndexableMessage.java deleted file mode 100644 index 972ac63..0000000 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/IndexableMessage.java +++ /dev/null @@ -1,250 +0,0 @@ -/**************************************************************** - * 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.model.message; - -import java.io.IOException; -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import org.apache.james.mailbox.store.extractor.TextExtractor; -import org.apache.james.mailbox.store.mail.model.MailboxMessage; -import org.apache.james.mailbox.store.mail.model.Property; -import org.apache.james.mime4j.MimeException; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.base.Preconditions; -import com.google.common.base.Throwables; -import com.google.common.collect.Multimap; - -public class IndexableMessage { - - public static IndexableMessage from(MailboxMessage message, TextExtractor textExtractor, ZoneId zoneId) { - Preconditions.checkNotNull(message.getMailboxId()); - IndexableMessage indexableMessage = new IndexableMessage(); - try { - MimePart parsingResult = new MimePartParser(message, textExtractor).parse(); - indexableMessage.bodyText = parsingResult.retrieveTextPlainBody(); - indexableMessage.bodyHtml = parsingResult.retrieveTextHtmlBody(); - indexableMessage.setFlattenedAttachments(parsingResult); - indexableMessage.copyHeaderFields(parsingResult.getHeaderCollection(), getSanitizedInternalDate(message, zoneId)); - indexableMessage.copyMessageFields(message, zoneId); - } catch (IOException | MimeException e) { - throw Throwables.propagate(e); - } - return indexableMessage; - } - - private void setFlattenedAttachments(MimePart parsingResult) { - attachments = parsingResult.getAttachmentsStream() - .collect(Collectors.toList()); - } - - private void copyHeaderFields(HeaderCollection headerCollection, ZonedDateTime internalDate) { - this.headers = headerCollection.getHeaders(); - this.subjects = headerCollection.getSubjectSet(); - this.from = headerCollection.getFromAddressSet(); - this.to = headerCollection.getToAddressSet(); - this.replyTo = headerCollection.getReplyToAddressSet(); - this.cc = headerCollection.getCcAddressSet(); - this.bcc = headerCollection.getBccAddressSet(); - this.sentDate = DateResolutionFormater.DATE_TIME_FOMATTER.format(headerCollection.getSentDate().orElse(internalDate)); - } - - private void copyMessageFields(MailboxMessage message, ZoneId zoneId) { - this.id = message.getUid(); - this.mailboxId = message.getMailboxId().serialize(); - this.modSeq = message.getModSeq(); - this.size = message.getFullContentOctets(); - this.date = DateResolutionFormater.DATE_TIME_FOMATTER.format(getSanitizedInternalDate(message, zoneId)); - this.isAnswered = message.isAnswered(); - this.isDeleted = message.isDeleted(); - this.isDraft = message.isDraft(); - this.isFlagged = message.isFlagged(); - this.isRecent = message.isRecent(); - this.isUnRead = ! message.isSeen(); - this.userFlags = message.createFlags().getUserFlags(); - this.properties = message.getProperties(); - } - - private static ZonedDateTime getSanitizedInternalDate(MailboxMessage message, ZoneId zoneId) { - if (message.getInternalDate() == null) { - return ZonedDateTime.now(); - } - return ZonedDateTime.ofInstant( - Instant.ofEpochMilli(message.getInternalDate().getTime()), - zoneId); - } - - private Long id; - private String mailboxId; - private long modSeq; - private long size; - private String date; - private boolean isUnRead; - private boolean isRecent; - private boolean isFlagged; - private boolean isDeleted; - private boolean isDraft; - private boolean isAnswered; - private String[] userFlags; - private Multimap<String, String> headers; - private Set<EMailer> from; - private Set<EMailer> to; - private Set<EMailer> cc; - private Set<EMailer> bcc; - private Set<EMailer> replyTo; - private Set<String> subjects; - private String sentDate; - private List<Property> properties; - private List<MimePart> attachments; - private Optional<String> bodyText; - private Optional<String> bodyHtml; - - @JsonProperty(JsonMessageConstants.ID) - public Long getId() { - return id; - } - - @JsonProperty(JsonMessageConstants.MAILBOX_ID) - public String getMailboxId() { - return mailboxId; - } - - @JsonProperty(JsonMessageConstants.MODSEQ) - public long getModSeq() { - return modSeq; - } - - @JsonProperty(JsonMessageConstants.SIZE) - public long getSize() { - return size; - } - - @JsonProperty(JsonMessageConstants.DATE) - public String getDate() { - return date; - } - - @JsonProperty(JsonMessageConstants.IS_UNREAD) - public boolean isUnRead() { - return isUnRead; - } - - @JsonProperty(JsonMessageConstants.IS_RECENT) - public boolean isRecent() { - return isRecent; - } - - @JsonProperty(JsonMessageConstants.IS_FLAGGED) - public boolean isFlagged() { - return isFlagged; - } - - @JsonProperty(JsonMessageConstants.IS_DELETED) - public boolean isDeleted() { - return isDeleted; - } - - @JsonProperty(JsonMessageConstants.IS_DRAFT) - public boolean isDraft() { - return isDraft; - } - - @JsonProperty(JsonMessageConstants.IS_ANSWERED) - public boolean isAnswered() { - return isAnswered; - } - - @JsonProperty(JsonMessageConstants.USER_FLAGS) - public String[] getUserFlags() { - return userFlags; - } - - @JsonProperty(JsonMessageConstants.HEADERS) - public Multimap<String, String> getHeaders() { - return headers; - } - - @JsonProperty(JsonMessageConstants.SUBJECT) - public Set<String> getSubjects() { - return subjects; - } - - @JsonProperty(JsonMessageConstants.FROM) - public Set<EMailer> getFrom() { - return from; - } - - @JsonProperty(JsonMessageConstants.TO) - public Set<EMailer> getTo() { - return to; - } - - @JsonProperty(JsonMessageConstants.CC) - public Set<EMailer> getCc() { - return cc; - } - - @JsonProperty(JsonMessageConstants.BCC) - public Set<EMailer> getBcc() { - return bcc; - } - - @JsonProperty(JsonMessageConstants.REPLY_TO) - public Set<EMailer> getReplyTo() { - return replyTo; - } - - @JsonProperty(JsonMessageConstants.SENT_DATE) - public String getSentDate() { - return sentDate; - } - - @JsonProperty(JsonMessageConstants.PROPERTIES) - public List<Property> getProperties() { - return properties; - } - - @JsonProperty(JsonMessageConstants.ATTACHMENTS) - public List<MimePart> getAttachments() { - return attachments; - } - - @JsonProperty(JsonMessageConstants.TEXT_BODY) - public Optional<String> getBodyText() { - return bodyText; - } - - @JsonIgnore - public Optional<String> getBodyHtml() { - return bodyHtml; - } - - @JsonProperty(JsonMessageConstants.HAS_ATTACHMENT) - public boolean getHasAttachment() { - return attachments.size() > 0; - } -} http://git-wip-us.apache.org/repos/asf/james-project/blob/f1116e2d/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/JsonMessageConstants.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/JsonMessageConstants.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/JsonMessageConstants.java deleted file mode 100644 index 3123dc2..0000000 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/JsonMessageConstants.java +++ /dev/null @@ -1,79 +0,0 @@ -/**************************************************************** - * 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.model.message; - -public interface JsonMessageConstants { - - /* - Properties defined by JMAP - */ - String ID = "id"; - String MAILBOX_ID = "mailboxId"; - String IS_UNREAD = "isUnread"; - String IS_FLAGGED = "isFlagged"; - String IS_ANSWERED = "isAnswered"; - String IS_DRAFT = "isDraft"; - String HEADERS = "headers"; - String FROM = "from"; - String TO = "to"; - String CC = "cc"; - String BCC = "bcc"; - String REPLY_TO = "replyTo"; - String SUBJECT = "subject"; - String DATE = "date"; - String SIZE = "size"; - String TEXT_BODY = "textBody"; - String SENT_DATE = "sentDate"; - String ATTACHMENTS = "attachments"; - - /* - James properties we can easily get - */ - String PROPERTIES = "properties"; - String MODSEQ = "modSeq"; - String USER_FLAGS = "userFlags"; - String IS_RECENT = "isRecent"; - String IS_DELETED = "isDeleted"; - String MEDIA_TYPE = "mediaType"; - String SUBTYPE = "subtype"; - String HAS_ATTACHMENT = "hasAttachment"; - - interface EMailer { - String NAME = "name"; - String ADDRESS = "address"; - } - - interface Attachment { - String TEXT_CONTENT = "textContent"; - String MEDIA_TYPE = "mediaType"; - String SUBTYPE = "subtype"; - String CONTENT_DISPOSITION = "contentDisposition"; - String FILENAME = "fileName"; - String FILE_EXTENSION = "fileExtension"; - String FILE_METADATA = "fileMetadata"; - } - - interface Property { - String NAMESPACE = "namespace"; - String NAME = "name"; - String VALUE = "value"; - } - -} http://git-wip-us.apache.org/repos/asf/james-project/blob/f1116e2d/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MessageUpdateJson.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MessageUpdateJson.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MessageUpdateJson.java deleted file mode 100644 index ede88a5..0000000 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MessageUpdateJson.java +++ /dev/null @@ -1,75 +0,0 @@ -/**************************************************************** - * 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.model.message; - -import javax.mail.Flags; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class MessageUpdateJson { - - private final Flags flags; - private final long modSeq; - - public MessageUpdateJson(Flags flags, long modSeq) { - this.flags = flags; - this.modSeq = modSeq; - } - - @JsonProperty(JsonMessageConstants.IS_ANSWERED) - public boolean isAnswered() { - return flags.contains(Flags.Flag.ANSWERED); - } - - @JsonProperty(JsonMessageConstants.IS_DELETED) - public boolean isDeleted() { - return flags.contains(Flags.Flag.DELETED); - } - - @JsonProperty(JsonMessageConstants.IS_DRAFT) - public boolean isDraft() { - return flags.contains(Flags.Flag.DRAFT); - } - - @JsonProperty(JsonMessageConstants.IS_FLAGGED) - public boolean isFlagged() { - return flags.contains(Flags.Flag.FLAGGED); - } - - @JsonProperty(JsonMessageConstants.IS_RECENT) - public boolean isRecent() { - return flags.contains(Flags.Flag.RECENT); - } - - @JsonProperty(JsonMessageConstants.IS_UNREAD) - public boolean isUnRead() { - return ! flags.contains(Flags.Flag.SEEN); - } - - - @JsonProperty(JsonMessageConstants.USER_FLAGS) - public String[] getUserFlags() { - return flags.getUserFlags(); - } - - @JsonProperty(JsonMessageConstants.MODSEQ) - public long getModSeq() { - return modSeq; - } - -} http://git-wip-us.apache.org/repos/asf/james-project/blob/f1116e2d/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePart.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePart.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePart.java deleted file mode 100644 index 7f573d3..0000000 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePart.java +++ /dev/null @@ -1,292 +0,0 @@ -/**************************************************************** - * 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.model.message; - -import java.io.InputStream; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; -import java.util.stream.Stream; - -import org.apache.commons.io.FilenameUtils; -import org.apache.james.mailbox.store.extractor.DefaultTextExtractor; -import org.apache.james.mailbox.store.extractor.ParsedContent; -import org.apache.james.mailbox.store.extractor.TextExtractor; -import org.apache.james.mime4j.stream.Field; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableMultimap; -import com.google.common.collect.Lists; -import com.google.common.collect.Multimap; - -public class MimePart { - - private static final String HTML_SUBTYPE = "html"; - private static final String PLAIN_SUBTYPE = "plain"; - - public static Builder builder() { - return new Builder(); - } - - public static class Builder implements MimePartContainerBuilder { - - private final HeaderCollection.Builder headerCollectionBuilder; - private Optional<InputStream> bodyContent; - private final List<MimePart> children; - private Optional<String> mediaType; - private Optional<String> subType; - private Optional<String> fileName; - private Optional<String> fileExtension; - private Optional<String> contentDisposition; - private TextExtractor textExtractor; - - private Builder() { - children = Lists.newArrayList(); - headerCollectionBuilder = HeaderCollection.builder(); - this.bodyContent = Optional.empty(); - this.mediaType = Optional.empty(); - this.subType = Optional.empty(); - this.fileName = Optional.empty(); - this.fileExtension = Optional.empty(); - this.contentDisposition = Optional.empty(); - this.textExtractor = new DefaultTextExtractor(); - } - - @Override - public Builder addToHeaders(Field field) { - headerCollectionBuilder.add(field); - return this; - } - - @Override - public Builder addBodyContent(InputStream bodyContent) { - this.bodyContent = Optional.of(bodyContent); - return this; - } - - @Override - public Builder addChild(MimePart mimePart) { - children.add(mimePart); - return this; - } - - @Override - public Builder addFileName(String fileName) { - this.fileName = Optional.ofNullable(fileName); - this.fileExtension = this.fileName.map(FilenameUtils::getExtension); - return this; - } - - @Override - public Builder addMediaType(String mediaType) { - this.mediaType = Optional.ofNullable(mediaType); - return this; - } - - @Override - public Builder addSubType(String subType) { - this.subType = Optional.ofNullable(subType); - return this; - } - - @Override - public Builder addContentDisposition(String contentDisposition) { - this.contentDisposition = Optional.ofNullable(contentDisposition); - return this; - } - - @Override - public MimePartContainerBuilder using(TextExtractor textExtractor) { - Preconditions.checkArgument(textExtractor != null, "Provided text extractor should not be null"); - this.textExtractor = textExtractor; - return this; - } - - @Override - public MimePart build() { - Optional<ParsedContent> parsedContent = parseContent(textExtractor); - return new MimePart( - headerCollectionBuilder.build(), - parsedContent.map( x -> Optional.ofNullable(x.getTextualContent())) - .orElse(Optional.empty()) - , - mediaType, - subType, - fileName, - fileExtension, - contentDisposition, - children, - parsedContent - .map(x -> x.getMetadata() - .entrySet() - .stream() - .reduce(ImmutableMultimap.<String, String>builder(), - (builder, entry) -> builder.putAll(entry.getKey(), entry.getValue()), - (builder1, builder2) -> builder1.putAll(builder2.build())).build()) - .orElse(ImmutableMultimap.of()) - ); - } - - private Optional<ParsedContent> parseContent(TextExtractor textExtractor) { - if (bodyContent.isPresent()) { - try { - return Optional.of(textExtractor.extractContent( - bodyContent.get(), - computeContentType().orElse(null), - fileName.orElse(null))); - } catch (Exception e) { - LOGGER.warn("Failed parsing attachment", e); - } - } - return Optional.empty(); - } - - private Optional<String> computeContentType() { - if (mediaType.isPresent() && subType.isPresent()) { - return Optional.of(mediaType.get() + "/" + subType.get()); - } else { - return Optional.empty(); - } - } - } - - private static final Logger LOGGER = LoggerFactory.getLogger(MimePart.class); - - private final HeaderCollection headerCollection; - private final Optional<String> bodyTextContent; - private final Optional<String> mediaType; - private final Optional<String> subType; - private final Optional<String> fileName; - private final Optional<String> fileExtension; - private final Optional<String> contentDisposition; - private final List<MimePart> attachments; - private final ImmutableMultimap<String, String> metadata; - - private MimePart(HeaderCollection headerCollection, Optional<String> bodyTextContent, Optional<String> mediaType, - Optional<String> subType, Optional<String> fileName, Optional<String> fileExtension, - Optional<String> contentDisposition, List<MimePart> attachments, Multimap<String, String> metadata) { - this.headerCollection = headerCollection; - this.mediaType = mediaType; - this.subType = subType; - this.fileName = fileName; - this.fileExtension = fileExtension; - this.contentDisposition = contentDisposition; - this.attachments = attachments; - this.bodyTextContent = bodyTextContent; - this.metadata = ImmutableMultimap.copyOf(metadata); - } - - @JsonIgnore - public List<MimePart> getAttachments() { - return attachments; - } - - @JsonIgnore - public HeaderCollection getHeaderCollection() { - return headerCollection; - } - - @JsonProperty(JsonMessageConstants.HEADERS) - public Multimap<String, String> getHeaders() { - return headerCollection.getHeaders(); - } - - @JsonProperty(JsonMessageConstants.Attachment.FILENAME) - public Optional<String> getFileName() { - return fileName; - } - - @JsonProperty(JsonMessageConstants.Attachment.FILE_EXTENSION) - public Optional<String> getFileExtension() { - return fileExtension; - } - - @JsonProperty(JsonMessageConstants.Attachment.MEDIA_TYPE) - public Optional<String> getMediaType() { - return mediaType; - } - - @JsonProperty(JsonMessageConstants.Attachment.SUBTYPE) - public Optional<String> getSubType() { - return subType; - } - - @JsonProperty(JsonMessageConstants.Attachment.CONTENT_DISPOSITION) - public Optional<String> getContentDisposition() { - return contentDisposition; - } - - @JsonProperty(JsonMessageConstants.Attachment.TEXT_CONTENT) - public Optional<String> getTextualBody() { - return bodyTextContent; - } - - @JsonProperty(JsonMessageConstants.Attachment.FILE_METADATA) - public ImmutableMultimap<String, String> getMetadata() { - return metadata; - } - - @JsonIgnore - public Optional<String> retrieveTextHtmlBody() { - return retrieveTextBody(MimePart::isHTML); - } - - @JsonIgnore - public Optional<String> retrieveTextPlainBody() { - return retrieveTextBody(MimePart::isPlain); - } - - private Optional<String> retrieveTextBody(Predicate<MimePart> filter) { - return Stream.concat( - Stream.of(this), - getAttachmentsStream()) - .filter(filter) - .map(MimePart::getTextualBody) - .filter(Optional::isPresent) - .map(Optional::get) - .findFirst(); - } - - @JsonIgnore - @VisibleForTesting boolean isHTML() { - return subType - .filter(HTML_SUBTYPE::equals) - .isPresent(); - } - - @JsonIgnore - @VisibleForTesting boolean isPlain() { - return subType - .filter(PLAIN_SUBTYPE::equals) - .isPresent(); - } - - @JsonIgnore - public Stream<MimePart> getAttachmentsStream() { - return attachments.stream() - .flatMap((mimePart) -> Stream.concat(Stream.of(mimePart), mimePart.getAttachmentsStream())); - } - -} http://git-wip-us.apache.org/repos/asf/james-project/blob/f1116e2d/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePartContainerBuilder.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePartContainerBuilder.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePartContainerBuilder.java deleted file mode 100644 index c9e671c..0000000 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePartContainerBuilder.java +++ /dev/null @@ -1,47 +0,0 @@ -/**************************************************************** - * 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.model.message; - -import org.apache.james.mailbox.store.extractor.TextExtractor; -import org.apache.james.mime4j.stream.Field; - -import java.io.InputStream; - -public interface MimePartContainerBuilder { - - MimePart build(); - - MimePartContainerBuilder using(TextExtractor textExtractor); - - MimePartContainerBuilder addToHeaders(Field field); - - MimePartContainerBuilder addBodyContent(InputStream bodyContent); - - MimePartContainerBuilder addChild(MimePart mimePart); - - MimePartContainerBuilder addFileName(String fileName); - - MimePartContainerBuilder addMediaType(String mediaType); - - MimePartContainerBuilder addSubType(String subType); - - MimePartContainerBuilder addContentDisposition(String contentDisposition); - -} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
