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 bt...@apache.org on 2015/06/29 10:25:02 UTC

svn commit: r1688115 - in /james/mailbox/trunk/elasticsearch: ./ src/main/java/org/apache/james/mailbox/elasticsearch/json/

Author: btellier
Date: Mon Jun 29 08:25:02 2015
New Revision: 1688115

URL: http://svn.apache.org/r1688115
Log:
MAILBOX-234 Provide mime parsing utilities - Patch I contributed with the help of Matthieu Baechlor

Added:
    james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/json/MimePart.java
    james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/json/MimePartContainerBuilder.java
    james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/json/MimePartParser.java
    james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/json/RootMimePartContainerBuilder.java
Modified:
    james/mailbox/trunk/elasticsearch/pom.xml

Modified: james/mailbox/trunk/elasticsearch/pom.xml
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/elasticsearch/pom.xml?rev=1688115&r1=1688114&r2=1688115&view=diff
==============================================================================
--- james/mailbox/trunk/elasticsearch/pom.xml (original)
+++ james/mailbox/trunk/elasticsearch/pom.xml Mon Jun 29 08:25:02 2015
@@ -45,6 +45,12 @@
             <groupId>${project.groupId}</groupId>
             <artifactId>apache-james-mailbox-store</artifactId>
         </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>apache-james-mailbox-store</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
 
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>

Added: james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/json/MimePart.java
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/json/MimePart.java?rev=1688115&view=auto
==============================================================================
--- james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/json/MimePart.java (added)
+++ james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/json/MimePart.java Mon Jun 29 08:25:02 2015
@@ -0,0 +1,223 @@
+/****************************************************************
+ * 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.mailbox.elasticsearch.json;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.james.mime4j.stream.Field;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+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 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();
+        }
+
+        @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 MimePart build() {
+            return new MimePart(
+                    headerCollectionBuilder.build(),
+                    decodeContent(),
+                    mediaType,
+                    subType,
+                    fileName,
+                    fileExtension,
+                    contentDisposition,
+                    children
+            );
+        }
+
+        private boolean isTextualMimePart() {
+            return mediaType.isPresent()
+                && mediaType.get().equalsIgnoreCase("text");
+        }
+
+        private Optional<String> decodeContent() {
+            if (bodyContent.isPresent() && isTextualMimePart()) {
+                try {
+                    return Optional.of(IOUtils.toString(bodyContent.get()));
+                } catch (IOException e) {
+                    LOGGER.warn("Can not decode body content", e);
+                }
+            }
+            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 MimePart(HeaderCollection headerCollection, Optional<String> bodyTextContent, Optional<String> mediaType,
+                    Optional<String> subType, Optional<String> fileName, Optional<String> fileExtension,
+                    Optional<String> contentDisposition, List<MimePart> attachments) {
+        this.headerCollection = headerCollection;
+        this.mediaType = mediaType;
+        this.subType = subType;
+        this.fileName = fileName;
+        this.fileExtension = fileExtension;
+        this.contentDisposition = contentDisposition;
+        this.attachments = attachments;
+        this.bodyTextContent = bodyTextContent;
+    }
+
+    @JsonIgnore
+    public List<MimePart> getAttachments() {
+        return attachments;
+    }
+
+    @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;
+    }
+
+    @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/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/json/MimePartContainerBuilder.java
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/json/MimePartContainerBuilder.java?rev=1688115&view=auto
==============================================================================
--- james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/json/MimePartContainerBuilder.java (added)
+++ james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/json/MimePartContainerBuilder.java Mon Jun 29 08:25:02 2015
@@ -0,0 +1,44 @@
+/****************************************************************
+ * 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.mailbox.elasticsearch.json;
+
+import org.apache.james.mime4j.stream.Field;
+
+import java.io.InputStream;
+
+public interface MimePartContainerBuilder {
+
+    MimePart build();
+
+    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/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/json/MimePartParser.java
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/json/MimePartParser.java?rev=1688115&view=auto
==============================================================================
--- james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/json/MimePartParser.java (added)
+++ james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/json/MimePartParser.java Mon Jun 29 08:25:02 2015
@@ -0,0 +1,119 @@
+/****************************************************************
+ * 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.mailbox.elasticsearch.json;
+
+import com.google.common.base.Preconditions;
+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 MimeTokenStream stream;
+    private final Deque<MimePartContainerBuilder> builderStack;
+    private MimePart result;
+    private MimePartContainerBuilder currentlyBuildMimePart;
+
+    public MimePartParser(Message<? extends MailboxId> message) {
+        this.message = message;
+        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.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());
+    }
+
+}

Added: james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/json/RootMimePartContainerBuilder.java
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/json/RootMimePartContainerBuilder.java?rev=1688115&view=auto
==============================================================================
--- james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/json/RootMimePartContainerBuilder.java (added)
+++ james/mailbox/trunk/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/json/RootMimePartContainerBuilder.java Mon Jun 29 08:25:02 2015
@@ -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.mailbox.elasticsearch.json;
+
+import org.apache.james.mime4j.stream.Field;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+
+public class RootMimePartContainerBuilder implements MimePartContainerBuilder {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(RootMimePartContainerBuilder.class);
+
+    private MimePart rootMimePart;
+
+    @Override
+    public MimePart build() {
+        return rootMimePart;
+    }
+
+    @Override
+    public MimePartContainerBuilder addToHeaders(Field field) {
+        LOGGER.warn("Trying to add headers to the Root MimePart container");
+        return this;
+    }
+
+    @Override
+    public MimePartContainerBuilder addBodyContent(InputStream bodyContent) {
+        LOGGER.warn("Trying to add body content to the Root MimePart container");
+        return this;
+    }
+
+    @Override
+    public MimePartContainerBuilder addChild(MimePart mimePart) {
+        if (rootMimePart == null) {
+            rootMimePart = mimePart;
+        } else {
+            LOGGER.warn("Trying to add several children to the Root MimePart container");
+        }
+        return this;
+    }
+
+    @Override
+    public MimePartContainerBuilder addFileName(String fileName) {
+        LOGGER.warn("Trying to add fineName to the Root MimePart container");
+        return this;
+    }
+
+    @Override
+    public MimePartContainerBuilder addMediaType(String mediaType) {
+        LOGGER.warn("Trying to add media type to the Root MimePart container");
+        return this;
+    }
+
+    @Override
+    public MimePartContainerBuilder addSubType(String subType) {
+        LOGGER.warn("Trying to add sub type to the Root MimePart container");
+        return this;
+    }
+
+    @Override
+    public MimePartContainerBuilder addContentDisposition(String contentDisposition) {
+        LOGGER.warn("Trying to add content disposition to the Root MimePart container");
+        return this;
+    }
+}



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