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 ma...@apache.org on 2017/09/29 07:22:02 UTC

[24/31] james-project git commit: MAILBOX-307 Removing unneeded interface-sception for ACLs

MAILBOX-307 Removing unneeded interface-sception for ACLs


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/4e10f10b
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/4e10f10b
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/4e10f10b

Branch: refs/heads/master
Commit: 4e10f10b6e5b67972bd5b9cbdfaeb1b3eb7dcef9
Parents: 91d303b
Author: benwa <bt...@linagora.com>
Authored: Wed Sep 27 09:41:23 2017 +0700
Committer: Matthieu Baechler <ma...@apache.org>
Committed: Fri Sep 29 09:20:40 2017 +0200

----------------------------------------------------------------------
 .../apache/james/mailbox/MailboxManager.java    |  11 +-
 .../james/mailbox/acl/MailboxACLResolver.java   |  11 +-
 .../mailbox/acl/UnionMailboxACLResolver.java    |  94 ++-
 .../exception/UnsupportedRightException.java    |   4 +-
 .../apache/james/mailbox/model/MailboxACL.java  | 823 +++++++++++++++----
 .../james/mailbox/model/SimpleMailboxACL.java   | 679 ---------------
 .../acl/UnionMailboxACLResolverTest.java        | 611 +++++++-------
 .../mailbox/model/MailboxACLEntryKeyTest.java   | 157 ++++
 .../james/mailbox/model/MailboxACLTest.java     | 224 +++++
 .../james/mailbox/model/Rfc4314RightsTest.java  |  75 +-
 .../model/SimpleMailboxACLEntryKeyTest.java     | 157 ----
 .../mailbox/model/SimpleMailboxACLTest.java     | 226 -----
 .../mailbox/caching/CachingMailboxMapper.java   |   3 +-
 .../cassandra/CassandraMailboxManager.java      |   4 +-
 .../cassandra/mail/CassandraACLMapper.java      |  13 +-
 .../cassandra/mail/CassandraMailboxMapper.java  |   2 +-
 .../cassandra/mail/CassandraACLMapperTest.java  |  91 +-
 .../mailbox/hbase/mail/HBaseMailboxMapper.java  |   2 +-
 .../mailbox/hbase/mail/model/HBaseMailbox.java  |   3 +-
 .../mailbox/jcr/mail/JCRMailboxMapper.java      |   2 +-
 .../mailbox/jcr/mail/model/JCRMailbox.java      |   3 +-
 .../mailbox/jpa/mail/JPAMailboxMapper.java      |   2 +-
 .../mailbox/jpa/mail/model/JPAMailbox.java      |   3 +-
 .../jpa/mail/TransactionalMailboxMapper.java    |   3 +-
 .../LuceneMailboxMessageSearchIndexTest.java    |   3 +-
 .../james/mailbox/maildir/MaildirFolder.java    |  17 +-
 .../maildir/mail/MaildirMailboxMapper.java      |   2 +-
 .../inmemory/mail/InMemoryMailboxMapper.java    |   2 +-
 .../mailbox/store/StoreMailboxManager.java      |  25 +-
 .../mailbox/store/StoreMessageManager.java      |   7 +-
 .../json/SimpleMailboxACLJsonConverter.java     |  11 +-
 .../james/mailbox/store/mail/MailboxMapper.java |   2 +-
 .../store/mail/model/impl/SimpleMailbox.java    |   5 +-
 .../store/TestMailboxSessionMapperFactory.java  |   2 +-
 .../store/json/MailboxACLJsonConverterTest.java | 140 ++++
 .../json/SimpleMailboxACLJsonConverterTest.java | 139 ----
 .../store/mail/model/ListMailboxAssertTest.java |   2 +-
 .../store/mail/model/MailboxMapperACLTest.java  | 103 ++-
 .../mpt/imapmailbox/GrantRightsOnHost.java      |   2 +-
 .../mpt/imapmailbox/suite/ACLCommands.java      |   5 +-
 .../mpt/imapmailbox/suite/ACLIntegration.java   |  68 +-
 .../suite/ACLScriptedTestProtocol.java          |   7 +-
 .../cyrus/host/GrantRightsOnCyrusHost.java      |   5 +-
 .../james/imap/encode/ACLResponseEncoder.java   |   8 +-
 .../imap/encode/ListRightsResponseEncoder.java  |   6 +-
 .../imap/encode/MyRightsResponseEncoder.java    |   4 +-
 .../imap/message/response/ACLResponse.java      |  16 +-
 .../message/response/ListRightsResponse.java    |  10 +-
 .../imap/message/response/MyRightsResponse.java |   8 +-
 .../imap/processor/DeleteACLProcessor.java      |  15 +-
 .../james/imap/processor/GetACLProcessor.java   |   8 +-
 .../james/imap/processor/GetQuotaProcessor.java |   6 +-
 .../imap/processor/GetQuotaRootProcessor.java   |   6 +-
 .../imap/processor/ListRightsProcessor.java     |  17 +-
 .../james/imap/processor/MyRightsProcessor.java |  18 +-
 .../james/imap/processor/SetACLProcessor.java   |  25 +-
 .../imap/processor/DeleteACLProcessorTest.java  |  22 +-
 .../imap/processor/GetACLProcessorTest.java     |  13 +-
 .../imap/processor/GetQuotaProcessorTest.java   |   8 +-
 .../processor/GetQuotaRootProcessorTest.java    |   7 +-
 .../imap/processor/ListRightsProcessorTest.java |  27 +-
 .../imap/processor/SetACLProcessorTest.java     |  29 +-
 .../base/MailboxEventAnalyserTest.java          |  16 +-
 ...ltMailboxesProvisioningFilterThreadTest.java |  17 +-
 64 files changed, 1908 insertions(+), 2128 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/4e10f10b/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java
----------------------------------------------------------------------
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java b/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java
index 2963ad6..3ef58b7 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java
@@ -40,7 +40,6 @@ import org.apache.james.mailbox.model.MailboxQuery;
 import org.apache.james.mailbox.model.MessageId;
 import org.apache.james.mailbox.model.MessageRange;
 import org.apache.james.mailbox.model.MultimailboxesSearchQuery;
-import org.apache.james.mailbox.model.SimpleMailboxACL;
 
 /**
  * <p>
@@ -348,7 +347,7 @@ public interface MailboxManager extends RequestAware, MailboxListenerSupport {
      *         mailbox; false otherwise.
      * @throws MailboxException
      */
-    boolean hasRight(MailboxPath mailboxPath, MailboxACL.MailboxACLRight right, MailboxSession session) throws MailboxException;
+    boolean hasRight(MailboxPath mailboxPath, MailboxACL.Right right, MailboxSession session) throws MailboxException;
 
     /**
      * Returns the rights applicable to the user who has sent the current
@@ -357,11 +356,11 @@ public interface MailboxManager extends RequestAware, MailboxListenerSupport {
      * @param mailboxPath Path of the mailbox you want to get your rights on.
      * @param session The session used to determine the user we should retrieve the rights of.
      * @return the rights applicable to the user who has sent the request,
-     *         returns {@link SimpleMailboxACL#NO_RIGHTS} if
+     *         returns {@link MailboxACL#NO_RIGHTS} if
      *         {@code session.getUser()} is null.
      * @throws UnsupportedRightException
      */
-    MailboxACL.MailboxACLRights myRights(MailboxPath mailboxPath, MailboxSession session) throws MailboxException;
+    MailboxACL.Rfc4314Rights myRights(MailboxPath mailboxPath, MailboxSession session) throws MailboxException;
 
     /**
      * Computes a result suitable for the LISTRIGHTS IMAP command. The result is
@@ -385,7 +384,7 @@ public interface MailboxManager extends RequestAware, MailboxListenerSupport {
      * @return result suitable for the LISTRIGHTS IMAP command
      * @throws UnsupportedRightException
      */
-    MailboxACL.MailboxACLRights[] listRigths(MailboxPath mailboxPath, MailboxACL.MailboxACLEntryKey identifier, MailboxSession session) throws MailboxException;
+    MailboxACL.Rfc4314Rights[] listRigths(MailboxPath mailboxPath, MailboxACL.EntryKey identifier, MailboxSession session) throws MailboxException;
 
     /**
      * Update the Mailbox ACL of the designated mailbox. We can either ADD REPLACE or REMOVE entries.
@@ -393,7 +392,7 @@ public interface MailboxManager extends RequestAware, MailboxListenerSupport {
      * @param mailboxACLCommand Update to perform.
      * @throws UnsupportedRightException
      */
