blob: 6532a5526869b92e3541bba81c0091da424ef63a [file] [log] [blame]
/****************************************************************
* 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();
}
}