| /**************************************************************** |
| * 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.methods; |
| |
| import static org.apache.james.jmap.draft.methods.Method.JMAP_PREFIX; |
| |
| import java.io.IOException; |
| import java.util.List; |
| |
| import javax.inject.Inject; |
| import javax.mail.Flags; |
| import javax.mail.MessagingException; |
| |
| import org.apache.james.jmap.draft.exceptions.InvalidOriginMessageForMDNException; |
| import org.apache.james.jmap.draft.exceptions.MessageNotFoundException; |
| import org.apache.james.jmap.draft.model.JmapMDN; |
| import org.apache.james.jmap.draft.model.SetError; |
| import org.apache.james.jmap.draft.model.SetMessagesRequest; |
| import org.apache.james.jmap.draft.model.SetMessagesResponse; |
| import org.apache.james.jmap.draft.model.message.view.MessageFullViewFactory.MetaDataWithContent; |
| import org.apache.james.mailbox.MailboxSession; |
| import org.apache.james.mailbox.MessageIdManager; |
| import org.apache.james.mailbox.MessageManager; |
| import org.apache.james.mailbox.Role; |
| import org.apache.james.mailbox.SystemMailboxesProvider; |
| import org.apache.james.mailbox.exception.MailboxException; |
| import org.apache.james.mailbox.exception.OverQuotaException; |
| import org.apache.james.mailbox.model.FetchGroup; |
| import org.apache.james.mailbox.model.MessageId; |
| import org.apache.james.mailbox.model.MessageResult; |
| import org.apache.james.metrics.api.MetricFactory; |
| import org.apache.james.mime4j.codec.DecodeMonitor; |
| import org.apache.james.mime4j.dom.Message; |
| import org.apache.james.mime4j.dom.field.ParseException; |
| import org.apache.james.mime4j.message.DefaultMessageBuilder; |
| import org.apache.james.mime4j.stream.MimeConfig; |
| import org.apache.james.server.core.Envelope; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import reactor.core.publisher.Flux; |
| |
| public class SendMDNProcessor implements SetMessagesProcessor { |
| |
| private static final Logger LOGGER = LoggerFactory.getLogger(SendMDNProcessor.class); |
| |
| private final MetricFactory metricFactory; |
| private final SystemMailboxesProvider systemMailboxesProvider; |
| private final MessageIdManager messageIdManager; |
| private final MessageAppender messageAppender; |
| private final MessageSender messageSender; |
| |
| @Inject |
| public SendMDNProcessor(MetricFactory metricFactory, SystemMailboxesProvider systemMailboxesProvider, |
| MessageIdManager messageIdManager, MessageAppender messageAppender, MessageSender messageSender) { |
| this.metricFactory = metricFactory; |
| this.systemMailboxesProvider = systemMailboxesProvider; |
| this.messageIdManager = messageIdManager; |
| this.messageAppender = messageAppender; |
| this.messageSender = messageSender; |
| } |
| |
| @Override |
| public SetMessagesResponse process(SetMessagesRequest request, MailboxSession mailboxSession) { |
| return metricFactory.decorateSupplierWithTimerMetricLogP99(JMAP_PREFIX + "SendMDN", |
| () -> handleMDNCreation(request, mailboxSession)); |
| } |
| |
| private SetMessagesResponse handleMDNCreation(SetMessagesRequest request, MailboxSession mailboxSession) { |
| return request.getSendMDN() |
| .stream() |
| .map(MDNCreationEntry -> handleMDNCreation(MDNCreationEntry, mailboxSession)) |
| .reduce(SetMessagesResponse.builder(), SetMessagesResponse.Builder::mergeWith) |
| .build(); |
| } |
| |
| private SetMessagesResponse.Builder handleMDNCreation(ValueWithId.MDNCreationEntry MDNCreationEntry, MailboxSession mailboxSession) { |
| try { |
| MessageId messageId = sendMdn(MDNCreationEntry, mailboxSession); |
| return SetMessagesResponse.builder() |
| .mdnSent(MDNCreationEntry.getCreationId(), messageId); |
| } catch (InvalidOriginMessageForMDNException e) { |
| return SetMessagesResponse.builder() |
| .mdnNotSent(MDNCreationEntry.getCreationId(), |
| SetError.builder() |
| .description(String.format("Origin messageId '%s' is invalid." + |
| " A Message Delivery Notification can not be generated for it." + |
| " Explanation: " + e.getExplanation(), |
| MDNCreationEntry.getValue().getMessageId().serialize())) |
| .type(SetError.Type.INVALID_ARGUMENTS) |
| .build()); |
| |
| } catch (MessageNotFoundException e) { |
| return SetMessagesResponse.builder() |
| .mdnNotSent(MDNCreationEntry.getCreationId(), |
| SetError.builder() |
| .description(String.format("Message with id %s not found. Thus could not send MDN.", |
| MDNCreationEntry.getValue().getMessageId().serialize())) |
| .type(SetError.Type.INVALID_ARGUMENTS) |
| .build()); |
| |
| } catch (OverQuotaException e) { |
| return SetMessagesResponse.builder() |
| .mdnNotSent(MDNCreationEntry.getCreationId(), |
| SetError.builder() |
| .description(e.getMessage()) |
| .type(SetError.Type.MAX_QUOTA_REACHED) |
| .build()); |
| |
| } catch (Exception e) { |
| LOGGER.error("Error while sending MDN", e); |
| return SetMessagesResponse.builder() |
| .mdnNotSent(MDNCreationEntry.getCreationId(), |
| SetError.builder() |
| .description(String.format("Could not send MDN %s", MDNCreationEntry.getCreationId().getId())) |
| .type(SetError.Type.ERROR) |
| .build()); |
| } |
| } |
| |
| private MessageId sendMdn(ValueWithId.MDNCreationEntry MDNCreationEntry, MailboxSession mailboxSession) |
| throws MailboxException, IOException, MessagingException, ParseException, MessageNotFoundException, InvalidOriginMessageForMDNException { |
| |
| JmapMDN mdn = MDNCreationEntry.getValue(); |
| Message originalMessage = retrieveOriginalMessage(mdn, mailboxSession); |
| |
| Message mdnAnswer = mdn.generateMDNMessage(originalMessage, mailboxSession); |
| |
| Flags seen = new Flags(Flags.Flag.SEEN); |
| MetaDataWithContent metaDataWithContent = messageAppender.appendMessageInMailbox(mdnAnswer, |
| getOutbox(mailboxSession), seen, mailboxSession); |
| |
| messageSender.sendMessage(metaDataWithContent, |
| Envelope.fromMime4JMessage(mdnAnswer), mailboxSession); |
| |
| return metaDataWithContent.getMessageId(); |
| } |
| |
| private Message retrieveOriginalMessage(JmapMDN mdn, MailboxSession mailboxSession) throws MailboxException, IOException, MessageNotFoundException { |
| List<MessageResult> messages = messageIdManager.getMessage(mdn.getMessageId(), FetchGroup.HEADERS, mailboxSession); |
| |
| if (messages.size() == 0) { |
| throw new MessageNotFoundException(); |
| } |
| |
| DefaultMessageBuilder messageBuilder = new DefaultMessageBuilder(); |
| messageBuilder.setMimeEntityConfig(MimeConfig.PERMISSIVE); |
| messageBuilder.setDecodeMonitor(DecodeMonitor.SILENT); |
| return messageBuilder.parseMessage(messages.get(0).getHeaders().getInputStream()); |
| } |
| |
| private MessageManager getOutbox(MailboxSession mailboxSession) throws MailboxException { |
| return Flux.from(systemMailboxesProvider.getMailboxByRole(Role.OUTBOX, mailboxSession.getUser())) |
| .toStream() |
| .findAny() |
| .orElseThrow(() -> new IllegalStateException("User don't have an Outbox")); |
| } |
| |
| } |