-    void setRights(MailboxPath mailboxPath, MailboxACL.MailboxACLCommand mailboxACLCommand, MailboxSession session) throws MailboxException;
+    void setRights(MailboxPath mailboxPath, MailboxACL.ACLCommand mailboxACLCommand, MailboxSession session) throws MailboxException;
 
 
     /**

http://git-wip-us.apache.org/repos/asf/james-project/blob/4e10f10b/mailbox/api/src/main/java/org/apache/james/mailbox/acl/MailboxACLResolver.java
----------------------------------------------------------------------
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/acl/MailboxACLResolver.java b/mailbox/api/src/main/java/org/apache/james/mailbox/acl/MailboxACLResolver.java
index 55fc15d..f858756 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/acl/MailboxACLResolver.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/acl/MailboxACLResolver.java
@@ -24,9 +24,6 @@ import javax.mail.Flags;
 
 import org.apache.james.mailbox.exception.UnsupportedRightException;
 import org.apache.james.mailbox.model.MailboxACL;
-import org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey;
-import org.apache.james.mailbox.model.MailboxACL.MailboxACLRight;
-import org.apache.james.mailbox.model.MailboxACL.MailboxACLRights;
 
 /**
  * Implements the interpretation of ACLs.
@@ -86,7 +83,7 @@ public interface MailboxACLResolver {
      *         resource; false otherwise.
      * @throws UnsupportedRightException
      */
