This is an automated email from the ASF dual-hosted git repository. Arsnael pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 3dbd07c8cb60d9676fd6437dbc08c9ce4d9d35e2 Author: Rene Cordier <[email protected]> AuthorDate: Mon May 18 13:15:18 2026 +0700 JAMES-4204 Restore messages from backup zip file --- .../james/mailbox/backup/MessageArchiveEntry.java | 43 +------- .../james/mailbox/backup/UnknownArchiveEntry.java | 12 +-- .../mailbox/backup/ZipMailArchiveRestorer.java | 70 +++++++------ .../mailbox/backup/zip/ExtraFieldExtractor.java | 33 ++++++ .../james/mailbox/backup/zip/FlagsExtraField.java | 24 +++++ .../mailbox/backup/zip/ZipArchivesLoader.java | 3 +- .../backup/zip/ZippedMailAccountIterator.java | 77 +++++++++++--- .../james/webadmin/service/RestoreServiceTest.java | 116 ++++++++++++++++++--- 8 files changed, 266 insertions(+), 112 deletions(-) diff --git a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/MessageArchiveEntry.java b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/MessageArchiveEntry.java index 4ac3bee8a3..6fbd68d773 100644 --- a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/MessageArchiveEntry.java +++ b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/MessageArchiveEntry.java @@ -23,47 +23,8 @@ import java.util.Date; import jakarta.mail.Flags; -public class MessageArchiveEntry implements MailArchiveEntry { - - private final SerializedMessageId messageId; - private final SerializedMailboxId mailboxId; - private final long size; - private final Date internalDate; - private final Flags flags; - private final InputStream content; - - public MessageArchiveEntry(SerializedMessageId messageId, SerializedMailboxId mailboxId, long size, Date internalDate, Flags flags, InputStream content) { - this.messageId = messageId; - this.mailboxId = mailboxId; - this.size = size; - this.internalDate = internalDate; - this.flags = flags; - this.content = content; - } - - public SerializedMessageId getMessageId() { - return messageId; - } - - public SerializedMailboxId getMailboxId() { - return mailboxId; - } - - public long getSize() { - return size; - } - - public Date getInternalDate() { - return internalDate; - } - - public Flags getFlags() { - return flags; - } - - public InputStream getContent() { - return content; - } +public record MessageArchiveEntry(SerializedMessageId messageId, SerializedMailboxId mailboxId, long size, + Date internalDate, Flags flags, InputStream content) implements MailArchiveEntry { @Override public ArchiveEntryType getType() { diff --git a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/UnknownArchiveEntry.java b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/UnknownArchiveEntry.java index 88c858c70c..9690a68c41 100644 --- a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/UnknownArchiveEntry.java +++ b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/UnknownArchiveEntry.java @@ -18,17 +18,7 @@ ****************************************************************/ package org.apache.james.mailbox.backup; -public class UnknownArchiveEntry implements MailArchiveEntry { - - private final String entryName; - - public UnknownArchiveEntry(String entryName) { - this.entryName = entryName; - } - - public String getEntryName() { - return entryName; - } +public record UnknownArchiveEntry(String entryName) implements MailArchiveEntry { @Override public ArchiveEntryType getType() { diff --git a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/ZipMailArchiveRestorer.java b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/ZipMailArchiveRestorer.java index 3949e0f586..a7ca20e588 100644 --- a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/ZipMailArchiveRestorer.java +++ b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/ZipMailArchiveRestorer.java @@ -20,10 +20,9 @@ package org.apache.james.mailbox.backup; import java.io.IOException; import java.io.InputStream; -import java.util.List; +import java.util.HashMap; import java.util.Map; import java.util.Optional; -import java.util.stream.Stream; import jakarta.inject.Inject; @@ -32,15 +31,15 @@ import org.apache.james.core.Username; import org.apache.james.mailbox.MailboxManager; import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.MessageManager; +import org.apache.james.mailbox.MessageManager.AppendResult; import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.ByteSourceContent; import org.apache.james.mailbox.model.MailboxId; import org.apache.james.mailbox.model.MailboxPath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.github.fge.lambdas.Throwing; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; public class ZipMailArchiveRestorer implements MailArchiveRestorer { @@ -61,38 +60,49 @@ public class ZipMailArchiveRestorer implements MailArchiveRestorer { mailboxManager.endProcessingRequest(session); } - private void restoreEntries(InputStream source, MailboxSession session) throws IOException { + private void restoreEntries(InputStream source, MailboxSession session) throws IOException, MailboxException { try (MailArchiveIterator archiveIterator = archiveLoader.load(source)) { - List<MailboxWithAnnotationsArchiveEntry> mailboxes = readMailboxes(archiveIterator); - restoreMailboxes(session, mailboxes); + Map<SerializedMailboxId, MessageManager> restoredMailboxes = new HashMap<>(); + while (archiveIterator.hasNext()) { + MailArchiveEntry entry = archiveIterator.next(); + switch (entry.getType()) { + case MAILBOX: + MailboxWithAnnotationsArchiveEntry mailboxEntry = (MailboxWithAnnotationsArchiveEntry) entry; + restoreMailboxEntry(session, mailboxEntry) + .ifPresent(pair -> restoredMailboxes.put(pair.getKey(), pair.getValue())); + break; + case MESSAGE: + MessageArchiveEntry messageEntry = (MessageArchiveEntry) entry; + restoreMessage(session, messageEntry, restoredMailboxes); + break; + case UNKNOWN: + String entryName = ((UnknownArchiveEntry) entry).entryName(); + LOGGER.warn("unknown entry found in zip :" + entryName); + break; + } + } } } - private Map<SerializedMailboxId, MessageManager> restoreMailboxes(MailboxSession session, List<MailboxWithAnnotationsArchiveEntry> mailboxes) { - return mailboxes.stream() - .flatMap(Throwing.<MailboxWithAnnotationsArchiveEntry, Stream<ImmutablePair<SerializedMailboxId, MessageManager>>>function( - mailboxEntry -> restoreMailboxEntry(session, mailboxEntry).stream()).sneakyThrow()) - .collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)); - } - - private List<MailboxWithAnnotationsArchiveEntry> readMailboxes(MailArchiveIterator iterator) { - ImmutableList.Builder<MailboxWithAnnotationsArchiveEntry> mailboxes = ImmutableList.builder(); - while (iterator.hasNext()) { - MailArchiveEntry entry = iterator.next(); - switch (entry.getType()) { - case MAILBOX: - mailboxes.add((MailboxWithAnnotationsArchiveEntry) entry); - break; - case MESSAGE: - //Ignore for know, TODO: implementation - break; - case UNKNOWN: - String entryName = ((UnknownArchiveEntry) entry).getEntryName(); - LOGGER.warn("unknown entry found in zip :" + entryName); - break; + private void restoreMessage(MailboxSession session, MessageArchiveEntry messageEntry, Map<SerializedMailboxId, MessageManager> mailboxes) { + try { + MessageManager messageManager = mailboxes.get(messageEntry.mailboxId()); + if (messageManager == null) { + LOGGER.warn("Mailbox {} not found for message {}", messageEntry.mailboxId(), messageEntry.messageId()); + return; + } + ByteSourceContent content = ByteSourceContent.of(messageEntry.content()); + MessageManager.AppendCommand command = MessageManager.AppendCommand.builder() + .withInternalDate(messageEntry.internalDate()) + .withFlags(messageEntry.flags()) + .build(content); + AppendResult result = messageManager.appendMessage(command, session); + if (!result.getSize().equals(messageEntry.size())) { + LOGGER.warn("Size {} for message {} different from zip entry one {}", result.getSize(), messageEntry.messageId(), messageEntry.size()); } + } catch (Exception e) { + LOGGER.error("Error restoring message {} to mailbox {}", messageEntry.messageId(), messageEntry.mailboxId(), e); } - return mailboxes.build(); } private Optional<ImmutablePair<SerializedMailboxId, MessageManager>> restoreMailboxEntry(MailboxSession session, diff --git a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ExtraFieldExtractor.java b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ExtraFieldExtractor.java index ceed635c20..57481817ab 100644 --- a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ExtraFieldExtractor.java +++ b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ExtraFieldExtractor.java @@ -19,11 +19,14 @@ package org.apache.james.mailbox.backup.zip; import java.util.Arrays; +import java.util.Date; import java.util.Optional; import java.util.function.Function; import java.util.zip.ZipEntry; import java.util.zip.ZipException; +import jakarta.mail.Flags; + import org.apache.commons.compress.archivers.zip.ExtraFieldUtils; import org.apache.commons.compress.archivers.zip.ZipExtraField; import org.apache.commons.compress.archivers.zip.ZipShort; @@ -40,6 +43,36 @@ public class ExtraFieldExtractor { .flatMap(Function.identity()); } + public static Optional<Long> getLongExtraField(ZipShort id, ZipEntry entry) throws ZipException { + ZipExtraField[] extraFields = ExtraFieldUtils.parse(entry.getExtra()); + return Arrays.stream(extraFields) + .filter(field -> field.getHeaderId().equals(id)) + .map(LongExtraField.class::cast) + .map(LongExtraField::getValue) + .findFirst() + .flatMap(Function.identity()); + } + + public static Optional<Date> getDateExtraField(ZipShort id, ZipEntry entry) throws ZipException { + ZipExtraField[] extraFields = ExtraFieldUtils.parse(entry.getExtra()); + return Arrays.stream(extraFields) + .filter(field -> field.getHeaderId().equals(id)) + .map(InternalDateExtraField.class::cast) + .map(InternalDateExtraField::getDateValue) + .findFirst() + .flatMap(Function.identity()); + } + + public static Optional<Flags> getFlagsExtraField(ZipShort id, ZipEntry entry) throws ZipException { + ZipExtraField[] extraFields = ExtraFieldUtils.parse(entry.getExtra()); + return Arrays.stream(extraFields) + .filter(field -> field.getHeaderId().equals(id)) + .map(FlagsExtraField.class::cast) + .map(FlagsExtraField::getFlagsValue) + .findFirst() + .flatMap(Function.identity()); + } + public static Optional<ZipEntryType> getEntryType(ZipEntry entry) { try { ZipExtraField[] extraFields = ExtraFieldUtils.parse(entry.getExtra()); diff --git a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/FlagsExtraField.java b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/FlagsExtraField.java index 7c9a5c2cee..0ee920b29c 100644 --- a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/FlagsExtraField.java +++ b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/FlagsExtraField.java @@ -48,6 +48,30 @@ public class FlagsExtraField extends StringExtraField implements WithZipHeader { super(Optional.of(serializeFlags(flags))); } + public Optional<Flags> getFlagsValue() { + return getValue().map(FlagsExtraField::parseFlags); + } + + private static Flags parseFlags(String serialized) { + Flags flags = new Flags(); + if (serialized == null || serialized.isEmpty()) { + return flags; + } + String[] parts = serialized.split("%"); + for (String part : parts) { + switch (part) { + case "\\ANSWERED" -> flags.add(Flags.Flag.ANSWERED); + case "\\DELETED" -> flags.add(Flags.Flag.DELETED); + case "\\DRAFT" -> flags.add(Flags.Flag.DRAFT); + case "\\FLAGGED" -> flags.add(Flags.Flag.FLAGGED); + case "\\RECENT" -> flags.add(Flags.Flag.RECENT); + case "\\SEEN" -> flags.add(Flags.Flag.SEEN); + default -> flags.add(part); + } + } + return flags; + } + @Override public ZipShort getHeaderId() { return ID_AP; diff --git a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ZipArchivesLoader.java b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ZipArchivesLoader.java index 14e6838d2a..e7ab6d68ef 100644 --- a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ZipArchivesLoader.java +++ b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ZipArchivesLoader.java @@ -29,7 +29,6 @@ public class ZipArchivesLoader implements MailArchivesLoader { @Override public MailArchiveIterator load(InputStream inputStream) throws IOException { ZipInputStream zipInputStream = new ZipInputStream(inputStream); - ZipEntryIterator zipEntryIterator = new ZipEntryIterator(zipInputStream); - return new ZippedMailAccountIterator(zipEntryIterator); + return new ZippedMailAccountIterator(zipInputStream); } } diff --git a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ZippedMailAccountIterator.java b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ZippedMailAccountIterator.java index 1688045aa6..d27b52ef1a 100644 --- a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ZippedMailAccountIterator.java +++ b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/zip/ZippedMailAccountIterator.java @@ -19,17 +19,23 @@ package org.apache.james.mailbox.backup.zip; import java.io.IOException; +import java.util.Date; import java.util.List; import java.util.NoSuchElementException; import java.util.Optional; import java.util.zip.ZipEntry; import java.util.zip.ZipException; +import java.util.zip.ZipInputStream; + +import jakarta.mail.Flags; import org.apache.commons.lang3.StringUtils; import org.apache.james.mailbox.backup.MailArchiveEntry; import org.apache.james.mailbox.backup.MailArchiveIterator; import org.apache.james.mailbox.backup.MailboxWithAnnotationsArchiveEntry; +import org.apache.james.mailbox.backup.MessageArchiveEntry; import org.apache.james.mailbox.backup.SerializedMailboxId; +import org.apache.james.mailbox.backup.SerializedMessageId; import org.apache.james.mailbox.backup.UnknownArchiveEntry; import org.apache.james.mailbox.model.MailboxAnnotation; import org.slf4j.Logger; @@ -41,37 +47,53 @@ import com.google.common.collect.ImmutableList; public class ZippedMailAccountIterator implements MailArchiveIterator { private static final Logger LOGGER = LoggerFactory.getLogger(ZippedMailAccountIterator.class); private static final List<MailboxAnnotation> NO_ANNOTATION = ImmutableList.of(); - private final ZipEntryIterator zipEntryIterator; - private Optional<ZipEntry> next; + private final ZipInputStream zipInputStream; + private Optional<ZipEntry> currentEntry; + private boolean closed; - public ZippedMailAccountIterator(ZipEntryIterator zipEntryIterator) { - this.zipEntryIterator = zipEntryIterator; - next = Optional.ofNullable(zipEntryIterator.next()); + public ZippedMailAccountIterator(ZipInputStream zipInputStream) { + this.zipInputStream = zipInputStream; + this.currentEntry = Optional.empty(); + this.closed = false; } @Override public void close() throws IOException { - zipEntryIterator.close(); + closed = true; + zipInputStream.close(); } @Override public boolean hasNext() { - return next.isPresent(); + if (closed) { + return false; + } + if (currentEntry.isPresent()) { + return true; + } + try { + closeCurrentEntryIfNeeded(); + ZipEntry nextEntry = zipInputStream.getNextEntry(); + currentEntry = Optional.ofNullable(nextEntry); + return currentEntry.isPresent(); + } catch (IOException e) { + LOGGER.error("Error reading next zip entry", e); + return false; + } } @Override public MailArchiveEntry next() { - return next.map(this::doNext).orElseThrow(() -> new NoSuchElementException()); - } - - private MailArchiveEntry doNext(ZipEntry currentElement) { - next = Optional.ofNullable(zipEntryIterator.next()); + if (!hasNext()) { + throw new NoSuchElementException(); + } + ZipEntry entry = currentEntry.get(); + currentEntry = Optional.empty(); try { - return getMailArchiveEntry(currentElement); + return getMailArchiveEntry(entry); } catch (Exception e) { - LOGGER.error("Error when reading archive on entry : " + currentElement.getName(), e); - next = Optional.empty(); - return new UnknownArchiveEntry(currentElement.getName()); + LOGGER.error("Error when reading archive on entry : " + entry.getName(), e); + return new UnknownArchiveEntry(entry.getName()); } } @@ -84,6 +106,14 @@ public class ZippedMailAccountIterator implements MailArchiveIterator { .orElseGet(() -> new UnknownArchiveEntry(currentElement.getName())); } + private void closeCurrentEntryIfNeeded() { + try { + zipInputStream.closeEntry(); + } catch (IOException e) { + LOGGER.warn("Error closing zip entry", e); + } + } + private Optional<SerializedMailboxId> getMailBoxId(ZipEntry entry) throws ZipException { return ExtraFieldExtractor.getStringExtraField(MailboxIdExtraField.ID_AM, entry) .map(SerializedMailboxId::new); @@ -97,10 +127,25 @@ public class ZippedMailAccountIterator implements MailArchiveIterator { return new MailboxWithAnnotationsArchiveEntry(getMailboxName(current), getMailBoxId(current).get(), NO_ANNOTATION); } + private MailArchiveEntry fromMessageEntry(ZipEntry current) throws ZipException { + SerializedMessageId messageId = ExtraFieldExtractor.getStringExtraField(MessageIdExtraField.ID_AL, current) + .map(SerializedMessageId::new) + .orElseThrow(() -> new ZipException("Message entry missing messageId")); + SerializedMailboxId mailboxId = getMailBoxId(current) + .orElseThrow(() -> new ZipException("Message entry missing mailboxId")); + long size = ExtraFieldExtractor.getLongExtraField(SizeExtraField.ID_AJ, current).orElse(0L); + Date internalDate = ExtraFieldExtractor.getDateExtraField(InternalDateExtraField.ID_AO, current).orElse(new Date()); + Flags flags = ExtraFieldExtractor.getFlagsExtraField(FlagsExtraField.ID_AP, current).orElse(new Flags()); + + return new MessageArchiveEntry(messageId, mailboxId, size, internalDate, flags, zipInputStream); + } + private MailArchiveEntry from(ZipEntry current, ZipEntryType currentEntryType) throws ZipException { switch (currentEntryType) { case MAILBOX: return fromMailboxEntry(current); + case MESSAGE: + return fromMessageEntry(current); default: return new UnknownArchiveEntry(current.getName()); } diff --git a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/RestoreServiceTest.java b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/RestoreServiceTest.java index 0ddc394ccb..a983e1382e 100644 --- a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/RestoreServiceTest.java +++ b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/RestoreServiceTest.java @@ -19,6 +19,7 @@ package org.apache.james.webadmin.service; +import static org.apache.james.mailbox.backup.MailboxMessageFixture.DATE_1; import static org.apache.james.webadmin.service.ExportServiceTestSystem.BOB; import static org.apache.james.webadmin.service.ExportServiceTestSystem.CEDRIC; import static org.assertj.core.api.Assertions.assertThat; @@ -29,7 +30,9 @@ import static org.mockito.Mockito.doThrow; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; -import java.nio.charset.StandardCharsets; +import java.util.Date; + +import jakarta.mail.Flags; import org.apache.james.blob.api.BlobId; import org.apache.james.blob.api.BlobStore; @@ -51,13 +54,12 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; -import com.github.fge.lambdas.Throwing; - import reactor.core.publisher.Mono; @ExtendWith(FileSystemExtension.class) class RestoreServiceTest { private static final int BUFFER_SIZE = 4096; + private static String OTHER_MAILBOX = "otherMailbox"; private static final String MESSAGE_CONTENT = "MIME-Version: 1.0\r\n" + "Subject: test\r\n" + "Content-Type: text/plain; charset=UTF-8\r\n" + @@ -87,7 +89,7 @@ class RestoreServiceTest { @Test void restoreShouldReturnCompleteWhenExistingUserWithoutDataAndNonEmptyZip() throws Exception { - createAMailboxWithAMail(MESSAGE_CONTENT); + createAMailboxWithAMail(); ByteArrayOutputStream destination = new ByteArrayOutputStream(BUFFER_SIZE); testSystem.backup.backupAccount(BOB, destination); @@ -101,7 +103,7 @@ class RestoreServiceTest { @Test void restoreShouldReturnPartialWhenNonEmptyAccount() throws Exception { - createAMailboxWithAMail(MESSAGE_CONTENT); + createAMailboxWithAMail(); ByteArrayOutputStream destination = new ByteArrayOutputStream(BUFFER_SIZE); testSystem.backup.backupAccount(BOB, destination); @@ -119,7 +121,7 @@ class RestoreServiceTest { .when(testSystem.blobStore) .read(any(), any()); - createAMailboxWithAMail(MESSAGE_CONTENT); + createAMailboxWithAMail(); ByteArrayOutputStream destination = new ByteArrayOutputStream(BUFFER_SIZE); testSystem.backup.backupAccount(BOB, destination); @@ -148,7 +150,7 @@ class RestoreServiceTest { @Test void restoreShouldRestoreContentFromNonEmptyZip() throws Exception { - createAMailboxWithAMail(MESSAGE_CONTENT); + createAMailboxWithAMail(); ByteArrayOutputStream destination = new ByteArrayOutputStream(BUFFER_SIZE); testSystem.backup.backupAccount(BOB, destination); @@ -161,15 +163,99 @@ class RestoreServiceTest { MailboxSession cedricSession = testSystem.mailboxManager.createSystemSession(CEDRIC); MessageManager mailbox = testSystem.mailboxManager.getMailbox(MailboxPath.inbox(CEDRIC), cedricSession); MessageResultIterator resultIterator = mailbox.getMessages(MessageRange.all(), FetchGroup.FULL_CONTENT, cedricSession); + assertThat(resultIterator).toIterable() + .hasSize(1) + .first() + .satisfies(result -> assertThat(new String(result.getFullContent().getInputStream().readAllBytes())).isEqualTo(MESSAGE_CONTENT)); + } + + @Test + void restoreShouldPreserveFlags() throws Exception { + Flags messageFlags = new Flags("customFlag"); + messageFlags.add(Flags.Flag.SEEN); + createAMailboxWithAMail(messageFlags, new Date()); + + ByteArrayOutputStream destination = new ByteArrayOutputStream(BUFFER_SIZE); + testSystem.backup.backupAccount(BOB, destination); + + InputStream source = new ByteArrayInputStream(destination.toByteArray()); + BlobId blobId = Mono.from(testSystem.blobStore.save(testSystem.blobStore.getDefaultBucketName(), source, BlobStore.StoragePolicy.LOW_COST)).block(); + + testee.restore(CEDRIC, blobId).block(); + + MailboxSession cedricSession = testSystem.mailboxManager.createSystemSession(CEDRIC); + MessageManager mailbox = testSystem.mailboxManager.getMailbox(MailboxPath.inbox(CEDRIC), cedricSession); + MessageResultIterator resultIterator = mailbox.getMessages(MessageRange.all(), FetchGroup.FULL_CONTENT, cedricSession); + assertThat(resultIterator).toIterable() + .hasSize(1) + .first() + .satisfies(result -> assertThat(result.getFlags()).isEqualTo(messageFlags)); + } + + @Test + void restoreShouldPreserveInternalDate() throws Exception { + Date internalDate = new Date(DATE_1.toEpochSecond()); + createAMailboxWithAMail(new Flags(), internalDate); + + ByteArrayOutputStream destination = new ByteArrayOutputStream(BUFFER_SIZE); + testSystem.backup.backupAccount(BOB, destination); + + InputStream source = new ByteArrayInputStream(destination.toByteArray()); + BlobId blobId = Mono.from(testSystem.blobStore.save(testSystem.blobStore.getDefaultBucketName(), source, BlobStore.StoragePolicy.LOW_COST)).block(); + + testee.restore(CEDRIC, blobId).block(); + + MailboxSession cedricSession = testSystem.mailboxManager.createSystemSession(CEDRIC); + MessageManager mailbox = testSystem.mailboxManager.getMailbox(MailboxPath.inbox(CEDRIC), cedricSession); + MessageResultIterator resultIterator = mailbox.getMessages(MessageRange.all(), FetchGroup.FULL_CONTENT, cedricSession); + assertThat(resultIterator).toIterable() + .hasSize(1) + .first() + .satisfies(result -> assertThat(result.getInternalDate()).isEqualTo(internalDate)); + } + + @Test + void restoreShouldRestoreMultipleMailboxesAndMessages() throws Exception { + MailboxPath bobInboxPath = MailboxPath.inbox(BOB); + MailboxPath otherBobMailboxPath = MailboxPath.forUser(BOB, OTHER_MAILBOX); + testSystem.mailboxManager.createMailbox(bobInboxPath, testSystem.bobSession); + testSystem.mailboxManager.createMailbox(otherBobMailboxPath, testSystem.bobSession); + + testSystem.mailboxManager.getMailbox(bobInboxPath, testSystem.bobSession) + .appendMessage(MessageManager.AppendCommand.builder() + .build(MESSAGE_CONTENT), + testSystem.bobSession); + testSystem.mailboxManager.getMailbox(bobInboxPath, testSystem.bobSession) + .appendMessage(MessageManager.AppendCommand.builder() + .build(MESSAGE_CONTENT), + testSystem.bobSession); + testSystem.mailboxManager.getMailbox(otherBobMailboxPath, testSystem.bobSession) + .appendMessage(MessageManager.AppendCommand.builder() + .build(MESSAGE_CONTENT), + testSystem.bobSession); + + ByteArrayOutputStream destination = new ByteArrayOutputStream(BUFFER_SIZE); + testSystem.backup.backupAccount(BOB, destination); + + InputStream source = new ByteArrayInputStream(destination.toByteArray()); + BlobId blobId = Mono.from(testSystem.blobStore.save(testSystem.blobStore.getDefaultBucketName(), source, BlobStore.StoragePolicy.LOW_COST)).block(); + + testee.restore(CEDRIC, blobId).block(); + + MailboxSession cedricSession = testSystem.mailboxManager.createSystemSession(CEDRIC); + MessageManager inbox = testSystem.mailboxManager.getMailbox(MailboxPath.inbox(CEDRIC), cedricSession); + MessageManager otherMailbox = testSystem.mailboxManager.getMailbox(MailboxPath.forUser(CEDRIC, OTHER_MAILBOX), cedricSession); + MessageResultIterator inboxMessages = inbox.getMessages(MessageRange.all(), FetchGroup.FULL_CONTENT, cedricSession); + MessageResultIterator otherMailboxMessages = otherMailbox.getMessages(MessageRange.all(), FetchGroup.FULL_CONTENT, cedricSession); SoftAssertions.assertSoftly(softly -> { - softly.assertThat(resultIterator).toIterable().hasSize(1); - softly.assertThat(Throwing.supplier(() -> resultIterator.next().getBody().asBytesSequence())).isEqualTo(MESSAGE_CONTENT.getBytes(StandardCharsets.UTF_8)); + softly.assertThat(inboxMessages).toIterable().hasSize(2); + softly.assertThat(otherMailboxMessages).toIterable().hasSize(1); }); } @Test void restoreShouldDeleteBlobAfterCompletion() throws Exception { - createAMailboxWithAMail(MESSAGE_CONTENT); + createAMailboxWithAMail(); ByteArrayOutputStream destination = new ByteArrayOutputStream(BUFFER_SIZE); testSystem.backup.backupAccount(BOB, destination); @@ -183,12 +269,18 @@ class RestoreServiceTest { .isInstanceOf(ObjectNotFoundException.class); } - private ComposedMessageId createAMailboxWithAMail(String message) throws MailboxException { + private ComposedMessageId createAMailboxWithAMail() throws MailboxException { + return createAMailboxWithAMail(new Flags(), new Date()); + } + + private ComposedMessageId createAMailboxWithAMail(Flags flags, Date internalDate) throws MailboxException { MailboxPath bobInboxPath = MailboxPath.inbox(BOB); testSystem.mailboxManager.createMailbox(bobInboxPath, testSystem.bobSession); return testSystem.mailboxManager.getMailbox(bobInboxPath, testSystem.bobSession) .appendMessage(MessageManager.AppendCommand.builder() - .build(message), + .withFlags(flags) + .withInternalDate(internalDate) + .build(MESSAGE_CONTENT), testSystem.bobSession) .getId(); } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
