JAMES-2608 implement MergingCassandraMailRepositoryMailDao to handle on-the-fly data migration
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/48b99bf7 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/48b99bf7 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/48b99bf7 Branch: refs/heads/master Commit: 48b99bf7b00862ee4e7fcef7b08b44c61fdcbc57 Parents: d0f973b Author: Matthieu Baechler <[email protected]> Authored: Wed Nov 28 19:11:44 2018 +0100 Committer: Raphael Ouazana <[email protected]> Committed: Wed Dec 19 09:24:12 2018 +0100 ---------------------------------------------------------------------- .../MergingCassandraMailRepositoryMailDao.java | 64 +++++++++ .../CassandraMailRepositoryMailDAOTest.java | 135 ++++++++++++++++++- 2 files changed, 197 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/48b99bf7/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/MergingCassandraMailRepositoryMailDao.java ---------------------------------------------------------------------- diff --git a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/MergingCassandraMailRepositoryMailDao.java b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/MergingCassandraMailRepositoryMailDao.java new file mode 100644 index 0000000..8b01a38 --- /dev/null +++ b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/MergingCassandraMailRepositoryMailDao.java @@ -0,0 +1,64 @@ +/**************************************************************** + * 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.mailrepository.cassandra; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import javax.inject.Inject; +import javax.mail.MessagingException; + +import org.apache.james.blob.api.BlobId; +import org.apache.james.mailrepository.api.MailKey; +import org.apache.james.mailrepository.api.MailRepositoryUrl; +import org.apache.james.util.OptionalUtils; +import org.apache.mailet.Mail; + +import com.google.common.annotations.VisibleForTesting; + +public class MergingCassandraMailRepositoryMailDao implements CassandraMailRepositoryMailDaoAPI { + + private final CassandraMailRepositoryMailDAO v1; + private final CassandraMailRepositoryMailDaoV2 v2; + + @Inject + @VisibleForTesting + MergingCassandraMailRepositoryMailDao(CassandraMailRepositoryMailDAO v1, CassandraMailRepositoryMailDaoV2 v2) { + this.v1 = v1; + this.v2 = v2; + } + + @Override + public CompletableFuture<Void> store(MailRepositoryUrl url, Mail mail, BlobId headerId, BlobId bodyId) throws MessagingException { + return v2.store(url, mail, headerId, bodyId); + } + + @Override + public CompletableFuture<Void> remove(MailRepositoryUrl url, MailKey key) { + return CompletableFuture.allOf(v1.remove(url, key), v2.remove(url, key)); + } + + @Override + public CompletableFuture<Optional<MailDTO>> read(MailRepositoryUrl url, MailKey key) { + return v2.read(url, key) + .thenCombine(v1.read(url, key), + (maybeV2Value, maybeV1Value) -> OptionalUtils.or(maybeV2Value, maybeV1Value)); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/48b99bf7/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java ---------------------------------------------------------------------- diff --git a/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java b/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java index c02966a..a50c69e 100644 --- a/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java +++ b/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java @@ -22,6 +22,10 @@ package org.apache.james.mailrepository.cassandra; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; +import java.util.Optional; + +import javax.mail.MessagingException; + import org.apache.james.backends.cassandra.CassandraCluster; import org.apache.james.backends.cassandra.CassandraClusterExtension; import org.apache.james.blob.api.BlobId; @@ -157,7 +161,7 @@ class CassandraMailRepositoryMailDAOTest { } @Nested - class v1 extends TestSuite { + class V1 extends TestSuite { private CassandraMailRepositoryMailDAO testee; @@ -173,7 +177,7 @@ class CassandraMailRepositoryMailDAOTest { } @Nested - class v2 extends TestSuite { + class V2 extends TestSuite { private CassandraMailRepositoryMailDaoV2 testee; @@ -187,4 +191,131 @@ class CassandraMailRepositoryMailDAOTest { return testee; } } + + @Nested + class Merging extends TestSuite { + + private MergingCassandraMailRepositoryMailDao testee; + private CassandraMailRepositoryMailDAO v1; + private CassandraMailRepositoryMailDaoV2 v2; + + @BeforeEach + void setUp(CassandraCluster cassandra) { + v1 = new CassandraMailRepositoryMailDAO(cassandra.getConf(), BLOB_ID_FACTORY, cassandra.getTypesProvider()); + v2 = new CassandraMailRepositoryMailDaoV2(cassandra.getConf(), BLOB_ID_FACTORY); + testee = new MergingCassandraMailRepositoryMailDao(v1, v2); + } + + @Override + CassandraMailRepositoryMailDaoAPI testee() { + return testee; + } + + @Test + void readShouldReturnV1Value() throws MessagingException { + BlobId blobIdBody = BLOB_ID_FACTORY.from("blobHeader"); + BlobId blobIdHeader = BLOB_ID_FACTORY.from("blobBody"); + + v1.store(URL, + FakeMail.builder() + .name(KEY_1.asString()) + .build(), + blobIdHeader, + blobIdBody) + .join(); + + CassandraMailRepositoryMailDaoAPI.MailDTO actual = testee.read(URL, KEY_1).join().get(); + Mail partialMail = actual.getMailBuilder().build(); + assertSoftly(softly -> { + softly.assertThat(actual.getBodyBlobId()).isEqualTo(blobIdBody); + softly.assertThat(actual.getHeaderBlobId()).isEqualTo(blobIdHeader); + softly.assertThat(partialMail.getName()).isEqualTo(KEY_1.asString()); + }); + } + + @Test + void readShouldReturnV2Value() throws MessagingException { + BlobId blobIdBody = BLOB_ID_FACTORY.from("blobHeader"); + BlobId blobIdHeader = BLOB_ID_FACTORY.from("blobBody"); + + v2.store(URL, + FakeMail.builder() + .name(KEY_1.asString()) + .build(), + blobIdHeader, + blobIdBody) + .join(); + + CassandraMailRepositoryMailDaoAPI.MailDTO actual = testee.read(URL, KEY_1).join().get(); + Mail partialMail = actual.getMailBuilder().build(); + assertSoftly(softly -> { + softly.assertThat(actual.getBodyBlobId()).isEqualTo(blobIdBody); + softly.assertThat(actual.getHeaderBlobId()).isEqualTo(blobIdHeader); + softly.assertThat(partialMail.getName()).isEqualTo(KEY_1.asString()); + }); + } + + @Test + void readShouldReturnV2ValueIfPresentInBoth() throws MessagingException { + BlobId blobIdBody1 = BLOB_ID_FACTORY.from("blobHeader"); + BlobId blobIdBody2 = BLOB_ID_FACTORY.from("blobHeader2"); + BlobId blobIdHeader1 = BLOB_ID_FACTORY.from("blobBody"); + BlobId blobIdHeader2 = BLOB_ID_FACTORY.from("blobBody2"); + + v1.store(URL, + FakeMail.builder() + .name(KEY_1.asString()) + .build(), + blobIdHeader1, + blobIdBody1) + .join(); + + v2.store(URL, + FakeMail.builder() + .name(KEY_1.asString()) + .build(), + blobIdHeader2, + blobIdBody2) + .join(); + + CassandraMailRepositoryMailDaoAPI.MailDTO actual = testee.read(URL, KEY_1).join().get(); + Mail partialMail = actual.getMailBuilder().build(); + assertSoftly(softly -> { + softly.assertThat(actual.getBodyBlobId()).isEqualTo(blobIdBody2); + softly.assertThat(actual.getHeaderBlobId()).isEqualTo(blobIdHeader2); + softly.assertThat(partialMail.getName()).isEqualTo(KEY_1.asString()); + }); + } + + @Test + void removeShouldRemoveInBOth() throws MessagingException { + BlobId blobIdBody1 = BLOB_ID_FACTORY.from("blobHeader"); + BlobId blobIdBody2 = BLOB_ID_FACTORY.from("blobHeader2"); + BlobId blobIdHeader1 = BLOB_ID_FACTORY.from("blobBody"); + BlobId blobIdHeader2 = BLOB_ID_FACTORY.from("blobBody2"); + + v1.store(URL, + FakeMail.builder() + .name(KEY_1.asString()) + .build(), + blobIdHeader1, + blobIdBody1) + .join(); + + v2.store(URL, + FakeMail.builder() + .name(KEY_1.asString()) + .build(), + blobIdHeader2, + blobIdBody2) + .join(); + + testee.remove(URL, KEY_1).join(); + + Optional<CassandraMailRepositoryMailDaoAPI.MailDTO> v1Entry = v1.read(URL, KEY_1).join(); + Optional<CassandraMailRepositoryMailDaoAPI.MailDTO> v2Entry = v2.read(URL, KEY_1).join(); + assertThat(v1Entry).isEmpty(); + assertThat(v2Entry).isEmpty(); + } + } } \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