-    boolean hasRight(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACLRight right, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException;
+    boolean hasRight(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACL.Right right, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException;
 
     /**
      * Maps the given {@code mailboxACLRights} to READ-WRITE and READ-ONLY
@@ -125,7 +122,7 @@ public interface MailboxACLResolver {
      * @return
      * @throws UnsupportedRightException
      */
-    boolean isReadWrite(MailboxACLRights mailboxACLRights, Flags sharedFlags) throws UnsupportedRightException;
+    boolean isReadWrite(MailboxACL.Rfc4314Rights mailboxACLRights, Flags sharedFlags) throws UnsupportedRightException;
 
     /**
      * Computes a result suitable for the LISTRIGHTS IMAP command. The result is
@@ -148,7 +145,7 @@ public interface MailboxACLResolver {
      *         of rights which can be set for the given identifier and resource.
      * @throws UnsupportedRightException
      */
-    MailboxACLRights[] listRights(MailboxACLEntryKey key, GroupMembershipResolver groupMembershipResolver, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException;
+    MailboxACL.Rfc4314Rights[] listRights(MailboxACL.EntryKey key, GroupMembershipResolver groupMembershipResolver, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException;
 
     /**
      * Computes the rights which apply to the given user and resource. Global
@@ -173,6 +170,6 @@ public interface MailboxACLResolver {
      * @return the rights applicable for the given user and resource.
      * @throws UnsupportedRightException
      */
-    MailboxACLRights resolveRights(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException;
+    MailboxACL.Rfc4314Rights resolveRights(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException;
 
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/4e10f10b/mailbox/api/src/main/java/org/apache/james/mailbox/acl/UnionMailboxACLResolver.java
----------------------------------------------------------------------
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/acl/UnionMailboxACLResolver.java b/mailbox/api/src/main/java/org/apache/james/mailbox/acl/UnionMailboxACLResolver.java
index 545d5df..8f51b1a 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/acl/UnionMailboxACLResolver.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/acl/UnionMailboxACLResolver.java
@@ -30,13 +30,11 @@ import javax.mail.Flags.Flag;
 
 import org.apache.james.mailbox.exception.UnsupportedRightException;
 import org.apache.james.mailbox.model.MailboxACL;
-import org.apache.james.mailbox.model.MailboxACL.MailboxACLEntryKey;
-import org.apache.james.mailbox.model.MailboxACL.MailboxACLRight;
-import org.apache.james.mailbox.model.MailboxACL.MailboxACLRights;
+import org.apache.james.mailbox.model.MailboxACL.EntryKey;
 import org.apache.james.mailbox.model.MailboxACL.NameType;
-import org.apache.james.mailbox.model.SimpleMailboxACL;
-import org.apache.james.mailbox.model.SimpleMailboxACL.Rfc4314Rights;
-import org.apache.james.mailbox.model.SimpleMailboxACL.SimpleMailboxACLEntryKey;
+import org.apache.james.mailbox.model.MailboxACL.Rfc4314Rights;
+import org.apache.james.mailbox.model.MailboxACL.Right;
+import org.apache.james.mailbox.model.MailboxACL.SpecialName;
 
 
 /**
@@ -56,12 +54,12 @@ import org.apache.james.mailbox.model.SimpleMailboxACL.SimpleMailboxACLEntryKey;
  * 
  */
 public class UnionMailboxACLResolver implements MailboxACLResolver {
-    public static final MailboxACL DEFAULT_GLOBAL_GROUP_ACL = SimpleMailboxACL.OWNER_FULL_EXCEPT_ADMINISTRATION_ACL;
+    public static final MailboxACL DEFAULT_GLOBAL_GROUP_ACL = MailboxACL.OWNER_FULL_EXCEPT_ADMINISTRATION_ACL;
 
     /**
      * Nothing else than full rights for the owner.
      */
-    public static final MailboxACL DEFAULT_GLOBAL_USER_ACL = SimpleMailboxACL.OWNER_FULL_ACL;
+    public static final MailboxACL DEFAULT_GLOBAL_USER_ACL = MailboxACL.OWNER_FULL_ACL;
 
     private static final int POSITIVE_INDEX = 0;
     private static final int NEGATIVE_INDEX = 1;
@@ -72,7 +70,7 @@ public class UnionMailboxACLResolver implements MailboxACLResolver {
      * computing
      * {@link #rightsOf(String, org.apache.james.mailbox.MailboxACLResolver.GroupMembershipResolver, Mailbox)}
      * and
-     * {@link #hasRight(String, Mailbox, MailboxACLRight, org.apache.james.mailbox.MailboxACLResolver.GroupMembershipResolver)}
+     * {@link #hasRight(String, Mailbox, Right, org.apache.james.mailbox.MailboxACLResolver.GroupMembershipResolver)}
      * .
      */
     private final MailboxACL userGlobalACL;
@@ -112,25 +110,25 @@ public class UnionMailboxACLResolver implements MailboxACLResolver {
     }
 
     /**
-     * Tells whether the given {@code aclKey} {@link MailboxACLEntryKey} is
+     * Tells whether the given {@code aclKey} {@link EntryKey} is
      * applicable for the given {@code queryKey}.
      * 
      * There are two use cases for which this method was designed and tested:
      * 
      * (1) Calls from
-     * {@link #hasRight(String, GroupMembershipResolver, MailboxACLRight, MailboxACL, String, boolean)}
+     * {@link #hasRight(String, GroupMembershipResolver, Right, MailboxACL, String, boolean)}
      * and
      * {@link #resolveRights(String, GroupMembershipResolver, MailboxACL, String, boolean)}
      * in which the {@code queryKey} is a {@link NameType#user}.
      * 
      * (2) Calls from
-     * {@link #listRights(MailboxACLEntryKey, GroupMembershipResolver, String, boolean)}
+     * {@link #listRights(EntryKey, GroupMembershipResolver, String, boolean)}
      * where {@code queryKey} can be anything including {@link NameType#user},
      * {@link NameType#group} and all {@link NameType#special} identifiers.
      * 
      * Clearly the set of cases which this method has to handle in (1) is a
      * proper subset of the cases handled in (2). See the javadoc on
-     * {@link #listRights(MailboxACLEntryKey, GroupMembershipResolver, String, boolean)}
+     * {@link #listRights(EntryKey, GroupMembershipResolver, String, boolean)}
      * for more details.
      * 
      * @param aclKey
@@ -140,10 +138,10 @@ public class UnionMailboxACLResolver implements MailboxACLResolver {
      * @param resourceOwnerIsGroup
      * @return
      */
-    protected static boolean applies(MailboxACLEntryKey aclKey, MailboxACLEntryKey queryKey, GroupMembershipResolver groupMembershipResolver, String resourceOwner, boolean resourceOwnerIsGroup) {
+    protected static boolean applies(EntryKey aclKey, EntryKey queryKey, GroupMembershipResolver groupMembershipResolver, String resourceOwner, boolean resourceOwnerIsGroup) {
         final String aclKeyName = aclKey.getName();
         final NameType aclKeyNameType = aclKey.getNameType();
-        if (MailboxACL.SpecialName.anybody.name().equals(aclKeyName)) {
+        if (SpecialName.anybody.name().equals(aclKeyName)) {
             /* this works also for unauthenticated users */
             return true;
         } else if (queryKey != null) {
@@ -153,14 +151,14 @@ public class UnionMailboxACLResolver implements MailboxACLResolver {
                 /* Authenticated users */
                 switch (aclKeyNameType) {
                 case special:
-                    if (MailboxACL.SpecialName.authenticated.name().equals(aclKeyName)) {
+                    if (SpecialName.authenticated.name().equals(aclKeyName)) {
                         /* non null query user is viewed as authenticated */
                         return true;
-                    } else if (MailboxACL.SpecialName.owner.name().equals(aclKeyName)) {
+                    } else if (SpecialName.owner.name().equals(aclKeyName)) {
                         return (!resourceOwnerIsGroup && queryUserOrGroupName.equals(resourceOwner)) || (resourceOwnerIsGroup && groupMembershipResolver.isMember(queryUserOrGroupName, resourceOwner));
                     } else {
                         /* should not happen unless the parent if is changed */
-                        throw new IllegalStateException("Unexpected " + MailboxACL.SpecialName.class.getName() + "." + aclKeyName);
+                        throw new IllegalStateException("Unexpected " + SpecialName.class.getName() + "." + aclKeyName);
                     }
                 case user:
                     return aclKeyName.equals(queryUserOrGroupName);
@@ -173,16 +171,16 @@ public class UnionMailboxACLResolver implements MailboxACLResolver {
                 /* query is a group */
                 switch (aclKeyNameType) {
                 case special:
-                    if (MailboxACL.SpecialName.authenticated.name().equals(aclKeyName)) {
+                    if (SpecialName.authenticated.name().equals(aclKeyName)) {
                         /*
                          * see the javadoc comment on listRights()
                          */
                         return true;
-                    } else if (MailboxACL.SpecialName.owner.name().equals(aclKeyName)) {
+                    } else if (SpecialName.owner.name().equals(aclKeyName)) {
                         return resourceOwnerIsGroup && queryUserOrGroupName.equals(resourceOwner);
                     } else {
                         /* should not happen unless the parent if is changed */
-                        throw new IllegalStateException("Unexpected " + MailboxACL.SpecialName.class.getName() + "." + aclKeyName);
+                        throw new IllegalStateException("Unexpected " + SpecialName.class.getName() + "." + aclKeyName);
                     }
                 case user:
                     /* query groups cannot match ACL users */
@@ -202,7 +200,7 @@ public class UnionMailboxACLResolver implements MailboxACLResolver {
                          * owner
                          */
                         return true;
-                    } else if (MailboxACL.SpecialName.owner.name().equals(queryUserOrGroupName) && MailboxACL.SpecialName.authenticated.name().equals(aclKeyName)) {
+                    } else if (SpecialName.owner.name().equals(queryUserOrGroupName) && SpecialName.authenticated.name().equals(aclKeyName)) {
                         /*
                          * query owner matches authenticated because owner will
                          * be resolved only if the user is authenticated
@@ -240,17 +238,17 @@ public class UnionMailboxACLResolver implements MailboxACLResolver {
      * @see org.apache.james.mailbox.store.mail.MailboxACLResolver#hasRight(java.
      *      lang.String, org.apache.james.mailbox.store.mail.MailboxACLResolver.
      *      GroupMembershipResolver,
-     *      org.apache.james.mailbox.MailboxACL.MailboxACLRight,
+     *      org.apache.james.mailbox.MailboxACL.Right,
      *      org.apache.james.mailbox.MailboxACL, java.lang.String)
      */
     @Override
-    public boolean hasRight(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACLRight right, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException {
-        final MailboxACLEntryKey queryKey = requestUser == null ? null : new SimpleMailboxACLEntryKey(requestUser, NameType.user, false);
+    public boolean hasRight(String requestUser, GroupMembershipResolver groupMembershipResolver, Right right, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException {
+        final EntryKey queryKey = requestUser == null ? null : new EntryKey(requestUser, NameType.user, false);
         boolean result = false;
-        Map<MailboxACLEntryKey, MailboxACLRights> entries = resourceOwnerIsGroup ? groupGlobalACL.getEntries() : userGlobalACL.getEntries();
+        Map<EntryKey, Rfc4314Rights> entries = resourceOwnerIsGroup ? groupGlobalACL.getEntries() : userGlobalACL.getEntries();
         if (entries != null) {
-            for (Entry<MailboxACLEntryKey, MailboxACLRights> entry : entries.entrySet()) {
-                final MailboxACLEntryKey key = entry.getKey();
+            for (Entry<EntryKey, Rfc4314Rights> entry : entries.entrySet()) {
+                final EntryKey key = entry.getKey();
                 if (applies(key, queryKey, groupMembershipResolver, resourceOwner, resourceOwnerIsGroup) && entry.getValue().contains(right)) {
                     if (key.isNegative()) {
                         return false;
@@ -264,8 +262,8 @@ public class UnionMailboxACLResolver implements MailboxACLResolver {
         if (resourceACL != null) {
             entries = resourceACL.getEntries();
             if (entries != null) {
-                for (Entry<MailboxACLEntryKey, MailboxACLRights> entry : entries.entrySet()) {
-                    final MailboxACLEntryKey key = entry.getKey();
+                for (Entry<EntryKey, Rfc4314Rights> entry : entries.entrySet()) {
+                    final EntryKey key = entry.getKey();
                     if (applies(key, queryKey, groupMembershipResolver, resourceOwner, resourceOwnerIsGroup) && entry.getValue().contains(right)) {
                         if (key.isNegative()) {
                             return false;
@@ -281,13 +279,13 @@ public class UnionMailboxACLResolver implements MailboxACLResolver {
     }
 
     /**
-     * @see org.apache.james.mailbox.acl.MailboxACLResolver#isReadWrite(org.apache.james.mailbox.model.MailboxACL.MailboxACLRights,
+     * @see org.apache.james.mailbox.acl.MailboxACLResolver#isReadWrite(org.apache.james.mailbox.model.Rfc4314Rights,
      *      javax.mail.Flags)
      */
     @Override
-    public boolean isReadWrite(MailboxACLRights mailboxACLRights, Flags sharedFlags) throws UnsupportedRightException {
+    public boolean isReadWrite(Rfc4314Rights Rfc4314Rights, Flags sharedFlags) throws UnsupportedRightException {
         /* the two fast cases first */
-        if (mailboxACLRights.contains(SimpleMailboxACL.Right.Insert) || mailboxACLRights.contains(SimpleMailboxACL.Right.PerformExpunge)) {
+        if (Rfc4314Rights.contains(MailboxACL.Right.Insert) || Rfc4314Rights.contains(MailboxACL.Right.PerformExpunge)) {
             return true;
         }
         /*
@@ -303,12 +301,12 @@ public class UnionMailboxACLResolver implements MailboxACLResolver {
          * flags.
          */
         else if (sharedFlags != null) {
-            if (sharedFlags.contains(Flag.DELETED) && mailboxACLRights.contains(SimpleMailboxACL.Right.DeleteMessages)) {
+            if (sharedFlags.contains(Flag.DELETED) && Rfc4314Rights.contains(MailboxACL.Right.DeleteMessages)) {
                 return true;
-            } else if (sharedFlags.contains(Flag.SEEN) && mailboxACLRights.contains(SimpleMailboxACL.Right.WriteSeenFlag)) {
+            } else if (sharedFlags.contains(Flag.SEEN) && Rfc4314Rights.contains(MailboxACL.Right.WriteSeenFlag)) {
                 return true;
             } else {
-                boolean hasWriteRight = mailboxACLRights.contains(SimpleMailboxACL.Right.Write);
+                boolean hasWriteRight = Rfc4314Rights.contains(MailboxACL.Right.Write);
                 return hasWriteRight && (sharedFlags.contains(Flag.ANSWERED) || sharedFlags.contains(Flag.DRAFT) || sharedFlags.contains(Flag.FLAGGED) || sharedFlags.contains(Flag.RECENT) || sharedFlags.contains(Flag.USER));
             }
         }
@@ -363,8 +361,8 @@ public class UnionMailboxACLResolver implements MailboxACLResolver {
      * @see org.apache.james.mailbox.acl.MailboxACLResolver#listRightsDefault(boolean)
      */
     @Override
-    public MailboxACLRights[] listRights(MailboxACLEntryKey queryKey, GroupMembershipResolver groupMembershipResolver, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException {
-        MailboxACL.MailboxACLRights[] positiveNegativePair = { SimpleMailboxACL.NO_RIGHTS, SimpleMailboxACL.NO_RIGHTS };
+    public Rfc4314Rights[] listRights(EntryKey queryKey, GroupMembershipResolver groupMembershipResolver, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException {
+        Rfc4314Rights[] positiveNegativePair = { MailboxACL.NO_RIGHTS, MailboxACL.NO_RIGHTS };
 
         MailboxACL userACL = resourceOwnerIsGroup ? groupGlobalACL : userGlobalACL;
         resolveRights(queryKey, groupMembershipResolver, userACL.getEntries(), resourceOwner, resourceOwnerIsGroup, positiveNegativePair);
@@ -376,15 +374,15 @@ public class UnionMailboxACLResolver implements MailboxACLResolver {
         }
     }
 
-    private static MailboxACLRights[] toListRightsArray(MailboxACLRights implicitRights) throws UnsupportedRightException {
-        List<MailboxACLRights> result = new ArrayList<>();
+    private static Rfc4314Rights[] toListRightsArray(Rfc4314Rights implicitRights) throws UnsupportedRightException {
+        List<Rfc4314Rights> result = new ArrayList<>();
         result.add(implicitRights);
-        for (MailboxACLRight right : SimpleMailboxACL.FULL_RIGHTS) {
+        for (Right right : MailboxACL.FULL_RIGHTS.list()) {
             if (!implicitRights.contains(right)) {
                 result.add(new Rfc4314Rights(right));
             }
         }
-        return result.toArray(new MailboxACLRights[result.size()]);
+        return result.toArray(new Rfc4314Rights[result.size()]);
     }
 
     /**
@@ -394,9 +392,9 @@ public class UnionMailboxACLResolver implements MailboxACLResolver {
      *      java.lang.String)
      */
     @Override
-    public MailboxACL.MailboxACLRights resolveRights(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException {
-        MailboxACL.MailboxACLRights[] positiveNegativePair = { SimpleMailboxACL.NO_RIGHTS, SimpleMailboxACL.NO_RIGHTS };
-        final MailboxACLEntryKey queryKey = requestUser == null ? null : new SimpleMailboxACLEntryKey(requestUser, NameType.user, false);
+    public Rfc4314Rights resolveRights(String requestUser, GroupMembershipResolver groupMembershipResolver, MailboxACL resourceACL, String resourceOwner, boolean resourceOwnerIsGroup) throws UnsupportedRightException {
+        Rfc4314Rights[] positiveNegativePair = { MailboxACL.NO_RIGHTS, MailboxACL.NO_RIGHTS };
+        final EntryKey queryKey = requestUser == null ? null : new EntryKey(requestUser, NameType.user, false);
         MailboxACL userACL = resourceOwnerIsGroup ? groupGlobalACL : userGlobalACL;
         resolveRights(queryKey, groupMembershipResolver, userACL.getEntries(), resourceOwner, resourceOwnerIsGroup, positiveNegativePair);
 
@@ -418,11 +416,11 @@ public class UnionMailboxACLResolver implements MailboxACLResolver {
      * @param positiveNegativePair
      * @throws UnsupportedRightException
      */
-    private void resolveRights(MailboxACLEntryKey queryKey, GroupMembershipResolver groupMembershipResolver, Map<MailboxACLEntryKey, MailboxACLRights> entries, String resourceOwner, boolean resourceOwnerIsGroup, MailboxACL.MailboxACLRights[] positiveNegativePair)
+    private void resolveRights(EntryKey queryKey, GroupMembershipResolver groupMembershipResolver, Map<EntryKey, Rfc4314Rights> entries, String resourceOwner, boolean resourceOwnerIsGroup, Rfc4314Rights[] positiveNegativePair)
             throws UnsupportedRightException {
         if (entries != null) {
-            for (Entry<MailboxACLEntryKey, MailboxACLRights> entry : entries.entrySet()) {
-                final MailboxACLEntryKey key = entry.getKey();
+            for (Entry<EntryKey, Rfc4314Rights> entry : entries.entrySet()) {
+                final EntryKey key = entry.getKey();
                 if (applies(key, queryKey, groupMembershipResolver, resourceOwner, resourceOwnerIsGroup)) {
                     if (key.isNegative()) {
                         positiveNegativePair[NEGATIVE_INDEX] = positiveNegativePair[NEGATIVE_INDEX].union(entry.getValue());

http://git-wip-us.apache.org/repos/asf/james-project/blob/4e10f10b/mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedRightException.java
----------------------------------------------------------------------
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedRightException.java b/mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedRightException.java
index 8ca5dcd..26b711c 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedRightException.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/exception/UnsupportedRightException.java
@@ -20,7 +20,7 @@
 
 package org.apache.james.mailbox.exception;
 
-import org.apache.james.mailbox.model.MailboxACL.MailboxACLRight;
+import org.apache.james.mailbox.model.MailboxACL.Right;
 
 /**
  * Thrown when the current system does not support the given right.
@@ -41,7 +41,7 @@ public class UnsupportedRightException extends MailboxSecurityException {
         this.unsupportedRight  = right;
     }
     
-    public UnsupportedRightException(MailboxACLRight unsupportedRight) {
+    public UnsupportedRightException(Right unsupportedRight) {
         this(unsupportedRight.asCharacter());
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/4e10f10b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxACL.java
----------------------------------------------------------------------
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxACL.java b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxACL.java
index b6e97d2..09dcbbb 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxACL.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MailboxACL.java
@@ -19,235 +19,686 @@
 
 package org.apache.james.mailbox.model;
 
+import java.util.AbstractMap;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.james.mailbox.exception.UnsupportedRightException;
 
+import com.github.fge.lambdas.Throwing;
+import com.github.steveash.guavate.Guavate;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
 /**
  * Stores an Access Control List (ACL) applicable to a mailbox. Inspired by
  * RFC4314 IMAP4 Access Control List (ACL) Extension.
- * 
+ *
  * Implementations must be immutable. Implementations should override
  * {@link #hashCode()} and {@link #equals(Object)}.
- * 
+ *
  */
-public interface MailboxACL {
+public class MailboxACL {
 
     /**
      * SETACL command mode.
      */
-    enum EditMode {
+    public enum EditMode {
         ADD, REMOVE, REPLACE
     }
 
+    public enum NameType {
+        group, special, user
+    }
+
     /**
-     * The key used in {@link MailboxACL#getEntries()}. Implementations should
-     * override {@link #hashCode()} and {@link #equals(Object)} in such a way
-     * that all of {@link #getName()}, {@link #getNameType()} and
-     * {@link #isNegative()} are significant.
-     * 
+     * Special name literals.
      */
-    interface MailboxACLEntryKey {
-        /**
-         * Returns the name of a user or of a group to which this
-         * {@link MailboxACLEntryKey} applies.
-         * 
-         * @return User name, group name or special name.
-         */
-        String getName();
+    public enum SpecialName {
+        anybody, authenticated, owner
+    }
 
-        /**
-         * Tells of what type is the name returned by {@link #getName()}.
-         * 
-         * @return type of the name returned by {@link #getName()}
-         */
-        NameType getNameType();
+    /**
+     * SETACL third argument prefix
+     */
+    public static final char ADD_RIGHTS_MARKER = '+';
 
-        /**
-         * If true the {@link MailboxACLRights} returned by
-         * {@link MailboxACLEntry#getRights()} should be interpreted as
-         * "negative rights" as described in RFC4314: If the identifier "-fred"
-         * is granted the "w" right, that indicates that the "w" right is to be
-         * removed from users matching the identifier "fred", even though the
-         * user "fred" might have the "w" right as a consequence of some other
-         * identifier in the ACL.
-         * 
-         * Note that {@link MailboxACLEntry#getName()} does not start with "-"
-         * when {@link MailboxACLEntry#getRights()} returns true.
-         * 
-         * @return
-         */
-        boolean isNegative();
+    /**
+     * Marks groups when (de)serializing {@link MailboxACLEntryKey}s.
+     *
+     * @see MailboxACLEntryKey#serialize()
+     */
+    public static final char DEFAULT_GROUP_MARKER = '$';
 
-        /**
-         * Returns a serialized form of this {@link MailboxACLEntryKey} as a
-         * {@link String}. Implementations should choose a consistent way how
-         * all of {@link #getName()}, {@link #getNameType()} and
-         * {@link #isNegative()} get serialized.
-         * 
-         * RFC4314 sction 2. states: All user name strings accepted by the LOGIN
-         * or AUTHENTICATE commands to authenticate to the IMAP server are
-         * reserved as identifiers for the corresponding users. Identifiers
-         * starting with a dash ("-") are reserved for "negative rights",
-         * described below. All other identifier strings are interpreted in an
-         * implementation-defined manner.
-         * 
-         * Dovecot and Cyrus mark groups with '$' prefix. See <a
-         * href="http://wiki2.dovecot.org/SharedMailboxes/Shared"
-         * >http://wiki2.dovecot.org/SharedMailboxes/Shared</a>:
-         * 
-         * <cite>The $group syntax is not a standard, but it is mentioned in RFC
-         * 4314 examples and is also understood by at least Cyrus IMAP. Having
-         * '-' before the identifier specifies negative rights.</cite>
-         * 
-         * @see MailboxACL#DEFAULT_GROUP_MARKER
-         * @see MailboxACL#DEFAULT_NEGATIVE_MARKER
-         * 
-         * @return serialized form as a {@link String}
-         */
-        String serialize();
-    }
+    /**
+     * Marks negative when (de)serializing {@link MailboxACLEntryKey}s.
+     *
+     * @see MailboxACLEntryKey#serialize()
+     */
+    public static final char DEFAULT_NEGATIVE_MARKER = '-';
+
+    /**
+     * SETACL third argument prefix
+     */
+    public static final char REMOVE_RIGHTS_MARKER = '-';
 
     /**
      * Single right applicable to a mailbox.
      */
-    interface MailboxACLRight {
+    public enum Right {
+        Administer('a'), // (perform SETACL/DELETEACL/GETACL/LISTRIGHTS)
+        PerformExpunge('e'), //perform EXPUNGE and expunge as a part of CLOSE
+        Insert('i'), //insert (perform APPEND, COPY into mailbox)
+        /*
+        * create mailboxes (CREATE new sub-mailboxes in any
+        * implementation-defined hierarchy, parent mailbox for the new mailbox
+        * name in RENAME)
+        * */
+        CreateMailbox('k'),
+        Lookup('l'), //lookup (mailbox is visible to LIST/LSUB commands, SUBSCRIBE mailbox)
+        Post('p'), //post (send mail to submission address for mailbox, not enforced by IMAP4 itself)
+        Read('r'), //read (SELECT the mailbox, perform STATUS)
+        /**
+         * keep seen/unseen information across sessions (set or clear \SEEN
+         * flag via STORE, also set \SEEN during APPEND/COPY/ FETCH BODY[...])
+         */
+        WriteSeenFlag('s'),
+        DeleteMessages('t'), //delete messages (set or clear \DELETED flag via STORE, set \DELETED flag during APPEND/COPY)
+        /**
+         * write (set or clear flags other than \SEEN and \DELETED via
+         * STORE, also set them during APPEND/COPY)
+         */
+        Write('w'),
+        DeleteMailbox('x'); //delete mailbox (DELETE mailbox, old mailbox name in RENAME)
+
+        private final char rightCharacter;
+
+        Right(char rightCharacter) {
+            this.rightCharacter = rightCharacter;
+        }
+
         /**
          * Returns the char representation of this right.
-         * 
+         *
          * @return char representation of this right
          */
-        char asCharacter();
+        public char asCharacter() {
+            return rightCharacter;
+        }
+
+        public static final EnumSet<Right> allRights = EnumSet.allOf(Right.class);
+
+        public static Right forChar(char c) throws UnsupportedRightException {
+            return Right.allRights
+                .stream()
+                .filter(r -> r.asCharacter() == c)
+                .findFirst()
+                .orElseThrow(() -> new UnsupportedRightException(c));
+        }
     }
 
     /**
-     * Iterable set of {@link MailboxACLRight}s.
-     * 
+     * Holds the collection of {@link MailboxACL.Right}s.
+     *
      * Implementations may decide to support only a specific range of rights,
      * e.g. the Standard Rights of RFC 4314 section 2.1.
-     * 
+     *
      * Implementations must not allow adding or removing of elements once this
      * MailboxACLRights are initialized.
      */
-    interface MailboxACLRights extends Iterable<MailboxACLRight> {
+    public static class Rfc4314Rights {
+        /**
+         * See RFC 4314 section 2.1.1. Obsolete Rights.
+         */
+        public enum CompatibilityMode {
+            ck_detx, ckx_det, NO_COMPATIBILITY
+        }
+
+        private static final char c_ObsoleteCreate = 'c';
+        private static final char d_ObsoleteDelete = 'd';
+
+        /**
+         * See RFC 4314 section 2.1.1. Obsolete Rights.
+         */
+        private final CompatibilityMode compatibilityMode = CompatibilityMode.ckx_det;
+
+        private final EnumSet<Right> value;
+
+        private Rfc4314Rights(EnumSet<Right> rights) {
+            this.value = EnumSet.copyOf(rights);
+        }
+
+        private Rfc4314Rights() {
+            this(EnumSet.noneOf(Right.class));
+        }
+
+        public Rfc4314Rights(Right... rights) {
+            this(EnumSet.copyOf(Arrays.asList(rights)));
+        }
+
+        public Rfc4314Rights(Right right) throws UnsupportedRightException {
+            this.value = EnumSet.of(Right.forChar(right.asCharacter()));
+        }
+
+        /* Used for json serialization (probably a bad idea) */
+        public Rfc4314Rights(int serializedRights) {
+            List<Right> rights = Right.allRights.stream()
+                .filter(right -> ((serializedRights >> right.ordinal()) & 1) != 0)
+                .collect(Collectors.toList());
+            if (rights.isEmpty()) {
+                this.value = EnumSet.noneOf(Right.class);
+            } else {
+                this.value = EnumSet.copyOf(rights);
+            }
+        }
+
+        public Rfc4314Rights(String serializedRfc4314Rights) throws UnsupportedRightException {
+            List<Right> rights = serializedRfc4314Rights.chars()
+                .mapToObj(i -> (char) i)
+                .flatMap(Throwing.function(this::convert).sneakyThrow())
+                .collect(Collectors.toList());
+            if (rights.isEmpty()) {
+                this.value = EnumSet.noneOf(Right.class);
+            } else {
+                this.value = EnumSet.copyOf(rights);
+            }
+        }
+
+        private Stream<Right> convert(char flag) throws UnsupportedRightException {
+            switch (flag) {
+            case c_ObsoleteCreate:
+                return convertObsoleteCreate(flag);
+            case d_ObsoleteDelete:
+                return convertObsoleteDelete(flag);
+            default:
+                return Stream.of(Right.forChar(flag));
+            }
+        }
+
+        private Stream<Right> convertObsoleteDelete(char flag) throws UnsupportedRightException {
+            switch (compatibilityMode) {
+            case ck_detx:
+                return Stream.of(Right.PerformExpunge, Right.DeleteMessages, Right.DeleteMailbox);
+            case ckx_det:
+                return Stream.of(Right.PerformExpunge, Right.DeleteMessages);
+            case NO_COMPATIBILITY:
+                throw new UnsupportedRightException(flag);
+            default:
+                throw new IllegalStateException("Unexpected enum member: " + CompatibilityMode.class.getName() + "." + compatibilityMode.name());
+            }
+        }
+
+        private Stream<Right> convertObsoleteCreate(char flag) throws UnsupportedRightException {
+            switch (compatibilityMode) {
+            case ck_detx:
+                return Stream.of(Right.CreateMailbox);
+            case ckx_det:
+                return Stream.of(Right.CreateMailbox, Right.DeleteMailbox);
+            case NO_COMPATIBILITY:
+                throw new UnsupportedRightException(flag);
+            default:
+                throw new IllegalStateException("Unexpected enum member: " + CompatibilityMode.class.getName() + "." + compatibilityMode.name());
+            }
+        }
 
         /**
          * Tells whether this contains the given right.
-         * 
-         * @param right
-         * @return
-         * @throws UnsupportedRightException
-         *             iff the given right is not supported.
+         *
+         * @throws UnsupportedRightException if the given right is not supported.
          */
-        boolean contains(MailboxACLRight right) throws UnsupportedRightException;
+        public boolean contains(char flag) throws UnsupportedRightException {
+            return contains(Right.forChar(flag));
+        }
+
+        public boolean contains(Right right) throws UnsupportedRightException {
+            return value.contains(Right.forChar(right.asCharacter()));
+        }
+
+        /* Used for json serialization (probably a bad idea) */
+        public int serializeAsInteger() {
+            return value.stream().mapToInt(x -> 1 << x.ordinal()).sum();
+        }
+
+        public boolean equals(Object o) {
+            if (o instanceof Rfc4314Rights) {
+                Rfc4314Rights that = (Rfc4314Rights) o;
+                return this.value.equals(that.value);
+            }
+            return false;
+        }
 
         /**
          * Performs the set theoretic operation of relative complement of
          * toRemove MailboxACLRights in this MailboxACLRights.
-         * 
+         *
          * A schematic example: "lrw".except("w") returns "lr".
-         * 
+         *
          * Implementations must return a new unmodifiable instance of
-         * {@link MailboxACLRights}. However, implementations may decide to
+         * {@link MailboxACL.MailboxACLRights}. However, implementations may decide to
          * return this or toRemove parameter value in case the result would be
          * equal to the respective one of those.
-         * 
-         * @param toRemove
-         * @return
+         *
          * @throws UnsupportedRightException
          */
-        MailboxACLRights except(MailboxACLRights toRemove) throws UnsupportedRightException;
+        public Rfc4314Rights except(Rfc4314Rights toRemove) throws UnsupportedRightException {
+            EnumSet<Right> copy = EnumSet.copyOf(value);
+            copy.removeAll(convertRightsToList(toRemove));
+            return new Rfc4314Rights(copy);
+        }
 
         /**
          * Tells if this set of rights is empty.
-         * 
+         *
          * @return true if there are no rights in this set; false otherwise.
          */
-        boolean isEmpty();
+        public boolean isEmpty() {
+            return value.isEmpty();
+        }
 
         /**
          * Tells whether the implementation supports the given right.
-         * 
+         *
          * @param right
          * @return true if this supports the given right.
          */
-        boolean isSupported(MailboxACLRight right);
+        public boolean isSupported(Right right) {
+            try {
+                contains(right.asCharacter());
+                return true;
+            } catch (UnsupportedRightException e) {
+                return false;
+            }
+        }
+
+        public Iterator<Right> iterator() {
+            ImmutableList<Right> rights = ImmutableList.copyOf(value);
+            return rights.iterator();
+        }
+
+        public List<Right> list() {
+            return ImmutableList.copyOf(value);
+        }
 
         /**
-         * Returns a serialized form of this {@link MailboxACLRights} as
+         * Returns a serialized form of this {@link MailboxACL.Right} as
          * {@link String}.
-         * 
+         *
          * @return a {@link String}
          */
-        String serialize();
+        public String serialize() {
+            return value.stream()
+                .map(Right::asCharacter)
+                .map(String::valueOf)
+                .collect(Collectors.joining());
+        }
+
+        public String toString() {
+            return serialize();
+        }
 
         /**
-         * Performs the set theoretic operation of union of this
-         * MailboxACLRights and toAdd MailboxACLRights.
-         * 
+         * Performs the theoretic operation of union of this
+         * Rfc4314Rights and toAdd Rfc4314Rights.
+         *
          * A schematic example: "lr".union("rw") returns "lrw".
-         * 
+         *
          * Implementations must return a new unmodifiable instance of
-         * {@link MailboxACLRights}. However, implementations may decide to
-         * return this or toAdd parameter value in case the result would be
-         * equal to the respective one of those.
-         * 
+         * {@link MailboxACL.Rfc4314Rights}.
+         *
          * @param toAdd
          * @return union of this and toAdd
          * @throws UnsupportedRightException
-         * 
+         *
          */
-        MailboxACLRights union(MailboxACLRights toAdd) throws UnsupportedRightException;
+        public Rfc4314Rights union(Rfc4314Rights toAdd) throws UnsupportedRightException {
+            Preconditions.checkNotNull(toAdd);
+            EnumSet<Right> rightUnion = EnumSet.noneOf(Right.class);
+            rightUnion.addAll(value);
+            rightUnion.addAll(convertRightsToList(toAdd));
+            return new Rfc4314Rights(rightUnion);
+        }
+
+        private List<Right> convertRightsToList(Rfc4314Rights toAdd) {
+            return Optional.ofNullable(toAdd).orElse(Rfc4314Rights.empty())
+                .list()
+                .stream()
+                .map(Throwing.function(right -> Right.forChar(right.asCharacter())))
+                .collect(Guavate.toImmutableList());
+        }
+
+        private static Rfc4314Rights empty() {
+            return new Rfc4314Rights();
+        }
+
+    }
 
+    /**
+     * A utility implementation of
+     * {@code Map.Entry<EntryKey, Rfc4314Rights>}.
+     */
+    public static class Entry extends AbstractMap.SimpleEntry<EntryKey, Rfc4314Rights> {
+        public Entry(EntryKey key, Rfc4314Rights value) {
+            super(key, value);
+        }
+
+        public Entry(String key, String value) throws UnsupportedRightException {
+            this(EntryKey.deserialize(key), new Rfc4314Rights(value));
+        }
     }
 
     /**
-     * Allows distinguishing between users, groups and special names (see
-     * {@link SpecialName}).
+     * The key used in {@link MailboxACL#getEntries()}. Implementations should
+     * override {@link #hashCode()} and {@link #equals(Object)} in such a way
+     * that all of {@link #getName()}, {@link #getNameType()} and
+     * {@link #isNegative()} are significant.
+     *
      */
+    public static class EntryKey {
+        public static EntryKey createGroup(String name) {
+            return new EntryKey(name, NameType.group, false);
+        }
+
+        public static EntryKey createGroup(String name, boolean negative) {
+            return new EntryKey(name, NameType.group, negative);
+        }
+
+        public static EntryKey createUser(String name) {
+            return new EntryKey(name, NameType.user, false);
+        }
 
-    interface MailboxACLCommand {
-        MailboxACLEntryKey getEntryKey();
+        public static EntryKey createUser(String name, boolean negative) {
+            return new EntryKey(name, NameType.user, negative);
+        }
 
-        EditMode getEditMode();
+        private final String name;
+        private final NameType nameType;
+        private final boolean negative;
+
+        /**
+         * Creates a new instance of SimpleMailboxACLEntryKey from the given
+         * serialized {@link String}. It supposes that negative rights are
+         * marked with {@link MailboxACL#DEFAULT_NEGATIVE_MARKER} and that
+         * groups are marked with {@link MailboxACL#DEFAULT_GROUP_MARKER}.
+         * 
+         * @param serialized
+         */
+        public static EntryKey deserialize(String serialized) {
+            Preconditions.checkNotNull(serialized, "Cannot parse null");
+            Preconditions.checkArgument(!serialized.isEmpty(), "Cannot parse an empty string");
+
+            boolean negative = serialized.charAt(0) == DEFAULT_NEGATIVE_MARKER;
+            int nameStart = negative ? 1 : 0;
+            boolean isGroup = serialized.charAt(nameStart) == DEFAULT_GROUP_MARKER;
+            Optional<NameType> explicitNameType = isGroup ? Optional.of(NameType.group) : Optional.empty();
+            String name = isGroup ? serialized.substring(nameStart + 1) : serialized.substring(nameStart);
+
+            if (name.isEmpty()) {
+                throw new IllegalStateException("Cannot parse a string with empty name");
+            }
+            NameType nameType = explicitNameType.orElseGet(() -> computeImplicitNameType(name));
+
+            return new EntryKey(name, nameType, negative);
+        }
+
+        private static NameType computeImplicitNameType(String name) {
+            boolean isSpecialName = Arrays.stream(SpecialName.values())
+                .anyMatch(specialName -> specialName.name().equals(name));
+            if (isSpecialName) {
+                return NameType.special;
+            }
+            return NameType.user;
+        }
+
+        public EntryKey(String name, NameType nameType, boolean negative) {
+            Preconditions.checkNotNull(name, "Provide a name for this " + getClass().getName());
+            Preconditions.checkNotNull(nameType, "Provide a nameType for this " + getClass().getName());
+
+            this.name = name;
+            this.nameType = nameType;
+            this.negative = negative;
+        }
+
+        public boolean equals(Object o) {
+            if (o instanceof EntryKey) {
+                EntryKey other = (EntryKey) o;
+                return Objects.equals(this.name, other.getName())
+                    && Objects.equals(this.nameType, other.getNameType())
+                    && Objects.equals(this.negative, other.isNegative());
+            }
+            return false;
+        }
 
-        MailboxACLRights getRights();
+        /**
+         * Returns the name of a user or of a group to which this
+         * {@link MailboxACL.MailboxACLEntryKey} applies.
+         *
+         * @return User name, group name or special name.
+         */
+        public String getName() {
+            return name;
+        }
+
+        /**
+         * Tells of what type is the name returned by {@link #getName()}.
+         *
+         * @return type of the name returned by {@link #getName()}
+         */
+        public NameType getNameType() {
+            return nameType;
+        }
+
+        public final int hashCode() {
+            return Objects.hash(negative, nameType, name);
+        }
+
+        /**
+         * If true the {@link MailboxACL.MailboxACLRights} returned by
+         * {@link MailboxACLEntry#getRights()} should be interpreted as
+         * "negative rights" as described in RFC4314: If the identifier "-fred"
+         * is granted the "w" right, that indicates that the "w" right is to be
+         * removed from users matching the identifier "fred", even though the
+         * user "fred" might have the "w" right as a consequence of some other
+         * identifier in the ACL.
+         *
+         * Note that {@link MailboxACLEntry#getName()} does not start with "-"
+         * when {@link MailboxACLEntry#getRights()} returns true.
+         *
+         * @return
+         */
+        public boolean isNegative() {
+            return negative;
+        }
+
+        /**
+         * Returns a serialized form of this {@link MailboxACL.MailboxACLEntryKey} as a
+         * {@link String}. Implementations should choose a consistent way how
+         * all of {@link #getName()}, {@link #getNameType()} and
+         * {@link #isNegative()} get serialized.
+         *
+         * RFC4314 sction 2. states: All user name strings accepted by the LOGIN
+         * or AUTHENTICATE commands to authenticate to the IMAP server are
+         * reserved as identifiers for the corresponding users. Identifiers
+         * starting with a dash ("-") are reserved for "negative rights",
+         * described below. All other identifier strings are interpreted in an
+         * implementation-defined manner.
+         *
+         * Dovecot and Cyrus mark groups with '$' prefix. See <a
+         * href="http://wiki2.dovecot.org/SharedMailboxes/Shared"
+         * >http://wiki2.dovecot.org/SharedMailboxes/Shared</a>:
+         *
+         * <cite>The $group syntax is not a standard, but it is mentioned in RFC
+         * 4314 examples and is also understood by at least Cyrus IMAP. Having
+         * '-' before the identifier specifies negative rights.</cite>
+         *
+         * @see MailboxACL#DEFAULT_GROUP_MARKER
+         * @see MailboxACL#DEFAULT_NEGATIVE_MARKER
+         *
+         * @return serialized form as a {@link String}
+         */
+        public String serialize() {
+            String negativePart = negative ? String.valueOf(DEFAULT_NEGATIVE_MARKER) : "";
+            String nameTypePart = nameType == NameType.group ? String.valueOf(DEFAULT_GROUP_MARKER) : "";
+
+            return negativePart + nameTypePart + name;
+        }
+
+        public String toString() {
+            return serialize();
+        }
     }
 
-    enum NameType {
-        group, special, user
+
+    public static class ACLCommand {
+        private final EntryKey key;
+        private final EditMode editMode;
+        private final Rfc4314Rights rights;
+
+        public ACLCommand(EntryKey key, EditMode editMode, Rfc4314Rights rights) {
+            this.key = key;
+            this.editMode = editMode;
+            this.rights = rights;
+        }
+
+        public EntryKey getEntryKey() {
+            return key;
+        }
+
+        public EditMode getEditMode() {
+            return editMode;
+        }
+
+        public Rfc4314Rights getRights() {
+            return rights;
+        }
+
+        public final boolean equals(Object o) {
+            if (o instanceof ACLCommand) {
+                ACLCommand that = (ACLCommand) o;
+
+                return Objects.equals(this.key, that.key)
+                    && Objects.equals(this.editMode, that.editMode)
+                    && Objects.equals(this.rights, that.rights);
+            }
+            return false;
+        }
+
+        public final int hashCode() {
+            return Objects.hash(key, editMode, rights);
+        }
     }
 
-    /**
-     * Special name literals.
-     */
-    enum SpecialName {
-        anybody, authenticated, owner
+    public static final EntryKey ANYBODY_KEY;
+    public static final EntryKey ANYBODY_NEGATIVE_KEY;
+    public static final EntryKey AUTHENTICATED_KEY;
+    public static final EntryKey AUTHENTICATED_NEGATIVE_KEY;
+    public static final MailboxACL EMPTY;
+
+    public static final Rfc4314Rights FULL_RIGHTS;
+
+    public static final Rfc4314Rights NO_RIGHTS;
+    public static final MailboxACL OWNER_FULL_ACL;
+    public static final MailboxACL OWNER_FULL_EXCEPT_ADMINISTRATION_ACL;
+
+    public static final EntryKey OWNER_KEY;
+    public static final EntryKey OWNER_NEGATIVE_KEY;
+
+    static {
+        try {
+            ANYBODY_KEY = new EntryKey(SpecialName.anybody.name(), NameType.special, false);
+            ANYBODY_NEGATIVE_KEY = new EntryKey(SpecialName.anybody.name(), NameType.special, true);
+            AUTHENTICATED_KEY = new EntryKey(SpecialName.authenticated.name(), NameType.special, false);
+            AUTHENTICATED_NEGATIVE_KEY = new EntryKey(SpecialName.authenticated.name(), NameType.special, true);
+            EMPTY = new MailboxACL();
+            FULL_RIGHTS =  new Rfc4314Rights(Right.allRights);
+            NO_RIGHTS = new Rfc4314Rights();
+            OWNER_KEY = new EntryKey(SpecialName.owner.name(), NameType.special, false);
+            OWNER_NEGATIVE_KEY = new EntryKey(SpecialName.owner.name(), NameType.special, true);
+            OWNER_FULL_ACL = new MailboxACL(new Entry[] { new Entry(MailboxACL.OWNER_KEY, MailboxACL.FULL_RIGHTS) });
+            OWNER_FULL_EXCEPT_ADMINISTRATION_ACL = new MailboxACL(new Entry[] { new Entry(MailboxACL.OWNER_KEY, MailboxACL.FULL_RIGHTS.except(new Rfc4314Rights(Right.Administer))) });
+        } catch (UnsupportedRightException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static Map<EntryKey, Rfc4314Rights> toMap(Properties props) throws UnsupportedRightException {
+        ImmutableMap.Builder<EntryKey, Rfc4314Rights> builder = ImmutableMap.builder();
+        for (Map.Entry prop : props.entrySet()) {
+            builder.put(EntryKey.deserialize((String) prop.getKey()), new Rfc4314Rights((String) prop.getValue()));
+        }
+        return builder.build();
     }
+    
+    private final Map<EntryKey, Rfc4314Rights> entries;
 
     /**
-     * SETACL third argument prefix
+     * Creates a new instance of SimpleMailboxACL containing no entries.
+     * 
      */
-    char ADD_RIGHTS_MARKER = '+';
+    public MailboxACL() {
+        this(ImmutableMap.of());
+    }
 
     /**
-     * Marks groups when (de)serializing {@link MailboxACLEntryKey}s.
+     * Creates a new instance of SimpleMailboxACL from the given array of
+     * entries.
      * 
-     * @see MailboxACLEntryKey#serialize()
+     * @param entries
      */
-    char DEFAULT_GROUP_MARKER = '$';
+    public MailboxACL(Map.Entry<EntryKey, Rfc4314Rights>... entries) {
+        this(ImmutableMap.copyOf(
+            Optional.ofNullable(entries)
+                .map(array -> Arrays.stream(array)
+                    .collect(Guavate.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)))
+            .orElse(ImmutableMap.of())));
+    }
 
     /**
-     * Marks negative when (de)serializing {@link MailboxACLEntryKey}s.
-     * 
-     * @see MailboxACLEntryKey#serialize()
+     * Creates a new instance of SimpleMailboxACL from the given {@link Map} of
+     * entries.
+     *
+     * @param entries
      */
-    char DEFAULT_NEGATIVE_MARKER = '-';
+    public MailboxACL(Map<EntryKey, Rfc4314Rights> entries) {
+        Preconditions.checkNotNull(entries);
+
+        this.entries = ImmutableMap.copyOf(entries);
+    }
 
     /**
-     * SETACL third argument prefix
+     * Creates a new instance of SimpleMailboxACL from {@link Properties}. The
+     * keys and values from the <code>props</code> parameter are parsed by the
+     * {@link String} constructors of {@link EntryKey} and
+     * {@link Rfc4314Rights} respectively.
+     * 
+     * @param props
+     * @throws UnsupportedRightException
      */
-    char REMOVE_RIGHTS_MARKER = '-';
+    public MailboxACL(Properties props) throws UnsupportedRightException {
+        this(toMap(props));
+    }
+
+    public boolean equals(Object o) {
+        if (o instanceof MailboxACL) {
+            MailboxACL acl = (MailboxACL) o;
+            return Objects.equals(this.getEntries(), acl.getEntries());
+        }
+        return false;
+    }
+
+    public int hashCode() {
+        return Objects.hash(entries);
+    }
 
     /**
      * Apply the given ACL update on current ACL and return the result as a new ACL.
@@ -256,96 +707,144 @@ public interface MailboxACL {
      * @return Copy of current ACL updated
      * @throws UnsupportedRightException
      */
-    MailboxACL apply(MailboxACLCommand aclUpdate) throws UnsupportedRightException;
+    public MailboxACL apply(ACLCommand aclUpdate) throws UnsupportedRightException {
+        switch (aclUpdate.getEditMode()) {
+            case ADD:
+                return union(aclUpdate.getEntryKey(), aclUpdate.getRights());
+            case REMOVE:
+                return except(aclUpdate.getEntryKey(), aclUpdate.getRights());
+            case REPLACE:
+                return replace(aclUpdate.getEntryKey(), aclUpdate.getRights());
+        }
+        throw new RuntimeException("Unknown edit mode");
+    }
 
     /**
      * Performs the set theoretic operation of relative complement of toRemove
      * {@link MailboxACL} in this {@link MailboxACL}.
-     * 
+     *
      * A schematic example: "user1:lr;user2:lrwt".except("user1:w;user2:t")
      * returns "user1:lr;user2:lrw".
-     * 
+     *
      * Implementations must return a new unmodifiable instance of
      * {@link MailboxACL}. However, implementations may decide to return this or
      * toRemove parameter value in case the result would be equal to the
      * respective one of those.
-     * 
+     *
      * Implementations must ensure that the result does not contain entries with
      * empty rigths. E.g. "user1:lr;user2:lrwt".except("user1:lr") should return
      * "user2:lrwt" rather than "user1:;user2:lrwt"
-     * 
+     *
      * @param toRemove
      * @return
      * @throws UnsupportedRightException
      */
-    MailboxACL except(MailboxACL toRemove) throws UnsupportedRightException;
+    public MailboxACL except(MailboxACL other) throws UnsupportedRightException {
+        return new MailboxACL(entries.entrySet()
+            .stream()
+            .map(entry -> Pair.of(entry.getKey(),
+                Optional.ofNullable(other.getEntries().get(entry.getKey()))
+                    .map(Throwing.function(exceptValue -> entry.getValue().except(exceptValue)))
+                    .orElse(entry.getValue())))
+            .filter(pair -> !pair.getValue().isEmpty())
+            .collect(Guavate.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)));
+    }
 
-    /**
-     * TODO except.
-     * 
-     * @param key
-     * @param toRemove
-     * @return
-     * @throws UnsupportedRightException
-     */
-    MailboxACL except(MailboxACLEntryKey key, MailboxACLRights toRemove) throws UnsupportedRightException;
+    public MailboxACL except(EntryKey key, Rfc4314Rights mailboxACLRights) throws UnsupportedRightException {
+        return except(new MailboxACL(new Entry(key, mailboxACLRights)));
+    }
 
     /**
      * {@link Map} of entries.
-     * 
+     *
      * @return the entries.
      */
-    Map<MailboxACLEntryKey, MailboxACLRights> getEntries();
+    public Map<EntryKey, Rfc4314Rights> getEntries() {
+        return entries;
+    }
 
     /**
      * Replaces the entry corresponding to the given {@code key} with
      * {@code toAdd}link MailboxACLRights}.
-     * 
+     *
      * Implementations must return a new unmodifiable instance of
      * {@link MailboxACL}. However, implementations may decide to return this in
      * case the result would be equal to it.
-     * 
+     *
      * Implementations must ensure that the result does not contain entries with
      * empty rigths. E.g. "user1:lr;user2:lrwt".replace("user1",
      * MailboxACLRights.EMPTY) should return "user2:lrwt" rather than
      * "user1:;user2:lrwt". The same result should be returned by
      * "user1:lr;user2:lrwt".replace("user1", null).
-     * 
+     *
      * @param key
      * @param toAdd
      * @return
      * @throws UnsupportedRightException
      */
-    MailboxACL replace(MailboxACLEntryKey key, MailboxACLRights toAdd) throws UnsupportedRightException;
+    public MailboxACL replace(EntryKey key, Rfc4314Rights replacement) throws UnsupportedRightException {
+        if (entries.containsKey(key)) {
+            return new MailboxACL(
+                entries.entrySet()
+                    .stream()
+                    .map(entry -> Pair.of(entry.getKey(),
+                        entry.getKey().equals(key) ? replacement : entry.getValue()))
+                    .filter(pair -> pair.getValue() != null && !pair.getValue().isEmpty())
+                    .collect(Guavate.toImmutableMap(Pair::getKey, Pair::getValue)));
+        } else {
+            return new MailboxACL(
+                ImmutableMap.<EntryKey, Rfc4314Rights>builder()
+                    .putAll(entries)
+                    .put(key, replacement)
+                    .build());
+        }
+    }
+
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+            .add("entries", entries)
+            .toString();
+    }
 
     /**
      * Performs the set theoretic operation of union of this {@link MailboxACL}
      * and toAdd {@link MailboxACL}.
-     * 
+     *
      * A schematic example:
      * "user1:lr;user2:lrwt".union("user1:at;-$group1:lrwt") returns
      * "user1:alrt;user2:lrwt;-$group1:lrwt".
-     * 
+     *
      * Implementations must return a new unmodifiable instance of
      * {@link MailboxACL}. However, implementations may decide to return this or
      * toAdd parameter value in case the result would be equal to the respective
      * one of those.
-     * 
-     * 
+     *
+     *
      * @param toAdd
      * @return
      * @throws UnsupportedRightException
      */
-    MailboxACL union(MailboxACL toAdd) throws UnsupportedRightException;
+    public MailboxACL union(MailboxACL other) throws UnsupportedRightException {
+        return new MailboxACL(
+            Stream.concat(
+                    this.entries.entrySet().stream(),
+                    other.getEntries().entrySet().stream())
+                .collect(Guavate.toImmutableListMultimap(Map.Entry::getKey, Map.Entry::getValue))
+                .asMap()
+                .entrySet()
+                .stream()
+                .map(entry -> Pair.of(entry.getKey(),
+                    entry.getValue()
+                        .stream()
+                        .reduce(
+                            new Rfc4314Rights(),
+                            Throwing.binaryOperator(Rfc4314Rights::union))))
+                .collect(Guavate.toImmutableMap(Pair::getKey, Pair::getValue)));
+    }
+    
 
-    /**
-     * TODO union.
-     * 
-     * @param key
-     * @param toAdd
-     * @return
-     * @throws UnsupportedRightException
-     */
-    MailboxACL union(MailboxACLEntryKey key, MailboxACLRights toAdd) throws UnsupportedRightException;
+    public MailboxACL union(EntryKey key, Rfc4314Rights mailboxACLRights) throws UnsupportedRightException {
+        return union(new MailboxACL(new Entry(key, mailboxACLRights)));
+    }
 
 }


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