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]

Reply via email to