JAMES-1925 Introduce CassandraMailboxPathDAO and its tests
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/422209ea Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/422209ea Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/422209ea Branch: refs/heads/master Commit: 422209ea46ae7b48b933b8165f7ad5e66dfc5b0f Parents: 6cee136 Author: Benoit Tellier <[email protected]> Authored: Tue Feb 14 09:28:39 2017 +0700 Committer: Antoine Duprat <[email protected]> Committed: Wed Feb 15 13:12:38 2017 +0100 ---------------------------------------------------------------------- .../cassandra/mail/CassandraMailboxPathDAO.java | 172 +++++++++++++++++++ .../table/CassandraMailboxPathTable.java | 2 + .../mail/CassandraMailboxPathDAOTest.java | 138 +++++++++++++++ 3 files changed, 312 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/422209ea/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathDAO.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathDAO.java new file mode 100644 index 0000000..552e010 --- /dev/null +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathDAO.java @@ -0,0 +1,172 @@ +/**************************************************************** + * 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.cassandra.mail; + +import static com.datastax.driver.core.querybuilder.QueryBuilder.bindMarker; +import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; +import static com.datastax.driver.core.querybuilder.QueryBuilder.insertInto; +import static com.datastax.driver.core.querybuilder.QueryBuilder.select; +import static org.apache.james.mailbox.cassandra.table.CassandraMailboxPathTable.FIELDS; +import static org.apache.james.mailbox.cassandra.table.CassandraMailboxPathTable.MAILBOX_ID; +import static org.apache.james.mailbox.cassandra.table.CassandraMailboxPathTable.MAILBOX_NAME; +import static org.apache.james.mailbox.cassandra.table.CassandraMailboxPathTable.NAMESPACE_AND_USER; +import static org.apache.james.mailbox.cassandra.table.CassandraMailboxPathTable.TABLE_NAME; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; + +import javax.inject.Inject; + +import org.apache.james.backends.cassandra.init.CassandraTypesProvider; +import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor; +import org.apache.james.backends.cassandra.utils.CassandraUtils; +import org.apache.james.mailbox.cassandra.CassandraId; +import org.apache.james.mailbox.cassandra.mail.utils.MailboxBaseTupleUtil; +import org.apache.james.mailbox.cassandra.table.CassandraMailboxTable; +import org.apache.james.mailbox.model.MailboxPath; + +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.Row; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.querybuilder.QueryBuilder; +import com.google.common.base.Objects; + +public class CassandraMailboxPathDAO { + + public static class CassandraIdAndPath { + private final CassandraId cassandraId; + private final MailboxPath mailboxPath; + + public CassandraIdAndPath(CassandraId cassandraId, MailboxPath mailboxPath) { + this.cassandraId = cassandraId; + this.mailboxPath = mailboxPath; + } + + public CassandraId getCassandraId() { + return cassandraId; + } + + public MailboxPath getMailboxPath() { + return mailboxPath; + } + + @Override + public final boolean equals(Object o) { + if (o instanceof CassandraIdAndPath) { + CassandraIdAndPath that = (CassandraIdAndPath) o; + + return Objects.equal(this.cassandraId, that.cassandraId) + && Objects.equal(this.mailboxPath, that.mailboxPath); + } + return false; + } + + @Override + public final int hashCode() { + return Objects.hashCode(cassandraId, mailboxPath); + } + } + + private final CassandraAsyncExecutor cassandraAsyncExecutor; + private final MailboxBaseTupleUtil mailboxBaseTupleUtil; + private final PreparedStatement delete; + private final PreparedStatement insert; + private final PreparedStatement select; + private final PreparedStatement selectAll; + + @Inject + public CassandraMailboxPathDAO(Session session, CassandraTypesProvider typesProvider) { + this.cassandraAsyncExecutor = new CassandraAsyncExecutor(session); + this.mailboxBaseTupleUtil = new MailboxBaseTupleUtil(typesProvider); + this.insert = prepareInsert(session); + this.delete = prepareDelete(session); + this.select = prepareSelect(session); + this.selectAll = prepareSelectAll(session); + } + + private PreparedStatement prepareDelete(Session session) { + return session.prepare(QueryBuilder.delete() + .from(TABLE_NAME) + .where(eq(NAMESPACE_AND_USER, bindMarker(NAMESPACE_AND_USER))) + .and(eq(MAILBOX_NAME, bindMarker(MAILBOX_NAME)))); + } + + private PreparedStatement prepareInsert(Session session) { + return session.prepare(insertInto(TABLE_NAME) + .value(NAMESPACE_AND_USER, bindMarker(NAMESPACE_AND_USER)) + .value(MAILBOX_NAME, bindMarker(MAILBOX_NAME)) + .value(MAILBOX_ID, bindMarker(MAILBOX_ID)) + .ifNotExists()); + } + + private PreparedStatement prepareSelect(Session session) { + return session.prepare(select(FIELDS) + .from(TABLE_NAME) + .where(eq(NAMESPACE_AND_USER, bindMarker(NAMESPACE_AND_USER))) + .and(eq(MAILBOX_NAME, bindMarker(MAILBOX_NAME)))); + } + + private PreparedStatement prepareSelectAll(Session session) { + return session.prepare(select(FIELDS) + .from(TABLE_NAME) + .where(eq(NAMESPACE_AND_USER, bindMarker(NAMESPACE_AND_USER)))); + } + + public CompletableFuture<Optional<CassandraIdAndPath>> retrieveId(MailboxPath mailboxPath) { + return cassandraAsyncExecutor.executeSingleRow( + select.bind() + .setUDTValue(NAMESPACE_AND_USER, mailboxBaseTupleUtil.createMailboxBaseUDT(mailboxPath.getNamespace(), mailboxPath.getUser())) + .setString(MAILBOX_NAME, mailboxPath.getName())) + .thenApply(rowOptional -> + rowOptional.map(row -> new CassandraIdAndPath( + CassandraId.of(row.getUUID(MAILBOX_ID)), + mailboxPath))); + } + + public CompletableFuture<Stream<CassandraIdAndPath>> listUserMailboxes(String namespace, String user) { + return cassandraAsyncExecutor.execute( + selectAll.bind() + .setUDTValue(NAMESPACE_AND_USER, mailboxBaseTupleUtil.createMailboxBaseUDT(namespace, user))) + .thenApply(resultSet -> CassandraUtils.convertToStream(resultSet).map(this::fromRowToCassandraIdAndPath)); + } + + private CassandraIdAndPath fromRowToCassandraIdAndPath(Row row) { + return new CassandraIdAndPath( + CassandraId.of(row.getUUID(MAILBOX_ID)), + new MailboxPath(row.getUDTValue(NAMESPACE_AND_USER).getString(CassandraMailboxTable.MailboxBase.NAMESPACE), + row.getUDTValue(NAMESPACE_AND_USER).getString(CassandraMailboxTable.MailboxBase.USER), + row.getString(MAILBOX_NAME))); + } + + public CompletableFuture<Boolean> save(MailboxPath mailboxPath, CassandraId mailboxId) { + return cassandraAsyncExecutor.executeReturnApplied(insert.bind() + .setUDTValue(NAMESPACE_AND_USER, mailboxBaseTupleUtil.createMailboxBaseUDT(mailboxPath.getNamespace(), mailboxPath.getUser())) + .setString(MAILBOX_NAME, mailboxPath.getName()) + .setUUID(MAILBOX_ID, mailboxId.asUuid())); + } + + public CompletableFuture<Void> delete(MailboxPath mailboxPath) { + return cassandraAsyncExecutor.executeVoid(delete.bind() + .setUDTValue(NAMESPACE_AND_USER, mailboxBaseTupleUtil.createMailboxBaseUDT(mailboxPath.getNamespace(), mailboxPath.getUser())) + .setString(MAILBOX_NAME, mailboxPath.getName())); + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/422209ea/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMailboxPathTable.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMailboxPathTable.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMailboxPathTable.java index be9eb54..efc6168 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMailboxPathTable.java +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMailboxPathTable.java @@ -29,4 +29,6 @@ public interface CassandraMailboxPathTable { String MAILBOX_ID = "mailboxId"; + String[] FIELDS = { NAMESPACE_AND_USER, MAILBOX_NAME, MAILBOX_ID}; + } http://git-wip-us.apache.org/repos/asf/james-project/blob/422209ea/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathDAOTest.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathDAOTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathDAOTest.java new file mode 100644 index 0000000..b6c1c10 --- /dev/null +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathDAOTest.java @@ -0,0 +1,138 @@ +/**************************************************************** + * 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.cassandra.mail; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Optional; + +import org.apache.james.backends.cassandra.CassandraCluster; +import org.apache.james.mailbox.cassandra.CassandraId; +import org.apache.james.mailbox.cassandra.mail.CassandraMailboxPathDAO.CassandraIdAndPath; +import org.apache.james.mailbox.cassandra.modules.CassandraMailboxModule; +import org.apache.james.mailbox.model.MailboxPath; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.github.steveash.guavate.Guavate; + +import nl.jqno.equalsverifier.EqualsVerifier; + +public class CassandraMailboxPathDAOTest { + private static final String PRIVATE_NAMESPACE = "#private"; + private static final String USER = "user"; + private static final String OTHER_USER = "other"; + private static final CassandraId INBOX_ID = CassandraId.timeBased(); + private static final CassandraId OUTBOX_ID = CassandraId.timeBased(); + private static final CassandraId otherMailboxId = CassandraId.timeBased(); + + public static final MailboxPath USER_INBOX_MAILBOXPATH = new MailboxPath(PRIVATE_NAMESPACE, USER, "INBOX"); + public static final CassandraIdAndPath INBOX_ID_AND_PATH = new CassandraIdAndPath(INBOX_ID, USER_INBOX_MAILBOXPATH); + public static final MailboxPath USER_OUTBOX_MAILBOXPATH = new MailboxPath(PRIVATE_NAMESPACE, USER, "OUTBOX"); + public static final MailboxPath OTHER_USER_MAILBOXPATH = new MailboxPath(PRIVATE_NAMESPACE, OTHER_USER, "INBOX"); + + private CassandraCluster cassandra; + + private CassandraMailboxPathDAO testee; + + @Before + public void setUp() throws Exception { + cassandra = CassandraCluster.create(new CassandraMailboxModule()); + cassandra.ensureAllTables(); + + testee = new CassandraMailboxPathDAO(cassandra.getConf(), cassandra.getTypesProvider()); + + } + + @After + public void tearDown() throws Exception { + cassandra.clearAllTables(); + } + + @Test + public void cassandraIdAndPathShouldRespectBeanContract() { + EqualsVerifier.forClass(CassandraMailboxPathDAO.CassandraIdAndPath.class).verify(); + } + + @Test + public void saveShouldInsertNewEntry() throws Exception { + assertThat(testee.save(USER_INBOX_MAILBOXPATH, INBOX_ID).join()).isTrue(); + + Optional<CassandraIdAndPath> cassandraIdAndPath = testee.retrieveId(USER_INBOX_MAILBOXPATH).join(); + assertThat(cassandraIdAndPath.get()) + .isEqualTo(INBOX_ID_AND_PATH); + } + + @Test + public void saveOnSecondShouldBeFalse() throws Exception { + assertThat(testee.save(USER_INBOX_MAILBOXPATH, INBOX_ID).join()).isTrue(); + assertThat(testee.save(USER_INBOX_MAILBOXPATH, INBOX_ID).join()).isFalse(); + } + + @Test + public void retrieveIdShouldReturnEmptyWhenEmptyData() throws Exception { + assertThat(testee.retrieveId(USER_INBOX_MAILBOXPATH).join() + .isPresent()) + .isFalse(); + } + + @Test + public void retrieveIdShouldReturnStoredData() throws Exception { + testee.save(USER_INBOX_MAILBOXPATH, INBOX_ID).join(); + + Optional<CassandraIdAndPath> cassandraIdAndPath = testee.retrieveId(USER_INBOX_MAILBOXPATH).join(); + assertThat(cassandraIdAndPath.get()) + .isEqualTo(INBOX_ID_AND_PATH); + } + + @Test + public void getUserMailboxesShouldReturnAllMailboxesOfUser() throws Exception { + testee.save(USER_INBOX_MAILBOXPATH, INBOX_ID).join(); + testee.save(USER_OUTBOX_MAILBOXPATH, OUTBOX_ID).join(); + testee.save(OTHER_USER_MAILBOXPATH, otherMailboxId).join(); + + List<CassandraIdAndPath> cassandraIds = testee + .listUserMailboxes(USER_INBOX_MAILBOXPATH.getNamespace(), USER_INBOX_MAILBOXPATH.getUser()) + .join() + .collect(Guavate.toImmutableList()); + + assertThat(cassandraIds) + .hasSize(2) + .containsOnly(INBOX_ID_AND_PATH, new CassandraIdAndPath(OUTBOX_ID, USER_OUTBOX_MAILBOXPATH)); + } + + @Test + public void deleteShouldNotThrowWhenEmpty() throws Exception { + testee.delete(USER_INBOX_MAILBOXPATH).join(); + } + + @Test + public void deleteShouldDeleteTheExistingMailboxId() throws Exception { + testee.save(USER_INBOX_MAILBOXPATH, INBOX_ID).join(); + + testee.delete(USER_INBOX_MAILBOXPATH).join(); + + assertThat(testee.retrieveId(USER_INBOX_MAILBOXPATH).join() + .isPresent()) + .isFalse(); + } +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
