You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by er...@apache.org on 2015/05/08 17:02:21 UTC

svn commit: r1678368 - in /james/mailbox/trunk: api/src/main/java/org/apache/james/mailbox/exception/ cassandra/ cassandra/src/main/java/org/apache/james/mailbox/cassandra/ cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/ cassandra/src/...

Author: eric
Date: Fri May  8 15:02:21 2015
New Revision: 1678368

URL: http://svn.apache.org/r1678368
Log:
Add ACL persitance for Cassandra Mailbox, patch contributed by  Tellier Benoit (MAILBOX-224)

Added:
    james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/MalformedSoredACLException.java
    james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapper.java
    james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/utils/
    james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/utils/FunctionRunnerWithRetry.java
    james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/utils/SimpleMailboxACLJsonConverter.java
    james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraACLTable.java
    james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapperTest.java
    james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/
    james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/FunctionRunnerWithRetryTest.java
    james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/SimpleMailboxACLJsonConverterTest.java
Modified:
    james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedRightException.java
    james/mailbox/trunk/cassandra/pom.xml
    james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxManager.java
    james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java
    james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraSession.java
    james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapper.java
    james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraClusterSingleton.java
    james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperTest.java
    james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraUidAndModSeqProviderTest.java

Added: james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/MalformedSoredACLException.java
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/MalformedSoredACLException.java?rev=1678368&view=auto
==============================================================================
--- james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/MalformedSoredACLException.java (added)
+++ james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/MalformedSoredACLException.java Fri May  8 15:02:21 2015
@@ -0,0 +1,38 @@
+/*
+ *   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.exception;
+
+public class MalformedSoredACLException extends MailboxException {
+
+    public MalformedSoredACLException() {
+        
+    }
+
+    public MalformedSoredACLException(String message) {
+        super(message);
+    }
+
+    public MalformedSoredACLException(String msg, Exception cause) {
+        super(msg, cause);
+    }
+
+    
+}

Modified: james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedRightException.java
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedRightException.java?rev=1678368&r1=1678367&r2=1678368&view=diff
==============================================================================
--- james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedRightException.java (original)
+++ james/mailbox/trunk/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedRightException.java Fri May  8 15:02:21 2015
@@ -44,7 +44,11 @@ public class UnsupportedRightException e
     public UnsupportedRightException(MailboxACLRight unsupportedRight) {
         this(unsupportedRight.getValue());
     }
-    
+
+    public UnsupportedRightException(String msg, Exception cause) {
+        super(msg, cause);
+    }
+
     public char getUnsupportedRight() {
         return unsupportedRight;
     }

Modified: james/mailbox/trunk/cassandra/pom.xml
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/cassandra/pom.xml?rev=1678368&r1=1678367&r2=1678368&view=diff
==============================================================================
--- james/mailbox/trunk/cassandra/pom.xml (original)
+++ james/mailbox/trunk/cassandra/pom.xml Fri May  8 15:02:21 2015
@@ -31,11 +31,6 @@
     <description>Apache James Mailbox implementation over Cassandra</description>
     <name>Apache James :: Mailbox :: Cassandra</name>
 
-    <properties>
-        <maven.compiler.source>1.8</maven.compiler.source>
-        <maven.compiler.target>1.8</maven.compiler.target>
-    </properties>
-
     <dependencies>
         <dependency>
             <groupId>${javax.mail.groupId}</groupId>
@@ -75,11 +70,20 @@
 	    <version>${cassandra-driver-core.version}</version>
         </dependency>
         <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <dependency>
     	    <groupId>org.cassandraunit</groupId>
    	    <artifactId>cassandra-unit</artifactId>
             <version>${cassandra-unit.version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <profiles>
@@ -211,6 +215,14 @@
                             </descriptorRefs>
                         </configuration>
                     </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-compiler-plugin</artifactId>
+                        <configuration>
+                            <source>1.8</source>
+                            <target>1.8</target>
+                        </configuration>
+                    </plugin>
                 </plugins>
             </build>
         </profile>

Modified: james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxManager.java
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxManager.java?rev=1678368&r1=1678367&r2=1678368&view=diff
==============================================================================
--- james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxManager.java (original)
+++ james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxManager.java Fri May  8 15:02:21 2015
@@ -27,6 +27,7 @@ import org.apache.james.mailbox.acl.Simp
 import org.apache.james.mailbox.acl.UnionMailboxACLResolver;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.SimpleMailboxACL;
 import org.apache.james.mailbox.store.Authenticator;
 import org.apache.james.mailbox.store.StoreMailboxManager;
 import org.apache.james.mailbox.store.StoreMessageManager;
@@ -46,7 +47,9 @@ public class CassandraMailboxManager ext
 
     @Override
     protected Mailbox<UUID> doCreateMailbox(MailboxPath mailboxPath, MailboxSession session) throws MailboxException {
-        return new SimpleMailbox<UUID>(mailboxPath, randomUidValidity());
+        SimpleMailbox<UUID> cassandraMailbox = new SimpleMailbox<>(mailboxPath, randomUidValidity());
+        cassandraMailbox.setACL(SimpleMailboxACL.EMPTY);
+        return cassandraMailbox;
     }
 
     @Override

Modified: james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java?rev=1678368&r1=1678367&r2=1678368&view=diff
==============================================================================
--- james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java (original)
+++ james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java Fri May  8 15:02:21 2015
@@ -39,14 +39,22 @@ import com.datastax.driver.core.Session;
  * 
  */
 public class CassandraMailboxSessionMapperFactory extends MailboxSessionMapperFactory<UUID> {
+    private static final int DEFAULT_MAX_RETRY = 1000;
+
     private Session session;
     private CassandraUidProvider uidProvider;
     private ModSeqProvider<UUID> modSeqProvider;
+    private int maxRetry;
 
     public CassandraMailboxSessionMapperFactory(CassandraUidProvider uidProvider, ModSeqProvider<UUID> modSeqProvider, CassandraSession session) {
         this.uidProvider = uidProvider;
         this.modSeqProvider = modSeqProvider;
         this.session = session;
+        this.maxRetry = DEFAULT_MAX_RETRY;
+    }
+
+    public void setMaxRetry(int maxRetry) {
+        this.maxRetry = maxRetry;
     }
 
     @Override
@@ -56,7 +64,7 @@ public class CassandraMailboxSessionMapp
 
     @Override
     public MailboxMapper<UUID> createMailboxMapper(MailboxSession mailboxSession) {
-        return new CassandraMailboxMapper(session);
+        return new CassandraMailboxMapper(session, maxRetry);
     }
 
     @Override
@@ -71,4 +79,8 @@ public class CassandraMailboxSessionMapp
     public UidProvider<UUID> getUidProvider() {
         return uidProvider;
     }
+
+    Session getSession() {
+        return session;
+    }
 }

