Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CachingEntryCollector.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CachingEntryCollector.java	(revision 1352607)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CachingEntryCollector.java	(working copy)
@@ -16,6 +16,13 @@
  */
 package org.apache.jackrabbit.core.security.authorization.acl;
 
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.jcr.RepositoryException;
+
 import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.cache.GrowingLRUMap;
@@ -24,9 +31,6 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.jcr.RepositoryException;
-import java.util.Map;
-
 /**
  * <code>CachingEntryCollector</code> extends <code>EntryCollector</code> by
  * keeping a cache of ACEs per access controlled nodeId.
@@ -45,9 +49,13 @@
      */
     private final EntryCache cache;
 
+    private Map<NodeId, List<FutureEntries>> sems = new ConcurrentHashMap<NodeId, List<FutureEntries>>();
+
+    private final String strategy;
+    
     /**
      * Create a new instance.
-     * 
+     *
      * @param systemSession A system session.
      * @param rootID The id of the root node.
      * @throws RepositoryException If an error occurs.
@@ -55,6 +63,9 @@
     CachingEntryCollector(SessionImpl systemSession, NodeId rootID) throws RepositoryException {
         super(systemSession, rootID);
         cache = new EntryCache();
+
+        String propname = "org.apache.jackrabbit.core.security.authorization.acl.CachingEntryCollector.strategy";
+        strategy = System.getProperty(propname, "T");
     }
 
     @Override
@@ -92,15 +103,36 @@
         return entries;
     }
 
+    private Entries updateCache(NodeImpl node) throws RepositoryException {
+        if ("T".equals(strategy)) {
+            return throttledUpdateCache(node);
+        } else if ("S".equals(strategy)) {
+            return synchronizedUpdateCache(node);
+        } else if ("P".equals(strategy)) {
+            return parallelUpdateCache(node);
+        } else {
+            // panic
+            throw new RuntimeException("invalid value for updateCacheStrategy: " + strategy);
+        }
+    }
+
+    synchronized private Entries synchronizedUpdateCache(NodeImpl node) throws RepositoryException {
+        return internalUpdateCache(node);
+    }
+
+    private Entries parallelUpdateCache(NodeImpl node) throws RepositoryException {
+        return internalUpdateCache(node);
+    }
+
     /**
      * Read the entries defined for the specified node and update the cache
      * accordingly.
-     * 
+     *
      * @param node The target node
      * @return The list of entries present on the specified node or an empty list.
      * @throws RepositoryException If an error occurs.
      */
-    private Entries updateCache(NodeImpl node) throws RepositoryException {
+    private Entries internalUpdateCache(NodeImpl node) throws RepositoryException {
         Entries entries = super.getEntries(node);
         if (!entries.isEmpty()) {
             // adjust the 'nextId' to point to the next access controlled
@@ -112,6 +144,61 @@
     }
 
     /**
+     *
+     * @param node
+     * @return
+     * @throws RepositoryException
+     */
+    private Entries throttledUpdateCache(NodeImpl node) throws RepositoryException {
+        NodeId id = node.getNodeId();
+        List<FutureEntries> l = null;
+        FutureEntries fe = null;
+
+        synchronized(sems) {
+            // obtain a list of future entries from the map
+            l = sems.get(id);
+            if (l == null) {
+                // found -- create empty list for other callers to add to
+                l = new LinkedList<FutureEntries>();
+                sems.put(id, l);
+            } else {
+                // found -- add a new entry
+                fe = new FutureEntries();
+                l.add(fe);
+            }
+        }
+
+        if (fe != null) {
+            // we have created a FutureEntries object, so use it
+            return fe.get();
+        } else {
+            // otherwise obtain result and when done notify waiting FutureEntries
+            try {
+                Entries e = internalUpdateCache(node);
+                synchronized(sems) {
+                    for (FutureEntries f : l) {
+                        f.setResult(e);
+                    }
+                    sems.remove(id);
+                }
+                return e;
+            } catch (Throwable problem) {
+                synchronized(sems) {
+                    for (FutureEntries f : l) {
+                        f.setProblem(problem);
+                    }
+                    sems.remove(id);
+                }
+                if (problem instanceof RepositoryException) {
+                    throw (RepositoryException)problem;
+                } else {
+                    throw new RuntimeException(problem);
+                }
+            }
+        }
+    }
+
+    /**
      * Find the next access control ancestor in the hierarchy 'null' indicates
      * that there is no ac-controlled ancestor.
      *
@@ -153,7 +240,7 @@
     /**
      * Evaluates if the given node is access controlled and holds a non-empty
      * rep:policy child node.
-     * 
+     *
      * @param n The node to test.
      * @return true if the specified node is access controlled and holds a
      * non-empty policy child node.
@@ -207,6 +294,44 @@
     }
 
     //--------------------------------------------------------------------------
+    // waits for a result to become ready
+    private class FutureEntries {
+
+        private boolean ready = false;
+        private Entries result = null;
+        private Throwable problem = null;
+
+        synchronized public Entries get() throws RepositoryException {
+            while (!ready) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                }
+            }
+            if (problem != null) {
+                if (problem instanceof RepositoryException) {
+                    throw new RepositoryException(problem);
+                }
+                else {
+                    throw new RuntimeException(problem);
+                }
+            }
+            return result;
+        }
+
+        synchronized public void setResult(Entries e) {
+            result = e;
+            ready = true;
+            notifyAll();
+        }
+
+        synchronized public void setProblem(Throwable t) {
+            problem = t;
+            ready = true;
+            notifyAll();
+        }
+    }
+
     /**
      * A cache to lookup the ACEs defined on a given (access controlled)
      * node. The internal map uses the ID of the node as key while the value
