JAMES-1947 Create new cassandra module for applicableFlag and its DAO
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/90d8c69d Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/90d8c69d Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/90d8c69d Branch: refs/heads/master Commit: 90d8c69d807dee04df363db9412b080eba145955 Parents: 44728d3 Author: Quynh Nguyen <[email protected]> Authored: Wed Feb 22 15:10:17 2017 +0700 Committer: benwa <[email protected]> Committed: Tue Feb 28 10:37:10 2017 +0700 ---------------------------------------------------------------------- .../mail/CassandraApplicableFlagDAO.java | 110 ++++++++++++ .../mailbox/cassandra/mail/FlagsExtractor.java | 12 ++ .../modules/CassandraApplicableFlagsModule.java | 76 ++++++++ .../table/CassandraApplicableFlagTable.java | 28 +++ .../james/mailbox/cassandra/table/Flag.java | 11 ++ .../mail/CassandraApplicableFlagDAOTest.java | 174 +++++++++++++++++++ 6 files changed, 411 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/90d8c69d/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraApplicableFlagDAO.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraApplicableFlagDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraApplicableFlagDAO.java new file mode 100644 index 0000000..4d5756e --- /dev/null +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraApplicableFlagDAO.java @@ -0,0 +1,110 @@ +/**************************************************************** + * 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.add; +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.select; +import static com.datastax.driver.core.querybuilder.QueryBuilder.set; +import static com.datastax.driver.core.querybuilder.QueryBuilder.update; +import static org.apache.james.mailbox.cassandra.table.CassandraApplicableFlagTable.FIELDS; +import static org.apache.james.mailbox.cassandra.table.CassandraApplicableFlagTable.MAILBOX_ID; +import static org.apache.james.mailbox.cassandra.table.CassandraApplicableFlagTable.TABLE_NAME; +import static org.apache.james.mailbox.cassandra.table.Flag.FLAG_TO_STRING_MAP; +import static org.apache.james.mailbox.cassandra.table.Flag.USER_FLAGS; + +import java.util.Arrays; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import javax.inject.Inject; +import javax.mail.Flags; +import javax.mail.Flags.Flag; + +import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor; +import org.apache.james.mailbox.FlagsBuilder; +import org.apache.james.mailbox.cassandra.CassandraId; + +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.querybuilder.Update; +import com.datastax.driver.core.querybuilder.Update.Assignments; + +public class CassandraApplicableFlagDAO { + private static final Flags.Flag[] ALL_APPLICABLE_FLAGS = {Flags.Flag.ANSWERED, Flags.Flag.DELETED, Flags.Flag.DRAFT, Flags.Flag.SEEN, Flags.Flag.FLAGGED }; + private static final Flags EMPTY_FLAGS = new Flags(); + + private final CassandraAsyncExecutor cassandraAsyncExecutor; + private final PreparedStatement select; + + @Inject + public CassandraApplicableFlagDAO(Session session) { + this.cassandraAsyncExecutor = new CassandraAsyncExecutor(session); + this.select = prepareSelect(session); + } + + private PreparedStatement prepareSelect(Session session) { + return session.prepare(select(FIELDS) + .from(TABLE_NAME) + .where(eq(MAILBOX_ID, bindMarker(MAILBOX_ID)))); + } + + public CompletableFuture<Optional<Flags>> retrieveApplicableFlag(CassandraId mailboxId) { + return cassandraAsyncExecutor.executeSingleRow( + select.bind() + .setUUID(MAILBOX_ID, mailboxId.asUuid())) + .thenApply(rowOptional -> + rowOptional.map(row -> new FlagsExtractor(row).getApplicableFlags())); + } + + public CompletableFuture<Void> updateApplicableFlags(CassandraId cassandraId, Flags flags) { + Flags newFlags = new FlagsBuilder().add(flags).build(); + newFlags.remove(Flag.RECENT); + newFlags.remove(Flag.USER); + + if (newFlags.equals(EMPTY_FLAGS)) { + return CompletableFuture.completedFuture(null); + } + return cassandraAsyncExecutor.executeVoid(updateQuery(cassandraId, newFlags)); + } + + private Update.Where updateQuery(CassandraId cassandraId, Flags flags) { + return addSystemFlagsToQuery(flags, + addUserFlagsToQuery(flags, + update(TABLE_NAME).with())) + .where(eq(MAILBOX_ID, cassandraId.asUuid())); + } + + private Assignments addUserFlagsToQuery(Flags flags, Assignments updateQuery) { + if (flags.getUserFlags() != null && flags.getUserFlags().length > 0) { + Arrays.stream(flags.getUserFlags()) + .forEach(userFlag -> updateQuery.and(add(USER_FLAGS, userFlag))); + } + return updateQuery; + } + + private Assignments addSystemFlagsToQuery(Flags flags, Assignments updateQuery) { + Arrays.stream(ALL_APPLICABLE_FLAGS) + .filter(flags::contains) + .forEach(flag -> updateQuery.and(set(FLAG_TO_STRING_MAP.get(flag), true))); + return updateQuery; + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/90d8c69d/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/FlagsExtractor.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/FlagsExtractor.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/FlagsExtractor.java index 09ef44b..7124495 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/FlagsExtractor.java +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/FlagsExtractor.java @@ -45,4 +45,16 @@ public class FlagsExtractor { return flags; } + public Flags getApplicableFlags() { + Flags flags = new Flags(); + for (String flag : Flag.ALL_APPLICABLE_FLAG) { + if (row.getBool(flag)) { + flags.add(Flag.JAVAX_MAIL_FLAG.get(flag)); + } + } + row.getSet(Flag.USER_FLAGS, String.class) + .stream() + .forEach(flags::add); + return flags; + } } http://git-wip-us.apache.org/repos/asf/james-project/blob/90d8c69d/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraApplicableFlagsModule.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraApplicableFlagsModule.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraApplicableFlagsModule.java new file mode 100644 index 0000000..5ec8023 --- /dev/null +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraApplicableFlagsModule.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.cassandra.modules; + +import static com.datastax.driver.core.DataType.cboolean; +import static com.datastax.driver.core.DataType.set; +import static com.datastax.driver.core.DataType.text; +import static com.datastax.driver.core.DataType.timeuuid; + +import java.util.Collections; +import java.util.List; + +import org.apache.james.backends.cassandra.components.CassandraIndex; +import org.apache.james.backends.cassandra.components.CassandraModule; +import org.apache.james.backends.cassandra.components.CassandraTable; +import org.apache.james.backends.cassandra.components.CassandraType; +import org.apache.james.mailbox.cassandra.table.CassandraApplicableFlagTable; +import org.apache.james.mailbox.cassandra.table.Flag; + +import com.datastax.driver.core.schemabuilder.SchemaBuilder; +import com.google.common.collect.ImmutableList; + +public class CassandraApplicableFlagsModule implements CassandraModule { + private final List<CassandraTable> tables; + private final List<CassandraIndex> index; + private final List<CassandraType> types; + + public CassandraApplicableFlagsModule() { + tables = ImmutableList.of( + new CassandraTable(CassandraApplicableFlagTable.TABLE_NAME, + SchemaBuilder.createTable(CassandraApplicableFlagTable.TABLE_NAME) + .ifNotExists() + .addPartitionKey(CassandraApplicableFlagTable.MAILBOX_ID, timeuuid()) + .addColumn(Flag.ANSWERED, cboolean()) + .addColumn(Flag.DELETED, cboolean()) + .addColumn(Flag.DRAFT, cboolean()) + .addColumn(Flag.FLAGGED, cboolean()) + .addColumn(Flag.SEEN, cboolean()) + .addColumn(Flag.USER_FLAGS, set(text())))); + index = Collections.emptyList(); + types = Collections.emptyList(); + } + + @Override + public List<CassandraTable> moduleTables() { + return tables; + } + + @Override + public List<CassandraIndex> moduleIndex() { + return index; + } + + @Override + public List<CassandraType> moduleTypes() { + return types; + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/90d8c69d/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraApplicableFlagTable.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraApplicableFlagTable.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraApplicableFlagTable.java new file mode 100644 index 0000000..a7eb74c --- /dev/null +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraApplicableFlagTable.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 CassandraApplicableFlagTable { + + String TABLE_NAME = "applicableFlag"; + String MAILBOX_ID = "mailboxId"; + + String[] FIELDS = { MAILBOX_ID, Flag.ANSWERED, Flag.DELETED, Flag.DRAFT, Flag.FLAGGED, Flag.SEEN, Flag.USER_FLAGS }; +} http://git-wip-us.apache.org/repos/asf/james-project/blob/90d8c69d/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/Flag.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/Flag.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/Flag.java index ed7953b..d575330 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/Flag.java +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/Flag.java @@ -33,6 +33,7 @@ public interface Flag { String USER = "flagUser"; String USER_FLAGS = "userFlags"; String[] ALL = { ANSWERED, DELETED, DRAFT, RECENT, SEEN, FLAGGED, USER }; + String[] ALL_APPLICABLE_FLAG = { ANSWERED, DELETED, DRAFT, SEEN, FLAGGED }; ImmutableMap<String, Flags.Flag> JAVAX_MAIL_FLAG = ImmutableMap.<String, Flags.Flag>builder() .put(ANSWERED, Flags.Flag.ANSWERED) @@ -43,4 +44,14 @@ public interface Flag { .put(FLAGGED, Flags.Flag.FLAGGED) .put(USER, Flags.Flag.USER) .build(); + + ImmutableMap<Flags.Flag, String> FLAG_TO_STRING_MAP = ImmutableMap.<Flags.Flag, String>builder() + .put(Flags.Flag.ANSWERED, ANSWERED) + .put(Flags.Flag.DELETED, DELETED) + .put(Flags.Flag.DRAFT, DRAFT) + .put(Flags.Flag.RECENT, RECENT) + .put(Flags.Flag.SEEN, SEEN) + .put(Flags.Flag.FLAGGED, FLAGGED) + .put(Flags.Flag.USER, USER) + .build(); } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/90d8c69d/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraApplicableFlagDAOTest.java ---------------------------------------------------------------------- diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraApplicableFlagDAOTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraApplicableFlagDAOTest.java new file mode 100644 index 0000000..f37ca92 --- /dev/null +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraApplicableFlagDAOTest.java @@ -0,0 +1,174 @@ +/**************************************************************** + * 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.Optional; + +import javax.mail.Flags; +import javax.mail.Flags.Flag; + +import org.apache.james.backends.cassandra.CassandraCluster; +import org.apache.james.mailbox.FlagsBuilder; +import org.apache.james.mailbox.cassandra.CassandraId; +import org.apache.james.mailbox.cassandra.modules.CassandraApplicableFlagsModule; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class CassandraApplicableFlagDAOTest { + + public static final String USER_FLAG = "User Flag"; + public static final String USER_FLAG2 = "User Flag 2"; + public static final CassandraId CASSANDRA_ID = CassandraId.timeBased(); + + private CassandraCluster cassandra; + + private CassandraApplicableFlagDAO testee; + + @Before + public void setUp() throws Exception { + cassandra = CassandraCluster.create(new CassandraApplicableFlagsModule()); + cassandra.ensureAllTables(); + + testee = new CassandraApplicableFlagDAO(cassandra.getConf()); + } + + @After + public void tearDown() throws Exception { + cassandra.clearAllTables(); + } + + @Test + public void updateApplicableFlagsShouldReturnEmptyByDefault() throws Exception { + Optional<Flags> actual = testee.retrieveApplicableFlag(CASSANDRA_ID).join(); + assertThat(actual.isPresent()).isFalse(); + } + + @Test + public void updateApplicableFlagsShouldSupportEmptyFlags() throws Exception { + testee.updateApplicableFlags(CASSANDRA_ID, new Flags()).join(); + + Optional<Flags> actual = testee.retrieveApplicableFlag(CASSANDRA_ID).join(); + assertThat(actual.isPresent()).isFalse(); + } + + @Test + public void updateApplicableFlagsShouldIgnoreRecentFlags() throws Exception { + testee.updateApplicableFlags(CASSANDRA_ID, new Flags(Flag.RECENT)).join(); + + Optional<Flags> actual = testee.retrieveApplicableFlag(CASSANDRA_ID).join(); + assertThat(actual.isPresent()).isFalse(); + } + + @Test + public void updateApplicableFlagsShouldUpdateMultiFlags() throws Exception { + Flags flags = new FlagsBuilder().add(Flag.ANSWERED, Flag.DELETED).build(); + testee.updateApplicableFlags(CASSANDRA_ID, flags).join(); + + Optional<Flags> actual = testee.retrieveApplicableFlag(CASSANDRA_ID).join(); + assertThat(actual.isPresent()).isTrue(); + assertThat(actual.get()).isEqualTo(flags); + } + + @Test + public void updateApplicableFlagsShouldAddAnsweredFlag() throws Exception { + Flags flags = new Flags(Flag.ANSWERED); + testee.updateApplicableFlags(CASSANDRA_ID, flags).join(); + + Optional<Flags> actual = testee.retrieveApplicableFlag(CASSANDRA_ID).join(); + assertThat(actual.isPresent()).isTrue(); + assertThat(actual.get()).isEqualTo(flags); + } + + @Test + public void updateApplicableFlagsShouldAddDeletedFlag() throws Exception { + Flags flags = new Flags(Flag.DELETED); + testee.updateApplicableFlags(CASSANDRA_ID, flags).join(); + + Optional<Flags> actual = testee.retrieveApplicableFlag(CASSANDRA_ID).join(); + assertThat(actual.isPresent()).isTrue(); + assertThat(actual.get()).isEqualTo(flags); + } + + @Test + public void updateApplicableFlagsShouldAddDraftFlag() throws Exception { + Flags flags = new Flags(Flag.DRAFT); + testee.updateApplicableFlags(CASSANDRA_ID, flags).join(); + + Optional<Flags> actual = testee.retrieveApplicableFlag(CASSANDRA_ID).join(); + assertThat(actual.isPresent()).isTrue(); + assertThat(actual.get()).isEqualTo(flags); + } + + @Test + public void updateApplicableFlagsShouldAddFlaggedFlag() throws Exception { + Flags flags = new Flags(Flag.FLAGGED); + testee.updateApplicableFlags(CASSANDRA_ID, flags).join(); + + Optional<Flags> actual = testee.retrieveApplicableFlag(CASSANDRA_ID).join(); + assertThat(actual.isPresent()).isTrue(); + assertThat(actual.get()).isEqualTo(flags); + } + + @Test + public void updateApplicableFlagsShouldAddSeenFlag() throws Exception { + Flags flags = new Flags(Flag.SEEN); + testee.updateApplicableFlags(CASSANDRA_ID, flags).join(); + + Optional<Flags> actual = testee.retrieveApplicableFlag(CASSANDRA_ID).join(); + assertThat(actual.isPresent()).isTrue(); + assertThat(actual.get()).isEqualTo(flags); + } + + @Test + public void updateApplicableFlagsShouldUnionSystemFlags() throws Exception { + testee.updateApplicableFlags(CASSANDRA_ID, new Flags(Flag.ANSWERED)).join(); + testee.updateApplicableFlags(CASSANDRA_ID, new Flags(Flag.SEEN)).join(); + + Optional<Flags> actual = testee.retrieveApplicableFlag(CASSANDRA_ID).join(); + assertThat(actual.isPresent()).isTrue(); + assertThat(actual.get()).isEqualTo(new FlagsBuilder().add(Flag.ANSWERED, Flag.SEEN).build()); + } + + @Test + public void updateApplicableFlagsShouldUpdateUserFlag() throws Exception { + Flags flags = new FlagsBuilder().add(Flag.ANSWERED).add(USER_FLAG).build(); + + testee.updateApplicableFlags(CASSANDRA_ID, flags).join(); + + Optional<Flags> actual = testee.retrieveApplicableFlag(CASSANDRA_ID).join(); + assertThat(actual.isPresent()).isTrue(); + assertThat(actual.get()).isEqualTo(flags); + } + + @Test + public void updateApplicableFlagsShouldUnionUserFlags() throws Exception { + testee.updateApplicableFlags(CASSANDRA_ID, new Flags(USER_FLAG)).join(); + + testee.updateApplicableFlags(CASSANDRA_ID, new Flags(USER_FLAG2)).join(); + + Optional<Flags> actual = testee.retrieveApplicableFlag(CASSANDRA_ID).join(); + assertThat(actual.isPresent()).isTrue(); + assertThat(actual.get()).isEqualTo(new FlagsBuilder().add(USER_FLAG, USER_FLAG2).build()); + } + +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
