MAILBOX-310 CassandraUserMailboxRightsDAO should store user -> accessible (non personnal) mailboxes
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/e55b3716 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/e55b3716 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/e55b3716 Branch: refs/heads/master Commit: e55b3716bb37125512358b623a992ad5d1a3eb8a Parents: 3192535 Author: benwa <btell...@linagora.com> Authored: Wed Oct 4 15:43:35 2017 +0700 Committer: Matthieu Baechler <matth...@apache.org> Committed: Thu Oct 5 09:48:53 2017 +0200 ---------------------------------------------------------------------- .../mail/CassandraUserMailboxRightsDAO.java | 147 +++++++++++++++++++ .../cassandra/modules/CassandraAclModule.java | 14 +- .../table/CassandraUserMailboxRightsTable.java | 28 ++++ .../mail/CassandraUserMailboxRightsDAOTest.java | 108 ++++++++++++++ 4 files changed, 296 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/e55b3716/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraUserMailboxRightsDAO.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraUserMailboxRightsDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraUserMailboxRightsDAO.java new file mode 100644 index 0000000..336a0ef --- /dev/null +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraUserMailboxRightsDAO.java @@ -0,0 +1,147 @@ +/**************************************************************** + * 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.CassandraUserMailboxRightsTable.MAILBOX_ID; +import static org.apache.james.mailbox.cassandra.table.CassandraUserMailboxRightsTable.RIGHTS; +import static org.apache.james.mailbox.cassandra.table.CassandraUserMailboxRightsTable.TABLE_NAME; +import static org.apache.james.mailbox.cassandra.table.CassandraUserMailboxRightsTable.USER_NAME; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.inject.Inject; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor; +import org.apache.james.backends.cassandra.utils.CassandraUtils; +import org.apache.james.mailbox.acl.PositiveUserACLChanged; +import org.apache.james.mailbox.cassandra.ids.CassandraId; +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxACL.Rfc4314Rights; +import org.apache.james.util.FluentFutureStream; + +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.github.fge.lambdas.Throwing; + +public class CassandraUserMailboxRightsDAO { + + private final CassandraAsyncExecutor cassandraAsyncExecutor; + private final CassandraUtils cassandraUtils; + private final PreparedStatement delete; + private final PreparedStatement insert; + private final PreparedStatement select; + private final PreparedStatement selectUser; + + @Inject + public CassandraUserMailboxRightsDAO(Session session, CassandraUtils cassandraUtils) { + this.cassandraAsyncExecutor = new CassandraAsyncExecutor(session); + this.cassandraUtils = cassandraUtils; + this.delete = prepareDelete(session); + this.insert = prepareInsert(session); + this.select = prepareSelect(session); + this.selectUser = prepareSelectAllForUser(session); + } + + private PreparedStatement prepareDelete(Session session) { + return session.prepare(QueryBuilder.delete() + .from(TABLE_NAME) + .where(eq(USER_NAME, bindMarker(USER_NAME))) + .and(eq(MAILBOX_ID, bindMarker(MAILBOX_ID)))); + } + + private PreparedStatement prepareInsert(Session session) { + return session.prepare(insertInto(TABLE_NAME) + .value(USER_NAME, bindMarker(USER_NAME)) + .value(MAILBOX_ID, bindMarker(MAILBOX_ID)) + .value(RIGHTS, bindMarker(RIGHTS))); + } + + private PreparedStatement prepareSelect(Session session) { + return session.prepare(select(RIGHTS) + .from(TABLE_NAME) + .where(eq(USER_NAME, bindMarker(USER_NAME))) + .and(eq(MAILBOX_ID, bindMarker(MAILBOX_ID)))); + } + + private PreparedStatement prepareSelectAllForUser(Session session) { + return session.prepare(select(MAILBOX_ID, RIGHTS) + .from(TABLE_NAME) + .where(eq(USER_NAME, bindMarker(USER_NAME)))); + } + + public CompletableFuture<Void> update(CassandraId cassandraId, PositiveUserACLChanged positiveUserAclChanged) { + return CompletableFuture.allOf( + addAll(cassandraId, positiveUserAclChanged.addedEntries()), + removeAll(cassandraId, positiveUserAclChanged.removedEntries()), + addAll(cassandraId, positiveUserAclChanged.changedEntries())); + } + + private CompletableFuture<Stream<Void>> removeAll(CassandraId cassandraId, Stream<MailboxACL.Entry> removedEntries) { + return FluentFutureStream.of(removedEntries + .map(entry -> cassandraAsyncExecutor.executeVoid( + delete.bind() + .setString(USER_NAME, entry.getKey().getName()) + .setUUID(MAILBOX_ID, cassandraId.asUuid())))) + .completableFuture(); + } + + private CompletableFuture<Stream<Void>> addAll(CassandraId cassandraId, Stream<MailboxACL.Entry> addedEntries) { + return FluentFutureStream.of(addedEntries + .map(entry -> cassandraAsyncExecutor.executeVoid( + insert.bind() + .setString(USER_NAME, entry.getKey().getName()) + .setUUID(MAILBOX_ID, cassandraId.asUuid()) + .setString(RIGHTS, entry.getValue().serialize())))) + .completableFuture(); + } + + public CompletableFuture<Optional<Rfc4314Rights>> retrieve(String userName, CassandraId mailboxId) { + return cassandraAsyncExecutor.executeSingleRow( + select.bind() + .setString(USER_NAME, userName) + .setUUID(MAILBOX_ID, mailboxId.asUuid())) + .thenApply(rowOptional -> + rowOptional.map(Throwing.function(row -> Rfc4314Rights.fromSerializedRfc4314Rights(row.getString(RIGHTS))))); + } + + public CompletableFuture<Map<CassandraId, Rfc4314Rights>> listRightsForUser(String userName) { + Function<Row, Pair<CassandraId, Rfc4314Rights>> toPair = Throwing.function(row -> + Pair.of(CassandraId.of(row.getUUID(MAILBOX_ID)), Rfc4314Rights.fromSerializedRfc4314Rights(row.getString(RIGHTS)))); + + return cassandraAsyncExecutor.execute( + selectUser.bind() + .setString(USER_NAME, userName)) + .thenApply(cassandraUtils::convertToStream) + .thenApply(row -> + row.map(toPair) + .collect(Collectors.toMap(Pair::getLeft, Pair::getRight))); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/e55b3716/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraAclModule.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraAclModule.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraAclModule.java index 25f4e47..a26fb18 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraAclModule.java +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraAclModule.java @@ -30,6 +30,7 @@ import org.apache.james.backends.cassandra.components.CassandraTable; import org.apache.james.backends.cassandra.components.CassandraType; import org.apache.james.backends.cassandra.utils.CassandraConstants; import org.apache.james.mailbox.cassandra.table.CassandraACLTable; +import org.apache.james.mailbox.cassandra.table.CassandraUserMailboxRightsTable; import com.datastax.driver.core.schemabuilder.SchemaBuilder; import com.google.common.collect.ImmutableList; @@ -50,7 +51,18 @@ public class CassandraAclModule implements CassandraModule { .withOptions() .comment("Holds mailbox ACLs") .caching(SchemaBuilder.KeyCaching.ALL, - SchemaBuilder.rows(CassandraConstants.DEFAULT_CACHED_ROW_PER_PARTITION)))); + SchemaBuilder.rows(CassandraConstants.DEFAULT_CACHED_ROW_PER_PARTITION))), + new CassandraTable(CassandraUserMailboxRightsTable.TABLE_NAME, + SchemaBuilder.createTable(CassandraUserMailboxRightsTable.TABLE_NAME) + .ifNotExists() + .addPartitionKey(CassandraUserMailboxRightsTable.USER_NAME, text()) + .addClusteringColumn(CassandraUserMailboxRightsTable.MAILBOX_ID, timeuuid()) + .addColumn(CassandraUserMailboxRightsTable.RIGHTS, text()) + .withOptions() + .compactionOptions(SchemaBuilder.leveledStrategy()) + .caching(SchemaBuilder.KeyCaching.ALL, + SchemaBuilder.rows(CassandraConstants.DEFAULT_CACHED_ROW_PER_PARTITION)) + .comment("Denormalisation table. Allow to retrieve non personal mailboxIds a user has right on"))); types = ImmutableList.of(); } http://git-wip-us.apache.org/repos/asf/james-project/blob/e55b3716/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraUserMailboxRightsTable.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraUserMailboxRightsTable.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraUserMailboxRightsTable.java new file mode 100644 index 0000000..65dff9f --- /dev/null +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraUserMailboxRightsTable.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.table; + +public interface CassandraUserMailboxRightsTable { + String TABLE_NAME = "UserMailboxACL"; + + String USER_NAME = "userName"; + String MAILBOX_ID = "mailboxid"; + String RIGHTS = "rights"; +} http://git-wip-us.apache.org/repos/asf/james-project/blob/e55b3716/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraUserMailboxRightsDAOTest.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraUserMailboxRightsDAOTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraUserMailboxRightsDAOTest.java new file mode 100644 index 0000000..6690eae --- /dev/null +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraUserMailboxRightsDAOTest.java @@ -0,0 +1,108 @@ +/**************************************************************** + * 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 org.apache.james.backends.cassandra.CassandraCluster; +import org.apache.james.backends.cassandra.DockerCassandraRule; +import org.apache.james.backends.cassandra.utils.CassandraUtils; +import org.apache.james.mailbox.acl.PositiveUserACLChanged; +import org.apache.james.mailbox.cassandra.ids.CassandraId; +import org.apache.james.mailbox.cassandra.modules.CassandraAclModule; +import org.apache.james.mailbox.model.MailboxACL; +import org.apache.james.mailbox.model.MailboxACL.Entry; +import org.apache.james.mailbox.model.MailboxACL.EntryKey; +import org.apache.james.mailbox.model.MailboxACL.Rfc4314Rights; +import org.apache.james.mailbox.model.MailboxACL.Right; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; + +public class CassandraUserMailboxRightsDAOTest { + + private static final String USER_NAME = "userName"; + private static final EntryKey ENTRY_KEY = EntryKey.createUserEntryKey(USER_NAME); + private static final CassandraId MAILBOX_ID = CassandraId.timeBased(); + private static final Rfc4314Rights RIGHTS = MailboxACL.FULL_RIGHTS; + private static final Rfc4314Rights OTHER_RIGHTS = new Rfc4314Rights(Right.Administer, Right.Read); + + @ClassRule public static DockerCassandraRule cassandraServer = new DockerCassandraRule(); + + private CassandraCluster cassandra; + + private CassandraUserMailboxRightsDAO testee; + + @Before + public void setUp() throws Exception { + cassandra = CassandraCluster.create(new CassandraAclModule(), cassandraServer.getIp(), cassandraServer.getBindingPort()); + testee = new CassandraUserMailboxRightsDAO(cassandra.getConf(), CassandraUtils.WITH_DEFAULT_CONFIGURATION); + } + + @After + public void tearDown() throws Exception { + cassandra.close(); + } + + @Test + public void saveShouldInsertNewEntry() throws Exception { + testee.update(MAILBOX_ID, new PositiveUserACLChanged( + MailboxACL.EMPTY, + new MailboxACL(new Entry(ENTRY_KEY, RIGHTS)))).join(); + + assertThat(testee.retrieve(USER_NAME, MAILBOX_ID).join()) + .contains(RIGHTS); + } + + @Test + public void saveOnSecondShouldOverwrite() throws Exception { + testee.update(MAILBOX_ID, new PositiveUserACLChanged( + MailboxACL.EMPTY, + new MailboxACL(new Entry(ENTRY_KEY, RIGHTS)))).join(); + + testee.update(MAILBOX_ID, new PositiveUserACLChanged( + new MailboxACL(new Entry(ENTRY_KEY, RIGHTS)), + new MailboxACL(new Entry(ENTRY_KEY, OTHER_RIGHTS)))).join(); + + assertThat(testee.retrieve(USER_NAME, MAILBOX_ID).join()) + .contains(OTHER_RIGHTS); + } + + @Test + public void listRightsForUserShouldReturnEmptyWhenEmptyData() throws Exception { + assertThat(testee.listRightsForUser(USER_NAME).join()) + .isEmpty(); + } + + @Test + public void deleteShouldDeleteWhenExisting() throws Exception { + testee.update(MAILBOX_ID, new PositiveUserACLChanged( + MailboxACL.EMPTY, + new MailboxACL(new Entry(ENTRY_KEY, RIGHTS)))).join(); + + + testee.update(MAILBOX_ID, new PositiveUserACLChanged( + new MailboxACL(new Entry(ENTRY_KEY, RIGHTS)), + MailboxACL.EMPTY)).join(); + + assertThat(testee.retrieve(USER_NAME, MAILBOX_ID).join()) + .isEmpty(); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org