blob: f7f92cadd993607492e5fca88561ca725257bc6a [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.send;
import java.util.Optional;
import jakarta.mail.Flags;
import jakarta.mail.Flags.Flag;
import org.apache.james.core.Username;
import org.apache.james.mailbox.MailboxManager;
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.MailboxRoleNotFoundException;
import org.apache.james.mailbox.model.FetchGroup;
import org.apache.james.mailbox.model.MailboxId;
import org.apache.james.mailbox.model.MessageId;
import org.apache.james.mailbox.model.MessageId.Factory;
import org.apache.james.queue.api.MailQueue.MailQueueException;
import org.apache.james.queue.api.MailQueue.MailQueueItem;
import org.apache.james.queue.api.MailQueueItemDecoratorFactory.MailQueueItemDecorator;
import org.apache.mailet.Attribute;
import org.apache.mailet.AttributeUtils;
import org.apache.mailet.Mail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableList;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class PostDequeueDecorator extends MailQueueItemDecorator {
private static final Logger LOG = LoggerFactory.getLogger(PostDequeueDecorator.class);
private static final Attribute IS_DELIVERED = Attribute.convertToAttribute("DELIVERED", "DELIVERED");
private final MailboxManager mailboxManager;
private final Factory messageIdFactory;
private final MessageIdManager messageIdManager;
private final SystemMailboxesProvider systemMailboxesProvider;
public PostDequeueDecorator(MailQueueItem mailQueueItem,
MailboxManager mailboxManager,
Factory messageIdFactory,
MessageIdManager messageIdManager,
SystemMailboxesProvider systemMailboxesProvider) {
super(mailQueueItem);
this.mailboxManager = mailboxManager;
this.messageIdFactory = messageIdFactory;
this.messageIdManager = messageIdManager;
this.systemMailboxesProvider = systemMailboxesProvider;
}
@Override
public Mail getMail() {
return mailQueueItem.getMail();
}
@Override
public void done(CompletionStatus success) throws MailQueueException {
mailQueueItem.done(success);
if (success == CompletionStatus.SUCCESS && mandatoryJmapMetaDataIsPresent()) {
Optional<?> optionalRawMessageId = retrieveMessageId();
MessageId messageId = messageIdFactory.fromString((String) optionalRawMessageId.get());
Optional<String> username = retrieveUsername();
if (!getMail().getAttribute(IS_DELIVERED.getName()).isPresent()) {
try {
MailboxSession mailboxSession = mailboxManager.createSystemSession(Username.of(username.get()));
moveFromOutboxToSentWithSeenFlag(messageId, mailboxSession);
getMail().setAttribute(IS_DELIVERED);
mailboxManager.endProcessingRequest(mailboxSession);
} catch (MailShouldBeInOutboxException e) {
LOG.info("Message does not exist on Outbox anymore, it could have already been sent", e);
}
}
}
}
private Optional<?> retrieveMessageId() {
return AttributeUtils.getAttributeValueFromMail(getMail(), MailMetadata.MAIL_METADATA_MESSAGE_ID_ATTRIBUTE);
}
private Optional<String> retrieveUsername() {
return AttributeUtils.getValueAndCastFromMail(getMail(), MailMetadata.MAIL_METADATA_USERNAME_ATTRIBUTE, String.class);
}
private boolean mandatoryJmapMetaDataIsPresent() {
return checkMessageIdAttribute()
&& checkUsernameAttribute();
}
private boolean checkMessageIdAttribute() {
return retrieveMessageId()
.map(this::validateMessageId)
.orElse(false);
}
private boolean validateMessageId(Object messageId) {
if (messageId instanceof String) {
try {
messageIdFactory.fromString((String) messageId);
return true;
} catch (Exception e) {
LOG.error("Invalid messageId: {}", messageId, e);
}
}
LOG.error("Non-String messageId {} has type {}", messageId, messageId.getClass());
return false;
}
private boolean checkUsernameAttribute() {
return retrieveUsername().isPresent();
}
private void moveFromOutboxToSentWithSeenFlag(MessageId messageId, MailboxSession mailboxSession) {
assertMessageBelongsToOutbox(messageId, mailboxSession)
.then(getSentMailboxId(mailboxSession)
.switchIfEmpty(Mono.error(() -> new MailboxRoleNotFoundException(Role.SENT)))
.flatMap(sentMailboxId ->
Mono.from(messageIdManager.setInMailboxesReactive(messageId,
ImmutableList.of(sentMailboxId), mailboxSession))
.then(Mono.from(messageIdManager.setFlagsReactive(new Flags(Flag.SEEN),
MessageManager.FlagsUpdateMode.ADD,
messageId, ImmutableList.of(sentMailboxId), mailboxSession)))))
.block();
}
private Mono<Void> assertMessageBelongsToOutbox(MessageId messageId, MailboxSession mailboxSession) {
return getOutboxMailboxId(mailboxSession)
.flatMap(outboxMailboxId -> Flux.from(messageIdManager.getMessagesReactive(ImmutableList.of(messageId), FetchGroup.MINIMAL, mailboxSession))
.filter(message -> message.getMailboxId().equals(outboxMailboxId))
.next()
.switchIfEmpty(Mono.error(() -> new MailShouldBeInOutboxException(messageId))))
.then();
}
private Mono<MailboxId> getSentMailboxId(MailboxSession session) {
return Flux.from(systemMailboxesProvider.getMailboxByRole(Role.SENT, session.getUser()))
.next()
.map(MessageManager::getId);
}
private Mono<MailboxId> getOutboxMailboxId(MailboxSession session) {
return Flux.from(systemMailboxesProvider.getMailboxByRole(Role.OUTBOX, session.getUser()))
.next()
.map(MessageManager::getId);
}
}