Author: angela
Date: Thu May  3 16:51:10 2018
New Revision: 1830843

URL: http://svn.apache.org/viewvc?rev=1830843&view=rev
Log:
OAK-7343 : Improvements to PermissionEntryProviderImpl

Added:
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/NumEntries.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionCache.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionCacheBuilder.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/EmptyPermissionCacheTest.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/NumEntriesTest.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionCacheBuilderTest.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionEntryCacheTest.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionStoreImplTest.java
   (with props)
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/PrincipalPermissionEntriesTest.java
   (with props)
Modified:
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/CompiledPermissionImpl.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/MountPermissionProvider.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionEntryCache.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionEntryProviderImpl.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionStore.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionStoreEditor.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionStoreImpl.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PrincipalPermissionEntries.java
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/MountPermissionStoreTest.java
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionEntryProviderImplTest.java
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionHookTest.java
    
jackrabbit/oak/trunk/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/permission/PermissionConstants.java
    
jackrabbit/oak/trunk/oak-security-spi/src/main/java/org/apache/jackrabbit/oak/spi/security/authorization/permission/package-info.java

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/CompiledPermissionImpl.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/CompiledPermissionImpl.java?rev=1830843&r1=1830842&r2=1830843&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/CompiledPermissionImpl.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/CompiledPermissionImpl.java
 Thu May  3 16:51:10 2018
@@ -110,9 +110,8 @@ final class CompiledPermissionImpl imple
             }
         }
 
-        PermissionEntryCache cache = new PermissionEntryCache();
-        userStore = new PermissionEntryProviderImpl(store, cache, userNames, 
options);
-        groupStore = new PermissionEntryProviderImpl(store, cache, groupNames, 
options);
+        userStore = new PermissionEntryProviderImpl(store, userNames, options);
+        groupStore = new PermissionEntryProviderImpl(store, groupNames, 
options);
 
         typeProvider = new TreeTypeProvider(ctx);
     }

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/MountPermissionProvider.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/MountPermissionProvider.java?rev=1830843&r1=1830842&r2=1830843&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/MountPermissionProvider.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/MountPermissionProvider.java
 Thu May  3 16:51:10 2018
@@ -16,16 +16,11 @@
  */
 package org.apache.jackrabbit.oak.security.authorization.permission;
 
-import static com.google.common.collect.Lists.newArrayList;
-
 import java.security.Principal;
 import java.util.Collection;
 import java.util.List;
 import java.util.Set;
-
-import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
 
 import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.commons.LongUtils;
@@ -36,6 +31,8 @@ import org.apache.jackrabbit.oak.spi.sec
 import org.apache.jackrabbit.oak.spi.security.Context;
 import 
org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionProvider;
 
+import static com.google.common.collect.Lists.newArrayList;
+
 public class MountPermissionProvider extends PermissionProviderImpl {
 
     @Nonnull
@@ -79,12 +76,12 @@ public class MountPermissionProvider ext
             this.stores = stores;
         }
 
-        @CheckForNull
+        @Nonnull
         @Override
-        public Collection<PermissionEntry> load(@Nullable 
Collection<PermissionEntry> entries, @Nonnull String principalName,
-                @Nonnull String path) {
+        public Collection<PermissionEntry> load(@Nonnull String principalName,
+                                                @Nonnull String path) {
             for (PermissionStoreImpl store : stores) {
-                Collection<PermissionEntry> col = store.load(null, 
principalName, path);
+                Collection<PermissionEntry> col = store.load(principalName, 
path);
                 if (col != null && !col.isEmpty()) {
                     return col;
                 }
@@ -97,22 +94,32 @@ public class MountPermissionProvider ext
         public PrincipalPermissionEntries load(@Nonnull String principalName) {
             PrincipalPermissionEntries ppe = new PrincipalPermissionEntries();
             for (PermissionStoreImpl store : stores) {
-                
ppe.getEntries().putAll(store.load(principalName).getEntries());
+                ppe.putAllEntries(store.load(principalName).getEntries());
             }
             ppe.setFullyLoaded(true);
             return ppe;
         }
 
+        @Nonnull
         @Override
-        public long getNumEntries(@Nonnull String principalName, long max) {
+        public NumEntries getNumEntries(@Nonnull String principalName, long 
max) {
             long num = 0;
+            boolean isExact = true;
             for (PermissionStoreImpl store : stores) {
-                num = LongUtils.safeAdd(num, 
store.getNumEntries(principalName, max));
-                if (num >= max) {
+                NumEntries ne = store.getNumEntries(principalName, max);
+                num = LongUtils.safeAdd(num, ne.size);
+                if (!ne.isExact) {
+                    isExact = false;
+                }
+                // if any of the stores doesn't reveal the exact number and max
+                // is reached, stop asking the remaining stores.
+                // as long as every store is reporting the exact number 
continue
+                // in order to (possibly) be able to return the exact number.
+                if (num >= max && !isExact) {
                     break;
                 }
             }
-            return num;
+            return NumEntries.valueOf(num, isExact);
         }
 
         @Override

Added: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/NumEntries.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/NumEntries.java?rev=1830843&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/NumEntries.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/NumEntries.java
 Thu May  3 16:51:10 2018
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.security.authorization.permission;
+
+import com.google.common.base.Objects;
+
+final class NumEntries {
+
+    static final NumEntries ZERO = new NumEntries(0, true);
+
+    final long size;
+    final boolean isExact;
+
+    private NumEntries(long size, boolean isExact) {
+        this.size = size;
+        this.isExact = isExact;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(size, isExact);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj instanceof NumEntries) {
+            NumEntries other = (NumEntries) obj;
+            return size == other.size && isExact == other.isExact;
+        } else {
+            return false;
+        }
+    }
+
+    static NumEntries valueOf(long size, boolean isExact) {
+        if (size == 0) {
+            // if size is zero we assume that this is the correct value
+            // irrespective of the isExact flag.
+            return ZERO;
+        } else {
+            return new NumEntries(size, isExact);
+        }
+    }
+}
\ No newline at end of file

Propchange: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/NumEntries.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionCache.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionCache.java?rev=1830843&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionCache.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionCache.java
 Thu May  3 16:51:10 2018
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.security.authorization.permission;
+
+import java.util.Collection;
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.oak.api.Tree;
+
+interface PermissionCache {
+
+    Collection<PermissionEntry> getEntries(@Nonnull String path);
+
+    Collection<PermissionEntry> getEntries(@Nonnull Tree accessControlledTree);
+}
\ No newline at end of file

Propchange: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionCache.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionCacheBuilder.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionCacheBuilder.java?rev=1830843&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionCacheBuilder.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionCacheBuilder.java
 Thu May  3 16:51:10 2018
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.security.authorization.permission;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.commons.LongUtils;
+import 
org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants;
+
+import static com.google.common.base.Preconditions.checkState;
+
+final class PermissionCacheBuilder {
+
+    private static final long MAX_PATHS_SIZE = 10;
+
+    private final PermissionStore store;
+    private final PermissionEntryCache peCache;
+
+    private Set<String> existingNames;
+    private boolean usePathEntryMap;
+
+    private boolean initialized = false;
+
+    PermissionCacheBuilder(@Nonnull PermissionStore store) {
+        this.store = store;
+        this.peCache = new PermissionEntryCache();
+    }
+
+    boolean init(@Nonnull Set<String> principalNames, long maxSize) {
+        existingNames = new HashSet<>();
+        long cnt = 0;
+        for (String name : principalNames) {
+            NumEntries ne = store.getNumEntries(name, maxSize);
+            long n = ne.size;
+            /*
+            if getNumEntries (n) returns a number bigger than 0, we
+            remember this principal name int the 'existingNames' set
+            */
+            if (n > 0) {
+                existingNames.add(name);
+                if (n <= MAX_PATHS_SIZE) {
+                    peCache.getFullyLoadedEntries(store, name);
+                } else {
+                    long expectedSize = (ne.isExact) ? n : Long.MAX_VALUE;
+                    peCache.init(name, expectedSize);
+                }
+            }
+            /*
+            Estimate the total number of access controlled paths (cnt) defined
+            for the given set of principals in order to be able to determine if
+            the pathEntryMap should be loaded upfront.
+            Note however that cache.getNumEntries (n) may return Long.MAX_VALUE
+            if the underlying implementation does not know the exact value, and
+            the child node count is higher than maxSize (see OAK-2465).
+            */
+            if (cnt < Long.MAX_VALUE) {
+                if (Long.MAX_VALUE == n) {
+                    cnt = Long.MAX_VALUE;
+                } else {
+                    cnt = LongUtils.safeAdd(cnt, n);
+                }
+            }
+        }
+
+        usePathEntryMap = (cnt > 0 && cnt < maxSize);
+        initialized = true;
+        return existingNames.isEmpty();
+    }
+
+    PermissionCache build() {
+        checkState(initialized);
+        if (existingNames.isEmpty()) {
+            return EmptyCache.INSTANCE;
+        }
+        if (usePathEntryMap) {
+            // the total number of access controlled paths is smaller that 
maxSize,
+            // so we can load all permission entries for all principals having
+            // any entries right away into the pathEntryMap
+            Map<String, Collection<PermissionEntry>> pathEntryMap = new 
HashMap<>();
+            for (String name : existingNames) {
+                PrincipalPermissionEntries ppe = 
peCache.getFullyLoadedEntries(store, name);
+                for (Map.Entry<String, Collection<PermissionEntry>> e : 
ppe.getEntries().entrySet()) {
+                    String path = e.getKey();
+                    Collection<PermissionEntry> pathEntries = 
pathEntryMap.get(path);
+                    if (pathEntries == null) {
+                        pathEntries = new TreeSet(e.getValue());
+                        pathEntryMap.put(path, pathEntries);
+                    } else {
+                        pathEntries.addAll(e.getValue());
+                    }
+                }
+            }
+            if (pathEntryMap.isEmpty()) {
+                return EmptyCache.INSTANCE;
+            } else {
+                return new PathEntryMapCache(pathEntryMap);
+            }
+        } else {
+            return new DefaultPermissionCache(store, peCache, existingNames);
+        }
+
+    }
+
+    //------------------------------------< PermissionCache Implementations 
>---
+    /**
+     * Default implementation of {@code PermissionCache} wrapping the
+     * {@code PermissionEntryCache}, which was previously hold as shared field
+     * inside the {@code PermissionEntryProviderImpl}
+     */
+    private static final class DefaultPermissionCache implements 
PermissionCache {
+        private final PermissionStore store;
+        private final PermissionEntryCache cache;
+        private final Set<String> existingNames;
+
+        DefaultPermissionCache(@Nonnull PermissionStore store, @Nonnull 
PermissionEntryCache cache, Set<String> existingNames) {
+            this.store = store;
+            this.cache = cache;
+            this.existingNames = existingNames;
+        }
+
+        @Override
+        public Collection<PermissionEntry> getEntries(@Nonnull String path) {
+            Collection<PermissionEntry> ret = new TreeSet();
+            for (String name : existingNames) {
+                cache.load(store, ret, name, path);
+            }
+            return ret;
+        }
+
+        @Override
+        public Collection<PermissionEntry> getEntries(@Nonnull Tree 
accessControlledTree) {
+            return 
(accessControlledTree.hasChild(AccessControlConstants.REP_POLICY)) ?
+                    getEntries(accessControlledTree.getPath()) :
+                    Collections.<PermissionEntry>emptyList();
+        }
+    }
+
+    /**
+     * Fixed size implementation of {@code PermissionCache} that holds a map
+     * containing all existing entries that in this case have been read eagerly
+     * upfront. This implementation replaces the optional {@code pathEntryMap}
+     * previously present inside the the {@code PermissionEntryProviderImpl}.
+     */
+    private static final class PathEntryMapCache implements PermissionCache {
+        private final Map<String, Collection<PermissionEntry>> pathEntryMap;
+
+        PathEntryMapCache(Map<String, Collection<PermissionEntry>> 
pathEntryMap) {
+            this.pathEntryMap = pathEntryMap;
+        }
+
+        @Override
+        public Collection<PermissionEntry> getEntries(@Nonnull String path) {
+            Collection<PermissionEntry> entries = pathEntryMap.get(path);
+            return (entries != null) ? entries : 
Collections.<PermissionEntry>emptyList();
+        }
+
+        @Override
+        public Collection<PermissionEntry> getEntries(@Nonnull Tree 
accessControlledTree) {
+            Collection<PermissionEntry> entries = 
pathEntryMap.get(accessControlledTree.getPath());
+            return (entries != null) ? entries : 
Collections.<PermissionEntry>emptyList();
+        }
+    }
+
+    /**
+     * Empty implementation of {@code PermissionCache} for those cases where
+     * for a given (possibly empty) set of principals no permission entries are
+     * present.
+     */
+    private static final class EmptyCache implements PermissionCache {
+
+        private static final PermissionCache INSTANCE = new EmptyCache();
+
+        @Override
+        public Collection<PermissionEntry> getEntries(@Nonnull String path) {
+            return Collections.<PermissionEntry>emptyList();
+        }
+
+        @Override
+        public Collection<PermissionEntry> getEntries(@Nonnull Tree 
accessControlledTree) {
+            return Collections.<PermissionEntry>emptyList();
+        }
+    }
+
+}
\ No newline at end of file

Propchange: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionCacheBuilder.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionEntryCache.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionEntryCache.java?rev=1830843&r1=1830842&r2=1830843&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionEntryCache.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionEntryCache.java
 Thu May  3 16:51:10 2018
@@ -20,55 +20,36 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
 import javax.annotation.Nonnull;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 /**
  * {@code PermissionEntryCache} caches the permission entries of principals.
  * The cache is held locally for each session and contains a version of the 
principal permission
  * entries of the session that read them last.
- *
- * TODO:
- * - report cache usage metrics
- * - limit size of local caches based on ppe sizes. the current implementation 
loads all ppes. this can get a memory
- *   problem, as well as a performance problem for principals with many 
entries. principals with many entries must
- *   fallback to the direct store.load() methods when providing the entries. 
if those principals with many entries
- *   are used often, they might get elected to live in the global cache; 
memory permitting.
  */
 class PermissionEntryCache {
 
-    private final Map<String, PrincipalPermissionEntries> entries = new 
HashMap<String, PrincipalPermissionEntries>();
+    private static final Logger log = 
LoggerFactory.getLogger(PermissionEntryCache.class);
+
+    private final Map<String, PrincipalPermissionEntries> entries = new 
HashMap<>();
 
     @Nonnull
-    PrincipalPermissionEntries getEntries(@Nonnull PermissionStore store,
-                                                 @Nonnull String 
principalName) {
+    PrincipalPermissionEntries getFullyLoadedEntries(@Nonnull PermissionStore 
store,
+                                                     @Nonnull String 
principalName) {
         PrincipalPermissionEntries ppe = entries.get(principalName);
-        if (ppe == null) {
+        if (ppe == null || !ppe.isFullyLoaded()) {
             ppe = store.load(principalName);
             entries.put(principalName, ppe);
-        } else {
-            if (!ppe.isFullyLoaded()) {
-                ppe = store.load(principalName);
-                entries.put(principalName, ppe);
-            }
         }
         return ppe;
     }
 
-    void load(@Nonnull PermissionStore store,
-              @Nonnull Map<String, Collection<PermissionEntry>> pathEntryMap,
-              @Nonnull String principalName) {
-        // todo: conditionally load entries if too many
-        PrincipalPermissionEntries ppe = getEntries(store, principalName);
-        for (Map.Entry<String, Collection<PermissionEntry>> e: 
ppe.getEntries().entrySet()) {
-            Collection<PermissionEntry> pathEntries = 
pathEntryMap.get(e.getKey());
-            if (pathEntries == null) {
-                pathEntries = new TreeSet<PermissionEntry>(e.getValue());
-                pathEntryMap.put(e.getKey(), pathEntries);
-            } else {
-                pathEntries.addAll(e.getValue());
-            }
+    void init(@Nonnull String principalName, long expectedSize) {
+        if (!entries.containsKey(principalName)) {
+            entries.put(principalName, new 
PrincipalPermissionEntries(expectedSize));
         }
     }
 
@@ -76,26 +57,29 @@ class PermissionEntryCache {
               @Nonnull Collection<PermissionEntry> ret,
               @Nonnull String principalName,
               @Nonnull String path) {
-        PrincipalPermissionEntries ppe = entries.get(principalName);
-        if (ppe == null) {
-            ppe = new PrincipalPermissionEntries();
-            entries.put(principalName, ppe);
-        }
-        Collection<PermissionEntry> pes = ppe.getEntries().get(path);
-        if (pes == null) {
-            pes = store.load(null, principalName, path);
-            if (pes == null) {
-                pes = Collections.emptySet();
+        if (entries.containsKey(principalName)) {
+            PrincipalPermissionEntries ppe = entries.get(principalName);
+            Collection<PermissionEntry> pes = ppe.getEntriesByPath(path);
+            if (ppe.isFullyLoaded() || pes != null) {
+                // no need to read from store
+                if (pes != null) {
+                    ret.addAll(pes);
+                }
             } else {
-                ret.addAll(pes);
+                // read entries for path from store
+                pes = store.load(principalName, path);
+                if (pes == null) {
+                    // nothing to add to the result collection 'ret'.
+                    // nevertheless, remember the absence of any permission 
entries
+                    // in the cache to avoid reading from store again.
+                    ppe.putEntriesByPath(path, Collections.emptySet());
+                } else {
+                    ppe.putEntriesByPath(path, pes);
+                    ret.addAll(pes);
+                }
             }
-            ppe.getEntries().put(path, pes);
         } else {
-            ret.addAll(pes);
+            log.error("Failed to load entries for principal '%s' at path %s", 
principalName, path);
         }
     }
-
-    void flush(@Nonnull Set<String> principalNames) {
-        entries.keySet().removeAll(principalNames);
-    }
 }
\ No newline at end of file

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionEntryProviderImpl.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionEntryProviderImpl.java?rev=1830843&r1=1830842&r2=1830843&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionEntryProviderImpl.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionEntryProviderImpl.java
 Thu May  3 16:51:10 2018
@@ -18,20 +18,14 @@ package org.apache.jackrabbit.oak.securi
 
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
-import java.util.Map;
 import java.util.Set;
-import java.util.TreeSet;
 import javax.annotation.Nonnull;
 
 import com.google.common.base.Strings;
 import org.apache.jackrabbit.commons.iterator.AbstractLazyIterator;
 import org.apache.jackrabbit.oak.api.Tree;
-import org.apache.jackrabbit.oak.commons.LongUtils;
 import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
-import 
org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants;
 
 class PermissionEntryProviderImpl implements PermissionEntryProvider {
 
@@ -45,84 +39,41 @@ class PermissionEntryProviderImpl implem
      */
     private final Set<String> principalNames;
 
-    /**
-     * The set of principal names for which the store contains any permission
-     * entries. This set is equals or just a subset of the {@code 
principalNames}
-     * defined above. The methods collecting the entries will shortcut in case
-     * this set is empty and thus no permission entries exist for the specified
-     * set of principal.
-     */
-    private final Set<String> existingNames = new HashSet<String>();
-
     private final PermissionStore store;
 
-    private final PermissionEntryCache cache;
-
     private final long maxSize;
 
-    private Map<String, Collection<PermissionEntry>> pathEntryMap;
+    /**
+     * Flag to indicate if the the store contains any permission entries for 
the
+     * given set of principal names.
+     */
+    private boolean noExistingNames;
+
+    private PermissionCache permissionCache;
 
-    PermissionEntryProviderImpl(@Nonnull PermissionStore store, @Nonnull 
PermissionEntryCache cache,
-                                @Nonnull Set<String> principalNames, @Nonnull 
ConfigurationParameters options) {
+    PermissionEntryProviderImpl(@Nonnull PermissionStore store, @Nonnull 
Set<String> principalNames, @Nonnull ConfigurationParameters options) {
         this.store = store;
-        this.cache = cache;
         this.principalNames = Collections.unmodifiableSet(principalNames);
         this.maxSize = options.getConfigValue(EAGER_CACHE_SIZE_PARAM, 
DEFAULT_SIZE);
         init();
     }
 
     private void init() {
-        long cnt = 0;
-        existingNames.clear();
-        for (String name : principalNames) {
-            long n = store.getNumEntries(name, maxSize);
-            /*
-            if cache.getNumEntries (n) returns a number bigger than 0, we
-            remember this principal name int the 'existingNames' set
-            */
-            if (n > 0) {
-                existingNames.add(name);
-            }
-            /*
-            Calculate the total number of permission entries (cnt) defined for 
the
-            given set of principals in order to be able to determine if the 
cache
-            should be loaded upfront.
-            Note however that cache.getNumEntries (n) may return Long.MAX_VALUE
-            if the underlying implementation does not know the exact value, and
-            the child node count is higher than maxSize (see OAK-2465).
-            */                        
-            if (cnt < Long.MAX_VALUE) {
-                if (Long.MAX_VALUE == n) {
-                    cnt = Long.MAX_VALUE;
-                } else {
-                    cnt = LongUtils.safeAdd(cnt, n);
-                }
-            }
-        }
-
-        if (cnt > 0 && cnt < maxSize) {
-            // the total number of entries is smaller that maxSize, so we can
-            // cache all entries for all principals having any entries right 
away
-            pathEntryMap = new HashMap<String, Collection<PermissionEntry>>();
-            for (String name : existingNames) {
-                cache.load(store, pathEntryMap, name);
-            }
-        } else {
-            pathEntryMap = null;
-        }
+        PermissionCacheBuilder builder = new PermissionCacheBuilder(store);
+        noExistingNames = builder.init(principalNames, maxSize);
+        permissionCache = builder.build();
     }
 
     //--------------------------------------------< PermissionEntryProvider 
>---
     @Override
     public void flush() {
-        cache.flush(principalNames);
         init();
     }
 
     @Override
     @Nonnull
     public Iterator<PermissionEntry> getEntryIterator(@Nonnull EntryPredicate 
predicate) {
-        if (existingNames.isEmpty()) {
+        if (noExistingNames) {
             return Collections.emptyIterator();
         } else {
             return new EntryIterator(predicate);
@@ -132,38 +83,13 @@ class PermissionEntryProviderImpl implem
     @Override
     @Nonnull
     public Collection<PermissionEntry> getEntries(@Nonnull Tree 
accessControlledTree) {
-        if (existingNames.isEmpty()) {
-            return Collections.emptyList();
-        } else if (pathEntryMap != null) {
-            Collection<PermissionEntry> entries = 
pathEntryMap.get(accessControlledTree.getPath());
-            return (entries != null) ? entries : 
Collections.<PermissionEntry>emptyList();
-        } else {
-            return 
(accessControlledTree.hasChild(AccessControlConstants.REP_POLICY)) ?
-                    loadEntries(accessControlledTree.getPath()) :
-                    Collections.<PermissionEntry>emptyList();
-        }
+        return permissionCache.getEntries(accessControlledTree);
     }
 
     //------------------------------------------------------------< private 
>---
     @Nonnull
     private Collection<PermissionEntry> getEntries(@Nonnull String path) {
-        if (existingNames.isEmpty()) {
-            return Collections.emptyList();
-        } else if (pathEntryMap != null) {
-            Collection<PermissionEntry> entries = pathEntryMap.get(path);
-            return (entries != null) ? entries : 
Collections.<PermissionEntry>emptyList();
-        } else {
-            return loadEntries(path);
-        }
-    }
-
-    @Nonnull
-    private Collection<PermissionEntry> loadEntries(@Nonnull String path) {
-        Collection<PermissionEntry> ret = new TreeSet<PermissionEntry>();
-        for (String name : existingNames) {
-            cache.load(store, ret, name, path);
-        }
-        return ret;
+        return permissionCache.getEntries(path);
     }
 
     private final class EntryIterator extends 
AbstractLazyIterator<PermissionEntry> {

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionStore.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionStore.java?rev=1830843&r1=1830842&r2=1830843&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionStore.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionStore.java
 Thu May  3 16:51:10 2018
@@ -20,7 +20,6 @@ import java.util.Collection;
 
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
 
 import org.apache.jackrabbit.oak.api.Root;
 
@@ -33,22 +32,21 @@ interface PermissionStore {
     long DYNAMIC_ALL_BITS = -1;
 
     /**
-     * Loads the permission entries for the given principal and path. if the 
given {@code entries} is {@code null}, it
-     * will be created automatically if needed. If a {@code entries} is given, 
it will reuse it and the same object is
-     * returned. If no entries can be found for the given principal or path, 
{@code null} is returned.
+     * Loads the permission entries for the given principal and path. If no
+     * entries can be found for the given principal or path, {@code null} is 
returned.
      *
-     * @param entries the permission entries or {@code null}
      * @param principalName name of the principal
      * @param path access controlled path.
      * @return the given {@code entries}, a new collection or {@code null}
      */
     @CheckForNull
-    Collection<PermissionEntry> load(@Nullable Collection<PermissionEntry> 
entries, @Nonnull String principalName, @Nonnull String path);
+    Collection<PermissionEntry> load(@Nonnull String principalName, @Nonnull 
String path);
 
     @Nonnull
     PrincipalPermissionEntries load(@Nonnull String principalName);
 
-    long getNumEntries(@Nonnull String principalName, long max);
+    @Nonnull
+    NumEntries getNumEntries(@Nonnull String principalName, long max);
 
     void flush(@Nonnull Root root);
 

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionStoreEditor.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionStoreEditor.java?rev=1830843&r1=1830842&r2=1830843&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionStoreEditor.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionStoreEditor.java
 Thu May  3 16:51:10 2018
@@ -92,7 +92,7 @@ final class PermissionStoreEditor implem
                         new AcEntry(ace, accessControlledPath, index, isAllow, 
privilegeBits, restrictions);
                 List<AcEntry> list = entries.get(entry.principalName);
                 if (list == null) {
-                    list = new ArrayList<AcEntry>();
+                    list = new ArrayList<>();
                     entries.put(entry.principalName, list);
                 }
                 list.add(entry);
@@ -117,6 +117,7 @@ final class PermissionStoreEditor implem
         for (String principalName : entries.keySet()) {
             if (permissionRoot.hasChildNode(principalName)) {
                 NodeBuilder principalRoot = 
permissionRoot.getChildNode(principalName);
+                boolean removed = false;
 
                 // find the ACL node that for this path and principal
                 NodeBuilder parent = principalRoot.getChildNode(nodeName);
@@ -139,11 +140,13 @@ final class PermissionStoreEditor implem
                             newParent.setChildNode(childName, 
child.getNodeState());
                         }
                     }
+
                     if (newParent != null) {
-                        // replace the 'parent', which needs to be removed
+                        // replace the 'parent', which got removed
                         principalRoot.setChildNode(nodeName, 
newParent.getNodeState());
+                        removed = true;
                     } else {
-                        parent.remove();
+                        removed = parent.remove();
                     }
                 } else {
                     // check if any of the child nodes match
@@ -153,10 +156,13 @@ final class PermissionStoreEditor implem
                         }
                         NodeBuilder child = parent.getChildNode(childName);
                         if (PermissionUtil.checkACLPath(child, 
accessControlledPath)) {
-                            child.remove();
+                            removed = child.remove();
                         }
                     }
                 }
+                if (removed) {
+                    updateNumEntries(principalName, principalRoot, -1);
+                }
             } else {
                 log.error("Unable to remove permission entry {}: Principal 
root missing.", this);
             }
@@ -210,6 +216,10 @@ final class PermissionStoreEditor implem
                 parent.setProperty(REP_ACCESS_CONTROLLED_PATH, 
accessControlledPath);
             }
             updateEntries(parent, entry.getValue());
+
+            if (parent.isNew()) {
+                updateNumEntries(principalName, principalRoot, +1);
+            }
         }
     }
 
@@ -225,6 +235,21 @@ final class PermissionStoreEditor implem
         }
     }
 
+    private static void updateNumEntries(@Nonnull String principalName, 
@Nonnull NodeBuilder principalRoot, int cnt) {
+        PropertyState ps = principalRoot.getProperty(REP_NUM_PERMISSIONS);
+        long numEntries = ((ps == null) ? 0 : ps.getValue(Type.LONG)) + cnt;
+        if (ps == null && !principalRoot.isNew()) {
+            // existing principal root that doesn't have the rep:numEntries set
+            return;
+        } else if  (numEntries < 0) {
+            // numEntries unexpectedly turned negative
+            log.error("NumEntries counter for principal '"+principalName+"' 
turned negative -> removing 'rep:numPermissions' property.");
+            principalRoot.removeProperty(REP_NUM_PERMISSIONS);
+        } else {
+            principalRoot.setProperty(REP_NUM_PERMISSIONS, numEntries, 
Type.LONG);
+        }
+    }
+
     private final class JcrAllAcEntry extends AcEntry {
 
         private JcrAllAcEntry(@Nonnull NodeState node,
@@ -251,7 +276,7 @@ final class PermissionStoreEditor implem
         private final int index;
         private int hashCode = -1;
 
-        private AcEntry(@Nonnull NodeState node, @Nonnull String 
accessControlledPath, int index,
+        AcEntry(@Nonnull NodeState node, @Nonnull String accessControlledPath, 
int index,
                         boolean isAllow, @Nonnull PrivilegeBits privilegeBits,
                         @Nonnull Set<Restriction> restrictions) {
             this.accessControlledPath = accessControlledPath;

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionStoreImpl.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionStoreImpl.java?rev=1830843&r1=1830842&r2=1830843&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionStoreImpl.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionStoreImpl.java
 Thu May  3 16:51:10 2018
@@ -20,21 +20,19 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.TreeSet;
-
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
 
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.tree.TreeUtil;
 import 
org.apache.jackrabbit.oak.spi.security.authorization.permission.PermissionConstants;
 import 
org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionProvider;
 import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBits;
 import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBitsProvider;
 import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
-import org.apache.jackrabbit.oak.plugins.tree.TreeUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -52,7 +50,7 @@ class PermissionStoreImpl implements Per
 
     private final RestrictionProvider restrictionProvider;
 
-    private final Map<String, Tree> principalTreeMap = new HashMap<String, 
Tree>();
+    private final Map<String, Tree> principalTreeMap = new HashMap<>();
 
     private Tree permissionsTree;
     private PrivilegeBits allBits;
@@ -75,21 +73,24 @@ class PermissionStoreImpl implements Per
     }
 
     //----------------------------------------------------< PermissionStore 
>---
-    @Override
     @CheckForNull
-    public Collection<PermissionEntry> load(@Nullable 
Collection<PermissionEntry> entries, @Nonnull String principalName, @Nonnull 
String path) {
+    @Override
+    public Collection<PermissionEntry> load(@Nonnull String principalName, 
@Nonnull String path) {
         Tree principalRoot = getPrincipalRoot(principalName);
+        Collection<PermissionEntry> entries = null;
         if (principalRoot != null) {
             String name = PermissionUtil.getEntryName(path);
             if (principalRoot.hasChild(name)) {
                 Tree child = principalRoot.getChild(name);
                 if (PermissionUtil.checkACLPath(child, path)) {
-                    entries = loadPermissionEntries(path, entries, child);
+                    entries = loadPermissionEntries(path, child);
                 } else {
-                    // check for child node
+                    // check for child node : there may at most be one child 
for
+                    // the given path.
                     for (Tree node : child.getChildren()) {
                         if (PermissionUtil.checkACLPath(node, path)) {
-                            entries = loadPermissionEntries(path, entries, 
node);
+                            entries = loadPermissionEntries(path, node);
+                            break;
                         }
                     }
                 }
@@ -98,11 +99,21 @@ class PermissionStoreImpl implements Per
         return entries == null || entries.isEmpty() ? null : entries;
     }
 
+    @Nonnull
     @Override
-    public long getNumEntries(@Nonnull String principalName, long max) {
-        // we ignore the hash-collisions here
+    public NumEntries getNumEntries(@Nonnull String principalName, long max) {
         Tree tree = getPrincipalRoot(principalName);
-        return tree == null ? 0 : tree.getChildrenCount(max);
+        if (tree == null) {
+            return NumEntries.ZERO;
+        } else {
+            // if rep:numPermissions is present it contains the exact number of
+            // access controlled nodes for the given principal name.
+            // if this property is missing (backward compat) we use the old
+            // mechanism and use child-cnt with a max value to get a rough idea
+            // about the magnitude (note: this approximation ignores the 
hash-collisions)
+            long l = TreeUtil.getLong(tree, REP_NUM_PERMISSIONS, -1);
+            return (l >= 0) ? NumEntries.valueOf(l, true) : 
NumEntries.valueOf(tree.getChildrenCount(max), false);
+        }
     }
 
     @Override
@@ -113,7 +124,7 @@ class PermissionStoreImpl implements Per
         Tree principalRoot = getPrincipalRoot(principalName);
         if (principalRoot != null) {
             for (Tree entryTree : principalRoot.getChildren()) {
-                loadPermissionEntries(entryTree, ret.getEntries());
+                loadPermissionEntries(entryTree, ret);
             }
         }
         ret.setFullyLoaded(true);
@@ -140,17 +151,17 @@ class PermissionStoreImpl implements Per
     }
 
     private void loadPermissionEntries(@Nonnull Tree tree,
-                                       @Nonnull Map<String, 
Collection<PermissionEntry>> pathEntryMap) {
+                                       @Nonnull PrincipalPermissionEntries 
principalPermissionEntries) {
         String path = TreeUtil.getString(tree, 
PermissionConstants.REP_ACCESS_CONTROLLED_PATH);
         if (path != null) {
-            Collection<PermissionEntry> entries = pathEntryMap.get(path);
+            Collection<PermissionEntry> entries = 
principalPermissionEntries.getEntriesByPath(path);
             if (entries == null) {
-                entries = new TreeSet<PermissionEntry>();
-                pathEntryMap.put(path, entries);
+                entries = new TreeSet<>();
+                principalPermissionEntries.putEntriesByPath(path, entries);
             }
             for (Tree child : tree.getChildren()) {
                 if (child.getName().charAt(0) == 'c') {
-                    loadPermissionEntries(child, pathEntryMap);
+                    loadPermissionEntries(child, principalPermissionEntries);
                 } else {
                     entries.add(createPermissionEntry(path, child));
                 }
@@ -160,15 +171,11 @@ class PermissionStoreImpl implements Per
         }
     }
 
-    @CheckForNull
     private Collection<PermissionEntry> loadPermissionEntries(@Nonnull String 
path,
-                                                              @Nullable 
Collection<PermissionEntry> ret,
-                                                              @Nonnull Tree 
tree) {
+                                       @Nonnull Tree tree) {
+        Collection<PermissionEntry> ret = new TreeSet<>();
         for (Tree ace : tree.getChildren()) {
             if (ace.getName().charAt(0) != 'c') {
-                if (ret == null) {
-                    ret = new TreeSet<PermissionEntry>();
-                }
                 ret.add(createPermissionEntry(path, ace));
             }
         }

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PrincipalPermissionEntries.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PrincipalPermissionEntries.java?rev=1830843&r1=1830842&r2=1830843&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PrincipalPermissionEntries.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PrincipalPermissionEntries.java
 Thu May  3 16:51:10 2018
@@ -19,6 +19,7 @@ package org.apache.jackrabbit.oak.securi
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
+import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 
 /**
@@ -26,6 +27,8 @@ import javax.annotation.Nonnull;
  */
 class PrincipalPermissionEntries {
 
+    private final long expectedSize;
+
     /**
      * indicating if all entries were loaded.
      */
@@ -34,9 +37,14 @@ class PrincipalPermissionEntries {
     /**
      * map of permission entries, accessed by path
      */
-    private Map<String, Collection<PermissionEntry>> entries = new 
HashMap<String, Collection<PermissionEntry>>();
+    private Map<String, Collection<PermissionEntry>> entries = new HashMap<>();
 
     PrincipalPermissionEntries() {
+        this(Long.MAX_VALUE);
+    }
+
+    PrincipalPermissionEntries(long expectedSize) {
+        this.expectedSize = expectedSize;
     }
 
     long getSize() {
@@ -55,4 +63,21 @@ class PrincipalPermissionEntries {
     Map<String, Collection<PermissionEntry>> getEntries() {
         return entries;
     }
+
+    @CheckForNull
+    Collection<PermissionEntry> getEntriesByPath(@Nonnull String path) {
+        return entries.get(path);
+    }
+
+    void putEntriesByPath(@Nonnull String path, @Nonnull 
Collection<PermissionEntry> pathEntries) {
+        entries.put(path, pathEntries);
+        if (entries.size() >= expectedSize) {
+            setFullyLoaded(true);
+        }
+    }
+
+    void putAllEntries(@Nonnull Map<String, Collection<PermissionEntry>> 
allEntries) {
+        entries.putAll(allEntries);
+        setFullyLoaded(true);
+    }
 }
\ No newline at end of file

Added: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/EmptyPermissionCacheTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/EmptyPermissionCacheTest.java?rev=1830843&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/EmptyPermissionCacheTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/EmptyPermissionCacheTest.java
 Thu May  3 16:51:10 2018
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.security.authorization.permission;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+public class EmptyPermissionCacheTest {
+
+    private PermissionCache empty;
+
+    @Before
+    public void before() {
+        PermissionCacheBuilder builder = new 
PermissionCacheBuilder(Mockito.mock(PermissionStore.class));
+        builder.init(ImmutableSet.of(), Long.MAX_VALUE);
+        empty = builder.build();
+    }
+
+    @Test
+    public void testGetEntriesByPath() {
+        assertTrue(empty.getEntries("/path").isEmpty());
+    }
+
+    @Test
+    public void testGetEntriesByTree() {
+        Tree tree = Mockito.mock(Tree.class);
+        when(tree.getPath()).thenReturn("/path");
+        assertTrue(empty.getEntries(tree).isEmpty());
+    }
+}
\ No newline at end of file

Propchange: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/EmptyPermissionCacheTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/MountPermissionStoreTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/MountPermissionStoreTest.java?rev=1830843&r1=1830842&r2=1830843&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/MountPermissionStoreTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/MountPermissionStoreTest.java
 Thu May  3 16:51:10 2018
@@ -46,6 +46,8 @@ import static org.junit.Assert.assertEqu
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.when;
 
 public class MountPermissionStoreTest extends AbstractSecurityTest {
@@ -114,14 +116,14 @@ public class MountPermissionStoreTest ex
 
     @Test
     public void testLoadByAccessControlledPath() {
-        Collection<PermissionEntry> entries = permissionStore.load(null, 
EveryonePrincipal.NAME, CONTENT_PATH);
+        Collection<PermissionEntry> entries = 
permissionStore.load(EveryonePrincipal.NAME, CONTENT_PATH);
         assertNotNull(entries);
         assertEquals(1, entries.size());
     }
 
     @Test
     public void testLoadByNonAccessControlledPath() {
-        Collection<PermissionEntry> entries = permissionStore.load(null, 
EveryonePrincipal.NAME, TEST_PATH);
+        Collection<PermissionEntry> entries = 
permissionStore.load(EveryonePrincipal.NAME, TEST_PATH);
         assertNull(entries);
     }
 
@@ -143,22 +145,36 @@ public class MountPermissionStoreTest ex
 
     @Test
     public void testGetNumEntries() {
-        assertEquals(2, permissionStore.getNumEntries(EveryonePrincipal.NAME, 
10));
+        assertEquals(2, permissionStore.getNumEntries(EveryonePrincipal.NAME, 
10).size);
     }
 
+    @Test
+    public void testGetNumEntriesMaxReachedExact() throws Exception {
+        PermissionStoreImpl mock = insertMockStore();
+        when(mock.getNumEntries(anyString(), 
anyLong())).thenReturn(NumEntries.valueOf(2, true));
+
+        NumEntries ne = permissionStore.getNumEntries(EveryonePrincipal.NAME, 
10);
+        assertEquals(NumEntries.valueOf(4, true), ne);
+
+        ne = permissionStore.getNumEntries(EveryonePrincipal.NAME, 2);
+        assertEquals(NumEntries.valueOf(4, true), ne);
+    }
 
     @Test
-    public void testGetNumEntriesMaxReached() throws Exception {
+    public void testGetNumEntriesMaxReachedNotExact() throws Exception {
         PermissionStoreImpl mock = insertMockStore();
-        when(mock.getNumEntries(EveryonePrincipal.NAME, 
Long.valueOf(10))).thenReturn(Long.valueOf(2));
+        when(mock.getNumEntries(anyString(), 
anyLong())).thenReturn(NumEntries.valueOf(2, false));
+
+        NumEntries ne = permissionStore.getNumEntries(EveryonePrincipal.NAME, 
10);
+        assertEquals(NumEntries.valueOf(4, false), ne);
 
-        assertEquals(4, permissionStore.getNumEntries(EveryonePrincipal.NAME, 
10));
-        assertEquals(2, permissionStore.getNumEntries(EveryonePrincipal.NAME, 
2));
+        ne = permissionStore.getNumEntries(EveryonePrincipal.NAME, 2);
+        assertEquals(NumEntries.valueOf(2, false), ne);
     }
 
     @Test
     public void testGetNumEntriesUnknownPrincipalName() {
-        assertEquals(0, permissionStore.getNumEntries("unknown", 10));
+        assertEquals(0, permissionStore.getNumEntries("unknown", 10).size);
     }
 
     @Test
@@ -176,7 +192,7 @@ public class MountPermissionStoreTest ex
 
         PermissionStoreImpl mock = Mockito.mock(PermissionStoreImpl.class);
         List<PermissionStoreImpl> stores = (List<PermissionStoreImpl>) 
f.get(permissionStore);
-        stores.add(mock);
+        stores.add(0, mock);
         return mock;
     }
 }
\ No newline at end of file

Added: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/NumEntriesTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/NumEntriesTest.java?rev=1830843&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/NumEntriesTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/NumEntriesTest.java
 Thu May  3 16:51:10 2018
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.security.authorization.permission;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+public class NumEntriesTest {
+
+    @Test
+    public void testZero() {
+        assertTrue(NumEntries.ZERO.isExact);
+        assertEquals(0, NumEntries.ZERO.size);
+    }
+
+    @Test
+    public void testValueOfExactZero() {
+        assertSame(NumEntries.ZERO, NumEntries.valueOf(0, true));
+    }
+
+    @Test
+    public void testValueOfNotExactZero() {
+        NumEntries ne = NumEntries.valueOf(0, false);
+        assertSame(NumEntries.ZERO, ne);
+    }
+
+    @Test
+    public void testValueOfExact() {
+        NumEntries ne = NumEntries.valueOf(25, true);
+        assertTrue(ne.isExact);
+        assertEquals(25, ne.size);
+    }
+
+    @Test
+    public void testValueOfNotExact() {
+        NumEntries ne = NumEntries.valueOf(25, false);
+        assertFalse(ne.isExact);
+        assertEquals(25, ne.size);
+    }
+
+    @Test
+    public void testEquals() {
+        assertEquals(NumEntries.ZERO, NumEntries.ZERO);
+        NumEntries ne1True = NumEntries.valueOf(1, true);
+        NumEntries ne2True = NumEntries.valueOf(2, true);
+        NumEntries ne1False = NumEntries.valueOf(1, false);
+
+        assertEquals(ne1True, ne1True);
+        assertNotEquals(ne1False, ne1True);
+        assertNotEquals(ne2True, ne1True);
+
+        assertFalse(ne1True.equals(null));
+        assertFalse(ne1True.equals(new Object()));
+    }
+
+    @Test
+    public void testHashCode() {
+        assertEquals(NumEntries.ZERO.hashCode(), NumEntries.ZERO.hashCode());
+        NumEntries ne1True = NumEntries.valueOf(1, true);
+        NumEntries ne1False = NumEntries.valueOf(1, false);
+
+        assertEquals(ne1True.hashCode(), ne1True.hashCode());
+        assertNotEquals(ne1False.hashCode(), ne1True.hashCode());
+    }
+}
\ No newline at end of file

Propchange: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/NumEntriesTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionCacheBuilderTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionCacheBuilderTest.java?rev=1830843&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionCacheBuilderTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionCacheBuilderTest.java
 Thu May  3 16:51:10 2018
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.security.authorization.permission;
+
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import 
org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionPattern;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBits;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.when;
+
+public class PermissionCacheBuilderTest {
+
+    private static final String EMPTY_CLASS_NAME = 
"org.apache.jackrabbit.oak.security.authorization.permission.PermissionCacheBuilder$EmptyCache";
+    private static final String SIMPLE_CLASS_NAME = 
"org.apache.jackrabbit.oak.security.authorization.permission.PermissionCacheBuilder$PathEntryMapCache";
+    private static final String DEFAULT_CLASS_NAME = 
"org.apache.jackrabbit.oak.security.authorization.permission.PermissionCacheBuilder$DefaultPermissionCache";
+
+    private PermissionStore store;
+    private PermissionCacheBuilder permissionCacheBuilder;
+
+    @Before
+    public void before() {
+        store = Mockito.mock(PermissionStore.class);
+        permissionCacheBuilder = new PermissionCacheBuilder(store);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testBuildBeforeInitialized() {
+        permissionCacheBuilder.build();
+    }
+
+    @Test
+    public void testBuildForEmptyPrincipals() {
+        assertTrue(permissionCacheBuilder.init(ImmutableSet.of(), 
Long.MAX_VALUE));
+        permissionCacheBuilder.init(ImmutableSet.of(), Long.MAX_VALUE);
+        PermissionCache cache = permissionCacheBuilder.build();
+        assertEquals(EMPTY_CLASS_NAME, cache.getClass().getName());
+    }
+
+    @Test
+    public void testBuildNoExistingEntries() throws Exception {
+        when(store.getNumEntries(anyString(), 
anyLong())).thenReturn(NumEntries.ZERO);
+        when(store.load(anyString())).thenReturn(new 
PrincipalPermissionEntries(0));
+
+        Set<String> principalNames = Sets.newHashSet("noEntries", 
"noEntries2", "noEntries3");
+
+        assertTrue(permissionCacheBuilder.init(principalNames, 
Long.MAX_VALUE));
+        PermissionCache cache = permissionCacheBuilder.build();
+        assertEquals(EMPTY_CLASS_NAME, cache.getClass().getName());
+    }
+
+    @Test
+    public void testBuildFewEntriesSamePath() throws Exception {
+        PrincipalPermissionEntries ppeA = new PrincipalPermissionEntries(1);
+        ppeA.putEntriesByPath("/path", ImmutableSet.of(new 
PermissionEntry("/path", false, 0, 
PrivilegeBits.BUILT_IN.get(PrivilegeBits.REP_READ_NODES), 
RestrictionPattern.EMPTY)));
+
+        PrincipalPermissionEntries ppeB = new PrincipalPermissionEntries(1);
+        ppeB.putEntriesByPath("/path", ImmutableSet.of(new 
PermissionEntry("/path", false, 1, 
PrivilegeBits.BUILT_IN.get(PrivilegeBits.REP_READ_NODES), 
RestrictionPattern.EMPTY)));
+
+        when(store.load("a")).thenReturn(ppeA);
+        when(store.load("b")).thenReturn(ppeB);
+        when(store.getNumEntries(anyString(), 
anyLong())).thenReturn(NumEntries.valueOf(1, true));
+
+        Set<String> principalNames = Sets.newHashSet("a", "b");
+        assertFalse(permissionCacheBuilder.init(principalNames, 
Long.MAX_VALUE));
+
+        PermissionCache cache = permissionCacheBuilder.build();
+        assertEquals(SIMPLE_CLASS_NAME, cache.getClass().getName());
+    }
+
+    @Test
+    public void testBuildFewEntriesDifferentPaths() throws Exception {
+        PrincipalPermissionEntries ppeA = new PrincipalPermissionEntries(1);
+        ppeA.putEntriesByPath("/path", ImmutableSet.of(new 
PermissionEntry("/path", false, 0, 
PrivilegeBits.BUILT_IN.get(PrivilegeBits.REP_READ_NODES), 
RestrictionPattern.EMPTY)));
+
+        PrincipalPermissionEntries ppeB = new PrincipalPermissionEntries(1);
+        ppeB.putEntriesByPath("/path", ImmutableSet.of(new 
PermissionEntry("/path", false, 1, 
PrivilegeBits.BUILT_IN.get(PrivilegeBits.REP_READ_NODES), 
RestrictionPattern.EMPTY)));
+
+        when(store.load("a")).thenReturn(ppeA);
+        when(store.load("b")).thenReturn(ppeB);
+        when(store.getNumEntries(anyString(), 
anyLong())).thenReturn(NumEntries.valueOf(1, true));
+
+        Set<String> principalNames = Sets.newHashSet("a", "b");
+        assertFalse(permissionCacheBuilder.init(principalNames, 
Long.MAX_VALUE));
+
+        PermissionCache cache = permissionCacheBuilder.build();
+        assertEquals(SIMPLE_CLASS_NAME, cache.getClass().getName());
+    }
+
+    @Test
+    public void testNoEntriesNonExactCnt() throws Exception {
+        when(store.load("a")).thenReturn(new PrincipalPermissionEntries());
+        when(store.load("b")).thenReturn(new PrincipalPermissionEntries());
+        when(store.getNumEntries(anyString(), 
anyLong())).thenReturn(NumEntries.valueOf(1, false));
+
+        Set<String> principalNames = Sets.newHashSet("a", "b");
+        assertFalse(permissionCacheBuilder.init(principalNames, 
Long.MAX_VALUE));
+
+        PermissionCache cache = permissionCacheBuilder.build();
+        assertEquals(EMPTY_CLASS_NAME, cache.getClass().getName());
+    }
+
+    @Test
+    public void testBuildMaxEntriesReached() throws Exception {
+        PrincipalPermissionEntries ppeA = new PrincipalPermissionEntries(1);
+        ppeA.putEntriesByPath("/path1", ImmutableSet.of(new 
PermissionEntry("/path1", false, 0, 
PrivilegeBits.BUILT_IN.get(PrivilegeBits.REP_READ_NODES), 
RestrictionPattern.EMPTY)));
+
+        PrincipalPermissionEntries ppeB = new PrincipalPermissionEntries(1);
+        ppeA.putEntriesByPath("/path2", ImmutableSet.of(new 
PermissionEntry("/path2", false, 0, 
PrivilegeBits.BUILT_IN.get(PrivilegeBits.REP_READ_NODES), 
RestrictionPattern.EMPTY)));
+
+        when(store.load("a")).thenReturn(ppeA);
+        when(store.load("b")).thenReturn(ppeB);
+        when(store.getNumEntries(anyString(), 
anyLong())).thenReturn(NumEntries.valueOf(1, true));
+
+        Set<String> principalNames = Sets.newHashSet("a", "b");
+        long maxSize = 1;
+        assertFalse(permissionCacheBuilder.init(principalNames, maxSize));
+
+        PermissionCache cache = permissionCacheBuilder.build();
+        assertEquals(DEFAULT_CLASS_NAME, cache.getClass().getName());
+    }
+}
\ No newline at end of file

Propchange: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionCacheBuilderTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionEntryCacheTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionEntryCacheTest.java?rev=1830843&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionEntryCacheTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionEntryCacheTest.java
 Thu May  3 16:51:10 2018
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.security.authorization.permission;
+
+import java.lang.reflect.Field;
+import java.util.Collection;
+import java.util.Map;
+import java.util.TreeSet;
+import javax.annotation.Nonnull;
+
+import com.google.common.collect.Sets;
+import 
org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionPattern;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBits;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+public class PermissionEntryCacheTest {
+
+    private PermissionEntryCache cache = new PermissionEntryCache();
+
+    private PermissionEntry permissionEntry;
+    private PrincipalPermissionEntries ppe;
+    private PermissionStore store;
+
+    @Before
+    public void before() {
+        permissionEntry = new PermissionEntry("/path", true, 0, 
PrivilegeBits.BUILT_IN.get(PrivilegeBits.JCR_READ), RestrictionPattern.EMPTY);
+        ppe = new PrincipalPermissionEntries();
+        ppe.putEntriesByPath("/path", Sets.newHashSet(permissionEntry));
+
+        store = Mockito.mock(PermissionStore.class);
+
+    }
+
+    private PrincipalPermissionEntries getPrincipalPermissionEntries(boolean 
fullyLoaded) {
+        ppe.setFullyLoaded(fullyLoaded);
+        return ppe;
+    }
+
+    @Test
+    public void testMissingInit() throws Exception {
+        Map<String, PrincipalPermissionEntries> entries = 
inspectEntries(cache);
+        assertNotNull(entries);
+        assertTrue(entries.isEmpty());
+    }
+
+    @Test
+    public void testInit() throws Exception {
+        cache.init("a", 5);
+
+        PrincipalPermissionEntries entries = inspectEntries(cache, "a");
+        assertNotNull(entries);
+        assertFalse(entries.isFullyLoaded());
+        assertEquals(0, entries.getSize());
+
+        entries = inspectEntries(cache, "notInitialized");
+        assertNull(entries);
+    }
+
+    @Test
+    public void testLoadMissingInit() throws Exception {
+        PrincipalPermissionEntries ppeA = getPrincipalPermissionEntries(true);
+
+        when(store.load("a")).thenReturn(ppeA);
+
+        Collection<PermissionEntry> result = new TreeSet();
+        cache.load(store, result, "a", "/path");
+
+        assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void testLoadNotComplete() throws Exception {
+        cache.init("a", Long.MAX_VALUE);
+
+        Collection<PermissionEntry> entries = Sets.newHashSet(permissionEntry);
+        when(store.load("a", "/path")).thenReturn(entries);
+
+        Collection<PermissionEntry> result = Sets.newHashSet();
+        cache.load(store, result, "a", "/path");
+
+        assertEquals(entries, result);
+
+        PrincipalPermissionEntries inspectedEntries = inspectEntries(cache, 
"a");
+        assertFalse(inspectedEntries.isFullyLoaded());
+
+        // requesting the entries again must NOT hit the store
+        when(store.load("a", "/path")).thenThrow(IllegalStateException.class);
+
+        result.clear();
+        cache.load(store, result, "a", "/path");
+
+        assertEquals(entries, result);
+    }
+
+    @Test
+    public void testLoadCompleted() throws Exception {
+        cache.init("a", 1);
+
+        Collection<PermissionEntry> entries = Sets.newHashSet(permissionEntry);
+        when(store.load("a", "/path")).thenReturn(entries);
+
+        Collection<PermissionEntry> result = Sets.newHashSet();
+        cache.load(store, result, "a", "/path");
+
+        assertEquals(entries, result);
+
+        PrincipalPermissionEntries inspectedEntries = inspectEntries(cache, 
"a");
+        assertTrue(inspectedEntries.isFullyLoaded());
+
+        // requesting the entries again must NOT hit the store
+        when(store.load("a", "/path")).thenThrow(IllegalStateException.class);
+
+        result.clear();
+        cache.load(store, result, "a", "/path");
+
+        assertEquals(entries, result);
+    }
+
+    @Test
+    public void testLoadNonExistingNotComplete() throws Exception {
+        cache.init("a", Long.MAX_VALUE);
+
+        when(store.load("a", "/path")).thenReturn(null);
+
+        Collection<PermissionEntry> result = Sets.newHashSet();
+        cache.load(store, result, "a", "/path");
+
+        assertTrue(result.isEmpty());
+
+        PrincipalPermissionEntries inspectedEntries = inspectEntries(cache, 
"a");
+        assertFalse(inspectedEntries.isFullyLoaded());
+
+        // requesting the entries again must NOT hit the store
+        when(store.load("a", "/path")).thenThrow(IllegalStateException.class);
+
+        result.clear();
+        cache.load(store, result, "a", "/path");
+
+        assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void testLoadNonExistingCompleted() throws Exception {
+        cache.init("a", 0);
+        when(store.load("a", "/path")).thenReturn(null);
+
+        Collection<PermissionEntry> result = Sets.newHashSet();
+        cache.load(store, result, "a", "/path");
+
+        assertTrue(result.isEmpty());
+
+        PrincipalPermissionEntries inspectedEntries = inspectEntries(cache, 
"a");
+        assertTrue(inspectedEntries.isFullyLoaded());
+
+        // requesting the entries again must NOT hit the store
+        when(store.load("a", "/path")).thenThrow(IllegalStateException.class);
+
+        result.clear();
+        cache.load(store, result, "a", "/path");
+
+        assertTrue(result.isEmpty());
+    }
+
+
+    @Test
+    public void testGetFullyLoadedEntries() throws Exception {
+        PrincipalPermissionEntries ppeA = getPrincipalPermissionEntries(true);
+
+        when(store.load("a")).thenReturn(ppeA);
+
+        PrincipalPermissionEntries entries = 
cache.getFullyLoadedEntries(store, "a");
+        assertSame(ppeA, entries);
+
+        PrincipalPermissionEntries inspectedEntries = inspectEntries(cache, 
"a");
+        assertSame(ppeA, inspectedEntries);
+
+        // requesting the entries again must NOT hit the store
+        when(store.load("a")).thenThrow(IllegalStateException.class);
+        entries = cache.getFullyLoadedEntries(store, "a");
+        assertSame(ppeA, entries);
+    }
+
+    private static PrincipalPermissionEntries inspectEntries(@Nonnull 
PermissionEntryCache cache, @Nonnull String principalName) throws Exception {
+        Map<String, PrincipalPermissionEntries> entries = 
inspectEntries(cache);
+        return entries.get(principalName);
+    }
+
+    private static Map<String, PrincipalPermissionEntries> 
inspectEntries(@Nonnull PermissionEntryCache cache) throws Exception {
+        Field f = PermissionEntryCache.class.getDeclaredField("entries");
+        f.setAccessible(true);
+
+        return (Map<String, PrincipalPermissionEntries>) f.get(cache);
+    }
+}
\ No newline at end of file

Propchange: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionEntryCacheTest.java
------------------------------------------------------------------------------
    svn:eol-style = native


Reply via email to