You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by an...@apache.org on 2011/03/16 19:07:08 UTC

svn commit: r1082239 [1/2] - in /jackrabbit/trunk/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/ main/java/org/apache/jackrabbit/core/security/ main/java/org/apache/jackrabbit/core/security/authorization/ main/java/org/apache/jackrabbit/cor...

Author: angela
Date: Wed Mar 16 18:07:07 2011
New Revision: 1082239

URL: http://svn.apache.org/viewvc?rev=1082239&view=rev
Log:
JCR-2887 : Split PrivilegeRegistry in a per-session manager instance and a repository level registry [work in progress]

Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SystemSession.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/DefaultAccessManager.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AbstractAccessControlProvider.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AbstractCompiledPermissions.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlEntryImpl.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/CompiledPermissions.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/Permission.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/PrivilegeManagerImpl.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/PrivilegeRegistry.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplate.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CompiledPermissionsImpl.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/combined/CombinedProvider.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLProvider.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserAccessControlProvider.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractACLTemplateTest.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractEntryTest.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/PrivilegeManagerImplTest.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/PrivilegeRegistryTest.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/EntryTest.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/principalbased/EntryTest.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserAccessControlProviderTest.java

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SystemSession.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SystemSession.java?rev=1082239&r1=1082238&r2=1082239&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SystemSession.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SystemSession.java Wed Mar 16 18:07:07 2011
@@ -280,7 +280,7 @@ class SystemSession extends SessionImpl 
         public Privilege[] getPrivileges(String absPath)
                 throws PathNotFoundException, RepositoryException {
             checkValidNodePath(absPath);
-            return new Privilege[] {getPrivilegeManager().getPrivilege(Privilege.JCR_ALL)};
+            return new Privilege[] {privilegeFromName(Privilege.JCR_ALL)};
         }
 
         /**

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/DefaultAccessManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/DefaultAccessManager.java?rev=1082239&r1=1082238&r2=1082239&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/DefaultAccessManager.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/DefaultAccessManager.java Wed Mar 16 18:07:07 2011
@@ -278,9 +278,8 @@ public class DefaultAccessManager extend
             log.debug("No privileges passed -> allowed.");
             return true;
         } else {
-            int privs = ((PrivilegeManagerImpl) privilegeManager).getBits(privileges);
             Path p = resolver.getQPath(absPath);
-            return (compiledPermissions.getPrivileges(p) | ~privs) == -1;
+            return compiledPermissions.hasPrivileges(p, privileges);
         }
     }
 
@@ -290,10 +289,8 @@ public class DefaultAccessManager extend
     public Privilege[] getPrivileges(String absPath) throws PathNotFoundException, RepositoryException {
         checkInitialized();
         checkValidNodePath(absPath);
-        int bits = compiledPermissions.getPrivileges(resolver.getQPath(absPath));
-        return (bits == PrivilegeRegistry.NO_PRIVILEGE) ?
-                new Privilege[0] :
-                ((PrivilegeManagerImpl) privilegeManager).getPrivileges(bits);
+        Set<Privilege> privs = compiledPermissions.getPrivilegeSet(resolver.getQPath(absPath));
+        return privs.toArray(new Privilege[privs.size()]);
     }
 
     /**
@@ -415,11 +412,10 @@ public class DefaultAccessManager extend
             log.debug("No privileges passed -> allowed.");
             return true;
         } else {
-            int privs = ((PrivilegeManagerImpl) privilegeManager).getBits(privileges);
             Path p = resolver.getQPath(absPath);
             CompiledPermissions perms = acProvider.compilePermissions(principals);
             try {
-                return (perms.getPrivileges(p) | ~privs) == -1;
+                return perms.hasPrivileges(p, privileges);
             } finally {
                 perms.close();
             }
@@ -436,10 +432,8 @@ public class DefaultAccessManager extend
 
         CompiledPermissions perms = acProvider.compilePermissions(principals);
         try {
-            int bits = perms.getPrivileges(resolver.getQPath(absPath));
-            return (bits == PrivilegeRegistry.NO_PRIVILEGE) ?
-                    new Privilege[0] :
-                    ((PrivilegeManagerImpl) privilegeManager).getPrivileges(bits);
+            Set<Privilege> privs = perms.getPrivilegeSet(resolver.getQPath(absPath));
+            return privs.toArray(new Privilege[privs.size()]);
         } finally {
             perms.close();
         }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AbstractAccessControlProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AbstractAccessControlProvider.java?rev=1082239&r1=1082238&r2=1082239&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AbstractAccessControlProvider.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AbstractAccessControlProvider.java Wed Mar 16 18:07:07 2011
@@ -17,6 +17,7 @@
 package org.apache.jackrabbit.core.security.authorization;
 
 import java.security.Principal;
+import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
 
@@ -78,6 +79,14 @@ public abstract class AbstractAccessCont
     }
 
     /**
+     * @return the PrivilegeManager
+     * @throws RepositoryException
+     */
+    protected PrivilegeManagerImpl getPrivilegeManagerImpl() throws RepositoryException {
+        return (PrivilegeManagerImpl) ((JackrabbitWorkspace) session.getWorkspace()).getPrivilegeManager();
+    }
+
+    /**
      * Returns compiled permissions for the administrator i.e. permissions
      * that grants everything and returns the int representation of {@link Privilege#JCR_ALL}
      * upon {@link CompiledPermissions#getPrivileges(Path)} for all
@@ -96,14 +105,24 @@ public abstract class AbstractAccessCont
                 return true;
             }
             public int getPrivileges(Path absPath) throws RepositoryException {
-                return getPrivilegeManagerImpl().getBits(new String[] {Privilege.JCR_ALL});
+                return getPrivilegeManagerImpl().getBits(new Privilege[] {getAllPrivilege()});
+            }
+            public boolean hasPrivileges(Path absPath, Privilege[] privileges) {
+                return true;
+            }
+            public Set<Privilege> getPrivilegeSet(Path absPath) throws RepositoryException {
+                return Collections.singleton(getAllPrivilege());
             }
             public boolean canReadAll() {
                 return true;
             }
-            public boolean canRead(Path itemPath, ItemId itemId) throws RepositoryException {
+            public boolean canRead(Path itemPath, ItemId itemId) {
                 return true;
             }
+
+            private Privilege getAllPrivilege() throws RepositoryException {
+                return getPrivilegeManagerImpl().getPrivilege(Privilege.JCR_ALL);
+            }
         };
     }
 
@@ -131,7 +150,21 @@ public abstract class AbstractAccessCont
                 if (isAcItem(absPath)) {
                     return PrivilegeRegistry.NO_PRIVILEGE;
                 } else {
-                    return getPrivilegeManagerImpl().getBits(new String[] {Privilege.JCR_READ});
+                    return getPrivilegeManagerImpl().getBits(new Privilege[] {getReadPrivilege()});
+                }
+            }
+            public boolean hasPrivileges(Path absPath, Privilege[] privileges) throws RepositoryException {
+                if (isAcItem(absPath)) {
+                    return false;
+                } else {
+                    return privileges != null && privileges.length == 1 && getReadPrivilege().equals(privileges[0]);
+                }
+            }
+            public Set<Privilege> getPrivilegeSet(Path absPath) throws RepositoryException {
+                if (isAcItem(absPath)) {
+                    return Collections.emptySet();
+                } else {
+                    return Collections.singleton(getReadPrivilege());
                 }
             }
             public boolean canReadAll() {
@@ -144,12 +177,13 @@ public abstract class AbstractAccessCont
                     return !isAcItem(session.getItemManager().getItem(itemId));
                 }
             }
+
+            private Privilege getReadPrivilege() throws RepositoryException {
+                return getPrivilegeManagerImpl().getPrivilege(Privilege.JCR_READ);
+            }
         };
     }
 
-    private PrivilegeManagerImpl getPrivilegeManagerImpl() throws RepositoryException {
-        return (PrivilegeManagerImpl) ((JackrabbitWorkspace) session.getWorkspace()).getPrivilegeManager();
-    }
     //-------------------------------------------------< AccessControlUtils >---
     /**
      * @see org.apache.jackrabbit.core.security.authorization.AccessControlUtils#isAcItem(Path)

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AbstractCompiledPermissions.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AbstractCompiledPermissions.java?rev=1082239&r1=1082238&r2=1082239&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AbstractCompiledPermissions.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AbstractCompiledPermissions.java Wed Mar 16 18:07:07 2011
@@ -20,7 +20,13 @@ import org.apache.commons.collections.ma
 import org.apache.jackrabbit.spi.Path;
 
 import javax.jcr.RepositoryException;
+import javax.jcr.security.AccessControlException;
+import javax.jcr.security.Privilege;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * <code>AbstractCompiledPermissions</code>...
@@ -63,6 +69,12 @@ public abstract class AbstractCompiledPe
     protected abstract Result buildResult(Path absPath) throws RepositoryException;
 
     /**
+     * 
+     * @return
+     */
+    protected abstract PrivilegeManagerImpl getPrivilegeManagerImpl() throws RepositoryException;
+
+    /**
      * Removes all entries from the cache.
      */
     protected void clearCache() {
@@ -71,6 +83,23 @@ public abstract class AbstractCompiledPe
         }
     }
 
