JAMES-2426 Allow to backup messages as ZIP files
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/84338dc4 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/84338dc4 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/84338dc4 Branch: refs/heads/master Commit: 84338dc4c71c61e1da26ad9c8f8740d13bc0042d Parents: ecb7c94 Author: Raphael Ouazana <[email protected]> Authored: Wed Jun 13 18:17:26 2018 +0200 Committer: benwa <[email protected]> Committed: Tue Jun 19 15:07:55 2018 +0700 ---------------------------------------------------------------------- mailbox/backup/pom.xml | 6 ++ .../org/apache/james/mailbox/backup/Backup.java | 4 +- .../org/apache/james/mailbox/backup/Zipper.java | 16 +++- .../mailbox/backup/MailboxMessageFixture.java | 83 ++++++++++++++++++++ .../mailbox/backup/ZipArchiveEntryAssert.java | 79 +++++++++++++++++++ .../apache/james/mailbox/backup/ZipAssert.java | 76 ++++++++++++++++++ .../apache/james/mailbox/backup/ZipperTest.java | 62 +++++++++++++-- 7 files changed, 317 insertions(+), 9 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/84338dc4/mailbox/backup/pom.xml ---------------------------------------------------------------------- diff --git a/mailbox/backup/pom.xml b/mailbox/backup/pom.xml index 613dcfc..2628ca3 100644 --- a/mailbox/backup/pom.xml +++ b/mailbox/backup/pom.xml @@ -39,6 +39,12 @@ </dependency> <dependency> <groupId>${project.groupId}</groupId> + <artifactId>apache-james-mailbox-api</artifactId> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> <artifactId>apache-james-mailbox-store</artifactId> </dependency> <dependency> http://git-wip-us.apache.org/repos/asf/james-project/blob/84338dc4/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/Backup.java ---------------------------------------------------------------------- diff --git a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/Backup.java b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/Backup.java index 2f2d6a4..80f246f 100644 --- a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/Backup.java +++ b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/Backup.java @@ -18,13 +18,13 @@ ****************************************************************/ package org.apache.james.mailbox.backup; -import java.io.File; import java.io.IOException; +import java.io.OutputStream; import java.util.List; import org.apache.james.mailbox.store.mail.model.MailboxMessage; public interface Backup { - void archive(List<MailboxMessage> messages, File destination) throws IOException; + void archive(List<MailboxMessage> messages, OutputStream destination) throws IOException; } http://git-wip-us.apache.org/repos/asf/james-project/blob/84338dc4/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/Zipper.java ---------------------------------------------------------------------- diff --git a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/Zipper.java b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/Zipper.java index c3ee4fb..c6d95ad 100644 --- a/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/Zipper.java +++ b/mailbox/backup/src/main/java/org/apache/james/mailbox/backup/Zipper.java @@ -20,17 +20,31 @@ package org.apache.james.mailbox.backup; import java.io.File; import java.io.IOException; +import java.io.OutputStream; import java.util.List; +import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; +import org.apache.commons.io.IOUtils; import org.apache.james.mailbox.store.mail.model.MailboxMessage; public class Zipper implements Backup { @Override - public void archive(List<MailboxMessage> messages, File destination) throws IOException { + public void archive(List<MailboxMessage> messages, OutputStream destination) throws IOException { try (ZipArchiveOutputStream archiveOutputStream = new ZipArchiveOutputStream(destination)) { + for (MailboxMessage message: messages) { + storeInArchive(message, archiveOutputStream); + } archiveOutputStream.finish(); } } + + private void storeInArchive(MailboxMessage message, ZipArchiveOutputStream archiveOutputStream) throws IOException { + String entryId = message.getMessageId().serialize(); + ArchiveEntry archiveEntry = archiveOutputStream.createArchiveEntry(new File(entryId), entryId); + archiveOutputStream.putArchiveEntry(archiveEntry); + IOUtils.copy(message.getFullContent(), archiveOutputStream); + archiveOutputStream.closeArchiveEntry(); + } } http://git-wip-us.apache.org/repos/asf/james-project/blob/84338dc4/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/MailboxMessageFixture.java ---------------------------------------------------------------------- diff --git a/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/MailboxMessageFixture.java b/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/MailboxMessageFixture.java new file mode 100644 index 0000000..8012e78 --- /dev/null +++ b/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/MailboxMessageFixture.java @@ -0,0 +1,83 @@ +/**************************************************************** + * 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.mailbox.backup; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.mail.Flags; +import javax.mail.util.SharedByteArrayInputStream; + +import org.apache.james.mailbox.model.MessageId; +import org.apache.james.mailbox.model.TestId; +import org.apache.james.mailbox.model.TestMessageId; +import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; +import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage; + +public interface MailboxMessageFixture { + + SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); + + Date DATE_1 = parseDate("2018-02-15 15:54:02"); + Date DATE_2 = parseDate("2018-03-15 15:54:02"); + + MessageId.Factory MESSAGE_ID_FACTORY = new TestMessageId.Factory(); + Charset MESSAGE_CHARSET = StandardCharsets.UTF_8; + String MESSAGE_CONTENT_1 = "Simple message content"; + SharedByteArrayInputStream CONTENT_STREAM_1 = new SharedByteArrayInputStream(MESSAGE_CONTENT_1.getBytes(MESSAGE_CHARSET)); + String MESSAGE_CONTENT_2 = "Other message content"; + SharedByteArrayInputStream CONTENT_STREAM_2 = new SharedByteArrayInputStream(MESSAGE_CONTENT_2.getBytes(MESSAGE_CHARSET)); + MessageId MESSAGE_ID_1 = MESSAGE_ID_FACTORY.generate(); + MessageId MESSAGE_ID_2 = MESSAGE_ID_FACTORY.generate(); + int SIZE_1 = 1000; + int SIZE_2 = 2000; + + SimpleMailboxMessage MESSAGE_1 = SimpleMailboxMessage.builder() + .messageId(MESSAGE_ID_1) + .content(CONTENT_STREAM_1) + .size(SIZE_1) + .internalDate(DATE_1) + .bodyStartOctet(0) + .flags(new Flags()) + .propertyBuilder(new PropertyBuilder()) + .mailboxId(TestId.of(1L)) + .build(); + SimpleMailboxMessage MESSAGE_2 = SimpleMailboxMessage.builder() + .messageId(MESSAGE_ID_2) + .content(CONTENT_STREAM_2) + .size(SIZE_2) + .internalDate(DATE_2) + .bodyStartOctet(0) + .flags(new Flags()) + .propertyBuilder(new PropertyBuilder()) + .mailboxId(TestId.of(1L)) + .build(); + + static Date parseDate(String input) { + try { + return SIMPLE_DATE_FORMAT.parse(input); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/84338dc4/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipArchiveEntryAssert.java ---------------------------------------------------------------------- diff --git a/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipArchiveEntryAssert.java b/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipArchiveEntryAssert.java new file mode 100644 index 0000000..f3d1b89 --- /dev/null +++ b/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipArchiveEntryAssert.java @@ -0,0 +1,79 @@ +/**************************************************************** + * 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.mailbox.backup; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipFile; +import org.apache.commons.io.IOUtils; +import org.assertj.core.api.AbstractAssert; +import org.assertj.core.error.BasicErrorMessageFactory; + +public class ZipArchiveEntryAssert extends AbstractAssert<ZipArchiveEntryAssert, ZipArchiveEntry> { + + public static ZipArchiveEntryAssert assertThatZipEntry(ZipFile zipFile, ZipArchiveEntry zipArchiveEntry) { + return new ZipArchiveEntryAssert(zipFile, zipArchiveEntry); + } + + private static BasicErrorMessageFactory shouldHaveName(ZipArchiveEntry zipArchiveEntry, String expected) { + return new BasicErrorMessageFactory("%nExpecting %s to have name %s but was %s", zipArchiveEntry, expected, zipArchiveEntry.getName()); + } + + private static BasicErrorMessageFactory contentShouldBePresent(ZipArchiveEntry zipArchiveEntry) { + return new BasicErrorMessageFactory("%nCould not retrieve %s content", zipArchiveEntry); + } + + private static BasicErrorMessageFactory shouldHaveContent(ZipArchiveEntry zipArchiveEntry, String expectedContent, String actualContent) { + return new BasicErrorMessageFactory("%nExpecting %s to have content %s but was %s", zipArchiveEntry, expectedContent, actualContent); + } + + private final ZipFile zipFile; + private final ZipArchiveEntry actual; + + private ZipArchiveEntryAssert(ZipFile zipFile, ZipArchiveEntry zipArchiveEntry) { + super(zipArchiveEntry, ZipArchiveEntryAssert.class); + this.zipFile = zipFile; + this.actual = zipArchiveEntry; + } + + public ZipArchiveEntryAssert hasName(String name) { + isNotNull(); + if (!actual.getName().equals(name)) { + throwAssertionError(shouldHaveName(actual, name)); + } + return myself; + } + + public ZipArchiveEntryAssert hasStringContent(String content) throws IOException { + isNotNull(); + InputStream inputStream = zipFile.getInputStream(actual); + if (inputStream == null) { + throwAssertionError(contentShouldBePresent(actual)); + } + String actualContentAsString = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + if (!actualContentAsString.equals(content)) { + throwAssertionError(shouldHaveContent(actual, content, actualContentAsString)); + } + return myself; + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/84338dc4/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipAssert.java ---------------------------------------------------------------------- diff --git a/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipAssert.java b/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipAssert.java new file mode 100644 index 0000000..f521b20 --- /dev/null +++ b/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipAssert.java @@ -0,0 +1,76 @@ +/**************************************************************** + * 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.mailbox.backup; + +import static org.apache.james.mailbox.backup.ZipArchiveEntryAssert.assertThatZipEntry; + +import java.util.Collections; +import java.util.List; + +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipFile; +import org.assertj.core.api.AbstractAssert; +import org.assertj.core.error.BasicErrorMessageFactory; + +public class ZipAssert extends AbstractAssert<ZipAssert, ZipFile> { + interface EntryCheck { + void test(ZipArchiveEntryAssert assertion) throws Exception; + } + + public static ZipAssert assertThatZip(ZipFile zipFile) { + return new ZipAssert(zipFile); + } + + private static BasicErrorMessageFactory shouldHaveSize(ZipFile zipFile, int expected, int actual) { + return new BasicErrorMessageFactory("%nExpecting %s to have side %d but was %d", zipFile, expected, actual); + } + + private static BasicErrorMessageFactory shouldBeEmpty(ZipFile zipFile) { + return new BasicErrorMessageFactory("%nExpecting %s to be empty", zipFile); + } + + private final ZipFile zipFile; + + private ZipAssert(ZipFile zipFile) { + super(zipFile, ZipAssert.class); + this.zipFile = zipFile; + } + + public ZipAssert containsExactlyEntriesMatching(EntryCheck... entryChecks) throws Exception { + isNotNull(); + List<ZipArchiveEntry> entries = Collections.list(zipFile.getEntries()); + if (entries.size() != entryChecks.length) { + throwAssertionError(shouldHaveSize(zipFile, entryChecks.length, entries.size())); + } + for (int i = 0; i < entries.size(); i++) { + entryChecks[i].test(assertThatZipEntry(zipFile, entries.get(i))); + } + return myself; + } + + public ZipAssert hasNoEntry() { + isNotNull(); + if (zipFile.getEntries().hasMoreElements()) { + throwAssertionError(shouldBeEmpty(zipFile)); + } + return myself; + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/84338dc4/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipperTest.java ---------------------------------------------------------------------- diff --git a/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipperTest.java b/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipperTest.java index 25d2fbb..888fed6 100644 --- a/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipperTest.java +++ b/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/ZipperTest.java @@ -18,9 +18,16 @@ ****************************************************************/ package org.apache.james.mailbox.backup; -import static org.assertj.core.api.Assertions.assertThat; +import static org.apache.james.mailbox.backup.MailboxMessageFixture.MESSAGE_1; +import static org.apache.james.mailbox.backup.MailboxMessageFixture.MESSAGE_2; +import static org.apache.james.mailbox.backup.MailboxMessageFixture.MESSAGE_CONTENT_1; +import static org.apache.james.mailbox.backup.MailboxMessageFixture.MESSAGE_CONTENT_2; +import static org.apache.james.mailbox.backup.MailboxMessageFixture.MESSAGE_ID_1; +import static org.apache.james.mailbox.backup.MailboxMessageFixture.MESSAGE_ID_2; +import static org.apache.james.mailbox.backup.ZipAssert.assertThatZip; import java.io.File; +import java.io.FileOutputStream; import org.apache.commons.compress.archivers.zip.ZipFile; import org.apache.james.junit.TemporaryFolderExtension; @@ -34,20 +41,63 @@ import com.google.common.collect.ImmutableList; @ExtendWith(TemporaryFolderExtension.class) public class ZipperTest { private Zipper testee; + private File destination; @BeforeEach - void beforeEach() { + void beforeEach(TemporaryFolder temporaryFolder) throws Exception { testee = new Zipper(); + destination = File.createTempFile("backup-test", ".zip", temporaryFolder.getTempDir()); } @Test - void archiveShouldWriteEmptyValidArchiveWhenNoMessage(TemporaryFolder temporaryFolder) throws Exception { - File destination = File.createTempFile("backup-test", ".zip", temporaryFolder.getTempDir()); - testee.archive(ImmutableList.of(), destination); + void archiveShouldWriteEmptyValidArchiveWhenNoMessage() throws Exception { + testee.archive(ImmutableList.of(), new FileOutputStream(destination)); try (ZipFile zipFile = new ZipFile(destination)) { - assertThat(zipFile.getEntries().hasMoreElements()).isFalse(); + assertThatZip(zipFile).hasNoEntry(); } } + @Test + void archiveShouldWriteOneMessageWhenOne() throws Exception { + testee.archive(ImmutableList.of(MESSAGE_1), new FileOutputStream(destination)); + + try (ZipFile zipFile = new ZipFile(destination)) { + assertThatZip(zipFile) + .containsExactlyEntriesMatching( + zipEntryAssert -> zipEntryAssert + .hasName(MESSAGE_ID_1.serialize()) + .hasStringContent(MESSAGE_CONTENT_1)); + } + } + + @Test + void archiveShouldWriteTwoMessagesWhenTwo() throws Exception { + testee.archive(ImmutableList.of(MESSAGE_1, MESSAGE_2), new FileOutputStream(destination)); + + try (ZipFile zipFile = new ZipFile(destination)) { + assertThatZip(zipFile) + .containsExactlyEntriesMatching( + zipEntryAssert -> zipEntryAssert + .hasName(MESSAGE_ID_1.serialize()) + .hasStringContent(MESSAGE_CONTENT_1), + zipEntryAssert -> zipEntryAssert + .hasName(MESSAGE_ID_2.serialize()) + .hasStringContent(MESSAGE_CONTENT_2)); + } + } + + @Test + void archiveShouldOverwriteContent() throws Exception { + testee.archive(ImmutableList.of(MESSAGE_1), new FileOutputStream(destination)); + testee.archive(ImmutableList.of(MESSAGE_2), new FileOutputStream(destination)); + + try (ZipFile zipFile = new ZipFile(destination)) { + assertThatZip(zipFile) + .containsExactlyEntriesMatching( + zipEntryAssert -> zipEntryAssert + .hasName(MESSAGE_ID_2.serialize()) + .hasStringContent(MESSAGE_CONTENT_2)); + } + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
