This is an automated email from the ASF dual-hosted git repository. chibenwa pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 2dd73a31d05d4bcdce4fbdebbc57d1c781720829 Author: Benoit TELLIER <[email protected]> AuthorDate: Wed Jun 17 09:24:21 2026 +0200 [FIX] Folder rename: upfront read of the mailbox and its children --- .../james/mailbox/store/StoreMailboxManager.java | 101 ++++++++++----------- 1 file changed, 48 insertions(+), 53 deletions(-) diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java index 44ce721635..4f6912cb01 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java @@ -24,6 +24,7 @@ import static org.apache.james.mailbox.store.mail.AbstractMessageMapper.UNLIMITE import java.time.Clock; import java.time.Duration; +import java.util.Comparator; import java.util.EnumSet; import java.util.List; import java.util.Map; @@ -618,13 +619,31 @@ public class StoreMailboxManager implements MailboxManager { MailboxSession fromSession, MailboxSession toSession) { LOGGER.debug("renameMailbox {} to {}", from, to); MailboxMapper mapper = mailboxSessionMapperFactory.getMailboxMapper(fromSession); - - Mono<Mailbox> fromMailboxPublisher = assertCanDeleteWhenRename(fromSession, from) - .then(mapper.findMailboxByPath(from) - .switchIfEmpty(Mono.error(() -> new MailboxNotFoundException(from)))); + Mono<List<Mailbox>> mailboxesToBeRenamed = retrieveSubMailboxes(from, fromSession, mapper); return sanitizedMailboxPath(to, toSession) - .flatMap(sanitizedPath -> processRename(fromMailboxPublisher, sanitizedPath, option, fromSession, toSession)); + .flatMap(sanitizedPath -> processRename(mailboxesToBeRenamed, sanitizedPath, option, fromSession, toSession)); + } + + private Mono<List<Mailbox>> retrieveSubMailboxes(MailboxPath from, MailboxSession fromSession, MailboxMapper mapper) { + MailboxQuery.UserBound query = MailboxQuery.builder() + .userAndNamespaceFrom(from) + .expression(new PrefixedWildcard(from.getName())) + .build() + .asUserBound(); + + return assertCanDeleteWhenRename(fromSession, from) + .then(mapper.findMailboxWithPathLike(query) + .filter(mailbox -> mailbox.generateAssociatedPath().getHierarchyLevels(fromSession.getPathDelimiter()).contains(from)) + .sort(Comparator.comparing(mailbox -> mailbox.generateAssociatedPath().getHierarchyLevels(fromSession.getPathDelimiter()).size())) + .collectList() + .handle((mailboxes, sink) -> { + if (mailboxes.stream().map(mailbox -> mailbox.generateAssociatedPath()).anyMatch(from::equals)) { + sink.next(mailboxes); + } else { + sink.error(new MailboxNotFoundException(from)); + } + })); } private Mono<List<MailboxRenamedResult>> renameSubscriptionsIfNeeded(List<MailboxRenamedResult> renamedResults, @@ -666,9 +685,8 @@ public class StoreMailboxManager implements MailboxManager { LOGGER.debug("renameMailbox {} to {}", mailboxId, newMailboxPath); MailboxMapper mapper = mailboxSessionMapperFactory.getMailboxMapper(session); - Mono<Mailbox> fromMailboxPublisher = mapper.findMailboxById(mailboxId) - .flatMap(mailbox -> assertCanDeleteWhenRename(session, mailbox.generateAssociatedPath()) - .thenReturn(mailbox)) + Mono<List<Mailbox>> fromMailboxPublisher = mapper.findMailboxById(mailboxId) + .flatMap(from -> retrieveSubMailboxes(from.generateAssociatedPath(), session, mapper)) .switchIfEmpty(Mono.error(() -> new MailboxNotFoundException(mailboxId))); return sanitizedMailboxPath(newMailboxPath, session) @@ -689,47 +707,28 @@ public class StoreMailboxManager implements MailboxManager { .flatMap(newMailboxPath -> Mono.fromCallable(() -> newMailboxPath.assertAcceptable(session.getPathDelimiter()))); } - private Mono<List<MailboxRenamedResult>> processRename(Mono<Mailbox> fromMailboxPublisher, MailboxPath to, RenameOption option, + private Mono<List<MailboxRenamedResult>> processRename(Mono<List<Mailbox>> fromMailboxPublisher, MailboxPath to, RenameOption option, MailboxSession fromSession, MailboxSession toSession) { MailboxMapper mapper = mailboxSessionMapperFactory.getMailboxMapper(fromSession); return mapper.executeReactive(fromMailboxPublisher - .flatMap(mailbox -> doRenameMailbox(mailbox, to, fromSession, toSession, mapper) - .doOnEach(ReactorUtils.logFinally(() -> AuditTrail.entry() + .flatMap(mailboxes -> doRenameMailboxes(mailboxes, to, fromSession, toSession, mapper) + .doOnNext(result -> AuditTrail.entry() .username(() -> fromSession.getUser().asString()) .sessionId(() -> String.valueOf(fromSession.getSessionId().getValue())) .protocol("mailbox") .action("rename") - .parameters(Throwing.supplier(() -> ImmutableMap.of("mailboxId", mailbox.getMailboxId().serialize(), - "fromMailboxPath", mailbox.generateAssociatedPath().asString(), - "toMailboxPath", to.asString()))) - .log("Mailbox Rename"))) + .parameters(Throwing.supplier(() -> ImmutableMap.of("mailboxId", result.getMailboxId().serialize(), + "fromMailboxPath", result.getOriginPath().asString(), + "toMailboxPath", result.getDestinationPath().asString()))) + .log("Mailbox Rename")) + .collectList() .flatMap(renamedResults -> renameSubscriptionsIfNeeded(renamedResults, option, fromSession, toSession)))); } - private Mono<List<MailboxRenamedResult>> doRenameMailbox(Mailbox mailbox, MailboxPath newMailboxPath, MailboxSession fromSession, MailboxSession toSession, MailboxMapper mapper) { - // TODO put this into a serilizable transaction - - ImmutableList.Builder<MailboxRenamedResult> resultBuilder = ImmutableList.builder(); - - MailboxPath from = mailbox.generateAssociatedPath(); - mailbox.setNamespace(newMailboxPath.getNamespace()); - mailbox.setUser(newMailboxPath.getUser()); - mailbox.setName(newMailboxPath.getName()); - // Find submailboxes - MailboxQuery.UserBound query = MailboxQuery.builder() - .userAndNamespaceFrom(from) - .expression(new PrefixedWildcard(from.getName() + fromSession.getPathDelimiter())) - .build() - .asUserBound(); - - return mapper.rename(mailbox, from) - .map(mailboxId -> { - resultBuilder.add(new MailboxRenamedResult(mailboxId, from, newMailboxPath)); - return mailboxId; - }) - .then(Mono.from(renameSubMailboxes(newMailboxPath, mapper, from, query, resultBuilder))) - .then(Mono.defer(() -> Flux.fromIterable(resultBuilder.build()) + private Flux<MailboxRenamedResult> doRenameMailboxes(List<Mailbox> mailboxes, MailboxPath newMailboxPath, MailboxSession fromSession, MailboxSession toSession, MailboxMapper mapper) { + return renameSubMailboxes(newMailboxPath, mapper, mailboxes.get(0).generateAssociatedPath(), mailboxes) + .flatMapIterable(results -> results) .concatMap(result -> eventBus.dispatch(EventFactory.mailboxRenamed() .randomEventId() .mailboxSession(fromSession) @@ -737,17 +736,16 @@ public class StoreMailboxManager implements MailboxManager { .oldPath(result.getOriginPath()) .newPath(result.getDestinationPath()) .build(), - new MailboxIdRegistrationKey(result.getMailboxId()))) - .then())) - .then(Mono.fromCallable(resultBuilder::build)); + new MailboxIdRegistrationKey(result.getMailboxId())) + .thenReturn(result)); } - private Publisher<Void> renameSubMailboxes(MailboxPath newMailboxPath, MailboxMapper mapper, - MailboxPath from, MailboxQuery.UserBound query, ImmutableList.Builder<MailboxRenamedResult> resultBuilder) { - if (DefaultMailboxes.INBOX.equalsIgnoreCase(from.getName())) { - return Mono.empty(); - } - return locker.executeReactiveWithLockReactive(from, mapper.findMailboxWithPathLike(query) + private Mono<List<MailboxRenamedResult>> renameSubMailboxes(MailboxPath newMailboxPath, MailboxMapper mapper, + MailboxPath from, List<Mailbox> mailboxes) { + // Renaming INBOX renames the INBOX mailbox itself but leaves its sub-mailboxes in place. + // The list is sorted shallowest-first, so the first entry is the renamed root. + List<Mailbox> mailboxesToRename = DefaultMailboxes.INBOX.equalsIgnoreCase(from.getName()) ? mailboxes.subList(0, 1) : mailboxes; + return Mono.from(locker.executeReactiveWithLockReactive(from, Flux.fromIterable(mailboxesToRename) .concatMap(sub -> { String subOriginalName = sub.getName(); String subNewName = newMailboxPath.getName() + subOriginalName.substring(from.getName().length()); @@ -755,14 +753,11 @@ public class StoreMailboxManager implements MailboxManager { sub.setName(subNewName); sub.setUser(newMailboxPath.getUser()); return mapper.rename(sub, fromPath) - .map(mailboxId -> { - resultBuilder.add(new MailboxRenamedResult(sub.getMailboxId(), fromPath, sub.generateAssociatedPath())); - return mailboxId; - }) + .map(mailboxId -> new MailboxRenamedResult(sub.getMailboxId(), fromPath, sub.generateAssociatedPath())) .retryWhen(Retry.backoff(5, Duration.ofMillis(10))) - .then(Mono.fromRunnable(() -> LOGGER.debug("Rename mailbox sub-mailbox {} to {}", subOriginalName, subNewName))); + .doOnSuccess(any -> LOGGER.debug("Rename mailbox sub-mailbox {} to {}", subOriginalName, subNewName)); }, LOW_CONCURRENCY) - .then(), MailboxPathLocker.LockType.Write); + .collectList(), MailboxPathLocker.LockType.Write)); } @Override --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
