Author: matthieu
Date: Fri Dec 11 12:34:26 2015
New Revision: 1719389

URL: http://svn.apache.org/viewvc?rev=1719389&view=rev
Log:
JAMES-1644 Add JMAP Message objects

        duped classes from backend-elasticsearch

Added:
    
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Attachment.java
    
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Emailer.java
    
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Message.java
    
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SubMessage.java
    
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/
    
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/DateResolutionFormater.java
    
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/EMailer.java
    
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/HeaderCollection.java
    
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/IndexableMessage.java
    
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/JsonMessageConstants.java
    
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MessageUpdateJson.java
    
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePart.java
    
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePartContainerBuilder.java
    
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePartParser.java
    
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/RootMimePartContainerBuilder.java
    
james/project/trunk/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/AttachmentTest.java
    
james/project/trunk/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/EmailerTest.java
    
james/project/trunk/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageTest.java
    
james/project/trunk/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SubMessageTest.java

Added: 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Attachment.java
URL: 
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Attachment.java?rev=1719389&view=auto
==============================================================================
--- 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Attachment.java
 (added)
+++ 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Attachment.java
 Fri Dec 11 12:34:26 2015
@@ -0,0 +1,148 @@
+/****************************************************************
+ * 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;
+
+import java.util.Optional;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+
+@JsonDeserialize(builder = Attachment.Builder.class)
+public class Attachment {
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @JsonPOJOBuilder(withPrefix = "")
+    public static class Builder {
+        private String blobId;
+        private String type;
+        private String name;
+        private Long size;
+        private String cid;
+        private boolean isInline;
+        private Long width;
+        private Long height;
+
+        public Builder blobId(String blobId) {
+            this.blobId = blobId;
+            return this;
+        }
+
+        public Builder type(String type) {
+            this.type = type;
+            return this;
+        }
+
+        public Builder name(String name) {
+            this.name = name;
+            return this;
+        }
+
+        public Builder size(long size) {
+            this.size = size;
+            return this;
+        }
+
+        public Builder cid(String cid) {
+            this.cid = cid;
+            return this;
+        }
+
+        public Builder isInline(boolean isInline) {
+            this.isInline = isInline;
+            return this;
+        }
+
+        public Builder width(long width) {
+            this.width = width;
+            return this;
+        }
+
+        public Builder height(long height) {
+            this.height = height;
+            return this;
+        }
+
+        public Attachment build() {
+            Preconditions.checkState(!Strings.isNullOrEmpty(blobId), "'blobId' 
is mandatory");
+            Preconditions.checkState(!Strings.isNullOrEmpty(type), "'type' is 
mandatory");
+            Preconditions.checkState(!Strings.isNullOrEmpty(name), "'name' is 
mandatory");
+            Preconditions.checkState(size != null, "'size' is mandatory");
+            return new Attachment(blobId, type, name, size, 
Optional.ofNullable(cid), isInline, Optional.ofNullable(width), 
Optional.ofNullable(height));
+        }
+    }
+
+    private final String blobId;
+    private final String type;
+    private final String name;
+    private final long size;
+    private final Optional<String> cid;
+    private final boolean isInline;
+    private final Optional<Long> width;
+    private final Optional<Long> height;
+
+    @VisibleForTesting Attachment(String blobId, String type, String name, 
long size, Optional<String> cid, boolean isInline, Optional<Long> width, 
Optional<Long> height) {
+        this.blobId = blobId;
+        this.type = type;
+        this.name = name;
+        this.size = size;
+        this.cid = cid;
+        this.isInline = isInline;
+        this.width = width;
+        this.height = height;
+    }
+
+    public String getBlobId() {
+        return blobId;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public long getSize() {
+        return size;
+    }
+
+    public Optional<String> getCid() {
+        return cid;
+    }
+
+    public boolean isInline() {
+        return isInline;
+    }
+
+    public Optional<Long> getWidth() {
+        return width;
+    }
+
+    public Optional<Long> getHeight() {
+        return height;
+    }
+}

Added: 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Emailer.java
URL: 
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Emailer.java?rev=1719389&view=auto
==============================================================================
--- 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Emailer.java
 (added)
+++ 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Emailer.java
 Fri Dec 11 12:34:26 2015
@@ -0,0 +1,73 @@
+/****************************************************************
+ * 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;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+
+@JsonDeserialize(builder = Emailer.Builder.class)
+public class Emailer {
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @JsonPOJOBuilder(withPrefix = "")
+    public static class Builder {
+        private String name;
+        private String email;
+
+        public Builder name(String name) {
+            this.name = name;
+            return this;
+        }
+
+        public Builder email(String email) {
+            this.email = email;
+            return this;
+        }
+
+        public Emailer build() {
+            Preconditions.checkState(!Strings.isNullOrEmpty(name), "'name' is 
mandatory");
+            Preconditions.checkState(!Strings.isNullOrEmpty(email), "'email' 
is mandatory");
+            Preconditions.checkState(email.contains("@"), "'email' must 
contain '@' character");
+            return new Emailer(name, email);
+        }
+    }
+
+    private final String name;
+    private final String email;
+
+    @VisibleForTesting Emailer(String name, String email) {
+        this.name = name;
+        this.email = email;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+}

Added: 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Message.java
URL: 
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Message.java?rev=1719389&view=auto
==============================================================================
--- 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Message.java
 (added)
+++ 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Message.java
 Fri Dec 11 12:34:26 2015
@@ -0,0 +1,372 @@
+/****************************************************************
+ * 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;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+@JsonDeserialize(builder = Message.Builder.class)
+public class Message {
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @JsonPOJOBuilder(withPrefix = "")
+    public static class Builder {
+        private String id;
+        private String blobId;
+        private String threadId;
+        private ImmutableList<String> mailboxIds;
+        private String inReplyToMessageId;
+        private boolean isUnread;
+        private boolean isFlagged;
+        private boolean isAnswered;
+        private boolean isDraft;
+        private boolean hasAttachment;
+        private ImmutableMap<String, String> headers;
+        private Emailer from;
+        private ImmutableList.Builder<Emailer> to;
+        private ImmutableList.Builder<Emailer> cc;
+        private ImmutableList.Builder<Emailer> bcc;
+        private ImmutableList.Builder<Emailer> replyTo;
+        private String subject;
+        private ZonedDateTime date;
+        private Long size;
+        private String preview;
+        private String textBody;
+        private String htmlBody;
+        private ImmutableList.Builder<Attachment> attachments;
+        private ImmutableMap.Builder<String, SubMessage> attachedMessages;
+
+        private Builder() {
+            to = ImmutableList.builder();
+            cc = ImmutableList.builder();
+            bcc = ImmutableList.builder();
+            replyTo = ImmutableList.builder();
+            attachments = ImmutableList.builder();
+            attachedMessages = ImmutableMap.builder();
+        }
+
+        public Builder id(String id) {
+            this.id = id;
+            return this;
+        }
+
+        public Builder blobId(String blobId) {
+            this.blobId = blobId;
+            return this;
+        }
+
+        public Builder threadId(String threadId) {
+            this.threadId = threadId;
+            return this;
+        }
+
+        public Builder mailboxIds(ImmutableList<String> mailboxIds) {
+            this.mailboxIds = mailboxIds;
+            return this;
+        }
+
+        public Builder inReplyToMessageId(String inReplyToMessageId) {
+            this.inReplyToMessageId = inReplyToMessageId;
+            return this;
+        }
+
+        public Builder isUnread(boolean isUnread) {
+            this.isUnread = isUnread;
+            return this;
+        }
+
+        public Builder isFlagged(boolean isFlagged) {
+            this.isFlagged = isFlagged;
+            return this;
+        }
+
+        public Builder isAnswered(boolean isAnswered) {
+            this.isAnswered = isAnswered;
+            return this;
+        }
+
+        public Builder isDraft(boolean isDraft) {
+            this.isDraft = isDraft;
+            return this;
+        }
+
+        public Builder hasAttachment(boolean hasAttachment) {
+            this.hasAttachment = hasAttachment;
+            return this;
+        }
+
+        public Builder headers(ImmutableMap<String, String> headers) {
+            this.headers = headers;
+            return this;
+        }
+
+        public Builder from(Emailer from) {
+            this.from = from;
+            return this;
+        }
+
+        public Builder to(List<Emailer> to) {
+            this.to.addAll(to);
+            return this;
+        }
+
+        public Builder cc(List<Emailer> cc) {
+            this.cc.addAll(cc);
+            return this;
+        }
+
+        public Builder bcc(List<Emailer> bcc) {
+            this.bcc.addAll(bcc);
+            return this;
+        }
+
+        public Builder replyTo(List<Emailer> replyTo) {
+            this.replyTo.addAll(replyTo);
+            return this;
+        }
+
+        public Builder subject(String subject) {
+            this.subject = subject;
+            return this;
+        }
+
+        public Builder date(ZonedDateTime date) {
+            this.date = date;
+            return this;
+        }
+
+        public Builder size(long size) {
+            this.size = size;
+            return this;
+        }
+
+        public Builder preview(String preview) {
+            this.preview = preview;
+            return this;
+        }
+
+        public Builder textBody(String textBody) {
+            this.textBody = textBody;
+            return this;
+        }
+
+        public Builder htmlBody(String htmlBody) {
+            this.htmlBody = htmlBody;
+            return this;
+        }
+
+        public Builder attachments(List<Attachment> attachments) {
+            this.attachments.addAll(attachments);
+            return this;
+        }
+
+        public Builder attachedMessages(Map<String, SubMessage> 
attachedMessages) {
+            this.attachedMessages.putAll(attachedMessages);
+            return this;
+        }
+
+        public Message build() {
+            Preconditions.checkState(!Strings.isNullOrEmpty(id), "'id' is 
mandatory");
+            Preconditions.checkState(!Strings.isNullOrEmpty(blobId), "'blobId' 
is mandatory");
+            Preconditions.checkState(!Strings.isNullOrEmpty(threadId), 
"'threadId' is mandatory");
+            Preconditions.checkState(mailboxIds != null, "'mailboxIds' is 
mandatory");
+            Preconditions.checkState(headers != null, "'headers' is 
mandatory");
+            Preconditions.checkState(!Strings.isNullOrEmpty(subject), 
"'subject' is mandatory");
+            Preconditions.checkState(size != null, "'size' is mandatory");
+            Preconditions.checkState(date != null, "'date' is mandatory");
+            Preconditions.checkState(!Strings.isNullOrEmpty(preview), 
"'preview' is mandatory");
+            ImmutableList<Attachment> attachments = this.attachments.build();
+            ImmutableMap<String, SubMessage> attachedMessages = 
this.attachedMessages.build();
+            
Preconditions.checkState(areAttachedMessagesKeysInAttachments(attachments, 
attachedMessages), "'attachedMessages' keys must be in 'attachements'");
+
+            return new Message(id, blobId, threadId, mailboxIds, 
Optional.ofNullable(inReplyToMessageId), isUnread, isFlagged, isAnswered, 
isDraft, hasAttachment, headers, Optional.ofNullable(from),
+                    to.build(), cc.build(), bcc.build(), replyTo.build(), 
subject, date, size, preview, Optional.ofNullable(textBody), 
Optional.ofNullable(htmlBody), attachments, attachedMessages);
+        }
+    }
+
+    protected static boolean 
areAttachedMessagesKeysInAttachments(ImmutableList<Attachment> attachments, 
ImmutableMap<String, SubMessage> attachedMessages) {
+        return attachments.stream()
+                .map(Attachment::getBlobId)
+                .allMatch(attachedMessages::containsKey);
+    }
+
+    private final String id;
+    private final String blobId;
+    private final String threadId;
+    private final ImmutableList<String> mailboxIds;
+    private final Optional<String> inReplyToMessageId;
+    private final boolean isUnread;
+    private final boolean isFlagged;
+    private final boolean isAnswered;
+    private final boolean isDraft;
+    private final boolean hasAttachment;
+    private final ImmutableMap<String, String> headers;
+    private final Optional<Emailer> from;
+    private final ImmutableList<Emailer> to;
+    private final ImmutableList<Emailer> cc;
+    private final ImmutableList<Emailer> bcc;
+    private final ImmutableList<Emailer> replyTo;
+    private final String subject;
+    private final ZonedDateTime date;
+    private final long size;
+    private final String preview;
+    private final Optional<String> textBody;
+    private final Optional<String> htmlBody;
+    private final ImmutableList<Attachment> attachments;
+    private final ImmutableMap<String, SubMessage> attachedMessages;
+
+    @VisibleForTesting Message(String id, String blobId, String threadId, 
ImmutableList<String> mailboxIds, Optional<String> inReplyToMessageId, boolean 
isUnread, boolean isFlagged, boolean isAnswered, boolean isDraft, boolean 
hasAttachment, ImmutableMap<String, String> headers, Optional<Emailer> from,
+            ImmutableList<Emailer> to, ImmutableList<Emailer> cc, 
ImmutableList<Emailer> bcc, ImmutableList<Emailer> replyTo, String subject, 
ZonedDateTime date, long size, String preview, Optional<String> textBody, 
Optional<String> htmlBody, ImmutableList<Attachment> attachments,
+            ImmutableMap<String, SubMessage> attachedMessages) {
+        this.id = id;
+        this.blobId = blobId;
+        this.threadId = threadId;
+        this.mailboxIds = mailboxIds;
+        this.inReplyToMessageId = inReplyToMessageId;
+        this.isUnread = isUnread;
+        this.isFlagged = isFlagged;
+        this.isAnswered = isAnswered;
+        this.isDraft = isDraft;
+        this.hasAttachment = hasAttachment;
+        this.headers = headers;
+        this.from = from;
+        this.to = to;
+        this.cc = cc;
+        this.bcc = bcc;
+        this.replyTo = replyTo;
+        this.subject = subject;
+        this.date = date;
+        this.size = size;
+        this.preview = preview;
+        this.textBody = textBody;
+        this.htmlBody = htmlBody;
+        this.attachments = attachments;
+        this.attachedMessages = attachedMessages;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getBlobId() {
+        return blobId;
+    }
+
+    public String getThreadId() {
+        return threadId;
+    }
+
+    public ImmutableList<String> getMailboxIds() {
+        return mailboxIds;
+    }
+
+    public Optional<String> getInReplyToMessageId() {
+        return inReplyToMessageId;
+    }
+
+    public boolean isIsUnread() {
+        return isUnread;
+    }
+
+    public boolean isIsFlagged() {
+        return isFlagged;
+    }
+
+    public boolean isIsAnswered() {
+        return isAnswered;
+    }
+
+    public boolean isIsDraft() {
+        return isDraft;
+    }
+
+    public boolean isHasAttachment() {
+        return hasAttachment;
+    }
+
+    public ImmutableMap<String, String> getHeaders() {
+        return headers;
+    }
+
+    public Optional<Emailer> getFrom() {
+        return from;
+    }
+
+    public ImmutableList<Emailer> getTo() {
+        return to;
+    }
+
+    public ImmutableList<Emailer> getCc() {
+        return cc;
+    }
+
+    public ImmutableList<Emailer> getBcc() {
+        return bcc;
+    }
+
+    public ImmutableList<Emailer> getReplyTo() {
+        return replyTo;
+    }
+
+    public String getSubject() {
+        return subject;
+    }
+
+    public ZonedDateTime getDate() {
+        return date;
+    }
+
+    public long getSize() {
+        return size;
+    }
+
+    public String getPreview() {
+        return preview;
+    }
+
+    public Optional<String> getTextBody() {
+        return textBody;
+    }
+
+    public Optional<String> getHtmlBody() {
+        return htmlBody;
+    }
+
+    public ImmutableList<Attachment> getAttachments() {
+        return attachments;
+    }
+
+    public ImmutableMap<String, SubMessage> getAttachedMessages() {
+        return attachedMessages;
+    }
+}

Added: 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SubMessage.java
URL: 
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SubMessage.java?rev=1719389&view=auto
==============================================================================
--- 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SubMessage.java
 (added)
+++ 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SubMessage.java
 Fri Dec 11 12:34:26 2015
@@ -0,0 +1,219 @@
+/****************************************************************
+ * 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;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+@JsonDeserialize(builder = SubMessage.Builder.class)
+public class SubMessage {
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @JsonPOJOBuilder(withPrefix = "")
+    public static class Builder {
+        private ImmutableMap<String, String> headers;
+        private Emailer from;
+        private ImmutableList.Builder<Emailer> to;
+        private ImmutableList.Builder<Emailer> cc;
+        private ImmutableList.Builder<Emailer> bcc;
+        private ImmutableList.Builder<Emailer> replyTo;
+        private String subject;
+        private ZonedDateTime date;
+        private String textBody;
+        private String htmlBody;
+        private ImmutableList.Builder<Attachment> attachments;
+        private ImmutableMap.Builder<String, SubMessage> attachedMessages;
+        
+        private Builder() {
+            to = ImmutableList.builder();
+            cc = ImmutableList.builder();
+            bcc = ImmutableList.builder();
+            replyTo = ImmutableList.builder();
+            attachments = ImmutableList.builder();
+            attachedMessages = ImmutableMap.builder();
+        }
+
+        public Builder headers(ImmutableMap<String, String> headers) {
+            this.headers = headers;
+            return this;
+        }
+
+        public Builder from(Emailer from) {
+            this.from = from;
+            return this;
+        }
+
+        public Builder to(List<Emailer> to) {
+            this.to.addAll(to);
+            return this;
+        }
+
+        public Builder cc(List<Emailer> cc) {
+            this.cc.addAll(cc);
+            return this;
+        }
+
+        public Builder bcc(List<Emailer> bcc) {
+            this.bcc.addAll(bcc);
+            return this;
+        }
+
+        public Builder replyTo(List<Emailer> replyTo) {
+            this.replyTo.addAll(replyTo);
+            return this;
+        }
+
+        public Builder subject(String subject) {
+            this.subject = subject;
+            return this;
+        }
+
+        public Builder date(ZonedDateTime date) {
+            this.date = date;
+            return this;
+        }
+
+        public Builder textBody(String textBody) {
+            this.textBody = textBody;
+            return this;
+        }
+
+        public Builder htmlBody(String htmlBody) {
+            this.htmlBody = htmlBody;
+            return this;
+        }
+
+        public Builder attachments(List<Attachment> attachments) {
+            this.attachments.addAll(attachments);
+            return this;
+        }
+
+        public Builder attachedMessages(Map<String, SubMessage> 
attachedMessages) {
+            this.attachedMessages.putAll(attachedMessages);
+            return this;
+        }
+
+        public SubMessage build() {
+            Preconditions.checkState(headers != null, "'headers' is 
mandatory");
+            Preconditions.checkState(!Strings.isNullOrEmpty(subject), 
"'subject' is mandatory");
+            Preconditions.checkState(date != null, "'date' is mandatory");
+            ImmutableList<Attachment> attachments = this.attachments.build();
+            ImmutableMap<String, SubMessage> attachedMessages = 
this.attachedMessages.build();
+            
Preconditions.checkState(Message.areAttachedMessagesKeysInAttachments(attachments,
 attachedMessages), "'attachedMessages' keys must be in 'attachements'");
+            return new SubMessage(headers, Optional.ofNullable(from), 
to.build(), cc.build(), bcc.build(),
+                    replyTo.build(), subject, date, 
Optional.ofNullable(textBody), Optional.ofNullable(htmlBody),
+                    attachments, attachedMessages
+                    );
+        }
+    }
+
+    private final ImmutableMap<String, String> headers;
+    private final Optional<Emailer> from;
+    private final ImmutableList<Emailer> to;
+    private final ImmutableList<Emailer> cc;
+    private final ImmutableList<Emailer> bcc;
+    private final ImmutableList<Emailer> replyTo;
+    private final String subject;
+    private final ZonedDateTime date;
+    private final Optional<String> textBody;
+    private final Optional<String> htmlBody;
+    private final ImmutableList<Attachment> attachments;
+    private final ImmutableMap<String, SubMessage> attachedMessages;
+
+    @VisibleForTesting SubMessage(ImmutableMap<String, String> headers, 
Optional<Emailer> from, ImmutableList<Emailer> to, ImmutableList<Emailer> cc, 
ImmutableList<Emailer> bcc, ImmutableList<Emailer> replyTo, String subject, 
ZonedDateTime date, Optional<String> textBody,
+            Optional<String> htmlBody, ImmutableList<Attachment> attachments, 
ImmutableMap<String, SubMessage> attachedMessages) {
+        super();
+        this.headers = headers;
+        this.from = from;
+        this.to = to;
+        this.cc = cc;
+        this.bcc = bcc;
+        this.replyTo = replyTo;
+        this.subject = subject;
+        this.date = date;
+        this.textBody = textBody;
+        this.htmlBody = htmlBody;
+        this.attachments = attachments;
+        this.attachedMessages = attachedMessages;
+    }
+
+    public ImmutableMap<String, String> getHeaders() {
+        return headers;
+    }
+
+    public Optional<Emailer> getFrom() {
+        return from;
+    }
+
+    public ImmutableList<Emailer> getTo() {
+        return to;
+    }
+
+    public ImmutableList<Emailer> getCc() {
+        return cc;
+    }
+
+    public ImmutableList<Emailer> getBcc() {
+        return bcc;
+    }
+
+    public ImmutableList<Emailer> getReplyTo() {
+        return replyTo;
+    }
+
+    public String getSubject() {
+        return subject;
+    }
+
+    public ZonedDateTime getDate() {
+        return date;
+    }
+
+    public Optional<String> getTextBody() {
+        return textBody;
+    }
+
+    public Optional<String> getHtmlBody() {
+        return htmlBody;
+    }
+
+    public ImmutableList<Attachment> getAttachments() {
+        return attachments;
+    }
+
+    public ImmutableMap<String, SubMessage> getAttachedMessages() {
+        return attachedMessages;
+    }
+    
+    
+}

Added: 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/DateResolutionFormater.java
URL: 
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/DateResolutionFormater.java?rev=1719389&view=auto
==============================================================================
--- 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/DateResolutionFormater.java
 (added)
+++ 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/DateResolutionFormater.java
 Fri Dec 11 12:34:26 2015
@@ -0,0 +1,66 @@
+/****************************************************************
+ * 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

Added: 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/EMailer.java
URL: 
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/EMailer.java?rev=1719389&view=auto
==============================================================================
--- 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/EMailer.java
 (added)
+++ 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/EMailer.java
 Fri Dec 11 12:34:26 2015
@@ -0,0 +1,68 @@
+/****************************************************************
+ * 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 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 com.google.common.base.Objects.toStringHelper(this)
+            .add("name", name)
+            .add("address", address)
+            .toString();
+    }
+}

Added: 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/HeaderCollection.java
URL: 
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/HeaderCollection.java?rev=1719389&view=auto
==============================================================================
--- 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/HeaderCollection.java
 (added)
+++ 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/HeaderCollection.java
 Fri Dec 11 12:34:26 2015
@@ -0,0 +1,245 @@
+/****************************************************************
+ * 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.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.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 = field.getBody();
+            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 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;
+    }
+
+}

Added: 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/IndexableMessage.java
URL: 
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/IndexableMessage.java?rev=1719389&view=auto
==============================================================================
--- 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/IndexableMessage.java
 (added)
+++ 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/IndexableMessage.java
 Fri Dec 11 12:34:26 2015
@@ -0,0 +1,257 @@
+/****************************************************************
+ * 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.Preconditions;
+import com.google.common.base.Throwables;
+import com.google.common.collect.Multimap;
+import org.apache.james.mailbox.store.extractor.TextExtractor;
+import org.apache.james.mailbox.store.mail.model.MailboxId;
+import org.apache.james.mailbox.store.mail.model.Message;
+import org.apache.james.mailbox.store.mail.model.Property;
+import org.apache.james.mime4j.MimeException;
+
+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;
+
+public class IndexableMessage {
+
+    public static IndexableMessage from(Message<? extends MailboxId> message, 
TextExtractor textExtractor, ZoneId zoneId) {
+        Preconditions.checkNotNull(message.getMailboxId());
+        IndexableMessage indexableMessage = new IndexableMessage();
+        try {
+            MimePart parsingResult = new MimePartParser(message, 
textExtractor).parse();
+            indexableMessage.bodyText = parsingResult.locateFirstTextualBody();
+            indexableMessage.setFlattenedAttachments(parsingResult);
+            
indexableMessage.copyHeaderFields(parsingResult.getHeaderCollection(), 
getSanitizedInternalDate(message, zoneId));
+        } catch (IOException | MimeException e) {
+            throw Throwables.propagate(e);
+        }
+        indexableMessage.copyMessageFields(message, zoneId);
+        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(Message<? extends MailboxId> 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.mediaType = message.getMediaType();
+        this.subType = message.getSubType();
+        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(Message<? extends 
MailboxId> 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 String mediaType;
+    private String subType;
+    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;
+
+    @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.MEDIA_TYPE)
+    public String getMediaType() {
+        return mediaType;
+    }
+
+    @JsonProperty(JsonMessageConstants.SUBTYPE)
+    public String getSubType() {
+        return subType;
+    }
+
+    @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;
+    }
+
+    @JsonProperty(JsonMessageConstants.HAS_ATTACHMENT)
+    public boolean getHasAttachment() {
+        return attachments.size() > 0;
+    }
+}

Added: 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/JsonMessageConstants.java
URL: 
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/JsonMessageConstants.java?rev=1719389&view=auto
==============================================================================
--- 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/JsonMessageConstants.java
 (added)
+++ 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/JsonMessageConstants.java
 Fri Dec 11 12:34:26 2015
@@ -0,0 +1,79 @@
+/****************************************************************
+ * 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";
+    }
+
+}

Added: 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MessageUpdateJson.java
URL: 
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MessageUpdateJson.java?rev=1719389&view=auto
==============================================================================
--- 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MessageUpdateJson.java
 (added)
+++ 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MessageUpdateJson.java
 Fri Dec 11 12:34:26 2015
@@ -0,0 +1,75 @@
+/****************************************************************
+ * 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;
+    }
+
+}

Added: 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePart.java
URL: 
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePart.java?rev=1719389&view=auto
==============================================================================
--- 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePart.java
 (added)
+++ 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePart.java
 Fri Dec 11 12:34:26 2015
@@ -0,0 +1,263 @@
+/****************************************************************
+ * 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.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+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 java.io.InputStream;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+public class MimePart {
+
+    public static class Builder implements MimePartContainerBuilder {
+
+        private HeaderCollection.Builder headerCollectionBuilder;
+        private Optional<InputStream> bodyContent;
+        private 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();
+            }
+        }
+
+    }
+    
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    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> locateFirstTextualBody() {
+        return Stream.concat(
+                    Stream.of(this),
+                    attachments.stream())
+                .map((mimePart) -> mimePart.bodyTextContent)
+                .filter(Optional::isPresent)
+                .map(Optional::get)
+                .findFirst();
+    }
+
+    @JsonIgnore
+    public Stream<MimePart> getAttachmentsStream() {
+        return attachments.stream()
+                .flatMap((mimePart) -> Stream.concat(Stream.of(mimePart), 
mimePart.getAttachmentsStream()));
+    }
+
+}

Added: 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePartContainerBuilder.java
URL: 
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePartContainerBuilder.java?rev=1719389&view=auto
==============================================================================
--- 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePartContainerBuilder.java
 (added)
+++ 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePartContainerBuilder.java
 Fri Dec 11 12:34:26 2015
@@ -0,0 +1,47 @@
+/****************************************************************
+ * 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);
+
+}

Added: 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePartParser.java
URL: 
http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePartParser.java?rev=1719389&view=auto
==============================================================================
--- 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePartParser.java
 (added)
+++ 
james/project/trunk/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/message/MimePartParser.java
 Fri Dec 11 12:34:26 2015
@@ -0,0 +1,122 @@
+/****************************************************************
+ * 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.base.Preconditions;
+import org.apache.james.mailbox.store.extractor.TextExtractor;
+import org.apache.james.mailbox.store.mail.model.MailboxId;
+import org.apache.james.mailbox.store.mail.model.Message;
+import org.apache.james.mime4j.MimeException;
+import org.apache.james.mime4j.message.DefaultBodyDescriptorBuilder;
+import org.apache.james.mime4j.message.MaximalBodyDescriptor;
+import org.apache.james.mime4j.stream.EntityState;
+import org.apache.james.mime4j.stream.MimeConfig;
+import org.apache.james.mime4j.stream.MimeTokenStream;
+
+import java.io.IOException;
+import java.util.Deque;
+import java.util.LinkedList;
+
+public class MimePartParser {
+
+    private final Message<? extends MailboxId> message;
+    private final TextExtractor textExtractor;
+    private final MimeTokenStream stream;
+    private final Deque<MimePartContainerBuilder> builderStack;
+    private MimePart result;
+    private MimePartContainerBuilder currentlyBuildMimePart;
+
+    public MimePartParser(Message<? extends MailboxId> message, TextExtractor 
textExtractor) {
+        this.message = message;
+        this.textExtractor = textExtractor;
+        this.builderStack = new LinkedList<>();
+        this.currentlyBuildMimePart = new RootMimePartContainerBuilder();
+        this.stream = new MimeTokenStream(
+            MimeConfig.custom().setMaxLineLen(-1).setMaxHeaderLen(-1).build(),
+            new DefaultBodyDescriptorBuilder());
+    }
+
+    public MimePart parse() throws IOException, MimeException {
+        stream.parse(message.getFullContent());
+        for (EntityState state = stream.getState(); state != 
EntityState.T_END_OF_STREAM; state = stream.next()) {
+            processMimePart(stream, state);
+        }
+        return result;
+    }
+
+    private void processMimePart(MimeTokenStream stream, EntityState state) 
throws IOException {
+        switch (state) {
+            case T_START_MULTIPART:
+            case T_START_MESSAGE:
+                stackCurrent();
+                break;
+            case T_START_HEADER:
+                currentlyBuildMimePart = MimePart.builder();
+                break;
+            case T_FIELD:
+                currentlyBuildMimePart.addToHeaders(stream.getField());
+                break;
+            case T_BODY:
+                manageBodyExtraction(stream);
+                closeMimePart();
+                break;
+            case T_END_MULTIPART:
+            case T_END_MESSAGE:
+                unstackToCurrent();
+                closeMimePart();
+                break;
+            default:
+                break;
+        }
+    }
+
+    private void stackCurrent() {
+        builderStack.push(currentlyBuildMimePart);
+        currentlyBuildMimePart = null;
+    }
+
+    private void unstackToCurrent() {
+        currentlyBuildMimePart = builderStack.pop();
+    }
+    
+    private void closeMimePart() {
+        MimePart bodyMimePart = 
currentlyBuildMimePart.using(textExtractor).build();
+        if (!builderStack.isEmpty()) {
+            builderStack.peek().addChild(bodyMimePart);
+        } else {
+            Preconditions.checkState(result == null);
+            result = bodyMimePart;
+        }
+    }
+
+    private void manageBodyExtraction(MimeTokenStream stream) throws 
IOException {
+        extractMimePartBodyDescription(stream);
+        currentlyBuildMimePart.addBodyContent(stream.getDecodedInputStream());
+    }
+
+    private void extractMimePartBodyDescription(MimeTokenStream stream) {
+        final MaximalBodyDescriptor descriptor = (MaximalBodyDescriptor) 
stream.getBodyDescriptor();
+        currentlyBuildMimePart.addMediaType(descriptor.getMediaType())
+            .addSubType(descriptor.getSubType())
+            .addContentDisposition(descriptor.getContentDispositionType())
+            .addFileName(descriptor.getContentDispositionFilename());
+    }
+
+}



---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to