Modified: james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraSession.java
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraSession.java?rev=1678368&r1=1678367&r2=1678368&view=diff
==============================================================================
--- james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraSession.java (original)
+++ james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraSession.java Fri May  8 15:02:21 2015
@@ -31,13 +31,15 @@ import com.google.common.util.concurrent
 
 /**
  * A Cassandra session with the default keyspace
- * 
+ *
  */
 public class CassandraSession implements Session {
     private final static String DEFAULT_CLUSTER_IP = "localhost";
     private final static int DEFAULT_CLUSTER_PORT = 9042;
     private final static String DEFAULT_KEYSPACE_NAME = "apache_james";
     private final static int DEFAULT_REPLICATION_FACTOR = 1;
+    
+    public static final int LIGHTWEIGHT_TRANSACTION_APPLIED = 0;
 
     private Session session;
 
@@ -59,6 +61,7 @@ public class CassandraSession implements
         session.execute("CREATE TABLE IF NOT EXISTS " + keyspace + ".message (" + "mailboxId UUID," + "uid bigint," + "internalDate timestamp," + "bodyStartOctet int," + "content blob," + "modSeq bigint," + "mediaType text," + "subType text," + "fullContentOctets int," + "bodyOctets int,"
                 + "textualLineCount bigint," + "bodyContent blob," + "headerContent blob," + "flagAnswered boolean," + "flagDeleted boolean," + "flagDraft boolean," + "flagRecent boolean," + "flagSeen boolean," + "flagFlagged boolean," + "flagUser boolean," + "flagVersion bigint," + "PRIMARY KEY (mailboxId, uid)" + ");");
         session.execute("CREATE TABLE IF NOT EXISTS " + keyspace + ".subscription (" + "user text," + "mailbox text," + "PRIMARY KEY (mailbox, user)" + ");");
+        session.execute("CREATE TABLE IF NOT EXISTS " + keyspace + ".acl (id uuid PRIMARY KEY, acl text, version bigint);");
         session.close();
     }
 

