MAILBOX-339 Introduce CassandraMailboxPathDAO interface
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/2fae24a8 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/2fae24a8 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/2fae24a8 Branch: refs/heads/master Commit: 2fae24a82c9d453bdb2556257cd9afbb96e56afd Parents: f8b587a Author: Antoine Duprat <adup...@linagora.com> Authored: Mon May 14 15:18:07 2018 +0200 Committer: Matthieu Baechler <matth...@apache.org> Committed: Mon May 28 17:38:00 2018 +0200 ---------------------------------------------------------------------- .../CassandraMailboxSessionMapperFactory.java | 6 +- .../cassandra/mail/CassandraIdAndPath.java | 58 ++++++ .../cassandra/mail/CassandraMailboxMapper.java | 8 +- .../cassandra/mail/CassandraMailboxPathDAO.java | 187 +------------------ .../mail/CassandraMailboxPathDAOImpl.java | 185 ++++++++++++++++++ .../CassandraSubscriptionManagerTest.java | 4 +- ...estCassandraMailboxSessionMapperFactory.java | 4 +- .../CassandraMailboxMapperConcurrencyTest.java | 2 +- .../mail/CassandraMailboxMapperTest.java | 4 +- .../mail/CassandraMailboxPathDAOImplTest.java | 28 +++ .../mail/CassandraMailboxPathDAOTest.java | 10 +- .../modules/mailbox/CassandraMailboxModule.java | 4 +- 12 files changed, 298 insertions(+), 202 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/2fae24a8/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java index f25c738..60ee504 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java @@ -39,7 +39,7 @@ import org.apache.james.mailbox.cassandra.mail.CassandraIndexTableHandler; import org.apache.james.mailbox.cassandra.mail.CassandraMailboxCounterDAO; import org.apache.james.mailbox.cassandra.mail.CassandraMailboxDAO; import org.apache.james.mailbox.cassandra.mail.CassandraMailboxMapper; -import org.apache.james.mailbox.cassandra.mail.CassandraMailboxPathDAO; +import org.apache.james.mailbox.cassandra.mail.CassandraMailboxPathDAOImpl; import org.apache.james.mailbox.cassandra.mail.CassandraMailboxRecentsDAO; import org.apache.james.mailbox.cassandra.mail.CassandraMessageDAO; import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdDAO; @@ -79,7 +79,7 @@ public class CassandraMailboxSessionMapperFactory extends MailboxSessionMapperFa private final CassandraMailboxRecentsDAO mailboxRecentsDAO; private final CassandraIndexTableHandler indexTableHandler; private final CassandraMailboxDAO mailboxDAO; - private final CassandraMailboxPathDAO mailboxPathDAO; + private final CassandraMailboxPathDAOImpl mailboxPathDAO; private final CassandraFirstUnseenDAO firstUnseenDAO; private final CassandraApplicableFlagDAO applicableFlagDAO; private final CassandraAttachmentDAO attachmentDAO; @@ -98,7 +98,7 @@ public class CassandraMailboxSessionMapperFactory extends MailboxSessionMapperFa CassandraMessageDAO messageDAO, CassandraMessageIdDAO messageIdDAO, CassandraMessageIdToImapUidDAO imapUidDAO, CassandraMailboxCounterDAO mailboxCounterDAO, CassandraMailboxRecentsDAO mailboxRecentsDAO, CassandraMailboxDAO mailboxDAO, - CassandraMailboxPathDAO mailboxPathDAO, CassandraFirstUnseenDAO firstUnseenDAO, CassandraApplicableFlagDAO applicableFlagDAO, + CassandraMailboxPathDAOImpl mailboxPathDAO, CassandraFirstUnseenDAO firstUnseenDAO, CassandraApplicableFlagDAO applicableFlagDAO, CassandraAttachmentDAO attachmentDAO, CassandraAttachmentDAOV2 attachmentDAOV2, CassandraDeletedMessageDAO deletedMessageDAO, ObjectStore objectStore, CassandraAttachmentMessageIdDAO attachmentMessageIdDAO, CassandraAttachmentOwnerDAO ownerDAO, CassandraACLMapper aclMapper, http://git-wip-us.apache.org/repos/asf/james-project/blob/2fae24a8/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraIdAndPath.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraIdAndPath.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraIdAndPath.java new file mode 100644 index 0000000..e476b02 --- /dev/null +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraIdAndPath.java @@ -0,0 +1,58 @@ +/**************************************************************** + * 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 org.apache.james.mailbox.cassandra.ids.CassandraId; +import org.apache.james.mailbox.model.MailboxPath; + +import com.google.common.base.Objects; + +public 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); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/2fae24a8/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapper.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapper.java index 865938b..8fe6f26 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapper.java +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapper.java @@ -65,13 +65,13 @@ public class CassandraMailboxMapper implements MailboxMapper { public static final String CLUSTERING_COLUMNS_IS_TOO_LONG = "The sum of all clustering columns is too long"; public static final Logger LOGGER = LoggerFactory.getLogger(CassandraMailboxMapper.class); - private final CassandraMailboxPathDAO mailboxPathDAO; + private final CassandraMailboxPathDAOImpl mailboxPathDAO; private final CassandraMailboxDAO mailboxDAO; private final CassandraACLMapper cassandraACLMapper; private final CassandraUserMailboxRightsDAO userMailboxRightsDAO; @Inject - public CassandraMailboxMapper(CassandraMailboxDAO mailboxDAO, CassandraMailboxPathDAO mailboxPathDAO, CassandraUserMailboxRightsDAO userMailboxRightsDAO, CassandraACLMapper aclMapper, CassandraConfiguration cassandraConfiguration) { + public CassandraMailboxMapper(CassandraMailboxDAO mailboxDAO, CassandraMailboxPathDAOImpl mailboxPathDAO, CassandraUserMailboxRightsDAO userMailboxRightsDAO, CassandraACLMapper aclMapper, CassandraConfiguration cassandraConfiguration) { this.mailboxDAO = mailboxDAO; this.mailboxPathDAO = mailboxPathDAO; this.userMailboxRightsDAO = userMailboxRightsDAO; @@ -92,7 +92,7 @@ public class CassandraMailboxMapper implements MailboxMapper { return mailboxPathDAO.retrieveId(path) .thenCompose(cassandraIdOptional -> cassandraIdOptional - .map(CassandraMailboxPathDAO.CassandraIdAndPath::getCassandraId) + .map(CassandraIdAndPath::getCassandraId) .map(this::retrieveMailbox) .orElse(CompletableFuture.completedFuture(Optional.empty()))) .join() @@ -145,7 +145,7 @@ public class CassandraMailboxMapper implements MailboxMapper { .collect(Guavate.toImmutableList()); } - private CompletableFuture<Optional<SimpleMailbox>> retrieveMailbox(CassandraMailboxPathDAO.CassandraIdAndPath idAndPath) { + private CompletableFuture<Optional<SimpleMailbox>> retrieveMailbox(CassandraIdAndPath idAndPath) { return retrieveMailbox(idAndPath.getCassandraId()) .thenApply(optional -> OptionalUtils.executeIfEmpty(optional, () -> LOGGER.warn("Could not retrieve mailbox {} with path {} in mailbox table.", idAndPath.getCassandraId(), idAndPath.getMailboxPath()))); http://git-wip-us.apache.org/repos/asf/james-project/blob/2fae24a8/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 index 8a06811..33fac3c 100644 --- 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 @@ -19,198 +19,23 @@ 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.GhostMailbox.TYPE; -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.GhostMailbox; import org.apache.james.mailbox.cassandra.ids.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.annotations.VisibleForTesting; -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 CassandraUtils cassandraUtils; - private final PreparedStatement delete; - private final PreparedStatement insert; - private final PreparedStatement select; - private final PreparedStatement selectAll; - - @Inject - public CassandraMailboxPathDAO(Session session, CassandraTypesProvider typesProvider, CassandraUtils cassandraUtils) { - this.cassandraAsyncExecutor = new CassandraAsyncExecutor(session); - this.mailboxBaseTupleUtil = new MailboxBaseTupleUtil(typesProvider); - this.cassandraUtils = cassandraUtils; - this.insert = prepareInsert(session); - this.delete = prepareDelete(session); - this.select = prepareSelect(session); - this.selectAll = prepareSelectAll(session); - } - - @VisibleForTesting - public CassandraMailboxPathDAO(Session session, CassandraTypesProvider typesProvider) { - this(session, typesProvider, CassandraUtils.WITH_DEFAULT_CONFIGURATION); - } - - 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(this::fromRowToCassandraIdAndPath)) - .thenApply(value -> logGhostMailbox(mailboxPath, value)); - } - - 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) - .peek(this::logReadSuccess)); - } +public interface CassandraMailboxPathDAO { - /** - * See https://issues.apache.org/jira/browse/MAILBOX-322 to read about the Ghost mailbox bug. - * - * A missed read on an existing mailbox is the cause of the ghost mailbox bug. Here we log missing reads. Successful - * reads and write operations are also added in order to allow audit in order to know if the mailbox existed. - */ - public Optional<CassandraIdAndPath> logGhostMailbox(MailboxPath mailboxPath, Optional<CassandraIdAndPath> value) { - if (value.isPresent()) { - CassandraIdAndPath cassandraIdAndPath = value.get(); - logReadSuccess(cassandraIdAndPath); - } else { - GhostMailbox.logger() - .addField(GhostMailbox.MAILBOX_NAME, mailboxPath) - .addField(TYPE, "readMiss") - .log(logger -> logger.info("Read mailbox missed")); - } - return value; - } + CompletableFuture<Optional<CassandraIdAndPath>> retrieveId(MailboxPath mailboxPath); - /** - * See https://issues.apache.org/jira/browse/MAILBOX-322 to read about the Ghost mailbox bug. - * - * Read success allows to know if a mailbox existed before (mailbox write history might be older than this log introduction - * or log history might have been dropped) - */ - private void logReadSuccess(CassandraIdAndPath cassandraIdAndPath) { - GhostMailbox.logger() - .addField(GhostMailbox.MAILBOX_NAME, cassandraIdAndPath.getMailboxPath()) - .addField(TYPE, "readSuccess") - .addField(GhostMailbox.MAILBOX_ID, cassandraIdAndPath.getCassandraId()) - .log(logger -> logger.info("Read mailbox succeeded")); - } + CompletableFuture<Stream<CassandraIdAndPath>> listUserMailboxes(String namespace, String user); - 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))); - } + Optional<CassandraIdAndPath> logGhostMailbox(MailboxPath mailboxPath, Optional<CassandraIdAndPath> value); - 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())); - } + CompletableFuture<Boolean> save(MailboxPath mailboxPath, CassandraId mailboxId); - 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())); - } + CompletableFuture<Void> delete(MailboxPath mailboxPath); } http://git-wip-us.apache.org/repos/asf/james-project/blob/2fae24a8/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathDAOImpl.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathDAOImpl.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathDAOImpl.java new file mode 100644 index 0000000..11080e2 --- /dev/null +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathDAOImpl.java @@ -0,0 +1,185 @@ +/**************************************************************** + * 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.GhostMailbox.TYPE; +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.GhostMailbox; +import org.apache.james.mailbox.cassandra.ids.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.annotations.VisibleForTesting; + +public class CassandraMailboxPathDAOImpl implements CassandraMailboxPathDAO { + + private final CassandraAsyncExecutor cassandraAsyncExecutor; + private final MailboxBaseTupleUtil mailboxBaseTupleUtil; + private final CassandraUtils cassandraUtils; + private final PreparedStatement delete; + private final PreparedStatement insert; + private final PreparedStatement select; + private final PreparedStatement selectAll; + + @Inject + public CassandraMailboxPathDAOImpl(Session session, CassandraTypesProvider typesProvider, CassandraUtils cassandraUtils) { + this.cassandraAsyncExecutor = new CassandraAsyncExecutor(session); + this.mailboxBaseTupleUtil = new MailboxBaseTupleUtil(typesProvider); + this.cassandraUtils = cassandraUtils; + this.insert = prepareInsert(session); + this.delete = prepareDelete(session); + this.select = prepareSelect(session); + this.selectAll = prepareSelectAll(session); + } + + @VisibleForTesting + public CassandraMailboxPathDAOImpl(Session session, CassandraTypesProvider typesProvider) { + this(session, typesProvider, CassandraUtils.WITH_DEFAULT_CONFIGURATION); + } + + 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(this::fromRowToCassandraIdAndPath)) + .thenApply(value -> logGhostMailbox(mailboxPath, value)); + } + + @Override + 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) + .peek(this::logReadSuccess)); + } + + /** + * See https://issues.apache.org/jira/browse/MAILBOX-322 to read about the Ghost mailbox bug. + * + * A missed read on an existing mailbox is the cause of the ghost mailbox bug. Here we log missing reads. Successful + * reads and write operations are also added in order to allow audit in order to know if the mailbox existed. + */ + @Override + public Optional<CassandraIdAndPath> logGhostMailbox(MailboxPath mailboxPath, Optional<CassandraIdAndPath> value) { + if (value.isPresent()) { + CassandraIdAndPath cassandraIdAndPath = value.get(); + logReadSuccess(cassandraIdAndPath); + } else { + GhostMailbox.logger() + .addField(GhostMailbox.MAILBOX_NAME, mailboxPath) + .addField(TYPE, "readMiss") + .log(logger -> logger.info("Read mailbox missed")); + } + return value; + } + + /** + * See https://issues.apache.org/jira/browse/MAILBOX-322 to read about the Ghost mailbox bug. + * + * Read success allows to know if a mailbox existed before (mailbox write history might be older than this log introduction + * or log history might have been dropped) + */ + private void logReadSuccess(CassandraIdAndPath cassandraIdAndPath) { + GhostMailbox.logger() + .addField(GhostMailbox.MAILBOX_NAME, cassandraIdAndPath.getMailboxPath()) + .addField(TYPE, "readSuccess") + .addField(GhostMailbox.MAILBOX_ID, cassandraIdAndPath.getCassandraId()) + .log(logger -> logger.info("Read mailbox succeeded")); + } + + 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))); + } + + @Override + 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())); + } + + @Override + 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/2fae24a8/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraSubscriptionManagerTest.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraSubscriptionManagerTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraSubscriptionManagerTest.java index 1222958..29ec40f 100644 --- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraSubscriptionManagerTest.java +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraSubscriptionManagerTest.java @@ -37,7 +37,7 @@ import org.apache.james.mailbox.cassandra.mail.CassandraDeletedMessageDAO; import org.apache.james.mailbox.cassandra.mail.CassandraFirstUnseenDAO; import org.apache.james.mailbox.cassandra.mail.CassandraMailboxCounterDAO; import org.apache.james.mailbox.cassandra.mail.CassandraMailboxDAO; -import org.apache.james.mailbox.cassandra.mail.CassandraMailboxPathDAO; +import org.apache.james.mailbox.cassandra.mail.CassandraMailboxPathDAOImpl; import org.apache.james.mailbox.cassandra.mail.CassandraMailboxRecentsDAO; import org.apache.james.mailbox.cassandra.mail.CassandraMessageDAO; import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdDAO; @@ -88,7 +88,7 @@ public class CassandraSubscriptionManagerTest extends AbstractSubscriptionManage CassandraMailboxCounterDAO mailboxCounterDAO = null; CassandraMailboxRecentsDAO mailboxRecentsDAO = null; CassandraMailboxDAO mailboxDAO = null; - CassandraMailboxPathDAO mailboxPathDAO = null; + CassandraMailboxPathDAOImpl mailboxPathDAO = null; CassandraFirstUnseenDAO firstUnseenDAO = null; CassandraApplicableFlagDAO applicableFlagDAO = null; CassandraAttachmentDAO attachmentDAO = null; http://git-wip-us.apache.org/repos/asf/james-project/blob/2fae24a8/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/TestCassandraMailboxSessionMapperFactory.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/TestCassandraMailboxSessionMapperFactory.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/TestCassandraMailboxSessionMapperFactory.java index ec7ac05..d26350a 100644 --- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/TestCassandraMailboxSessionMapperFactory.java +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/TestCassandraMailboxSessionMapperFactory.java @@ -35,7 +35,7 @@ import org.apache.james.mailbox.cassandra.mail.CassandraDeletedMessageDAO; import org.apache.james.mailbox.cassandra.mail.CassandraFirstUnseenDAO; import org.apache.james.mailbox.cassandra.mail.CassandraMailboxCounterDAO; import org.apache.james.mailbox.cassandra.mail.CassandraMailboxDAO; -import org.apache.james.mailbox.cassandra.mail.CassandraMailboxPathDAO; +import org.apache.james.mailbox.cassandra.mail.CassandraMailboxPathDAOImpl; import org.apache.james.mailbox.cassandra.mail.CassandraMailboxRecentsDAO; import org.apache.james.mailbox.cassandra.mail.CassandraMessageDAO; import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdDAO; @@ -70,7 +70,7 @@ public class TestCassandraMailboxSessionMapperFactory { new CassandraMailboxCounterDAO(session), new CassandraMailboxRecentsDAO(session), new CassandraMailboxDAO(session, typesProvider), - new CassandraMailboxPathDAO(session, typesProvider), + new CassandraMailboxPathDAOImpl(session, typesProvider), new CassandraFirstUnseenDAO(session), new CassandraApplicableFlagDAO(session), new CassandraAttachmentDAO(session, utils, cassandraConfiguration), http://git-wip-us.apache.org/repos/asf/james-project/blob/2fae24a8/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperConcurrencyTest.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperConcurrencyTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperConcurrencyTest.java index 6d7822b..3d2bd73 100644 --- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperConcurrencyTest.java +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperConcurrencyTest.java @@ -57,7 +57,7 @@ public class CassandraMailboxMapperConcurrencyTest { CassandraModuleComposite modules = new CassandraModuleComposite(new CassandraMailboxModule(), new CassandraAclModule()); cassandra = CassandraCluster.create(modules, cassandraServer.getIp(), cassandraServer.getBindingPort()); CassandraMailboxDAO mailboxDAO = new CassandraMailboxDAO(cassandra.getConf(), cassandra.getTypesProvider()); - CassandraMailboxPathDAO mailboxPathDAO = new CassandraMailboxPathDAO(cassandra.getConf(), cassandra.getTypesProvider()); + CassandraMailboxPathDAOImpl mailboxPathDAO = new CassandraMailboxPathDAOImpl(cassandra.getConf(), cassandra.getTypesProvider()); CassandraUserMailboxRightsDAO userMailboxRightsDAO = new CassandraUserMailboxRightsDAO(cassandra.getConf(), CassandraUtils.WITH_DEFAULT_CONFIGURATION); testee = new CassandraMailboxMapper( mailboxDAO, http://git-wip-us.apache.org/repos/asf/james-project/blob/2fae24a8/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperTest.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperTest.java index e761b7e..350c523 100644 --- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperTest.java +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperTest.java @@ -47,7 +47,7 @@ public class CassandraMailboxMapperTest { @ClassRule public static DockerCassandraRule cassandraServer = new DockerCassandraRule(); private CassandraCluster cassandra; - private CassandraMailboxPathDAO mailboxPathDAO; + private CassandraMailboxPathDAOImpl mailboxPathDAO; private CassandraMailboxMapper testee; @Before @@ -55,7 +55,7 @@ public class CassandraMailboxMapperTest { CassandraModuleComposite modules = new CassandraModuleComposite(new CassandraMailboxModule(), new CassandraAclModule()); cassandra = CassandraCluster.create(modules, cassandraServer.getIp(), cassandraServer.getBindingPort()); CassandraMailboxDAO mailboxDAO = new CassandraMailboxDAO(cassandra.getConf(), cassandra.getTypesProvider()); - mailboxPathDAO = new CassandraMailboxPathDAO(cassandra.getConf(), cassandra.getTypesProvider()); + mailboxPathDAO = new CassandraMailboxPathDAOImpl(cassandra.getConf(), cassandra.getTypesProvider()); CassandraUserMailboxRightsDAO userMailboxRightsDAO = new CassandraUserMailboxRightsDAO(cassandra.getConf(), CassandraUtils.WITH_DEFAULT_CONFIGURATION); testee = new CassandraMailboxMapper( mailboxDAO, http://git-wip-us.apache.org/repos/asf/james-project/blob/2fae24a8/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathDAOImplTest.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathDAOImplTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathDAOImplTest.java new file mode 100644 index 0000000..c532bd7 --- /dev/null +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathDAOImplTest.java @@ -0,0 +1,28 @@ +/**************************************************************** + * 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; + +public class CassandraMailboxPathDAOImplTest extends CassandraMailboxPathDAOTest { + + @Override + CassandraMailboxPathDAO testee() { + return new CassandraMailboxPathDAOImpl(cassandra.getConf(), cassandra.getTypesProvider()); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/2fae24a8/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 index ce79b44..a88f1d6 100644 --- 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 @@ -26,7 +26,6 @@ import java.util.List; import org.apache.james.backends.cassandra.CassandraCluster; import org.apache.james.backends.cassandra.DockerCassandraRule; import org.apache.james.mailbox.cassandra.ids.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; @@ -38,7 +37,7 @@ import com.github.steveash.guavate.Guavate; import nl.jqno.equalsverifier.EqualsVerifier; -public class CassandraMailboxPathDAOTest { +public abstract class CassandraMailboxPathDAOTest { private static final String USER = "user"; private static final String OTHER_USER = "other"; private static final CassandraId INBOX_ID = CassandraId.timeBased(); @@ -52,14 +51,15 @@ public class CassandraMailboxPathDAOTest { @ClassRule public static DockerCassandraRule cassandraServer = new DockerCassandraRule(); - private CassandraCluster cassandra; + protected CassandraCluster cassandra; private CassandraMailboxPathDAO testee; + abstract CassandraMailboxPathDAO testee(); @Before public void setUp() throws Exception { cassandra = CassandraCluster.create(new CassandraMailboxModule(), cassandraServer.getIp(), cassandraServer.getBindingPort()); - testee = new CassandraMailboxPathDAO(cassandra.getConf(), cassandra.getTypesProvider()); + testee = testee(); } @@ -70,7 +70,7 @@ public class CassandraMailboxPathDAOTest { @Test public void cassandraIdAndPathShouldRespectBeanContract() { - EqualsVerifier.forClass(CassandraMailboxPathDAO.CassandraIdAndPath.class).verify(); + EqualsVerifier.forClass(CassandraIdAndPath.class).verify(); } @Test http://git-wip-us.apache.org/repos/asf/james-project/blob/2fae24a8/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java ---------------------------------------------------------------------- diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java index 69a44fc..f63ba6d 100644 --- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java +++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java @@ -46,7 +46,7 @@ import org.apache.james.mailbox.cassandra.mail.CassandraFirstUnseenDAO; import org.apache.james.mailbox.cassandra.mail.CassandraMailboxCounterDAO; import org.apache.james.mailbox.cassandra.mail.CassandraMailboxDAO; import org.apache.james.mailbox.cassandra.mail.CassandraMailboxMapper; -import org.apache.james.mailbox.cassandra.mail.CassandraMailboxPathDAO; +import org.apache.james.mailbox.cassandra.mail.CassandraMailboxPathDAOImpl; import org.apache.james.mailbox.cassandra.mail.CassandraMailboxRecentsDAO; import org.apache.james.mailbox.cassandra.mail.CassandraMessageDAO; import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdDAO; @@ -103,7 +103,7 @@ public class CassandraMailboxModule extends AbstractModule { bind(CassandraFirstUnseenDAO.class).in(Scopes.SINGLETON); bind(CassandraMailboxCounterDAO.class).in(Scopes.SINGLETON); bind(CassandraMailboxDAO.class).in(Scopes.SINGLETON); - bind(CassandraMailboxPathDAO.class).in(Scopes.SINGLETON); + bind(CassandraMailboxPathDAOImpl.class).in(Scopes.SINGLETON); bind(CassandraMailboxRecentsDAO.class).in(Scopes.SINGLETON); bind(CassandraMessageDAO.class).in(Scopes.SINGLETON); bind(CassandraMessageIdDAO.class).in(Scopes.SINGLETON); --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org