james-server-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From btell...@apache.org
Subject [10/19] james-project git commit: JAMES-2541 Implement a MimeMessageStore on top of the blob store
Date Mon, 10 Sep 2018 10:34:13 GMT
JAMES-2541 Implement a MimeMessageStore on top of the blob store


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

Branch: refs/heads/master
Commit: 6d0f22de8c20172dfe3402b085f61035fd38d6b0
Parents: 0f7bd35
Author: Benoit Tellier <btellier@linagora.com>
Authored: Thu Sep 6 12:14:12 2018 +0700
Committer: Benoit Tellier <btellier@linagora.com>
Committed: Mon Sep 10 17:17:41 2018 +0700

----------------------------------------------------------------------
 pom.xml                                         |   5 +
 server/blob/mail-store/pom.xml                  |  75 +++++++++++
 .../james/blob/mail/MimeMessageStore.java       | 133 +++++++++++++++++++
 .../james/blob/mail/MimeMessageStoreTest.java   | 130 ++++++++++++++++++
 server/blob/pom.xml                             |   2 +
 5 files changed, 345 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/6d0f22de/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 0f7898f..d59b2fc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1046,6 +1046,11 @@
                 <artifactId>blob-cassandra</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>${james.groupId}</groupId>
+                <artifactId>blob-memory</artifactId>
+                <version>${project.version}</version>
+            </dependency>
              <dependency>
                 <groupId>${james.groupId}</groupId>
                 <artifactId>event-sourcing-core</artifactId>

