blob: 56f7a8ea85d07646d437daa6dcf082c0d74c36a2 [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.method
import jakarta.inject.Inject
import org.apache.james.jmap.core.SetError
import org.apache.james.jmap.core.SetError.SetErrorDescription
import org.apache.james.jmap.mail.{MailboxGet, MailboxSetError, MailboxSetRequest, RemoveEmailsOnDestroy, UnparsedMailboxId}
import org.apache.james.jmap.method.MailboxSetDeletePerformer.{MailboxDeletionFailure, MailboxDeletionResult, MailboxDeletionResults, MailboxDeletionSuccess}
import org.apache.james.mailbox.exception.MailboxNotFoundException
import org.apache.james.mailbox.model.{FetchGroup, MailboxId, MessageRange}
import org.apache.james.mailbox.{MailboxManager, MailboxSession, MessageManager, Role, SubscriptionManager}
import org.slf4j.LoggerFactory
import reactor.core.publisher.SynchronousSink
import reactor.core.scala.publisher.{SFlux, SMono}
object MailboxSetDeletePerformer {
private val LOGGER = LoggerFactory.getLogger(classOf[MailboxSetDeletePerformer])
sealed trait MailboxDeletionResult
case class MailboxDeletionSuccess(mailboxId: MailboxId) extends MailboxDeletionResult
case class MailboxDeletionFailure(mailboxId: UnparsedMailboxId, exception: Throwable) extends MailboxDeletionResult {
def asMailboxSetError: SetError = exception match {
case e: MailboxNotFoundException =>
LOGGER.info("Attempt to delete a non existing mailbox: {}", e.getMessage)
SetError.notFound(SetErrorDescription(e.getMessage))
case e: MailboxHasMailException =>
LOGGER.info("Attempt to delete a mailbox with mails")
MailboxSetError.mailboxHasEmail(SetErrorDescription(s"${e.mailboxId.serialize} is not empty"))
case e: MailboxHasChildException =>
LOGGER.info("Attempt to delete a mailbox with children")
MailboxSetError.mailboxHasChild(SetErrorDescription(s"${e.mailboxId.serialize} has child mailboxes"))
case e: SystemMailboxChangeException =>
LOGGER.info("Attempt to delete a system folder")
SetError.invalidArguments(SetErrorDescription("System mailboxes cannot be destroyed"))
case e: IllegalArgumentException =>
LOGGER.info("Illegal argument in Mailbox/set delete", e)
SetError.invalidArguments(SetErrorDescription(s"${mailboxId.id} is not a mailboxId: ${e.getMessage}"))
case e =>
LOGGER.error("Failed to delete mailbox", e)
SetError.serverFail(SetErrorDescription(exception.getMessage))
}
}
case class MailboxDeletionResults(results: Seq[MailboxDeletionResult]) {
def destroyed: Seq[MailboxId] =
results.flatMap(result => result match {
case success: MailboxDeletionSuccess => Some(success)
case _ => None
}).map(_.mailboxId)
def retrieveErrors: Map[UnparsedMailboxId, SetError] =
results.flatMap(result => result match {
case failure: MailboxDeletionFailure => Some(failure.mailboxId, failure.asMailboxSetError)
case _ => None
})
.toMap
}
}
class MailboxSetDeletePerformer @Inject()(mailboxManager: MailboxManager,
subscriptionManager: SubscriptionManager,
mailboxIdFactory: MailboxId.Factory) {
def deleteMailboxes(mailboxSession: MailboxSession, mailboxSetRequest: MailboxSetRequest): SMono[MailboxDeletionResults] =
SFlux.fromIterable(mailboxSetRequest.destroy.getOrElse(Seq()).toSet)
.flatMap(id => delete(mailboxSession, id, mailboxSetRequest.onDestroyRemoveEmails.getOrElse(RemoveEmailsOnDestroy(false)))
.onErrorRecover(e => MailboxDeletionFailure(id, e)),
maxConcurrency = 5)
.collectSeq()
.map(MailboxDeletionResults)
private def delete(mailboxSession: MailboxSession, id: UnparsedMailboxId, onDestroy: RemoveEmailsOnDestroy): SMono[MailboxDeletionResult] =
MailboxGet.parse(mailboxIdFactory)(id)
.fold(e => SMono.error(e),
id => doDelete(mailboxSession, id, onDestroy)
.`then`(SMono.just[MailboxDeletionResult](MailboxDeletionSuccess(id))))
private def doDelete(mailboxSession: MailboxSession, id: MailboxId, onDestroy: RemoveEmailsOnDestroy): SMono[Unit] =
SMono(mailboxManager.getMailboxReactive(id, mailboxSession))
.flatMap((mailbox: MessageManager) =>
SMono(mailboxManager.hasChildrenReactive(mailbox.getMailboxPath, mailboxSession))
.flatMap(hasChildren => {
if (isASystemMailbox(mailbox)) {
throw SystemMailboxChangeException(id)
}
if (hasChildren) {
throw MailboxHasChildException(id)
}
if (onDestroy.value) {
SMono(mailboxManager.deleteMailboxReactive(id, mailboxSession))
.flatMap(deletedMailbox => SMono(subscriptionManager.unsubscribeReactive(deletedMailbox.generateAssociatedPath(), mailboxSession)))
.`then`()
} else {
SMono(mailbox.getMessagesReactive(MessageRange.all(), FetchGroup.MINIMAL, mailboxSession)).hasElement
.handle((hasElement: Boolean, sink: SynchronousSink[Unit]) => {
if (hasElement) {
sink.error(MailboxHasMailException(id))
}
})
.`then`(SMono(mailboxManager.deleteMailboxReactive(id, mailboxSession))
.flatMap(deletedMailbox => SMono(subscriptionManager.unsubscribeReactive(deletedMailbox.generateAssociatedPath(), mailboxSession)))
.`then`())
}
}))
private def isASystemMailbox(mailbox: MessageManager): Boolean = Role.from(mailbox.getMailboxPath.getName).isPresent
}