Added: james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapper.java
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapper.java?rev=1678368&view=auto
==============================================================================
--- james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapper.java (added)
+++ james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapper.java Fri May  8 15:02:21 2015
@@ -0,0 +1,182 @@
+/****************************************************************
+ * 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 com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.Row;
+import com.datastax.driver.core.Session;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Throwables;
+import org.apache.james.mailbox.cassandra.CassandraSession;
+import org.apache.james.mailbox.cassandra.mail.utils.SimpleMailboxACLJsonConverter;
+import org.apache.james.mailbox.cassandra.mail.utils.FunctionRunnerWithRetry;
+import org.apache.james.mailbox.cassandra.table.CassandraACLTable;
+import org.apache.james.mailbox.cassandra.table.CassandraMailboxTable;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.exception.UnsupportedRightException;
+import org.apache.james.mailbox.model.MailboxACL;
+import org.apache.james.mailbox.model.SimpleMailboxACL;
+import org.apache.james.mailbox.store.mail.model.Mailbox;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Optional;
+import java.util.UUID;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.update;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.insertInto;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.set;
+
+public class CassandraACLMapper {
+
+    @FunctionalInterface
+    public interface CodeInjector {
+        void inject();
+    }
+
+    private final Mailbox<UUID> mailbox;
+    private final Session session;
+    private final int maxRetry;
+    private final CodeInjector codeInjector;
+
+    private static final Logger LOG = LoggerFactory.getLogger(CassandraACLMapper.class);
+
+    public CassandraACLMapper(Mailbox<UUID> mailbox, Session session, int maxRetry) {
+        this(mailbox, session, maxRetry, () -> {});
+    }
+
+    public CassandraACLMapper(Mailbox<UUID> mailbox, Session session, int maxRetry, CodeInjector codeInjector) {
+        Preconditions.checkArgument(maxRetry > 0);
+        Preconditions.checkArgument(mailbox.getMailboxId() != null);
+        this.mailbox = mailbox;
+        this.session = session;
+        this.maxRetry = maxRetry;
+        this.codeInjector = codeInjector;
+    }
+
+    public MailboxACL getACL() {
+        ResultSet resultSet = getStoredACLRow();
+        if (resultSet.isExhausted()) {
+            return SimpleMailboxACL.EMPTY;
+        }
+        String serializedACL = resultSet.one().getString(CassandraACLTable.ACL);
+        return deserializeACL(serializedACL);
+    }
+
+    public void updateACL(final MailboxACL.MailboxACLCommand command) throws MailboxException {
+        new FunctionRunnerWithRetry(maxRetry).execute(
+            () -> {
+                codeInjector.inject();
+                ResultSet resultSet = getAclWithVersion()
+                        .map((x) -> x.apply(command))
+                        .map(this::updateStoredACL)
+                        .orElseGet(() -> insertACL(applyCommandOnEmptyACL(command)));
+                return resultSet.one().getBool(CassandraSession.LIGHTWEIGHT_TRANSACTION_APPLIED);
+            }
+        );
+    }
+
+    private MailboxACL applyCommandOnEmptyACL(MailboxACL.MailboxACLCommand command) {
+        try {
+            return SimpleMailboxACL.EMPTY.apply(command);
+        } catch (UnsupportedRightException exception) {
+            throw Throwables.propagate(exception);
+        }
+    }
+
+    private ResultSet getStoredACLRow() {
+        return session.execute(
+            select(CassandraACLTable.ACL, CassandraACLTable.VERSION)
+                .from(CassandraACLTable.TABLE_NAME)
+                .where(eq(CassandraMailboxTable.ID, mailbox.getMailboxId()))
+        );
+    }
+
+    private ResultSet updateStoredACL(ACLWithVersion aclWithVersion) {
+        try {
+            return session.execute(
+                update(CassandraACLTable.TABLE_NAME)
+                    .with(set(CassandraACLTable.ACL, SimpleMailboxACLJsonConverter.toJson(aclWithVersion.mailboxACL)))
+                    .and(set(CassandraACLTable.VERSION, aclWithVersion.version + 1))
+                    .where(eq(CassandraACLTable.ID, mailbox.getMailboxId()))
+                    .onlyIf(eq(CassandraACLTable.VERSION, aclWithVersion.version))
+            );
+        } catch (JsonProcessingException exception) {
+            throw Throwables.propagate(exception);
+        }
+    }
+
+    private ResultSet insertACL(MailboxACL acl) {
+        try {
+            return session.execute(
+                insertInto(CassandraACLTable.TABLE_NAME)
+                    .value(CassandraACLTable.ID, mailbox.getMailboxId())
+                    .value(CassandraACLTable.ACL, SimpleMailboxACLJsonConverter.toJson(acl))
+                    .value(CassandraACLTable.VERSION, 0)
+                    .ifNotExists()
+            );
+        } catch (JsonProcessingException exception) {
+            throw Throwables.propagate(exception);
+        }
+    }
+
+    private Optional<ACLWithVersion> getAclWithVersion() {
+        ResultSet resultSet = getStoredACLRow();
+        if (resultSet.isExhausted()) {
+            return Optional.empty();
+        }
+        Row row = resultSet.one();
+        return Optional.of(new ACLWithVersion(row.getLong(CassandraACLTable.VERSION), deserializeACL(row.getString(CassandraACLTable.ACL))));
+    }
+
+    private MailboxACL deserializeACL(String serializedACL) {
+        try {
+            return SimpleMailboxACLJsonConverter.toACL(serializedACL);
+        } catch(IOException exception) {
+            LOG.error("Unable to read stored ACL. " +
+                "We will use empty ACL instead." +
+                "Mailbox is {}:{}:{} ." +
+                "ACL is {}", mailbox.getNamespace(), mailbox.getUser(), mailbox.getName(), serializedACL, exception);
+            return SimpleMailboxACL.EMPTY;
+        }
+    }
+
+    private class ACLWithVersion {
+        private final long version;
+        private final MailboxACL mailboxACL;
+
+        public ACLWithVersion(long version, MailboxACL mailboxACL) {
+            this.version = version;
+            this.mailboxACL = mailboxACL;
+        }
+
+        public ACLWithVersion apply(MailboxACL.MailboxACLCommand command) {
+            try {
+                return new ACLWithVersion(version, mailboxACL.apply(command));
+            } catch(UnsupportedRightException exception) {
+                throw Throwables.propagate(exception);
+            }
+        }
+    }
+}

Modified: james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapper.java
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapper.java?rev=1678368&r1=1678367&r2=1678368&view=diff
==============================================================================
--- james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapper.java (original)
+++ james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapper.java Fri May  8 15:02:21 2015
@@ -19,37 +19,27 @@
 
 package org.apache.james.mailbox.cassandra.mail;
 
-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.CassandraMailboxTable.FIELDS;
-import static org.apache.james.mailbox.cassandra.table.CassandraMailboxTable.ID;
-import static org.apache.james.mailbox.cassandra.table.CassandraMailboxTable.NAME;
-import static org.apache.james.mailbox.cassandra.table.CassandraMailboxTable.NAMESPACE;
-import static org.apache.james.mailbox.cassandra.table.CassandraMailboxTable.PATH;
-import static org.apache.james.mailbox.cassandra.table.CassandraMailboxTable.TABLE_NAME;
-import static org.apache.james.mailbox.cassandra.table.CassandraMailboxTable.UIDVALIDITY;
-import static org.apache.james.mailbox.cassandra.table.CassandraMailboxTable.USER;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.*;
+import static org.apache.james.mailbox.cassandra.table.CassandraMailboxTable.*;
 
 import java.util.List;
 import java.util.UUID;
 
+import com.google.common.base.Preconditions;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.exception.MailboxNotFoundException;
 import org.apache.james.mailbox.model.MailboxACL;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mailbox.store.mail.MailboxMapper;
 import org.apache.james.mailbox.store.mail.model.Mailbox;
-import org.apache.james.mailbox.store.mail.model.impl.SimpleMailbox;
 
 import com.datastax.driver.core.ResultSet;
 import com.datastax.driver.core.Row;
 import com.datastax.driver.core.Session;
 import com.datastax.driver.core.querybuilder.QueryBuilder;
-import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableList.Builder;
+import org.apache.james.mailbox.store.mail.model.impl.SimpleMailbox;
 
 /**
  * Data access management for mailbox.
@@ -57,9 +47,11 @@ import com.google.common.collect.Immutab
 public class CassandraMailboxMapper implements MailboxMapper<UUID> {
 
     private Session session;
+    private int maxRetry;
 
-    public CassandraMailboxMapper(Session session) {
+    public CassandraMailboxMapper(Session session, int maxRetry) {
         this.session = session;
+        this.maxRetry = maxRetry;
     }
 
     @Override
@@ -68,7 +60,7 @@ public class CassandraMailboxMapper impl
     }
 
     @Override
-    public Mailbox<UUID> findMailboxByPath(MailboxPath path) throws MailboxException, MailboxNotFoundException {
+    public Mailbox<UUID> findMailboxByPath(MailboxPath path) throws MailboxException {
         ResultSet resultSet = session.execute(select(FIELDS).from(TABLE_NAME).where(eq(PATH, path.toString())));
         if (resultSet.isExhausted()) {
             throw new MailboxNotFoundException(path);
@@ -80,6 +72,7 @@ public class CassandraMailboxMapper impl
     private SimpleMailbox<UUID> mailbox(Row row) {
         SimpleMailbox<UUID> mailbox = new SimpleMailbox<UUID>(new MailboxPath(row.getString(NAMESPACE), row.getString(USER), row.getString(NAME)), row.getLong(UIDVALIDITY));
         mailbox.setMailboxId(row.getUUID(ID));
+        mailbox.setACL(new CassandraACLMapper(mailbox, session, maxRetry).getACL());
         return mailbox;
     }
 
@@ -98,16 +91,24 @@ public class CassandraMailboxMapper impl
 
     @Override
     public void save(Mailbox<UUID> mailbox) throws MailboxException {
-        Preconditions.checkArgument(mailbox instanceof SimpleMailbox<?>);
-        SimpleMailbox<UUID> simpleMailbox = (SimpleMailbox<UUID>) mailbox;
-        if (simpleMailbox.getMailboxId() == null) {
-            simpleMailbox.setMailboxId(UUID.randomUUID());
+        Preconditions.checkArgument(mailbox instanceof SimpleMailbox);
+        SimpleMailbox<UUID> cassandraMailbox = (SimpleMailbox<UUID>) mailbox;
+        if (cassandraMailbox.getMailboxId() == null) {
+            cassandraMailbox.setMailboxId(UUID.randomUUID());
         }
-        upsertMailbox(simpleMailbox);
+        upsertMailbox(cassandraMailbox);
     }
 
-    private void upsertMailbox(SimpleMailbox<UUID> mailbox) {
-        session.execute(insertInto(TABLE_NAME).value(ID, mailbox.getMailboxId()).value(NAME, mailbox.getName()).value(NAMESPACE, mailbox.getNamespace()).value(UIDVALIDITY, mailbox.getUidValidity()).value(USER, mailbox.getUser()).value(PATH, path(mailbox).toString()));
+    private void upsertMailbox(SimpleMailbox<UUID> mailbox) throws MailboxException {
+        session.execute(
+            insertInto(TABLE_NAME)
+                .value(ID, mailbox.getMailboxId())
+                .value(NAME, mailbox.getName())
+                .value(NAMESPACE, mailbox.getNamespace())
+                .value(UIDVALIDITY, mailbox.getUidValidity())
+                .value(USER, mailbox.getUser())
+                .value(PATH, path(mailbox).toString())
+        );
     }
 
     private MailboxPath path(Mailbox<?> mailbox) {
@@ -147,6 +148,7 @@ public class CassandraMailboxMapper impl
 
     @Override
     public void updateACL(Mailbox<UUID> mailbox, MailboxACL.MailboxACLCommand mailboxACLCommand) throws MailboxException {
-        mailbox.setACL(mailbox.getACL().apply(mailboxACLCommand));
+        new CassandraACLMapper(mailbox, session, maxRetry).updateACL(mailboxACLCommand);
     }
+
 }

Added: james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/utils/FunctionRunnerWithRetry.java
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/utils/FunctionRunnerWithRetry.java?rev=1678368&view=auto
==============================================================================
--- james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/utils/FunctionRunnerWithRetry.java (added)
+++ james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/utils/FunctionRunnerWithRetry.java Fri May  8 15:02:21 2015
@@ -0,0 +1,45 @@
+/****************************************************************
+ * 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.utils;
+
+import com.google.common.base.Preconditions;
+import org.apache.james.mailbox.exception.MailboxException;
+
+import java.util.function.BooleanSupplier;
+import java.util.stream.IntStream;
+
+public class FunctionRunnerWithRetry {
+    
+    private final int maxRetry;
+
+    public FunctionRunnerWithRetry(int maxRetry) {
+        Preconditions.checkArgument(maxRetry > 0);
+        this.maxRetry = maxRetry;
+    }
+
+    public void execute(BooleanSupplier functionNotifyingSuccess) throws MailboxException {
+        IntStream.range(0, maxRetry)
+            .filter(
+                (x) -> functionNotifyingSuccess.getAsBoolean()
+            ).findFirst()
+            .orElseThrow(() -> new MailboxException("Can not execute Boolean Supplier."));
+    }
+    
+}

Added: james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/utils/SimpleMailboxACLJsonConverter.java
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/utils/SimpleMailboxACLJsonConverter.java?rev=1678368&view=auto
==============================================================================
--- james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/utils/SimpleMailboxACLJsonConverter.java (added)
+++ james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/utils/SimpleMailboxACLJsonConverter.java Fri May  8 15:02:21 2015
@@ -0,0 +1,66 @@
+/****************************************************************
+ * 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.utils;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.KeyDeserializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.apache.james.mailbox.model.MailboxACL;
+import org.apache.james.mailbox.model.SimpleMailboxACL;
+
+import java.io.IOException;
+
+public class SimpleMailboxACLJsonConverter {
+
+    interface Rfc4314RightsMixIn {
+        @JsonValue
+        int getValue();
+    }
+
+    static class ACLKeyDeserializer extends KeyDeserializer {
+        @Override
+        public Object deserializeKey(String key, DeserializationContext deserializationContext ) throws IOException {
+            return new SimpleMailboxACL.SimpleMailboxACLEntryKey(key);
+        }
+    }
+
+    private static ObjectMapper objectMapper = new ObjectMapper();
+
+    static {
+        objectMapper.addMixInAnnotations(SimpleMailboxACL.Rfc4314Rights.class, Rfc4314RightsMixIn.class);
+        SimpleModule module = new SimpleModule();
+        module.addAbstractTypeMapping(MailboxACL.MailboxACLEntryKey.class, SimpleMailboxACL.SimpleMailboxACLEntryKey.class);
+        module.addAbstractTypeMapping(MailboxACL.MailboxACLRights.class, SimpleMailboxACL.Rfc4314Rights.class);
+        module.addKeyDeserializer(MailboxACL.MailboxACLEntryKey.class, new ACLKeyDeserializer());
+        objectMapper.registerModule(module);
+    }
+
+    public static String toJson(MailboxACL acl) throws JsonProcessingException {
+        return objectMapper.writeValueAsString(acl);
+    }
+    
+    public static MailboxACL toACL(String jsonACLString) throws IOException {
+        return objectMapper.readValue(jsonACLString, SimpleMailboxACL.class);
+    }
+}

Added: james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraACLTable.java
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraACLTable.java?rev=1678368&view=auto
==============================================================================
--- james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraACLTable.java (added)
+++ james/mailbox/trunk/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraACLTable.java Fri May  8 15:02:21 2015
@@ -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 CassandraACLTable {
+    String TABLE_NAME = "acl";
+
+    String ID = "id";
+    String ACL = "acl";
+    String VERSION = "version";
+}

Modified: james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraClusterSingleton.java
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraClusterSingleton.java?rev=1678368&r1=1678367&r2=1678368&view=diff
==============================================================================
--- james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraClusterSingleton.java (original)
+++ james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraClusterSingleton.java Fri May  8 15:02:21 2015
@@ -23,8 +23,6 @@ import org.cassandraunit.utils.EmbeddedC
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.datastax.driver.core.Session;
-
 /**
  * Class that will creates a single instance of Cassandra session.
  */
