All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.james.jmap.draft.model.CreationMessage Maven / Gradle / Ivy

There is a newer version: 3.8.1
Show newest version
/****************************************************************
 * 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.draft.model;

import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;

import org.apache.james.jmap.draft.methods.ValidationResult;
import org.apache.james.jmap.draft.model.MessageProperties.MessageProperty;
import org.apache.james.jmap.draft.model.message.view.SubMessage;
import org.apache.james.mailbox.MessageManager;
import org.apache.james.util.OptionalUtils;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

@JsonDeserialize(builder = CreationMessage.Builder.class)
public class CreationMessage {

    private static final String RECIPIENT_PROPERTY_NAMES = ImmutableList.of(MessageProperty.to, MessageProperty.cc, MessageProperty.bcc).stream()
            .map(MessageProperty::asFieldName)
            .collect(Collectors.joining(", "));

    public static Builder builder() {
        return new Builder();
    }

    @JsonPOJOBuilder(withPrefix = "")
    public static class Builder {
        private ImmutableList mailboxIds;
        private String inReplyToMessageId;
        private final OldKeyword.Builder oldKeywordBuilder;
        private final ImmutableMap.Builder headers;
        private Optional from = Optional.empty();
        private final ImmutableList.Builder to;
        private final ImmutableList.Builder cc;
        private final ImmutableList.Builder bcc;
        private final ImmutableList.Builder replyTo;
        private String subject;
        private ZonedDateTime date;
        private String textBody;
        private String htmlBody;
        private final ImmutableList.Builder attachments;
        private final ImmutableMap.Builder attachedMessages;
        private Optional> keywords = Optional.empty();

        private Builder() {
            to = ImmutableList.builder();
            cc = ImmutableList.builder();
            bcc = ImmutableList.builder();
            replyTo = ImmutableList.builder();
            attachments = ImmutableList.builder();
            attachedMessages = ImmutableMap.builder();
            headers = ImmutableMap.builder();
            oldKeywordBuilder = OldKeyword.builder();
        }

        public Builder mailboxId(String... mailboxIds) {
            return mailboxIds(Arrays.asList(mailboxIds));
        }

        @JsonDeserialize
        public Builder mailboxIds(List mailboxIds) {
            this.mailboxIds = ImmutableList.copyOf(mailboxIds);
            return this;
        }

        public Builder inReplyToMessageId(String inReplyToMessageId) {
            this.inReplyToMessageId = inReplyToMessageId;
            return this;
        }

        public Builder isUnread(Optional isUnread) {
            oldKeywordBuilder.isUnread(isUnread);
            return this;
        }

        public Builder isFlagged(Optional isFlagged) {
            oldKeywordBuilder.isFlagged(isFlagged);
            return this;
        }

        public Builder isAnswered(Optional isAnswered) {
            oldKeywordBuilder.isAnswered(isAnswered);
            return this;
        }

        public Builder isDraft(Optional isDraft) {
            oldKeywordBuilder.isDraft(isDraft);
            return this;
        }

        public Builder isForwarded(Optional isForwarded) {
            oldKeywordBuilder.isForwarded(isForwarded);
            return this;
        }

        public Builder headers(ImmutableMap headers) {
            this.headers.putAll(headers);
            return this;
        }

        public Builder from(DraftEmailer from) {
            this.from = Optional.ofNullable(from);
            return this;
        }

        public Builder to(List to) {
            this.to.addAll(to);
            return this;
        }

        public Builder cc(List cc) {
            this.cc.addAll(cc);
            return this;
        }

        public Builder bcc(List bcc) {
            this.bcc.addAll(bcc);
            return this;
        }

        public Builder replyTo(List replyTo) {
            this.replyTo.addAll(replyTo);
            return this;
        }

        public Builder subject(String subject) {
            this.subject = Strings.nullToEmpty(subject);
            return this;
        }

        public Builder date(ZonedDateTime date) {
            this.date = date;
            return this;
        }

        public Builder textBody(String textBody) {
            this.textBody = textBody;
            return this;
        }

        public Builder htmlBody(String htmlBody) {
            this.htmlBody = htmlBody;
            return this;
        }

        public Builder attachments(Attachment... attachments) {
            return attachments(Arrays.asList(attachments));
        }
        
        @JsonDeserialize
        public Builder attachments(List attachments) {
            this.attachments.addAll(attachments);
            return this;
        }

        public Builder attachedMessages(Map attachedMessages) {
            this.attachedMessages.putAll(attachedMessages);
            return this;
        }

        public Builder keywords(Map keywords) {
            this.keywords = Optional.of(ImmutableMap.copyOf(keywords));
            return this;
        }

        private static boolean areAttachedMessagesKeysInAttachments(ImmutableList attachments, ImmutableMap attachedMessages) {
            return attachedMessages.isEmpty() || attachedMessages.keySet().stream()
                    .anyMatch(inAttachments(attachments));
        }

        private static Predicate inAttachments(ImmutableList attachments) {
            return (key) -> attachments.stream()
                .map(Attachment::getBlobId)
                .anyMatch(blobId -> blobId.equals(key));
        }

        public CreationMessage build() {
            Preconditions.checkState(mailboxIds != null, "'mailboxIds' is mandatory");
            Preconditions.checkState(headers != null, "'headers' is mandatory");
            ImmutableList attachments = this.attachments.build();
            ImmutableMap attachedMessages = this.attachedMessages.build();
            Preconditions.checkState(areAttachedMessagesKeysInAttachments(attachments, attachedMessages), "'attachedMessages' keys must be in 'attachments'");

            if (date == null) {
                date = ZonedDateTime.now();
            }

            Optional maybeKeywords = creationKeywords();
            Optional oldKeywords = oldKeywordBuilder.computeOldKeyword();

            return new CreationMessage(mailboxIds, Optional.ofNullable(inReplyToMessageId), headers.build(), from,
                    to.build(), cc.build(), bcc.build(), replyTo.build(), subject, date, Optional.ofNullable(textBody), Optional.ofNullable(htmlBody),
                    attachments, attachedMessages, computeKeywords(maybeKeywords, oldKeywords));
        }

        private Optional creationKeywords() {
            return keywords.map(map -> Keywords.strictFactory()
                    .fromMap(map));
        }

        public Keywords computeKeywords(Optional keywords, Optional oldKeywords) {
            Preconditions.checkArgument(!(keywords.isPresent() && oldKeywords.isPresent()), "Does not support keyword and is* at the same time");
            return OptionalUtils
                .or(keywords,
                    oldKeywords.map(OldKeyword::asKeywords))
                .orElse(Keywords.DEFAULT_VALUE);
        }

    }

    private final ImmutableList mailboxIds;
    private final Optional inReplyToMessageId;
    private final ImmutableMap headers;
    private final Optional from;
    private final ImmutableList to;
    private final ImmutableList cc;
    private final ImmutableList bcc;
    private final ImmutableList replyTo;
    private final String subject;
    private final ZonedDateTime date;
    private final Optional textBody;
    private final Optional htmlBody;
    private final ImmutableList attachments;
    private final ImmutableMap attachedMessages;
    private final Keywords keywords;

    @VisibleForTesting
    CreationMessage(ImmutableList mailboxIds, Optional inReplyToMessageId, ImmutableMap headers, Optional from,
                    ImmutableList to, ImmutableList cc, ImmutableList bcc, ImmutableList replyTo, String subject, ZonedDateTime date, Optional textBody, Optional htmlBody, ImmutableList attachments,
                    ImmutableMap attachedMessages, Keywords keywords) {
        this.mailboxIds = mailboxIds;
        this.inReplyToMessageId = inReplyToMessageId;
        this.headers = headers;
        this.from = from;
        this.to = to;
        this.cc = cc;
        this.bcc = bcc;
        this.replyTo = replyTo;
        this.subject = subject;
        this.date = date;
        this.textBody = textBody;
        this.htmlBody = htmlBody;
        this.attachments = attachments;
        this.attachedMessages = attachedMessages;
        this.keywords = keywords;
    }

    public Keywords getKeywords() {
        return keywords;
    }

    public ImmutableList getMailboxIds() {
        return mailboxIds;
    }

    public Optional getInReplyToMessageId() {
        return inReplyToMessageId;
    }

    public ImmutableMap getHeaders() {
        return headers;
    }

    public Optional getFrom() {
        return from;
    }

    public ImmutableList getTo() {
        return to;
    }

    public ImmutableList getCc() {
        return cc;
    }

    public ImmutableList getBcc() {
        return bcc;
    }

    public ImmutableList getReplyTo() {
        return replyTo;
    }

    public String getSubject() {
        return subject;
    }

    public ZonedDateTime getDate() {
        return date;
    }

    public Optional getTextBody() {
        return textBody;
    }

    public Optional getHtmlBody() {
        return htmlBody;
    }

    public ImmutableList getAttachments() {
        return attachments;
    }

    public ImmutableMap getAttachedMessages() {
        return attachedMessages;
    }

    public boolean isValid() {
        return validate().isEmpty();
    }

    public boolean isDraft() {
        return keywords.contains(Keyword.DRAFT);
    }

    public List validate() {
        ImmutableList.Builder errors = ImmutableList.builder();
        assertValidFromProvided(errors);
        assertAtLeastOneValidRecipient(errors);
        return errors.build();
    }

    private void assertAtLeastOneValidRecipient(ImmutableList.Builder errors) {
        ImmutableList recipients = ImmutableList.builder().addAll(to).addAll(cc).addAll(bcc).build();
        boolean hasAtLeastOneAddressToSendTo = recipients.stream().anyMatch(DraftEmailer::hasValidEmail);
        boolean recipientsHaveValidAddresses = recipients.stream().allMatch(e1 -> e1.getEmail() != null);
        if (!(recipientsHaveValidAddresses && hasAtLeastOneAddressToSendTo)) {
            errors.add(ValidationResult.builder().message("no recipient address set").property(RECIPIENT_PROPERTY_NAMES).build());
        }
    }

    private void assertValidFromProvided(ImmutableList.Builder errors) {
        ValidationResult invalidPropertyFrom = ValidationResult.builder()
                .property(MessageProperty.from.asFieldName())
                .message("'from' address is mandatory")
                .build();
        if (!from.isPresent()) {
            errors.add(invalidPropertyFrom);
        }
        from.filter(f -> !f.hasValidEmail()).ifPresent(f -> errors.add(invalidPropertyFrom));
    }

    public boolean isIn(MessageManager mailbox) {
        return mailboxIds.contains(mailbox.getId().serialize());
    }

    public boolean isOnlyIn(MessageManager mailbox) {
        return isIn(mailbox)
            && mailboxIds.size() == 1;
    }
    
    @JsonDeserialize(builder = DraftEmailer.Builder.class)
    public static class DraftEmailer {

        public static Builder builder() {
            return new Builder();
        }

        @JsonPOJOBuilder(withPrefix = "")
        public static class Builder {
            private Optional name = Optional.empty();
            private Optional email = Optional.empty();

            public Builder name(String name) {
                this.name = Optional.ofNullable(name);
                return this;
            }

            public Builder email(String email) {
                this.email = Optional.ofNullable(email);
                return this;
            }

            public DraftEmailer build() {
                return new DraftEmailer(name, email);
            }
        }

        private final Optional name;
        private final Optional email;
        private final EmailValidator emailValidator;

        @VisibleForTesting
        DraftEmailer(Optional name, Optional email) {
            this.name = name;
            this.email = email;
            this.emailValidator = new EmailValidator();
        }

        public Optional getName() {
            return name;
        }

        public Optional getEmail() {
            return email;
        }

        public boolean hasValidEmail() {
            return getEmail().isPresent() && emailValidator.isValid(getEmail().get());
        }

        public EmailUserAndDomain getEmailUserAndDomain() {
            String[] splitAddress = email.get().split("@", 2);
            return new EmailUserAndDomain(Optional.ofNullable(splitAddress[0]), Optional.ofNullable(splitAddress[1]));
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof DraftEmailer) {
                DraftEmailer otherEMailer = (DraftEmailer) o;
                return Objects.equals(name, otherEMailer.name)
                        && Objects.equals(email.orElse(""), otherEMailer.email.orElse(""));
            }
            return false;
        }

        @Override
        public int hashCode() {
            return Objects.hash(name, email);
        }

        @Override
        public String toString() {
            return MoreObjects.toStringHelper(this)
                    .add("name", name)
                    .add("email", email.orElse(""))
                    .toString();
        }
    }

    public static class EmailUserAndDomain {
        private final Optional user;
        private final Optional domain;

        public EmailUserAndDomain(Optional user, Optional domain) {
            this.user = user;
            this.domain = domain;
        }

        public Optional getUser() {
            return user;
        }

        public Optional getDomain() {
            return domain;
        }
    }

    public static class EmailValidator {

        public boolean isValid(String email) {
            boolean result = true;
            try {
                InternetAddress emailAddress = new InternetAddress(email);
                // verrrry permissive validator !
                emailAddress.validate();
            } catch (AddressException ex) {
                result = false;
            }
            return result;
        }
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
            .add("mailboxIds", mailboxIds)
            .toString();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy