This is an automated email from the ASF dual-hosted git repository. btellier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit e2bf45be00994739841c46b417c4d8ca6de33b1d Author: Tran Tien Duc <[email protected]> AuthorDate: Thu Mar 14 11:49:19 2019 +0700 MAILBOX-385 DeletedMessageZipper impl --- mailbox/backup/pom.xml | 1 - mailbox/plugin/deleted-messages-vault/pom.xml | 18 ++ .../org/apache/james/vault/DeletedMessage.java | 3 + .../james/vault/DeletedMessageConverter.java | 2 +- .../james/vault/DeletedMessageWithContent.java | 59 +++++ .../apache/james/vault/DeletedMessageZipper.java | 104 +++++++++ .../apache/james/vault/DeletedMessageFixture.java | 5 +- .../org/apache/james/vault/DeletedMessageTest.java | 16 ++ .../james/vault/DeletedMessageZipperTest.java | 241 +++++++++++++++++++++ pom.xml | 16 ++ .../routes/DeletedMessagesVaultRoutesTest.java | 22 +- 11 files changed, 475 insertions(+), 12 deletions(-) diff --git a/mailbox/backup/pom.xml b/mailbox/backup/pom.xml index 57409e7..cdd122e 100644 --- a/mailbox/backup/pom.xml +++ b/mailbox/backup/pom.xml @@ -63,7 +63,6 @@ <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-compress</artifactId> - <version>1.18</version> </dependency> <dependency> <groupId>org.assertj</groupId> diff --git a/mailbox/plugin/deleted-messages-vault/pom.xml b/mailbox/plugin/deleted-messages-vault/pom.xml index 0d09332..97e2aea 100644 --- a/mailbox/plugin/deleted-messages-vault/pom.xml +++ b/mailbox/plugin/deleted-messages-vault/pom.xml @@ -67,6 +67,16 @@ </dependency> <dependency> <groupId>${james.groupId}</groupId> + <artifactId>backup</artifactId> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>backup</artifactId> + <scope>test</scope> + <type>test-jar</type> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> <artifactId>james-server-core</artifactId> </dependency> <dependency> @@ -79,6 +89,10 @@ <scope>test</scope> </dependency> <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-compress</artifactId> + </dependency> + <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <scope>test</scope> @@ -93,5 +107,9 @@ <artifactId>junit-platform-launcher</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + </dependency> </dependencies> </project> \ No newline at end of file diff --git a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessage.java b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessage.java index 110c191..0f549cc 100644 --- a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessage.java +++ b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessage.java @@ -34,6 +34,7 @@ import org.apache.james.vault.DeletedMessage.Builder.FinalStage; import org.apache.james.vault.DeletedMessage.Builder.Steps.RequireMetadata; import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; public class DeletedMessage { @@ -171,6 +172,8 @@ public class DeletedMessage { public DeletedMessage(MessageId messageId, List<MailboxId> originMailboxes, User owner, ZonedDateTime deliveryDate, ZonedDateTime deletionDate, MaybeSender sender, List<MailAddress> recipients, Optional<String> subject, boolean hasAttachment, long size) { + Preconditions.checkArgument(size > 0, "'size' is required to be a strictly positive number"); + this.messageId = messageId; this.originMailboxes = originMailboxes; this.owner = owner; diff --git a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageConverter.java b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageConverter.java index 105929b..af03284 100644 --- a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageConverter.java +++ b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageConverter.java @@ -61,7 +61,7 @@ class DeletedMessageConverter { .sender(retrieveSender(mimeMessage)) .recipients(retrieveRecipients(mimeMessage)) .hasAttachment(!message.getAttachments().isEmpty()) - .size(message.getBodyOctets() + message.getHeaderOctets()) + .size(message.getFullContentOctets()) .subject(mimeMessage.map(Message::getSubject)) .build(); } diff --git a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageWithContent.java b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageWithContent.java new file mode 100644 index 0000000..b8ce977 --- /dev/null +++ b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageWithContent.java @@ -0,0 +1,59 @@ +/**************************************************************** + * 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.vault; + +import java.io.IOException; +import java.io.InputStream; + +/** + * This class carries a {@link org.apache.james.vault.DeletedMessage} + * and its data inside an InputStream. + * + * The InputStream is created and maintained by the callers. + */ +public class DeletedMessageWithContent implements AutoCloseable { + + private final DeletedMessage deletedMessage; + private final InputStream content; + + public DeletedMessageWithContent(DeletedMessage deletedMessage, InputStream content) { + this.deletedMessage = deletedMessage; + this.content = content; + } + + public DeletedMessage getDeletedMessage() { + return deletedMessage; + } + + /** + * Returns the original InputStream passed to the constructor. + * Thus, if the InputStream is already closed by the callers, it cannot be reused + * + * @return content + */ + public InputStream getContent() { + return content; + } + + @Override + public void close() throws IOException { + content.close(); + } +} diff --git a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageZipper.java b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageZipper.java new file mode 100644 index 0000000..4f00e46 --- /dev/null +++ b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageZipper.java @@ -0,0 +1,104 @@ +/**************************************************************** + * 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.vault; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Optional; +import java.util.stream.Stream; + +import org.apache.commons.compress.archivers.zip.ExtraFieldUtils; +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; +import org.apache.commons.io.IOUtils; +import org.apache.james.mailbox.backup.MessageIdExtraField; +import org.apache.james.mailbox.backup.SizeExtraField; +import org.apache.james.mailbox.model.MessageId; +import org.apache.james.util.OptionalUtils; +import org.reactivestreams.Publisher; + +import com.github.fge.lambdas.Throwing; +import com.google.common.annotations.VisibleForTesting; + +import reactor.core.publisher.Mono; + +public class DeletedMessageZipper { + + interface DeletedMessageContentLoader { + Publisher<InputStream> load(DeletedMessage deletedMessage); + } + + DeletedMessageZipper() { + ExtraFieldUtils.register(MessageIdExtraField.class); + ExtraFieldUtils.register(SizeExtraField.class); + } + + public void zip(DeletedMessageContentLoader contentLoader, Stream<DeletedMessage> deletedMessages, + OutputStream outputStream) throws IOException { + try (ZipArchiveOutputStream zipOutputStream = newZipArchiveOutputStream(outputStream)) { + deletedMessages + .map(message -> messageWithContent(message, contentLoader)) + .flatMap(OptionalUtils::toStream) + .forEach(Throwing.<DeletedMessageWithContent>consumer( + messageWithContent -> putMessageToEntry(zipOutputStream, messageWithContent)).sneakyThrow()); + + zipOutputStream.finish(); + } + } + + @VisibleForTesting + Optional<DeletedMessageWithContent> messageWithContent(DeletedMessage message, DeletedMessageContentLoader loader) { + return Mono.from(loader.load(message)) + .map(messageContent -> new DeletedMessageWithContent(message, messageContent)) + .blockOptional(); + } + + @VisibleForTesting + ZipArchiveOutputStream newZipArchiveOutputStream(OutputStream outputStream) { + return new ZipArchiveOutputStream(outputStream); + } + + @VisibleForTesting + void putMessageToEntry(ZipArchiveOutputStream zipOutputStream, DeletedMessageWithContent message) throws IOException { + try (DeletedMessageWithContent closableMessage = message) { + ZipArchiveEntry archiveEntry = createEntry(zipOutputStream, message); + zipOutputStream.putArchiveEntry(archiveEntry); + + IOUtils.copy(message.getContent(), zipOutputStream); + + zipOutputStream.closeArchiveEntry(); + } + } + + @VisibleForTesting + ZipArchiveEntry createEntry(ZipArchiveOutputStream zipOutputStream, + DeletedMessageWithContent fullMessage) throws IOException { + DeletedMessage message = fullMessage.getDeletedMessage(); + MessageId messageId = message.getMessageId(); + + ZipArchiveEntry archiveEntry = (ZipArchiveEntry) zipOutputStream.createArchiveEntry(new File(messageId.serialize()), messageId.serialize()); + archiveEntry.addExtraField(new MessageIdExtraField(messageId.serialize())); + archiveEntry.addExtraField(new SizeExtraField(message.getSize())); + + return archiveEntry; + } +} diff --git a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageFixture.java b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageFixture.java index ee3016c..73c72fb 100644 --- a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageFixture.java +++ b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageFixture.java @@ -59,7 +59,7 @@ public interface DeletedMessageFixture { .hasAttachment(false) .size(CONTENT.length) .build(); - Supplier<DeletedMessage.Builder.FinalStage> FINAL_STAGE = () -> DeletedMessage.builder() + DeletedMessage.Builder.RequireSize<DeletedMessage.Builder.FinalStage> SIZE_STAGE = DeletedMessage.builder() .messageId(MESSAGE_ID) .originMailboxes(MAILBOX_ID_1, MAILBOX_ID_2) .user(USER) @@ -67,7 +67,8 @@ public interface DeletedMessageFixture { .deletionDate(DELETION_DATE) .sender(MaybeSender.of(SENDER)) .recipients(RECIPIENT1, RECIPIENT2) - .hasAttachment(false) + .hasAttachment(false); + Supplier<DeletedMessage.Builder.FinalStage> FINAL_STAGE = () -> SIZE_STAGE .size(CONTENT.length); DeletedMessage DELETED_MESSAGE_WITH_SUBJECT = FINAL_STAGE.get() .subject(SUBJECT) diff --git a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageTest.java b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageTest.java index e6e35bf..57880e4 100644 --- a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageTest.java +++ b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageTest.java @@ -27,12 +27,14 @@ import static org.apache.james.vault.DeletedMessageFixture.DELIVERY_DATE; import static org.apache.james.vault.DeletedMessageFixture.MAILBOX_ID_1; import static org.apache.james.vault.DeletedMessageFixture.MAILBOX_ID_2; import static org.apache.james.vault.DeletedMessageFixture.MESSAGE_ID; +import static org.apache.james.vault.DeletedMessageFixture.SIZE_STAGE; import static org.apache.james.vault.DeletedMessageFixture.SUBJECT; import static org.apache.james.vault.DeletedMessageFixture.USER; import static org.apache.mailet.base.MailAddressFixture.RECIPIENT1; import static org.apache.mailet.base.MailAddressFixture.RECIPIENT2; import static org.apache.mailet.base.MailAddressFixture.SENDER; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.apache.james.core.MaybeSender; import org.assertj.core.api.SoftAssertions; @@ -69,4 +71,18 @@ class DeletedMessageTest { void buildShouldReturnDeletedMessageWithSubject() { assertThat(DELETED_MESSAGE_WITH_SUBJECT.getSubject()).contains(SUBJECT); } + + @Test + void buildShouldThrowWhenPassingZeroSize() { + assertThatThrownBy(() -> SIZE_STAGE.size(0L).build()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("'size' is required to be a strictly positive number"); + } + + @Test + void buildShouldThrowWhenPassingNegativeSize() { + assertThatThrownBy(() -> SIZE_STAGE.size(-1L).build()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("'size' is required to be a strictly positive number"); + } } \ No newline at end of file diff --git a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageZipperTest.java b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageZipperTest.java new file mode 100644 index 0000000..6614eaf --- /dev/null +++ b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageZipperTest.java @@ -0,0 +1,241 @@ +/**************************************************************** + * 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.vault; + +import static org.apache.james.mailbox.backup.ZipAssert.EntryChecks.hasName; +import static org.apache.james.mailbox.backup.ZipAssert.assertThatZip; +import static org.apache.james.vault.DeletedMessageFixture.CONTENT; +import static org.apache.james.vault.DeletedMessageFixture.DELETED_MESSAGE; +import static org.apache.james.vault.DeletedMessageFixture.DELETED_MESSAGE_2; +import static org.apache.james.vault.DeletedMessageFixture.MESSAGE_ID; +import static org.apache.james.vault.DeletedMessageFixture.MESSAGE_ID_2; +import static org.apache.james.vault.DeletedMessageZipper.DeletedMessageContentLoader; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; +import org.apache.commons.compress.archivers.zip.ZipFile; +import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; +import org.apache.james.mailbox.backup.MessageIdExtraField; +import org.apache.james.mailbox.backup.SizeExtraField; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import com.github.fge.lambdas.Throwing; + +import reactor.core.publisher.Mono; + +class DeletedMessageZipperTest { + + private class ZipArchiveStreamCaptor implements Answer<ZipArchiveOutputStream> { + + ZipArchiveOutputStream captured; + + @Override + public ZipArchiveOutputStream answer(InvocationOnMock invocation) throws Throwable { + ZipArchiveOutputStream zipArchiveOutputStream = (ZipArchiveOutputStream) invocation.callRealMethod(); + captured = spy(zipArchiveOutputStream); + return captured; + } + } + + private class LoadedResourcesStreamCaptor implements Answer<Optional<DeletedMessageWithContent>> { + + List<DeletedMessageWithContent> captured = new ArrayList<>(); + + @Override + public Optional<DeletedMessageWithContent> answer(InvocationOnMock invocation) throws Throwable { + Optional<DeletedMessageWithContent> messageWithContent = (Optional<DeletedMessageWithContent>) invocation.callRealMethod(); + return messageWithContent.map(this::returnSpied); + } + + private DeletedMessageWithContent returnSpied(DeletedMessageWithContent message) { + DeletedMessageWithContent spied = spy(message); + captured.add(spied); + return spied; + } + } + + private static final DeletedMessageContentLoader CONTENT_LOADER = message -> Mono.just(new ByteArrayInputStream(CONTENT)); + private static final String MESSAGE_CONTENT = new String(CONTENT, StandardCharsets.UTF_8); + private DeletedMessageZipper zipper; + + @BeforeEach + void beforeEach() { + zipper = spy(new DeletedMessageZipper()); + } + + @Test + void zipShouldPutEntriesToOutputStream() throws Exception { + ByteArrayOutputStream messageContents = zip(Stream.of(DELETED_MESSAGE, DELETED_MESSAGE_2)); + try (ZipFile zipFile = zipFile(messageContents)) { + assertThatZip(zipFile) + .containsOnlyEntriesMatching( + hasName(MESSAGE_ID.serialize()).hasStringContent(MESSAGE_CONTENT), + hasName(MESSAGE_ID_2.serialize()).hasStringContent(MESSAGE_CONTENT)); + } + } + + @Test + void zipShouldPutExtraFields() throws Exception { + ByteArrayOutputStream messageContents = zip(Stream.of(DELETED_MESSAGE)); + try (ZipFile zipFile = zipFile(messageContents)) { + assertThatZip(zipFile) + .containsOnlyEntriesMatching( + hasName(MESSAGE_ID.serialize()) + .containsExtraFields(new MessageIdExtraField(MESSAGE_ID)) + .containsExtraFields(new SizeExtraField(CONTENT.length))); + } + } + + @Test + void constructorShouldNotFailWhenCalledMultipleTimes() { + assertThatCode(() -> { + new DeletedMessageZipper(); + new DeletedMessageZipper(); + }).doesNotThrowAnyException(); + } + + @Test + void zipShouldCloseAllResourcesStreamWhenFinishZipping() throws Exception { + LoadedResourcesStreamCaptor captor = new LoadedResourcesStreamCaptor(); + doAnswer(captor) + .when(zipper).messageWithContent(any(), any()); + + zip(Stream.of(DELETED_MESSAGE, DELETED_MESSAGE_2)); + + List<DeletedMessageWithContent> loadedResources = captor.captured; + assertThat(loadedResources) + .hasSize(2); + loadedResources.stream() + .forEach(Throwing.consumer(spiedMessage -> verify(spiedMessage, times(1)).close())); + } + + @Test + void zipShouldTerminateZipArchiveStreamWhenFinishZipping() throws Exception { + ZipArchiveStreamCaptor captor = new ZipArchiveStreamCaptor(); + doAnswer(captor) + .when(zipper).newZipArchiveOutputStream(any()); + + zip(Stream.of(DELETED_MESSAGE, DELETED_MESSAGE_2)); + + ZipArchiveOutputStream captured = captor.captured; + verify(captured, times(1)).finish(); + verify(captured, times(1)).close(); + } + + @Test + void zipShouldThrowWhenCreateEntryGetException() throws Exception { + doThrow(new IOException("mocked exception")) + .when(zipper).createEntry(any(), any()); + + assertThatThrownBy(() -> zip(Stream.of(DELETED_MESSAGE, DELETED_MESSAGE_2))) + .isInstanceOf(IOException.class); + } + + @Test + void zipShouldThrowWhenPutMessageToEntryGetException() throws Exception { + doThrow(new IOException("mocked exception")) + .when(zipper).putMessageToEntry(any(), any()); + + assertThatThrownBy(() -> zip(Stream.of(DELETED_MESSAGE, DELETED_MESSAGE_2))) + .isInstanceOf(IOException.class); + } + + @Test + void zipShouldStopLoadingResourcesWhenGettingException() throws Exception { + doThrow(new IOException("mocked exception")) + .when(zipper).createEntry(any(), any()); + + LoadedResourcesStreamCaptor captor = new LoadedResourcesStreamCaptor(); + doAnswer(captor) + .when(zipper).messageWithContent(any(), any()); + + assertThatThrownBy(() -> zip(Stream.of(DELETED_MESSAGE, DELETED_MESSAGE_2))) + .isInstanceOf(IOException.class); + + List<DeletedMessageWithContent> loadedResources = captor.captured; + assertThat(loadedResources) + .hasSize(1); + loadedResources.stream() + .forEach(Throwing.consumer(spiedMessage -> verify(spiedMessage, times(1)).close())); + } + + @Test + void zipShouldCloseParameterOutputStreamWhenGettingException() throws Exception { + doThrow(new IOException("mocked exception")) + .when(zipper).putMessageToEntry(any(), any()); + + ByteArrayOutputStream outputStream = spy(new ByteArrayOutputStream()); + assertThatThrownBy(() -> zipper.zip(CONTENT_LOADER, Stream.of(DELETED_MESSAGE), outputStream)) + .isInstanceOf(IOException.class); + + verify(outputStream, times(1)).close(); + } + + @Test + void zipShouldTerminateZipArchiveStreamWhenGettingException() throws Exception { + doThrow(new IOException("mocked exception")) + .when(zipper).putMessageToEntry(any(), any()); + + ZipArchiveStreamCaptor captor = new ZipArchiveStreamCaptor(); + doAnswer(captor) + .when(zipper).newZipArchiveOutputStream(any()); + + assertThatThrownBy(() -> zip(Stream.of(DELETED_MESSAGE, DELETED_MESSAGE_2))) + .isInstanceOf(IOException.class); + + ZipArchiveOutputStream captured = captor.captured; + verify(captured, times(1)).finish(); + verify(captured, times(1)).close(); + } + + private ByteArrayOutputStream zip(Stream<DeletedMessage> deletedMessages, DeletedMessageZipper zipper) throws IOException { + ByteArrayOutputStream zipOutputStream = new ByteArrayOutputStream(); + zipper.zip(CONTENT_LOADER, deletedMessages, zipOutputStream); + return zipOutputStream; + } + + private ByteArrayOutputStream zip(Stream<DeletedMessage> deletedMessages) throws IOException { + return zip(deletedMessages, zipper); + } + + private ZipFile zipFile(ByteArrayOutputStream output) throws IOException { + return new ZipFile(new SeekableInMemoryByteChannel(output.toByteArray())); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index bac9810..bed7ab4 100644 --- a/pom.xml +++ b/pom.xml @@ -1044,6 +1044,17 @@ </dependency> <dependency> <groupId>${james.groupId}</groupId> + <artifactId>backup</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>backup</artifactId> + <version>${project.version}</version> + <type>test-jar</type> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> <artifactId>james-core</artifactId> <version>${project.version}</version> </dependency> @@ -2155,6 +2166,11 @@ </dependency> <dependency> <groupId>org.apache.commons</groupId> + <artifactId>commons-compress</artifactId> + <version>1.18</version> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.7</version> </dependency> diff --git a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java index cf7ac0a..793edb9 100644 --- a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java +++ b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java @@ -799,7 +799,10 @@ class DeletedMessagesVaultRoutesTest { @Test void restoreShouldAppendMessageToMailboxWhenMatchingNoAttachment() throws Exception { - DeletedMessage deletedMessage = buildMessageWithHasAttachment(false); + DeletedMessage deletedMessage = messageWithAttachmentBuilder() + .hasAttachment(false) + .size(CONTENT.length) + .build(); storeDeletedMessage(deletedMessage); String query = @@ -832,7 +835,10 @@ class DeletedMessagesVaultRoutesTest { @Test void restoreShouldAppendMessageToMailboxWhenMatchingHasAttachment() throws Exception { - DeletedMessage deletedMessage = buildMessageWithHasAttachment(true); + DeletedMessage deletedMessage = messageWithAttachmentBuilder() + .hasAttachment() + .size(CONTENT.length) + .build(); storeDeletedMessage(deletedMessage); String query = @@ -865,7 +871,10 @@ class DeletedMessagesVaultRoutesTest { @Test void restoreShouldNotAppendMessageToMailboxWhenMatchingHasNoAttachment() throws Exception { - DeletedMessage deletedMessage = buildMessageWithHasAttachment(false); + DeletedMessage deletedMessage = messageWithAttachmentBuilder() + .hasAttachment(false) + .size(CONTENT.length) + .build(); storeDeletedMessage(deletedMessage); String query = @@ -1610,7 +1619,7 @@ class DeletedMessagesVaultRoutesTest { return ImmutableList.copyOf(messageManager.getMessages(MessageRange.all(), FetchGroupImpl.MINIMAL, session)); } - private DeletedMessage buildMessageWithHasAttachment(boolean hasAttachment) { + private DeletedMessage.Builder.RequireHasAttachment<DeletedMessage.Builder.RequireSize<DeletedMessage.Builder.FinalStage>> messageWithAttachmentBuilder() { return DeletedMessage.builder() .messageId(InMemoryMessageId.of(MESSAGE_ID_GENERATOR.incrementAndGet())) .originMailboxes(MAILBOX_ID_1) @@ -1618,10 +1627,7 @@ class DeletedMessagesVaultRoutesTest { .deliveryDate(DELIVERY_DATE) .deletionDate(DELETION_DATE) .sender(MaybeSender.of(SENDER)) - .recipients(RECIPIENT1, RECIPIENT2) - .hasAttachment(hasAttachment) - .size(CONTENT.length) - .build(); + .recipients(RECIPIENT1, RECIPIENT2); } private DeletedMessage storeDeletedMessage(DeletedMessage deletedMessage) { --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