@@ -40,7 +38,7 @@ public final class CassandraClusterSingl
 
     /**
      * Builds a MiniCluster instance.
-     * 
+     *
      * @return the {@link CassandraClusterSingleton} instance
      * @throws RuntimeException
      */
@@ -66,16 +64,16 @@ public final class CassandraClusterSingl
 
     /**
      * Return a configuration for the runnning MiniCluster.
-     * 
+     *
      * @return
      */
-    public Session getConf() {
+    public CassandraSession getConf() {
         return session;
     }
 
     /**
      * Create a specific table.
-     * 
+     *
      * @param tableName
      *            the table name
      */
@@ -94,6 +92,14 @@ public final class CassandraClusterSingl
                     + "flagVersion bigint,"+ "PRIMARY KEY (mailboxId, uid)" + ");");
         } else if (tableName.equals("subscription")) {
             session.execute("CREATE TABLE IF NOT EXISTS " + session.getLoggedKeyspace() + ".subscription (" + "user text," + "mailbox text," + "PRIMARY KEY (mailbox, user)" + ");");
+        } else if (tableName.equals("quota")) {
+            session.execute("CREATE TABLE IF NOT EXISTS " + session.getLoggedKeyspace() + ".quota ("
+                    + "user text PRIMARY KEY,"
+                    + "size_quota counter,"
+                    + "count_quota counter"
+                    + ");");
+        }  else if (tableName.equals("acl")) {
+            session.execute("CREATE TABLE IF NOT EXISTS " + session.getLoggedKeyspace() + ".acl (id uuid PRIMARY KEY, acl text, version bigint);");
         } else {
             throw new NotImplementedException("We don't support the class " + tableName);
         }
@@ -107,6 +113,7 @@ public final class CassandraClusterSingl
         ensureTable("mailboxCounters");
         ensureTable("message");
         ensureTable("subscription");