+    /**
+     * Adds the given <code>privileges</code> to the specified
+     * <code>target</code> set if they are not present in the specified
+     * <code>complement</code> set.
+     * 
+     * @param privileges
+     * @param target
+     * @param complement
+     */
+    protected static void updatePrivileges(Collection<Privilege> privileges, Set<Privilege> target, Set<Privilege> complement) {
+        for (Privilege p : privileges) {
+            if (!complement.contains(p)) {
+                target.add(p);
+            }
+        }
+    }
+
     //------------------------------------------------< CompiledPermissions >---
     /**
      * @see CompiledPermissions#close()
@@ -94,6 +123,50 @@ public abstract class AbstractCompiledPe
     }
 
     /**
+     * @see CompiledPermissions#hasPrivileges(Path, Privilege[])
+     */
+    public boolean hasPrivileges(Path absPath, Privilege[] privileges) throws RepositoryException {
+        Result result = getResult(absPath);
+        int builtin = getPrivilegeManagerImpl().getBits(privileges);
+
+        if ((result.allowPrivileges | ~builtin) == -1) {
+            // in addition check all custom privileges
+            for (Privilege p : privileges) {
+                if (getPrivilegeManagerImpl().isCustomPrivilege(p)) {
+                    if (!result.customAllow.contains(p)) {
+                        if (p.isAggregate()) {
+                            // test if aggregated privs were granted individually.
+                            for (Privilege aggr : p.getAggregatePrivileges()) {
+                                if (!aggr.isAggregate() && !result.customAllow.contains(aggr)) {
+                                    // an aggregated custom priv is not allowed -> return false
+                                    return false;
+                                }
+                            }
+                        } else {
+                            // simple custom allow not allowed -> return false
+                            return false;
+                        }
+                    } // else: custom privilege allowed -> continue.
+                } // else: not a custom priv -> already covered.
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * @see CompiledPermissions#getPrivilegeSet(Path)
+     */
+    public Set<Privilege> getPrivilegeSet(Path absPath) throws RepositoryException {
+        Result result = getResult(absPath);
+        Set<Privilege> privileges = new HashSet<Privilege>();
+        privileges.addAll(getPrivilegeManagerImpl().getPrivileges(result.getPrivileges()));
+        privileges.addAll(result.customAllow);
+        return privileges;
+    }
+
+    /**
      * @see CompiledPermissions#canReadAll()
      */
     public boolean canReadAll() throws RepositoryException {
@@ -102,7 +175,7 @@ public abstract class AbstractCompiledPe
 
     //--------------------------------------------------------< inner class >---
     /**
-     *
+     * Result of permission (and optionally privilege) evaluation for a given path.
      */
     public static class Result {
 
@@ -113,13 +186,24 @@ public abstract class AbstractCompiledPe
         private final int allowPrivileges;
         private final int denyPrivileges;
 
+        private final Set<Privilege> customAllow;
+        private final Set<Privilege> customDeny;
+
         private int hashCode = -1;
 
         public Result(int allows, int denies, int allowPrivileges, int denyPrivileges) {
+            this(allows, denies, allowPrivileges, denyPrivileges, Collections.<Privilege>emptySet(), Collections.<Privilege>emptySet());
+        }
+
+        public Result(int allows, int denies, int allowPrivileges, int denyPrivileges,
+                      Set<Privilege> customAllow, Set<Privilege> customDeny) {
             this.allows = allows;
             this.denies = denies;
             this.allowPrivileges = allowPrivileges;
             this.denyPrivileges = denyPrivileges;
+
+            this.customAllow = customAllow;
+            this.customDeny = customDeny;
         }
 
         public boolean grants(int permissions) {
@@ -135,7 +219,15 @@ public abstract class AbstractCompiledPe
             int cDenies = denies | Permission.diff(other.denies, allows);
             int cAPrivs = allowPrivileges | Permission.diff(other.allowPrivileges, denyPrivileges);
             int cDPrivs = denyPrivileges | Permission.diff(other.denyPrivileges, allowPrivileges);
-            return new Result(cAllows, cDenies, cAPrivs, cDPrivs);
+
+            Set<Privilege> combinedAllow = new HashSet<Privilege>();
+            combinedAllow.addAll(customAllow);
+            updatePrivileges(other.customAllow, combinedAllow, customDeny);
+
+            Set<Privilege> combinedDeny = new HashSet<Privilege>();
+            combinedDeny.addAll(customDeny);
+            updatePrivileges(other.customDeny, combinedDeny, customAllow);
+            return new Result(cAllows, cDenies, cAPrivs, cDPrivs, customAllow, customDeny);
         }
 
         /**
@@ -149,6 +241,8 @@ public abstract class AbstractCompiledPe
                 h = 37 * h + denies;
                 h = 37 * h + allowPrivileges;
                 h = 37 * h + denyPrivileges;
+                h = 37 * h + customAllow.hashCode();
+                h = 37 * h + customDeny.hashCode();
                 hashCode = h;
             }
             return hashCode;
@@ -167,7 +261,9 @@ public abstract class AbstractCompiledPe
                 return allows == other.allows &&
                        denies == other.denies &&
                        allowPrivileges == other.allowPrivileges &&
-                       denyPrivileges == other.denyPrivileges;
+                       denyPrivileges == other.denyPrivileges &&
+                       customAllow.equals(other.customAllow) &&
+                       customDeny.equals(other.customDeny);
             }
             return false;
         }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlEntryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlEntryImpl.java?rev=1082239&r1=1082238&r2=1082239&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlEntryImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlEntryImpl.java Wed Mar 16 18:07:07 2011
@@ -30,7 +30,9 @@ import javax.jcr.security.Privilege;
 import java.security.Principal;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Simple, immutable implementation of the
@@ -40,12 +42,12 @@ import java.util.Map;
 public abstract class AccessControlEntryImpl implements JackrabbitAccessControlEntry {
 
     /**
-     * Privileges contained in this entry
+     * All privileges contained in this entry
      */
     private final Privilege[] privileges;
 
     /**
-     * PrivilegeBits calculated from the privileges
+     * PrivilegeBits calculated from built-in privileges
      */
     private final int privilegeBits;
 
@@ -72,6 +74,12 @@ public abstract class AccessControlEntry
     private int hashCode = -1;
 
     /**
+     * Calculated set of non-aggregate custom privileges (see also privilegeBits
+     * above for the built-in privileges) used upon ac evaluation.
+     */
+    private Set<Privilege> customPrivs;
+
+    /**
      * Construct an access control entry for the given principal and privileges.
      *
      * @param principal Principal for this access control entry.
@@ -128,31 +136,20 @@ public abstract class AccessControlEntry
     }
 
     /**
-     * 
+     *
      * @param base
      * @param privileges
      * @param isAllow
      * @throws AccessControlException
      */
-    protected AccessControlEntryImpl(AccessControlEntryImpl base, Privilege[] privileges, boolean isAllow) throws AccessControlException {
-        // make sure no abstract privileges are passed.
-        for (Privilege privilege : privileges) {
-            if (privilege.isAbstract()) {
-                throw new AccessControlException("Privilege " + privilege + " is abstract.");
-            }
-        }
-        this.principal = base.principal;
-        this.privileges = privileges;
-        this.privilegeBits = getPrivilegeManager().getBits(privileges);
-        this.allow = isAllow;
+    protected AccessControlEntryImpl(AccessControlEntryImpl base, Privilege[] privileges, boolean isAllow)
+            throws AccessControlException, RepositoryException {
+        this(base.principal, privileges, isAllow, (base.restrictions.isEmpty()) ? null : Collections.<String, Value>emptyMap());
 
-        if (base.restrictions == null) {
-            this.restrictions = Collections.emptyMap();
-        } else {
-            this.restrictions = new HashMap<Name, Value>(base.restrictions.size());
+        if (!base.restrictions.isEmpty()) {
             // validate the passed restrictions and fill the map
-            for (Name name : restrictions.keySet()) {
-                Value value = restrictions.get(name);
+            for (Name name : base.restrictions.keySet()) {
+                Value value = base.restrictions.get(name);
                 value = ValueHelper.copy(value, getValueFactory());
                 this.restrictions.put(name, value);
             }
@@ -160,13 +157,42 @@ public abstract class AccessControlEntry
     }
     
     /**
-     * @return the int representation of the privileges defined for this entry.
+     * @return the permission bits that correspond to the privileges defined by this entry.
      */
     public int getPrivilegeBits() {
         return privilegeBits;
     }
 
     /**
+     * @return A collection of all non-aggregate custom privileges defined by
+     * this entry including those contained in the aggregated custom privileges.
+     */
+    public Set<Privilege> getCustomPrivileges() {
+        if (customPrivs == null) {
+            customPrivs = new HashSet<Privilege>();
+            for (Privilege p : privileges) {
+                try {
+                    if (getPrivilegeManager().isCustomPrivilege(p)) {
+                        if (p.isAggregate()) {
+                            for (Privilege aggr : p.getAggregatePrivileges()) {
+                                if (!aggr.isAggregate()) {
+                                    customPrivs.add(p);
+                                }
+                            }
+                        } else {
+                            customPrivs.add(p);
+
+                        }
+                    }
+                } catch (AccessControlException e) {
+                    // ignore.
+                }
+            }
+        }
+        return customPrivs;
+    }
+
+    /**
      * Returns <code>true</code> if this ACE defines any restriction.
      *
      * @return <code>true</code> if this ACE defines any restriction;
@@ -216,7 +242,9 @@ public abstract class AccessControlEntry
     protected int buildHashCode() {
         int h = 17;
         h = 37 * h + principal.getName().hashCode();
-        h = 37 * h + privilegeBits;
+        for (Privilege p : privileges) {
+            h = 37 * h + p.hashCode();
+        }
         h = 37 * h + Boolean.valueOf(allow).hashCode();
         h = 37 * h + restrictions.hashCode();
         return h;
@@ -287,11 +315,19 @@ public abstract class AccessControlEntry
             return true;
         }
         if (obj instanceof AccessControlEntryImpl) {
-            AccessControlEntryImpl tmpl = (AccessControlEntryImpl) obj;
-            return principal.getName().equals(tmpl.principal.getName()) &&
-                   privilegeBits == tmpl.privilegeBits && allow == tmpl.allow &&
-                   restrictions.equals(tmpl.restrictions);
+            AccessControlEntryImpl other = (AccessControlEntryImpl) obj;
+            return principal.getName().equals(other.principal.getName()) &&
+                   privilegeBits == other.privilegeBits &&
+                   allow == other.allow &&
+                   restrictions.equals(other.restrictions) &&
+                   equalCustomPrivileges(other);
         }
         return false;
     }
+
+    private boolean equalCustomPrivileges(AccessControlEntryImpl other) {
+        Set<Privilege> a1 = getCustomPrivileges();
+        Set<Privilege> a2 = other.getCustomPrivileges();
+        return a1.size() == a2.size() && a1.containsAll(a2);
+    }
 }
\ No newline at end of file

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/CompiledPermissions.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/CompiledPermissions.java?rev=1082239&r1=1082238&r2=1082239&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/CompiledPermissions.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/CompiledPermissions.java Wed Mar 16 18:07:07 2011
@@ -20,6 +20,9 @@ import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.core.id.ItemId;
 
 import javax.jcr.RepositoryException;
+import javax.jcr.security.Privilege;
+import java.util.Collections;
+import java.util.Set;
 
 /**
  * <code>CompiledPermissions</code> represents the evaluation of an
@@ -52,17 +55,38 @@ public interface CompiledPermissions {
 
     /**
      * Returns the <code>Privilege</code> bits granted by the underlying policy
-     * if the given <code>absPath</code> denotes an existing <code>Node</code>,
-     * otherwise it returns zero.
+     * if the given <code>absPath</code>.
      *
      * @param absPath Absolute path to a <code>Node</code>.
-     * @return the granted privileges at <code>absPath</code> or zero if
-     * the path does not denote an existing <code>Node</code>.
+     * @return the granted privileges at <code>absPath</code>.
      * @throws RepositoryException if an error occurs
+     * @deprecated Use {@link #getPrivilegeSet(Path)} instead.
      */
     int getPrivileges(Path absPath) throws RepositoryException;
 
     /**
+     * Returns <code>true</code> if the given privileges are granted at the
+     * specified <code>absPath</code>.
+     *
+     * @param absPath
+     * @param privileges
+     * @return <code>true</code> if the given privileges are granted at the
+     * specified <code>absPath</code>.
+     * @throws RepositoryException
+     */
+    boolean hasPrivileges(Path absPath, Privilege[] privileges) throws RepositoryException;
+
+    /**
+     * Returns the <code>Privilege</code>s granted by the underlying policy
+     * at the given <code>absPath</code>.
+     *
+     * @param absPath Absolute path to a <code>Node</code>.
+     * @return the granted privileges at <code>absPath</code>.
+     * @throws RepositoryException if an error occurs
+     */
+    Set<Privilege> getPrivilegeSet(Path absPath) throws RepositoryException;
+
+    /**
      * Returns <code>true</code> if READ permission is granted everywhere.
      * This method acts as shortcut for {@link #grants(Path, int)} where
      * permissions is {@link Permission#READ} and allows to shorten the
@@ -110,6 +134,14 @@ public interface CompiledPermissions {
         public int getPrivileges(Path absPath) {
             return PrivilegeRegistry.NO_PRIVILEGE;
         }
+
+        public boolean hasPrivileges(Path absPath, Privilege[] privileges) throws RepositoryException {
+            return false;
+        }
+        public Set<Privilege> getPrivilegeSet(Path absPath) throws RepositoryException {
+            return Collections.emptySet();
+        }
+
         public boolean canReadAll() {
             return false;
         }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/Permission.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/Permission.java?rev=1082239&r1=1082238&r2=1082239&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/Permission.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/Permission.java Wed Mar 16 18:07:07 2011
@@ -55,9 +55,9 @@ public final class Permission {
      * Returns those bits from <code>permissions</code> that are not present in
      * the <code>otherPermissions</code>, i.e. subtracts the other permissions
      * from permissions.<br>
-     * If the specified <code>otherBits</code> do not intersect with
-     * <code>bits</code>,  <code>bits</code> are returned.<br>
-     * If <code>bits</code> is included <code>otherBits</code>,
+     * If the specified <code>otherPermissions</code> do not intersect with
+     * <code>permissions</code>,  <code>permissions</code> are returned.<br>
+     * If <code>permissions</code> is included in <code>otherPermissions</code>,
      * {@link #NONE} is returned.
      *
      * @param permissions

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/PrivilegeManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/PrivilegeManagerImpl.java?rev=1082239&r1=1082238&r2=1082239&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/PrivilegeManagerImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/PrivilegeManagerImpl.java Wed Mar 16 18:07:07 2011
@@ -19,7 +19,6 @@ package org.apache.jackrabbit.core.secur
 import org.apache.jackrabbit.api.security.authorization.PrivilegeManager;
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.spi.Name;
-import org.apache.jackrabbit.spi.commons.conversion.IllegalNameException;
 import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
 import org.slf4j.Logger;
@@ -50,7 +49,7 @@ public final class PrivilegeManagerImpl 
     private static final Privilege[] EMPTY_ARRAY = new Privilege[0];
 
     /**
-     * 
+     * The privilege registry
      */
     private final PrivilegeRegistry registry;
 
@@ -79,9 +78,7 @@ public final class PrivilegeManagerImpl 
 
     //---------------------------------------------------< PrivilegeManager >---
     /**
-     * Returns all registered privileges.
-     *
-     * @return all registered privileges.
+     * @see PrivilegeManager#getRegisteredPrivileges()
      */
     public Privilege[] getRegisteredPrivileges() throws RepositoryException {
         PrivilegeRegistry.Definition[] allDefs = registry.getAll();
@@ -98,12 +95,7 @@ public final class PrivilegeManagerImpl 
     }
 
     /**
-     * Returns the privilege with the specified <code>privilegeName</code>.
-     *
-     * @param privilegeName Name of the principal.
-     * @return the privilege with the specified <code>privilegeName</code>.
-     * @throws javax.jcr.security.AccessControlException If no privilege with the given name exists.
-     * @throws javax.jcr.RepositoryException If another error occurs.
+     * @see PrivilegeManager#getPrivilege(String)
      */
     public Privilege getPrivilege(String privilegeName) throws AccessControlException, RepositoryException {
         Name name = resolver.getQName(privilegeName);
@@ -120,14 +112,14 @@ public final class PrivilegeManagerImpl 
      * <li>the namespace URI must be a valid, registered namespace excluding
      * those namespaces marked as being reserved</li>
      * <li>an aggregate custom privilege is valid if all declared aggregate
-     * names can be resolved to registered privileges and if there exists no
-     * registered privilege with the same aggregated privileges.</li>
+     * names can be resolved to registered custom privileges and if there exists
+     * no registered privilege with the same aggregated privileges.</li>
      * </ul>
      *
      * <strong>Please note</strong><br>
      * Custom privilege(s) will not be enforced for any kind of repository
      * operations. Those are exclusively covered by the built-in privileges.
-     * This also implies that the {@link Permission}s are not effected by
+     * This also implies that the {@link Permission}s are not affected by
      * custom privileges.<p/>
      * Applications making use of the custom privilege(s) are in charge of
      * asserting whether the privileges are granted/denied according to their
@@ -143,6 +135,7 @@ public final class PrivilegeManagerImpl 
      * for isn't the administrator.
      * @throws RepositoryException If the privilege could not be registered due
      * to constraint violations or if persisting the custom privilege fails.
+     * @see PrivilegeManager#registerPrivilege(String, boolean, String[])
      */
     public Privilege registerPrivilege(String privilegeName, boolean isAbstract,
                                        String[] declaredAggregateNames)
@@ -167,59 +160,50 @@ public final class PrivilegeManagerImpl 
         return getPrivilege(privilegeName);
     }
 
-    //-----------------------------< implementation specific public methods >---
+    //-----------------------------< implementation specific public methods >---       
+    /**
+     * Returns <code>true</code> if the specified privilege is a custom
+     * privilege that has been
+     * {@link #registerPrivilege(String, boolean, String[]) registered} before.
+     *
+     * @param privilege
+     * @return <code>true</code> if the specified privilege is a custom
+     * privilege; <code>false</code> otherwise.
+     * @throws AccessControlException If the specified privilege is
+     * <code>null</code> or unknown to this manager.
+     */
+    public boolean isCustomPrivilege(Privilege privilege) throws AccessControlException {
+        if (privilege instanceof PrivilegeImpl) {
+            return ((PrivilegeImpl) privilege).definition.isCustom();
+        } else {
+            throw new AccessControlException("Invalid privilege instance.");
+        }
+    }
+
     /**
      * @param privileges An array of privileges.
-     * @return The privilege bits.
+     * @return The bits of the built-in privileges contained in the specified
+     * array.
      * @throws AccessControlException If the specified array is null, empty
      * or if it contains an unregistered privilege.
-     * @see #getPrivileges(int)
      */
     public int getBits(Privilege[] privileges) throws AccessControlException {
         if (privileges == null || privileges.length == 0) {
             throw new AccessControlException("Privilege array is empty or null.");
         }
-        int bits = PrivilegeRegistry.NO_PRIVILEGE;
-        for (Privilege priv : privileges) {
+        PrivilegeRegistry.Definition[] defs = new PrivilegeRegistry.Definition[privileges.length];
+        for (int i = 0; i < privileges.length; i++) {
+            Privilege priv = privileges[i];
             if (priv instanceof PrivilegeImpl) {
-                bits |= ((PrivilegeImpl) priv).definition.getBits();
+                defs[i] = ((PrivilegeImpl) priv).definition;
             } else {
                 throw new AccessControlException("Unknown privilege '" + priv.getName() + "'.");
             }
         }
-        return bits;
+        return registry.getBits(defs);
     }
 
     /**
-     * 
-     * @param privilegeNames
-     * @return
-     * @throws AccessControlException
-     * @throws RepositoryException
-     */
-    public int getBits(String[] privilegeNames) throws AccessControlException {
-        if (privilegeNames == null || privilegeNames.length == 0) {
-            throw new AccessControlException("Privilege name array is null or empty.");
-        }
-        int bits = PrivilegeRegistry.NO_PRIVILEGE;
-        for (String privName : privilegeNames) {
-            try {
-                PrivilegeRegistry.Definition def = registry.get(resolver.getQName(privName));
-                if (def == null) {
-                    throw new AccessControlException("Unknown privilege name '" + privName + "'.");
-                } else {
-                    bits |= def.getBits();
-                }
-            } catch (NamespaceException e) {
-                throw new AccessControlException("Invalid privilege name '" + privName + "'.");
-            } catch (IllegalNameException e) {
-                throw new AccessControlException("Invalid privilege name '" + privName + "'.");
-            }
-        }
-        return bits;
-    }
-        
-    /**
      * Returns an array of registered <code>Privilege</code>s. If the specified
      * <code>bits</code> represent a single registered privilege the returned array
      * contains a single element. Otherwise the returned array contains the
@@ -232,25 +216,31 @@ public final class PrivilegeManagerImpl 
      * <code>bits</code> or an empty array if <code>bits</code> cannot be
      * resolved to registered <code>Privilege</code>s.
      * @see #getBits(Privilege[])
-     * @see #getBits(String[])
      */
-    public Privilege[] getPrivileges(int bits) {
+    public Set<Privilege> getPrivileges(int bits) {
         Name[] names = registry.getNames(bits);
         if (names.length == 0) {
-            return EMPTY_ARRAY;
+            return Collections.emptySet();
         } else {
-            Privilege[] privs = new Privilege[names.length];
-            for (int i = 0; i < names.length; i++) {
+            Set<Privilege> privs = new HashSet<Privilege>(names.length);
+            for (Name n : names) {
                 try {
-                    privs[i] = getPrivilege(names[i]);
+                    privs.add(getPrivilege(n));
                 } catch (RepositoryException e) {
-                    log.error("Internal error: invalid privilege name " + names[i].toString());
+                    log.error("Internal error: invalid privilege name " + n.toString());
                 }
             }
             return privs;
         }
     }
 
+    //------------------------------------------------------------< private >---
+    /**
+     * @param name
+     * @return The privilege with the specified name.
+     * @throws AccessControlException
+     * @throws RepositoryException
+     */
     private Privilege getPrivilege(Name name) throws AccessControlException, RepositoryException {
         Privilege privilege;
         synchronized (cache) {
@@ -270,7 +260,6 @@ public final class PrivilegeManagerImpl 
     }
 
     //-----------------------------------------< PrivilegeRegistry.Listener >---
-
     /**
      * @see PrivilegeRegistry.Listener#privilegeRegistered(org.apache.jackrabbit.spi.Name)
      */
@@ -296,14 +285,14 @@ public final class PrivilegeManagerImpl 
         private PrivilegeImpl(PrivilegeRegistry.Definition definition) throws RepositoryException {
             this.definition = definition;
 
-            Name[] aggrNames = definition.getDeclaredAggregateNames();
-            if (aggrNames.length == 0) {
+            Name[] declAggrNames = definition.getDeclaredAggregateNames();
+            if (declAggrNames.length == 0) {
                 declaredAggregates = EMPTY_ARRAY;
                 aggregates = EMPTY_ARRAY;
             } else {
-                declaredAggregates = new Privilege[aggrNames.length];
-                for (int i = 0; i < aggrNames.length; i++) {
-                    declaredAggregates[i] = getPrivilege(aggrNames[i]);
+                declaredAggregates = new Privilege[declAggrNames.length];
+                for (int i = 0; i < declAggrNames.length; i++) {
+                    declaredAggregates[i] = getPrivilege(declAggrNames[i]);
                 }
 
                 Set<Privilege> aggr = new HashSet<Privilege>();

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/PrivilegeRegistry.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/PrivilegeRegistry.java?rev=1082239&r1=1082238&r2=1082239&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/PrivilegeRegistry.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/PrivilegeRegistry.java Wed Mar 16 18:07:07 2011
@@ -29,6 +29,7 @@ import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.NameFactory;
 import org.apache.jackrabbit.spi.commons.conversion.DefaultNamePathResolver;
 import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
+import org.apache.jackrabbit.spi.commons.iterator.Iterators;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
 import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
 import org.apache.jackrabbit.util.Text;
@@ -71,6 +72,11 @@ public final class PrivilegeRegistry {
     public static final Name REP_WRITE_NAME = NAME_FACTORY.create(REP_WRITE);
 
     /**
+     * A custom privilege for which bits were not calculated
+     */
+    private static final int UNDEFINED = -1;
+
+    /**
      * No privileges 
      */
     public static final int NO_PRIVILEGE = 0;
@@ -122,8 +128,6 @@ public final class PrivilegeRegistry {
 
     private final NameResolver resolver;
 
-    private int nextBits = RETENTION_MNGMT << 1;
-
     public PrivilegeRegistry(NamespaceRegistry namespaceRegistry, FileSystem fs)
             throws RepositoryException {
 
@@ -133,7 +137,7 @@ public final class PrivilegeRegistry {
 
         try {
             Map<Name, DefinitionStub> customDefs = customPrivilegesStore.load();
-            Map<Name, Definition> definitions = createPrivilegeDefinitions(customDefs);
+            Map<Name, Definition> definitions = createCustomDefinitions(customDefs);
             registerDefinitions(definitions);
         } catch (IOException e) {
             throw new RepositoryException("Failed to load custom privileges", e);
@@ -203,15 +207,14 @@ public final class PrivilegeRegistry {
      * @deprecated Use {@link PrivilegeManagerImpl#getPrivileges(int)} instead.
      */
     public Privilege[] getPrivileges(int bits) {
-        return new PrivilegeManagerImpl(this, resolver).getPrivileges(bits);
+        Set<Privilege> prvs = new PrivilegeManagerImpl(this, resolver).getPrivileges(bits);
+        return prvs.toArray(new Privilege[prvs.size()]);
     }
 
     /**
      * Best effort approach to calculate bits for built-in privileges. Throws
      * <code>UnsupportedOperationException</code> if the workaround fails.
-     * Note, that the bits calculated for jcr:all does not include any
-     * registered custom privileges.
-     *
+     * 
      * @param privileges An array of privileges.
      * @return The privilege bits.
      * @throws AccessControlException If the specified array is null
@@ -228,7 +231,7 @@ public final class PrivilegeRegistry {
         lookup.put(Name.NS_REP_PREFIX, Name.NS_REP_URI);
         lookup.put(Name.NS_JCR_PREFIX, Name.NS_JCR_URI);
 
-        int bits = PrivilegeRegistry.NO_PRIVILEGE;
+        int bits = NO_PRIVILEGE;
         for (Privilege priv : privileges) {
             String prefix = Text.getNamespacePrefix(priv.getName());
             if (lookup.containsKey(prefix)) {
@@ -374,8 +377,8 @@ public final class PrivilegeRegistry {
             throw new UnsupportedOperationException("No privilege store defined.");
         }
         synchronized (registeredPrivileges) {
-            Map<Name, DefinitionStub> stubs = Collections.singletonMap(privilegeName, new DefinitionStub(privilegeName, isAbstract, declaredAggregateNames, true));
-            Map<Name, Definition> definitions = createPrivilegeDefinitions(stubs);
+            Map<Name, DefinitionStub> stubs = Collections.singletonMap(privilegeName, new DefinitionStub(privilegeName, isAbstract, declaredAggregateNames));
+            Map<Name, Definition> definitions = createCustomDefinitions(stubs);
             try {
                 // write the new custom privilege to the store and upon successful
                 // update of the file system resource add finally it to the map of
@@ -417,14 +420,38 @@ public final class PrivilegeRegistry {
     }
 
     /**
+     * Calculates the bits of the specified definitions. Note, that custom
+     * privileges don't have a integer representation as they are not used
+     * for permission calculation.
+     *
+     * @param defs
+     * @return
+     */
+    int getBits(Definition[] defs) {
+        int bits = NO_PRIVILEGE;
+        for (Definition def : defs) {
+            bits |= def.bits;
+        }
+        return bits;
+    }
+
+    /**
+     * Returns the names of the privileges identified by the specified bits.
+     * Note, that custom privileges don't have a integer representation as they
+     * are not used for permission calculation.
+     * 
      * @param bits The privilege bits.
      * @return Privilege names that corresponds to the given bits.
      */
     Name[] getNames(int bits) {
-        if (bitsToNames.containsKey(bits)) {
+        if (bits <= NO_PRIVILEGE) {
+            return Name.EMPTY_ARRAY;
+        } else if (bitsToNames.containsKey(bits)) {
+            // matches all built-in aggregates and single built-in privileges
             Set<Name> ips = bitsToNames.get(bits);
             return ips.toArray(new Name[ips.size()]);
         } else {
+            // bits are a combination of built-in privileges.
             Set<Name> names = new HashSet<Name>();
             if ((bits & READ) == READ) {
                 names.add(NameConstants.JCR_READ);
@@ -471,13 +498,6 @@ public final class PrivilegeRegistry {
                 names.add(NameConstants.JCR_RETENTION_MANAGEMENT);
             }
 
-            // include matching custom privilege names 
-            for (Definition def : registeredPrivileges.values()) {
-                if (def.isCustom && ((bits & def.bits) == def.bits)) {
-                    names.add(def.name);
-                }
-            }
-
             if (!names.isEmpty()) {
                 bitsToNames.put(bits, names);
             }
@@ -499,7 +519,9 @@ public final class PrivilegeRegistry {
     private void registerDefinitions(Map<Name, Definition> definitions) {
         registeredPrivileges.putAll(definitions);
         for (Definition def : definitions.values()) {
-            bitsToNames.put(def.bits, Collections.singleton(def.name));
+            if (def.bits > NO_PRIVILEGE) {
+                bitsToNames.put(def.bits, Collections.singleton(def.name));
+            }
         }
 
         if (!definitions.containsKey(NameConstants.JCR_ALL)) {
@@ -509,12 +531,7 @@ public final class PrivilegeRegistry {
             Set<Name> allAggrNames = all.declaredAggregateNames;
             allAggrNames.addAll(definitions.keySet());
 
-            int allBits = all.bits;
-            for (Definition def : definitions.values()) {
-                allBits |= def.bits;
-            }
-
-            all = new Definition(NameConstants.JCR_ALL, false, allAggrNames, allBits);
+            all = new Definition(NameConstants.JCR_ALL, false, allAggrNames, all.bits);
             registeredPrivileges.put(NameConstants.JCR_ALL, all);
             bitsToNames.put(all.bits, Collections.singleton(NameConstants.JCR_ALL));
         }
@@ -566,7 +583,7 @@ public final class PrivilegeRegistry {
 
     /**
      * Validates the specified <code>DefinitionStub</code>s and creates
-     * new <code>PrivilegeDefinition</code>s. The validation includes name
+     * new custom <code>PrivilegeDefinition</code>s. The validation includes name
      * validation and resolution of declared aggregate names. The latter
      * also includes checks to prevent cyclic aggregation.
      *  
@@ -574,7 +591,7 @@ public final class PrivilegeRegistry {
      * @return new privilege definitions.
      * @throws RepositoryException If any of the specified stubs is invalid.
      */
-    private Map<Name, Definition> createPrivilegeDefinitions(Map<Name, DefinitionStub> toRegister) throws RepositoryException {
+    private Map<Name, Definition> createCustomDefinitions(Map<Name, DefinitionStub> toRegister) throws RepositoryException {
         Map<Name, Definition> definitions = new HashMap<Name, Definition>(toRegister.size());
         Set<DefinitionStub> aggregates = new HashSet<DefinitionStub>();
 
@@ -598,7 +615,7 @@ public final class PrivilegeRegistry {
             // validate aggregates
             if (stub.declaredAggregateNames.isEmpty()) {
                 // not an aggregate priv definition.
-                definitions.put(name, new Definition(stub, nextBits()));
+                definitions.put(name, new Definition(stub, NO_PRIVILEGE));
             } else {
                 for (Name declaredAggregateName : stub.declaredAggregateNames) {
                     if (name.equals(declaredAggregateName)) {
@@ -629,16 +646,40 @@ public final class PrivilegeRegistry {
             // look for those definitions whose declared aggregates have all been processed.
             for (Iterator<DefinitionStub> itr = aggregates.iterator(); itr.hasNext();) {
                 DefinitionStub stub = itr.next();
-                int bts = getAggregateBits(stub.declaredAggregateNames, definitions);
-                if (bitsToNames.containsKey(bts) && bitsToNames.get(bts).size() == 1) {
-                    Name existingName = bitsToNames.get(bts).iterator().next();
-                    throw new RepositoryException("Custom aggregate privilege '" + stub.name + "' is already covered by '" + existingName.toString() + "'");
+
+                int bts = NO_PRIVILEGE;
+                for (Name n : stub.declaredAggregateNames) {
+                    Definition aggr = null;
+                    if (registeredPrivileges.containsKey(n)) {
+                        aggr = registeredPrivileges.get(n);
+                    } else if (definitions.containsKey(n)) {
+                        aggr = definitions.get(n);
+                    }
+
+                    if (aggr == null) {
+                        // unknown dependency
+                        bts = UNDEFINED;
+                        break;
+                    } else if (!aggr.isCustom()) {
+                        throw new RepositoryException("Custom privileges may only aggregate custom privileges.");
+                    } // else: a known custom privilege -> try next.
                 }
-                if (bts != NO_PRIVILEGE) {
+
+                if (bts == NO_PRIVILEGE) {
                     Definition def = new Definition(stub, bts);
+
+                    // final validation if a custom aggregated has not yet been defined.
+                    Iterator<Definition> it = Iterators.iteratorChain(registeredPrivileges.values().iterator(), definitions.values().iterator());
+                    while (it.hasNext()) {
+                        Definition d = it.next();
+                        if (isEquivalentAggregate(d, def, definitions)) {
+                            throw new RepositoryException("Custom aggregate privilege '" + def.name + "' is already defined by '"+ d.name+"'");
+                        }
+                    }
+
                     definitions.put(def.name, def);
                     itr.remove();
-                }
+                } // unresolvable bts -> postpone to next iterator.
             }
 
             if (cnt == aggregates.size()) {
@@ -650,41 +691,33 @@ public final class PrivilegeRegistry {
         return definitions;
     }
 
-    /**
-     *
-     * @return
-     */
-    private int nextBits() {
-        int b = nextBits;
-        nextBits = nextBits << 1;
-        return b;
-    }
-
-    /**
-     *
-     * @param declaredAggregateNames
-     * @param toRegister
-     * @return
-     */
-    private int getAggregateBits(Set<Name> declaredAggregateNames, Map<Name, Definition> toRegister) {
-        int bts = NO_PRIVILEGE;
-        for (Name n : declaredAggregateNames) {
-            if (registeredPrivileges.containsKey(n)) {
-                bts |= registeredPrivileges.get(n).bits;
-            } else if (toRegister.containsKey(n)) {
-                Definition def = toRegister.get(n);
-                if (def.bits == NO_PRIVILEGE) {
-                    // not yet processed dependency -> wait for next iteration.
-                    return NO_PRIVILEGE;
-                } else {
-                    bts |= def.bits;
-                }
+    private boolean isEquivalentAggregate(Definition d, Definition otherDef,
+                                          Map<Name, Definition> unregistered) {
+        // either of the definitions isn't an aggregate.
+        if (d.declaredAggregateNames.isEmpty() || otherDef.declaredAggregateNames.isEmpty()) {
+            return false;
+        }
+        // two aggregates that defined the same declared aggregate names
+        if (d.declaredAggregateNames.equals(otherDef.declaredAggregateNames)) {
+            return true;
+        }
+        // two aggregates that defined the same aggregation of simple definitions.
+        Set<Name> aggrNames = getAggrNames(d, unregistered);
+        Set<Name> otherAggrNames = getAggrNames(otherDef, unregistered);
+        return aggrNames.size() == otherAggrNames.size() && aggrNames.containsAll(otherAggrNames);
+    }
+
+    private Set<Name> getAggrNames(Definition def, Map<Name, Definition> unregistered) {
+        Set<Name> names = new HashSet<Name>();
+        for (Name n : def.declaredAggregateNames) {
+            Definition a = (unregistered.containsKey(n)) ? unregistered.get(n) : registeredPrivileges.get(n);
+            if (a.declaredAggregateNames.isEmpty()) {
+                names.add(a.name);
             } else {
-                // unknown dependency (should not get here)
-                return NO_PRIVILEGE;
+                names.addAll(getAggrNames(a, unregistered));
             }
         }
-        return bts;
+        return names;
     }
 
     /**
@@ -758,15 +791,13 @@ public final class PrivilegeRegistry {
         protected final Name name;
         protected final boolean isAbstract;
         protected final Set<Name> declaredAggregateNames;
-        protected final boolean isCustom;
-        
+
         private int hashCode;
 
-        private DefinitionStub(Name name, boolean isAbstract, Set<Name> declaredAggregateNames, boolean isCustom) {
+        private DefinitionStub(Name name, boolean isAbstract, Set<Name> declaredAggregateNames) {
             this.name = name;
             this.isAbstract = isAbstract;
             this.declaredAggregateNames = (declaredAggregateNames == null) ? Collections.<Name>emptySet() : declaredAggregateNames;
-            this.isCustom = isCustom;
         }
 
         //---------------------------------------------------------< Object >---
@@ -781,6 +812,8 @@ public final class PrivilegeRegistry {
                 int h = 17;
                 h = 37 * h + name.hashCode();
                 h = 37 * h + Boolean.valueOf(isAbstract).hashCode();
+                /* NOTE: evaluation of decl-aggr-names is sufficient as
+                   uniqueness is asserted upon registration */
                 h = 37 * h + declaredAggregateNames.hashCode();
                 hashCode = h;
             }
@@ -796,6 +829,8 @@ public final class PrivilegeRegistry {
                 DefinitionStub other = (DefinitionStub) obj;
                 return name.equals(other.name)
                         && isAbstract==other.isAbstract
+                        /* NOTE: comparison of decl-aggr-names is sufficient as
+                           uniqueness is asserted upon registration */
                         && declaredAggregateNames.equals(other.declaredAggregateNames);
             }
             return false;
@@ -810,30 +845,22 @@ public final class PrivilegeRegistry {
         private final int bits;
 
         private Definition(DefinitionStub stub, int bits) {
-            this(stub.name, stub.isAbstract, stub.declaredAggregateNames, bits, stub.isCustom);
+            this(stub.name, stub.isAbstract, stub.declaredAggregateNames, bits);
         }
 
         private Definition(Name name, boolean isAbstract, int bits) {
-            this(name, isAbstract, Collections.<Name>emptySet(), bits, false);
+            this(name, isAbstract, Collections.<Name>emptySet(), bits);
         }
 
         private Definition(Name name, boolean isAbstract, Set<Name> declaredAggregateNames, int bits) {
-            this(name, isAbstract, declaredAggregateNames, bits, false);
-        }
-
-        private Definition(Name name, boolean isAbstract, Set<Name> declaredAggregateNames, int bits, boolean isCustom) {
-            super(name, isAbstract, declaredAggregateNames, isCustom);
-            if (bits == NO_PRIVILEGE) {
+            super(name, isAbstract, declaredAggregateNames);
+            if (bits < NO_PRIVILEGE) {
                 throw new IllegalArgumentException("Failed to build int representation of PrivilegeDefinition.");
             } else {
                 this.bits = bits;
             }
         }
 
-        int getBits() {
-            return bits;
-        }
-
         Name getName() {
             return name;
         }
@@ -849,6 +876,10 @@ public final class PrivilegeRegistry {
                 return declaredAggregateNames.toArray(new Name[declaredAggregateNames.size()]);
             }
         }
+
+        boolean isCustom() {
+            return bits == NO_PRIVILEGE;
+        }
     }
 
     /**
@@ -896,7 +927,7 @@ public final class PrivilegeRegistry {
                         if (stubs.containsKey(privName)) {
                             throw new RepositoryException("Duplicate entry for custom privilege with name " + privName.toString());
                         }
-                        stubs.put(privName, new DefinitionStub(privName, isAbstract, declaredAggrNames, true));
+                        stubs.put(privName, new DefinitionStub(privName, isAbstract, declaredAggrNames));
                     }
                 } finally {
                     in.close();

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplate.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplate.java?rev=1082239&r1=1082238&r2=1082239&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplate.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplate.java Wed Mar 16 18:07:07 2011
@@ -21,6 +21,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import javax.jcr.NamespaceException;
 import javax.jcr.NodeIterator;
@@ -45,8 +46,8 @@ import org.apache.jackrabbit.core.securi
 import org.apache.jackrabbit.core.security.authorization.AccessControlEntryImpl;
 import org.apache.jackrabbit.core.security.authorization.Permission;
 import org.apache.jackrabbit.core.security.authorization.PrivilegeManagerImpl;
-import org.apache.jackrabbit.core.security.authorization.PrivilegeRegistry;
 import org.apache.jackrabbit.core.security.authorization.GlobPattern;
+import org.apache.jackrabbit.core.security.authorization.PrivilegeRegistry;
 import org.apache.jackrabbit.core.security.principal.PrincipalImpl;
 import org.apache.jackrabbit.core.security.principal.UnknownPrincipal;
 import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
@@ -171,8 +172,8 @@ class ACLTemplate extends AbstractACLTem
                         privs,
                         NT_REP_GRANT_ACE.equals(((NodeTypeImpl) aceNode.getPrimaryNodeType()).getQName()),
                         restrictions);
-                // add the entry
-                internalAdd(ace);
+                // add the entry omitting any validation.
+                entries.add(ace);
             } catch (RepositoryException e) {
                 log.debug("Failed to build ACE from content.", e.getMessage());
             }
@@ -219,16 +220,20 @@ class ACLTemplate extends AbstractACLTem
                 // the same entry is already contained -> no modification
                 return false;
             }
+
             // check if need to adjust existing entries
             int updateIndex = -1;
             Entry complementEntry = null;
 
+            Set<Privilege> otherCustom = entry.getCustomPrivileges();
             for (Entry e : entriesPerPrincipal) {
                 if (equalRestriction(entry, e)) {
                     if (entry.isAllow() == e.isAllow()) {
                         // need to update an existing entry
                         int existingPrivs = e.getPrivilegeBits();
-                        if ((existingPrivs | ~entry.getPrivilegeBits()) == -1) {
+                        Set<Privilege> existingCustom = e.getCustomPrivileges();
+                        if ((existingPrivs | ~entry.getPrivilegeBits()) == -1 &&
+                                existingCustom.containsAll(otherCustom)) {
                             // all privileges to be granted/denied are already present
                             // in the existing entry -> not modified
                             return false;
@@ -237,13 +242,16 @@ class ACLTemplate extends AbstractACLTem
                         // remember the index of the existing entry to be updated later on.
                         updateIndex = entries.indexOf(e);
 
-                        // remove the existing entry and create a new that includes
-                        // both the new privileges and the existing ones.
+                        // remove the existing entry and create a new one that
+                        // includes both the new privileges and the existing ones.
                         entries.remove(e);
+
                         int mergedBits = e.getPrivilegeBits() | entry.getPrivilegeBits();
-                        Privilege[] mergedPrivs = privilegeMgr.getPrivileges(mergedBits);
+                        Set<Privilege> mergedPrivs = privilegeMgr.getPrivileges(mergedBits);
+                        mergedPrivs.addAll(existingCustom);
+                        mergedPrivs.addAll(otherCustom);
                         // omit validation check.
-                        entry = createEntry(entry, mergedPrivs, entry.isAllow());
+                        entry = createEntry(entry, mergedPrivs.toArray(new Privilege[mergedPrivs.size()]), entry.isAllow());
                     } else {
                         complementEntry = e;
                     }
@@ -256,20 +264,27 @@ class ACLTemplate extends AbstractACLTem
             if (complementEntry != null) {
 
                 int complPrivs = complementEntry.getPrivilegeBits();
-                int resultPrivs = Permission.diff(complPrivs, entry.getPrivilegeBits());
+                int diff = Permission.diff(complPrivs, entry.getPrivilegeBits());
 
-                if (resultPrivs == PrivilegeRegistry.NO_PRIVILEGE) {
+                Set<Privilege> result = complementEntry.getCustomPrivileges();
+                boolean customMod = result.removeAll(otherCustom);
+                
+                if (diff == PrivilegeRegistry.NO_PRIVILEGE && result.isEmpty()) {
                     // remove the complement entry as the new entry covers
                     // all privileges granted by the existing entry.
                     entries.remove(complementEntry);
                     updateIndex--;
-                    
-                } else if (resultPrivs != complPrivs) {
+
+                } else if (diff != complPrivs || customMod) {
                     // replace the existing entry having the privileges adjusted
                     int index = entries.indexOf(complementEntry);
                     entries.remove(complementEntry);
+
+                    // combine set of new builtin and custom privileges
+                    result.addAll(privilegeMgr.getPrivileges(diff));
+                    // and create a new entry.
                     Entry tmpl = createEntry(entry,
-                            privilegeMgr.getPrivileges(resultPrivs),
+                            result.toArray(new Privilege[result.size()]),
                             !entry.isAllow());
                     entries.add(index, tmpl);
                 } /* else: does not need to be modified.*/

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CompiledPermissionsImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CompiledPermissionsImpl.java?rev=1082239&r1=1082238&r2=1082239&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CompiledPermissionsImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CompiledPermissionsImpl.java Wed Mar 16 18:07:07 2011
@@ -17,6 +17,7 @@
 package org.apache.jackrabbit.core.security.authorization.acl;
 
 import org.apache.commons.collections.map.LRUMap;
+import org.apache.jackrabbit.api.JackrabbitWorkspace;
 import org.apache.jackrabbit.core.ItemImpl;
 import org.apache.jackrabbit.core.ItemManager;
 import org.apache.jackrabbit.core.NodeImpl;
@@ -29,6 +30,7 @@ import org.apache.jackrabbit.core.securi
 import org.apache.jackrabbit.core.security.authorization.AccessControlModifications;
 import org.apache.jackrabbit.core.security.authorization.AccessControlUtils;
 import org.apache.jackrabbit.core.security.authorization.Permission;
+import org.apache.jackrabbit.core.security.authorization.PrivilegeManagerImpl;
 import org.apache.jackrabbit.core.security.authorization.PrivilegeRegistry;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.Path;
@@ -37,8 +39,10 @@ import org.apache.jackrabbit.util.Text;
 import javax.jcr.ItemNotFoundException;
 import javax.jcr.RepositoryException;
 import javax.jcr.security.AccessControlEntry;
+import javax.jcr.security.Privilege;
 import java.security.Principal;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -104,10 +108,13 @@ class CompiledPermissionsImpl extends Ab
         int allows = Permission.NONE;
         int denies = Permission.NONE;
 
-        int allowPrivileges = PrivilegeRegistry.NO_PRIVILEGE;
-        int denyPrivileges = PrivilegeRegistry.NO_PRIVILEGE;
-        int parentAllows = PrivilegeRegistry.NO_PRIVILEGE;
-        int parentDenies = PrivilegeRegistry.NO_PRIVILEGE;
+        int allowBits = PrivilegeRegistry.NO_PRIVILEGE;
+        int denyBits = PrivilegeRegistry.NO_PRIVILEGE;
+        int parentAllowBits = PrivilegeRegistry.NO_PRIVILEGE;
+        int parentDenyBits = PrivilegeRegistry.NO_PRIVILEGE;
+
+        Set<Privilege> customAllow = new HashSet<Privilege>();
+        Set<Privilege> customDeny = new HashSet<Privilege>();
 
         String parentPath = Text.getRelativeParent(filter.getPath(), 1);
 
@@ -126,22 +133,27 @@ class CompiledPermissionsImpl extends Ab
             boolean matchesParent = (!isLocal && ace.matches(parentPath));
             if (matchesParent) {
                 if (ace.isAllow()) {
-                    parentAllows |= Permission.diff(entryBits, parentDenies);
+                    parentAllowBits |= Permission.diff(entryBits, parentDenyBits);
                 } else {
-                    parentDenies |= Permission.diff(entryBits, parentAllows);
+                    parentDenyBits |= Permission.diff(entryBits, parentAllowBits);
                 }
             }
             if (ace.isAllow()) {
-                allowPrivileges |= Permission.diff(entryBits, denyPrivileges);
-                int permissions = PrivilegeRegistry.calculatePermissions(allowPrivileges, parentAllows, true, isAcItem);
+                allowBits |= Permission.diff(entryBits, denyBits);
+                int permissions = PrivilegeRegistry.calculatePermissions(allowBits, parentAllowBits, true, isAcItem);
                 allows |= Permission.diff(permissions, denies);
+
+                updatePrivileges(ace.getCustomPrivileges(), customAllow, customDeny);
             } else {
-                denyPrivileges |= Permission.diff(entryBits, allowPrivileges);
-                int permissions = PrivilegeRegistry.calculatePermissions(denyPrivileges, parentDenies, false, isAcItem);
+                denyBits |= Permission.diff(entryBits, allowBits);
+                int permissions = PrivilegeRegistry.calculatePermissions(denyBits, parentDenyBits, false, isAcItem);
                 denies |= Permission.diff(permissions, allows);
+
+                updatePrivileges(ace.getCustomPrivileges(), customDeny, customAllow);
             }
         }
-        return new Result(allows, denies, allowPrivileges, denyPrivileges);
+
+        return new Result(allows, denies, allowBits, denyBits, customAllow, customDeny);
     }
 
     //------------------------------------< AbstractCompiledPermissions >---
@@ -190,6 +202,14 @@ class CompiledPermissionsImpl extends Ab
     }
 
     /**
+     * @see AbstractCompiledPermissions#getPrivilegeManagerImpl()
+     */
+    @Override
+    protected PrivilegeManagerImpl getPrivilegeManagerImpl() throws RepositoryException {
+        return (PrivilegeManagerImpl) ((JackrabbitWorkspace) session.getWorkspace()).getPrivilegeManager();
+    }
+
+    /**
      * @see AbstractCompiledPermissions#clearCache()
      */
     @Override

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/combined/CombinedProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/combined/CombinedProvider.java?rev=1082239&r1=1082238&r2=1082239&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/combined/CombinedProvider.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/combined/CombinedProvider.java Wed Mar 16 18:07:07 2011
@@ -23,6 +23,7 @@ import org.apache.jackrabbit.core.securi
 import org.apache.jackrabbit.core.security.authorization.Permission;
 import org.apache.jackrabbit.core.security.authorization.AccessControlUtils;
 import org.apache.jackrabbit.core.security.authorization.AbstractCompiledPermissions;
+import org.apache.jackrabbit.core.security.authorization.PrivilegeManagerImpl;
 import org.apache.jackrabbit.core.security.authorization.principalbased.ACLProvider;
 import org.apache.jackrabbit.core.ItemImpl;
 import org.apache.jackrabbit.core.id.ItemId;
@@ -219,7 +220,16 @@ public class CombinedProvider extends Ab
             }
         }
 
-        //------------------------------------< AbstractCompiledPermissions >---
+        //------------------------------------< AbstractCompiledPermissions >---      
+        /**
+         * @see AbstractCompiledPermissions#getResult(Path)
+         */
+        @Override
+        public Result getResult(Path absPath) throws RepositoryException {
+            // TODO: missing caching
+            return buildResult(absPath);
+        }
+
         /**
          * @see AbstractCompiledPermissions#buildResult(Path)
          */
@@ -234,12 +244,11 @@ public class CombinedProvider extends Ab
         }
 
         /**
-         * @see AbstractCompiledPermissions#getResult(Path)
+         * @see AbstractCompiledPermissions#getPrivilegeManagerImpl()
          */
         @Override
-        public Result getResult(Path absPath) throws RepositoryException {
-            // TODO: missing caching
-            return buildResult(absPath);
+        protected PrivilegeManagerImpl getPrivilegeManagerImpl() throws RepositoryException {
+            return CombinedProvider.this.getPrivilegeManagerImpl();
         }
 
         //--------------------------------------------< CompiledPermissions >---

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLProvider.java?rev=1082239&r1=1082238&r2=1082239&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLProvider.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLProvider.java Wed Mar 16 18:07:07 2011
@@ -101,8 +101,8 @@ public class ACLProvider extends Abstrac
 
         editor = new ACLEditor(session, resolver.getQPath(acRoot.getPath()));
         entriesCache = new EntriesCache(session, editor, acRoot.getPath());
-        JackrabbitWorkspace wsp = (JackrabbitWorkspace) session.getWorkspace();
-        readBits = ((PrivilegeManagerImpl) wsp.getPrivilegeManager()).getBits(new String[] {Privilege.JCR_READ});
+        PrivilegeManagerImpl pm = getPrivilegeManagerImpl();
+        readBits = pm.getBits(new Privilege[] {pm.getPrivilege(Privilege.JCR_READ)});
 
         // TODO: replace by configurable default policy (see JCR-2331)
         if (!configuration.containsKey(PARAM_OMIT_DEFAULT_PERMISSIONS)) {
@@ -395,6 +395,13 @@ public class ACLProvider extends Abstrac
             return buildResult(jcrPath, isAcItem);
         }
 
+        /**
+         * @see AbstractCompiledPermissions#getPrivilegeManagerImpl()
+         */
+        @Override
+        protected PrivilegeManagerImpl getPrivilegeManagerImpl() throws RepositoryException {
+            return ACLProvider.this.getPrivilegeManagerImpl();
+        }
 
         /**
          * Loop over all entries and evaluate allows/denies for those matching
@@ -402,7 +409,7 @@ public class ACLProvider extends Abstrac
          * 
          * @param targetPath Path used for the evaluation; pointing to an
          * existing or non-existing item.
-         * @param isAcItem the item
+         * @param isAcItem the item.
          * @return the result
          * @throws RepositoryException if an error occurs
          */
@@ -410,10 +417,14 @@ public class ACLProvider extends Abstrac
                                    boolean isAcItem) throws RepositoryException {
             int allows = Permission.NONE;
             int denies = Permission.NONE;
-            int allowPrivileges = PrivilegeRegistry.NO_PRIVILEGE;
-            int denyPrivileges = PrivilegeRegistry.NO_PRIVILEGE;
-            int parentAllows = PrivilegeRegistry.NO_PRIVILEGE;
-            int parentDenies = PrivilegeRegistry.NO_PRIVILEGE;
+
+            int allowBits = PrivilegeRegistry.NO_PRIVILEGE;
+            int denyBits = PrivilegeRegistry.NO_PRIVILEGE;
+            int parentAllowBits = PrivilegeRegistry.NO_PRIVILEGE;
+            int parentDenyBits = PrivilegeRegistry.NO_PRIVILEGE;
+
+            Set<Privilege> customAllow = new HashSet<Privilege>();
+            Set<Privilege> customDeny = new HashSet<Privilege>();
 
             String parentPath = Text.getRelativeParent(targetPath, 1);
             for (AccessControlEntry entry : entries) {
@@ -426,26 +437,31 @@ public class ACLProvider extends Abstrac
 
                 if (!"".equals(parentPath) && entr.matches(parentPath)) {
                     if (entr.isAllow()) {
-                        parentAllows |= Permission.diff(privs, parentDenies);
+                        parentAllowBits |= Permission.diff(privs, parentDenyBits);
                     } else {
-                        parentDenies |= Permission.diff(privs, parentAllows);
+                        parentDenyBits |= Permission.diff(privs, parentAllowBits);
                     }
                 }
 
                 boolean matches = entr.matches(targetPath);
                 if (matches) {
                     if (entr.isAllow()) {
-                        allowPrivileges |= Permission.diff(privs, denyPrivileges);
-                        int permissions = PrivilegeRegistry.calculatePermissions(allowPrivileges, parentAllows, true, isAcItem);
+                        allowBits |= Permission.diff(privs, denyBits);
+                        int permissions = PrivilegeRegistry.calculatePermissions(allowBits, parentAllowBits, true, isAcItem);
                         allows |= Permission.diff(permissions, denies);
+
+                        updatePrivileges(entr.getCustomPrivileges(), customAllow, customDeny);
                     } else {
-                        denyPrivileges |= Permission.diff(privs, allowPrivileges);
-                        int permissions = PrivilegeRegistry.calculatePermissions(denyPrivileges, parentDenies, false, isAcItem);
+                        denyBits |= Permission.diff(privs, allowBits);
+                        int permissions = PrivilegeRegistry.calculatePermissions(denyBits, parentDenyBits, false, isAcItem);
                         denies |= Permission.diff(permissions, allows);
+
+                        updatePrivileges(entr.getCustomPrivileges(), customDeny, customAllow);
                     }
                 }
             }
-            return new Result(allows, denies, allowPrivileges, denyPrivileges);
+
+            return new Result(allows, denies, allowBits, denyBits, customAllow, customDeny);
         }
 
         //--------------------------------------------< CompiledPermissions >---

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserAccessControlProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserAccessControlProvider.java?rev=1082239&r1=1082238&r2=1082239&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserAccessControlProvider.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserAccessControlProvider.java Wed Mar 16 18:07:07 2011
@@ -53,6 +53,8 @@ import javax.jcr.security.Privilege;
 
 import java.security.Principal;
 import java.security.acl.Group;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
@@ -314,11 +316,6 @@ public class UserAccessControlProvider e
         }
     }
 
-    private int getPrivilegeBits(String privName) throws RepositoryException {
-        PrivilegeManagerImpl impl = (PrivilegeManagerImpl) ((JackrabbitWorkspace) session.getWorkspace()).getPrivilegeManager();
-        return impl.getBits(new String[] {privName});
-    }
-
     private static boolean containsGroup(Set<Principal> principals, Principal group) {
         for (Iterator<Principal> it = principals.iterator(); it.hasNext() && group != null;) {
             Principal p = it.next();
@@ -380,6 +377,11 @@ public class UserAccessControlProvider e
             observationMgr.addEventListener(this, events, groupsPath, true, null, null, false);
         }
 
+        private int getPrivilegeBits(String privName) throws RepositoryException {
+            PrivilegeManagerImpl impl = getPrivilegeManagerImpl();
+            return impl.getBits(new Privilege[] {impl.getPrivilege(privName)});
+        }
+
         //------------------------------------< AbstractCompiledPermissions >---
         /**
          * @see AbstractCompiledPermissions#buildResult(Path)
@@ -489,6 +491,11 @@ public class UserAccessControlProvider e
             return new Result(allows, denies, privs, PrivilegeRegistry.NO_PRIVILEGE);
         }
 
+        @Override
+        protected PrivilegeManagerImpl getPrivilegeManagerImpl() throws RepositoryException {
+            return (PrivilegeManagerImpl) ((JackrabbitWorkspace) session.getWorkspace()).getPrivilegeManager();
+        }
+
         //--------------------------------------------< CompiledPermissions >---
         /**
          * @see CompiledPermissions#close()

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractACLTemplateTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractACLTemplateTest.java?rev=1082239&r1=1082238&r2=1082239&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractACLTemplateTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractACLTemplateTest.java Wed Mar 16 18:07:07 2011
@@ -119,8 +119,8 @@ public abstract class AbstractACLTemplat
                 public boolean isAllow() {
                     return false;
                 }
-                public int getPrivilegeBits() throws RepositoryException, NotExecutableException {
-                    return privilegeMgr.getBits(new String[] {Privilege.JCR_READ});
+                public int getPrivilegeBits() throws RepositoryException {
+                    return privilegeMgr.getBits(new Privilege[] {privilegeMgr.getPrivilege(Privilege.JCR_READ)});
                 }
                 public String[] getRestrictionNames() {
                     return new String[0];