| /**************************************************************** |
| * 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.io.IOException; |
| import java.util.Date; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.stream.Stream; |
| |
| import org.apache.james.core.Domain; |
| import org.apache.james.core.Username; |
| import org.apache.james.jmap.draft.exceptions.InvalidOriginMessageForMDNException; |
| import org.apache.james.mailbox.MailboxSession; |
| import org.apache.james.mailbox.model.MessageId; |
| import org.apache.james.mdn.MDN; |
| import org.apache.james.mdn.MDNReport; |
| import org.apache.james.mdn.fields.Disposition; |
| import org.apache.james.mdn.fields.ReportingUserAgent; |
| import org.apache.james.mime4j.codec.DecodeMonitor; |
| import org.apache.james.mime4j.dom.Message; |
| import org.apache.james.mime4j.dom.address.AddressList; |
| import org.apache.james.mime4j.dom.address.Mailbox; |
| import org.apache.james.mime4j.dom.address.MailboxList; |
| import org.apache.james.mime4j.dom.field.AddressListField; |
| import org.apache.james.mime4j.dom.field.ParseException; |
| import org.apache.james.mime4j.field.AddressListFieldLenientImpl; |
| import org.apache.james.mime4j.util.MimeUtil; |
| |
| 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.collect.ImmutableList; |
| |
| @JsonDeserialize(builder = JmapMDN.Builder.class) |
| public class JmapMDN { |
| |
| public static final String DISPOSITION_NOTIFICATION_TO = "Disposition-Notification-To"; |
| public static final String RETURN_PATH = "Return-Path"; |
| |
| public static Builder builder() { |
| return new Builder(); |
| } |
| |
| @JsonPOJOBuilder(withPrefix = "") |
| public static class Builder { |
| private MessageId messageId; |
| private String subject; |
| private String textBody; |
| private String reportingUA; |
| private MDNDisposition disposition; |
| |
| public Builder messageId(MessageId messageId) { |
| this.messageId = messageId; |
| return this; |
| } |
| |
| public Builder subject(String subject) { |
| this.subject = subject; |
| return this; |
| } |
| |
| public Builder textBody(String textBody) { |
| this.textBody = textBody; |
| return this; |
| } |
| |
| public Builder reportingUA(String reportingUA) { |
| this.reportingUA = reportingUA; |
| return this; |
| } |
| |
| public Builder disposition(MDNDisposition disposition) { |
| this.disposition = disposition; |
| return this; |
| } |
| |
| public JmapMDN build() { |
| Preconditions.checkState(messageId != null, "'messageId' is mandatory"); |
| Preconditions.checkState(subject != null, "'subject' is mandatory"); |
| Preconditions.checkState(textBody != null, "'textBody' is mandatory"); |
| Preconditions.checkState(reportingUA != null, "'reportingUA' is mandatory"); |
| Preconditions.checkState(disposition != null, "'disposition' is mandatory"); |
| |
| return new JmapMDN(messageId, subject, textBody, reportingUA, disposition); |
| } |
| |
| } |
| |
| private final MessageId messageId; |
| private final String subject; |
| private final String textBody; |
| private final String reportingUA; |
| private final MDNDisposition disposition; |
| |
| @VisibleForTesting |
| JmapMDN(MessageId messageId, String subject, String textBody, String reportingUA, MDNDisposition disposition) { |
| this.messageId = messageId; |
| this.subject = subject; |
| this.textBody = textBody; |
| this.reportingUA = reportingUA; |
| this.disposition = disposition; |
| } |
| |
| public MessageId getMessageId() { |
| return messageId; |
| } |
| |
| public String getSubject() { |
| return subject; |
| } |
| |
| public String getTextBody() { |
| return textBody; |
| } |
| |
| public ReportingUserAgent getReportingUA() { |
| return ReportingUserAgent.builder().userAgentName(reportingUA).build(); |
| } |
| |
| public MDNDisposition getDisposition() { |
| return disposition; |
| } |
| |
| public Message generateMDNMessage(Message originalMessage, MailboxSession mailboxSession) throws ParseException, IOException, InvalidOriginMessageForMDNException { |
| |
| Username username = mailboxSession.getUser(); |
| |
| return MDN.builder() |
| .report(generateReport(originalMessage, mailboxSession)) |
| .humanReadableText(textBody) |
| .build() |
| .asMime4JMessageBuilder() |
| .setTo(getSenderAddress(originalMessage)) |
| .setFrom(username.asString()) |
| .setSubject(subject) |
| .setDate(new Date()) |
| .setMessageId(MimeUtil.createUniqueMessageId(username.getDomainPart().map(Domain::name).orElse(null))) |
| .build(); |
| } |
| |
| private String getSenderAddress(Message originalMessage) throws InvalidOriginMessageForMDNException { |
| return getAddressForHeader(originalMessage, DISPOSITION_NOTIFICATION_TO) |
| .orElseThrow(() -> InvalidOriginMessageForMDNException.missingHeader(DISPOSITION_NOTIFICATION_TO)) |
| .getAddress(); |
| } |
| |
| private Optional<Mailbox> getAddressForHeader(Message originalMessage, String fieldName) { |
| return Optional.ofNullable(originalMessage.getHeader() |
| .getFields(fieldName)) |
| .orElse(ImmutableList.of()) |
| .stream() |
| .map(field -> AddressListFieldLenientImpl.PARSER.parse(field, new DecodeMonitor())) |
| .findFirst() |
| .map(AddressListField::getAddressList) |
| .map(AddressList::flatten) |
| .map(MailboxList::stream) |
| .orElse(Stream.of()) |
| .findFirst(); |
| } |
| |
| private MDNReport generateReport(Message originalMessage, MailboxSession mailboxSession) throws InvalidOriginMessageForMDNException { |
| if (originalMessage.getMessageId() == null) { |
| throw InvalidOriginMessageForMDNException.missingHeader("Message-ID"); |
| } |
| return MDNReport.builder() |
| .dispositionField(generateDisposition()) |
| .originalRecipientField(mailboxSession.getUser().asString()) |
| .originalMessageIdField(originalMessage.getMessageId()) |
| .finalRecipientField(mailboxSession.getUser().asString()) |
| .reportingUserAgentField(getReportingUA()) |
| .build(); |
| } |
| |
| private Disposition generateDisposition() { |
| return Disposition.builder() |
| .actionMode(disposition.getActionMode()) |
| .sendingMode(disposition.getSendingMode()) |
| .type(disposition.getType()) |
| .build(); |
| } |
| |
| @Override |
| public final boolean equals(Object o) { |
| if (o instanceof JmapMDN) { |
| JmapMDN that = (JmapMDN) o; |
| |
| return Objects.equals(this.messageId, that.messageId) |
| && Objects.equals(this.subject, that.subject) |
| && Objects.equals(this.textBody, that.textBody) |
| && Objects.equals(this.reportingUA, that.reportingUA) |
| && Objects.equals(this.disposition, that.disposition); |
| } |
| return false; |
| } |
| |
| @Override |
| public final int hashCode() { |
| return Objects.hash(messageId, subject, textBody, reportingUA, disposition); |
| } |
| |
| @Override |
| public String toString() { |
| return MoreObjects.toStringHelper(this) |
| .add("messageId", messageId) |
| .add("subject", subject) |
| .add("textBody", textBody) |
| .add("reportingUA", reportingUA) |
| .add("mdnDisposition", disposition) |
| .toString(); |
| } |
| } |