+        ensureTable("acl");
     }
 
     /**
@@ -126,6 +133,7 @@ public final class CassandraClusterSingl
         clearTable("mailboxCounters");
         clearTable("message");
         clearTable("subscription");
+        clearTable("acl");
     }
 
 }

Added: james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapperTest.java
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapperTest.java?rev=1678368&view=auto
==============================================================================
--- james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapperTest.java (added)
+++ james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapperTest.java Fri May  8 15:02:21 2015
@@ -0,0 +1,219 @@
+/****************************************************************
+ * 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 static com.datastax.driver.core.querybuilder.QueryBuilder.insertInto;
+
+import com.google.common.base.Throwables;
+import org.apache.james.mailbox.cassandra.CassandraClusterSingleton;
+import org.apache.james.mailbox.cassandra.table.CassandraACLTable;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.model.MailboxACL;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.SimpleMailboxACL;
+import org.apache.james.mailbox.store.mail.model.impl.SimpleMailbox;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+public class CassandraACLMapperTest {
+
+    private CassandraACLMapper cassandraACLMapper;
+    private CassandraClusterSingleton cassandra;
+    private SimpleMailbox<UUID> mailbox;
+    private int uidValidity;
+    private int maxRetry;
+    private ExecutorService executor;
+
+    @Before
+    public void setUp() {
+        cassandra = CassandraClusterSingleton.build();
+        cassandra.ensureAllTables();
+        uidValidity = 10;
+        mailbox = new SimpleMailbox<>(new MailboxPath("#private", "benwa@linagora.com", "INBOX"), uidValidity);
+        mailbox.setMailboxId(UUID.fromString("87B045A5-7657-44B7-81E8-40C2937BC9FE"));
+        maxRetry = 100;
+        cassandraACLMapper = new CassandraACLMapper(mailbox, cassandra.getConf(), maxRetry);
+        executor = Executors.newFixedThreadPool(2);
+    }
+
+    @After
+    public void tearDown() {
+        cassandra.clearAllTables();
+        executor.shutdownNow();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void creatingACLMapperWithNegativeMaxRetryShouldFail() {
+        new CassandraACLMapper(mailbox, cassandra.getConf(), -1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void creatingACLMapperWithNullMaxRetryShouldFail() {
+        new CassandraACLMapper(mailbox, cassandra.getConf(), 0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void creatingACLMapperWithNoMailboxIdShouldFail() {
+        new CassandraACLMapper(new SimpleMailbox<>(new MailboxPath("#private", "user", "name"), uidValidity), cassandra.getConf(), maxRetry);
+    }
+
+    @Test
+    public void retrieveACLWhenPresentInBaseShouldReturnCorrespondingACL() throws Exception {
+        cassandra.getConf().execute(
+            insertInto(CassandraACLTable.TABLE_NAME)
+                .value(CassandraACLTable.ID, mailbox.getMailboxId())
+                .value(CassandraACLTable.ACL, "{\"entries\":{\"bob\":64}}")
+                .value(CassandraACLTable.VERSION, 1)
+        );
+        assertThat(cassandraACLMapper.getACL())
+            .isEqualTo(
+                SimpleMailboxACL.EMPTY.union(
+                    new SimpleMailboxACL.SimpleMailboxACLEntryKey("bob", MailboxACL.NameType.user, false),
+                    new SimpleMailboxACL.Rfc4314Rights(SimpleMailboxACL.Rfc4314Rights.r_Read_RIGHT))
+            );
+    }
+
+    @Test
+    public void retrieveACLWhenInvalidInBaseShouldReturnEmptyACL() throws Exception {
+        cassandra.getConf().execute(
+            insertInto(CassandraACLTable.TABLE_NAME)
+                .value(CassandraACLTable.ID, mailbox.getMailboxId())
+                .value(CassandraACLTable.ACL, "{\"entries\":{\"bob\":invalid}}")
+                .value(CassandraACLTable.VERSION, 1)
+        );
+        assertThat(cassandraACLMapper.getACL()).isEqualTo(SimpleMailboxACL.EMPTY);
+    }
+
+    @Test
+    public void retrieveACLWhenNoACLStoredShouldReturnEmptyACL() {
+        assertThat(cassandraACLMapper.getACL()).isEqualTo(SimpleMailboxACL.EMPTY);
+    }
+
+    @Test
+    public void addACLWhenNoneStoredShouldReturnUpdatedACL() throws Exception {
+        SimpleMailboxACL.SimpleMailboxACLEntryKey key = new SimpleMailboxACL.SimpleMailboxACLEntryKey("bob", MailboxACL.NameType.user, false);
+        SimpleMailboxACL.Rfc4314Rights rights = new SimpleMailboxACL.Rfc4314Rights(new SimpleMailboxACL.SimpleMailboxACLRight('r'));
+        cassandraACLMapper.updateACL(new SimpleMailboxACL.SimpleMailboxACLCommand(key, MailboxACL.EditMode.ADD, rights));
+        assertThat(cassandraACLMapper.getACL()).isEqualTo(new SimpleMailboxACL().union(key, rights));
+    }
+
+    @Test
+    public void modifyACLWhenStoredShouldReturnUpdatedACL() throws MailboxException {
+        SimpleMailboxACL.SimpleMailboxACLEntryKey keyBob = new SimpleMailboxACL.SimpleMailboxACLEntryKey("bob", MailboxACL.NameType.user, false);
+        SimpleMailboxACL.Rfc4314Rights rights = new SimpleMailboxACL.Rfc4314Rights(new SimpleMailboxACL.SimpleMailboxACLRight('r'));
+        cassandraACLMapper.updateACL(new SimpleMailboxACL.SimpleMailboxACLCommand(keyBob, MailboxACL.EditMode.ADD, rights));
+        SimpleMailboxACL.SimpleMailboxACLEntryKey keyAlice = new SimpleMailboxACL.SimpleMailboxACLEntryKey("alice", MailboxACL.NameType.user, false);
+        cassandraACLMapper.updateACL(new SimpleMailboxACL.SimpleMailboxACLCommand(keyAlice, MailboxACL.EditMode.ADD, rights));
+        assertThat(cassandraACLMapper.getACL()).isEqualTo(new SimpleMailboxACL().union(keyBob, rights).union(keyAlice, rights));
+    }
+
+    @Test
+    public void removeWhenStoredShouldReturnUpdatedACL() throws MailboxException {
+        SimpleMailboxACL.SimpleMailboxACLEntryKey key = new SimpleMailboxACL.SimpleMailboxACLEntryKey("bob", MailboxACL.NameType.user, false);
+        SimpleMailboxACL.Rfc4314Rights rights = new SimpleMailboxACL.Rfc4314Rights(new SimpleMailboxACL.SimpleMailboxACLRight('r'));
+        cassandraACLMapper.updateACL(new SimpleMailboxACL.SimpleMailboxACLCommand(key, MailboxACL.EditMode.ADD, rights));
+        cassandraACLMapper.updateACL(new SimpleMailboxACL.SimpleMailboxACLCommand(key, MailboxACL.EditMode.REMOVE, rights));
+        assertThat(cassandraACLMapper.getACL()).isEqualTo(SimpleMailboxACL.EMPTY);
+    }
+
+    @Test
+    public void replaceForSingleKeyWithNullRightsWhenSingleKeyStoredShouldReturnEmptyACL() throws MailboxException {
+        SimpleMailboxACL.SimpleMailboxACLEntryKey key = new SimpleMailboxACL.SimpleMailboxACLEntryKey("bob", MailboxACL.NameType.user, false);
+        SimpleMailboxACL.Rfc4314Rights rights = new SimpleMailboxACL.Rfc4314Rights(new SimpleMailboxACL.SimpleMailboxACLRight('r'));
+        cassandraACLMapper.updateACL(new SimpleMailboxACL.SimpleMailboxACLCommand(key, MailboxACL.EditMode.ADD, rights));
+        cassandraACLMapper.updateACL(new SimpleMailboxACL.SimpleMailboxACLCommand(key, MailboxACL.EditMode.REPLACE, null));
+        assertThat(cassandraACLMapper.getACL()).isEqualTo(SimpleMailboxACL.EMPTY);
+    }
+
+    @Test
+    public void replaceWhenNotStoredShouldUpdateACLEntry() throws MailboxException {
+        SimpleMailboxACL.SimpleMailboxACLEntryKey key = new SimpleMailboxACL.SimpleMailboxACLEntryKey("bob", MailboxACL.NameType.user, false);
+        SimpleMailboxACL.Rfc4314Rights rights = new SimpleMailboxACL.Rfc4314Rights(new SimpleMailboxACL.SimpleMailboxACLRight('r'));
+        cassandraACLMapper.updateACL(new SimpleMailboxACL.SimpleMailboxACLCommand(key, MailboxACL.EditMode.REPLACE, rights));
+        assertThat(cassandraACLMapper.getACL()).isEqualTo(new SimpleMailboxACL().union(key, rights));
+    }
+
+    @Test
+    public void updateInvalidACLShouldBeBasedOnEmptyACL() throws Exception {
+        cassandra.getConf().execute(
+            insertInto(CassandraACLTable.TABLE_NAME)
+                .value(CassandraACLTable.ID, mailbox.getMailboxId())
+                .value(CassandraACLTable.ACL, "{\"entries\":{\"bob\":invalid}}")
+                .value(CassandraACLTable.VERSION, 1)
+        );
+        SimpleMailboxACL.SimpleMailboxACLEntryKey key = new SimpleMailboxACL.SimpleMailboxACLEntryKey("bob", MailboxACL.NameType.user, false);
+        SimpleMailboxACL.Rfc4314Rights rights = new SimpleMailboxACL.Rfc4314Rights(new SimpleMailboxACL.SimpleMailboxACLRight('r'));
+        cassandraACLMapper.updateACL(new SimpleMailboxACL.SimpleMailboxACLCommand(key, MailboxACL.EditMode.ADD, rights));
+        assertThat(cassandraACLMapper.getACL()).isEqualTo(new SimpleMailboxACL().union(key, rights));
+    }
+
+    @Test
+    public void twoConcurrentUpdatesWhenNoACEStoredShouldReturnACEWithTwoEntries() throws Exception {
+        CountDownLatch countDownLatch = new CountDownLatch(2);
+        SimpleMailboxACL.SimpleMailboxACLEntryKey keyBob = new SimpleMailboxACL.SimpleMailboxACLEntryKey("bob", MailboxACL.NameType.user, false);
+        SimpleMailboxACL.Rfc4314Rights rights = new SimpleMailboxACL.Rfc4314Rights(new SimpleMailboxACL.SimpleMailboxACLRight('r'));
+        SimpleMailboxACL.SimpleMailboxACLEntryKey keyAlice = new SimpleMailboxACL.SimpleMailboxACLEntryKey("alice", MailboxACL.NameType.user, false);
+        Future<Boolean> future1 = performACLUpdateInExecutor(executor, keyBob, rights, countDownLatch::countDown);
+        Future<Boolean> future2 = performACLUpdateInExecutor(executor, keyAlice, rights, countDownLatch::countDown);
+        awaitAll(future1, future2);
+        assertThat(cassandraACLMapper.getACL()).isEqualTo(new SimpleMailboxACL().union(keyBob, rights).union(keyAlice, rights));
+    }
+
+    @Test
+    public void twoConcurrentUpdatesWhenStoredShouldReturnACEWithTwoEntries() throws Exception {
+        CountDownLatch countDownLatch = new CountDownLatch(2);
+        SimpleMailboxACL.SimpleMailboxACLEntryKey keyBenwa = new SimpleMailboxACL.SimpleMailboxACLEntryKey("benwa", MailboxACL.NameType.user, false);
+        SimpleMailboxACL.Rfc4314Rights rights = new SimpleMailboxACL.Rfc4314Rights(new SimpleMailboxACL.SimpleMailboxACLRight('r'));
+        cassandraACLMapper.updateACL(new SimpleMailboxACL.SimpleMailboxACLCommand(keyBenwa, MailboxACL.EditMode.ADD, rights));
+        SimpleMailboxACL.SimpleMailboxACLEntryKey keyBob = new SimpleMailboxACL.SimpleMailboxACLEntryKey("bob", MailboxACL.NameType.user, false);
+        SimpleMailboxACL.SimpleMailboxACLEntryKey keyAlice = new SimpleMailboxACL.SimpleMailboxACLEntryKey("alice", MailboxACL.NameType.user, false);
+        Future<Boolean> future1 = performACLUpdateInExecutor(executor, keyBob, rights, countDownLatch::countDown);
+        Future<Boolean> future2 = performACLUpdateInExecutor(executor, keyAlice, rights, countDownLatch::countDown);
+        awaitAll(future1, future2);
+        assertThat(cassandraACLMapper.getACL()).isEqualTo(new SimpleMailboxACL().union(keyBob, rights).union(keyAlice, rights).union(keyBenwa, rights));
+    }
+
+    private void awaitAll(Future<Boolean>... futures) throws InterruptedException, java.util.concurrent.ExecutionException, java.util.concurrent.TimeoutException {
+        for (Future<Boolean> future : futures) {
+            future.get(10l, TimeUnit.SECONDS);
+        }
+    }
+
+    private Future<Boolean> performACLUpdateInExecutor(ExecutorService executor, SimpleMailboxACL.SimpleMailboxACLEntryKey key, SimpleMailboxACL.Rfc4314Rights rights, CassandraACLMapper.CodeInjector runnable) {
+        return executor.submit(() -> {
+            CassandraACLMapper aclMapper = new CassandraACLMapper(mailbox, cassandra.getConf(), maxRetry, runnable);
+            try {
+                aclMapper.updateACL(new SimpleMailboxACL.SimpleMailboxACLCommand(key, MailboxACL.EditMode.ADD, rights));
+            } catch (MailboxException exception) {
+                throw Throwables.propagate(exception);
+            }
+            return true;
+        });
+    }
+
+}

Modified: james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperTest.java
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperTest.java?rev=1678368&r1=1678367&r2=1678368&view=diff
==============================================================================
--- james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperTest.java (original)
+++ james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperTest.java Fri May  8 15:02:21 2015
@@ -33,13 +33,12 @@ import org.apache.james.mailbox.exceptio
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mailbox.store.mail.model.Mailbox;
 import org.apache.james.mailbox.store.mail.model.impl.SimpleMailbox;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.datastax.driver.core.Session;
-
 /**
  * CassandraMailboxMapper unit tests.
  * 
@@ -47,28 +46,31 @@ import com.datastax.driver.core.Session;
 public class CassandraMailboxMapperTest {
 
     private static final Logger LOG = LoggerFactory.getLogger(CassandraMailboxMapperTest.class);
-    public static final CassandraClusterSingleton CLUSTER = CassandraClusterSingleton.build();
+    public static final CassandraClusterSingleton CASSANDRA = CassandraClusterSingleton.build();
     private static CassandraMailboxMapper mapper;
     private static List<SimpleMailbox<UUID>> mailboxList;
     private static List<MailboxPath> pathsList;
     private static final int NAMESPACES = 5;
     private static final int USERS = 5;
     private static final int MAILBOX_NO = 5;
+    private static final int MAX_RETRY = 100;
     private static final char SEPARATOR = '%';
-    private Session session;
 
     @Before
     public void setUp() throws Exception {
-        CLUSTER.ensureAllTables();
-        CLUSTER.clearAllTables();
-        session = CLUSTER.getConf();
+        CASSANDRA.ensureAllTables();
         fillMailboxList();
-        mapper = new CassandraMailboxMapper(session);
+        mapper = new CassandraMailboxMapper(CASSANDRA.getConf(), MAX_RETRY);
         for (SimpleMailbox<UUID> mailbox : mailboxList) {
             mapper.save(mailbox);
         }
     }
 
+    @After
+    public void cleanUp() {
+        CASSANDRA.clearAllTables();
+    }
+
     /**
      * Test an ordered scenario with list, delete... methods.
      * 
@@ -119,7 +121,7 @@ public class CassandraMailboxMapperTest
             if (i % 2 == 0) {
                 newPath.setUser(null);
             }
-            addMailbox(new SimpleMailbox<UUID>(newPath, 1234));
+            addMailbox(new SimpleMailbox<>(newPath, 1234));
         }
         result = mapper.findMailboxWithPathLike(path);
         assertEquals(end - start + 1, result.size());
@@ -193,7 +195,7 @@ public class CassandraMailboxMapperTest
         LOG.info("hasChildren");
         String oldName;
         for (MailboxPath path : pathsList) {
-            final SimpleMailbox<UUID> mailbox = new SimpleMailbox<UUID>(path, 12455);
+            final SimpleMailbox<UUID> mailbox = new SimpleMailbox<>(path, 12455);
             oldName = mailbox.getName();
             if (path.getUser().equals("user3")) {
                 mailbox.setName("test");
@@ -210,8 +212,8 @@ public class CassandraMailboxMapperTest
     }
 
     private static void fillMailboxList() {
-        mailboxList = new ArrayList<SimpleMailbox<UUID>>();
-        pathsList = new ArrayList<MailboxPath>();
+        mailboxList = new ArrayList<>();
+        pathsList = new ArrayList<>();
         MailboxPath path;
         String name;
         for (int i = 0; i < NAMESPACES; i++) {
@@ -224,7 +226,7 @@ public class CassandraMailboxMapperTest
                     }
                     path = new MailboxPath("namespace" + i, "user" + j, name);
                     pathsList.add(path);
-                    mailboxList.add(new SimpleMailbox<UUID>(path, 13));
+                    mailboxList.add(new SimpleMailbox<>(path, 13));
                 }
             }
         }

Modified: james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraUidAndModSeqProviderTest.java
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraUidAndModSeqProviderTest.java?rev=1678368&r1=1678367&r2=1678368&view=diff
==============================================================================
--- james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraUidAndModSeqProviderTest.java (original)
+++ james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraUidAndModSeqProviderTest.java Fri May  8 15:02:21 2015
@@ -27,13 +27,12 @@ import java.util.UUID;
 import org.apache.james.mailbox.cassandra.CassandraClusterSingleton;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mailbox.store.mail.model.impl.SimpleMailbox;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.datastax.driver.core.Session;
-
 /**
  * Unit tests for UidProvider and ModSeqProvider.
  * 
@@ -41,8 +40,7 @@ import com.datastax.driver.core.Session;
 public class CassandraUidAndModSeqProviderTest {
 
     private static final Logger LOG = LoggerFactory.getLogger(CassandraUidAndModSeqProviderTest.class);
-    private static final CassandraClusterSingleton CLUSTER = CassandraClusterSingleton.build();
-    private static Session session;
+    private static final CassandraClusterSingleton CASSANDRA = CassandraClusterSingleton.build();
     private static CassandraUidProvider uidProvider;
     private static CassandraModSeqProvider modSeqProvider;
     private static CassandraMailboxMapper mapper;
@@ -51,25 +49,29 @@ public class CassandraUidAndModSeqProvid
     private static final int NAMESPACES = 5;
     private static final int USERS = 5;
     private static final int MAILBOX_NO = 5;
+    private static final int MAX_RETRY = 100;
     private static final char SEPARATOR = '%';
 
     @Before
     public void setUpClass() throws Exception {
-        CLUSTER.ensureAllTables();
-        CLUSTER.ensureAllTables();
-        session = CLUSTER.getConf();
-        uidProvider = new CassandraUidProvider(session);
-        modSeqProvider = new CassandraModSeqProvider(session);
-        mapper = new CassandraMailboxMapper(session);
+        CASSANDRA.ensureAllTables();
+        uidProvider = new CassandraUidProvider(CASSANDRA.getConf());
+        modSeqProvider = new CassandraModSeqProvider(CASSANDRA.getConf());
+        mapper = new CassandraMailboxMapper(CASSANDRA.getConf(), MAX_RETRY);
         fillMailboxList();
         for (SimpleMailbox<UUID> mailbox : mailboxList) {
             mapper.save(mailbox);
         }
     }
 
+    @After
+    public void cleanUp() {
+        CASSANDRA.clearAllTables();
+    }
+
     private static void fillMailboxList() {
-        mailboxList = new ArrayList<SimpleMailbox<UUID>>();
-        pathsList = new ArrayList<MailboxPath>();
+        mailboxList = new ArrayList<>();
+        pathsList = new ArrayList<>();
         MailboxPath path;
         String name;
         for (int i = 0; i < NAMESPACES; i++) {
@@ -82,7 +84,7 @@ public class CassandraUidAndModSeqProvid
                     }
                     path = new MailboxPath("namespace" + i, "user" + j, name);
                     pathsList.add(path);
-                    mailboxList.add(new SimpleMailbox<UUID>(path, 13));
+                    mailboxList.add(new SimpleMailbox<>(path, 13));
                 }
             }
         }
@@ -97,7 +99,7 @@ public class CassandraUidAndModSeqProvid
     public void testLastUid() throws Exception {
         LOG.info("lastUid");
         final MailboxPath path = new MailboxPath("gsoc", "ieugen", "Trash");
-        final SimpleMailbox<UUID> newBox = new SimpleMailbox<UUID>(path, 1234);
+        final SimpleMailbox<UUID> newBox = new SimpleMailbox<>(path, 1234);
         mapper.save(newBox);
         mailboxList.add(newBox);
         pathsList.add(path);
@@ -133,7 +135,7 @@ public class CassandraUidAndModSeqProvid
         LOG.info("highestModSeq");
         LOG.info("lastUid");
         MailboxPath path = new MailboxPath("gsoc", "ieugen", "Trash");
-        SimpleMailbox<UUID> newBox = new SimpleMailbox<UUID>(path, 1234);
+        SimpleMailbox<UUID> newBox = new SimpleMailbox<>(path, 1234);
         mapper.save(newBox);
         mailboxList.add(newBox);
         pathsList.add(path);

Added: james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/FunctionRunnerWithRetryTest.java
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/FunctionRunnerWithRetryTest.java?rev=1678368&view=auto
==============================================================================
--- james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/FunctionRunnerWithRetryTest.java (added)
+++ james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/FunctionRunnerWithRetryTest.java Fri May  8 15:02:21 2015
@@ -0,0 +1,85 @@
+/****************************************************************
+ * 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.utils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.commons.lang.mutable.MutableInt;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.junit.Test;
+
+public class FunctionRunnerWithRetryTest {
+    
+    private final static int MAX_RETRY = 10;
+
+    @Test(expected = IllegalArgumentException.class)
+    public void functionRunnerWithInvalidMaxRetryShouldFail() throws Exception {
+        new FunctionRunnerWithRetry(-1);
+    }
+
+    @Test(expected = MailboxException.class)
+    public void functionRunnerShouldFailIfTransactionCanNotBePerformed() throws Exception {
+        final MutableInt value = new MutableInt(0);
+        new FunctionRunnerWithRetry(MAX_RETRY).execute(
+            () -> {
+                value.increment();
+                return false;
+            }
+        );
+        assertThat(value.getValue()).isEqualTo(MAX_RETRY);
+    }
+    
+    @Test
+    public void functionRunnerShouldWorkOnFirstTry() throws Exception {
+        final MutableInt value = new MutableInt(0);
+        new FunctionRunnerWithRetry(MAX_RETRY).execute(
+            () -> {
+                value.increment();
+                return true;
+            }
+        );
+        assertThat(value.getValue()).isEqualTo(1);
+    }
+
+    @Test
+    public void functionRunnerShouldWorkIfNotSucceededOnFirstTry() throws Exception {
+        final MutableInt value = new MutableInt(0);
+        new FunctionRunnerWithRetry(MAX_RETRY).execute(
+            () -> {
+                value.increment();
+                return (Integer) value.getValue() == MAX_RETRY / 2;
+            }
+        );
+        assertThat(value.getValue()).isEqualTo(MAX_RETRY / 2);
+    }
+
+    @Test
+    public void functionRunnerShouldWorkIfNotSucceededOnMaxRetryReached() throws Exception {
+        final MutableInt value = new MutableInt(0);
+        new FunctionRunnerWithRetry(MAX_RETRY).execute(
+                () -> {
+                    value.increment();
+                    return (Integer) value.getValue() == MAX_RETRY;
+                }
+        );
+        assertThat(value.getValue()).isEqualTo(MAX_RETRY);
+    }
+    
+}

Added: james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/SimpleMailboxACLJsonConverterTest.java
URL: http://svn.apache.org/viewvc/james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/SimpleMailboxACLJsonConverterTest.java?rev=1678368&view=auto
==============================================================================
--- james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/SimpleMailboxACLJsonConverterTest.java (added)
+++ james/mailbox/trunk/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/SimpleMailboxACLJsonConverterTest.java Fri May  8 15:02:21 2015
@@ -0,0 +1,119 @@
+/****************************************************************
+ * 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.utils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.james.mailbox.model.MailboxACL;
+import org.apache.james.mailbox.model.SimpleMailboxACL;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class SimpleMailboxACLJsonConverterTest {
+
+    public class ACLMapBuilder {
+        private Map<SimpleMailboxACL.MailboxACLEntryKey, MailboxACL.MailboxACLRights> map;
+
+        public ACLMapBuilder() {
+            map = new HashMap<>();
+        }
+
+        public ACLMapBuilder addSingleUserEntryToMap() {
+            SimpleMailboxACL.Rfc4314Rights rights = new SimpleMailboxACL.Rfc4314Rights(false, true, true, true, false, true, false, true, true, true, true);
+            SimpleMailboxACL.MailboxACLEntryKey key = new SimpleMailboxACL.SimpleMailboxACLEntryKey("user", MailboxACL.NameType.user, true);
+            map.put(key, rights);
+            return this;
+        }
+
+        public ACLMapBuilder addSingleSpecialEntryToMap() {
+            SimpleMailboxACL.Rfc4314Rights rights = new SimpleMailboxACL.Rfc4314Rights(false, false, true, true, false, true, false, true, false, true, true);
+            SimpleMailboxACL.MailboxACLEntryKey key = new SimpleMailboxACL.SimpleMailboxACLEntryKey("special", MailboxACL.NameType.special, true);
+            map.put(key, rights);
+            return this;
+        }
+
+        public ACLMapBuilder addSingleGroupEntryToMap() {
+            SimpleMailboxACL.Rfc4314Rights rights = new SimpleMailboxACL.Rfc4314Rights(false, false, true, true, false, true, false, true, true, true, true);
+            SimpleMailboxACL.MailboxACLEntryKey key = new SimpleMailboxACL.SimpleMailboxACLEntryKey("group", MailboxACL.NameType.group, true);
+            map.put(key, rights);
+            return this;
+        }
+
+        public MailboxACL buildAsACL() {
+            return new SimpleMailboxACL(new HashMap<>(map));
+        }
+
+    }
+
+    @Test
+    public void emptyACLShouldBeWellSerialized() throws Exception {
+        assertThat(SimpleMailboxACLJsonConverter.toJson(SimpleMailboxACL.EMPTY)).isEqualTo("{\"entries\":{}}");
+    }
+
+    @Test
+    public void singleUserEntryACLShouldBeWellSerialized() throws Exception {
+        assertThat(SimpleMailboxACLJsonConverter.toJson(new ACLMapBuilder().addSingleUserEntryToMap().buildAsACL()))
+            .isEqualTo("{\"entries\":{\"-user\":2040}}");
+    }
+
+    @Test
+    public void singleGroupEntryACLShouldBeWellSerialized() throws Exception {
+        assertThat(SimpleMailboxACLJsonConverter.toJson(new ACLMapBuilder().addSingleGroupEntryToMap().buildAsACL()))
+            .isEqualTo("{\"entries\":{\"-$group\":2032}}");
+    }
+
+    @Test
+    public void singleSpecialEntryACLShouldBeWellSerialized() throws Exception {
+        assertThat(SimpleMailboxACLJsonConverter.toJson(new ACLMapBuilder().addSingleSpecialEntryToMap().buildAsACL()))
+            .isEqualTo("{\"entries\":{\"-special\":1968}}");
+    }
+
+    @Test
+    public void multipleEntriesACLShouldBeWellSerialized() throws Exception {
+        assertThat(SimpleMailboxACLJsonConverter.toJson(new ACLMapBuilder().addSingleUserEntryToMap().addSingleGroupEntryToMap().buildAsACL()))
+            .isEqualTo("{\"entries\":{\"-user\":2040,\"-$group\":2032}}");
+    }
+
+    @Test
+    public void emptyACLShouldBeWellDeSerialized() throws Exception {
+        assertThat(SimpleMailboxACLJsonConverter.toACL("{\"entries\":{}}")).isEqualTo(SimpleMailboxACL.EMPTY);
+    }
+
+    @Test
+    public void singleUserEntryACLShouldBeWellDeSerialized() throws Exception {
+        assertThat(SimpleMailboxACLJsonConverter.toACL("{\"entries\":{\"-user\":2040}}"))
+            .isEqualTo(new ACLMapBuilder().addSingleUserEntryToMap().buildAsACL());
+    }
+
+    @Test
+    public void singleGroupEntryACLShouldBeWellDeSerialized() throws Exception {
+        assertThat(SimpleMailboxACLJsonConverter.toACL("{\"entries\":{\"-$group\":2032}}"))
+            .isEqualTo(new ACLMapBuilder().addSingleGroupEntryToMap().buildAsACL());
+    }
+
+    @Test
+    public void multipleEntriesACLShouldBeWellDeSerialized() throws Exception {
+        assertThat(SimpleMailboxACLJsonConverter.toACL("{\"entries\":{\"-user\":2040,\"-$group\":2032}}"))
+            .isEqualTo(new ACLMapBuilder().addSingleUserEntryToMap().addSingleGroupEntryToMap().buildAsACL());
+    }
+
+}



---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org