You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by ad...@apache.org on 2016/07/01 15:30:35 UTC

[1/4] james-project git commit: JAMES-1777 Retrieve attachment name in GetMessages JMAP method

Repository: james-project
Updated Branches:
  refs/heads/master bc5afa0c8 -> b6e967de7


JAMES-1777 Retrieve attachment name in GetMessages JMAP method


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/b6e967de
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/b6e967de
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/b6e967de

Branch: refs/heads/master
Commit: b6e967de70012b32186d5d9120274453dc5afa3c
Parents: 8b7cdb2
Author: Antoine Duprat <ad...@linagora.com>
Authored: Fri Jun 24 08:50:03 2016 +0200
Committer: Antoine Duprat <ad...@linagora.com>
Committed: Fri Jul 1 16:32:41 2016 +0200

----------------------------------------------------------------------
 .../src/main/java/org/apache/james/jmap/model/MessageFactory.java   | 1 +
 1 file changed, 1 insertion(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/b6e967de/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 8a5a277..b31f403 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
@@ -160,6 +160,7 @@ public class MessageFactory {
         return Attachment.builder()
                     .blobId(attachment.getAttachmentId().getId())
                     .type(attachment.getType())
+                    .name(attachment.getName().orNull())
                     .size(attachment.getSize())
                     .build();
     }


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


[3/4] james-project git commit: JAMES-1777 Parse attachment file name

Posted by ad...@apache.org.
JAMES-1777 Parse attachment file name


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/8b7cdb2a
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/8b7cdb2a
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/8b7cdb2a

Branch: refs/heads/master
Commit: 8b7cdb2acac03a85525e8cb5208579b4ab2f7e92
Parents: 9d0b3b4
Author: Antoine Duprat <ad...@linagora.com>
Authored: Thu Jun 23 22:17:09 2016 +0200
Committer: Antoine Duprat <ad...@linagora.com>
Committed: Fri Jul 1 16:32:41 2016 +0200

----------------------------------------------------------------------
 .../store/mail/model/impl/MessageParser.java    | 56 ++++++++++++--------
 .../AbstractMailboxManagerAttachmentTest.java   | 14 +++++
 .../mail/model/impl/MessageParserTest.java      | 19 +++++++
 .../resources/eml/oneAttachmentWithoutName.eml  | 38 +++++++++++++
 4 files changed, 105 insertions(+), 22 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/8b7cdb2a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/MessageParser.java
----------------------------------------------------------------------
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/MessageParser.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/MessageParser.java
index be342d8..56099ff 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/MessageParser.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/MessageParser.java
@@ -31,21 +31,19 @@ import org.apache.james.mime4j.dom.Entity;
 import org.apache.james.mime4j.dom.MessageWriter;
 import org.apache.james.mime4j.dom.Multipart;
 import org.apache.james.mime4j.dom.field.ContentDispositionField;
+import org.apache.james.mime4j.dom.field.ContentTypeField;
 import org.apache.james.mime4j.message.DefaultMessageBuilder;
 import org.apache.james.mime4j.message.DefaultMessageWriter;
 import org.apache.james.mime4j.stream.Field;
 
+import com.google.common.base.Function;
 import com.google.common.base.Optional;
-import com.google.common.base.Splitter;
-import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 
 public class MessageParser {
 
     private static final String CONTENT_TYPE = "Content-Type";
     private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
-    private static final String HEADER_SEPARATOR = ";";
 
     public List<Attachment> retrieveAttachments(InputStream fullContent) throws MimeException, IOException {
         Body body = new DefaultMessageBuilder()
@@ -67,38 +65,52 @@ public class MessageParser {
         MessageWriter messageWriter = new DefaultMessageWriter();
         for (Entity entity : multipart.getBodyParts()) {
             if (isAttachment(entity)) {
-                Optional<String> contentType = contentType(entity.getHeader().getField(CONTENT_TYPE));
-                attachments.add(createAttachment(messageWriter, entity.getBody(), contentType.or(DEFAULT_CONTENT_TYPE)));
+                Optional<ContentTypeField> contentTypeField = contentTypeField(entity.getHeader().getField(CONTENT_TYPE));
+                Optional<String> contentType = contentType(contentTypeField);
+                Optional<String> name = name(contentTypeField);
+                
+                attachments.add(Attachment.builder()
+                        .bytes(getBytes(messageWriter, entity.getBody()))
+                        .type(contentType.or(DEFAULT_CONTENT_TYPE))
+                        .name(name)
+                        .build());
             }
         }
         return attachments.build();
     }
 
-    private Optional<String> contentType(Field contentType) {
-        if (contentType == null) {
-            return Optional.absent();
-        }
-        String body = contentType.getBody();
-        if (Strings.isNullOrEmpty(body)) {
+    private Optional<String> contentType(Optional<ContentTypeField> contentTypeField) {
+        return contentTypeField.transform(new Function<ContentTypeField, Optional<String>>() {
+            @Override
+            public Optional<String> apply(ContentTypeField field) {
+                return Optional.fromNullable(field.getMimeType());
+            }
+        }).or(Optional.<String> absent());
+    }
+
+    private Optional<String> name(Optional<ContentTypeField> contentTypeField) {
+        return contentTypeField.transform(new Function<ContentTypeField, Optional<String>>() {
+            @Override
+            public Optional<String> apply(ContentTypeField field) {
+                return Optional.fromNullable(field.getParameter("name"));
+            }
+        }).or(Optional.<String> absent());
+    }
+
+    private Optional<ContentTypeField> contentTypeField(Field contentType) {
+        if (contentType == null || !(contentType instanceof ContentTypeField)) {
             return Optional.absent();
         }
-        if (body.contains(HEADER_SEPARATOR)) {
-            return Optional.of(Iterables.get(Splitter.on(HEADER_SEPARATOR).split(body), 0));
-        }
-        return Optional.of(body);
+        return Optional.of((ContentTypeField) contentType);
     }
 
     private boolean isAttachment(Entity part) {
         return ContentDispositionField.DISPOSITION_TYPE_ATTACHMENT.equalsIgnoreCase(part.getDispositionType());
     }
 
-    private Attachment createAttachment(MessageWriter messageWriter, Body body, String contentType) throws IOException {
+    private byte[] getBytes(MessageWriter messageWriter, Body body) throws IOException {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         messageWriter.writeBody(body, out);
-        byte[] bytes = out.toByteArray();
-        return Attachment.builder()
-                .bytes(bytes)
-                .type(contentType)
-                .build();
+        return out.toByteArray();
     }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/8b7cdb2a/mailbox/store/src/test/java/org/apache/james/mailbox/store/AbstractMailboxManagerAttachmentTest.java
----------------------------------------------------------------------
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/AbstractMailboxManagerAttachmentTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/AbstractMailboxManagerAttachmentTest.java
index bde8984..5f14fda 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/AbstractMailboxManagerAttachmentTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/AbstractMailboxManagerAttachmentTest.java
@@ -48,6 +48,8 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+import com.google.common.base.Optional;
+
 public abstract class AbstractMailboxManagerAttachmentTest {
     private static final String USERNAME = "user@domain.tld";
     private static final Date SUN_SEP_9TH_2001 = new Date(1000000000000L);
@@ -108,6 +110,18 @@ public abstract class AbstractMailboxManagerAttachmentTest {
     }
 
     @Test
+    public void appendMessageShouldStoreAttachmentNameWhenMailWithOneAttachment() throws Exception {
+        InputStream mailInputStream = ClassLoader.getSystemResourceAsStream("eml/oneAttachmentAndSomeInlined.eml");
+        inboxMessageManager.appendMessage(mailInputStream, SUN_SEP_9TH_2001, mailboxSession, true, new Flags(Flags.Flag.RECENT));
+
+        Optional<String> expectedName = Optional.of("exploits_of_a_mom.png");
+
+        Iterator<MailboxMessage> messages = messageMapper.findInMailbox(inbox, MessageRange.all(), FetchType.Full, 1);
+        List<AttachmentId> attachmentsIds = messages.next().getAttachmentsIds();
+        assertThat(attachmentMapper.getAttachment(attachmentsIds.get(0)).getName()).isEqualTo(expectedName);
+    }
+
+    @Test
     public void appendMessageShouldStoreARetrievableAttachmentWhenMailWithOneAttachment() throws Exception {
         InputStream mailInputStream = ClassLoader.getSystemResourceAsStream("eml/oneAttachmentAndSomeInlined.eml");
         inboxMessageManager.appendMessage(mailInputStream, SUN_SEP_9TH_2001, mailboxSession, true, new Flags(Flags.Flag.RECENT));

http://git-wip-us.apache.org/repos/asf/james-project/blob/8b7cdb2a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/MessageParserTest.java
----------------------------------------------------------------------
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/MessageParserTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/MessageParserTest.java
index ab8b88b..ca6847d 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/MessageParserTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/MessageParserTest.java
@@ -27,6 +27,8 @@ import org.apache.james.mailbox.store.mail.model.Attachment;
 import org.junit.Before;
 import org.junit.Test;
 
+import com.google.common.base.Optional;
+
 public class MessageParserTest {
 
     private MessageParser testee;
@@ -51,6 +53,23 @@ public class MessageParserTest {
     }
 
     @Test
+    public void getAttachmentsShouldRetrieveAttachmentNameWhenOne() throws Exception {
+        List<Attachment> attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneAttachmentAndSomeInlined.eml"));
+
+        assertThat(attachments).hasSize(1);
+        Optional<String> expectedName = Optional.of("exploits_of_a_mom.png");
+        assertThat(attachments.get(0).getName()).isEqualTo(expectedName);
+    }
+
+    @Test
+    public void getAttachmentsShouldRetrieveEmptyNameWhenNone() throws Exception {
+        List<Attachment> attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneAttachmentWithoutName.eml"));
+
+        assertThat(attachments).hasSize(1);
+        assertThat(attachments.get(0).getName()).isEqualTo(Optional.absent());
+    }
+
+    @Test
     public void getAttachmentsShouldNotFailWhenContentTypeIsNotHere() throws Exception {
         List<Attachment> attachments = testee.retrieveAttachments(ClassLoader.getSystemResourceAsStream("eml/oneAttachmentWithoutContentType.eml"));
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/8b7cdb2a/mailbox/store/src/test/resources/eml/oneAttachmentWithoutName.eml
----------------------------------------------------------------------
diff --git a/mailbox/store/src/test/resources/eml/oneAttachmentWithoutName.eml b/mailbox/store/src/test/resources/eml/oneAttachmentWithoutName.eml
new file mode 100644
index 0000000..b5a9aa2
--- /dev/null
+++ b/mailbox/store/src/test/resources/eml/oneAttachmentWithoutName.eml
@@ -0,0 +1,38 @@
+Mail content:
+To: "=?utf-8?B?UmFuaSBBc3NhZg==?=" <ra...@jri.obm.lng.org>
+Subject: =?utf-8?B?VHIuIDogUGhvdG9zICE=?=
+Importance: Normal
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+	boundary="----=_Part_0_1330682067197"
+
+------=_Part_0_1330682067197
+Content-Type: multipart/alternative;
+	boundary="----=_Part_2_1330682067197"
+
+------=_Part_2_1330682067197
+Content-Type: text/plain;
+	charset= utf-8
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline
+
+Content of part 1-1
+------=_Part_2_1330682067197
+Content-Type: text/html;
+	charset= utf-8
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline
+
+<b>Content of part 1-2</b>
+------=_Part_2_1330682067197--
+
+------=_Part_0_1330682067197
+Content-Type: application/octet-stream;
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment;
+	filename="gimp.png"
+
+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
+
+------=_Part_0_1330682067197--
+


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


[2/4] james-project git commit: JAMES-1777 Add name in Attachment table

Posted by ad...@apache.org.
JAMES-1777 Add name in Attachment table


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/9d0b3b41
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/9d0b3b41
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/9d0b3b41

Branch: refs/heads/master
Commit: 9d0b3b41954f08831e9feab6a0028adda1e98bb6
Parents: f643c18
Author: Antoine Duprat <ad...@linagora.com>
Authored: Thu Jun 23 21:47:28 2016 +0200
Committer: Antoine Duprat <ad...@linagora.com>
Committed: Fri Jul 1 16:32:41 2016 +0200

----------------------------------------------------------------------
 .../mail/CassandraAttachmentMapper.java         |  5 +-
 .../modules/CassandraAttachmentModule.java      |  1 +
 .../table/CassandraAttachmentTable.java         |  3 +-
 .../mailbox/store/mail/model/Attachment.java    | 47 +++++++++++------
 .../store/mail/model/impl/MessageParser.java    |  5 +-
 .../store/mail/model/AttachmentMapperTest.java  | 18 +++++--
 .../store/mail/model/AttachmentTest.java        | 54 ++++++++++++++------
 .../james/jmap/model/MailboxMessageTest.java    |  1 -
 8 files changed, 94 insertions(+), 40 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/9d0b3b41/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentMapper.java
----------------------------------------------------------------------
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentMapper.java
index c36d824..fcbfec8 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentMapper.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentMapper.java
@@ -24,6 +24,7 @@ import static com.datastax.driver.core.querybuilder.QueryBuilder.insertInto;
 import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
 import static org.apache.james.mailbox.cassandra.table.CassandraAttachmentTable.FIELDS;
 import static org.apache.james.mailbox.cassandra.table.CassandraAttachmentTable.ID;
+import static org.apache.james.mailbox.cassandra.table.CassandraAttachmentTable.NAME;
 import static org.apache.james.mailbox.cassandra.table.CassandraAttachmentTable.PAYLOAD;
 import static org.apache.james.mailbox.cassandra.table.CassandraAttachmentTable.SIZE;
 import static org.apache.james.mailbox.cassandra.table.CassandraAttachmentTable.TABLE_NAME;
@@ -46,6 +47,7 @@ import com.datastax.driver.core.Row;
 import com.datastax.driver.core.Session;
 import com.github.fge.lambdas.Throwing;
 import com.github.fge.lambdas.ThrownByLambdaException;
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 
 public class CassandraAttachmentMapper implements AttachmentMapper {
@@ -82,7 +84,7 @@ public class CassandraAttachmentMapper implements AttachmentMapper {
                 .attachmentId(AttachmentId.from(row.getString(ID)))
                 .bytes(row.getBytes(PAYLOAD).array())
                 .type(row.getString(TYPE))
-                .size(row.getLong(SIZE))
+                .name(Optional.fromNullable(row.getString(NAME)))
                 .build();
     }
 
@@ -101,6 +103,7 @@ public class CassandraAttachmentMapper implements AttachmentMapper {
                 .value(ID, attachment.getAttachmentId().getId())
                 .value(PAYLOAD, ByteBuffer.wrap(IOUtils.toByteArray(attachment.getStream())))
                 .value(TYPE, attachment.getType())
+                .value(NAME, attachment.getName().orNull())
                 .value(SIZE, attachment.getSize())
         );
     }

http://git-wip-us.apache.org/repos/asf/james-project/blob/9d0b3b41/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraAttachmentModule.java
----------------------------------------------------------------------
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraAttachmentModule.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraAttachmentModule.java
index 0108161..b26fe71 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraAttachmentModule.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraAttachmentModule.java
@@ -48,6 +48,7 @@ public class CassandraAttachmentModule implements CassandraModule {
                     .addPartitionKey(CassandraAttachmentTable.ID, text())
                     .addColumn(CassandraAttachmentTable.PAYLOAD, blob())
                     .addColumn(CassandraAttachmentTable.TYPE, text())
+                    .addColumn(CassandraAttachmentTable.NAME, text())
                     .addColumn(CassandraAttachmentTable.SIZE, bigint())));
         index = Collections.emptyList();
         types = Collections.emptyList();

http://git-wip-us.apache.org/repos/asf/james-project/blob/9d0b3b41/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraAttachmentTable.java
----------------------------------------------------------------------
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraAttachmentTable.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraAttachmentTable.java
index a7115eb..17158d1 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraAttachmentTable.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraAttachmentTable.java
@@ -25,7 +25,8 @@ public interface CassandraAttachmentTable {
     String ID = "id";
     String PAYLOAD = "payload";
     String TYPE = "type";
+    String NAME = "name";
     String SIZE = "size";
-    String[] FIELDS = { ID, PAYLOAD, TYPE, SIZE };
+    String[] FIELDS = { ID, PAYLOAD, TYPE, NAME, SIZE };
 
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/9d0b3b41/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Attachment.java
----------------------------------------------------------------------
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Attachment.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Attachment.java
index 5af2b28..6205647 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Attachment.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Attachment.java
@@ -26,6 +26,7 @@ import java.util.Arrays;
 
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Objects;
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 
@@ -35,23 +36,15 @@ public class Attachment {
         return new Builder();
     }
 
-    public static Attachment from(byte[] bytes, String type) {
-        return builder()
-                .attachmentId(AttachmentId.forPayload(bytes))
-                .bytes(bytes)
-                .type(type)
-                .size(bytes.length)
-                .build();
-    }
-
     public static class Builder {
 
         private AttachmentId attachmentId;
         private byte[] bytes;
         private String type;
-        private Long size;
+        private Optional<String> name;
 
         private Builder() {
+            name = Optional.absent();
         }
 
         public Builder attachmentId(AttachmentId attachmentId) {
@@ -72,29 +65,43 @@ public class Attachment {
             return this;
         }
 
-        public Builder size(long size) {
-            this.size = size;
+        public Builder name(Optional<String> name) {
+            Preconditions.checkArgument(name != null);
+            this.name = name;
             return this;
         }
 
         public Attachment build() {
-            Preconditions.checkState(attachmentId != null, "'attachmentId' is mandatory");
             Preconditions.checkState(bytes != null, "'bytes' is mandatory");
+            AttachmentId builtAttachmentId = attachmentId();
+            Preconditions.checkState(builtAttachmentId != null, "'attachmentId' is mandatory");
             Preconditions.checkState(type != null, "'type' is mandatory");
-            Preconditions.checkState(size != null, "'size' is mandatory");
-            return new Attachment(bytes, attachmentId, type, size);
+            return new Attachment(bytes, builtAttachmentId, type, name, size());
+        }
+
+        private AttachmentId attachmentId() {
+            if (attachmentId != null) {
+                return attachmentId;
+            }
+            return AttachmentId.forPayload(bytes);
+        }
+
+        private long size() {
+            return bytes.length;
         }
     }
 
     private final byte[] bytes;
     private final AttachmentId attachmentId;
     private final String type;
+    private final Optional<String> name;
     private final long size;
 
-    private Attachment(byte[] bytes, AttachmentId attachmentId, String type, long size) {
+    private Attachment(byte[] bytes, AttachmentId attachmentId, String type, Optional<String> name, long size) {
         this.bytes = bytes;
         this.attachmentId = attachmentId;
         this.type = type;
+        this.name = name;
         this.size = size;
     }
 
@@ -106,6 +113,10 @@ public class Attachment {
         return type;
     }
 
+    public Optional<String> getName() {
+        return name;
+    }
+
     public long getSize() {
         return size;
     }
@@ -121,6 +132,7 @@ public class Attachment {
             return Objects.equal(attachmentId, other.attachmentId)
                 && Arrays.equals(bytes, other.bytes)
                 && Objects.equal(type, other.type)
+                && Objects.equal(name, other.name)
                 && Objects.equal(size, other.size);
         }
         return false;
@@ -128,7 +140,7 @@ public class Attachment {
 
     @Override
     public int hashCode() {
-        return Objects.hashCode(attachmentId, bytes, type, size);
+        return Objects.hashCode(attachmentId, bytes, type, name, size);
     }
 
     @Override
@@ -138,6 +150,7 @@ public class Attachment {
                 .add("attachmentId", attachmentId)
                 .add("bytes", bytes)
                 .add("type", type)
+                .add("name", name)
                 .add("size", size)
                 .toString();
     }

http://git-wip-us.apache.org/repos/asf/james-project/blob/9d0b3b41/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/MessageParser.java
----------------------------------------------------------------------
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/MessageParser.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/MessageParser.java
index c784474..be342d8 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/MessageParser.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/MessageParser.java
@@ -96,6 +96,9 @@ public class MessageParser {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         messageWriter.writeBody(body, out);
         byte[] bytes = out.toByteArray();
-        return Attachment.from(bytes, contentType);
+        return Attachment.builder()
+                .bytes(bytes)
+                .type(contentType)
+                .build();
     }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/9d0b3b41/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/AttachmentMapperTest.java
----------------------------------------------------------------------
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/AttachmentMapperTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/AttachmentMapperTest.java
index 0fbfe1a..39b26e6 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/AttachmentMapperTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/AttachmentMapperTest.java
@@ -31,6 +31,7 @@ import org.xenei.junit.contract.Contract;
 import org.xenei.junit.contract.ContractTest;
 import org.xenei.junit.contract.IProducer;
 
+import com.google.common.base.Charsets;
 import com.google.common.collect.ImmutableList;
 
 @Contract(MapperProvider.class)
@@ -62,13 +63,16 @@ public class AttachmentMapperTest<T extends MapperProvider> {
     @ContractTest
     public void getAttachmentShouldThrowWhenNonReferencedAttachmentId() throws Exception {
         expected.expect(AttachmentNotFoundException.class);
-        attachmentMapper.getAttachment(AttachmentId.forPayload("unknown".getBytes()));
+        attachmentMapper.getAttachment(AttachmentId.forPayload("unknown".getBytes(Charsets.UTF_8)));
     }
 
     @ContractTest
     public void getAttachmentShouldReturnTheAttachmentWhenReferenced() throws Exception {
         //Given
-        Attachment expected = Attachment.from("payload".getBytes(), "content");
+        Attachment expected = Attachment.builder()
+                .bytes("payload".getBytes(Charsets.UTF_8))
+                .type("content")
+                .build();
         AttachmentId attachmentId = expected.getAttachmentId();
         attachmentMapper.storeAttachment(expected);
         //When
@@ -80,8 +84,14 @@ public class AttachmentMapperTest<T extends MapperProvider> {
     @ContractTest
     public void getAttachmentShouldReturnTheAttachmentsWhenMultipleStored() throws Exception {
         //Given
-        Attachment expected1 = Attachment.from("payload1".getBytes(), "content1");
-        Attachment expected2 = Attachment.from("payload2".getBytes(), "content2");
+        Attachment expected1 = Attachment.builder()
+                .bytes("payload1".getBytes(Charsets.UTF_8))
+                .type("content1")
+                .build();
+        Attachment expected2 = Attachment.builder()
+                .bytes("payload2".getBytes(Charsets.UTF_8))
+                .type("content2")
+                .build();
         AttachmentId attachmentId1 = expected1.getAttachmentId();
         AttachmentId attachmentId2 = expected2.getAttachmentId();
         //When

http://git-wip-us.apache.org/repos/asf/james-project/blob/9d0b3b41/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/AttachmentTest.java
----------------------------------------------------------------------
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/AttachmentTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/AttachmentTest.java
index 5e96c7d..f3505f4 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/AttachmentTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/AttachmentTest.java
@@ -27,12 +27,17 @@ import java.io.InputStream;
 import org.apache.commons.io.IOUtils;
 import org.junit.Test;
 
+import com.google.common.base.Optional;
+
 public class AttachmentTest {
 
     @Test
     public void streamShouldBeConsumedOneTime() throws Exception {
         String input = "mystream";
-        Attachment attachment = Attachment.from(input.getBytes(), "content");
+        Attachment attachment = Attachment.builder()
+                .bytes(input.getBytes())
+                .type("content")
+                .build();
 
         InputStream stream = attachment.getStream();
         assertThat(stream).isNotNull();
@@ -42,7 +47,10 @@ public class AttachmentTest {
     @Test
     public void streamShouldBeConsumedMoreThanOneTime() throws Exception {
         String input = "mystream";
-        Attachment attachment = Attachment.from(input.getBytes(), "content");
+        Attachment attachment = Attachment.builder()
+                .bytes(input.getBytes())
+                .type("content")
+                .build();
 
         attachment.getStream();
         InputStream stream = attachment.getStream();
@@ -74,6 +82,12 @@ public class AttachmentTest {
             .type("");
     }
 
+    @Test (expected = IllegalArgumentException.class)
+    public void builderShouldThrowWhenNameIsNull() {
+        Attachment.builder()
+            .name(null);
+    }
+
     @Test (expected = IllegalStateException.class)
     public void buildShouldThrowWhenAttachmentIdIsNotProvided() {
         Attachment.builder().build();
@@ -94,29 +108,39 @@ public class AttachmentTest {
             .build();
     }
 
-    @Test (expected = IllegalStateException.class)
-    public void buildShouldThrowWhenSizeIsNotProvided() {
-        Attachment.builder()
-            .attachmentId(AttachmentId.forPayload("mystream".getBytes()))
-            .bytes("mystream".getBytes())
-            .type("content")
-            .build();
-    }
-
     @Test
-    public void fromShouldSetTheAttachmentId() throws Exception {
+    public void buildShouldSetTheAttachmentId() throws Exception {
         byte[] bytes = "mystream".getBytes();
-        Attachment attachment = Attachment.from(bytes, "content");
+        Attachment attachment = Attachment.builder()
+                .bytes(bytes)
+                .type("content")
+                .build();
         AttachmentId expected = AttachmentId.forPayload(bytes);
 
         assertThat(attachment.getAttachmentId()).isEqualTo(expected);
     }
 
     @Test
-    public void fromShouldSetTheSize() throws Exception {
+    public void buildShouldSetTheSize() throws Exception {
         String input = "mystream";
-        Attachment attachment = Attachment.from(input.getBytes(), "content");
+        Attachment attachment = Attachment.builder()
+                .bytes(input.getBytes())
+                .type("content")
+                .build();
 
         assertThat(attachment.getSize()).isEqualTo(input.getBytes().length);
     }
+
+    @Test
+    public void buildShouldSetTheName() throws Exception {
+        String input = "mystream";
+        Optional<String> expectedName = Optional.of("myName");
+        Attachment attachment = Attachment.builder()
+                .bytes(input.getBytes())
+                .type("content")
+                .name(expectedName)
+                .build();
+
+        assertThat(attachment.getName()).isEqualTo(expectedName);
+    }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/9d0b3b41/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MailboxMessageTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MailboxMessageTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MailboxMessageTest.java
index f356460..eae0c49 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MailboxMessageTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MailboxMessageTest.java
@@ -422,7 +422,6 @@ public class MailboxMessageTest {
                         .attachmentId(AttachmentId.from(blodId))
                         .bytes(payload.getBytes())
                         .type(type)
-                        .size(payload.length())
                         .build()), 
                 x -> MessageId.of("user|box|" + x));
 


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


[4/4] james-project git commit: JAMES-1777 Add name variable in download endpoint

Posted by ad...@apache.org.
JAMES-1777 Add name variable in download endpoint


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/f643c184
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/f643c184
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/f643c184

Branch: refs/heads/master
Commit: f643c184bfba52c003d70265063063aec5995b9d
Parents: bc5afa0
Author: Antoine Duprat <ad...@linagora.com>
Authored: Thu Jun 23 14:26:01 2016 +0200
Committer: Antoine Duprat <ad...@linagora.com>
Committed: Fri Jul 1 16:32:41 2016 +0200

----------------------------------------------------------------------
 .../integration/cucumber/DownloadStepdefs.java  | 30 +++++--
 .../test/resources/cucumber/DownloadGet.feature |  6 ++
 .../org/apache/james/jmap/DownloadServlet.java  | 35 ++++----
 .../apache/james/jmap/utils/DownloadPath.java   | 65 +++++++++++++++
 .../apache/james/jmap/DownloadServletTest.java  | 13 +--
 .../james/jmap/utils/DownloadPathTest.java      | 84 ++++++++++++++++++++
 6 files changed, 202 insertions(+), 31 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/f643c184/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/DownloadStepdefs.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/DownloadStepdefs.java b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/DownloadStepdefs.java
index 74b2d49..e675d10 100644
--- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/DownloadStepdefs.java
+++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/DownloadStepdefs.java
@@ -38,6 +38,7 @@ import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Multimap;
 import com.jayway.restassured.http.ContentType;
 import com.jayway.restassured.response.Response;
+import com.jayway.restassured.response.ValidatableResponse;
 import com.jayway.restassured.specification.RequestSpecification;
 
 import cucumber.api.java.en.Given;
@@ -53,6 +54,7 @@ public class DownloadStepdefs {
     private Response response;
     private Multimap<String, String> attachmentsByMessageId;
     private Map<String, String> blobIdByAttachmentId;
+    private ValidatableResponse validatableResponse;
 
     @Inject
     private DownloadStepdefs(MainStepdefs mainStepdefs, UserStepdefs userStepdefs) {
@@ -125,33 +127,44 @@ public class DownloadStepdefs {
                 .post("/download/" + blobId);
     }
 
+    @When("^\"([^\"]*)\" downloads \"([^\"]*)\" with \"([^\"]*)\" name$")
+    public void downloadsWithName(String username, String attachmentId, String name) {
+        String blobId = blobIdByAttachmentId.get(attachmentId);
+        AccessToken accessToken = userStepdefs.tokenByUser.get(username);
+        RequestSpecification with = with();
+        if (accessToken != null) {
+            with.header("Authorization", accessToken.serialize());
+        }
+        response = with.get("/download/" + blobId + "/" + name);
+    }
+
     @Then("^the user should be authorized$")
-    public void httpStatusDifferentFromUnauthorized() throws Exception {
+    public void httpStatusDifferentFromUnauthorized() {
         response.then()
             .statusCode(not(401));
     }
 
     @Then("^the user should not be authorized$")
-    public void httpUnauthorizedStatus() throws Exception {
+    public void httpUnauthorizedStatus() {
         response.then()
             .statusCode(401);
     }
 
     @Then("^the user should receive a bad request response$")
-    public void httpBadRequestStatus() throws Throwable {
+    public void httpBadRequestStatus() {
         response.then()
             .statusCode(400);
     }
 
     @Then("^the user should receive that attachment$")
-    public void httpOkStatusAndExpectedContent() throws Throwable {
-        response.then()
+    public void httpOkStatusAndExpectedContent() {
+        validatableResponse = response.then()
             .statusCode(200)
             .content(notNullValue());
     }
 
     @Then("^the user should receive a not found response$")
-    public void httpNotFoundStatus() throws Throwable {
+    public void httpNotFoundStatus() {
         response.then()
             .statusCode(404);
     }
@@ -163,4 +176,9 @@ public class DownloadStepdefs {
             .contentType(ContentType.TEXT)
             .content(notNullValue());
     }
+
+    @Then("^the attachment is named \"([^\"]*)\"$")
+    public void assertContentDisposition(String name) {
+        validatableResponse.header("Content-Disposition", name);
+    }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/f643c184/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/DownloadGet.feature
----------------------------------------------------------------------
diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/DownloadGet.feature b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/DownloadGet.feature
index 7b02ee8..bf3d21a 100644
--- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/DownloadGet.feature
+++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/DownloadGet.feature
@@ -15,3 +15,9 @@ Feature: Download GET
   Scenario: Getting an attachment with an unknown blobId
     When "username@domain.tld" downloads "123"
     Then the user should receive a not found response
+
+  Scenario: Getting an attachment previously stored with a desired name
+    Given "username@domain.tld" mailbox "inbox" contains a message "1" with an attachment "2"
+    When "username@domain.tld" downloads "2" with "myFileName.txt" name
+    Then the user should receive that attachment
+    And the attachment is named "myFileName.txt"

http://git-wip-us.apache.org/repos/asf/james-project/blob/f643c184/server/protocols/jmap/src/main/java/org/apache/james/jmap/DownloadServlet.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/DownloadServlet.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/DownloadServlet.java
index ccd8d86..49d0c79 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/DownloadServlet.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/DownloadServlet.java
@@ -24,6 +24,7 @@ import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
 import static javax.servlet.http.HttpServletResponse.SC_OK;
 
 import java.io.IOException;
+import java.util.Optional;
 
 import javax.inject.Inject;
 import javax.servlet.ServletException;
@@ -33,6 +34,7 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.james.jmap.api.SimpleTokenFactory;
+import org.apache.james.jmap.utils.DownloadPath;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.exception.AttachmentNotFoundException;
 import org.apache.james.mailbox.exception.MailboxException;
@@ -44,11 +46,9 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Strings;
 
 public class DownloadServlet extends HttpServlet {
 
-    private static final String ROOT_URL = "/";
     private static final Logger LOGGER = LoggerFactory.getLogger(DownloadServlet.class);
     private static final String TEXT_PLAIN_CONTENT_TYPE = "text/plain";
 
@@ -64,14 +64,16 @@ public class DownloadServlet extends HttpServlet {
     @Override
     protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
         String pathInfo = req.getPathInfo();
-        if (Strings.isNullOrEmpty(pathInfo) || pathInfo.equals(ROOT_URL)) {
+        try {
+            respondAttachmentAccessToken(getMailboxSession(req), DownloadPath.from(pathInfo), resp);
+        } catch (IllegalArgumentException e) {
+            LOGGER.error(String.format("Error while generating attachment access token '%s'", pathInfo), e);
             resp.setStatus(SC_BAD_REQUEST);
-        } else {
-            respondAttachmentAccessToken(getMailboxSession(req), blobIdFrom(pathInfo), resp);
         }
     }
 
-    private void respondAttachmentAccessToken(MailboxSession mailboxSession, String blobId, HttpServletResponse resp) {
+    private void respondAttachmentAccessToken(MailboxSession mailboxSession, DownloadPath downloadPath, HttpServletResponse resp) {
+        String blobId = downloadPath.getBlobId();
         try {
             if (! attachmentExists(mailboxSession, blobId)) {
                 resp.setStatus(SC_NOT_FOUND);
@@ -99,22 +101,23 @@ public class DownloadServlet extends HttpServlet {
     @Override
     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
         String pathInfo = req.getPathInfo();
-        if (Strings.isNullOrEmpty(pathInfo) || pathInfo.equals(ROOT_URL)) {
+        try {
+            download(getMailboxSession(req), DownloadPath.from(pathInfo), resp);
+        } catch (IllegalArgumentException e) {
+            LOGGER.error(String.format("Error while downloading '%s'", pathInfo), e);
             resp.setStatus(SC_BAD_REQUEST);
-        } else {
-            download(getMailboxSession(req), blobIdFrom(pathInfo), resp);
         }
     }
 
-    @VisibleForTesting String blobIdFrom(String pathInfo) {
-        return pathInfo.substring(1);
-    }
-
-    @VisibleForTesting void download(MailboxSession mailboxSession, String blobId, HttpServletResponse resp) {
+    @VisibleForTesting void download(MailboxSession mailboxSession, DownloadPath downloadPath, HttpServletResponse resp) {
+        String blobId = downloadPath.getBlobId();
         try {
+            addContentDispositionHeader(downloadPath.getName(), resp);
+
             AttachmentMapper attachmentMapper = mailboxSessionMapperFactory.createAttachmentMapper(mailboxSession);
             Attachment attachment = attachmentMapper.getAttachment(AttachmentId.from(blobId));
             IOUtils.copy(attachment.getStream(), resp.getOutputStream());
+
             resp.setStatus(SC_OK);
         } catch (AttachmentNotFoundException e) {
             LOGGER.info(String.format("Attachment '%s' not found", blobId), e);
@@ -125,6 +128,10 @@ public class DownloadServlet extends HttpServlet {
         }
     }
 
+    private void addContentDispositionHeader(Optional<String> optionalName, HttpServletResponse resp) {
+        optionalName.ifPresent(name -> resp.addHeader("Content-Disposition", name));
+    }
+
     private MailboxSession getMailboxSession(HttpServletRequest req) {
         return (MailboxSession) req.getAttribute(AuthenticationFilter.MAILBOX_SESSION);
     }

http://git-wip-us.apache.org/repos/asf/james-project/blob/f643c184/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/DownloadPath.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/DownloadPath.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/DownloadPath.java
new file mode 100644
index 0000000..3755e8a
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/DownloadPath.java
@@ -0,0 +1,65 @@
+/****************************************************************
+ * 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.utils;
+
+import java.util.List;
+import java.util.Optional;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+
+public class DownloadPath {
+
+    public static DownloadPath from(String path) {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(path), "'path' is mandatory");
+
+        // path =  /blobId/name
+        // idx  = 0 1      2
+        List<String> pathVariables = Splitter.on('/').splitToList(path);
+        Preconditions.checkArgument(pathVariables.size() >= 1 && pathVariables.size() <= 3, "'blobId' is mandatory");
+
+        String blobId = Iterables.get(pathVariables, 1, null);
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(blobId), "'blobId' is mandatory");
+
+        return new DownloadPath(blobId, name(pathVariables));
+    }
+
+    private static Optional<String> name(List<String> pathVariables) {
+        return Optional.ofNullable(Strings.emptyToNull(Iterables.get(pathVariables, 2, null)));
+    }
+
+    private final String blobId;
+    private final Optional<String> name;
+
+    private DownloadPath(String blobId, Optional<String> name) {
+        this.blobId = blobId;
+        this.name = name;
+    }
+
+    public String getBlobId() {
+        return blobId;
+    }
+
+    public Optional<String> getName() {
+        return name;
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/f643c184/server/protocols/jmap/src/test/java/org/apache/james/jmap/DownloadServletTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/DownloadServletTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/DownloadServletTest.java
index de9058b..79302b7 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/DownloadServletTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/DownloadServletTest.java
@@ -19,7 +19,6 @@
 
 package org.apache.james.jmap;
 
-import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -27,6 +26,7 @@ import static org.mockito.Mockito.when;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.james.jmap.api.SimpleTokenFactory;
+import org.apache.james.jmap.utils.DownloadPath;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.store.MailboxSessionMapperFactory;
@@ -35,14 +35,6 @@ import org.junit.Test;
 public class DownloadServletTest {
 
     @Test
-    public void blobIdFromShouldSkipTheFirstCharacter() {
-        MailboxSessionMapperFactory nullMailboxSessionMapperFactory = null;
-        SimpleTokenFactory nullSimpleTokenFactory = null;
-        String blobId = new DownloadServlet(nullMailboxSessionMapperFactory, nullSimpleTokenFactory).blobIdFrom("1234");
-        assertThat(blobId).isEqualTo("234");
-    }
-
-    @Test
     public void downloadMayFailWhenUnableToCreateAttachmentMapper() throws Exception {
         MailboxSession mailboxSession = mock(MailboxSession.class);
         MailboxSessionMapperFactory mailboxSessionMapperFactory = mock(MailboxSessionMapperFactory.class);
@@ -52,9 +44,8 @@ public class DownloadServletTest {
 
         DownloadServlet testee = new DownloadServlet(mailboxSessionMapperFactory, nullSimpleTokenFactory);
 
-        String blobId = null;
         HttpServletResponse resp = mock(HttpServletResponse.class);
-        testee.download(mailboxSession, blobId, resp);
+        testee.download(mailboxSession, DownloadPath.from("/blobId"), resp);
 
         verify(resp).setStatus(500);
     }

http://git-wip-us.apache.org/repos/asf/james-project/blob/f643c184/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/DownloadPathTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/DownloadPathTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/DownloadPathTest.java
new file mode 100644
index 0000000..53eff13
--- /dev/null
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/DownloadPathTest.java
@@ -0,0 +1,84 @@
+/****************************************************************
+ * 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.utils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.junit.Test;
+
+public class DownloadPathTest {
+
+    @Test
+    public void fromShouldThrowWhenPathIsNull() {
+        assertThatThrownBy(()-> DownloadPath.from(null)).isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void fromShouldThrowWhenPathIsEmpty() {
+        assertThatThrownBy(()-> DownloadPath.from("")).isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void fromShouldThrowWhenNoBlobId() {
+        assertThatThrownBy(()-> DownloadPath.from("/")).isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void fromShouldThrowWhenBlobIdIsEmpty() {
+        assertThatThrownBy(()-> DownloadPath.from("//")).isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void fromShouldParseWhenBlobId() {
+        String expectedBlobId = "123456789";
+        DownloadPath downloadPath = DownloadPath.from("/" + expectedBlobId);
+        assertThat(downloadPath.getBlobId()).isEqualTo(expectedBlobId);
+    }
+
+    @Test
+    public void nameShouldBeEmptyWhenNone() {
+        DownloadPath downloadPath = DownloadPath.from("/123456789");
+        assertThat(downloadPath.getName()).isEmpty();
+    }
+
+    @Test
+    public void nameShouldBeEmptyWhenNoneButSlash() {
+        DownloadPath downloadPath = DownloadPath.from("/123456789/");
+        assertThat(downloadPath.getName()).isEmpty();
+    }
+
+    @Test
+    public void nameShouldBePresentWhenGiven() {
+        String expectedName = "myName";
+        DownloadPath downloadPath = DownloadPath.from("/123456789/" + expectedName);
+        assertThat(downloadPath.getName()).hasValue(expectedName);
+    }
+
+    @Test
+    public void fromShouldThrowWhenExtraPathVariables() {
+        assertThatThrownBy(()-> DownloadPath.from("/123456789/myName/132/456/789")).isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void fromShouldThrowWhenExtraPathSeparator() {
+        assertThatThrownBy(()-> DownloadPath.from("/123456789//myName")).isInstanceOf(IllegalArgumentException.class);
+    }
+}


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