http://git-wip-us.apache.org/repos/asf/james-project/blob/6d0f22de/server/blob/mail-store/pom.xml
----------------------------------------------------------------------
diff --git a/server/blob/mail-store/pom.xml b/server/blob/mail-store/pom.xml
new file mode 100644
index 0000000..5622826
--- /dev/null
+++ b/server/blob/mail-store/pom.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>james-server-blob</artifactId>
+        <groupId>org.apache.james</groupId>
+        <version>3.2.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>james-server-mail-store</artifactId>
+
+    <name>Apache James :: Server :: Blob :: Mail store</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>blob-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>blob-memory</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.platform</groupId>
+            <artifactId>junit-platform-launcher</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>testcontainers</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/6d0f22de/server/blob/mail-store/src/main/java/org/apache/james/blob/mail/MimeMessageStore.java
----------------------------------------------------------------------
diff --git a/server/blob/mail-store/src/main/java/org/apache/james/blob/mail/MimeMessageStore.java
b/server/blob/mail-store/src/main/java/org/apache/james/blob/mail/MimeMessageStore.java
new file mode 100644
index 0000000..75cd973
--- /dev/null
+++ b/server/blob/mail-store/src/main/java/org/apache/james/blob/mail/MimeMessageStore.java
@@ -0,0 +1,133 @@
+/****************************************************************
+ * 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.blob.mail;
+
+import static org.apache.commons.io.output.NullOutputStream.NULL_OUTPUT_STREAM;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.SequenceInputStream;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.inject.Inject;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.james.blob.api.BlobStore;
+import org.apache.james.blob.api.Store;
+import org.apache.james.util.BodyOffsetInputStream;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+
+public class MimeMessageStore extends Store.Impl<MimeMessage> {
+    public static final BlobType HEADER_BLOB_TYPE = new BlobType("mailHeader");
+    public static final BlobType BODY_BLOB_TYPE = new BlobType("mailBody");
+
+    static class MailEncoder implements Encoder<MimeMessage> {
+        @Override
+        public Map<BlobType, InputStream> encode(MimeMessage message) {
+            try {
+                byte[] messageAsArray = messageToArray(message);
+                int bodyStartOctet = computeBodyStartOctet(messageAsArray);
+                return ImmutableMap.of(
+                    HEADER_BLOB_TYPE, new ByteArrayInputStream(getHeaderBytes(messageAsArray,
bodyStartOctet)),
+                    BODY_BLOB_TYPE, new ByteArrayInputStream(getBodyBytes(messageAsArray,
bodyStartOctet)));
+            } catch (MessagingException | IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        private static byte[] messageToArray(MimeMessage message) throws IOException, MessagingException
{
+            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+            message.writeTo(byteArrayOutputStream);
+            return byteArrayOutputStream.toByteArray();
+        }
+
+        private static byte[] getHeaderBytes(byte[] messageContentAsArray, int bodyStartOctet)
{
+            ByteBuffer headerContent = ByteBuffer.wrap(messageContentAsArray, 0, bodyStartOctet);
+            byte[] headerBytes = new byte[bodyStartOctet];
+            headerContent.get(headerBytes);
+            return headerBytes;
+        }
+
+        private static byte[] getBodyBytes(byte[] messageContentAsArray, int bodyStartOctet)
{
+            if (bodyStartOctet < messageContentAsArray.length) {
+                ByteBuffer bodyContent = ByteBuffer.wrap(messageContentAsArray,
+                    bodyStartOctet,
+                    messageContentAsArray.length - bodyStartOctet);
+                byte[] bodyBytes = new byte[messageContentAsArray.length - bodyStartOctet];
+                bodyContent.get(bodyBytes);
+                return bodyBytes;
+            } else {
+                return new byte[] {};
+            }
+        }
+
+        private static int computeBodyStartOctet(byte[] messageAsArray) throws IOException
{
+            try (BodyOffsetInputStream bodyOffsetInputStream =
+                     new BodyOffsetInputStream(new ByteArrayInputStream(messageAsArray)))
{
+                consume(bodyOffsetInputStream);
+
+                if (bodyOffsetInputStream.getBodyStartOffset() == -1) {
+                    return 0;
+                }
+                return (int) bodyOffsetInputStream.getBodyStartOffset();
+            }
+        }
+
+        private static void consume(InputStream in) throws IOException {
+            IOUtils.copy(in, NULL_OUTPUT_STREAM);
+        }
+    }
+
+    static class MailDecoder implements Decoder<MimeMessage> {
+        @Override
+        public MimeMessage decode(Map<BlobType, InputStream> streams) {
+            Preconditions.checkNotNull(streams);
+            Preconditions.checkArgument(streams.containsKey(HEADER_BLOB_TYPE));
+            Preconditions.checkArgument(streams.containsKey(BODY_BLOB_TYPE));
+
+            return toMimeMessage(
+                new SequenceInputStream(
+                    streams.get(HEADER_BLOB_TYPE),
+                    streams.get(BODY_BLOB_TYPE)));
+        }
+
+        private MimeMessage toMimeMessage(InputStream inputStream) {
+            try {
+                return new MimeMessage(Session.getInstance(new Properties()), inputStream);
+            } catch (MessagingException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    @Inject
+    public MimeMessageStore(BlobStore blobStore) {
+        super(new MailEncoder(), new MailDecoder(), blobStore);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/6d0f22de/server/blob/mail-store/src/test/java/org/apache/james/blob/mail/MimeMessageStoreTest.java
----------------------------------------------------------------------
diff --git a/server/blob/mail-store/src/test/java/org/apache/james/blob/mail/MimeMessageStoreTest.java
b/server/blob/mail-store/src/test/java/org/apache/james/blob/mail/MimeMessageStoreTest.java
new file mode 100644
index 0000000..3bc3440
--- /dev/null
+++ b/server/blob/mail-store/src/test/java/org/apache/james/blob/mail/MimeMessageStoreTest.java
@@ -0,0 +1,130 @@
+/****************************************************************
+ * 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.blob.mail;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+import javax.mail.internet.MimeMessage;
+
+import org.apache.james.blob.api.BlobId;
+import org.apache.james.blob.api.HashBlobId;
+import org.apache.james.blob.api.Store;
+import org.apache.james.blob.memory.MemoryBlobStore;
+import org.apache.james.core.builder.MimeMessageBuilder;
+import org.apache.james.util.MimeMessageUtil;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class MimeMessageStoreTest {
+    private static final HashBlobId.Factory BLOB_ID_FACTORY = new HashBlobId.Factory();
+
+    private MimeMessageStore testee;
+    private MemoryBlobStore blobStore;
+
+    @BeforeEach
+    void setUp() {
+        blobStore = new MemoryBlobStore(BLOB_ID_FACTORY);
+        testee = new MimeMessageStore(blobStore);
+    }
+
+    @Test
+    void saveShouldThrowWhenNull() {
+        assertThatThrownBy(() -> testee.save(null))
+            .isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    void readShouldThrowWhenNull() {
+        assertThatThrownBy(() -> testee.read(null))
+            .isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    void mailStoreShouldPreserveContent() throws Exception {
+        MimeMessage message = MimeMessageBuilder.mimeMessageBuilder()
+            .addFrom("any@any.com")
+            .addToRecipient("toddy@any.com")
+            .setSubject("Important Mail")
+            .setText("Important mail content")
+            .build();
+
+        Map<Store.BlobType, BlobId> parts = testee.save(message);
+
+        MimeMessage retrievedMessage = testee.read(parts);
+
+        assertThat(MimeMessageUtil.asString(retrievedMessage))
+            .isEqualTo(MimeMessageUtil.asString(message));
+    }
+
+    @Test
+    void mailStoreShouldPreserveMailWithoutBody() throws Exception {
+        MimeMessage message = MimeMessageBuilder.mimeMessageBuilder()
+            .addFrom("any@any.com")
+            .addToRecipient("toddy@any.com")
+            .setSubject("Important Mail")
+            .build();
+
+        Map<Store.BlobType, BlobId> parts = testee.save(message);
+
+        MimeMessage retrievedMessage = testee.read(parts);
+
+        assertThat(MimeMessageUtil.asString(retrievedMessage))
+            .isEqualTo(MimeMessageUtil.asString(message));
+    }
+
+    @Test
+    void saveShouldSeparateHeadersAndBodyInDifferentBlobs() throws Exception {
+        MimeMessage message = MimeMessageBuilder.mimeMessageBuilder()
+            .addHeader("Date", "Thu, 6 Sep 2018 13:29:13 +0700 (ICT)")
+            .addHeader("Message-ID", "<84739718.0.1536215353507@localhost.localdomain>")
+            .addFrom("any@any.com")
+            .addToRecipient("toddy@any.com")
+            .setSubject("Important Mail")
+            .setText("Important mail content")
+            .build();
+
+        Map<Store.BlobType, BlobId> parts = testee.save(message);
+
+        SoftAssertions.assertSoftly(
+            softly -> {
+                softly.assertThat(parts).containsKeys(MimeMessageStore.HEADER_BLOB_TYPE,
MimeMessageStore.BODY_BLOB_TYPE);
+
+                BlobId headerBlobId = parts.get(MimeMessageStore.HEADER_BLOB_TYPE);
+                BlobId bodyBlobId = parts.get(MimeMessageStore.BODY_BLOB_TYPE);
+
+                softly.assertThat(new String(blobStore.readBytes(headerBlobId).join(), StandardCharsets.UTF_8))
+                    .isEqualTo("Date: Thu, 6 Sep 2018 13:29:13 +0700 (ICT)\r\n" +
+                        "From: any@any.com\r\n" +
+                        "To: toddy@any.com\r\n" +
+                        "Message-ID: <84739718.0.1536215353507@localhost.localdomain>\r\n"
+
+                        "Subject: Important Mail\r\n" +
+                        "MIME-Version: 1.0\r\n" +
+                        "Content-Type: text/plain; charset=UTF-8\r\n" +
+                        "Content-Transfer-Encoding: 7bit\r\n\r\n");
+                softly.assertThat(new String(blobStore.readBytes(bodyBlobId).join(), StandardCharsets.UTF_8))
+                    .isEqualTo("Important mail content");
+            });
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/6d0f22de/server/blob/pom.xml
----------------------------------------------------------------------
diff --git a/server/blob/pom.xml b/server/blob/pom.xml
index ad1754d..1b674bc 100644
--- a/server/blob/pom.xml
+++ b/server/blob/pom.xml
@@ -37,6 +37,8 @@
         <module>blob-cassandra</module>
         <module>blob-memory</module>
         <module>blob-objectstorage</module>
+
+        <module>mail-store</module>
     </modules>
 
 </project>


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


Mime
View raw message